bfabric-web-apps 0.1.0__py3-none-any.whl → 0.1.1__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- bfabric_web_apps/__init__.py +35 -5
- bfabric_web_apps/layouts/layouts.py +200 -8
- bfabric_web_apps/objects/BfabricInterface.py +215 -9
- bfabric_web_apps/objects/Logger.py +55 -12
- bfabric_web_apps/utils/app_config.py +27 -0
- bfabric_web_apps/utils/app_init.py +11 -0
- bfabric_web_apps/utils/callbacks.py +152 -0
- bfabric_web_apps/utils/components.py +22 -0
- bfabric_web_apps/utils/get_logger.py +14 -0
- bfabric_web_apps/utils/get_power_user_wrapper.py +29 -0
- bfabric_web_apps-0.1.1.dist-info/LICENSE +21 -0
- {bfabric_web_apps-0.1.0.dist-info → bfabric_web_apps-0.1.1.dist-info}/METADATA +4 -3
- bfabric_web_apps-0.1.1.dist-info/RECORD +14 -0
- {bfabric_web_apps-0.1.0.dist-info → bfabric_web_apps-0.1.1.dist-info}/WHEEL +1 -1
- bfabric_web_apps/objects/__init__.py +0 -0
- bfabric_web_apps-0.1.0.dist-info/RECORD +0 -8
bfabric_web_apps/__init__.py
CHANGED
@@ -1,6 +1,36 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
Logger
|
4
|
-
)
|
1
|
+
# Export objects and classes
|
2
|
+
from bfabric_web_apps.objects import BfabricInterface, Logger
|
5
3
|
|
6
|
-
|
4
|
+
# Export components
|
5
|
+
from .utils.components import *
|
6
|
+
|
7
|
+
# Export layouts
|
8
|
+
from .layouts.layouts import get_static_layout
|
9
|
+
|
10
|
+
# Export app initialization utilities
|
11
|
+
from .utils.app_init import create_app
|
12
|
+
from .utils.app_config import load_config
|
13
|
+
from .utils.get_logger import get_logger
|
14
|
+
from .utils.get_power_user_wrapper import get_power_user_wrapper
|
15
|
+
|
16
|
+
# Export callbacks
|
17
|
+
from .utils.callbacks import process_url_and_token, submit_bug_report
|
18
|
+
|
19
|
+
HOST = '0.0.0.0'
|
20
|
+
PORT = 8050
|
21
|
+
DEV = False
|
22
|
+
CONFIG_FILE_PATH = "~/.bfabricpy.yml"
|
23
|
+
|
24
|
+
# Define __all__ for controlled imports
|
25
|
+
__all__ = [
|
26
|
+
"BfabricInterface",
|
27
|
+
"Logger",
|
28
|
+
"components",
|
29
|
+
"get_static_layout",
|
30
|
+
"create_app",
|
31
|
+
"load_config",
|
32
|
+
"process_url_and_token",
|
33
|
+
"submit_bug_report",
|
34
|
+
'get_logger',
|
35
|
+
'get_power_user_wrapper'
|
36
|
+
]
|
@@ -1,13 +1,205 @@
|
|
1
|
+
from dash import html, dcc
|
2
|
+
import dash_bootstrap_components as dbc
|
1
3
|
|
4
|
+
def get_static_layout(base_title = None, main_content = None, documentation_content = None):
|
5
|
+
"""
|
6
|
+
Returns a layout with static tabs for Main, Documentation, and Report a Bug.
|
7
|
+
The main content is customizable, while the other tabs are generic.
|
8
|
+
"""
|
9
|
+
return html.Div(
|
10
|
+
children=[
|
11
|
+
dcc.Location(id='url', refresh=False),
|
12
|
+
dcc.Store(id='token', storage_type='session'),
|
13
|
+
dcc.Store(id='entity', storage_type='session'),
|
14
|
+
dcc.Store(id='token_data', storage_type='session'),
|
15
|
+
dbc.Container(
|
16
|
+
children=[
|
17
|
+
# Banner
|
18
|
+
dbc.Row(
|
19
|
+
dbc.Col(
|
20
|
+
html.Div(
|
21
|
+
className="banner",
|
22
|
+
children=[
|
23
|
+
html.Div(
|
24
|
+
children=[
|
25
|
+
html.P(
|
26
|
+
base_title,
|
27
|
+
style={
|
28
|
+
'color': '#ffffff',
|
29
|
+
'margin-top': '15px',
|
30
|
+
'height': '80px',
|
31
|
+
'width': '100%',
|
32
|
+
'font-size': '40px',
|
33
|
+
'margin-left': '20px'
|
34
|
+
}
|
35
|
+
)
|
36
|
+
],
|
37
|
+
style={"background-color": "#000000", "border-radius": "10px"}
|
38
|
+
)
|
39
|
+
],
|
40
|
+
),
|
41
|
+
),
|
42
|
+
),
|
43
|
+
# Page Title
|
44
|
+
dbc.Row(
|
45
|
+
dbc.Col(
|
46
|
+
[
|
47
|
+
html.Div(
|
48
|
+
children=[
|
49
|
+
html.P(
|
50
|
+
id="page-title",
|
51
|
+
children=[str(" ")],
|
52
|
+
style={"font-size": "40px", "margin-left": "20px", "margin-top": "10px"}
|
53
|
+
)
|
54
|
+
],
|
55
|
+
style={
|
56
|
+
"margin-top": "0px",
|
57
|
+
"min-height": "80px",
|
58
|
+
"height": "6vh",
|
59
|
+
"border-bottom": "2px solid #d4d7d9"
|
60
|
+
}
|
61
|
+
),
|
62
|
+
dbc.Alert(
|
63
|
+
"Your bug report has been submitted. Thanks for helping us improve!",
|
64
|
+
id="alert-fade-bug-success",
|
65
|
+
dismissable=True,
|
66
|
+
is_open=False,
|
67
|
+
color="info",
|
68
|
+
style={
|
69
|
+
"max-width": "50vw",
|
70
|
+
"margin-left": "10px",
|
71
|
+
"margin-top": "10px",
|
72
|
+
}
|
73
|
+
),
|
74
|
+
dbc.Alert(
|
75
|
+
"Failed to submit bug report! Please email the developers directly at the email below!",
|
76
|
+
id="alert-fade-bug-fail",
|
77
|
+
dismissable=True,
|
78
|
+
is_open=False,
|
79
|
+
color="danger",
|
80
|
+
style={
|
81
|
+
"max-width": "50vw",
|
82
|
+
"margin-left": "10px",
|
83
|
+
"margin-top": "10px",
|
84
|
+
}
|
85
|
+
),
|
86
|
+
]
|
87
|
+
)
|
88
|
+
),
|
89
|
+
# Tabs
|
90
|
+
dbc.Tabs(
|
91
|
+
[
|
92
|
+
dbc.Tab(main_content, label="Main", tab_id="main"),
|
93
|
+
dbc.Tab(get_documentation_tab(documentation_content), label="Documentation", tab_id="documentation"),
|
94
|
+
dbc.Tab(get_report_bug_tab(), label="Report a Bug", tab_id="report-bug"),
|
95
|
+
],
|
96
|
+
id="tabs",
|
97
|
+
active_tab="main",
|
98
|
+
),
|
99
|
+
],
|
100
|
+
fluid=True,
|
101
|
+
style={"width": "100vw"}
|
102
|
+
)
|
103
|
+
],
|
104
|
+
style={"width": "100vw", "overflow-x": "hidden", "overflow-y": "scroll"}
|
105
|
+
)
|
2
106
|
|
3
|
-
def get_empty_layout():
|
4
107
|
|
5
|
-
|
108
|
+
def get_documentation_tab(documentation_content):
|
109
|
+
"""
|
110
|
+
Returns the content for the Documentation tab with the upgraded layout.
|
111
|
+
"""
|
112
|
+
return dbc.Row(
|
113
|
+
id="page-content-docs",
|
114
|
+
children=[
|
115
|
+
dbc.Col(
|
116
|
+
html.Div(
|
117
|
+
id="sidebar_docs",
|
118
|
+
children=[],
|
119
|
+
style={
|
120
|
+
"border-right": "2px solid #d4d7d9",
|
121
|
+
"height": "100%",
|
122
|
+
"padding": "20px",
|
123
|
+
"font-size": "20px",
|
124
|
+
},
|
125
|
+
),
|
126
|
+
width=3,
|
127
|
+
),
|
128
|
+
dbc.Col(
|
129
|
+
html.Div(
|
130
|
+
id="page-content-docs-children",
|
131
|
+
children= documentation_content,
|
132
|
+
style={"margin-top":"2vh", "margin-left":"2vw", "font-size":"20px", "padding-right":"40px", "overflow-y": "scroll", "max-height": "60vh"},
|
133
|
+
),
|
134
|
+
width=9,
|
135
|
+
),
|
136
|
+
],
|
137
|
+
style={"margin-top": "0px", "min-height": "40vh"},
|
138
|
+
)
|
6
139
|
|
7
|
-
def get_layout_with_sidebar():
|
8
|
-
|
9
|
-
pass
|
10
140
|
|
11
|
-
def
|
12
|
-
|
13
|
-
|
141
|
+
def get_report_bug_tab():
|
142
|
+
"""
|
143
|
+
Returns the content for the Report a Bug tab with the upgraded layout.
|
144
|
+
"""
|
145
|
+
return dbc.Row(
|
146
|
+
id="page-content-bug-report",
|
147
|
+
children=[
|
148
|
+
dbc.Col(
|
149
|
+
html.Div(
|
150
|
+
id="sidebar_bug_report",
|
151
|
+
children=[], # Optional: Add sidebar content here if needed
|
152
|
+
style={
|
153
|
+
"border-right": "2px solid #d4d7d9",
|
154
|
+
"height": "100%",
|
155
|
+
"padding": "20px",
|
156
|
+
"font-size": "20px",
|
157
|
+
},
|
158
|
+
),
|
159
|
+
width=3,
|
160
|
+
),
|
161
|
+
dbc.Col(
|
162
|
+
html.Div(
|
163
|
+
id="page-content-bug-report-children",
|
164
|
+
children=[
|
165
|
+
html.H2("Report a Bug"),
|
166
|
+
html.P(
|
167
|
+
[
|
168
|
+
"Please use the form below to report a bug. If you have any questions, please email the developer at ",
|
169
|
+
html.A(
|
170
|
+
"griffin@gwcustom.com",
|
171
|
+
href="mailto:griffin@gwcustom.com",
|
172
|
+
),
|
173
|
+
]
|
174
|
+
),
|
175
|
+
html.Br(),
|
176
|
+
html.H4("Session Details: "),
|
177
|
+
html.Br(),
|
178
|
+
html.P(id="session-details", children="No Active Session"),
|
179
|
+
html.Br(),
|
180
|
+
html.H4("Bug Description"),
|
181
|
+
dbc.Textarea(
|
182
|
+
id="bug-description",
|
183
|
+
placeholder="Please describe the bug you encountered here.",
|
184
|
+
style={"width": "100%"},
|
185
|
+
),
|
186
|
+
html.Br(),
|
187
|
+
dbc.Button(
|
188
|
+
"Submit Bug Report",
|
189
|
+
id="submit-bug-report",
|
190
|
+
n_clicks=0,
|
191
|
+
style={"margin-bottom": "60px"},
|
192
|
+
),
|
193
|
+
],
|
194
|
+
style={
|
195
|
+
"margin-top": "2vh",
|
196
|
+
"margin-left": "2vw",
|
197
|
+
"font-size": "20px",
|
198
|
+
"padding-right": "40px",
|
199
|
+
},
|
200
|
+
),
|
201
|
+
width=9,
|
202
|
+
),
|
203
|
+
],
|
204
|
+
style={"margin-top": "0px", "min-height": "40vh"},
|
205
|
+
)
|
@@ -1,16 +1,222 @@
|
|
1
|
-
from bfabric import Bfabric
|
1
|
+
from bfabric import Bfabric
|
2
|
+
import requests
|
3
|
+
import json
|
4
|
+
import datetime
|
5
|
+
import bfabric
|
6
|
+
from bfabric import BfabricAuth
|
7
|
+
from bfabric import BfabricClientConfig
|
8
|
+
from dash import html
|
9
|
+
import dash_bootstrap_components as dbc
|
10
|
+
from bfabric_web_apps.objects.Logger import Logger
|
11
|
+
import os
|
2
12
|
|
3
|
-
class BfabricInterface( Bfabric ):
|
4
13
|
|
5
|
-
|
6
|
-
|
14
|
+
VALIDATION_URL = "https://fgcz-bfabric.uzh.ch/bfabric/rest/token/validate?token="
|
15
|
+
HOST = "fgcz-bfabric.uzh.ch"
|
7
16
|
|
8
|
-
def token_to_data(self, token):
|
9
|
-
pass
|
10
17
|
|
11
|
-
|
12
|
-
|
18
|
+
class BfabricInterface( Bfabric ):
|
19
|
+
"""
|
20
|
+
A class to interface with the Bfabric API, providing methods to validate tokens,
|
21
|
+
retrieve data, and send bug reports.
|
22
|
+
"""
|
13
23
|
|
14
|
-
def
|
24
|
+
def __init__(self):
|
25
|
+
"""
|
26
|
+
Initializes an instance of BfabricInterface.
|
27
|
+
"""
|
15
28
|
pass
|
16
29
|
|
30
|
+
def token_to_data(self, token):
|
31
|
+
"""
|
32
|
+
Validates the given token and retrieves its associated data.
|
33
|
+
|
34
|
+
Args:
|
35
|
+
token (str): The token to validate.
|
36
|
+
|
37
|
+
Returns:
|
38
|
+
str: A JSON string containing token data if valid.
|
39
|
+
str: "EXPIRED" if the token is expired.
|
40
|
+
None: If the token is invalid or validation fails.
|
41
|
+
"""
|
42
|
+
|
43
|
+
if not token:
|
44
|
+
return None
|
45
|
+
|
46
|
+
validation_url = VALIDATION_URL + token
|
47
|
+
res = requests.get(validation_url, headers={"Host": HOST})
|
48
|
+
|
49
|
+
if res.status_code != 200:
|
50
|
+
res = requests.get(validation_url)
|
51
|
+
|
52
|
+
if res.status_code != 200:
|
53
|
+
return None
|
54
|
+
try:
|
55
|
+
master_data = json.loads(res.text)
|
56
|
+
except:
|
57
|
+
return None
|
58
|
+
|
59
|
+
if True:
|
60
|
+
|
61
|
+
userinfo = json.loads(res.text)
|
62
|
+
expiry_time = userinfo['expiryDateTime']
|
63
|
+
current_time = datetime.datetime.now()
|
64
|
+
five_minutes_later = current_time + datetime.timedelta(minutes=5)
|
65
|
+
|
66
|
+
# Comparing the parsed expiry time with the five minutes later time
|
67
|
+
|
68
|
+
if not five_minutes_later <= datetime.datetime.strptime(expiry_time, "%Y-%m-%d %H:%M:%S"):
|
69
|
+
return "EXPIRED"
|
70
|
+
|
71
|
+
environment_dict = {"Production":"https://fgcz-bfabric.uzh.ch/bfabric","Test":"https://fgcz-bfabric-test.uzh.ch/bfabric"}
|
72
|
+
|
73
|
+
token_data = dict(
|
74
|
+
environment = userinfo['environment'],
|
75
|
+
user_data = userinfo['user'],
|
76
|
+
token_expires = expiry_time,
|
77
|
+
entity_id_data = userinfo['entityId'],
|
78
|
+
entityClass_data = userinfo['entityClassName'],
|
79
|
+
webbase_data = environment_dict.get(userinfo['environment'], None),
|
80
|
+
application_params_data = {},
|
81
|
+
application_data = str(userinfo['applicationId']),
|
82
|
+
userWsPassword = userinfo['userWsPassword'],
|
83
|
+
jobId = userinfo['jobId']
|
84
|
+
)
|
85
|
+
|
86
|
+
return json.dumps(token_data)
|
87
|
+
|
88
|
+
|
89
|
+
|
90
|
+
def token_response_to_bfabric(self, token_response):
|
91
|
+
|
92
|
+
"""
|
93
|
+
Converts token response data into a Bfabric object for further interactions.
|
94
|
+
|
95
|
+
Args:
|
96
|
+
token_response (dict): The token response data.
|
97
|
+
|
98
|
+
Returns:
|
99
|
+
Bfabric: An authenticated Bfabric instance.
|
100
|
+
"""
|
101
|
+
|
102
|
+
bfabric_auth = BfabricAuth(login=token_response.get('user_data'), password=token_response.get('userWsPassword'))
|
103
|
+
bfabric_client_config = BfabricClientConfig(base_url=token_response.get('webbase_data'))
|
104
|
+
|
105
|
+
bfabric_wrapper = bfabric.Bfabric(config=bfabric_client_config, auth=bfabric_auth)
|
106
|
+
|
107
|
+
return bfabric_wrapper
|
108
|
+
|
109
|
+
|
110
|
+
|
111
|
+
|
112
|
+
def entity_data(self, token_data: dict) -> str:
|
113
|
+
|
114
|
+
"""
|
115
|
+
Retrieves entity data associated with the provided token.
|
116
|
+
|
117
|
+
Args:
|
118
|
+
token_data (dict): The token data.
|
119
|
+
|
120
|
+
Returns:
|
121
|
+
str: A JSON string containing entity data.
|
122
|
+
None: If the retrieval fails.
|
123
|
+
"""
|
124
|
+
|
125
|
+
entity_class_map = {
|
126
|
+
"Run": "run",
|
127
|
+
"Sample": "sample",
|
128
|
+
"Project": "container",
|
129
|
+
"Order": "container",
|
130
|
+
"Container": "container",
|
131
|
+
"Plate": "plate"
|
132
|
+
}
|
133
|
+
|
134
|
+
if not token_data:
|
135
|
+
return None
|
136
|
+
|
137
|
+
wrapper = self.token_response_to_bfabric(token_data)
|
138
|
+
entity_class = token_data.get('entityClass_data', None)
|
139
|
+
endpoint = entity_class_map.get(entity_class, None)
|
140
|
+
entity_id = token_data.get('entity_id_data', None)
|
141
|
+
jobId = token_data.get('jobId', None)
|
142
|
+
username = token_data.get("user_data", "None")
|
143
|
+
environment= token_data.get("environment", "None")
|
144
|
+
|
145
|
+
|
146
|
+
if wrapper and entity_class and endpoint and entity_id and jobId:
|
147
|
+
|
148
|
+
L = Logger(
|
149
|
+
jobid = jobId,
|
150
|
+
username= username,
|
151
|
+
environment= environment
|
152
|
+
)
|
153
|
+
|
154
|
+
# Log the read operation directly using Logger L
|
155
|
+
entity_data_dict = L.logthis(
|
156
|
+
api_call=wrapper.read,
|
157
|
+
endpoint=endpoint,
|
158
|
+
obj={"id": entity_id},
|
159
|
+
max_results=None,
|
160
|
+
params = None,
|
161
|
+
flush_logs = True
|
162
|
+
)[0]
|
163
|
+
|
164
|
+
if entity_data_dict:
|
165
|
+
json_data = json.dumps({
|
166
|
+
"name": entity_data_dict.get("name", ""),
|
167
|
+
"createdby": entity_data_dict.get("createdby"),
|
168
|
+
"created": entity_data_dict.get("created"),
|
169
|
+
"modified": entity_data_dict.get("modified"),
|
170
|
+
})
|
171
|
+
return json_data
|
172
|
+
else:
|
173
|
+
L.log_operation(
|
174
|
+
operation= "entity_data",
|
175
|
+
message= "Entity data retrieval failed or returned None.",
|
176
|
+
params=None,
|
177
|
+
flush_logs=True
|
178
|
+
)
|
179
|
+
print("entity_data_dict is empty or None")
|
180
|
+
return None
|
181
|
+
|
182
|
+
else:
|
183
|
+
print("Invalid input or entity information")
|
184
|
+
return None
|
185
|
+
|
186
|
+
|
187
|
+
def send_bug_report(self, token_data = None, entity_data = None, description = None):
|
188
|
+
"""
|
189
|
+
Sends a bug report via email.
|
190
|
+
|
191
|
+
Args:
|
192
|
+
token_data (dict): Token data to include in the report.
|
193
|
+
entity_data (dict): Entity data to include in the report.
|
194
|
+
description (str): A description of the bug.
|
195
|
+
|
196
|
+
Returns:
|
197
|
+
bool: True if the report is sent successfully, False otherwise.
|
198
|
+
"""
|
199
|
+
|
200
|
+
mail_string = f"""
|
201
|
+
BUG REPORT FROM QC-UPLOADER
|
202
|
+
\n\n
|
203
|
+
token_data: {token_data} \n\n
|
204
|
+
entity_data: {entity_data} \n\n
|
205
|
+
description: {description} \n\n
|
206
|
+
sent_at: {datetime.datetime.now()} \n\n
|
207
|
+
"""
|
208
|
+
|
209
|
+
mail = f"""
|
210
|
+
echo "{mail_string}" | mail -s "Bug Report" gwtools@fgcz.system
|
211
|
+
"""
|
212
|
+
|
213
|
+
print("MAIL STRING:")
|
214
|
+
print(mail_string)
|
215
|
+
|
216
|
+
print("MAIL:")
|
217
|
+
print(mail)
|
218
|
+
|
219
|
+
os.system(mail)
|
220
|
+
|
221
|
+
return True
|
222
|
+
|
@@ -1,47 +1,80 @@
|
|
1
1
|
import os
|
2
2
|
import pickle
|
3
|
+
from typing import List
|
3
4
|
from bfabric import Bfabric
|
4
5
|
from datetime import datetime as dt
|
5
6
|
import base64
|
7
|
+
from ..utils.app_config import load_config
|
6
8
|
|
7
|
-
|
8
|
-
from PARAMS import CONFIG_FILE_PATH
|
9
|
-
except ImportError:
|
10
|
-
CONFIG_FILE_PATH = "~/.bfabricpy.yml"
|
11
|
-
|
9
|
+
CONFIG_FILE_PATH = load_config()["CONFIG_FILE_PATH"]
|
12
10
|
|
13
11
|
class Logger:
|
14
12
|
"""
|
15
13
|
A Logger class to manage and batch API call logs locally and flush them to the backend when needed.
|
16
14
|
"""
|
17
|
-
def __init__(self, jobid: int, username: str):
|
15
|
+
def __init__(self, jobid: int, username: str, environment: str):
|
16
|
+
"""
|
17
|
+
Initializes the Logger with a job ID, username, and environment.
|
18
|
+
|
19
|
+
Args:
|
20
|
+
jobid (int): The ID of the current job.
|
21
|
+
username (str): The name of the user performing the operations.
|
22
|
+
environment (str): The environment (e.g., Production, Test).
|
23
|
+
"""
|
18
24
|
self.jobid = jobid
|
19
25
|
self.username = username
|
20
|
-
self.power_user_wrapper = self._get_power_user_wrapper()
|
26
|
+
self.power_user_wrapper = self._get_power_user_wrapper(environment)
|
21
27
|
self.logs = []
|
22
28
|
|
23
|
-
def _get_power_user_wrapper(self) -> Bfabric:
|
29
|
+
def _get_power_user_wrapper(self, environment) -> Bfabric:
|
24
30
|
"""
|
25
31
|
Initializes a B-Fabric wrapper using the power user's credentials.
|
32
|
+
|
33
|
+
Args:
|
34
|
+
environment (str): The environment in which to initialize the wrapper.
|
35
|
+
|
36
|
+
Returns:
|
37
|
+
Bfabric: An authenticated Bfabric instance.
|
26
38
|
"""
|
27
39
|
power_user_wrapper = Bfabric.from_config(
|
28
|
-
config_path=os.path.expanduser(CONFIG_FILE_PATH)
|
40
|
+
config_path = os.path.expanduser(CONFIG_FILE_PATH),
|
41
|
+
config_env = environment.upper()
|
29
42
|
)
|
30
43
|
return power_user_wrapper
|
31
44
|
|
32
45
|
def to_pickle(self):
|
46
|
+
"""
|
47
|
+
Serializes the Logger object and encodes it as a base64 string.
|
48
|
+
|
49
|
+
Returns:
|
50
|
+
dict: A dictionary containing the base64-encoded pickle string.
|
51
|
+
"""
|
33
52
|
# Pickle the object and then encode it as a base64 string
|
34
53
|
return {"data": base64.b64encode(pickle.dumps(self)).decode('utf-8')}
|
35
54
|
|
36
55
|
@classmethod
|
37
56
|
def from_pickle(cls, pickle_object):
|
57
|
+
"""
|
58
|
+
Deserializes a Logger object from a base64-encoded pickle string.
|
59
|
+
|
60
|
+
Args:
|
61
|
+
pickle_object (dict): A dictionary containing the base64-encoded pickle string.
|
62
|
+
|
63
|
+
Returns:
|
64
|
+
Logger: The deserialized Logger object.
|
65
|
+
"""
|
38
66
|
# Decode the base64 string back to bytes and then unpickle
|
39
67
|
return pickle.loads(base64.b64decode(pickle_object.get("data").encode('utf-8')))
|
40
68
|
|
41
69
|
def log_operation(self, operation: str, message: str, params = None, flush_logs: bool = True):
|
42
70
|
"""
|
43
|
-
|
44
|
-
|
71
|
+
Logs an operation locally or flushes it to the backend.
|
72
|
+
|
73
|
+
Args:
|
74
|
+
operation (str): The name of the operation being logged.
|
75
|
+
message (str): A detailed message about the operation.
|
76
|
+
params (dict, optional): Additional parameters to log. Defaults to None.
|
77
|
+
flush_logs (bool, optional): Whether to immediately flush the logs to the backend. Defaults to True.
|
45
78
|
"""
|
46
79
|
# Define the timestamp format
|
47
80
|
timestamp = dt.now().strftime('%Y-%m-%d %H:%M:%S')
|
@@ -83,7 +116,17 @@ class Logger:
|
|
83
116
|
|
84
117
|
def logthis(self, api_call: callable, *args, params=None , flush_logs: bool = True, **kwargs) -> any:
|
85
118
|
"""
|
86
|
-
|
119
|
+
Wraps an API call with logging functionality.
|
120
|
+
|
121
|
+
Args:
|
122
|
+
api_call (callable): The API call to be logged and executed.
|
123
|
+
*args: Positional arguments to pass to the API call.
|
124
|
+
params (dict, optional): Additional parameters to log. Defaults to None.
|
125
|
+
flush_logs (bool, optional): Whether to flush logs immediately. Defaults to True.
|
126
|
+
**kwargs: Keyword arguments to pass to the API call.
|
127
|
+
|
128
|
+
Returns:
|
129
|
+
any: The result of the API call.
|
87
130
|
"""
|
88
131
|
# Construct a message describing the API call
|
89
132
|
call_args = ', '.join([repr(arg) for arg in args])
|
@@ -0,0 +1,27 @@
|
|
1
|
+
import os
|
2
|
+
import importlib.util
|
3
|
+
|
4
|
+
def load_config(params_path="./PARAMS.py"):
|
5
|
+
"""Load configuration for the Dash app."""
|
6
|
+
if os.path.exists(params_path):
|
7
|
+
try:
|
8
|
+
# Dynamically import the PARAMS module
|
9
|
+
spec = importlib.util.spec_from_file_location("PARAMS", params_path)
|
10
|
+
params_module = importlib.util.module_from_spec(spec)
|
11
|
+
spec.loader.exec_module(params_module)
|
12
|
+
|
13
|
+
# Retrieve values with defaults
|
14
|
+
PORT = getattr(params_module, "PORT", 8050)
|
15
|
+
HOST = getattr(params_module, "HOST", "localhost")
|
16
|
+
DEV = getattr(params_module, "DEV", True)
|
17
|
+
CONFIG_FILE_PATH = getattr(params_module, "CONFIG_FILE_PATH", "~/.bfabricpy.yml")
|
18
|
+
except ImportError:
|
19
|
+
# Fallback to default values in case of import errors
|
20
|
+
PORT, HOST, DEV = 8050, 'localhost', True
|
21
|
+
CONFIG_FILE_PATH = "~/.bfabricpy.yml"
|
22
|
+
else:
|
23
|
+
# Fallback to default values if PARAMS.py is not found
|
24
|
+
PORT, HOST, DEV = 8050, 'localhost', True
|
25
|
+
CONFIG_FILE_PATH = "~/.bfabricpy.yml"
|
26
|
+
|
27
|
+
return {"PORT": PORT, "HOST": HOST, "DEV": DEV, "CONFIG_FILE_PATH": CONFIG_FILE_PATH}
|
@@ -0,0 +1,11 @@
|
|
1
|
+
from dash import Dash
|
2
|
+
import dash_bootstrap_components as dbc
|
3
|
+
|
4
|
+
def create_app():
|
5
|
+
"""Initialize and return a Dash app instance with suppressed callback exceptions."""
|
6
|
+
return Dash(
|
7
|
+
__name__,
|
8
|
+
suppress_callback_exceptions=True, # Allow dynamic callbacks
|
9
|
+
external_stylesheets=[dbc.themes.BOOTSTRAP],
|
10
|
+
meta_tags=[{"name": "viewport", "content": "width=device-width, initial-scale=1.0"}],
|
11
|
+
)
|
@@ -0,0 +1,152 @@
|
|
1
|
+
from dash import Input, Output, State, html, dcc
|
2
|
+
from bfabric_web_apps.objects.BfabricInterface import BfabricInterface
|
3
|
+
import json
|
4
|
+
import dash_bootstrap_components as dbc
|
5
|
+
from bfabric_web_apps.objects.Logger import Logger
|
6
|
+
from datetime import datetime as dt
|
7
|
+
|
8
|
+
def process_url_and_token(url_params):
|
9
|
+
"""
|
10
|
+
Processes URL parameters to extract the token, validates it, and retrieves the corresponding data.
|
11
|
+
|
12
|
+
Args:
|
13
|
+
url_params (str): The URL parameters containing the token.
|
14
|
+
base_title (str): The base title of the page.
|
15
|
+
|
16
|
+
Returns:
|
17
|
+
tuple: A tuple containing token data, entity data, and the page content.
|
18
|
+
(token, token_data, entity_data, page_content, page_title)
|
19
|
+
"""
|
20
|
+
base_title = " "
|
21
|
+
|
22
|
+
if not url_params:
|
23
|
+
return None, None, None, base_title, None
|
24
|
+
|
25
|
+
token = "".join(url_params.split('token=')[1:])
|
26
|
+
bfabric_interface = BfabricInterface()
|
27
|
+
tdata_raw = bfabric_interface.token_to_data(token)
|
28
|
+
|
29
|
+
if tdata_raw:
|
30
|
+
if tdata_raw == "EXPIRED":
|
31
|
+
return None, None, None, base_title, None
|
32
|
+
else:
|
33
|
+
tdata = json.loads(tdata_raw)
|
34
|
+
else:
|
35
|
+
return None, None, None, base_title, None
|
36
|
+
|
37
|
+
if tdata:
|
38
|
+
entity_data_json = bfabric_interface.entity_data(tdata)
|
39
|
+
entity_data = json.loads(entity_data_json)
|
40
|
+
page_title = (
|
41
|
+
f"{tdata['entityClass_data']} - {entity_data['name']} "
|
42
|
+
f"({tdata['environment']} System)"
|
43
|
+
) if tdata else "Bfabric App Interface"
|
44
|
+
|
45
|
+
if not entity_data:
|
46
|
+
return token, tdata, None, page_title, None
|
47
|
+
else:
|
48
|
+
session_details = [
|
49
|
+
html.P([
|
50
|
+
html.B("Entity Name: "), entity_data['name'],
|
51
|
+
html.Br(),
|
52
|
+
html.B("Entity Class: "), tdata['entityClass_data'],
|
53
|
+
html.Br(),
|
54
|
+
html.B("Environment: "), tdata['environment'],
|
55
|
+
html.Br(),
|
56
|
+
html.B("Entity ID: "), tdata['entity_id_data'],
|
57
|
+
html.Br(),
|
58
|
+
html.B("User Name: "), tdata['user_data'],
|
59
|
+
html.Br(),
|
60
|
+
html.B("Session Expires: "), tdata['token_expires'],
|
61
|
+
html.Br(),
|
62
|
+
html.B("Current Time: "), str(dt.now().strftime("%Y-%m-%d %H:%M:%S"))
|
63
|
+
])
|
64
|
+
]
|
65
|
+
return token, tdata, entity_data, page_title, session_details
|
66
|
+
else:
|
67
|
+
return None, None, None, base_title, None
|
68
|
+
|
69
|
+
|
70
|
+
def submit_bug_report(n_clicks, bug_description, token, entity_data):
|
71
|
+
"""
|
72
|
+
Submits a bug report based on user input, token, and entity data.
|
73
|
+
|
74
|
+
Args:
|
75
|
+
n_clicks (int): The number of times the submit button has been clicked.
|
76
|
+
bug_description (str): The description of the bug provided by the user.
|
77
|
+
token (str): The authentication token.
|
78
|
+
entity_data (dict): The data related to the current entity.
|
79
|
+
|
80
|
+
Returns:
|
81
|
+
tuple: A tuple containing two boolean values indicating success and failure status of the submission.
|
82
|
+
(is_open_success, is_open_failure)
|
83
|
+
"""
|
84
|
+
bfabric_interface = BfabricInterface()
|
85
|
+
print("submit bug report", token)
|
86
|
+
|
87
|
+
# Parse token data if token is provided, otherwise set it to an empty dictionary
|
88
|
+
if token:
|
89
|
+
token_data = json.loads(bfabric_interface.token_to_data(token))
|
90
|
+
else:
|
91
|
+
token_data = {}
|
92
|
+
|
93
|
+
print(token_data)
|
94
|
+
|
95
|
+
# Extract logging-related information from token_data, with defaults for missing values
|
96
|
+
jobId = token_data.get('jobId', None)
|
97
|
+
username = token_data.get("user_data", "None")
|
98
|
+
environment = token_data.get("environment", "None")
|
99
|
+
|
100
|
+
# Initialize the logger only if token_data is available
|
101
|
+
L = None
|
102
|
+
if token_data:
|
103
|
+
L = Logger(
|
104
|
+
jobid=jobId,
|
105
|
+
username=username,
|
106
|
+
environment=environment
|
107
|
+
)
|
108
|
+
|
109
|
+
if n_clicks:
|
110
|
+
# Log the operation only if the logger is initialized
|
111
|
+
if L:
|
112
|
+
L.log_operation(
|
113
|
+
"bug report",
|
114
|
+
"Initiating bug report submission process.",
|
115
|
+
params=None,
|
116
|
+
flush_logs=False,
|
117
|
+
)
|
118
|
+
try:
|
119
|
+
sending_result = bfabric_interface.send_bug_report(
|
120
|
+
token_data, entity_data, bug_description
|
121
|
+
)
|
122
|
+
|
123
|
+
if sending_result:
|
124
|
+
if L:
|
125
|
+
L.log_operation(
|
126
|
+
"bug report",
|
127
|
+
f"Bug report successfully submitted. | DESCRIPTION: {bug_description}",
|
128
|
+
params=None,
|
129
|
+
flush_logs=True,
|
130
|
+
)
|
131
|
+
return True, False
|
132
|
+
else:
|
133
|
+
if L:
|
134
|
+
L.log_operation(
|
135
|
+
"bug report",
|
136
|
+
"Failed to submit bug report!",
|
137
|
+
params=None,
|
138
|
+
flush_logs=True,
|
139
|
+
)
|
140
|
+
return False, True
|
141
|
+
except Exception as e:
|
142
|
+
if L:
|
143
|
+
L.log_operation(
|
144
|
+
"bug report",
|
145
|
+
f"Failed to submit bug report! Error: {str(e)}",
|
146
|
+
params=None,
|
147
|
+
flush_logs=True,
|
148
|
+
)
|
149
|
+
return False, True
|
150
|
+
|
151
|
+
return False, False
|
152
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
from dash import html
|
2
|
+
|
3
|
+
DEVELOPER_EMAIL = "gwhite@fgcz.ethz.ch"
|
4
|
+
|
5
|
+
expired = [
|
6
|
+
html.P("Your session has expired. Please log into bfabric to continue:"),
|
7
|
+
html.A('Login to Bfabric', href='https://fgcz-bfabric.uzh.ch/bfabric/')
|
8
|
+
]
|
9
|
+
|
10
|
+
no_entity = [
|
11
|
+
html.P("There was an error fetching the data for your entity. Please try accessing the applicaiton again from bfabric:"),
|
12
|
+
html.A('Login to Bfabric', href='https://fgcz-bfabric.uzh.ch/bfabric/')
|
13
|
+
]
|
14
|
+
|
15
|
+
dev = [html.P("This page is under development. Please check back later."),html.Br(),html.A("email the developer for more details",href="mailto:"+DEVELOPER_EMAIL)]
|
16
|
+
|
17
|
+
auth = [html.Div(id="auth-div")]
|
18
|
+
|
19
|
+
no_auth = [
|
20
|
+
html.P("You are not currently logged into an active session. Please log into bfabric to continue:"),
|
21
|
+
html.A('Login to Bfabric', href='https://fgcz-bfabric.uzh.ch/bfabric/')
|
22
|
+
]
|
@@ -0,0 +1,14 @@
|
|
1
|
+
from bfabric_web_apps.objects.Logger import Logger
|
2
|
+
|
3
|
+
def get_logger(token_data):
|
4
|
+
|
5
|
+
""" Extract logging-related information from token_data, with defaults for missing values """
|
6
|
+
jobId = token_data.get('jobId', None)
|
7
|
+
username = token_data.get("user_data", "None")
|
8
|
+
environment = token_data.get("environment", "None")
|
9
|
+
|
10
|
+
return Logger(
|
11
|
+
jobid=jobId,
|
12
|
+
username=username,
|
13
|
+
environment=environment
|
14
|
+
)
|
@@ -0,0 +1,29 @@
|
|
1
|
+
import os
|
2
|
+
from bfabric import Bfabric
|
3
|
+
from .app_config import load_config
|
4
|
+
|
5
|
+
CONFIG_FILE_PATH = load_config()["CONFIG_FILE_PATH"]
|
6
|
+
|
7
|
+
def get_power_user_wrapper(token_data):
|
8
|
+
"""
|
9
|
+
Initializes and returns a Bfabric power user instance configured for a specific environment.
|
10
|
+
|
11
|
+
This function retrieves the environment information from the provided `token_data`
|
12
|
+
and uses it to initialize a Bfabric instance. The configuration file path is
|
13
|
+
determined by the `CONFIG_FILE_PATH` from the application's configuration.
|
14
|
+
|
15
|
+
Args:
|
16
|
+
token_data (dict): A dictionary containing token information.
|
17
|
+
The key "environment" is used to determine the environment
|
18
|
+
(default is "None" if not specified).
|
19
|
+
|
20
|
+
Returns:
|
21
|
+
Bfabric: A Bfabric instance initialized with the configuration
|
22
|
+
corresponding to the specified environment.
|
23
|
+
"""
|
24
|
+
environment = token_data.get("environment", "None")
|
25
|
+
|
26
|
+
return Bfabric.from_config(
|
27
|
+
config_path = os.path.expanduser(CONFIG_FILE_PATH),
|
28
|
+
config_env = environment.upper()
|
29
|
+
)
|
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2024 GWC GmbH
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
@@ -1,10 +1,11 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: bfabric-web-apps
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.1
|
4
4
|
Summary: A package containing handy boilerplate utilities for developing bfabric web-applications
|
5
|
-
Author:
|
5
|
+
Author: Marc Zuber, Griffin White, GWC GmbH
|
6
6
|
Requires-Python: >=3.8,<4.0
|
7
7
|
Classifier: Programming Language :: Python :: 3
|
8
|
-
Classifier: Programming Language :: Python :: 3.10
|
9
8
|
Classifier: Programming Language :: Python :: 3.8
|
10
9
|
Classifier: Programming Language :: Python :: 3.9
|
10
|
+
Classifier: Programming Language :: Python :: 3.10
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
@@ -0,0 +1,14 @@
|
|
1
|
+
bfabric_web_apps/__init__.py,sha256=XDzAg0boEE3nj3RXGB85YbVc0KgEeRNOcQfrSkJoV-0,880
|
2
|
+
bfabric_web_apps/layouts/layouts.py,sha256=oQHfT65PdoFU5js8JkEJqFhxfgNjwU0GRvD1xbkfYD4,8758
|
3
|
+
bfabric_web_apps/objects/BfabricInterface.py,sha256=pHse1UgC_XFD-PsYqOs0Ho3xTRMDRCvtRgsILVYy-0k,6919
|
4
|
+
bfabric_web_apps/objects/Logger.py,sha256=ikMJJZYDPhJZEmgCY63_htGmPvXv1ItSmfb_xiFFZMQ,5225
|
5
|
+
bfabric_web_apps/utils/app_config.py,sha256=u7lz8sonf9CSkJsVK0nhTzIls227FksGCOb_SzYE_fE,1204
|
6
|
+
bfabric_web_apps/utils/app_init.py,sha256=RCdpCXp19cF74bouYJLPe-KSETZ0Vwqtd02Ta2VXEF8,428
|
7
|
+
bfabric_web_apps/utils/callbacks.py,sha256=OlNelwAJUKsEtnaJ4OuTdrgyu9PhEgm4uqCde4NNKHA,5270
|
8
|
+
bfabric_web_apps/utils/components.py,sha256=V7ECGmF2XYy5O9ciDJVH1nofJYP2a_ELQF3z3X_ADbo,844
|
9
|
+
bfabric_web_apps/utils/get_logger.py,sha256=_cn0en-itaGEeeCDtws-nw2ubd36w2xc6VfDNoBMFu4,433
|
10
|
+
bfabric_web_apps/utils/get_power_user_wrapper.py,sha256=ZUrv-xNLEXtMVeFEpAwmPbPFUmcAxu3hITIuPY2dhF8,1078
|
11
|
+
bfabric_web_apps-0.1.1.dist-info/LICENSE,sha256=k0O_i2k13i9e35aO-j7FerJafAqzzu8x0kkBs0OWF3c,1065
|
12
|
+
bfabric_web_apps-0.1.1.dist-info/METADATA,sha256=R1zgL1bVK18CV4uPQc1vUABgBnOn6us0YBbcC6yqy0g,480
|
13
|
+
bfabric_web_apps-0.1.1.dist-info/WHEEL,sha256=d2fvjOD7sXsVzChCqf0Ty0JbHKBaLYwDbGQDwQTnJ50,88
|
14
|
+
bfabric_web_apps-0.1.1.dist-info/RECORD,,
|
File without changes
|
@@ -1,8 +0,0 @@
|
|
1
|
-
bfabric_web_apps/__init__.py,sha256=6IFMcIq8biYvtMafM-gELPRBq19k5kHRlEYmRTsXlYI,87
|
2
|
-
bfabric_web_apps/layouts/layouts.py,sha256=6b5_wk2fk1ZLyWhhhKfz2EnsSdZtlPx8aR-pMfPC1t0,135
|
3
|
-
bfabric_web_apps/objects/BfabricInterface.py,sha256=IU1D1Q6ZdAR0oo64Fpcru9LWVj-3Ydmlti6Vy_-VSj4,275
|
4
|
-
bfabric_web_apps/objects/Logger.py,sha256=Sikkqfbkp7NGqEkFZ4cHKtjGX1Bu9Y8sf1PWDLOOjco,3467
|
5
|
-
bfabric_web_apps/objects/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
6
|
-
bfabric_web_apps-0.1.0.dist-info/WHEEL,sha256=y3eDiaFVSNTPbgzfNn0nYn5tEn1cX6WrdetDlQM4xWw,83
|
7
|
-
bfabric_web_apps-0.1.0.dist-info/METADATA,sha256=9GcQM9qWxhYBDHgmq0r0Gu9mAmvvn0Lxy0YJrfFCiPc,429
|
8
|
-
bfabric_web_apps-0.1.0.dist-info/RECORD,,
|