guard.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680
  1. ########################################################################
  2. # Searx-qt - Lightweight desktop application for SearX.
  3. # Copyright (C) 2020 CYBERDEViL
  4. #
  5. # This file is part of Searx-qt.
  6. #
  7. # Searx-qt is free software: you can redistribute it and/or modify
  8. # it under the terms of the GNU General Public License as published by
  9. # the Free Software Foundation, either version 3 of the License, or
  10. # (at your option) any later version.
  11. #
  12. # Searx-qt is distributed in the hope that it will be useful,
  13. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. # GNU General Public License for more details.
  16. #
  17. # You should have received a copy of the GNU General Public License
  18. # along with this program. If not, see <https://www.gnu.org/licenses/>.
  19. #
  20. ########################################################################
  21. from requests.status_codes import _codes as StatusCodes
  22. from PyQt5.QtWidgets import (
  23. QAbstractItemView,
  24. QCheckBox,
  25. QComboBox,
  26. QDialog,
  27. QFormLayout,
  28. QHBoxLayout,
  29. QHeaderView,
  30. QLabel,
  31. QMessageBox,
  32. QMenu,
  33. QSpinBox,
  34. QTableWidget,
  35. QTableWidgetItem,
  36. QTabWidget,
  37. QVBoxLayout,
  38. QWidget
  39. )
  40. from PyQt5.QtGui import QGuiApplication
  41. from PyQt5.QtCore import Qt, QVariant, QItemSelectionModel
  42. from searxqt.core.requests import ErrorType as RequestsErrorTypes
  43. from searxqt.core.requests import ErrorTypeStr as RequestsErrorTypeStr
  44. from searxqt.core.guard import ConsequenceType, ConsequenceTypeStr
  45. from searxqt.widgets.buttons import Button
  46. from searxqt.translations import _, timeToString, durationMinutesToString
  47. class GuardSettings(QWidget):
  48. def __init__(self, guard, parent=None):
  49. QWidget.__init__(self, parent=parent)
  50. self.__guard = guard
  51. layout = QVBoxLayout(self)
  52. # Title label
  53. layout.addWidget(QLabel("<h2>{0}</h2>".format(_("Guard")), self))
  54. # Info label
  55. infoLabel = QLabel(
  56. _("Guard can put failing instances on the blacklist or on a"
  57. " timeout based on set rules below."),
  58. self
  59. )
  60. infoLabel.setWordWrap(True)
  61. layout.addWidget(infoLabel)
  62. # Enable checkbox
  63. self.__enableCheck = QCheckBox(_("Enable guard"), self)
  64. layout.addWidget(self.__enableCheck)
  65. # Store log checkbox
  66. self.__storeLogCheck = QCheckBox(_("Store log"), self)
  67. layout.addWidget(self.__storeLogCheck)
  68. # Max log period
  69. self.__maxLogPeriod = QSpinBox(self)
  70. self.__maxLogPeriod.setSuffix(_(" days"))
  71. self.__maxLogPeriod.setMinimum(1)
  72. self.__maxLogPeriod.setMaximum(999)
  73. self.__maxLogPeriod.setEnabled(False)
  74. layout.addWidget(self.__maxLogPeriod, 0, Qt.AlignLeft)
  75. # Tab widget
  76. self.__tabWidget = QTabWidget(self)
  77. self.__tabWidget.setTabShape(QTabWidget.Triangular)
  78. self.__tabWidget.setTabPosition(QTabWidget.West)
  79. layout.addWidget(self.__tabWidget)
  80. # Rule editor
  81. self.__ruleEditor = GuardRuleEditor(guard, self)
  82. self.__tabWidget.addTab(self.__ruleEditor, _("Rules"))
  83. # Log viewer
  84. self.__logViewer = GuardLogViewer(guard, self)
  85. self.__tabWidget.addTab(self.__logViewer, _("Log"))
  86. # Initial state
  87. self.__enableCheck.setChecked(self.__guard.isEnabled())
  88. self.__storeLogCheck.setChecked(self.__guard.doesStoreLog())
  89. self.__maxLogPeriod.setEnabled(self.__guard.doesStoreLog())
  90. self.__maxLogPeriod.setValue(self.__guard.maxLogPeriod())
  91. # Connections
  92. self.__enableCheck.stateChanged.connect(self.__enableCheckStateChange)
  93. self.__storeLogCheck.stateChanged.connect(self.__storeLogStateChange)
  94. self.__maxLogPeriod.valueChanged.connect(self.__logPeriodChanged)
  95. def __enableCheckStateChange(self, state):
  96. self.__guard.setEnabled(bool(state))
  97. if not state:
  98. # Guard disabled; ask to clear the log (when any present)
  99. if self.__guard.log():
  100. answer = QMessageBox.question(
  101. self,
  102. _("Clear log?"),
  103. _("You've disabled Guard but there are currently logs"
  104. " present. Do you want to clear the log?"),
  105. QMessageBox.Yes | QMessageBox.No
  106. )
  107. if answer == QMessageBox.Yes:
  108. self.__logViewer.clearLog()
  109. def __storeLogStateChange(self, state):
  110. state = bool(state)
  111. self.__guard.setStoreLog(state)
  112. self.__maxLogPeriod.setEnabled(state)
  113. def __logPeriodChanged(self, value):
  114. self.__guard.setMaxLogPeriod(value)
  115. class GuardRuleEditor(QWidget):
  116. def __init__(self, guard, parent=None):
  117. QWidget.__init__(self, parent=parent)
  118. self.__guard = guard
  119. layout = QVBoxLayout(self)
  120. toolBar = QWidget(self)
  121. toolBarLayout = QHBoxLayout(toolBar)
  122. self.__addButton = Button("Add", self)
  123. self.__editButton = Button("Edit", self)
  124. self.__editButton.setEnabled(False)
  125. self.__delButton = Button("Del", self)
  126. self.__delButton.setEnabled(False)
  127. self.__upButton = Button("▲", self)
  128. self.__upButton.setToolTip(_("Move rule up."))
  129. self.__downButton = Button("▼", self)
  130. self.__downButton.setToolTip(_("Move rule down."))
  131. toolBarLayout.addWidget(self.__addButton, 0, Qt.AlignLeft)
  132. toolBarLayout.addWidget(self.__editButton, 0, Qt.AlignLeft)
  133. toolBarLayout.addWidget(self.__delButton, 0, Qt.AlignLeft)
  134. toolBarLayout.addWidget(self.__upButton, 0, Qt.AlignLeft)
  135. toolBarLayout.addWidget(self.__downButton, 1, Qt.AlignLeft)
  136. self.__rulesTable = QTableWidget(self)
  137. self.__rulesTable.setEditTriggers(QAbstractItemView.NoEditTriggers)
  138. self.__rulesTable.setSelectionBehavior(QAbstractItemView.SelectRows)
  139. self.__rulesTable.setSelectionMode(QAbstractItemView.SingleSelection)
  140. layout.addWidget(toolBar)
  141. layout.addWidget(self.__rulesTable)
  142. # Fill the table
  143. self.updateTable()
  144. # Connections
  145. self.__addButton.clicked.connect(self.__addRuleDialog)
  146. self.__editButton.clicked.connect(self.__editSelectedRule)
  147. self.__delButton.clicked.connect(self.__delSelectedRule)
  148. self.__upButton.clicked.connect(self.moveSelectedUp)
  149. self.__downButton.clicked.connect(self.moveSelectedDown)
  150. self.__rulesTable.itemSelectionChanged.connect(self.__selectionChanged)
  151. def moveSelected(self, amount=-1):
  152. """
  153. @param amount: index offset to move.
  154. @type amount: int
  155. """
  156. # move the selected rule.
  157. index = self.__rulesTable.currentIndex().row()
  158. toIndex = index + amount
  159. self.__guard.moveRule(index, toIndex)
  160. # update the view.
  161. self.updateTable()
  162. # re-select the selected rule on new index.
  163. item = self.__rulesTable.item(toIndex, 0)
  164. self.__rulesTable.setCurrentItem(
  165. item,
  166. QItemSelectionModel.Rows | QItemSelectionModel.SelectCurrent
  167. )
  168. def moveSelectedUp(self):
  169. self.moveSelected(-1)
  170. def moveSelectedDown(self):
  171. self.moveSelected(1)
  172. def updateTable(self):
  173. self.__rulesTable.clear()
  174. # Columns
  175. # - errorType
  176. # - amount
  177. # - period
  178. # - statusCode
  179. # - destinationType
  180. # - duration
  181. self.__rulesTable.setColumnCount(6)
  182. self.__rulesTable.setHorizontalHeaderLabels([
  183. "errorType",
  184. "amount",
  185. "period",
  186. "status",
  187. "destination",
  188. "duration"
  189. ])
  190. rules = self.__guard.rules()
  191. self.__rulesTable.setRowCount(len(rules))
  192. index = 0
  193. for rule in rules:
  194. self.updateRuleIndex(
  195. index,
  196. rule.condition.errorType,
  197. rule.condition.amount,
  198. rule.condition.period,
  199. rule.condition.status,
  200. rule.consequence.type,
  201. rule.consequence.duration
  202. )
  203. index += 1
  204. def __delSelectedRule(self):
  205. index = self.__rulesTable.currentRow()
  206. self.__guard.delRule(index)
  207. self.__rulesTable.removeRow(index)
  208. def __editSelectedRule(self):
  209. # Get data of selected rule.
  210. rowIndex = self.__rulesTable.currentRow()
  211. errorType = self.__rulesTable.item(rowIndex, 0).data(Qt.UserRole)
  212. amount = self.__rulesTable.item(rowIndex, 1).data(Qt.UserRole)
  213. period = self.__rulesTable.item(rowIndex, 2).data(Qt.UserRole)
  214. statusCode = self.__rulesTable.item(rowIndex, 3).data(Qt.UserRole)
  215. destinationType = self.__rulesTable.item(rowIndex, 4).data(Qt.UserRole)
  216. duration = self.__rulesTable.item(rowIndex, 5).data(Qt.UserRole)
  217. # Open edit dialog with data from the selected rule.
  218. dialog = GuardEditRuleDialog(
  219. errorType=errorType,
  220. amount=amount,
  221. period=period,
  222. statusCode=statusCode,
  223. destinationType=destinationType,
  224. duration=duration
  225. )
  226. if dialog.exec():
  227. # Accept button is pressed; update the data.
  228. rule = self.__guard.getRule(rowIndex)
  229. rule.condition.errorType = dialog.errorType()
  230. rule.condition.amount = dialog.amount()
  231. rule.condition.period = dialog.period()
  232. rule.condition.status = dialog.statusCode()
  233. rule.consequence.type = dialog.destinationType()
  234. rule.consequence.duration = dialog.duration()
  235. self.updateTable() # Re-generate the table.
  236. def __selectionChanged(self):
  237. # When no rule is selected disable the edit and del buttons.
  238. if self.__rulesTable.currentItem():
  239. self.__editButton.setEnabled(True)
  240. self.__delButton.setEnabled(True)
  241. # Only enable up when the item can go up (index > 0)
  242. currentIndex = self.__rulesTable.currentIndex().row()
  243. if currentIndex:
  244. self.__upButton.setEnabled(True)
  245. else:
  246. self.__upButton.setEnabled(False)
  247. # Only enable down when the item can go down
  248. if currentIndex < self.__rulesTable.rowCount() - 1:
  249. self.__downButton.setEnabled(True)
  250. else:
  251. self.__downButton.setEnabled(False)
  252. else:
  253. self.__editButton.setEnabled(False)
  254. self.__delButton.setEnabled(False)
  255. self.__upButton.setEnabled(False)
  256. self.__downButton.setEnabled(False)
  257. def updateRuleIndex(
  258. self, index, errorType, amount,
  259. period, statusCode, destinationType, duration
  260. ):
  261. # errorType
  262. tableItem = QTableWidgetItem(RequestsErrorTypeStr[errorType])
  263. tableItem.setData(Qt.UserRole, QVariant(errorType))
  264. self.__rulesTable.setItem(index, 0, tableItem)
  265. # amount
  266. str_ = str(amount) + "x"
  267. tableItem = QTableWidgetItem(str_)
  268. tableItem.setData(Qt.UserRole, QVariant(amount))
  269. self.__rulesTable.setItem(index, 1, tableItem)
  270. # period
  271. if period:
  272. str_ = durationMinutesToString(period)
  273. else:
  274. str_ = _("Always")
  275. tableItem = QTableWidgetItem(str_)
  276. tableItem.setData(Qt.UserRole, QVariant(period))
  277. self.__rulesTable.setItem(index, 2, tableItem)
  278. # statusCode
  279. if statusCode:
  280. str_ = str(statusCode)
  281. else:
  282. str_ = "-"
  283. tableItem = QTableWidgetItem(str_)
  284. tableItem.setData(Qt.UserRole, QVariant(statusCode))
  285. self.__rulesTable.setItem(index, 3, tableItem)
  286. # destinationType
  287. tableItem = QTableWidgetItem(ConsequenceTypeStr[destinationType])
  288. tableItem.setData(Qt.UserRole, QVariant(destinationType))
  289. self.__rulesTable.setItem(index, 4, tableItem)
  290. # duration
  291. if duration:
  292. str_ = durationMinutesToString(duration)
  293. elif destinationType == ConsequenceType.Blacklist:
  294. str_ = "-"
  295. else:
  296. str_ = _("Until restart")
  297. tableItem = QTableWidgetItem(str_)
  298. tableItem.setData(Qt.UserRole, QVariant(duration))
  299. self.__rulesTable.setItem(index, 5, tableItem)
  300. def __addRuleDialog(self):
  301. dialog = GuardEditRuleDialog()
  302. if dialog.exec():
  303. # errorType
  304. # amount
  305. # period
  306. # statusCode
  307. # destinationType
  308. # duration
  309. rowIndex = self.__rulesTable.rowCount()
  310. self.__rulesTable.setRowCount(rowIndex + 1)
  311. # Add view
  312. self.updateRuleIndex(
  313. rowIndex,
  314. dialog.errorType(),
  315. dialog.amount(),
  316. dialog.period(),
  317. dialog.statusCode(),
  318. dialog.destinationType(),
  319. dialog.duration()
  320. )
  321. # Add the rule itself to Guard
  322. self.__guard.addRule(
  323. dialog.errorType(),
  324. dialog.destinationType(),
  325. amount=dialog.amount(),
  326. period=dialog.period(),
  327. status=dialog.statusCode(),
  328. duration=dialog.duration()
  329. )
  330. class GuardEditRuleDialog(QDialog):
  331. def __init__(
  332. self,
  333. errorType=RequestsErrorTypes.WrongStatus,
  334. amount=0,
  335. period=0,
  336. statusCode=None,
  337. destinationType=ConsequenceType.Timeout,
  338. duration=0,
  339. parent=None
  340. ):
  341. QDialog.__init__(self, parent=parent)
  342. self.setWindowTitle(_("Guard rule editor"))
  343. layout = QFormLayout(self)
  344. # errorType QComboBox
  345. # amount QSpinBox
  346. # period QSpinBox
  347. # status QComboBox
  348. # destination QComboBox
  349. # duration QSpinBox
  350. # Error type
  351. self.__errorTypeCombo = QComboBox(self)
  352. for errType, errTypeStr in RequestsErrorTypeStr.items():
  353. self.__errorTypeCombo.addItem(errTypeStr, QVariant(errType))
  354. index = self.__errorTypeCombo.findData(QVariant(errorType))
  355. self.__errorTypeCombo.setCurrentIndex(index)
  356. layout.addRow(QLabel(_("Error type") + ":"), self.__errorTypeCombo)
  357. # Amount of failes to trigger.
  358. self.__amountSpin = QSpinBox(self)
  359. self.__amountSpin.setRange(0, 64)
  360. self.__amountSpin.setValue(amount)
  361. label = QLabel(_("Amount of failes to trigger") + ":")
  362. label.setWordWrap(True)
  363. layout.addRow(label, self.__amountSpin)
  364. # Timeframe in minutes where the amount of failes have to occur in.
  365. self.__periodSpin = QSpinBox(self)
  366. self.__periodSpin.setSuffix(_("min"))
  367. self.__periodSpin.setRange(0, 6000)
  368. self.__periodSpin.setValue(period)
  369. label = QLabel(
  370. _("Timeframe in minutes where the amount of failes have to occur"
  371. " in") + ":"
  372. )
  373. label.setWordWrap(True)
  374. layout.addRow(label, self.__periodSpin)
  375. # Status code
  376. self.__statusCodeCombo = QComboBox(self)
  377. self.__statusCodeCombo.addItem(_("All"), QVariant(None))
  378. for code, statusStr in StatusCodes.items():
  379. if code >= 400:
  380. self.__statusCodeCombo.addItem(
  381. str(code) + " - " + statusStr[0],
  382. QVariant(code)
  383. )
  384. index = self.__statusCodeCombo.findData(QVariant(statusCode))
  385. self.__statusCodeCombo.setCurrentIndex(index)
  386. label = QLabel(_("Response status code") + ":")
  387. label.setWordWrap(True)
  388. layout.addRow(label, self.__statusCodeCombo)
  389. # Destination
  390. self.__destinationCombo = QComboBox(self)
  391. for destType, destStr in ConsequenceTypeStr.items():
  392. self.__destinationCombo.addItem(destStr, QVariant(destType))
  393. index = self.__destinationCombo.findData(QVariant(destinationType))
  394. self.__destinationCombo.setCurrentIndex(index)
  395. label = QLabel(_("Destination") + ":")
  396. label.setWordWrap(True)
  397. layout.addRow(label, self.__destinationCombo)
  398. # Duration
  399. self.__durationSpin = QSpinBox(self)
  400. self.__durationSpin.setSuffix(_("min"))
  401. self.__durationSpin.setRange(0, 6000)
  402. self.__durationSpin.setValue(duration)
  403. label = QLabel(_("Duration of the timeout") + ":")
  404. label.setWordWrap(True)
  405. layout.addRow(label, self.__durationSpin)
  406. # Cancel/Save button
  407. cancelButton = Button(_("Cancel"), self)
  408. saveButton = Button(_("Save"), self)
  409. layout.addRow(cancelButton, saveButton)
  410. # Initial enable / disable errorType/destination combo
  411. self.__errorTypeChanged(self.__errorTypeCombo.currentIndex())
  412. self.__destinationChanged(self.__destinationCombo.currentIndex())
  413. # Connections
  414. self.__errorTypeCombo.currentIndexChanged.connect(
  415. self.__errorTypeChanged
  416. )
  417. self.__destinationCombo.currentIndexChanged.connect(
  418. self.__destinationChanged
  419. )
  420. cancelButton.clicked.connect(self.reject)
  421. saveButton.clicked.connect(self.accept)
  422. def errorType(self):
  423. return self.__errorTypeCombo.currentData()
  424. def amount(self):
  425. return self.__amountSpin.value()
  426. def period(self):
  427. return self.__periodSpin.value()
  428. def statusCode(self):
  429. errorType = self.__errorTypeCombo.currentData()
  430. if errorType == RequestsErrorTypes.WrongStatus:
  431. return self.__statusCodeCombo.currentData()
  432. return None
  433. def destinationType(self):
  434. return self.__destinationCombo.currentData()
  435. def duration(self):
  436. destType = self.__destinationCombo.currentData()
  437. if destType == ConsequenceType.Timeout:
  438. return self.__durationSpin.value()
  439. return 0
  440. def __errorTypeChanged(self, index):
  441. # The status code combobox should only be enabled when the WrongStatus
  442. # errorType is selected.
  443. errorType = self.__errorTypeCombo.itemData(index)
  444. if errorType == RequestsErrorTypes.WrongStatus:
  445. self.__statusCodeCombo.setEnabled(True)
  446. else:
  447. self.__statusCodeCombo.setEnabled(False)
  448. def __destinationChanged(self, index):
  449. # The duration spinbox should only be enabled when the destinationType
  450. # is Timeout.
  451. destType = self.__destinationCombo.itemData(index)
  452. if destType == ConsequenceType.Timeout:
  453. self.__durationSpin.setEnabled(True)
  454. else:
  455. self.__durationSpin.setEnabled(False)
  456. class GuardLogViewer(QWidget):
  457. def __init__(self, guard, parent=None):
  458. QWidget.__init__(self, parent=parent)
  459. self.__guard = guard
  460. layout = QVBoxLayout(self)
  461. layout.addWidget(QLabel("<h2>Log</h2>", self), 0, Qt.AlignTop)
  462. # Toolbar
  463. buttonLayout = QHBoxLayout()
  464. self.__refreshButton = Button("Refresh", self)
  465. buttonLayout.addWidget(self.__refreshButton, 0, Qt.AlignLeft)
  466. self.__clearButton = Button("Clear", self)
  467. buttonLayout.addWidget(self.__clearButton, 1, Qt.AlignLeft)
  468. layout.addLayout(buttonLayout)
  469. # Table
  470. self.__logTable = QTableWidget(self)
  471. self.__logTable.setEditTriggers(QAbstractItemView.NoEditTriggers)
  472. self.__logTable.setSelectionBehavior(QAbstractItemView.SelectRows)
  473. self.__logTable.setContextMenuPolicy(Qt.CustomContextMenu)
  474. layout.addWidget(self.__logTable)
  475. self.__refreshButton.clicked.connect(self.updateTable)
  476. self.__clearButton.clicked.connect(self.__clearLogPressed)
  477. self.__logTable.customContextMenuRequested.connect(self.__contextMenu)
  478. self.updateTable()
  479. def __contextMenu(self, pos):
  480. selectionModel = self.__logTable.selectionModel()
  481. if selectionModel.hasSelection():
  482. indexes = selectionModel.selectedRows(column=1)
  483. menu = QMenu(self)
  484. # Copy URL
  485. copyUrlAction = menu.addAction(_("Copy URL"))
  486. # Clear log entries for instance(s)
  487. removeInstanceEntriesAction = menu.addAction(
  488. _("Clear log for selected instance(s)")
  489. )
  490. # Exec the menu
  491. action = menu.exec_(self.__logTable.mapToGlobal(pos))
  492. if action == copyUrlAction:
  493. index = indexes[0]
  494. item = self.__logTable.item(index.row(), index.column())
  495. url = item.data(Qt.UserRole)
  496. clipboard = QGuiApplication.clipboard()
  497. clipboard.setText(url)
  498. elif action == removeInstanceEntriesAction:
  499. removedInstances = []
  500. for modelIndex in indexes:
  501. item = self.__logTable.item(
  502. modelIndex.row(), modelIndex.column()
  503. )
  504. url = item.data(Qt.UserRole)
  505. if url in removedInstances:
  506. continue
  507. self.__guard.clearInstanceLog(url)
  508. removedInstances.append(url)
  509. self.updateTable()
  510. def __clearLogPressed(self):
  511. answer = QMessageBox.question(
  512. self,
  513. _("Confirm clear log"),
  514. _("Clear log?"),
  515. QMessageBox.Yes | QMessageBox.No
  516. )
  517. if answer == QMessageBox.Yes:
  518. self.clearLog()
  519. def clearLog(self):
  520. self.__guard.clearLog()
  521. self.updateTable()
  522. def updateTable(self):
  523. # Disable sorting, else it will mess up when already sorted.
  524. self.__logTable.setSortingEnabled(False)
  525. self.__logTable.clear()
  526. self.__logTable.setColumnCount(5)
  527. self.__logTable.setHorizontalHeaderLabels([
  528. "datetime",
  529. "instance",
  530. "error",
  531. "status",
  532. "content"
  533. ])
  534. self.__logTable.horizontalHeader().setStretchLastSection(True)
  535. self.__logTable.horizontalHeader().setSectionResizeMode(
  536. QHeaderView.ResizeToContents
  537. )
  538. # Calc row count
  539. rowCount = 0
  540. log = self.__guard.log()
  541. for url in log:
  542. for errType in log[url]:
  543. rowCount += len(log[url][errType])
  544. self.__logTable.setRowCount(rowCount)
  545. index = 0
  546. for url in log:
  547. for errType in log[url]:
  548. errCount = len(log[url][errType])
  549. for logTime, status, content in log[url][errType]:
  550. # Datetime
  551. tableItem = QTableWidgetItem(
  552. timeToString(logTime * 60, fmt="%D %H:%M:%S")
  553. )
  554. tableItem.setData(Qt.UserRole, QVariant(logTime))
  555. self.__logTable.setItem(index, 0, tableItem)
  556. # Url
  557. tableItem = QTableWidgetItem(url)
  558. tableItem.setData(Qt.UserRole, QVariant(url))
  559. tableItem.setToolTip(str(errCount))
  560. self.__logTable.setItem(index, 1, tableItem)
  561. # Error
  562. tableItem = QTableWidgetItem(RequestsErrorTypeStr[errType])
  563. self.__logTable.setItem(index, 2, tableItem)
  564. # Status
  565. tableItem = QTableWidgetItem(str(status))
  566. if status:
  567. tableItem.setToolTip(str(StatusCodes[status]))
  568. self.__logTable.setItem(index, 3, tableItem)
  569. # Content
  570. tableItem = QTableWidgetItem(str(content))
  571. self.__logTable.setItem(index, 4, tableItem)
  572. index += 1
  573. # Enable sorting again (restore the set sorting.)
  574. self.__logTable.setSortingEnabled(True)