bec-widgets 0.66.1__py3-none-any.whl → 0.68.0__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.
- CHANGELOG.md +42 -40
- PKG-INFO +1 -1
- bec_widgets/cli/client.py +82 -3
- bec_widgets/cli/client_utils.py +46 -34
- bec_widgets/cli/server.py +61 -18
- bec_widgets/utils/bec_dispatcher.py +14 -6
- bec_widgets/widgets/bec_status_box/__init__.py +0 -0
- bec_widgets/widgets/bec_status_box/bec_status_box.py +352 -0
- bec_widgets/widgets/bec_status_box/status_item.py +171 -0
- {bec_widgets-0.66.1.dist-info → bec_widgets-0.68.0.dist-info}/METADATA +1 -1
- {bec_widgets-0.66.1.dist-info → bec_widgets-0.68.0.dist-info}/RECORD +24 -18
- docs/developer/widgets/widgets.md +0 -1
- docs/requirements.txt +1 -0
- docs/user/getting_started/installation.md +2 -2
- docs/user/widgets/bec_status_box.gif +0 -0
- docs/user/widgets/bec_status_box.md +30 -0
- docs/user/widgets/buttons.md +0 -1
- docs/user/widgets/widgets.md +2 -0
- pyproject.toml +1 -1
- tests/end-2-end/conftest.py +1 -4
- tests/unit_tests/test_bec_status_box.py +152 -0
- {bec_widgets-0.66.1.dist-info → bec_widgets-0.68.0.dist-info}/WHEEL +0 -0
- {bec_widgets-0.66.1.dist-info → bec_widgets-0.68.0.dist-info}/entry_points.txt +0 -0
- {bec_widgets-0.66.1.dist-info → bec_widgets-0.68.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,352 @@
|
|
1
|
+
"""This module contains the BECStatusBox widget, which displays the status of different BEC services in a collapsible tree widget.
|
2
|
+
The widget automatically updates the status of all running BEC services, and displays their status.
|
3
|
+
"""
|
4
|
+
|
5
|
+
from __future__ import annotations
|
6
|
+
|
7
|
+
import sys
|
8
|
+
from typing import TYPE_CHECKING
|
9
|
+
|
10
|
+
import qdarktheme
|
11
|
+
from bec_lib.utils.import_utils import lazy_import_from
|
12
|
+
from pydantic import BaseModel, Field, field_validator
|
13
|
+
from qtpy.QtCore import QObject, QTimer, Signal, Slot
|
14
|
+
from qtpy.QtWidgets import QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget
|
15
|
+
|
16
|
+
from bec_widgets.utils.bec_connector import BECConnector, ConnectionConfig
|
17
|
+
from bec_widgets.widgets.bec_status_box.status_item import StatusItem
|
18
|
+
|
19
|
+
if TYPE_CHECKING:
|
20
|
+
from bec_lib.client import BECClient
|
21
|
+
|
22
|
+
# TODO : Put normal imports back when Pydantic gets faster
|
23
|
+
BECStatus = lazy_import_from("bec_lib.messages", ("BECStatus",))
|
24
|
+
|
25
|
+
|
26
|
+
class BECStatusBoxConfig(ConnectionConfig):
|
27
|
+
pass
|
28
|
+
|
29
|
+
|
30
|
+
class BECServiceInfoContainer(BaseModel):
|
31
|
+
"""Container to store information about the BEC services."""
|
32
|
+
|
33
|
+
service_name: str
|
34
|
+
status: BECStatus | str = Field(
|
35
|
+
default="NOTCONNECTED",
|
36
|
+
description="The status of the service. Can be any of the BECStatus names, or NOTCONNECTED.",
|
37
|
+
)
|
38
|
+
info: dict
|
39
|
+
metrics: dict | None
|
40
|
+
model_config: dict = {"validate_assignment": True}
|
41
|
+
|
42
|
+
@field_validator("status")
|
43
|
+
@classmethod
|
44
|
+
def validate_status(cls, v):
|
45
|
+
"""Validate input for status. Accept BECStatus and NOTCONNECTED.
|
46
|
+
|
47
|
+
Args:
|
48
|
+
v (BECStatus | str): The input value.
|
49
|
+
|
50
|
+
Returns:
|
51
|
+
str: The validated status.
|
52
|
+
"""
|
53
|
+
if v in list(BECStatus.__members__.values()):
|
54
|
+
return v.name
|
55
|
+
if v in list(BECStatus.__members__.keys()) or v == "NOTCONNECTED":
|
56
|
+
return v
|
57
|
+
raise ValueError(
|
58
|
+
f"Status must be one of {BECStatus.__members__.values()} or 'NOTCONNECTED'. Input {v}"
|
59
|
+
)
|
60
|
+
|
61
|
+
|
62
|
+
class BECServiceStatusMixin(QObject):
|
63
|
+
"""A mixin class to update the service status, and metrics.
|
64
|
+
It emits a signal 'services_update' when the service status is updated.
|
65
|
+
|
66
|
+
Args:
|
67
|
+
client (BECClient): The client object to connect to the BEC server.
|
68
|
+
"""
|
69
|
+
|
70
|
+
services_update = Signal(dict, dict)
|
71
|
+
|
72
|
+
def __init__(self, client: BECClient):
|
73
|
+
super().__init__()
|
74
|
+
self.client = client
|
75
|
+
self._service_update_timer = QTimer()
|
76
|
+
self._service_update_timer.timeout.connect(self._get_service_status)
|
77
|
+
self._service_update_timer.start(1000)
|
78
|
+
|
79
|
+
def _get_service_status(self):
|
80
|
+
"""Pull latest service and metrics updates from REDIS for all services, and emit both via 'services_update' signal."""
|
81
|
+
# pylint: disable=protected-access
|
82
|
+
self.client._update_existing_services()
|
83
|
+
self.services_update.emit(self.client._services_info, self.client._services_metric)
|
84
|
+
|
85
|
+
|
86
|
+
class BECStatusBox(BECConnector, QTreeWidget):
|
87
|
+
"""A widget to display the status of different BEC services.
|
88
|
+
This widget automatically updates the status of all running BEC services, and displays their status.
|
89
|
+
Information about the individual services is collapsible, and double clicking on
|
90
|
+
the individual service will display the metrics about the service.
|
91
|
+
|
92
|
+
Args:
|
93
|
+
parent Optional : The parent widget for the BECStatusBox. Defaults to None.
|
94
|
+
service_name Optional(str): The name of the top service label. Defaults to "BEC Server".
|
95
|
+
client Optional(BECClient): The client object to connect to the BEC server. Defaults to None
|
96
|
+
config Optional(BECStatusBoxConfig | dict): The configuration for the status box. Defaults to None.
|
97
|
+
gui_id Optional(str): The unique id for the widget. Defaults to None.
|
98
|
+
"""
|
99
|
+
|
100
|
+
CORE_SERVICES = ["DeviceServer", "ScanServer", "SciHub", "ScanBundler", "FileWriterManager"]
|
101
|
+
|
102
|
+
service_update = Signal(dict)
|
103
|
+
bec_core_state = Signal(str)
|
104
|
+
|
105
|
+
def __init__(
|
106
|
+
self,
|
107
|
+
parent=None,
|
108
|
+
service_name: str = "BEC Server",
|
109
|
+
client: BECClient = None,
|
110
|
+
config: BECStatusBoxConfig | dict = None,
|
111
|
+
gui_id: str = None,
|
112
|
+
):
|
113
|
+
if config is None:
|
114
|
+
config = BECStatusBoxConfig(widget_class=self.__class__.__name__)
|
115
|
+
else:
|
116
|
+
if isinstance(config, dict):
|
117
|
+
config = BECStatusBoxConfig(**config)
|
118
|
+
super().__init__(client=client, config=config, gui_id=gui_id)
|
119
|
+
QTreeWidget.__init__(self, parent=parent)
|
120
|
+
|
121
|
+
self.service_name = service_name
|
122
|
+
self.config = config
|
123
|
+
|
124
|
+
self.bec_service_info_container = {}
|
125
|
+
self.tree_items = {}
|
126
|
+
self.tree_top_item = None
|
127
|
+
self.bec_service_status = BECServiceStatusMixin(client=self.client)
|
128
|
+
|
129
|
+
self.init_ui()
|
130
|
+
self.bec_service_status.services_update.connect(self.update_service_status)
|
131
|
+
self.bec_core_state.connect(self.update_top_item_status)
|
132
|
+
self.itemDoubleClicked.connect(self.on_tree_item_double_clicked)
|
133
|
+
|
134
|
+
def init_ui(self) -> None:
|
135
|
+
"""Initialize the UI for the status box, and add QTreeWidget as the basis for the status box."""
|
136
|
+
self.init_ui_tree_widget()
|
137
|
+
top_label = self._create_status_widget(self.service_name, status=BECStatus.IDLE)
|
138
|
+
self.tree_top_item = QTreeWidgetItem()
|
139
|
+
self.tree_top_item.setExpanded(True)
|
140
|
+
self.tree_top_item.setDisabled(True)
|
141
|
+
self.addTopLevelItem(self.tree_top_item)
|
142
|
+
self.setItemWidget(self.tree_top_item, 0, top_label)
|
143
|
+
self.service_update.connect(top_label.update_config)
|
144
|
+
|
145
|
+
def _create_status_widget(
|
146
|
+
self, service_name: str, status=BECStatus, info: dict = None, metrics: dict = None
|
147
|
+
) -> StatusItem:
|
148
|
+
"""Creates a StatusItem (QWidget) for the given service, and stores all relevant
|
149
|
+
information about the service in the bec_service_info_container.
|
150
|
+
|
151
|
+
Args:
|
152
|
+
service_name (str): The name of the service.
|
153
|
+
status (BECStatus): The status of the service.
|
154
|
+
info Optional(dict): The information about the service. Default is {}
|
155
|
+
metric Optional(dict): Metrics for the respective service. Default is None
|
156
|
+
|
157
|
+
Returns:
|
158
|
+
StatusItem: The status item widget.
|
159
|
+
"""
|
160
|
+
if info is None:
|
161
|
+
info = {}
|
162
|
+
self._update_bec_service_container(service_name, status, info, metrics)
|
163
|
+
item = StatusItem(
|
164
|
+
parent=self,
|
165
|
+
config={
|
166
|
+
"service_name": service_name,
|
167
|
+
"status": status.name,
|
168
|
+
"info": info,
|
169
|
+
"metrics": metrics,
|
170
|
+
},
|
171
|
+
)
|
172
|
+
return item
|
173
|
+
|
174
|
+
@Slot(str)
|
175
|
+
def update_top_item_status(self, status: BECStatus) -> None:
|
176
|
+
"""Method to update the status of the top item in the tree widget.
|
177
|
+
Gets the status from the Signal 'bec_core_state' and updates the StatusItem via the signal 'service_update'.
|
178
|
+
|
179
|
+
Args:
|
180
|
+
status (BECStatus): The state of the core services.
|
181
|
+
"""
|
182
|
+
self.bec_service_info_container[self.service_name].status = status
|
183
|
+
self.service_update.emit(self.bec_service_info_container[self.service_name].model_dump())
|
184
|
+
|
185
|
+
def _update_bec_service_container(
|
186
|
+
self, service_name: str, status: BECStatus, info: dict, metrics: dict = None
|
187
|
+
) -> None:
|
188
|
+
"""Update the bec_service_info_container with the newest status and metrics for the BEC service.
|
189
|
+
If information about the service already exists, it will create a new entry.
|
190
|
+
|
191
|
+
Args:
|
192
|
+
service_name (str): The name of the service.
|
193
|
+
service_info (StatusMessage): A class containing the service status.
|
194
|
+
service_metric (ServiceMetricMessage): A class containing the service metrics.
|
195
|
+
"""
|
196
|
+
container = self.bec_service_info_container.get(service_name, None)
|
197
|
+
if container:
|
198
|
+
container.status = status
|
199
|
+
container.info = info
|
200
|
+
container.metrics = metrics
|
201
|
+
return
|
202
|
+
service_info_item = BECServiceInfoContainer(
|
203
|
+
service_name=service_name, status=status, info=info, metrics=metrics
|
204
|
+
)
|
205
|
+
self.bec_service_info_container.update({service_name: service_info_item})
|
206
|
+
|
207
|
+
@Slot(dict, dict)
|
208
|
+
def update_service_status(self, services_info: dict, services_metric: dict) -> None:
|
209
|
+
"""Callback function services_metric from BECServiceStatusMixin.
|
210
|
+
It updates the status of all services.
|
211
|
+
|
212
|
+
Args:
|
213
|
+
services_info (dict): A dictionary containing the service status for all running BEC services.
|
214
|
+
services_metric (dict): A dictionary containing the service metrics for all running BEC services.
|
215
|
+
"""
|
216
|
+
checked = []
|
217
|
+
services_info = self.update_core_services(services_info, services_metric)
|
218
|
+
checked.extend(self.CORE_SERVICES)
|
219
|
+
|
220
|
+
for service_name, msg in sorted(services_info.items()):
|
221
|
+
checked.append(service_name)
|
222
|
+
metric_msg = services_metric.get(service_name, None)
|
223
|
+
metrics = metric_msg.metrics if metric_msg else None
|
224
|
+
if service_name in self.tree_items:
|
225
|
+
self._update_bec_service_container(
|
226
|
+
service_name=service_name, status=msg.status, info=msg.info, metrics=metrics
|
227
|
+
)
|
228
|
+
self.service_update.emit(self.bec_service_info_container[service_name].model_dump())
|
229
|
+
continue
|
230
|
+
|
231
|
+
item_widget = self._create_status_widget(
|
232
|
+
service_name=service_name, status=msg.status, info=msg.info, metrics=metrics
|
233
|
+
)
|
234
|
+
item = QTreeWidgetItem()
|
235
|
+
item.setDisabled(True)
|
236
|
+
self.service_update.connect(item_widget.update_config)
|
237
|
+
self.tree_top_item.addChild(item)
|
238
|
+
self.setItemWidget(item, 0, item_widget)
|
239
|
+
self.tree_items.update({service_name: (item, item_widget)})
|
240
|
+
|
241
|
+
self.check_redundant_tree_items(checked)
|
242
|
+
|
243
|
+
def update_core_services(self, services_info: dict, services_metric: dict) -> dict:
|
244
|
+
"""Method to process status and metrics updates of core services (stored in CORE_SERVICES).
|
245
|
+
If a core services is not connected, it should not be removed from the status widget
|
246
|
+
|
247
|
+
Args:
|
248
|
+
services_info (dict): A dictionary containing the service status of different services.
|
249
|
+
services_metric (dict): A dictionary containing the service metrics of different services.
|
250
|
+
|
251
|
+
Returns:
|
252
|
+
dict: The services_info dictionary after removing the info updates related to the CORE_SERVICES
|
253
|
+
"""
|
254
|
+
bec_core_state = "RUNNING"
|
255
|
+
for service_name in sorted(self.CORE_SERVICES):
|
256
|
+
metric_msg = services_metric.get(service_name, None)
|
257
|
+
metrics = metric_msg.metrics if metric_msg else None
|
258
|
+
if service_name not in services_info:
|
259
|
+
self.bec_service_info_container[service_name].status = "NOTCONNECTED"
|
260
|
+
bec_core_state = "ERROR"
|
261
|
+
else:
|
262
|
+
msg = services_info.pop(service_name)
|
263
|
+
self._update_bec_service_container(
|
264
|
+
service_name=service_name, status=msg.status, info=msg.info, metrics=metrics
|
265
|
+
)
|
266
|
+
bec_core_state = (
|
267
|
+
"RUNNING" if (msg.status.value > 1 and bec_core_state == "RUNNING") else "ERROR"
|
268
|
+
)
|
269
|
+
|
270
|
+
if service_name in self.tree_items:
|
271
|
+
self.service_update.emit(self.bec_service_info_container[service_name].model_dump())
|
272
|
+
continue
|
273
|
+
self.add_tree_item(service_name, msg.status, msg.info, metrics)
|
274
|
+
|
275
|
+
self.bec_core_state.emit(bec_core_state)
|
276
|
+
return services_info
|
277
|
+
|
278
|
+
def check_redundant_tree_items(self, checked: list) -> None:
|
279
|
+
"""Utility method to check and remove redundant objects from the BECStatusBox.
|
280
|
+
|
281
|
+
Args:
|
282
|
+
checked (list): A list of services that are currently running.
|
283
|
+
"""
|
284
|
+
to_be_deleted = [key for key in self.tree_items if key not in checked]
|
285
|
+
|
286
|
+
for key in to_be_deleted:
|
287
|
+
item, _ = self.tree_items.pop(key)
|
288
|
+
self.tree_top_item.removeChild(item)
|
289
|
+
|
290
|
+
def add_tree_item(
|
291
|
+
self, service_name: str, status: BECStatus, info: dict = None, metrics: dict = None
|
292
|
+
) -> None:
|
293
|
+
"""Method to add a new QTreeWidgetItem together with a StatusItem to the tree widget.
|
294
|
+
|
295
|
+
Args:
|
296
|
+
service_name (str): The name of the service.
|
297
|
+
service_status_msg (StatusMessage): The status of the service.
|
298
|
+
metrics (dict): The metrics of the service.
|
299
|
+
"""
|
300
|
+
item_widget = self._create_status_widget(
|
301
|
+
service_name=service_name, status=status, info=info, metrics=metrics
|
302
|
+
)
|
303
|
+
item = QTreeWidgetItem()
|
304
|
+
self.service_update.connect(item_widget.update_config)
|
305
|
+
self.tree_top_item.addChild(item)
|
306
|
+
self.setItemWidget(item, 0, item_widget)
|
307
|
+
self.tree_items.update({service_name: (item, item_widget)})
|
308
|
+
|
309
|
+
def init_ui_tree_widget(self) -> None:
|
310
|
+
"""Initialise the tree widget for the status box."""
|
311
|
+
self.setHeaderHidden(True)
|
312
|
+
self.setStyleSheet(
|
313
|
+
"QTreeWidget::item:!selected "
|
314
|
+
"{ "
|
315
|
+
"border: 1px solid gainsboro; "
|
316
|
+
"border-left: none; "
|
317
|
+
"border-top: none; "
|
318
|
+
"}"
|
319
|
+
"QTreeWidget::item:selected {}"
|
320
|
+
)
|
321
|
+
|
322
|
+
@Slot(QTreeWidgetItem, int)
|
323
|
+
def on_tree_item_double_clicked(self, item: QTreeWidgetItem, column: int) -> None:
|
324
|
+
"""Callback function for double clicks on individual QTreeWidgetItems in the collapsed section.
|
325
|
+
|
326
|
+
Args:
|
327
|
+
item (QTreeWidgetItem): The item that was double clicked.
|
328
|
+
column (int): The column that was double clicked.
|
329
|
+
"""
|
330
|
+
for _, (tree_item, status_widget) in self.tree_items.items():
|
331
|
+
if tree_item == item:
|
332
|
+
status_widget.show_popup()
|
333
|
+
|
334
|
+
def closeEvent(self, event):
|
335
|
+
super().cleanup()
|
336
|
+
QTreeWidget().closeEvent(event)
|
337
|
+
|
338
|
+
|
339
|
+
def main():
|
340
|
+
"""Main method to run the BECStatusBox widget."""
|
341
|
+
# pylint: disable=import-outside-toplevel
|
342
|
+
from qtpy.QtWidgets import QApplication
|
343
|
+
|
344
|
+
app = QApplication(sys.argv)
|
345
|
+
qdarktheme.setup_theme("auto")
|
346
|
+
main_window = BECStatusBox()
|
347
|
+
main_window.show()
|
348
|
+
sys.exit(app.exec())
|
349
|
+
|
350
|
+
|
351
|
+
if __name__ == "__main__":
|
352
|
+
main()
|
@@ -0,0 +1,171 @@
|
|
1
|
+
""" Module for a StatusItem widget to display status and metrics for a BEC service.
|
2
|
+
The widget is bound to be used with the BECStatusBox widget."""
|
3
|
+
|
4
|
+
import enum
|
5
|
+
import sys
|
6
|
+
from datetime import datetime
|
7
|
+
|
8
|
+
import qdarktheme
|
9
|
+
from bec_lib.utils.import_utils import lazy_import_from
|
10
|
+
from pydantic import Field
|
11
|
+
from qtpy.QtCore import Qt, Slot
|
12
|
+
from qtpy.QtWidgets import QDialog, QHBoxLayout, QLabel, QStyle, QVBoxLayout, QWidget
|
13
|
+
|
14
|
+
from bec_widgets.utils.bec_connector import ConnectionConfig
|
15
|
+
|
16
|
+
# TODO : Put normal imports back when Pydantic gets faster
|
17
|
+
BECStatus = lazy_import_from("bec_lib.messages", ("BECStatus",))
|
18
|
+
|
19
|
+
|
20
|
+
class IconsEnum(enum.Enum):
|
21
|
+
"""Enum class for icons in the status item widget."""
|
22
|
+
|
23
|
+
RUNNING = "SP_DialogApplyButton"
|
24
|
+
BUSY = "SP_BrowserReload"
|
25
|
+
IDLE = "SP_MessageBoxWarning"
|
26
|
+
ERROR = "SP_DialogCancelButton"
|
27
|
+
NOTCONNECTED = "SP_TitleBarContextHelpButton"
|
28
|
+
|
29
|
+
|
30
|
+
class StatusWidgetConfig(ConnectionConfig):
|
31
|
+
"""Configuration class for the status item widget."""
|
32
|
+
|
33
|
+
service_name: str
|
34
|
+
status: str
|
35
|
+
info: dict
|
36
|
+
metrics: dict | None
|
37
|
+
icon_size: tuple = Field(default=(24, 24), description="The size of the icon in the widget.")
|
38
|
+
font_size: int = Field(16, description="The font size of the text in the widget.")
|
39
|
+
|
40
|
+
|
41
|
+
class StatusItem(QWidget):
|
42
|
+
"""A widget to display the status of a service.
|
43
|
+
|
44
|
+
Args:
|
45
|
+
parent: The parent widget.
|
46
|
+
config (dict): The configuration for the service.
|
47
|
+
"""
|
48
|
+
|
49
|
+
def __init__(self, parent=None, config: dict = None):
|
50
|
+
if config is None:
|
51
|
+
config = StatusWidgetConfig(widget_class=self.__class__.__name__)
|
52
|
+
else:
|
53
|
+
if isinstance(config, dict):
|
54
|
+
config = StatusWidgetConfig(**config)
|
55
|
+
self.config = config
|
56
|
+
QWidget.__init__(self, parent=parent)
|
57
|
+
self.parent = parent
|
58
|
+
self.layout = None
|
59
|
+
self.config = config
|
60
|
+
self._popup_label_ref = {}
|
61
|
+
self._label = None
|
62
|
+
self._icon = None
|
63
|
+
self.init_ui()
|
64
|
+
|
65
|
+
def init_ui(self) -> None:
|
66
|
+
"""Init the UI for the status item widget."""
|
67
|
+
self.layout = QHBoxLayout()
|
68
|
+
self.layout.setContentsMargins(5, 5, 5, 5)
|
69
|
+
self.setLayout(self.layout)
|
70
|
+
self._label = QLabel()
|
71
|
+
self._icon = QLabel()
|
72
|
+
self.layout.addWidget(self._label)
|
73
|
+
self.layout.addWidget(self._icon)
|
74
|
+
self.update_ui()
|
75
|
+
|
76
|
+
@Slot(dict)
|
77
|
+
def update_config(self, config: dict) -> None:
|
78
|
+
"""Update the configuration of the status item widget.
|
79
|
+
This method is invoked from the parent widget.
|
80
|
+
The UI values are later updated based on the new configuration.
|
81
|
+
|
82
|
+
Args:
|
83
|
+
config (dict): Config updates from parent widget.
|
84
|
+
"""
|
85
|
+
if config["service_name"] != self.config.service_name:
|
86
|
+
return
|
87
|
+
self.config.status = config["status"]
|
88
|
+
self.config.info = config["info"]
|
89
|
+
self.config.metrics = config["metrics"]
|
90
|
+
self.update_ui()
|
91
|
+
|
92
|
+
def update_ui(self) -> None:
|
93
|
+
"""Update the UI of the labels, and popup dialog."""
|
94
|
+
self.set_text()
|
95
|
+
self.set_status()
|
96
|
+
self._set_popup_text()
|
97
|
+
|
98
|
+
def set_text(self) -> None:
|
99
|
+
"""Set the text of the QLabel basae on the config."""
|
100
|
+
service = self.config.service_name
|
101
|
+
status = self.config.status
|
102
|
+
if "BECClient" in service.split("/"):
|
103
|
+
service = service.split("/")[0] + "/..." + service.split("/")[1][-4:]
|
104
|
+
if status == "NOTCONNECTED":
|
105
|
+
status = "NOT CONNECTED"
|
106
|
+
text = f"{service} is {status}"
|
107
|
+
self._label.setText(text)
|
108
|
+
|
109
|
+
def set_status(self) -> None:
|
110
|
+
"""Set the status icon for the status item widget."""
|
111
|
+
icon_name = IconsEnum[self.config.status].value
|
112
|
+
icon = self.style().standardIcon(getattr(QStyle.StandardPixmap, icon_name))
|
113
|
+
self._icon.setPixmap(icon.pixmap(*self.config.icon_size))
|
114
|
+
self._icon.setAlignment(Qt.AlignmentFlag.AlignRight)
|
115
|
+
|
116
|
+
def show_popup(self) -> None:
|
117
|
+
"""Method that is invoked when the user double clicks on the StatusItem widget."""
|
118
|
+
dialog = QDialog(self)
|
119
|
+
dialog.setWindowTitle(f"{self.config.service_name} Details")
|
120
|
+
layout = QVBoxLayout()
|
121
|
+
popup_label = self._make_popup_label()
|
122
|
+
self._set_popup_text()
|
123
|
+
layout.addWidget(popup_label)
|
124
|
+
dialog.setLayout(layout)
|
125
|
+
dialog.finished.connect(self._cleanup_popup_label)
|
126
|
+
dialog.exec()
|
127
|
+
|
128
|
+
def _make_popup_label(self) -> QLabel:
|
129
|
+
"""Create a QLabel for the popup dialog.
|
130
|
+
|
131
|
+
Returns:
|
132
|
+
QLabel: The label for the popup dialog.
|
133
|
+
"""
|
134
|
+
label = QLabel()
|
135
|
+
label.setWordWrap(True)
|
136
|
+
self._popup_label_ref.update({"label": label})
|
137
|
+
return label
|
138
|
+
|
139
|
+
def _set_popup_text(self) -> None:
|
140
|
+
"""Compile the metrics text for the status item widget."""
|
141
|
+
if self._popup_label_ref.get("label") is None:
|
142
|
+
return
|
143
|
+
metrics_text = (
|
144
|
+
f"<b>SERVICE:</b> {self.config.service_name}<br><b>STATUS:</b> {self.config.status}<br>"
|
145
|
+
)
|
146
|
+
if self.config.metrics:
|
147
|
+
for key, value in self.config.metrics.items():
|
148
|
+
if key == "create_time":
|
149
|
+
value = datetime.fromtimestamp(value).strftime("%Y-%m-%d %H:%M:%S")
|
150
|
+
metrics_text += f"<b>{key.upper()}:</b> {value}<br>"
|
151
|
+
self._popup_label_ref["label"].setText(metrics_text)
|
152
|
+
|
153
|
+
def _cleanup_popup_label(self) -> None:
|
154
|
+
"""Cleanup the popup label."""
|
155
|
+
self._popup_label_ref.clear()
|
156
|
+
|
157
|
+
|
158
|
+
def main():
|
159
|
+
"""Run the status item widget."""
|
160
|
+
# pylint: disable=import-outside-toplevel
|
161
|
+
from qtpy.QtWidgets import QApplication
|
162
|
+
|
163
|
+
app = QApplication(sys.argv)
|
164
|
+
qdarktheme.setup_theme("auto")
|
165
|
+
main_window = StatusItem()
|
166
|
+
main_window.show()
|
167
|
+
sys.exit(app.exec())
|
168
|
+
|
169
|
+
|
170
|
+
if __name__ == "__main__":
|
171
|
+
main()
|
@@ -2,11 +2,11 @@
|
|
2
2
|
.gitlab-ci.yml,sha256=RnYDz4zKXjlqltTryprlB1s5vLXxI2-seW-Vb70NNF0,8162
|
3
3
|
.pylintrc,sha256=OstrgmEyP0smNFBKoIN5_26-UmNZgMHnbjvAWX0UrLs,18535
|
4
4
|
.readthedocs.yaml,sha256=aSOc277LqXcsTI6lgvm_JY80lMlr69GbPKgivua2cS0,603
|
5
|
-
CHANGELOG.md,sha256=
|
5
|
+
CHANGELOG.md,sha256=EhWy5RDQRny8SHputsNnGM7Hy1HOZoSmZ6cJB2Gw8Cw,7198
|
6
6
|
LICENSE,sha256=YRKe85CBRyP7UpEAWwU8_qSIyuy5-l_9C-HKg5Qm8MQ,1511
|
7
|
-
PKG-INFO,sha256=
|
7
|
+
PKG-INFO,sha256=I5LEt7gWZyuBZpnkwiIXlKyxz5sQOmtI-RAIDZjzD9s,1302
|
8
8
|
README.md,sha256=y4jB6wvArS7N8_iTbKWnSM_oRAqLA2GqgzUR-FMh5sU,2645
|
9
|
-
pyproject.toml,sha256=
|
9
|
+
pyproject.toml,sha256=wCSTnyvyFrQnQiDgC5Pn18_zeLOguN86bLfJVSIboz8,2162
|
10
10
|
.git_hooks/pre-commit,sha256=n3RofIZHJl8zfJJIUomcMyYGFi_rwq4CC19z0snz3FI,286
|
11
11
|
.gitlab/issue_templates/bug_report_template.md,sha256=gAuyEwl7XlnebBrkiJ9AqffSNOywmr8vygUFWKTuQeI,386
|
12
12
|
.gitlab/issue_templates/documentation_update_template.md,sha256=FHLdb3TS_D9aL4CYZCjyXSulbaW5mrN2CmwTaeLPbNw,860
|
@@ -17,12 +17,12 @@ bec_widgets/assets/bec_widgets_icon.png,sha256=K8dgGwIjalDh9PRHUsSQBqgdX7a00nM3i
|
|
17
17
|
bec_widgets/assets/terminal_icon.png,sha256=bJl7Tft4Fi2uxvuXI8o14uMHnI9eAWKSU2uftXCH9ws,3889
|
18
18
|
bec_widgets/cli/__init__.py,sha256=d0Q6Fn44e7wFfLabDOBxpcJ1DPKWlFunGYDUBmO-4hA,22
|
19
19
|
bec_widgets/cli/auto_updates.py,sha256=DyBV3HnjMSH-cvVkYNcDiYKVf0Xut4Qy2qGQqkW47Bw,4833
|
20
|
-
bec_widgets/cli/client.py,sha256=
|
21
|
-
bec_widgets/cli/client_utils.py,sha256=
|
20
|
+
bec_widgets/cli/client.py,sha256=Zd4oMSE5-HY3IBUIVcparGGV2Ew86gaWAFTd4OjFVmg,58005
|
21
|
+
bec_widgets/cli/client_utils.py,sha256=_Hb2nl1rKEf7k4By9VZDYl5YyGFczxMuYIFMVrOAZD0,12182
|
22
22
|
bec_widgets/cli/generate_cli.py,sha256=Bi8HxHhge1I87vbdYHZUZiZwvbB-OSkLYS5Xfmwiz9M,4922
|
23
23
|
bec_widgets/cli/rpc_register.py,sha256=QxXUZu5XNg00Yf5O3UHWOXg3-f_pzKjjoZYMOa-MOJc,2216
|
24
24
|
bec_widgets/cli/rpc_wigdet_handler.py,sha256=1qQOGrM8rozaWLkoxAW8DTVLv_L_DZdZgUMDPy5MOek,1486
|
25
|
-
bec_widgets/cli/server.py,sha256=
|
25
|
+
bec_widgets/cli/server.py,sha256=3bFBPmtXKXFMjeja18d0hF3CO66Jo0-LEDtcF7lYb7k,7166
|
26
26
|
bec_widgets/examples/__init__.py,sha256=WWQ0cu7m8sA4Ehy-DWdTIqSISjaHsbxhsNmNrMnhDZU,202
|
27
27
|
bec_widgets/examples/jupyter_console/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
28
28
|
bec_widgets/examples/jupyter_console/jupyter_console_window.py,sha256=FXf0q7oz9GyJSct8PAgeOalzNnIJjApiaRvNfXsZPs0,5345
|
@@ -32,7 +32,7 @@ bec_widgets/examples/motor_movement/motor_control_compilations.py,sha256=8rpA7a2
|
|
32
32
|
bec_widgets/examples/motor_movement/motor_controller.ui,sha256=83XX6NGILwntoUIghvzWnMuGf80O8khK3SduVKTAEFM,29105
|
33
33
|
bec_widgets/utils/__init__.py,sha256=1930ji1Jj6dVuY81Wd2kYBhHYNV-2R0bN_L4o9zBj1U,533
|
34
34
|
bec_widgets/utils/bec_connector.py,sha256=RxHJNF7JjtY5pRbTMu2eQTiRXvoyJ53QuTYxHjZba38,5357
|
35
|
-
bec_widgets/utils/bec_dispatcher.py,sha256=
|
35
|
+
bec_widgets/utils/bec_dispatcher.py,sha256=yM9PG04O7ABhiA9Nzk38Rv9Qbjc5O93wi2xfSbOlOxc,6202
|
36
36
|
bec_widgets/utils/bec_table.py,sha256=nA2b8ukSeUfquFMAxGrUVOqdrzMoDYD6O_4EYbOG2zk,717
|
37
37
|
bec_widgets/utils/colors.py,sha256=GYSDe0ZxsJSwxvuy-yG2BH17qlf_Sjq8dhDcyp9IhBI,8532
|
38
38
|
bec_widgets/utils/container_utils.py,sha256=m3VUyAYmSWkEwApP9tBvKxPYVtc2kHw4toxIpMryJy4,1495
|
@@ -47,6 +47,9 @@ bec_widgets/utils/validator_delegate.py,sha256=Emj1WF6W8Ke1ruBWUfmHdVJpmOSPezuOt
|
|
47
47
|
bec_widgets/utils/widget_io.py,sha256=f36198CvT_EzWQ_cg2G-4tRRsaMdJ3yVqsZWKJCQEfA,10880
|
48
48
|
bec_widgets/utils/yaml_dialog.py,sha256=cMVif-39SB9WjwGH5FWBJcFs4tnfFJFs5cacydRyhy0,1853
|
49
49
|
bec_widgets/widgets/__init__.py,sha256=6RE9Pot2ud6BNJc_ZKiE--U-lgVRUff2IVR91lPcCbo,214
|
50
|
+
bec_widgets/widgets/bec_status_box/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
51
|
+
bec_widgets/widgets/bec_status_box/bec_status_box.py,sha256=ARdVzwabmbT_Jti6Wp-5KrxDQIingYVC-pZQHd6uz7o,14375
|
52
|
+
bec_widgets/widgets/bec_status_box/status_item.py,sha256=wPkDm0GCGNXXpy3rR_Ljaxy0ZHeiiYcrWFqEntZnz4E,5869
|
50
53
|
bec_widgets/widgets/buttons/__init__.py,sha256=74ucIRU6-anoqQ-zT7wbrysmxhg_3_04xGhN_kllNUI,48
|
51
54
|
bec_widgets/widgets/buttons/stop_button/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
52
55
|
bec_widgets/widgets/buttons/stop_button/stop_button.py,sha256=x4a7RvlMkHzOd05zKOGYkyTmBza7Me7jgOL9WIgA_c4,906
|
@@ -103,7 +106,7 @@ docs/Makefile,sha256=i2WHuFlgfyAPEW4ssEP8NY4cOibDJrVjvzSEU8_Ggwc,634
|
|
103
106
|
docs/conf.py,sha256=HxLxupNGu0Smhwn57g1kFdjZzFuaWVREgRJKhT1zi2k,2464
|
104
107
|
docs/index.md,sha256=8ZCgaLIbJsYvt-jwi--QxsNwnK4-k3rejIeOOLclG40,1101
|
105
108
|
docs/make.bat,sha256=vKazJE8RW49Cy8K7hm8QYbletvAd8YkeKsaPA_dWnXs,800
|
106
|
-
docs/requirements.txt,sha256=
|
109
|
+
docs/requirements.txt,sha256=TcjRnzVYFOg64N-lJc2sn-G6YQdhPjzp0lrLuPsPMXY,156
|
107
110
|
docs/_static/custom.css,sha256=v4Nk7r8LZslhOV8RaSUb15bG4miwiZ4-kZyXBLnSyms,13487
|
108
111
|
docs/_templates/custom-class-template.rst,sha256=HPuPaGJob2zXlWOl5FmA-hAZRbUTGQmdqo3HS1iIFog,711
|
109
112
|
docs/_templates/custom-module-template.rst,sha256=MXYXAz06HP_mbblO--iFwL08xROmSBo7U4O-hPbMcZU,1228
|
@@ -118,7 +121,7 @@ docs/assets/rocket_launch_48dp.svg,sha256=pdrPrBcKWUa5OlgWKM0B6TA6qAW7E57d7C7YW2
|
|
118
121
|
docs/developer/developer.md,sha256=VUdMnQBsSRCawYMFCe0vya5oj1MimLML7Pd58kY6fYY,765
|
119
122
|
docs/developer/getting_started/development.md,sha256=aYLmuLMYpp5FcIXeDUqCfcStIV8veuiMBjOt5bTW_30,1406
|
120
123
|
docs/developer/getting_started/getting_started.md,sha256=My_K_6O7LLaXVB_eINrRku5o-jVx95lsmGgHxgZhT7A,378
|
121
|
-
docs/developer/widgets/widgets.md,sha256=
|
124
|
+
docs/developer/widgets/widgets.md,sha256=aNsJgG7R-3EerumNB6GH84JLIXfZqGN5GjvpKWDi0Hk,504
|
122
125
|
docs/introduction/introduction.md,sha256=wp7jmhkUtJnSnEnmIAZGUcau_3-5e5-FohvZb63khw4,1432
|
123
126
|
docs/user/customisation.md,sha256=wCW8fAbqtlgGE3mURvXOrK67Xo0_B-lxfg0sYuQWB40,3186
|
124
127
|
docs/user/user.md,sha256=uCTcjclIi6rdjYRQebko6bWFEVsjyfshsVU3BDYrC-Y,1403
|
@@ -128,11 +131,13 @@ docs/user/getting_started/BECDockArea.png,sha256=t3vSm_rVRk371J5LOutbolETuEjStNc
|
|
128
131
|
docs/user/getting_started/auto_updates.md,sha256=Gicx3lplI6JRBlnPj_VL6IhqOIcsWjYF4_EdZSCje2A,3754
|
129
132
|
docs/user/getting_started/getting_started.md,sha256=lxZXCr6HAkM61oo5Bu-YjINSKo4wihWhAPJdotEAAVQ,358
|
130
133
|
docs/user/getting_started/gui_complex_gui.gif,sha256=ovv9u371BGG5GqhzyBMl4mvqMHLfJS0ylr-dR0Ydwtw,6550393
|
131
|
-
docs/user/getting_started/installation.md,sha256=
|
134
|
+
docs/user/getting_started/installation.md,sha256=5_fPbmUqLGtwOskFHTlytd4PJKrMcHqHShzM9ymM0oI,1149
|
132
135
|
docs/user/getting_started/quick_start.md,sha256=VGU880GwamcIZcBE8tjxuqX2syE-71jqZedtskCoBbA,9405
|
133
136
|
docs/user/widgets/BECFigure.png,sha256=8dQr4u0uk_y0VV-R1Jh9yTR3Vidd9HDEno_07R0swaE,1605920
|
134
137
|
docs/user/widgets/bec_figure.md,sha256=BwcumbhZd6a2zKmoHTvwKr8kG8WxBx9lS_QwxNiBMpQ,5155
|
135
|
-
docs/user/widgets/
|
138
|
+
docs/user/widgets/bec_status_box.gif,sha256=kLxf40HbS6fjdUIQ2b9SiduBEXdBd4DDWGEnQDOFMcY,1259044
|
139
|
+
docs/user/widgets/bec_status_box.md,sha256=0ILY12UnSjiVCtd5qpC8G2dPBYhug3J_rmQLilDxulY,1164
|
140
|
+
docs/user/widgets/buttons.md,sha256=Yci21PmxlRvfKcrvY7mVI7JkECPmE6j9WyWyH3j7Y2o,1221
|
136
141
|
docs/user/widgets/image_plot.gif,sha256=_mVFhMTXGqwDOcEtrBHMZj5Thn2sLhDAHEeL2XyHN-s,14098977
|
137
142
|
docs/user/widgets/motor.gif,sha256=FtaWdRHx4UZaGJPpq8LNhMMgX4PFcAB6IZ93JCMEh_w,2280719
|
138
143
|
docs/user/widgets/progress_bar.gif,sha256=5jh0Zw2BBGPuNxszV1DBLJCb4_6glIRX-U2ABjnsK2k,5263592
|
@@ -141,10 +146,10 @@ docs/user/widgets/spiral_progress_bar.md,sha256=QTgUDIl6XPuK_HwSfB6sNijZ4bss26bi
|
|
141
146
|
docs/user/widgets/text_box.md,sha256=_ST7RQWXl67MKLm6dTa995GjoughPUyK_hLnF8SPZcM,925
|
142
147
|
docs/user/widgets/w1D.gif,sha256=tuHbleJpl6bJFNNC2OdndF5LF7IyfvlkFCMGZajrQPs,622773
|
143
148
|
docs/user/widgets/website.md,sha256=wfudAupdtHX-Sfritg0xMWXZLLczJ4XwMLNWvu6ww-w,705
|
144
|
-
docs/user/widgets/widgets.md,sha256=
|
149
|
+
docs/user/widgets/widgets.md,sha256=6H8C8M2fFmTxFFlrAuOE-jBpOUVUrOIIzWP0l_EwMGo,397
|
145
150
|
tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
146
151
|
tests/end-2-end/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
147
|
-
tests/end-2-end/conftest.py,sha256
|
152
|
+
tests/end-2-end/conftest.py,sha256=-BLnFE-NeCerf6xahGCkbZ4Ktactowi6RkBnboIzRvg,1767
|
148
153
|
tests/end-2-end/test_bec_dock_rpc_e2e.py,sha256=8iJz4lITspY7eHdSgy9YvGUGTu3fsSperoVGBvTGT0U,9067
|
149
154
|
tests/end-2-end/test_bec_figure_rpc_e2e.py,sha256=zTbB_F4Fs-QG8KhMK24xfsrCQBgZUAguMk3KFdEdP2o,5095
|
150
155
|
tests/end-2-end/test_rpc_register_e2e.py,sha256=3dfCnSvdcRO92pzHt9WlCTK0vzTKAvPtliEoEKrtuzQ,1604
|
@@ -156,6 +161,7 @@ tests/unit_tests/test_bec_dispatcher.py,sha256=rYPiRizHaswhGZw55IBMneDFxmPiCCLAZ
|
|
156
161
|
tests/unit_tests/test_bec_dock.py,sha256=BXKXpuyIYj-l6KSyhQtM_p3kRFCRECIoXLzvkcJZDlM,3611
|
157
162
|
tests/unit_tests/test_bec_figure.py,sha256=aEd2R8K6fU2ON8QvPemGWpql_LaaYLipRlvnjBY2qFA,8009
|
158
163
|
tests/unit_tests/test_bec_motor_map.py,sha256=AfD_9-x6VV3TPnkQgNfFYRndPHDsGx-a_YknFeDr6hc,4588
|
164
|
+
tests/unit_tests/test_bec_status_box.py,sha256=zZ4pe7DaBzzpRsy62yHFkUGgAGb3zZU3I6zQIPsqUTY,6070
|
159
165
|
tests/unit_tests/test_client_utils.py,sha256=eViJ1Tz-HX9TkMvQH6W8cO-c3_1I8bUc4_Yen6LOc0E,830
|
160
166
|
tests/unit_tests/test_color_validation.py,sha256=csdvVKAohENZIRY-JQ97Hv-TShb1erj4oKMX7QRwo78,1883
|
161
167
|
tests/unit_tests/test_crosshair.py,sha256=3OMAJ2ZaISYXMOtkXf1rPdy94vCr8njeLi6uHblBL9Q,5045
|
@@ -180,8 +186,8 @@ tests/unit_tests/test_configs/config_device_no_entry.yaml,sha256=hdvue9KLc_kfNzG
|
|
180
186
|
tests/unit_tests/test_configs/config_scan.yaml,sha256=vo484BbWOjA_e-h6bTjSV9k7QaQHrlAvx-z8wtY-P4E,1915
|
181
187
|
tests/unit_tests/test_msgs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
182
188
|
tests/unit_tests/test_msgs/available_scans_message.py,sha256=m_z97hIrjHXXMa2Ex-UvsPmTxOYXfjxyJaGkIY6StTY,46532
|
183
|
-
bec_widgets-0.
|
184
|
-
bec_widgets-0.
|
185
|
-
bec_widgets-0.
|
186
|
-
bec_widgets-0.
|
187
|
-
bec_widgets-0.
|
189
|
+
bec_widgets-0.68.0.dist-info/METADATA,sha256=I5LEt7gWZyuBZpnkwiIXlKyxz5sQOmtI-RAIDZjzD9s,1302
|
190
|
+
bec_widgets-0.68.0.dist-info/WHEEL,sha256=zEMcRr9Kr03x1ozGwg5v9NQBKn3kndp6LSoSlVg-jhU,87
|
191
|
+
bec_widgets-0.68.0.dist-info/entry_points.txt,sha256=OvoqiNzNF9bizFQNhbAmmdc_njHrnVewLE-Kl-u9sh0,115
|
192
|
+
bec_widgets-0.68.0.dist-info/licenses/LICENSE,sha256=YRKe85CBRyP7UpEAWwU8_qSIyuy5-l_9C-HKg5Qm8MQ,1511
|
193
|
+
bec_widgets-0.68.0.dist-info/RECORD,,
|