1 <style type=
"text/css">
15 #logFilterBar>button
{
16 display: inline-block
;
31 #logMessageTableFixedHeaderDiv .dynamicTableHeader
,
32 #logPeerTableFixedHeaderDiv .dynamicTableHeader
{
37 background-image: url
("../images/edit-find.svg");
38 background-repeat: no-repeat
;
39 background-position: left
;
40 background-size: 1.5em;
41 padding: 4px 5px 4px 2em;
44 border: 1px solid var
(--color-border-default
);
59 color: var
(--color-text-default
);
63 color: var
(--color-text-blue
);
67 color: var
(--color-text-orange
);
72 color: var
(--color-text-red
);
76 padding: 4px 12px !important
;
83 <div id=
"logFilterBar">
84 <label for=
"logLevelSelect">QBT_TR(Log Levels:)QBT_TR[CONTEXT=ExecutionLogWidget]
</label>
85 <select multiple
size=
"1" id=
"logLevelSelect" class=
"logLevelSelect" onchange=
"window.qBittorrent.Log.logLevelChanged()">
86 <option value=
"1">QBT_TR(Normal Messages)QBT_TR[CONTEXT=ExecutionLogWidget]
</option>
87 <option value=
"2">QBT_TR(Information Messages)QBT_TR[CONTEXT=ExecutionLogWidget]
</option>
88 <option value=
"4">QBT_TR(Warning Messages)QBT_TR[CONTEXT=ExecutionLogWidget]
</option>
89 <option value=
"8">QBT_TR(Critical Messages)QBT_TR[CONTEXT=ExecutionLogWidget]
</option>
92 <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">
93 <button type=
"button" title=
"Clear input" onclick=
"javascript:document.querySelector('#filterTextInput').value='';window.qBittorrent.Log.filterTextChanged();">QBT_TR(Clear)QBT_TR[CONTEXT=ExecutionLogWidget]
</button>
96 <div id=
"logFilterSummary">
97 <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>
101 <div id=
"logContentView">
102 <div id=
"logMessageView">
103 <div id=
"logMessageTableFixedHeaderDiv" class=
"dynamicTableFixedHeaderDiv">
104 <table class=
"dynamicTable unselectable" style=
"position:relative;">
106 <tr class=
"dynamicTableHeader"></tr>
110 <div id=
"logMessageTableDiv" class=
"dynamicTableDiv">
111 <table class=
"dynamicTable unselectable">
113 <tr class=
"dynamicTableHeader"></tr>
119 <div id=
"logPeerView" class=
"invisible">
120 <div id=
"logPeerTableFixedHeaderDiv" class=
"dynamicTableFixedHeaderDiv">
121 <table class=
"dynamicTable unselectable" style=
"position:relative;">
123 <tr class=
"dynamicTableHeader"></tr>
127 <div id=
"logPeerTableDiv" class=
"dynamicTableDiv">
128 <table class=
"dynamicTable unselectable">
130 <tr class=
"dynamicTableHeader"></tr>
139 <ul id=
"logTableMenu" class=
"contextMenu">
140 <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>
141 <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>
147 window
.qBittorrent
??= {};
148 window
.qBittorrent
.Log
??= (() => {
149 const exports
= () => {
154 setCurrentTab
: setCurrentTab
,
155 getFilterText
: getFilterText
,
156 getSelectedLevels
: getSelectedLevels
,
157 logLevelChanged
: logLevelChanged
,
158 filterTextChanged
: filterTextChanged
162 let currentSelectedTab
= "main";
165 instance
: new window
.qBittorrent
.DynamicTable
.LogMessageTable(),
171 instance
: new window
.qBittorrent
.DynamicTable
.LogPeerTable(),
178 let customSyncLogDataInterval
= null;
179 let logFilterTimer
= -1;
180 let inputtedFilterText
= "";
182 let selectedLogLevels
= JSON
.parse(LocalPreferences
.get("qbt_selected_log_levels")) || ["1", "2", "4", "8"];
185 $("logLevelSelect").getElements("option").each((x
) => {
186 if (selectedLogLevels
.indexOf(x
.value
.toString()) !== -1)
192 selectBox
= new vanillaSelectBox("#logLevelSelect", {
196 all
: "QBT_TR(All)QBT_TR[CONTEXT=ExecutionLogWidget]",
197 item
: "QBT_TR(item)QBT_TR[CONTEXT=ExecutionLogWidget]",
198 items
: "QBT_TR(items)QBT_TR[CONTEXT=ExecutionLogWidget]",
199 selectAll
: "QBT_TR(Select All)QBT_TR[CONTEXT=ExecutionLogWidget]",
200 clearAll
: "QBT_TR(Clear All)QBT_TR[CONTEXT=ExecutionLogWidget]",
202 placeHolder
: "QBT_TR(Choose a log level...)QBT_TR[CONTEXT=ExecutionLogWidget]",
203 keepInlineStyles
: false
206 const logTableContextMenu
= new window
.qBittorrent
.ContextMenu
.ContextMenu({
207 targets
: ".logTableRow",
208 menu
: "logTableMenu",
211 tableInfo
[currentSelectedTab
].instance
.selectedRowsIds().forEach((rowId
) => {
212 tableInfo
[currentSelectedTab
].instance
.removeRow(rowId
);
224 tableInfo
["main"].instance
.setup("logMessageTableDiv", "logMessageTableFixedHeaderDiv", logTableContextMenu
);
225 tableInfo
["peer"].instance
.setup("logPeerTableDiv", "logPeerTableFixedHeaderDiv", logTableContextMenu
);
227 MUI
.Panels
.instances
.LogPanel
.contentEl
.style
.height
= "100%";
228 $("logView").style
.height
= "inherit";
233 const unload
= () => {
234 for (const table
in tableInfo
) {
235 if (!Object
.hasOwn(tableInfo
, table
))
237 resetTableTimer(table
);
242 customSyncLogDataInterval
= null;
243 syncLogWithInterval(100);
246 const resetTableTimer
= (curTab
) => {
247 if (curTab
=== undefined)
248 curTab
= currentSelectedTab
;
250 clearTimeout(tableInfo
[curTab
].timer
);
251 tableInfo
[curTab
].timer
= null;
254 const syncLogWithInterval
= (interval
) => {
255 if (!tableInfo
[currentSelectedTab
].progress
) {
256 clearTimeout(tableInfo
[currentSelectedTab
].timer
);
257 tableInfo
[currentSelectedTab
].timer
= syncLogData
.delay(interval
, null, currentSelectedTab
);
261 const getFilterText
= () => {
262 return inputtedFilterText
;
265 const getSelectedLevels
= () => {
266 return selectedLogLevels
;
269 const getSyncLogDataInterval
= () => {
270 return customSyncLogDataInterval
? customSyncLogDataInterval
: serverSyncMainDataInterval
;
273 const logLevelChanged
= () => {
274 const value
= selectBox
.getResult().sort();
276 if (selectedLogLevels
!== value
) {
277 tableInfo
[currentSelectedTab
].last_id
= -1;
278 selectedLogLevels
= value
;
279 LocalPreferences
.set("qbt_selected_log_levels", JSON
.stringify(selectedLogLevels
));
284 const filterTextChanged
= () => {
285 const value
= $("filterTextInput").value
.trim();
286 if (inputtedFilterText
!== value
) {
287 inputtedFilterText
= value
;
292 const logFilterChanged
= () => {
293 clearTimeout(logFilterTimer
);
294 logFilterTimer
= setTimeout((curTab
) => {
297 tableInfo
[curTab
].instance
.updateTable(false);
298 updateLabelCount(curTab
);
299 }, window
.qBittorrent
.Misc
.FILTER_INPUT_DELAY
, currentSelectedTab
);
302 const setCurrentTab
= (tab
) => {
303 if (tab
=== currentSelectedTab
)
306 currentSelectedTab
= tab
;
307 if (currentSelectedTab
=== "main") {
309 $("logMessageView").removeClass("invisible");
310 $("logPeerView").addClass("invisible");
311 resetTableTimer("peer");
315 $("logMessageView").addClass("invisible");
316 $("logPeerView").removeClass("invisible");
317 resetTableTimer("main");
320 clearTimeout(logFilterTimer
);
324 if (tableInfo
[currentSelectedTab
].instance
.filterText
!== getFilterText())
325 tableInfo
[currentSelectedTab
].instance
.updateTable();
330 const updateLabelCount
= (curTab
) => {
331 if (curTab
=== undefined)
332 curTab
= currentSelectedTab
;
334 $("numFilteredLogs").textContent
= tableInfo
[curTab
].instance
.filteredLength();
335 $("numTotalLogs").textContent
= tableInfo
[curTab
].instance
.getRowIds().length
;
338 const syncLogData
= (curTab
) => {
339 if (curTab
=== undefined)
340 curTab
= currentSelectedTab
;
343 if (curTab
=== "main") {
344 url
= new URI("api/v2/log/main");
346 normal
: selectedLogLevels
.indexOf("1") !== -1,
347 info
: selectedLogLevels
.indexOf("2") !== -1,
348 warning
: selectedLogLevels
.indexOf("4") !== -1,
349 critical
: selectedLogLevels
.indexOf("8") !== -1
353 url
= new URI("api/v2/log/peers");
356 url
.setData("last_known_id", tableInfo
[curTab
].last_id
);
357 tableInfo
[curTab
].progress
= true;
363 onFailure: function(response
) {
364 const errorDiv
= $("error_div");
366 errorDiv
.textContent
= "QBT_TR(qBittorrent client is not reachable)QBT_TR[CONTEXT=HttpServer]";
367 tableInfo
[curTab
].progress
= false;
368 syncLogWithInterval(10000);
370 onSuccess: function(response
) {
371 $("error_div").textContent
= "";
373 if ($("logTabColumn").hasClass("invisible"))
376 if (response
.length
> 0) {
377 clearTimeout(logFilterTimer
);
380 for (let i
= 0; i
< response
.length
; ++i
) {
382 if (curTab
=== "main") {
384 rowId
: response
[i
].id
,
385 message
: response
[i
].message
,
386 timestamp
: response
[i
].timestamp
,
387 type
: response
[i
].type
,
392 rowId
: response
[i
].id
,
394 timestamp
: response
[i
].timestamp
,
395 blocked
: response
[i
].blocked
,
396 reason
: response
[i
].reason
,
399 tableInfo
[curTab
].instance
.updateRowData(row
);
400 tableInfo
[curTab
].last_id
= Math
.max(response
[i
].id
.toInt(), tableInfo
[curTab
].last_id
);
403 tableInfo
[curTab
].instance
.updateTable();
404 updateLabelCount(curTab
);
407 tableInfo
[curTab
].progress
= false;
408 syncLogWithInterval(getSyncLogDataInterval());
413 new ClipboardJS(".copyLogDataToClipboard", {
416 tableInfo
[currentSelectedTab
].instance
.selectedRowsIds().each((rowId
) => {
417 msg
.push(tableInfo
[currentSelectedTab
].instance
.rows
.get(rowId
).full_data
[(currentSelectedTab
=== "main") ? "message" : "ip"]);
420 return msg
.join("\n");
426 Object
.freeze(window
.qBittorrent
.Log
);