bec-widgets 1.18.0__py3-none-any.whl → 1.19.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 +31 -0
- PKG-INFO +1 -1
- bec_widgets/cli/client.py +21 -0
- bec_widgets/utils/bec_connector.py +4 -1
- bec_widgets/widgets/__init__.py +0 -1
- bec_widgets/widgets/containers/dock/dock_area.py +7 -0
- bec_widgets/widgets/control/device_input/signal_combobox/register_signal_combo_box.py +17 -0
- bec_widgets/widgets/control/device_input/signal_combobox/signal_combo_box.pyproject +1 -0
- bec_widgets/widgets/control/device_input/signal_combobox/signal_combo_box_plugin.py +54 -0
- bec_widgets/widgets/control/device_input/signal_line_edit/signal_line_edit_plugin.py +1 -1
- bec_widgets/widgets/editors/text_box/text_box.py +5 -5
- bec_widgets/widgets/utility/logpanel/__init__.py +3 -0
- bec_widgets/widgets/utility/logpanel/_util.py +58 -0
- bec_widgets/widgets/utility/logpanel/log_panel.pyproject +1 -0
- bec_widgets/widgets/utility/logpanel/log_panel_plugin.py +54 -0
- bec_widgets/widgets/utility/logpanel/logpanel.py +516 -0
- bec_widgets/widgets/utility/logpanel/register_log_panel.py +15 -0
- {bec_widgets-1.18.0.dist-info → bec_widgets-1.19.0.dist-info}/METADATA +1 -1
- {bec_widgets-1.18.0.dist-info → bec_widgets-1.19.0.dist-info}/RECORD +23 -14
- pyproject.toml +1 -1
- {bec_widgets-1.18.0.dist-info → bec_widgets-1.19.0.dist-info}/WHEEL +0 -0
- {bec_widgets-1.18.0.dist-info → bec_widgets-1.19.0.dist-info}/entry_points.txt +0 -0
- {bec_widgets-1.18.0.dist-info → bec_widgets-1.19.0.dist-info}/licenses/LICENSE +0 -0
CHANGELOG.md
CHANGED
@@ -1,6 +1,37 @@
|
|
1
1
|
# CHANGELOG
|
2
2
|
|
3
3
|
|
4
|
+
## v1.19.0 (2025-01-31)
|
5
|
+
|
6
|
+
### Bug Fixes
|
7
|
+
|
8
|
+
- Enable type checking for BECDispatcher in BECConnector
|
9
|
+
([`50a572d`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/50a572dacd5dfc29a9ecf1b567aac6822b632f60))
|
10
|
+
|
11
|
+
### Documentation
|
12
|
+
|
13
|
+
- Add docs for LogPanel
|
14
|
+
([`f219c6f`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/f219c6fb573cf42964f6a7c6f4a0b0b9946fb98d))
|
15
|
+
|
16
|
+
### Features
|
17
|
+
|
18
|
+
- **widget**: Add LogPanel widget
|
19
|
+
([`f048880`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/f0488802775401319a54a51d05a0ad534292af09))
|
20
|
+
|
21
|
+
|
22
|
+
## v1.18.1 (2025-01-30)
|
23
|
+
|
24
|
+
### Bug Fixes
|
25
|
+
|
26
|
+
- **signal_combo_box**: Added missing plugin modules for signal line_edit/combobox
|
27
|
+
([`db70442`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/db70442cc21247d20e6f6ad78ad0e1d3aca24bf7))
|
28
|
+
|
29
|
+
### Documentation
|
30
|
+
|
31
|
+
- Add screenshots for device and signal input
|
32
|
+
([`f0c4efe`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/f0c4efefa03bf36ae57bf1a17f6a1b2e4d32c6c4))
|
33
|
+
|
34
|
+
|
4
35
|
## v1.18.0 (2025-01-30)
|
5
36
|
|
6
37
|
### Bug Fixes
|
PKG-INFO
CHANGED
bec_widgets/cli/client.py
CHANGED
@@ -31,6 +31,7 @@ class Widgets(str, enum.Enum):
|
|
31
31
|
DeviceComboBox = "DeviceComboBox"
|
32
32
|
DeviceLineEdit = "DeviceLineEdit"
|
33
33
|
LMFitDialog = "LMFitDialog"
|
34
|
+
LogPanel = "LogPanel"
|
34
35
|
Minesweeper = "Minesweeper"
|
35
36
|
PositionIndicator = "PositionIndicator"
|
36
37
|
PositionerBox = "PositionerBox"
|
@@ -3183,6 +3184,26 @@ class LMFitDialog(RPCBase):
|
|
3183
3184
|
"""
|
3184
3185
|
|
3185
3186
|
|
3187
|
+
class LogPanel(RPCBase):
|
3188
|
+
@rpc_call
|
3189
|
+
def set_plain_text(self, text: str) -> None:
|
3190
|
+
"""
|
3191
|
+
Set the plain text of the widget.
|
3192
|
+
|
3193
|
+
Args:
|
3194
|
+
text (str): The text to set.
|
3195
|
+
"""
|
3196
|
+
|
3197
|
+
@rpc_call
|
3198
|
+
def set_html_text(self, text: str) -> None:
|
3199
|
+
"""
|
3200
|
+
Set the HTML text of the widget.
|
3201
|
+
|
3202
|
+
Args:
|
3203
|
+
text (str): The text to set.
|
3204
|
+
"""
|
3205
|
+
|
3206
|
+
|
3186
3207
|
class Minesweeper(RPCBase): ...
|
3187
3208
|
|
3188
3209
|
|
@@ -4,7 +4,7 @@ from __future__ import annotations
|
|
4
4
|
import os
|
5
5
|
import time
|
6
6
|
import uuid
|
7
|
-
from typing import Optional
|
7
|
+
from typing import TYPE_CHECKING, Optional
|
8
8
|
|
9
9
|
from bec_lib.logger import bec_logger
|
10
10
|
from bec_lib.utils.import_utils import lazy_import_from
|
@@ -17,6 +17,9 @@ from bec_widgets.qt_utils.error_popups import ErrorPopupUtility
|
|
17
17
|
from bec_widgets.qt_utils.error_popups import SafeSlot as pyqtSlot
|
18
18
|
from bec_widgets.utils.yaml_dialog import load_yaml, load_yaml_gui, save_yaml, save_yaml_gui
|
19
19
|
|
20
|
+
if TYPE_CHECKING:
|
21
|
+
from bec_widgets.utils.bec_dispatcher import BECDispatcher
|
22
|
+
|
20
23
|
logger = bec_logger.logger
|
21
24
|
BECDispatcher = lazy_import_from("bec_widgets.utils.bec_dispatcher", ("BECDispatcher",))
|
22
25
|
|
bec_widgets/widgets/__init__.py
CHANGED
@@ -1 +0,0 @@
|
|
1
|
-
|
@@ -30,6 +30,7 @@ from bec_widgets.widgets.plots.waveform.waveform_widget import BECWaveformWidget
|
|
30
30
|
from bec_widgets.widgets.progress.ring_progress_bar.ring_progress_bar import RingProgressBar
|
31
31
|
from bec_widgets.widgets.services.bec_queue.bec_queue import BECQueue
|
32
32
|
from bec_widgets.widgets.services.bec_status_box.bec_status_box import BECStatusBox
|
33
|
+
from bec_widgets.widgets.utility.logpanel.logpanel import LogPanel
|
33
34
|
from bec_widgets.widgets.utility.visual.dark_mode_button.dark_mode_button import DarkModeButton
|
34
35
|
|
35
36
|
|
@@ -139,6 +140,9 @@ class BECDockArea(BECWidget, QWidget):
|
|
139
140
|
tooltip="Add Circular ProgressBar",
|
140
141
|
filled=True,
|
141
142
|
),
|
143
|
+
"log_panel": MaterialIconAction(
|
144
|
+
icon_name=LogPanel.ICON_NAME, tooltip="Add LogPanel", filled=True
|
145
|
+
),
|
142
146
|
},
|
143
147
|
),
|
144
148
|
"separator_2": SeparatorAction(),
|
@@ -200,6 +204,9 @@ class BECDockArea(BECWidget, QWidget):
|
|
200
204
|
self.toolbar.widgets["menu_utils"].widgets["progress_bar"].triggered.connect(
|
201
205
|
lambda: self.add_dock(widget="RingProgressBar", prefix="progress_bar")
|
202
206
|
)
|
207
|
+
self.toolbar.widgets["menu_utils"].widgets["log_panel"].triggered.connect(
|
208
|
+
lambda: self.add_dock(widget="LogPanel", prefix="log_panel")
|
209
|
+
)
|
203
210
|
|
204
211
|
# Icons
|
205
212
|
self.toolbar.widgets["attach_all"].action.triggered.connect(self.attach_all)
|
@@ -0,0 +1,17 @@
|
|
1
|
+
def main(): # pragma: no cover
|
2
|
+
from qtpy import PYSIDE6
|
3
|
+
|
4
|
+
if not PYSIDE6:
|
5
|
+
print("PYSIDE6 is not available in the environment. Cannot patch designer.")
|
6
|
+
return
|
7
|
+
from PySide6.QtDesigner import QPyDesignerCustomWidgetCollection
|
8
|
+
|
9
|
+
from bec_widgets.widgets.control.device_input.signal_combobox.signal_combo_box_plugin import (
|
10
|
+
SignalComboBoxPlugin,
|
11
|
+
)
|
12
|
+
|
13
|
+
QPyDesignerCustomWidgetCollection.addCustomWidget(SignalComboBoxPlugin())
|
14
|
+
|
15
|
+
|
16
|
+
if __name__ == "__main__": # pragma: no cover
|
17
|
+
main()
|
@@ -0,0 +1 @@
|
|
1
|
+
{'files': ['signal_combobox.py']}
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# Copyright (C) 2022 The Qt Company Ltd.
|
2
|
+
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
3
|
+
|
4
|
+
from qtpy.QtDesigner import QDesignerCustomWidgetInterface
|
5
|
+
|
6
|
+
from bec_widgets.utils.bec_designer import designer_material_icon
|
7
|
+
from bec_widgets.widgets.control.device_input.signal_combobox.signal_combobox import SignalComboBox
|
8
|
+
|
9
|
+
DOM_XML = """
|
10
|
+
<ui language='c++'>
|
11
|
+
<widget class='SignalComboBox' name='signal_combo_box'>
|
12
|
+
</widget>
|
13
|
+
</ui>
|
14
|
+
"""
|
15
|
+
|
16
|
+
|
17
|
+
class SignalComboBoxPlugin(QDesignerCustomWidgetInterface): # pragma: no cover
|
18
|
+
def __init__(self):
|
19
|
+
super().__init__()
|
20
|
+
self._form_editor = None
|
21
|
+
|
22
|
+
def createWidget(self, parent):
|
23
|
+
t = SignalComboBox(parent)
|
24
|
+
return t
|
25
|
+
|
26
|
+
def domXml(self):
|
27
|
+
return DOM_XML
|
28
|
+
|
29
|
+
def group(self):
|
30
|
+
return "BEC Input Widgets"
|
31
|
+
|
32
|
+
def icon(self):
|
33
|
+
return designer_material_icon(SignalComboBox.ICON_NAME)
|
34
|
+
|
35
|
+
def includeFile(self):
|
36
|
+
return "signal_combo_box"
|
37
|
+
|
38
|
+
def initialize(self, form_editor):
|
39
|
+
self._form_editor = form_editor
|
40
|
+
|
41
|
+
def isContainer(self):
|
42
|
+
return False
|
43
|
+
|
44
|
+
def isInitialized(self):
|
45
|
+
return self._form_editor is not None
|
46
|
+
|
47
|
+
def name(self):
|
48
|
+
return "SignalComboBox"
|
49
|
+
|
50
|
+
def toolTip(self):
|
51
|
+
return "Signal ComboBox Example for BEC Widgets with autocomplete."
|
52
|
+
|
53
|
+
def whatsThis(self):
|
54
|
+
return self.toolTip()
|
@@ -50,7 +50,7 @@ class SignalLineEditPlugin(QDesignerCustomWidgetInterface): # pragma: no cover
|
|
50
50
|
return "SignalLineEdit"
|
51
51
|
|
52
52
|
def toolTip(self):
|
53
|
-
return ""
|
53
|
+
return "Signal LineEdit Example for BEC Widgets with autocomplete."
|
54
54
|
|
55
55
|
def whatsThis(self):
|
56
56
|
return self.toolTip()
|
@@ -5,9 +5,9 @@ from html.parser import HTMLParser
|
|
5
5
|
|
6
6
|
from bec_lib.logger import bec_logger
|
7
7
|
from pydantic import Field
|
8
|
-
from qtpy.QtCore import Property, Slot
|
9
8
|
from qtpy.QtWidgets import QTextEdit, QVBoxLayout, QWidget
|
10
9
|
|
10
|
+
from bec_widgets.qt_utils.error_popups import SafeProperty, SafeSlot
|
11
11
|
from bec_widgets.utils.bec_connector import ConnectionConfig
|
12
12
|
from bec_widgets.utils.bec_widget import BECWidget
|
13
13
|
|
@@ -66,7 +66,7 @@ class TextBox(BECWidget, QWidget):
|
|
66
66
|
else:
|
67
67
|
self.set_html_text(DEFAULT_TEXT)
|
68
68
|
|
69
|
-
@
|
69
|
+
@SafeSlot(str)
|
70
70
|
def set_plain_text(self, text: str) -> None:
|
71
71
|
"""Set the plain text of the widget.
|
72
72
|
|
@@ -77,7 +77,7 @@ class TextBox(BECWidget, QWidget):
|
|
77
77
|
self.config.text = text
|
78
78
|
self.config.is_html = False
|
79
79
|
|
80
|
-
@
|
80
|
+
@SafeSlot(str)
|
81
81
|
def set_html_text(self, text: str) -> None:
|
82
82
|
"""Set the HTML text of the widget.
|
83
83
|
|
@@ -88,7 +88,7 @@ class TextBox(BECWidget, QWidget):
|
|
88
88
|
self.config.text = text
|
89
89
|
self.config.is_html = True
|
90
90
|
|
91
|
-
@
|
91
|
+
@SafeProperty(str)
|
92
92
|
def plain_text(self) -> str:
|
93
93
|
"""Get the text of the widget.
|
94
94
|
|
@@ -106,7 +106,7 @@ class TextBox(BECWidget, QWidget):
|
|
106
106
|
"""
|
107
107
|
self.set_plain_text(text)
|
108
108
|
|
109
|
-
@
|
109
|
+
@SafeProperty(str)
|
110
110
|
def html_text(self) -> str:
|
111
111
|
"""Get the HTML text of the widget.
|
112
112
|
|
@@ -0,0 +1,58 @@
|
|
1
|
+
""" Utilities for filtering and formatting in the LogPanel"""
|
2
|
+
|
3
|
+
from __future__ import annotations
|
4
|
+
|
5
|
+
import re
|
6
|
+
from collections import deque
|
7
|
+
from typing import Callable, Iterator
|
8
|
+
|
9
|
+
from bec_lib.logger import LogLevel
|
10
|
+
from bec_lib.messages import LogMessage
|
11
|
+
from qtpy.QtCore import QDateTime
|
12
|
+
|
13
|
+
LinesHtmlFormatter = Callable[[deque[LogMessage]], Iterator[str]]
|
14
|
+
LineFormatter = Callable[[LogMessage], str]
|
15
|
+
LineFilter = Callable[[LogMessage], bool] | None
|
16
|
+
|
17
|
+
ANSI_ESCAPE_REGEX = re.compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])")
|
18
|
+
|
19
|
+
|
20
|
+
def replace_escapes(s: str):
|
21
|
+
s = ANSI_ESCAPE_REGEX.sub("", s)
|
22
|
+
return s.replace(" ", " ").replace("\n", "<br />").replace("\t", " ")
|
23
|
+
|
24
|
+
|
25
|
+
def level_filter(msg: LogMessage, thresh: int):
|
26
|
+
return LogLevel[msg.content["log_type"].upper()].value >= thresh
|
27
|
+
|
28
|
+
|
29
|
+
def noop_format(line: LogMessage):
|
30
|
+
_textline = line.log_msg if isinstance(line.log_msg, str) else line.log_msg["text"]
|
31
|
+
return replace_escapes(_textline.strip()) + "<br />"
|
32
|
+
|
33
|
+
|
34
|
+
def simple_color_format(line: LogMessage, colors: dict[LogLevel, str]):
|
35
|
+
color = colors.get(LogLevel[line.content["log_type"].upper()]) or colors[LogLevel.INFO]
|
36
|
+
return f'<font color="{color}">{noop_format(line)}</font>'
|
37
|
+
|
38
|
+
|
39
|
+
def create_formatter(line_format: LineFormatter, line_filter: LineFilter) -> LinesHtmlFormatter:
|
40
|
+
def _formatter(data: deque[LogMessage]):
|
41
|
+
if line_filter is not None:
|
42
|
+
return (line_format(line) for line in data if line_filter(line))
|
43
|
+
else:
|
44
|
+
return (line_format(line) for line in data)
|
45
|
+
|
46
|
+
return _formatter
|
47
|
+
|
48
|
+
|
49
|
+
def log_txt(line):
|
50
|
+
return line.log_msg if isinstance(line.log_msg, str) else line.log_msg["text"]
|
51
|
+
|
52
|
+
|
53
|
+
def log_time(line):
|
54
|
+
return QDateTime.fromMSecsSinceEpoch(int(line.log_msg["record"]["time"]["timestamp"] * 1000))
|
55
|
+
|
56
|
+
|
57
|
+
def log_svc(line):
|
58
|
+
return line.log_msg["service_name"]
|
@@ -0,0 +1 @@
|
|
1
|
+
{'files': ['logpanel.py']}
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# Copyright (C) 2022 The Qt Company Ltd.
|
2
|
+
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
3
|
+
|
4
|
+
from qtpy.QtDesigner import QDesignerCustomWidgetInterface
|
5
|
+
|
6
|
+
from bec_widgets.utils.bec_designer import designer_material_icon
|
7
|
+
from bec_widgets.widgets.utility.logpanel.logpanel import LogPanel
|
8
|
+
|
9
|
+
DOM_XML = """
|
10
|
+
<ui language='c++'>
|
11
|
+
<widget class='LogPanel' name='log_panel'>
|
12
|
+
</widget>
|
13
|
+
</ui>
|
14
|
+
"""
|
15
|
+
|
16
|
+
|
17
|
+
class LogPanelPlugin(QDesignerCustomWidgetInterface): # pragma: no cover
|
18
|
+
def __init__(self):
|
19
|
+
super().__init__()
|
20
|
+
self._form_editor = None
|
21
|
+
|
22
|
+
def createWidget(self, parent):
|
23
|
+
t = LogPanel(parent)
|
24
|
+
return t
|
25
|
+
|
26
|
+
def domXml(self):
|
27
|
+
return DOM_XML
|
28
|
+
|
29
|
+
def group(self):
|
30
|
+
return "BEC Utils"
|
31
|
+
|
32
|
+
def icon(self):
|
33
|
+
return designer_material_icon(LogPanel.ICON_NAME)
|
34
|
+
|
35
|
+
def includeFile(self):
|
36
|
+
return "log_panel"
|
37
|
+
|
38
|
+
def initialize(self, form_editor):
|
39
|
+
self._form_editor = form_editor
|
40
|
+
|
41
|
+
def isContainer(self):
|
42
|
+
return False
|
43
|
+
|
44
|
+
def isInitialized(self):
|
45
|
+
return self._form_editor is not None
|
46
|
+
|
47
|
+
def name(self):
|
48
|
+
return "LogPanel"
|
49
|
+
|
50
|
+
def toolTip(self):
|
51
|
+
return "Displays a log panel"
|
52
|
+
|
53
|
+
def whatsThis(self):
|
54
|
+
return self.toolTip()
|
@@ -0,0 +1,516 @@
|
|
1
|
+
""" Module for a LogPanel widget to display BEC log messages """
|
2
|
+
|
3
|
+
from __future__ import annotations
|
4
|
+
|
5
|
+
import operator
|
6
|
+
import os
|
7
|
+
import re
|
8
|
+
from collections import deque
|
9
|
+
from functools import partial, reduce
|
10
|
+
from re import Pattern
|
11
|
+
from typing import TYPE_CHECKING, Literal
|
12
|
+
|
13
|
+
from bec_lib.client import BECClient
|
14
|
+
from bec_lib.connector import ConnectorBase
|
15
|
+
from bec_lib.endpoints import MessageEndpoints
|
16
|
+
from bec_lib.logger import LogLevel, bec_logger
|
17
|
+
from bec_lib.messages import LogMessage, StatusMessage
|
18
|
+
from qtpy.QtCore import QDateTime, Qt, Signal # type: ignore
|
19
|
+
from qtpy.QtGui import QFont
|
20
|
+
from qtpy.QtWidgets import (
|
21
|
+
QApplication,
|
22
|
+
QCheckBox,
|
23
|
+
QComboBox,
|
24
|
+
QDateTimeEdit,
|
25
|
+
QDialog,
|
26
|
+
QGridLayout,
|
27
|
+
QHBoxLayout,
|
28
|
+
QLabel,
|
29
|
+
QLineEdit,
|
30
|
+
QPushButton,
|
31
|
+
QScrollArea,
|
32
|
+
QTextEdit,
|
33
|
+
QVBoxLayout,
|
34
|
+
QWidget,
|
35
|
+
)
|
36
|
+
|
37
|
+
from bec_widgets.qt_utils.error_popups import SafeSlot
|
38
|
+
from bec_widgets.utils.colors import get_theme_palette, set_theme
|
39
|
+
from bec_widgets.widgets.editors.text_box.text_box import TextBox
|
40
|
+
from bec_widgets.widgets.services.bec_status_box.bec_status_box import BECServiceStatusMixin
|
41
|
+
from bec_widgets.widgets.utility.logpanel._util import (
|
42
|
+
LineFilter,
|
43
|
+
LineFormatter,
|
44
|
+
LinesHtmlFormatter,
|
45
|
+
create_formatter,
|
46
|
+
level_filter,
|
47
|
+
log_svc,
|
48
|
+
log_time,
|
49
|
+
log_txt,
|
50
|
+
noop_format,
|
51
|
+
simple_color_format,
|
52
|
+
)
|
53
|
+
|
54
|
+
if TYPE_CHECKING:
|
55
|
+
from qtpy.QtCore import pyqtBoundSignal # type: ignore
|
56
|
+
|
57
|
+
logger = bec_logger.logger
|
58
|
+
|
59
|
+
MODULE_PATH = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
|
60
|
+
|
61
|
+
# TODO: improve log color handling
|
62
|
+
DEFAULT_LOG_COLORS = {
|
63
|
+
LogLevel.INFO: "#FFFFFF",
|
64
|
+
LogLevel.SUCCESS: "#00FF00",
|
65
|
+
LogLevel.WARNING: "#FFCC00",
|
66
|
+
LogLevel.ERROR: "#FF0000",
|
67
|
+
LogLevel.DEBUG: "#0000CC",
|
68
|
+
}
|
69
|
+
|
70
|
+
|
71
|
+
class BecLogsQueue:
|
72
|
+
"""Manages getting logs from BEC Redis and formatting them for display"""
|
73
|
+
|
74
|
+
def __init__(
|
75
|
+
self,
|
76
|
+
conn: ConnectorBase,
|
77
|
+
new_message_signal: pyqtBoundSignal,
|
78
|
+
maxlen: int = 1000,
|
79
|
+
line_formatter: LineFormatter = noop_format,
|
80
|
+
) -> None:
|
81
|
+
self._timestamp_start: QDateTime | None = None
|
82
|
+
self._timestamp_end: QDateTime | None = None
|
83
|
+
self._conn = conn
|
84
|
+
self._new_message_signal: pyqtBoundSignal | None = new_message_signal
|
85
|
+
self._max_length = maxlen
|
86
|
+
self._data: deque[LogMessage] = deque([], self._max_length)
|
87
|
+
self._display_queue: deque[str] = deque([], self._max_length)
|
88
|
+
self._log_level: str | None = None
|
89
|
+
self._search_query: Pattern | str | None = None
|
90
|
+
self._selected_services: set[str] | None = None
|
91
|
+
self._set_formatter_and_update_filter(line_formatter)
|
92
|
+
self._conn.register([MessageEndpoints.log()], None, self._process_incoming_log_msg)
|
93
|
+
|
94
|
+
def disconnect(self):
|
95
|
+
self._conn.unregister([MessageEndpoints.log()], None, self._process_incoming_log_msg)
|
96
|
+
self._new_message_signal = None
|
97
|
+
|
98
|
+
def _process_incoming_log_msg(self, msg: dict):
|
99
|
+
_msg: LogMessage = msg["data"]
|
100
|
+
self._data.append(_msg)
|
101
|
+
if self.filter is None or self.filter(_msg):
|
102
|
+
self._display_queue.append(self._line_formatter(_msg))
|
103
|
+
if self._new_message_signal:
|
104
|
+
self._new_message_signal.emit()
|
105
|
+
|
106
|
+
def _set_formatter_and_update_filter(self, line_formatter: LineFormatter = noop_format):
|
107
|
+
self._line_formatter: LineFormatter = line_formatter
|
108
|
+
self._queue_formatter: LinesHtmlFormatter = create_formatter(
|
109
|
+
self._line_formatter, self.filter
|
110
|
+
)
|
111
|
+
|
112
|
+
def _combine_filters(self, *args: LineFilter):
|
113
|
+
return lambda msg: reduce(operator.and_, [filt(msg) for filt in args if filt is not None])
|
114
|
+
|
115
|
+
def _create_re_filter(self) -> LineFilter:
|
116
|
+
if self._search_query is None:
|
117
|
+
return None
|
118
|
+
elif isinstance(self._search_query, str):
|
119
|
+
return lambda line: self._search_query in log_txt(line)
|
120
|
+
return lambda line: self._search_query.match(log_txt(line)) is not None
|
121
|
+
|
122
|
+
def _create_service_filter(self):
|
123
|
+
return (
|
124
|
+
lambda line: self._selected_services is None or log_svc(line) in self._selected_services
|
125
|
+
)
|
126
|
+
|
127
|
+
def _create_timestamp_filter(self) -> LineFilter:
|
128
|
+
s, e = self._timestamp_start, self._timestamp_end
|
129
|
+
if s is e is None:
|
130
|
+
return lambda msg: True
|
131
|
+
|
132
|
+
def _time_filter(msg):
|
133
|
+
msg_time = log_time(msg)
|
134
|
+
if s is None:
|
135
|
+
return msg_time <= e
|
136
|
+
if e is None:
|
137
|
+
return s <= msg_time
|
138
|
+
return s <= msg_time <= e
|
139
|
+
|
140
|
+
return _time_filter
|
141
|
+
|
142
|
+
@property
|
143
|
+
def filter(self) -> LineFilter:
|
144
|
+
thresh = LogLevel[self._log_level].value if self._log_level is not None else 0
|
145
|
+
return self._combine_filters(
|
146
|
+
partial(level_filter, thresh=thresh),
|
147
|
+
self._create_re_filter(),
|
148
|
+
self._create_timestamp_filter(),
|
149
|
+
self._create_service_filter(),
|
150
|
+
)
|
151
|
+
|
152
|
+
def update_level_filter(self, level: str):
|
153
|
+
if level not in [l.name for l in LogLevel]:
|
154
|
+
logger.error(f"Logging level {level} unrecognized for filter!")
|
155
|
+
return
|
156
|
+
self._log_level = level
|
157
|
+
self._set_formatter_and_update_filter(self._line_formatter)
|
158
|
+
|
159
|
+
def update_search_filter(self, search_query: Pattern | str | None = None):
|
160
|
+
self._search_query = search_query
|
161
|
+
self._set_formatter_and_update_filter(self._line_formatter)
|
162
|
+
|
163
|
+
def update_time_filter(self, start: QDateTime | None, end: QDateTime | None):
|
164
|
+
self._timestamp_start = start
|
165
|
+
self._timestamp_end = end
|
166
|
+
self._set_formatter_and_update_filter(self._line_formatter)
|
167
|
+
|
168
|
+
def update_service_filter(self, services: set[str]):
|
169
|
+
self._selected_services = services
|
170
|
+
self._set_formatter_and_update_filter(self._line_formatter)
|
171
|
+
|
172
|
+
def update_line_formatter(self, line_formatter: LineFormatter):
|
173
|
+
self._set_formatter_and_update_filter(line_formatter)
|
174
|
+
|
175
|
+
def display_all(self) -> str:
|
176
|
+
return "\n".join(self._queue_formatter(self._data.copy()))
|
177
|
+
|
178
|
+
def format_new(self):
|
179
|
+
res = "\n".join(self._display_queue)
|
180
|
+
self._display_queue = deque([], self._max_length)
|
181
|
+
return res
|
182
|
+
|
183
|
+
def clear_logs(self):
|
184
|
+
self._data = deque([])
|
185
|
+
self._display_queue = deque([])
|
186
|
+
|
187
|
+
def fetch_history(self):
|
188
|
+
self._data = deque(
|
189
|
+
item["data"]
|
190
|
+
for item in self._conn.xread(
|
191
|
+
MessageEndpoints.log().endpoint, from_start=True, count=self._max_length
|
192
|
+
)
|
193
|
+
)
|
194
|
+
|
195
|
+
def unique_service_names_from_history(self) -> set[str]:
|
196
|
+
return set(msg.log_msg["service_name"] for msg in self._data)
|
197
|
+
|
198
|
+
|
199
|
+
class LogPanelToolbar(QWidget):
|
200
|
+
|
201
|
+
services_selected: pyqtBoundSignal = Signal(set)
|
202
|
+
|
203
|
+
def __init__(self, parent: QWidget | None = None) -> None:
|
204
|
+
super().__init__(parent)
|
205
|
+
|
206
|
+
# in unix time
|
207
|
+
self._timestamp_start: QDateTime | None = None
|
208
|
+
self._timestamp_end: QDateTime | None = None
|
209
|
+
|
210
|
+
self._unique_service_names: set[str] = set()
|
211
|
+
self._services_selected: set[str] | None = None
|
212
|
+
|
213
|
+
self.layout = QHBoxLayout(self) # type: ignore
|
214
|
+
|
215
|
+
self.service_choice_button = QPushButton("Select services", self)
|
216
|
+
self.layout.addWidget(self.service_choice_button)
|
217
|
+
self.service_choice_button.clicked.connect(self._open_service_filter_dialog)
|
218
|
+
|
219
|
+
self.filter_level_dropdown = self._log_level_box()
|
220
|
+
self.layout.addWidget(self.filter_level_dropdown)
|
221
|
+
|
222
|
+
self.clear_button = QPushButton("Clear all", self)
|
223
|
+
self.layout.addWidget(self.clear_button)
|
224
|
+
self.fetch_button = QPushButton("Fetch history", self)
|
225
|
+
self.layout.addWidget(self.fetch_button)
|
226
|
+
|
227
|
+
self._string_search_box()
|
228
|
+
|
229
|
+
self.timerange_button = QPushButton("Set time range", self)
|
230
|
+
self.layout.addWidget(self.timerange_button)
|
231
|
+
|
232
|
+
@property
|
233
|
+
def time_start(self):
|
234
|
+
return self._timestamp_start
|
235
|
+
|
236
|
+
@property
|
237
|
+
def time_end(self):
|
238
|
+
return self._timestamp_end
|
239
|
+
|
240
|
+
def _string_search_box(self):
|
241
|
+
self.layout.addWidget(QLabel("Search: "))
|
242
|
+
self.search_textbox = QLineEdit()
|
243
|
+
self.layout.addWidget(self.search_textbox)
|
244
|
+
self.layout.addWidget(QLabel("Use regex: "))
|
245
|
+
self.regex_enabled = QCheckBox()
|
246
|
+
self.layout.addWidget(self.regex_enabled)
|
247
|
+
self.update_re_button = QPushButton("Update search", self)
|
248
|
+
self.layout.addWidget(self.update_re_button)
|
249
|
+
|
250
|
+
def _log_level_box(self):
|
251
|
+
box = QComboBox()
|
252
|
+
box.setToolTip("Display logs with equal or greater significance to the selected level.")
|
253
|
+
[box.addItem(l.name) for l in LogLevel]
|
254
|
+
return box
|
255
|
+
|
256
|
+
def _current_ts(self, selection_type: Literal["start", "end"]):
|
257
|
+
if selection_type == "start":
|
258
|
+
return self._timestamp_start
|
259
|
+
elif selection_type == "end":
|
260
|
+
return self._timestamp_end
|
261
|
+
else:
|
262
|
+
raise ValueError(f"timestamps can only be for the start or end, not {selection_type}")
|
263
|
+
|
264
|
+
def _open_datetime_dialog(self):
|
265
|
+
"""Open dialog window for timestamp filter selection"""
|
266
|
+
self._dt_dialog = QDialog(self)
|
267
|
+
self._dt_dialog.setWindowTitle("Time range selection")
|
268
|
+
layout = QVBoxLayout()
|
269
|
+
|
270
|
+
label_start = QLabel()
|
271
|
+
label_end = QLabel()
|
272
|
+
|
273
|
+
def date_button_set(selection_type: Literal["start", "end"], label: QLabel):
|
274
|
+
dt = self._current_ts(selection_type)
|
275
|
+
_layout = QHBoxLayout()
|
276
|
+
layout.addLayout(_layout)
|
277
|
+
date_button = QPushButton(f"Time {selection_type}")
|
278
|
+
_layout.addWidget(date_button)
|
279
|
+
label.setText(dt.toString() if dt else "not selected")
|
280
|
+
_layout.addWidget(label)
|
281
|
+
date_button.clicked.connect(partial(self._open_cal_dialog, selection_type, label))
|
282
|
+
date_clear_button = QPushButton("clear")
|
283
|
+
date_clear_button.clicked.connect(
|
284
|
+
lambda: (
|
285
|
+
partial(self._update_time, selection_type)(None),
|
286
|
+
label.setText("not selected"),
|
287
|
+
)
|
288
|
+
)
|
289
|
+
_layout.addWidget(date_clear_button)
|
290
|
+
|
291
|
+
for v in [("start", label_start), ("end", label_end)]:
|
292
|
+
date_button_set(*v)
|
293
|
+
|
294
|
+
close_button = QPushButton("Close")
|
295
|
+
close_button.clicked.connect(self._dt_dialog.accept)
|
296
|
+
layout.addWidget(close_button)
|
297
|
+
self._dt_dialog.setLayout(layout)
|
298
|
+
self._dt_dialog.exec()
|
299
|
+
self._dt_dialog = None
|
300
|
+
|
301
|
+
def _open_cal_dialog(self, selection_type: Literal["start", "end"], label: QLabel):
|
302
|
+
"""Open dialog window for timestamp filter selection"""
|
303
|
+
dt = self._current_ts(selection_type) or QDateTime.currentDateTime()
|
304
|
+
label.setText(dt.toString() if dt else "not selected")
|
305
|
+
if selection_type == "start":
|
306
|
+
self._timestamp_start = dt
|
307
|
+
else:
|
308
|
+
self._timestamp_end = dt
|
309
|
+
self._cal_dialog = QDialog(self)
|
310
|
+
self._cal_dialog.setWindowTitle(f"Select time range {selection_type}")
|
311
|
+
layout = QVBoxLayout()
|
312
|
+
cal = QDateTimeEdit()
|
313
|
+
cal.setCalendarPopup(True)
|
314
|
+
cal.setDateTime(dt)
|
315
|
+
cal.setDisplayFormat("yyyy-MM-dd HH:mm:ss.zzz")
|
316
|
+
cal.dateTimeChanged.connect(partial(self._update_time, selection_type))
|
317
|
+
layout.addWidget(cal)
|
318
|
+
close_button = QPushButton("Close")
|
319
|
+
close_button.clicked.connect(self._cal_dialog.accept)
|
320
|
+
layout.addWidget(close_button)
|
321
|
+
self._cal_dialog.setLayout(layout)
|
322
|
+
self._cal_dialog.exec()
|
323
|
+
self._cal_dialog = None
|
324
|
+
|
325
|
+
def _update_time(self, selection_type: Literal["start", "end"], dt: QDateTime | None):
|
326
|
+
if selection_type == "start":
|
327
|
+
self._timestamp_start = dt
|
328
|
+
else:
|
329
|
+
self._timestamp_end = dt
|
330
|
+
|
331
|
+
@SafeSlot(dict, set)
|
332
|
+
def service_list_update(
|
333
|
+
self, services_info: dict[str, StatusMessage], services_from_history: set[str], *_, **__
|
334
|
+
):
|
335
|
+
self._unique_service_names = set([s.split("/")[0] for s in services_info.keys()])
|
336
|
+
self._unique_service_names |= services_from_history
|
337
|
+
if self._services_selected is None:
|
338
|
+
self._services_selected = self._unique_service_names
|
339
|
+
|
340
|
+
@SafeSlot()
|
341
|
+
def _open_service_filter_dialog(self):
|
342
|
+
if len(self._unique_service_names) == 0 or self._services_selected is None:
|
343
|
+
return
|
344
|
+
self._svc_dialog = QDialog(self)
|
345
|
+
self._svc_dialog.setWindowTitle(f"Select services to show logs from")
|
346
|
+
layout = QVBoxLayout()
|
347
|
+
service_cb_grid = QGridLayout(parent=self)
|
348
|
+
layout.addLayout(service_cb_grid)
|
349
|
+
|
350
|
+
def check_box(name: str, checked: Qt.CheckState):
|
351
|
+
if checked == Qt.CheckState.Checked:
|
352
|
+
self._services_selected.add(name)
|
353
|
+
else:
|
354
|
+
if name in self._services_selected:
|
355
|
+
self._services_selected.remove(name)
|
356
|
+
self.services_selected.emit(self._services_selected)
|
357
|
+
|
358
|
+
for i, svc in enumerate(self._unique_service_names):
|
359
|
+
service_cb_grid.addWidget(QLabel(svc), i, 0)
|
360
|
+
cb = QCheckBox()
|
361
|
+
cb.setChecked(svc in self._services_selected)
|
362
|
+
cb.checkStateChanged.connect(partial(check_box, svc))
|
363
|
+
service_cb_grid.addWidget(cb, i, 1)
|
364
|
+
|
365
|
+
close_button = QPushButton("Close")
|
366
|
+
close_button.clicked.connect(self._svc_dialog.accept)
|
367
|
+
layout.addWidget(close_button)
|
368
|
+
self._svc_dialog.setLayout(layout)
|
369
|
+
self._svc_dialog.exec()
|
370
|
+
self._svc_dialog = None
|
371
|
+
|
372
|
+
|
373
|
+
class LogPanel(TextBox):
|
374
|
+
"""Displays a log panel"""
|
375
|
+
|
376
|
+
ICON_NAME = "terminal"
|
377
|
+
_new_messages = Signal()
|
378
|
+
service_list_update = Signal(dict, set)
|
379
|
+
|
380
|
+
def __init__(self, parent=None, client: BECClient | None = None, **kwargs):
|
381
|
+
"""Initialize the LogPanel widget."""
|
382
|
+
super().__init__(parent=parent, client=client, **kwargs)
|
383
|
+
self._update_colors()
|
384
|
+
self._service_status = BECServiceStatusMixin(self, client=self.client) # type: ignore
|
385
|
+
self._log_manager = BecLogsQueue(
|
386
|
+
self.client.connector, # type: ignore
|
387
|
+
new_message_signal=self._new_messages,
|
388
|
+
line_formatter=partial(simple_color_format, colors=self._colors),
|
389
|
+
)
|
390
|
+
|
391
|
+
self.toolbar = LogPanelToolbar(parent=parent)
|
392
|
+
self.toolbar_area = QScrollArea()
|
393
|
+
self.toolbar_area.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
|
394
|
+
self.toolbar_area.setSizeAdjustPolicy(QScrollArea.SizeAdjustPolicy.AdjustToContents)
|
395
|
+
self.toolbar_area.setFixedHeight(int(self.toolbar.clear_button.height() * 2))
|
396
|
+
self.toolbar_area.setWidget(self.toolbar)
|
397
|
+
|
398
|
+
self.layout.addWidget(self.toolbar_area)
|
399
|
+
self.toolbar.clear_button.clicked.connect(self._on_clear)
|
400
|
+
self.toolbar.fetch_button.clicked.connect(self._on_fetch)
|
401
|
+
self.toolbar.update_re_button.clicked.connect(self._on_re_update)
|
402
|
+
self.toolbar.search_textbox.returnPressed.connect(self._on_re_update)
|
403
|
+
self.toolbar.regex_enabled.checkStateChanged.connect(self._on_re_update)
|
404
|
+
self.toolbar.filter_level_dropdown.currentTextChanged.connect(self._set_level_filter)
|
405
|
+
self._new_messages.connect(self._on_append)
|
406
|
+
|
407
|
+
self.toolbar.timerange_button.clicked.connect(self._choose_datetime)
|
408
|
+
self._service_status.services_update.connect(self._update_service_list)
|
409
|
+
self.service_list_update.connect(self.toolbar.service_list_update)
|
410
|
+
self.toolbar.services_selected.connect(self._update_service_filter)
|
411
|
+
|
412
|
+
self.text_box_text_edit.setFont(QFont("monospace", 12))
|
413
|
+
self.text_box_text_edit.setHtml("")
|
414
|
+
self.text_box_text_edit.setLineWrapMode(QTextEdit.LineWrapMode.NoWrap)
|
415
|
+
|
416
|
+
self._connect_to_theme_change()
|
417
|
+
|
418
|
+
@SafeSlot(set)
|
419
|
+
def _update_service_filter(self, services: set[str]):
|
420
|
+
self._log_manager.update_service_filter(services)
|
421
|
+
self._on_redraw()
|
422
|
+
|
423
|
+
@SafeSlot(dict, dict)
|
424
|
+
def _update_service_list(self, services_info: dict[str, StatusMessage], *_, **__):
|
425
|
+
self.service_list_update.emit(
|
426
|
+
services_info, self._log_manager.unique_service_names_from_history()
|
427
|
+
)
|
428
|
+
|
429
|
+
@SafeSlot()
|
430
|
+
def _choose_datetime(self):
|
431
|
+
self.toolbar._open_datetime_dialog()
|
432
|
+
self._set_time_filter()
|
433
|
+
|
434
|
+
def _connect_to_theme_change(self):
|
435
|
+
"""Connect to the theme change signal."""
|
436
|
+
qapp = QApplication.instance()
|
437
|
+
if hasattr(qapp, "theme_signal"):
|
438
|
+
qapp.theme_signal.theme_updated.connect(self._on_redraw) # type: ignore
|
439
|
+
|
440
|
+
def _update_colors(self):
|
441
|
+
self._colors = DEFAULT_LOG_COLORS.copy()
|
442
|
+
self._colors.update({LogLevel.INFO: get_theme_palette().text().color().name()})
|
443
|
+
|
444
|
+
def _cursor_to_end(self):
|
445
|
+
c = self.text_box_text_edit.textCursor()
|
446
|
+
c.movePosition(c.MoveOperation.End)
|
447
|
+
self.text_box_text_edit.setTextCursor(c)
|
448
|
+
|
449
|
+
@SafeSlot()
|
450
|
+
@SafeSlot(str)
|
451
|
+
def _on_redraw(self, *_):
|
452
|
+
self._update_colors()
|
453
|
+
self._log_manager.update_line_formatter(partial(simple_color_format, colors=self._colors))
|
454
|
+
self.set_html_text(self._log_manager.display_all())
|
455
|
+
self._cursor_to_end()
|
456
|
+
|
457
|
+
@SafeSlot()
|
458
|
+
def _on_append(self):
|
459
|
+
self._cursor_to_end()
|
460
|
+
self.text_box_text_edit.insertHtml(self._log_manager.format_new())
|
461
|
+
|
462
|
+
@SafeSlot()
|
463
|
+
def _on_clear(self):
|
464
|
+
self._log_manager.clear_logs()
|
465
|
+
self.set_html_text(self._log_manager.display_all())
|
466
|
+
self._cursor_to_end()
|
467
|
+
|
468
|
+
@SafeSlot()
|
469
|
+
@SafeSlot(Qt.CheckState)
|
470
|
+
def _on_re_update(self, *_):
|
471
|
+
if self.toolbar.regex_enabled.isChecked():
|
472
|
+
try:
|
473
|
+
search_query = re.compile(self.toolbar.search_textbox.text())
|
474
|
+
except Exception as e:
|
475
|
+
logger.warning(f"Failed to compile search regex with error {e}")
|
476
|
+
search_query = None
|
477
|
+
logger.info(f"Setting LogPanel search regex to {search_query}")
|
478
|
+
else:
|
479
|
+
search_query = self.toolbar.search_textbox.text()
|
480
|
+
logger.info(f'Setting LogPanel search string to "{search_query}"')
|
481
|
+
self._log_manager.update_search_filter(search_query)
|
482
|
+
self.set_html_text(self._log_manager.display_all())
|
483
|
+
self._cursor_to_end()
|
484
|
+
|
485
|
+
@SafeSlot()
|
486
|
+
def _on_fetch(self):
|
487
|
+
self._log_manager.fetch_history()
|
488
|
+
self.set_html_text(self._log_manager.display_all())
|
489
|
+
self._cursor_to_end()
|
490
|
+
|
491
|
+
@SafeSlot(str)
|
492
|
+
def _set_level_filter(self, level: str):
|
493
|
+
self._log_manager.update_level_filter(level)
|
494
|
+
self._on_redraw()
|
495
|
+
|
496
|
+
@SafeSlot()
|
497
|
+
def _set_time_filter(self):
|
498
|
+
self._log_manager.update_time_filter(self.toolbar.time_start, self.toolbar.time_end)
|
499
|
+
self._on_redraw()
|
500
|
+
|
501
|
+
def cleanup(self):
|
502
|
+
self._log_manager.disconnect()
|
503
|
+
self._new_messages.disconnect(self._on_append)
|
504
|
+
|
505
|
+
|
506
|
+
if __name__ == "__main__": # pragma: no cover
|
507
|
+
import sys
|
508
|
+
|
509
|
+
from qtpy.QtWidgets import QApplication # pylint: disable=ungrouped-imports
|
510
|
+
|
511
|
+
app = QApplication(sys.argv)
|
512
|
+
set_theme("dark")
|
513
|
+
widget = LogPanel()
|
514
|
+
|
515
|
+
widget.show()
|
516
|
+
sys.exit(app.exec())
|
@@ -0,0 +1,15 @@
|
|
1
|
+
def main(): # pragma: no cover
|
2
|
+
from qtpy import PYSIDE6
|
3
|
+
|
4
|
+
if not PYSIDE6:
|
5
|
+
print("PYSIDE6 is not available in the environment. Cannot patch designer.")
|
6
|
+
return
|
7
|
+
from PySide6.QtDesigner import QPyDesignerCustomWidgetCollection
|
8
|
+
|
9
|
+
from bec_widgets.widgets.utility.logpanel.log_panel_plugin import LogPanelPlugin
|
10
|
+
|
11
|
+
QPyDesignerCustomWidgetCollection.addCustomWidget(LogPanelPlugin())
|
12
|
+
|
13
|
+
|
14
|
+
if __name__ == "__main__": # pragma: no cover
|
15
|
+
main()
|
@@ -2,11 +2,11 @@
|
|
2
2
|
.gitlab-ci.yml,sha256=PuL-FmkTHm7qs467Mh9D8quWcEj4tgEA-UUGDieMuWk,8774
|
3
3
|
.pylintrc,sha256=eeY8YwSI74oFfq6IYIbCqnx3Vk8ZncKaatv96n_Y8Rs,18544
|
4
4
|
.readthedocs.yaml,sha256=aSOc277LqXcsTI6lgvm_JY80lMlr69GbPKgivua2cS0,603
|
5
|
-
CHANGELOG.md,sha256=
|
5
|
+
CHANGELOG.md,sha256=JsoUKOxpbxaYNfvXfDwnvrhGlQXreYGQaAM95trzq7o,226776
|
6
6
|
LICENSE,sha256=YRKe85CBRyP7UpEAWwU8_qSIyuy5-l_9C-HKg5Qm8MQ,1511
|
7
|
-
PKG-INFO,sha256=
|
7
|
+
PKG-INFO,sha256=ktXzXlqQyM2uA8iQbeKqLNvXeM6bWqZFjAKtb1DYVUQ,1219
|
8
8
|
README.md,sha256=KgdKusjlvEvFtdNZCeDMO91y77MWK2iDcYMDziksOr4,2553
|
9
|
-
pyproject.toml,sha256=
|
9
|
+
pyproject.toml,sha256=dBH-z_r3T8L74-Hq11fjOl-FowARiFQVUmkB2LiISB4,2549
|
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
|
@@ -24,7 +24,7 @@ bec_widgets/assets/app_icons/alignment_1d.png,sha256=5VouaWieb4lVv3wUBNHaO5ovUW2
|
|
24
24
|
bec_widgets/assets/app_icons/bec_widgets_icon.png,sha256=K8dgGwIjalDh9PRHUsSQBqgdX7a00nM3igZdc20pkYM,1747017
|
25
25
|
bec_widgets/cli/__init__.py,sha256=d0Q6Fn44e7wFfLabDOBxpcJ1DPKWlFunGYDUBmO-4hA,22
|
26
26
|
bec_widgets/cli/auto_updates.py,sha256=Pj8OHlSlKN3JOAmuuBC5oUMzdfC8TYRY7QKT5BQ2cZo,5171
|
27
|
-
bec_widgets/cli/client.py,sha256=
|
27
|
+
bec_widgets/cli/client.py,sha256=k1dSiZy4CBHb9pgfpV4ygTNaDUeqtWUbigKMxk9gtsY,100272
|
28
28
|
bec_widgets/cli/client_utils.py,sha256=DXHHw1LNkKVCApbWmOAdZqU_qdvvqx28QiWV-Uf-jos,12207
|
29
29
|
bec_widgets/cli/generate_cli.py,sha256=lT-eEXEPpzk9cPrfFVf0U5gxQg7HF2oulxDH16T-SBk,6849
|
30
30
|
bec_widgets/cli/server.py,sha256=Hzhhzhc9PQhTtjpNeu8Jmbmahad0DbyieutaRp7Vejc,10485
|
@@ -59,7 +59,7 @@ bec_widgets/qt_utils/toolbar.py,sha256=YY_-UGc7uZhahYn7xnTvBGbalmTkpTa4WLikpsHwn
|
|
59
59
|
bec_widgets/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
60
60
|
bec_widgets/tests/utils.py,sha256=GbQtN7qf9n-8FoAfNddZ4aAqA7oBo_hGAlnKELd6Xzw,6943
|
61
61
|
bec_widgets/utils/__init__.py,sha256=1930ji1Jj6dVuY81Wd2kYBhHYNV-2R0bN_L4o9zBj1U,533
|
62
|
-
bec_widgets/utils/bec_connector.py,sha256=
|
62
|
+
bec_widgets/utils/bec_connector.py,sha256=xMHOn0Rq2OShd0ONYEEZCJZR3oYGnORqMgxjDqgpLMU,10171
|
63
63
|
bec_widgets/utils/bec_designer.py,sha256=XBy38NbNMoRDpvRx5lGP2XnJNG34YKZ7I-ARFkn-gzs,5017
|
64
64
|
bec_widgets/utils/bec_dispatcher.py,sha256=OFmkx9vOz4pA4Sdc14QreyDZ870QYskJ4B5daVVeYg4,6325
|
65
65
|
bec_widgets/utils/bec_signal_proxy.py,sha256=soKdA4pJL8S0d-93C0QqcIUxLA4rfb1-B1jyRXHmMxk,3011
|
@@ -87,11 +87,11 @@ bec_widgets/utils/widget_state_manager.py,sha256=tzrxVmnGa6IHSEdeh-R68aQ934BsuS9
|
|
87
87
|
bec_widgets/utils/yaml_dialog.py,sha256=T6UyGNGdmpXW74fa_7Nk6b99T5pp2Wvyw3AOauRc8T8,2407
|
88
88
|
bec_widgets/utils/plugin_templates/plugin.template,sha256=DWtJdHpdsVtbiTTOniH3zBe5a40ztQ20o_-Hclyu38s,1266
|
89
89
|
bec_widgets/utils/plugin_templates/register.template,sha256=XyL3OZPT_FTArLAM8tHd5qMqv2ZuAbJAZLsNNnHcagU,417
|
90
|
-
bec_widgets/widgets/__init__.py,sha256=
|
90
|
+
bec_widgets/widgets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
91
91
|
bec_widgets/widgets/containers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
92
92
|
bec_widgets/widgets/containers/dock/__init__.py,sha256=B7foHt02gnhM7mFksa7GJVwT7n0j_JvYDCt6wc6XR5g,61
|
93
93
|
bec_widgets/widgets/containers/dock/dock.py,sha256=_cw5bbqCLZxaD5LmosHNmm_eXelMDoTyxFdTnSWkoOI,10379
|
94
|
-
bec_widgets/widgets/containers/dock/dock_area.py,sha256=
|
94
|
+
bec_widgets/widgets/containers/dock/dock_area.py,sha256=5OZHapVkxdjWU9RwDxwscgvUJbA-khoIRg21vTQdavU,18354
|
95
95
|
bec_widgets/widgets/containers/dock/dock_area.pyproject,sha256=URW0UrDXCnkzk80rbQmUMgF6Uqay2TjHsq8Dq0g1j-c,37
|
96
96
|
bec_widgets/widgets/containers/dock/dock_area_plugin.py,sha256=fDVXKPZuHr85B2fLfAYf_Ic5d9mZQpnZrODTDquzZpM,1331
|
97
97
|
bec_widgets/widgets/containers/dock/register_dock_area.py,sha256=L7BL4qknCjtqsDP-RMQzk2qRPpcYuzXWlb7sJB_0DDM,475
|
@@ -184,12 +184,15 @@ bec_widgets/widgets/control/device_input/device_line_edit/device_line_edit.pypro
|
|
184
184
|
bec_widgets/widgets/control/device_input/device_line_edit/device_line_edit_plugin.py,sha256=LoG1VyO21pZ9dbnDVU03xzqgP8P1oEmdeotlkYs_pE8,1466
|
185
185
|
bec_widgets/widgets/control/device_input/device_line_edit/register_device_line_edit.py,sha256=NTB3HghW5S7NvUlPe_k_uFYQLWPYgjgln2bAYipfkpM,527
|
186
186
|
bec_widgets/widgets/control/device_input/signal_combobox/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
187
|
+
bec_widgets/widgets/control/device_input/signal_combobox/register_signal_combo_box.py,sha256=VEdFRUfLph7JE2arcnzHw8etsE-4wZkwyzlNLMJBsZk,526
|
188
|
+
bec_widgets/widgets/control/device_input/signal_combobox/signal_combo_box.pyproject,sha256=xod6iyRD-WD0Uk6LWXjSxFJCQy-831pvTkKcw2FAdnM,33
|
189
|
+
bec_widgets/widgets/control/device_input/signal_combobox/signal_combo_box_plugin.py,sha256=sstqm2KtyR5wwOIYJRbzOqHMq5_9ExKP-YS5qV5ACrA,1373
|
187
190
|
bec_widgets/widgets/control/device_input/signal_combobox/signal_combobox.py,sha256=0aOAq3e9MdZMXTGJQY7WqQSvl-oPnqwrfHjBKGzeRAI,4572
|
188
191
|
bec_widgets/widgets/control/device_input/signal_line_edit/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
189
192
|
bec_widgets/widgets/control/device_input/signal_line_edit/register_signal_line_edit.py,sha256=aQLTy_3gbji0vq5VvvAddHFimpwGGaMYJy5iGgX23aM,527
|
190
193
|
bec_widgets/widgets/control/device_input/signal_line_edit/signal_line_edit.py,sha256=B1xnzP0XYf8PiOLj8pu7kVMhq3ZtFPRbQb84nXsKHI0,5174
|
191
194
|
bec_widgets/widgets/control/device_input/signal_line_edit/signal_line_edit.pyproject,sha256=3NBnjBB6JRuF2W9-SR6x09KO1C2oB1IEV3VW__miIgI,34
|
192
|
-
bec_widgets/widgets/control/device_input/signal_line_edit/signal_line_edit_plugin.py,sha256=
|
195
|
+
bec_widgets/widgets/control/device_input/signal_line_edit/signal_line_edit_plugin.py,sha256=t2VBGsbysCL6154Z5Ny5Nk2UWcURMGS-ibVKiRvYs6Y,1384
|
193
196
|
bec_widgets/widgets/control/scan_control/__init__.py,sha256=IOfHl15vxb_uC6KN62-PeUzbBha_vQyqkkXbJ2HU674,38
|
194
197
|
bec_widgets/widgets/control/scan_control/register_scan_control.py,sha256=j7KrYSn9O6wp6ay2Yb7BWDjdbWzpkSLcCI0neeZi048,483
|
195
198
|
bec_widgets/widgets/control/scan_control/scan_control.py,sha256=Nixsb7NrEn0eQzeax8jd2o2_omlpKN5s6IcWRsL_JhQ,18109
|
@@ -219,7 +222,7 @@ bec_widgets/widgets/editors/jupyter_console/__init__.py,sha256=47DEQpj8HBSa-_TIm
|
|
219
222
|
bec_widgets/widgets/editors/jupyter_console/jupyter_console.py,sha256=-e7HQOECeH5eDrJYh4BFIzRL78LDkooU4otabyN0aX4,2343
|
220
223
|
bec_widgets/widgets/editors/text_box/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
221
224
|
bec_widgets/widgets/editors/text_box/register_text_box.py,sha256=xRgVugvjLhX3iKb-vaAxflE6pWpal7pVFWDaUSUZLyE,467
|
222
|
-
bec_widgets/widgets/editors/text_box/text_box.py,sha256=
|
225
|
+
bec_widgets/widgets/editors/text_box/text_box.py,sha256=F_BdWKPwEjltgfAsPmGHipOs5sPtI4o0Y0EO68s3Og0,4311
|
223
226
|
bec_widgets/widgets/editors/text_box/text_box.pyproject,sha256=XohO1BIe2hrpU-z_KHKRgjcUkXru7jeFte31j2TPbNk,26
|
224
227
|
bec_widgets/widgets/editors/text_box/text_box_plugin.py,sha256=0sgOZ_2Z0tpLGHYJyRlDGcPHWR79vzj38sQ6V9lnpjc,1310
|
225
228
|
bec_widgets/widgets/editors/vscode/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -310,6 +313,12 @@ bec_widgets/widgets/services/device_browser/register_device_browser.py,sha256=Or
|
|
310
313
|
bec_widgets/widgets/services/device_browser/device_item/__init__.py,sha256=VGY-uNVCnpcY-q-gijteB2N8KxFNgYR-qQ209MVu1QI,36
|
311
314
|
bec_widgets/widgets/services/device_browser/device_item/device_item.py,sha256=u3CTgXJBqwNP3uxrIWYGacEuoOu1a_DxfXYZ3Q4sK_M,1901
|
312
315
|
bec_widgets/widgets/utility/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
316
|
+
bec_widgets/widgets/utility/logpanel/__init__.py,sha256=HldSvPLYgrqBjCgIQj0f7Wa4slkSMksk4bsRJOQi__Y,91
|
317
|
+
bec_widgets/widgets/utility/logpanel/_util.py,sha256=4vkN2KDJI8CHgwo_R8QjN76Lqg8zKoWAa_pWyFgLP94,1837
|
318
|
+
bec_widgets/widgets/utility/logpanel/log_panel.pyproject,sha256=2ncs1bsu-wICstR1gOYwFFdr0UuZmrBQEpwhvNKVFMY,26
|
319
|
+
bec_widgets/widgets/utility/logpanel/log_panel_plugin.py,sha256=KY7eS1uGZzLYtDAdBv6S2mw8UjcDGVt3UklN_D5M06A,1250
|
320
|
+
bec_widgets/widgets/utility/logpanel/logpanel.py,sha256=VxFXoy5kUS8AI42PzwZc556NrT-HNGD07_yc9zmiPyQ,19171
|
321
|
+
bec_widgets/widgets/utility/logpanel/register_log_panel.py,sha256=LFUE5JzCYvIwJQtTqZASLVAHYy3gO1nrHzPVH_kpCEY,470
|
313
322
|
bec_widgets/widgets/utility/spinner/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
314
323
|
bec_widgets/widgets/utility/spinner/register_spinner_widget.py,sha256=96A13dEcyTgXfc9G0sTdlXYCDcVav8Z2P2eDC95bESQ,484
|
315
324
|
bec_widgets/widgets/utility/spinner/spinner.py,sha256=6c0fN7mdGzELg4mf_yG08ubses3svb6w0EqMeHDFkIw,2651
|
@@ -341,8 +350,8 @@ bec_widgets/widgets/utility/visual/dark_mode_button/dark_mode_button.py,sha256=Z
|
|
341
350
|
bec_widgets/widgets/utility/visual/dark_mode_button/dark_mode_button.pyproject,sha256=Lbi9zb6HNlIq14k6hlzR-oz6PIFShBuF7QxE6d87d64,34
|
342
351
|
bec_widgets/widgets/utility/visual/dark_mode_button/dark_mode_button_plugin.py,sha256=CzChz2SSETYsR8-36meqWnsXCT-FIy_J_xeU5coWDY8,1350
|
343
352
|
bec_widgets/widgets/utility/visual/dark_mode_button/register_dark_mode_button.py,sha256=rMpZ1CaoucwobgPj1FuKTnt07W82bV1GaSYdoqcdMb8,521
|
344
|
-
bec_widgets-1.
|
345
|
-
bec_widgets-1.
|
346
|
-
bec_widgets-1.
|
347
|
-
bec_widgets-1.
|
348
|
-
bec_widgets-1.
|
353
|
+
bec_widgets-1.19.0.dist-info/METADATA,sha256=ktXzXlqQyM2uA8iQbeKqLNvXeM6bWqZFjAKtb1DYVUQ,1219
|
354
|
+
bec_widgets-1.19.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
355
|
+
bec_widgets-1.19.0.dist-info/entry_points.txt,sha256=dItMzmwA1wizJ1Itx15qnfJ0ZzKVYFLVJ1voxT7K7D4,214
|
356
|
+
bec_widgets-1.19.0.dist-info/licenses/LICENSE,sha256=YRKe85CBRyP7UpEAWwU8_qSIyuy5-l_9C-HKg5Qm8MQ,1511
|
357
|
+
bec_widgets-1.19.0.dist-info/RECORD,,
|
pyproject.toml
CHANGED
File without changes
|
File without changes
|
File without changes
|