mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 21:04:40 +01:00
331 lines
10 KiB
Python
331 lines
10 KiB
Python
from Qt import QtCore, QtWidgets
|
|
from avalon.vendor import qtawesome
|
|
from .models import LogModel, LogsFilterProxy
|
|
|
|
|
|
class SearchComboBox(QtWidgets.QComboBox):
|
|
"""Searchable ComboBox with empty placeholder value as first value"""
|
|
|
|
def __init__(self, parent=None, placeholder=""):
|
|
super(SearchComboBox, self).__init__(parent)
|
|
|
|
self.setEditable(True)
|
|
self.setInsertPolicy(self.NoInsert)
|
|
self.lineEdit().setPlaceholderText(placeholder)
|
|
|
|
# Apply completer settings
|
|
completer = self.completer()
|
|
completer.setCompletionMode(completer.PopupCompletion)
|
|
completer.setCaseSensitivity(QtCore.Qt.CaseInsensitive)
|
|
|
|
# Force style sheet on popup menu
|
|
# It won't take the parent stylesheet for some reason
|
|
# todo: better fix for completer popup stylesheet
|
|
if parent:
|
|
popup = completer.popup()
|
|
popup.setStyleSheet(parent.styleSheet())
|
|
|
|
self.currentIndexChanged.connect(self.onIndexChange)
|
|
|
|
def onIndexChange(self, index):
|
|
print(index)
|
|
|
|
def populate(self, items):
|
|
self.clear()
|
|
self.addItems([""]) # ensure first item is placeholder
|
|
self.addItems(items)
|
|
|
|
def get_valid_value(self):
|
|
"""Return the current text if it's a valid value else None
|
|
|
|
Note: The empty placeholder value is valid and returns as ""
|
|
|
|
"""
|
|
|
|
text = self.currentText()
|
|
lookup = set(self.itemText(i) for i in range(self.count()))
|
|
if text not in lookup:
|
|
return None
|
|
|
|
return text
|
|
|
|
|
|
class SelectableMenu(QtWidgets.QMenu):
|
|
|
|
selection_changed = QtCore.Signal()
|
|
|
|
def mouseReleaseEvent(self, event):
|
|
action = self.activeAction()
|
|
if action and action.isEnabled():
|
|
action.trigger()
|
|
self.selection_changed.emit()
|
|
else:
|
|
super(SelectableMenu, self).mouseReleaseEvent(event)
|
|
|
|
|
|
class CustomCombo(QtWidgets.QWidget):
|
|
|
|
selection_changed = QtCore.Signal()
|
|
|
|
def __init__(self, title, parent=None):
|
|
super(CustomCombo, self).__init__(parent)
|
|
toolbutton = QtWidgets.QToolButton(self)
|
|
toolbutton.setText(title)
|
|
|
|
toolmenu = SelectableMenu(self)
|
|
|
|
toolbutton.setMenu(toolmenu)
|
|
toolbutton.setPopupMode(QtWidgets.QToolButton.MenuButtonPopup)
|
|
|
|
layout = QtWidgets.QHBoxLayout()
|
|
layout.setContentsMargins(0, 0, 0, 0)
|
|
layout.addWidget(toolbutton)
|
|
|
|
self.setLayout(layout)
|
|
|
|
# toolmenu.selection_changed.connect(self.on_selection_changed)
|
|
toolmenu.selection_changed.connect(self.selection_changed)
|
|
|
|
self.toolbutton = toolbutton
|
|
self.toolmenu = toolmenu
|
|
self.main_layout = layout
|
|
|
|
def populate(self, items):
|
|
self.toolmenu.clear()
|
|
self.addItems(items)
|
|
|
|
def addItems(self, items):
|
|
for item in items:
|
|
action = self.toolmenu.addAction(item)
|
|
action.setCheckable(True)
|
|
action.setChecked(True)
|
|
self.toolmenu.addAction(action)
|
|
|
|
def items(self):
|
|
for action in self.toolmenu.actions():
|
|
yield action
|
|
|
|
|
|
class LogsWidget(QtWidgets.QWidget):
|
|
"""A widget that lists the published subsets for an asset"""
|
|
|
|
def __init__(self, detail_widget, parent=None):
|
|
super(LogsWidget, self).__init__(parent=parent)
|
|
|
|
model = LogModel()
|
|
proxy_model = LogsFilterProxy()
|
|
proxy_model.setSourceModel(model)
|
|
proxy_model.col_usernames = model.COLUMNS.index("username")
|
|
|
|
filter_layout = QtWidgets.QHBoxLayout()
|
|
|
|
# user_filter = SearchComboBox(self, "Users")
|
|
user_filter = CustomCombo("Users", self)
|
|
users = model.dbcon.distinct("username")
|
|
user_filter.populate(users)
|
|
user_filter.selection_changed.connect(self._user_changed)
|
|
|
|
proxy_model.update_users_filter(users)
|
|
|
|
level_filter = CustomCombo("Levels", self)
|
|
# levels = [(level, True) for level in model.dbcon.distinct("level")]
|
|
levels = model.dbcon.distinct("level")
|
|
level_filter.addItems(levels)
|
|
level_filter.selection_changed.connect(self._level_changed)
|
|
|
|
detail_widget.update_level_filter(levels)
|
|
|
|
spacer = QtWidgets.QWidget()
|
|
|
|
icon = qtawesome.icon("fa.refresh", color="white")
|
|
refresh_btn = QtWidgets.QPushButton(icon, "")
|
|
|
|
filter_layout.addWidget(user_filter)
|
|
filter_layout.addWidget(level_filter)
|
|
filter_layout.addWidget(spacer, 1)
|
|
filter_layout.addWidget(refresh_btn)
|
|
|
|
view = QtWidgets.QTreeView(self)
|
|
view.setAllColumnsShowFocus(True)
|
|
view.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
|
|
|
|
layout = QtWidgets.QVBoxLayout(self)
|
|
layout.setContentsMargins(0, 0, 0, 0)
|
|
layout.addLayout(filter_layout)
|
|
layout.addWidget(view)
|
|
|
|
view.setModel(proxy_model)
|
|
|
|
view.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
|
|
view.setSortingEnabled(True)
|
|
view.sortByColumn(
|
|
model.COLUMNS.index("started"),
|
|
QtCore.Qt.DescendingOrder
|
|
)
|
|
|
|
view.selectionModel().selectionChanged.connect(self._on_index_change)
|
|
refresh_btn.clicked.connect(self._on_refresh_clicked)
|
|
|
|
# Store to memory
|
|
self.model = model
|
|
self.proxy_model = proxy_model
|
|
self.view = view
|
|
|
|
self.user_filter = user_filter
|
|
self.level_filter = level_filter
|
|
|
|
self.detail_widget = detail_widget
|
|
self.refresh_btn = refresh_btn
|
|
|
|
# prepare
|
|
self.refresh()
|
|
|
|
def refresh(self):
|
|
self.model.refresh()
|
|
self.detail_widget.refresh()
|
|
|
|
def _on_refresh_clicked(self):
|
|
self.refresh()
|
|
|
|
def _on_index_change(self, to_index, from_index):
|
|
index = self._selected_log()
|
|
if index:
|
|
logs = index.data(self.model.ROLE_LOGS)
|
|
else:
|
|
logs = []
|
|
self.detail_widget.set_detail(logs)
|
|
|
|
def _user_changed(self):
|
|
checked_values = set()
|
|
for action in self.user_filter.items():
|
|
if action.isChecked():
|
|
checked_values.add(action.text())
|
|
self.proxy_model.update_users_filter(checked_values)
|
|
|
|
def _level_changed(self):
|
|
checked_values = set()
|
|
for action in self.level_filter.items():
|
|
if action.isChecked():
|
|
checked_values.add(action.text())
|
|
self.detail_widget.update_level_filter(checked_values)
|
|
|
|
def on_context_menu(self, point):
|
|
# TODO will be any actions? it's ready
|
|
return
|
|
|
|
point_index = self.view.indexAt(point)
|
|
if not point_index.isValid():
|
|
return
|
|
|
|
# Get selected subsets without groups
|
|
selection = self.view.selectionModel()
|
|
rows = selection.selectedRows(column=0)
|
|
|
|
def _selected_log(self):
|
|
selection = self.view.selectionModel()
|
|
rows = selection.selectedRows(column=0)
|
|
if len(rows) == 1:
|
|
return rows[0]
|
|
return None
|
|
|
|
|
|
class OutputWidget(QtWidgets.QWidget):
|
|
def __init__(self, parent=None):
|
|
super(OutputWidget, self).__init__(parent=parent)
|
|
layout = QtWidgets.QVBoxLayout(self)
|
|
|
|
show_timecode_checkbox = QtWidgets.QCheckBox("Show timestamp")
|
|
|
|
output_text = QtWidgets.QTextEdit()
|
|
output_text.setReadOnly(True)
|
|
# output_text.setLineWrapMode(QtWidgets.QTextEdit.FixedPixelWidth)
|
|
|
|
layout.addWidget(show_timecode_checkbox)
|
|
layout.addWidget(output_text)
|
|
|
|
show_timecode_checkbox.stateChanged.connect(
|
|
self.on_show_timecode_change
|
|
)
|
|
self.setLayout(layout)
|
|
self.output_text = output_text
|
|
self.show_timecode_checkbox = show_timecode_checkbox
|
|
|
|
self.refresh()
|
|
|
|
def refresh(self):
|
|
self.set_detail()
|
|
|
|
def show_timecode(self):
|
|
return self.show_timecode_checkbox.isChecked()
|
|
|
|
def on_show_timecode_change(self):
|
|
self.set_detail(self.las_logs)
|
|
|
|
def update_level_filter(self, levels):
|
|
self.filter_levels = set()
|
|
for level in levels or tuple():
|
|
self.filter_levels.add(level.lower())
|
|
|
|
self.set_detail(self.las_logs)
|
|
|
|
def add_line(self, line):
|
|
self.output_text.append(line)
|
|
|
|
def set_detail(self, logs=None):
|
|
self.las_logs = logs
|
|
self.output_text.clear()
|
|
if not logs:
|
|
return
|
|
|
|
show_timecode = self.show_timecode()
|
|
for log in logs:
|
|
level = log["level"].lower()
|
|
if level not in self.filter_levels:
|
|
continue
|
|
|
|
line_f = "<font color=\"White\">{message}"
|
|
if level == "debug":
|
|
line_f = (
|
|
"<font color=\"Yellow\"> -"
|
|
" <font color=\"Lime\">{{ {loggerName} }}: ["
|
|
" <font color=\"White\">{message}"
|
|
" <font color=\"Lime\">]"
|
|
)
|
|
elif level == "info":
|
|
line_f = (
|
|
"<font color=\"Lime\">>>> ["
|
|
" <font color=\"White\">{message}"
|
|
" <font color=\"Lime\">]"
|
|
)
|
|
elif level == "warning":
|
|
line_f = (
|
|
"<font color=\"Yellow\">*** WRN:"
|
|
" <font color=\"Lime\"> >>> {{ {loggerName} }}: ["
|
|
" <font color=\"White\">{message}"
|
|
" <font color=\"Lime\">]"
|
|
)
|
|
elif level == "error":
|
|
line_f = (
|
|
"<font color=\"Red\">!!! ERR:"
|
|
" <font color=\"White\">{timestamp}"
|
|
" <font color=\"Lime\">>>> {{ {loggerName} }}: ["
|
|
" <font color=\"White\">{message}"
|
|
" <font color=\"Lime\">]"
|
|
)
|
|
|
|
exc = log.get("exception")
|
|
if exc:
|
|
log["message"] = exc["message"]
|
|
|
|
line = line_f.format(**log)
|
|
|
|
if show_timecode:
|
|
timestamp = log["timestamp"]
|
|
line = timestamp.strftime("%Y-%d-%m %H:%M:%S") + " " + line
|
|
|
|
self.add_line(line)
|
|
|
|
if not exc:
|
|
continue
|
|
for _line in exc["stackTrace"].split("\n"):
|
|
self.add_line(_line)
|