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.
Files changed (23) hide show
  1. {bfabric_web_apps-0.1.3 → bfabric_web_apps-0.1.5}/PKG-INFO +4 -1
  2. bfabric_web_apps-0.1.5/bfabric_web_apps/__init__.py +57 -0
  3. {bfabric_web_apps-0.1.3 → bfabric_web_apps-0.1.5}/bfabric_web_apps/layouts/layouts.py +98 -6
  4. {bfabric_web_apps-0.1.3 → bfabric_web_apps-0.1.5}/bfabric_web_apps/objects/BfabricInterface.py +4 -11
  5. bfabric_web_apps-0.1.5/bfabric_web_apps/utils/callbacks.py +338 -0
  6. bfabric_web_apps-0.1.5/bfabric_web_apps/utils/config.py +38 -0
  7. bfabric_web_apps-0.1.5/bfabric_web_apps/utils/redis_connection.py +6 -0
  8. bfabric_web_apps-0.1.5/bfabric_web_apps/utils/redis_queue.py +6 -0
  9. bfabric_web_apps-0.1.5/bfabric_web_apps/utils/redis_worker_init.py +28 -0
  10. bfabric_web_apps-0.1.5/bfabric_web_apps/utils/resource_utilities.py +169 -0
  11. bfabric_web_apps-0.1.5/bfabric_web_apps/utils/run_main_pipeline.py +414 -0
  12. {bfabric_web_apps-0.1.3 → bfabric_web_apps-0.1.5}/pyproject.toml +4 -1
  13. bfabric_web_apps-0.1.3/bfabric_web_apps/__init__.py +0 -100
  14. bfabric_web_apps-0.1.3/bfabric_web_apps/utils/callbacks.py +0 -170
  15. bfabric_web_apps-0.1.3/bfabric_web_apps/utils/defaults.py +0 -11
  16. bfabric_web_apps-0.1.3/bfabric_web_apps/utils/resource_utilities.py +0 -218
  17. {bfabric_web_apps-0.1.3 → bfabric_web_apps-0.1.5}/LICENSE +0 -0
  18. {bfabric_web_apps-0.1.3 → bfabric_web_apps-0.1.5}/bfabric_web_apps/objects/Logger.py +0 -0
  19. {bfabric_web_apps-0.1.3 → bfabric_web_apps-0.1.5}/bfabric_web_apps/utils/app_init.py +0 -0
  20. {bfabric_web_apps-0.1.3 → bfabric_web_apps-0.1.5}/bfabric_web_apps/utils/components.py +0 -0
  21. {bfabric_web_apps-0.1.3 → bfabric_web_apps-0.1.5}/bfabric_web_apps/utils/create_app_in_bfabric.py +0 -0
  22. {bfabric_web_apps-0.1.3 → bfabric_web_apps-0.1.5}/bfabric_web_apps/utils/get_logger.py +0 -0
  23. {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
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
  )
@@ -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
- print('app_info', app_info)
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,6 @@
1
+
2
+ from .config import settings as config
3
+
4
+ from redis import Redis
5
+
6
+ redis_conn = Redis(host=config.REDIS_HOST, port=config.REDIS_PORT)
@@ -0,0 +1,6 @@
1
+ from rq import Queue
2
+ from .redis_connection import redis_conn as conn
3
+
4
+
5
+ def q(queue_name):
6
+ return Queue(name=queue_name, connection=conn, default_timeout=10000000)
@@ -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()