bfabric-web-apps 0.1.3__tar.gz → 0.1.5__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.
- {bfabric_web_apps-0.1.3 → bfabric_web_apps-0.1.5}/PKG-INFO +4 -1
- bfabric_web_apps-0.1.5/bfabric_web_apps/__init__.py +57 -0
- {bfabric_web_apps-0.1.3 → bfabric_web_apps-0.1.5}/bfabric_web_apps/layouts/layouts.py +98 -6
- {bfabric_web_apps-0.1.3 → bfabric_web_apps-0.1.5}/bfabric_web_apps/objects/BfabricInterface.py +4 -11
- bfabric_web_apps-0.1.5/bfabric_web_apps/utils/callbacks.py +338 -0
- bfabric_web_apps-0.1.5/bfabric_web_apps/utils/config.py +38 -0
- bfabric_web_apps-0.1.5/bfabric_web_apps/utils/redis_connection.py +6 -0
- bfabric_web_apps-0.1.5/bfabric_web_apps/utils/redis_queue.py +6 -0
- bfabric_web_apps-0.1.5/bfabric_web_apps/utils/redis_worker_init.py +28 -0
- bfabric_web_apps-0.1.5/bfabric_web_apps/utils/resource_utilities.py +169 -0
- bfabric_web_apps-0.1.5/bfabric_web_apps/utils/run_main_pipeline.py +414 -0
- {bfabric_web_apps-0.1.3 → bfabric_web_apps-0.1.5}/pyproject.toml +4 -1
- bfabric_web_apps-0.1.3/bfabric_web_apps/__init__.py +0 -100
- bfabric_web_apps-0.1.3/bfabric_web_apps/utils/callbacks.py +0 -170
- bfabric_web_apps-0.1.3/bfabric_web_apps/utils/defaults.py +0 -11
- bfabric_web_apps-0.1.3/bfabric_web_apps/utils/resource_utilities.py +0 -218
- {bfabric_web_apps-0.1.3 → bfabric_web_apps-0.1.5}/LICENSE +0 -0
- {bfabric_web_apps-0.1.3 → bfabric_web_apps-0.1.5}/bfabric_web_apps/objects/Logger.py +0 -0
- {bfabric_web_apps-0.1.3 → bfabric_web_apps-0.1.5}/bfabric_web_apps/utils/app_init.py +0 -0
- {bfabric_web_apps-0.1.3 → bfabric_web_apps-0.1.5}/bfabric_web_apps/utils/components.py +0 -0
- {bfabric_web_apps-0.1.3 → bfabric_web_apps-0.1.5}/bfabric_web_apps/utils/create_app_in_bfabric.py +0 -0
- {bfabric_web_apps-0.1.3 → bfabric_web_apps-0.1.5}/bfabric_web_apps/utils/get_logger.py +0 -0
- {bfabric_web_apps-0.1.3 → bfabric_web_apps-0.1.5}/bfabric_web_apps/utils/get_power_user_wrapper.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: bfabric-web-apps
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.5
|
4
4
|
Summary: A package containing handy boilerplate utilities for developing bfabric web-applications
|
5
5
|
Author: Marc Zuber, Griffin White, GWC GmbH
|
6
6
|
Requires-Python: >=3.8,<4.0
|
@@ -9,3 +9,6 @@ Classifier: Programming Language :: Python :: 3.8
|
|
9
9
|
Classifier: Programming Language :: Python :: 3.9
|
10
10
|
Classifier: Programming Language :: Python :: 3.10
|
11
11
|
Classifier: Programming Language :: Python :: 3.11
|
12
|
+
Requires-Dist: pydantic-settings (>=2.8.1,<3.0.0)
|
13
|
+
Requires-Dist: pydantic[email] (>=2.10.6,<3.0.0)
|
14
|
+
Requires-Dist: rq (==1.15.1)
|
@@ -0,0 +1,57 @@
|
|
1
|
+
import os
|
2
|
+
|
3
|
+
# Export objects and classes
|
4
|
+
from bfabric_web_apps.objects import BfabricInterface, Logger
|
5
|
+
|
6
|
+
# Export components
|
7
|
+
from .utils import components
|
8
|
+
|
9
|
+
# Export layouts
|
10
|
+
from .layouts.layouts import get_static_layout
|
11
|
+
|
12
|
+
# Export app initialization utilities
|
13
|
+
from .utils.app_init import create_app
|
14
|
+
from .utils.get_logger import get_logger
|
15
|
+
from .utils.get_power_user_wrapper import get_power_user_wrapper
|
16
|
+
from .utils.create_app_in_bfabric import create_app_in_bfabric
|
17
|
+
|
18
|
+
# Export callbacks
|
19
|
+
from .utils.callbacks import (
|
20
|
+
process_url_and_token,
|
21
|
+
submit_bug_report,
|
22
|
+
populate_workunit_details,
|
23
|
+
get_redis_queue_layout
|
24
|
+
)
|
25
|
+
|
26
|
+
from .utils.config import settings as config
|
27
|
+
|
28
|
+
from. utils.run_main_pipeline import run_main_job
|
29
|
+
|
30
|
+
from .utils.resource_utilities import (
|
31
|
+
create_workunit,
|
32
|
+
create_resource,
|
33
|
+
create_workunits,
|
34
|
+
create_resources
|
35
|
+
)
|
36
|
+
|
37
|
+
from .utils.redis_worker_init import run_worker, test_job
|
38
|
+
from .utils.redis_queue import q
|
39
|
+
|
40
|
+
REDIS_HOST = config.REDIS_HOST
|
41
|
+
REDIS_PORT = config.REDIS_PORT
|
42
|
+
|
43
|
+
HOST = config.HOST
|
44
|
+
PORT = config.PORT
|
45
|
+
DEV = config.DEV
|
46
|
+
DEBUG = config.DEBUG
|
47
|
+
|
48
|
+
CONFIG_FILE_PATH = config.CONFIG_FILE_PATH
|
49
|
+
|
50
|
+
DEVELOPER_EMAIL_ADDRESS = config.DEVELOPER_EMAIL_ADDRESS
|
51
|
+
BUG_REPORT_EMAIL_ADDRESS = config.BUG_REPORT_EMAIL_ADDRESS
|
52
|
+
|
53
|
+
GSTORE_REMOTE_PATH = config.GSTORE_REMOTE_PATH
|
54
|
+
SCRATCH_PATH = config.SCRATCH_PATH
|
55
|
+
TRX_LOGIN = config.TRX_LOGIN
|
56
|
+
TRX_SSH_KEY = config.TRX_SSH_KEY
|
57
|
+
URL = config.URL
|
@@ -2,7 +2,7 @@ from dash import html, dcc
|
|
2
2
|
import dash_bootstrap_components as dbc
|
3
3
|
import bfabric_web_apps
|
4
4
|
|
5
|
-
def get_static_layout(base_title=None, main_content=None, documentation_content=None):
|
5
|
+
def get_static_layout(base_title=None, main_content=None, documentation_content=None, layout_config={}):
|
6
6
|
"""
|
7
7
|
Returns a layout with static tabs for Main, Documentation, and Report a Bug.
|
8
8
|
The main content is customizable, while the other tabs are generic.
|
@@ -11,10 +11,24 @@ def get_static_layout(base_title=None, main_content=None, documentation_content=
|
|
11
11
|
base_title (str): The main title to be displayed in the banner.
|
12
12
|
main_content (html.Div): Content to be displayed in the "Main" tab.
|
13
13
|
documentation_content (html.Div): Content for the "Documentation" tab.
|
14
|
+
layout_config (dict): Configuration for the layout, determining which tabs are shown.
|
14
15
|
|
15
16
|
Returns:
|
16
17
|
html.Div: The complete static layout of the web app.
|
17
18
|
"""
|
19
|
+
|
20
|
+
tab_list = [
|
21
|
+
dbc.Tab(main_content, label="Main", tab_id="main"),
|
22
|
+
dbc.Tab(dcc.Loading(get_documentation_tab(documentation_content)), label="Documentation", tab_id="documentation"),
|
23
|
+
]
|
24
|
+
|
25
|
+
if layout_config.get("workunits", False):
|
26
|
+
tab_list.append(dbc.Tab(dcc.Loading(get_workunits_tab()), label="Workunits", tab_id="workunits"))
|
27
|
+
if layout_config.get("queue", False):
|
28
|
+
tab_list.append(dbc.Tab(get_queue_tab(), label="Queue", tab_id="queue"))
|
29
|
+
if layout_config.get("bug", False):
|
30
|
+
tab_list.append(dbc.Tab(dcc.Loading(get_report_bug_tab()), label="Report a Bug", tab_id="report-bug"))
|
31
|
+
|
18
32
|
return html.Div(
|
19
33
|
children=[
|
20
34
|
dcc.Location(id='url', refresh=False),
|
@@ -142,11 +156,7 @@ def get_static_layout(base_title=None, main_content=None, documentation_content=
|
|
142
156
|
|
143
157
|
# Tabs Section
|
144
158
|
dbc.Tabs(
|
145
|
-
|
146
|
-
dbc.Tab(main_content, label="Main", tab_id="main"),
|
147
|
-
dbc.Tab(get_documentation_tab(documentation_content), label="Documentation", tab_id="documentation"),
|
148
|
-
dbc.Tab(get_report_bug_tab(), label="Report a Bug", tab_id="report-bug"),
|
149
|
-
],
|
159
|
+
tab_list,
|
150
160
|
id="tabs",
|
151
161
|
active_tab="main",
|
152
162
|
),
|
@@ -250,10 +260,92 @@ def get_report_bug_tab():
|
|
250
260
|
"margin-left": "2vw",
|
251
261
|
"font-size": "20px",
|
252
262
|
"padding-right": "40px",
|
263
|
+
"overflow-y": "scroll",
|
264
|
+
"max-height": "65vh",
|
253
265
|
},
|
254
266
|
),
|
255
267
|
width=9,
|
256
268
|
),
|
257
269
|
],
|
258
270
|
style={"margin-top": "0px", "min-height": "40vh"},
|
271
|
+
)
|
272
|
+
|
273
|
+
|
274
|
+
def get_workunits_tab():
|
275
|
+
"""
|
276
|
+
Returns the content for the Workunits tab with the upgraded layout.
|
277
|
+
"""
|
278
|
+
return dbc.Row(
|
279
|
+
id="page-content-workunits",
|
280
|
+
children=[
|
281
|
+
dbc.Col(
|
282
|
+
html.Div(
|
283
|
+
id="sidebar_workunits",
|
284
|
+
children=[], # Optional: Add sidebar content here if needed
|
285
|
+
style={
|
286
|
+
"border-right": "2px solid #d4d7d9",
|
287
|
+
"height": "100%",
|
288
|
+
"padding": "20px",
|
289
|
+
"font-size": "20px",
|
290
|
+
},
|
291
|
+
),
|
292
|
+
width=3,
|
293
|
+
),
|
294
|
+
dbc.Col(
|
295
|
+
html.Div(
|
296
|
+
id="page-content-workunits-children",
|
297
|
+
children=[
|
298
|
+
html.H2("Workunits"),
|
299
|
+
html.Div(id="refresh-workunits", children=[]),
|
300
|
+
html.Div(
|
301
|
+
id="workunits-content"
|
302
|
+
)
|
303
|
+
],
|
304
|
+
style={
|
305
|
+
"margin-top": "2vh",
|
306
|
+
"margin-left": "2vw",
|
307
|
+
"font-size": "20px",
|
308
|
+
"padding-right": "40px",
|
309
|
+
"overflow-y": "scroll",
|
310
|
+
"max-height": "65vh",
|
311
|
+
},
|
312
|
+
),
|
313
|
+
width=9,
|
314
|
+
),
|
315
|
+
],
|
316
|
+
style={"margin-top": "0px", "min-height": "40vh"},
|
317
|
+
)
|
318
|
+
|
319
|
+
|
320
|
+
def get_queue_tab():
|
321
|
+
|
322
|
+
return dbc.Row(
|
323
|
+
id="page-content-queue",
|
324
|
+
children=[
|
325
|
+
dbc.Col(
|
326
|
+
html.Div(
|
327
|
+
id="page-content-queue-children",
|
328
|
+
children=[],
|
329
|
+
style={
|
330
|
+
"margin-top": "2vh",
|
331
|
+
"margin-left": "2vw",
|
332
|
+
"font-size": "20px",
|
333
|
+
"padding-right": "40px",
|
334
|
+
"overflow-y": "scroll",
|
335
|
+
"max-height": "65vh",
|
336
|
+
},
|
337
|
+
),
|
338
|
+
),
|
339
|
+
dbc.Col(
|
340
|
+
children = [
|
341
|
+
dcc.Interval(
|
342
|
+
id="queue-interval",
|
343
|
+
interval=5 * 1000, # in milliseconds
|
344
|
+
n_intervals=0,
|
345
|
+
),
|
346
|
+
],
|
347
|
+
style={"display": "none"}
|
348
|
+
)
|
349
|
+
],
|
350
|
+
style={"margin-top": "0px", "min-height": "40vh"},
|
259
351
|
)
|
{bfabric_web_apps-0.1.3 → bfabric_web_apps-0.1.5}/bfabric_web_apps/objects/BfabricInterface.py
RENAMED
@@ -9,12 +9,9 @@ from bfabric_web_apps.utils.get_logger import get_logger
|
|
9
9
|
import os
|
10
10
|
import bfabric_web_apps
|
11
11
|
|
12
|
-
|
13
|
-
|
14
12
|
VALIDATION_URL = "https://fgcz-bfabric.uzh.ch/bfabric/rest/token/validate?token="
|
15
13
|
HOST = "fgcz-bfabric.uzh.ch"
|
16
14
|
|
17
|
-
|
18
15
|
class BfabricInterface( Bfabric ):
|
19
16
|
_instance = None # Singleton instance
|
20
17
|
_wrapper = None # Shared wrapper instance
|
@@ -50,8 +47,7 @@ class BfabricInterface( Bfabric ):
|
|
50
47
|
if self._wrapper is None:
|
51
48
|
raise RuntimeError("Bfabric wrapper is not initialized. Token validation must run first.")
|
52
49
|
return self._wrapper
|
53
|
-
|
54
|
-
|
50
|
+
|
55
51
|
|
56
52
|
def token_to_data(self, token):
|
57
53
|
"""
|
@@ -96,8 +92,6 @@ class BfabricInterface( Bfabric ):
|
|
96
92
|
|
97
93
|
environment_dict = {"Production":"https://fgcz-bfabric.uzh.ch/bfabric","Test":"https://fgcz-bfabric-test.uzh.ch/bfabric"}
|
98
94
|
|
99
|
-
print('userinfo', userinfo)
|
100
|
-
|
101
95
|
token_data = dict(
|
102
96
|
environment = userinfo['environment'],
|
103
97
|
user_data = userinfo['user'],
|
@@ -267,7 +261,7 @@ class BfabricInterface( Bfabric ):
|
|
267
261
|
|
268
262
|
# Extract App ID, Name, and Description
|
269
263
|
app_info = app_data_dict[0] # First (and only) result
|
270
|
-
|
264
|
+
|
271
265
|
json_data = json.dumps({
|
272
266
|
"id": app_info.get("id", "Unknown"),
|
273
267
|
"name": app_info.get("name", "Unknown"),
|
@@ -312,10 +306,9 @@ class BfabricInterface( Bfabric ):
|
|
312
306
|
os.system(mail)
|
313
307
|
|
314
308
|
return True
|
309
|
+
|
315
310
|
|
316
|
-
|
317
|
-
|
318
|
-
|
311
|
+
|
319
312
|
# Create a globally accessible instance
|
320
313
|
bfabric_interface = BfabricInterface()
|
321
314
|
|
@@ -0,0 +1,338 @@
|
|
1
|
+
from dash import Input, Output, State, html, dcc
|
2
|
+
from bfabric_web_apps.objects.BfabricInterface import bfabric_interface
|
3
|
+
import json
|
4
|
+
import dash_bootstrap_components as dbc
|
5
|
+
from datetime import datetime as dt
|
6
|
+
from bfabric_web_apps.utils.get_logger import get_logger
|
7
|
+
from rq import Queue
|
8
|
+
from .redis_connection import redis_conn
|
9
|
+
from rq.registry import StartedJobRegistry, FailedJobRegistry, FinishedJobRegistry
|
10
|
+
|
11
|
+
|
12
|
+
def process_url_and_token(url_params):
|
13
|
+
"""
|
14
|
+
Processes URL parameters to extract the token, validates it, and retrieves the corresponding data.
|
15
|
+
Additionally, it constructs a dynamic job link based on the environment and job ID.
|
16
|
+
|
17
|
+
Args:
|
18
|
+
url_params (str): The URL parameters containing the token.
|
19
|
+
|
20
|
+
Returns:
|
21
|
+
tuple: A tuple containing:
|
22
|
+
- token (str): Authentication token.
|
23
|
+
- token_data (dict): Token metadata.
|
24
|
+
- entity_data (dict): Retrieved entity information.
|
25
|
+
- page_title (str): Title for the page header.
|
26
|
+
- session_details (list): HTML-formatted session details.
|
27
|
+
- job_link (str): Dynamically generated link to the job page.
|
28
|
+
"""
|
29
|
+
base_title = " "
|
30
|
+
|
31
|
+
if not url_params:
|
32
|
+
return None, None, None, None, base_title, None, None
|
33
|
+
|
34
|
+
token = "".join(url_params.split('token=')[1:])
|
35
|
+
tdata_raw = bfabric_interface.token_to_data(token)
|
36
|
+
|
37
|
+
if tdata_raw:
|
38
|
+
if tdata_raw == "EXPIRED":
|
39
|
+
return None, None, None, None, base_title, None, None
|
40
|
+
else:
|
41
|
+
tdata = json.loads(tdata_raw)
|
42
|
+
else:
|
43
|
+
return None, None, None, None, base_title, None, None
|
44
|
+
|
45
|
+
if tdata:
|
46
|
+
entity_data_json = bfabric_interface.entity_data(tdata)
|
47
|
+
app_data_json = bfabric_interface.app_data(tdata)
|
48
|
+
entity_data = json.loads(entity_data_json)
|
49
|
+
app_data = json.loads(app_data_json)
|
50
|
+
page_title = (
|
51
|
+
f"{tdata.get('entityClass_data', 'Unknown')} - {entity_data.get('name', 'Unknown')} "
|
52
|
+
f"({tdata.get('environment', 'Unknown')} System)"
|
53
|
+
) if tdata else "Bfabric App Interface"
|
54
|
+
|
55
|
+
environment = tdata.get("environment", "").strip().lower() # 'test' or 'prod'
|
56
|
+
job_id = tdata.get("jobId", None) # Extract job ID
|
57
|
+
|
58
|
+
job_link = None
|
59
|
+
if job_id:
|
60
|
+
if "test" in environment:
|
61
|
+
job_link = f"https://fgcz-bfabric-test.uzh.ch/bfabric/job/show.html?id={job_id}&tab=details"
|
62
|
+
else:
|
63
|
+
job_link = f"https://fgcz-bfabric.uzh.ch/bfabric/job/show.html?id={job_id}&tab=details"
|
64
|
+
|
65
|
+
session_details = [
|
66
|
+
html.P([
|
67
|
+
html.B("Entity Name: "), entity_data.get('name', 'Unknown'),
|
68
|
+
html.Br(),
|
69
|
+
html.B("Entity Class: "), tdata.get('entityClass_data', 'Unknown'),
|
70
|
+
html.Br(),
|
71
|
+
html.B("Environment: "), tdata.get('environment', 'Unknown'),
|
72
|
+
html.Br(),
|
73
|
+
html.B("Entity ID: "), tdata.get('entity_id_data', 'Unknown'),
|
74
|
+
html.Br(),
|
75
|
+
html.B("Job ID: "), job_id if job_id else "Unknown",
|
76
|
+
html.Br(),
|
77
|
+
html.B("User Name: "), tdata.get('user_data', 'Unknown'),
|
78
|
+
html.Br(),
|
79
|
+
html.B("Session Expires: "), tdata.get('token_expires', 'Unknown'),
|
80
|
+
html.Br(),
|
81
|
+
html.B("App Name: "), app_data.get("name", "Unknown"),
|
82
|
+
html.Br(),
|
83
|
+
html.B("App Description: "), app_data.get("description", "No description available"),
|
84
|
+
html.Br(),
|
85
|
+
html.B("Current Time: "), str(dt.now().strftime("%Y-%m-%d %H:%M:%S"))
|
86
|
+
])
|
87
|
+
]
|
88
|
+
|
89
|
+
return token, tdata, entity_data, app_data, page_title, session_details, job_link
|
90
|
+
else:
|
91
|
+
return None, None, None, None, base_title, None, None
|
92
|
+
|
93
|
+
|
94
|
+
def submit_bug_report(n_clicks, bug_description, token, entity_data):
|
95
|
+
"""
|
96
|
+
Submits a bug report based on user input, token, and entity data.
|
97
|
+
|
98
|
+
Args:
|
99
|
+
n_clicks (int): The number of times the submit button has been clicked.
|
100
|
+
bug_description (str): The description of the bug provided by the user.
|
101
|
+
token (str): The authentication token.
|
102
|
+
entity_data (dict): The data related to the current entity.
|
103
|
+
|
104
|
+
Returns:
|
105
|
+
tuple: A tuple containing two boolean values indicating success and failure status of the submission.
|
106
|
+
(is_open_success, is_open_failure)
|
107
|
+
"""
|
108
|
+
|
109
|
+
print("submit bug report", token)
|
110
|
+
|
111
|
+
# Parse token data if token is provided, otherwise set it to an empty dictionary
|
112
|
+
if token:
|
113
|
+
token_data = json.loads(bfabric_interface.token_to_data(token))
|
114
|
+
else:
|
115
|
+
token_data = {}
|
116
|
+
|
117
|
+
print(token_data)
|
118
|
+
|
119
|
+
# Extract logging-related information from token_data, with defaults for missing values
|
120
|
+
jobId = token_data.get('jobId', None)
|
121
|
+
username = token_data.get("user_data", "None")
|
122
|
+
environment = token_data.get("environment", "None")
|
123
|
+
|
124
|
+
# Initialize the logger only if token_data is available
|
125
|
+
L = None
|
126
|
+
if token_data:
|
127
|
+
L = get_logger(token_data)
|
128
|
+
|
129
|
+
if n_clicks:
|
130
|
+
# Log the operation only if the logger is initialized
|
131
|
+
if L:
|
132
|
+
L.log_operation(
|
133
|
+
"bug report",
|
134
|
+
"Initiating bug report submission process.",
|
135
|
+
params=None,
|
136
|
+
flush_logs=False,
|
137
|
+
)
|
138
|
+
try:
|
139
|
+
sending_result = bfabric_interface.send_bug_report(
|
140
|
+
token_data, entity_data, bug_description
|
141
|
+
)
|
142
|
+
|
143
|
+
if sending_result:
|
144
|
+
if L:
|
145
|
+
L.log_operation(
|
146
|
+
"bug report",
|
147
|
+
f"Bug report successfully submitted. | DESCRIPTION: {bug_description}",
|
148
|
+
params=None,
|
149
|
+
flush_logs=True,
|
150
|
+
)
|
151
|
+
return True, False
|
152
|
+
else:
|
153
|
+
if L:
|
154
|
+
L.log_operation(
|
155
|
+
"bug report",
|
156
|
+
"Failed to submit bug report!",
|
157
|
+
params=None,
|
158
|
+
flush_logs=True,
|
159
|
+
)
|
160
|
+
return False, True
|
161
|
+
except Exception as e:
|
162
|
+
if L:
|
163
|
+
L.log_operation(
|
164
|
+
"bug report",
|
165
|
+
f"Failed to submit bug report! Error: {str(e)}",
|
166
|
+
params=None,
|
167
|
+
flush_logs=True,
|
168
|
+
)
|
169
|
+
return False, True
|
170
|
+
|
171
|
+
return False, False
|
172
|
+
|
173
|
+
|
174
|
+
def populate_workunit_details(token_data):
|
175
|
+
|
176
|
+
"""
|
177
|
+
Function to populate workunit data for the current app instance.
|
178
|
+
|
179
|
+
Args:
|
180
|
+
token_data (dict): Token metadata.
|
181
|
+
|
182
|
+
Returns:
|
183
|
+
html.Div: A div containing the populated workunit data.
|
184
|
+
"""
|
185
|
+
|
186
|
+
environment_urls = {
|
187
|
+
"Test": "https://fgcz-bfabric-test.uzh.ch/bfabric/workunit/show.html?id=",
|
188
|
+
"Production": "https://fgcz-bfabric.uzh.ch/bfabric/workunit/show.html?id="
|
189
|
+
}
|
190
|
+
|
191
|
+
if token_data:
|
192
|
+
|
193
|
+
jobId = token_data.get('jobId', None)
|
194
|
+
print("jobId", jobId)
|
195
|
+
|
196
|
+
job = bfabric_interface.get_wrapper().read("job", {"id": jobId})[0]
|
197
|
+
workunits = job.get("workunit", [])
|
198
|
+
|
199
|
+
if workunits:
|
200
|
+
wus = bfabric_interface.get_wrapper().read(
|
201
|
+
"workunit",
|
202
|
+
{"id": [wu["id"] for wu in workunits]}
|
203
|
+
)
|
204
|
+
else:
|
205
|
+
return html.Div(
|
206
|
+
[
|
207
|
+
html.P("No workunits found for the current job.")
|
208
|
+
]
|
209
|
+
)
|
210
|
+
|
211
|
+
wu_cards = []
|
212
|
+
|
213
|
+
for wu in wus:
|
214
|
+
print(wu)
|
215
|
+
wu_card = html.A(
|
216
|
+
dbc.Card([
|
217
|
+
dbc.CardHeader(html.B(f"Workunit {wu['id']}")),
|
218
|
+
dbc.CardBody([
|
219
|
+
html.P(f"Name: {wu.get('name', 'n/a')}"),
|
220
|
+
html.P(f"Description: {wu.get('description', 'n/a')}"),
|
221
|
+
html.P(f"Num Resources: {len(wu.get('resource', []))}"),
|
222
|
+
html.P(f"Created: {wu.get('created', 'n/a')}"),
|
223
|
+
html.P(f"Status: {wu.get('status', 'n/a')}")
|
224
|
+
])
|
225
|
+
], style={"width": "400px", "margin":"10px"}),
|
226
|
+
href=environment_urls[token_data.get("environment", "Test")] + str(wu["id"]),
|
227
|
+
target="_blank",
|
228
|
+
style={"text-decoration": "none"}
|
229
|
+
)
|
230
|
+
|
231
|
+
wu_cards.append(wu_card)
|
232
|
+
|
233
|
+
return dbc.Container(wu_cards, style={"display": "flex", "flex-wrap": "wrap"})
|
234
|
+
else:
|
235
|
+
return html.Div()
|
236
|
+
|
237
|
+
def get_redis_queue_layout():
|
238
|
+
# Get all queues dynamically
|
239
|
+
queues = Queue.all(connection=redis_conn)
|
240
|
+
|
241
|
+
queue_cards = []
|
242
|
+
|
243
|
+
print("QUEUES", queues)
|
244
|
+
|
245
|
+
for queue in queues:
|
246
|
+
queue_name = queue.name
|
247
|
+
|
248
|
+
# Get queue stats
|
249
|
+
started_registry = StartedJobRegistry(queue_name, connection=redis_conn)
|
250
|
+
failed_registry = FailedJobRegistry(queue_name, connection=redis_conn)
|
251
|
+
finished_registry = FinishedJobRegistry(queue_name, connection=redis_conn)
|
252
|
+
|
253
|
+
stats = {
|
254
|
+
"Jobs in queue": queue.count,
|
255
|
+
"Running": started_registry.count,
|
256
|
+
"Failed": failed_registry.count,
|
257
|
+
"Completed": finished_registry.count,
|
258
|
+
}
|
259
|
+
|
260
|
+
print("STAT", stats)
|
261
|
+
|
262
|
+
stats_row = dbc.Row([
|
263
|
+
dbc.Col([
|
264
|
+
html.P([html.B("Jobs in queue: "), f"{queue.count}"]),
|
265
|
+
html.P([html.B("Running: "), f"{started_registry.count}"]),
|
266
|
+
],width=6),
|
267
|
+
dbc.Col([
|
268
|
+
html.P([html.B("Failed: "), f"{failed_registry.count}"]),
|
269
|
+
html.P([html.B("Completed: "), f"{finished_registry.count}"]),
|
270
|
+
], width=6)
|
271
|
+
])
|
272
|
+
|
273
|
+
# Fetch job details
|
274
|
+
job_cards = []
|
275
|
+
for job_id in started_registry.get_job_ids():
|
276
|
+
job = queue.fetch_job(job_id)
|
277
|
+
if job:
|
278
|
+
job_cards.append(
|
279
|
+
dbc.Card(
|
280
|
+
dbc.CardBody([
|
281
|
+
html.H6(f"Job ID: {job.id}", className="card-title"),
|
282
|
+
html.P(f"Function: {job.func_name}", className="card-text"),
|
283
|
+
html.P(f"Status: Running", className="text-success"),
|
284
|
+
]),
|
285
|
+
style={"maxWidth": "36vw", "backgroundColor": "#d4edda"}, className="mb-2"
|
286
|
+
)
|
287
|
+
)
|
288
|
+
|
289
|
+
for job_id in failed_registry.get_job_ids():
|
290
|
+
job = queue.fetch_job(job_id)
|
291
|
+
if job:
|
292
|
+
job_cards.append(
|
293
|
+
dbc.Card(
|
294
|
+
dbc.CardBody([
|
295
|
+
html.H6(f"Job ID: {job.id}", className="card-title"),
|
296
|
+
html.P(f"Function: {job.func_name}", className="card-text"),
|
297
|
+
html.P(f"Status: Failed", className="text-danger"),
|
298
|
+
]),
|
299
|
+
style={"maxWidth": "36vw", "backgroundColor": "#f8d7da"}, className="mb-2"
|
300
|
+
)
|
301
|
+
)
|
302
|
+
|
303
|
+
for job_id in finished_registry.get_job_ids():
|
304
|
+
job = queue.fetch_job(job_id)
|
305
|
+
if job:
|
306
|
+
finished_time = job.ended_at.strftime("%Y-%m-%d %H:%M:%S") if job.ended_at else "Unknown"
|
307
|
+
job_cards.append(
|
308
|
+
dbc.Card(
|
309
|
+
dbc.CardBody([
|
310
|
+
html.H6(f"Job ID: {job.id}", className="card-title"),
|
311
|
+
html.P(f"Function: {job.func_name}", className="card-text"),
|
312
|
+
html.P(f"Status: Completed", className="text-primary"),
|
313
|
+
html.P(f"Finished at: {finished_time}", className="text-muted"),
|
314
|
+
]),
|
315
|
+
style={"maxWidth": "36vw", "backgroundColor": "#d1ecf1"}, className="mb-2"
|
316
|
+
)
|
317
|
+
)
|
318
|
+
|
319
|
+
# Create queue card
|
320
|
+
queue_card = dbc.Col([
|
321
|
+
dbc.Card(
|
322
|
+
[
|
323
|
+
dbc.CardHeader(html.H5(f"Queue: {queue_name}")),
|
324
|
+
dbc.CardBody([
|
325
|
+
stats_row, # Fixed: Convert dictionary to list
|
326
|
+
html.Hr(),
|
327
|
+
*job_cards # Add job sub-cards
|
328
|
+
], style={"maxHeight": "58vh", "overflow-y": "scroll"})
|
329
|
+
],
|
330
|
+
style={"maxWidth": "36vw", "backgroundColor": "#f8f9fa", "max-height":"60vh"}, className="mb-4"
|
331
|
+
)
|
332
|
+
])
|
333
|
+
|
334
|
+
queue_cards.append(queue_card)
|
335
|
+
|
336
|
+
container_children = dbc.Row(queue_cards)
|
337
|
+
|
338
|
+
return dbc.Container(container_children, className="mt-4")
|
@@ -0,0 +1,38 @@
|
|
1
|
+
from pydantic_settings import BaseSettings
|
2
|
+
from pydantic import EmailStr
|
3
|
+
|
4
|
+
class Settings(BaseSettings):
|
5
|
+
|
6
|
+
REDIS_HOST: str = "localhost"
|
7
|
+
REDIS_PORT: int = 6379
|
8
|
+
|
9
|
+
CONFIG_FILE_PATH: str = "~/.bfabricpy.yml"
|
10
|
+
|
11
|
+
HOST: str = "127.0.0.1"
|
12
|
+
PORT: int = 8050
|
13
|
+
|
14
|
+
DEV: bool = False
|
15
|
+
DEBUG: bool = False
|
16
|
+
|
17
|
+
DEVELOPER_EMAIL_ADDRESS: EmailStr = "griffin@gwcustom.com"
|
18
|
+
BUG_REPORT_EMAIL_ADDRESS: EmailStr = "gwtools@fgcz.system"
|
19
|
+
|
20
|
+
#Run main pipeline config (only FGCZ specific)
|
21
|
+
GSTORE_REMOTE_PATH: str = "/path/to/remote/gstore"
|
22
|
+
SCRATCH_PATH: str = "/scratch/folder"
|
23
|
+
TRX_LOGIN: str = "trxcopy@fgcz-server.uzh.ch"
|
24
|
+
TRX_SSH_KEY: str = "/home/user/.ssh/your_ssh_key"
|
25
|
+
URL: str = "https:/fgcz/dummy/url"
|
26
|
+
|
27
|
+
class Config:
|
28
|
+
|
29
|
+
env_file = ".env"
|
30
|
+
|
31
|
+
# Disable reading from environment variables
|
32
|
+
@classmethod
|
33
|
+
def customise_sources(cls, init_settings, env_settings, file_secret_settings):
|
34
|
+
return file_secret_settings, init_settings
|
35
|
+
|
36
|
+
# Instantiate settings
|
37
|
+
settings = Settings()
|
38
|
+
|
@@ -0,0 +1,28 @@
|
|
1
|
+
import redis
|
2
|
+
from rq import Worker, Queue, Connection
|
3
|
+
import time
|
4
|
+
|
5
|
+
def test_job():
|
6
|
+
|
7
|
+
"""
|
8
|
+
A test job that prints a message to the console.
|
9
|
+
"""
|
10
|
+
print("Hello, this is a test job!")
|
11
|
+
time.sleep(10)
|
12
|
+
print("Test job finished!")
|
13
|
+
return
|
14
|
+
|
15
|
+
|
16
|
+
def run_worker(host, port, queue_names):
|
17
|
+
"""
|
18
|
+
Provides internal interface for running workers on a specified host and port.
|
19
|
+
|
20
|
+
Args:
|
21
|
+
host (str): The host to run
|
22
|
+
port (int): The port to run
|
23
|
+
queue_names (list): A list of queue names to listen to
|
24
|
+
"""
|
25
|
+
conn = redis.Redis(host=host, port=port)
|
26
|
+
with Connection(conn):
|
27
|
+
worker = Worker(map(Queue, queue_names))
|
28
|
+
worker.work()
|