1 <style type=
"text/css">
20 #logFilterBar>button
{
21 display: inline-block
;
27 flex-direction: column
;
38 #logMessageTableFixedHeaderDiv .dynamicTableHeader
,
39 #logPeerTableFixedHeaderDiv .dynamicTableHeader
{
44 background-image: url
("../images/edit-find.svg");
45 background-repeat: no-repeat
;
46 background-position: left
;
47 background-size: 1.5em;
48 padding: 2px 12px 2px 2em;
51 border: 1px solid var
(--color-border-default
);
65 .logTableRowlogNormal {
66 color: var
(--color-text-default
);
70 color: var
(--color-text-blue
);
73 .logTableRowlogWarning {
74 color: var
(--color-text-orange
);
77 .logTableRowlogCritical
,
78 .logTableRowpeerBlocked {
79 color: var
(--color-text-red
);
83 padding: 2px 12px !important
;
90 <div id=
"logFilterBar">
91 <label for=
"logLevelSelect">QBT_TR(Log Levels:)QBT_TR[CONTEXT=ExecutionLogWidget]
</label>
92 <select multiple
size=
"1" id=
"logLevelSelect" class=
"logLevelSelect" onchange=
"window.qBittorrent.Log.logLevelChanged()">
93 <option value=
"1">QBT_TR(Normal Messages)QBT_TR[CONTEXT=ExecutionLogWidget]
</option>
94 <option value=
"2">QBT_TR(Information Messages)QBT_TR[CONTEXT=ExecutionLogWidget]
</option>
95 <option value=
"4">QBT_TR(Warning Messages)QBT_TR[CONTEXT=ExecutionLogWidget]
</option>
96 <option value=
"8">QBT_TR(Critical Messages)QBT_TR[CONTEXT=ExecutionLogWidget]
</option>
99 <input type=
"text" id=
"filterTextInput" onkeyup=
"window.qBittorrent.Log.filterTextChanged()" placeholder=
"QBT_TR(Filter logs)QBT_TR[CONTEXT=ExecutionLogWidget]" aria-label=
"QBT_TR(Filter logs)QBT_TR[CONTEXT=ExecutionLogWidget]" autocomplete=
"off" autocorrect=
"off" autocapitalize=
"none">
100 <button type=
"button" title=
"Clear input" onclick=
"javascript:document.querySelector('#filterTextInput').value='';window.qBittorrent.Log.filterTextChanged();">QBT_TR(Clear)QBT_TR[CONTEXT=ExecutionLogWidget]
</button>
103 <div id=
"logFilterSummary">
104 <span>QBT_TR(Results)QBT_TR[CONTEXT=ExecutionLogWidget] (QBT_TR(showing)QBT_TR[CONTEXT=ExecutionLogWidget]
<span id=
"numFilteredLogs">0</span> QBT_TR(out of)QBT_TR[CONTEXT=ExecutionLogWidget]
<span id=
"numTotalLogs">0</span>):
</span>
108 <div id=
"logContentView">
109 <div id=
"logMessageView">
110 <div id=
"logMessageTableFixedHeaderDiv" class=
"dynamicTableFixedHeaderDiv">
111 <table class=
"dynamicTable unselectable" style=
"position:relative;">
113 <tr class=
"dynamicTableHeader"></tr>
117 <div id=
"logMessageTableDiv" class=
"dynamicTableDiv">
118 <table class=
"dynamicTable unselectable">
120 <tr class=
"dynamicTableHeader"></tr>
126 <div id=
"logPeerView" class=
"invisible">
127 <div id=
"logPeerTableFixedHeaderDiv" class=
"dynamicTableFixedHeaderDiv">
128 <table class=
"dynamicTable unselectable" style=
"position:relative;">
130 <tr class=
"dynamicTableHeader"></tr>
134 <div id=
"logPeerTableDiv" class=
"dynamicTableDiv">
135 <table class=
"dynamicTable unselectable">
137 <tr class=
"dynamicTableHeader"></tr>
146 <ul id=
"logTableMenu" class=
"contextMenu">
147 <li><a href=
"#" class=
"copyLogDataToClipboard"><img src=
"images/edit-copy.svg" alt=
"QBT_TR(Copy)QBT_TR[CONTEXT=ExecutionLogWidget]">QBT_TR(Copy)QBT_TR[CONTEXT=ExecutionLogWidget]
</a></li>
148 <li><a href=
"#Clear"><img src=
"images/list-remove.svg" alt=
"QBT_TR(Clear)QBT_TR[CONTEXT=ExecutionLogWidget]">QBT_TR(Clear)QBT_TR[CONTEXT=ExecutionLogWidget]
</a></li>
154 window
.qBittorrent
??= {};
155 window
.qBittorrent
.Log
??= (() => {
156 const exports
= () => {
161 setCurrentTab
: setCurrentTab
,
162 getFilterText
: getFilterText
,
163 getSelectedLevels
: getSelectedLevels
,
164 logLevelChanged
: logLevelChanged
,
165 filterTextChanged
: filterTextChanged
169 let currentSelectedTab
= "main";
172 instance
: new window
.qBittorrent
.DynamicTable
.LogMessageTable(),
178 instance
: new window
.qBittorrent
.DynamicTable
.LogPeerTable(),
185 let logFilterTimer
= -1;
186 let inputtedFilterText
= "";
188 let selectedLogLevels
= JSON
.parse(LocalPreferences
.get("qbt_selected_log_levels")) || ["1", "2", "4", "8"];
191 $("logLevelSelect").getElements("option").each((x
) => {
192 if (selectedLogLevels
.indexOf(x
.value
.toString()) !== -1)
198 selectBox
= new vanillaSelectBox("#logLevelSelect", {
202 all
: "QBT_TR(All)QBT_TR[CONTEXT=ExecutionLogWidget]",
203 item
: "QBT_TR(item)QBT_TR[CONTEXT=ExecutionLogWidget]",
204 items
: "QBT_TR(items)QBT_TR[CONTEXT=ExecutionLogWidget]",
205 selectAll
: "QBT_TR(Select All)QBT_TR[CONTEXT=ExecutionLogWidget]",
206 clearAll
: "QBT_TR(Clear All)QBT_TR[CONTEXT=ExecutionLogWidget]",
208 placeHolder
: "QBT_TR(Choose a log level...)QBT_TR[CONTEXT=ExecutionLogWidget]",
209 keepInlineStyles
: false
212 const logTableContextMenu
= new window
.qBittorrent
.ContextMenu
.ContextMenu({
213 targets
: ".logTableRow",
214 menu
: "logTableMenu",
217 tableInfo
[currentSelectedTab
].instance
.selectedRowsIds().forEach((rowId
) => {
218 tableInfo
[currentSelectedTab
].instance
.removeRow(rowId
);
230 tableInfo
["main"].instance
.setup("logMessageTableDiv", "logMessageTableFixedHeaderDiv", logTableContextMenu
);
231 tableInfo
["peer"].instance
.setup("logPeerTableDiv", "logPeerTableFixedHeaderDiv", logTableContextMenu
);
233 MUI
.Panels
.instances
.LogPanel
.contentEl
.style
.height
= "100%";
238 const unload
= () => {
239 for (const table
in tableInfo
) {
240 if (!Object
.hasOwn(tableInfo
, table
))
242 resetTableTimer(table
);
247 syncLogWithInterval(100);
250 const resetTableTimer
= (curTab
) => {
251 if (curTab
=== undefined)
252 curTab
= currentSelectedTab
;
254 clearTimeout(tableInfo
[curTab
].timer
);
255 tableInfo
[curTab
].timer
= null;
258 const syncLogWithInterval
= (interval
) => {
259 if (!tableInfo
[currentSelectedTab
].progress
) {
260 clearTimeout(tableInfo
[currentSelectedTab
].timer
);
261 tableInfo
[currentSelectedTab
].timer
= syncLogData
.delay(interval
, null, currentSelectedTab
);
265 const getFilterText
= () => {
266 return inputtedFilterText
;
269 const getSelectedLevels
= () => {
270 return selectedLogLevels
;
273 const getSyncLogDataInterval
= () => {
274 return serverSyncMainDataInterval
;
277 const logLevelChanged
= () => {
278 const value
= selectBox
.getResult().sort();
280 if (selectedLogLevels
!== value
) {
281 tableInfo
[currentSelectedTab
].last_id
= -1;
282 selectedLogLevels
= value
;
283 LocalPreferences
.set("qbt_selected_log_levels", JSON
.stringify(selectedLogLevels
));
288 const filterTextChanged
= () => {
289 const value
= $("filterTextInput").value
.trim();
290 if (inputtedFilterText
!== value
) {
291 inputtedFilterText
= value
;
296 const logFilterChanged
= () => {
297 clearTimeout(logFilterTimer
);
298 logFilterTimer
= setTimeout((curTab
) => {
301 tableInfo
[curTab
].instance
.updateTable(false);
302 updateLabelCount(curTab
);
303 }, window
.qBittorrent
.Misc
.FILTER_INPUT_DELAY
, currentSelectedTab
);
306 const setCurrentTab
= (tab
) => {
307 if (tab
=== currentSelectedTab
)
310 currentSelectedTab
= tab
;
311 if (currentSelectedTab
=== "main") {
313 $("logMessageView").removeClass("invisible");
314 $("logPeerView").addClass("invisible");
315 resetTableTimer("peer");
319 $("logMessageView").addClass("invisible");
320 $("logPeerView").removeClass("invisible");
321 resetTableTimer("main");
324 clearTimeout(logFilterTimer
);
328 if (tableInfo
[currentSelectedTab
].instance
.filterText
!== getFilterText())
329 tableInfo
[currentSelectedTab
].instance
.updateTable();
334 const updateLabelCount
= (curTab
) => {
335 if (curTab
=== undefined)
336 curTab
= currentSelectedTab
;
338 $("numFilteredLogs").textContent
= tableInfo
[curTab
].instance
.filteredLength();
339 $("numTotalLogs").textContent
= tableInfo
[curTab
].instance
.getRowSize();
342 const syncLogData
= (curTab
) => {
343 if (curTab
=== undefined)
344 curTab
= currentSelectedTab
;
347 if (curTab
=== "main") {
348 url
= new URI("api/v2/log/main");
350 normal
: selectedLogLevels
.indexOf("1") !== -1,
351 info
: selectedLogLevels
.indexOf("2") !== -1,
352 warning
: selectedLogLevels
.indexOf("4") !== -1,
353 critical
: selectedLogLevels
.indexOf("8") !== -1
357 url
= new URI("api/v2/log/peers");
360 url
.setData("last_known_id", tableInfo
[curTab
].last_id
);
361 tableInfo
[curTab
].progress
= true;
367 onFailure
: (response
) => {
368 const errorDiv
= $("error_div");
370 errorDiv
.textContent
= "QBT_TR(qBittorrent client is not reachable)QBT_TR[CONTEXT=HttpServer]";
371 tableInfo
[curTab
].progress
= false;
372 syncLogWithInterval(10000);
374 onSuccess
: (response
) => {
375 $("error_div").textContent
= "";
377 if ($("logTabColumn").hasClass("invisible"))
380 if (response
.length
> 0) {
381 clearTimeout(logFilterTimer
);
384 for (let i
= 0; i
< response
.length
; ++i
) {
386 if (curTab
=== "main") {
388 rowId
: response
[i
].id
,
389 message
: response
[i
].message
,
390 timestamp
: response
[i
].timestamp
,
391 type
: response
[i
].type
,
396 rowId
: response
[i
].id
,
398 timestamp
: response
[i
].timestamp
,
399 blocked
: response
[i
].blocked
,
400 reason
: response
[i
].reason
,
403 tableInfo
[curTab
].instance
.updateRowData(row
);
404 tableInfo
[curTab
].last_id
= Math
.max(response
[i
].id
.toInt(), tableInfo
[curTab
].last_id
);
407 tableInfo
[curTab
].instance
.updateTable();
408 updateLabelCount(curTab
);
411 tableInfo
[curTab
].progress
= false;
412 syncLogWithInterval(getSyncLogDataInterval());
417 new ClipboardJS(".copyLogDataToClipboard", {
420 tableInfo
[currentSelectedTab
].instance
.selectedRowsIds().forEach((rowId
) => {
421 msg
.push(tableInfo
[currentSelectedTab
].instance
.getRow(rowId
).full_data
[(currentSelectedTab
=== "main") ? "message" : "ip"]);
424 return msg
.join("\n");
430 Object
.freeze(window
.qBittorrent
.Log
);