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
;
79 .contextMenu>li>a>img {
87 <div id=
"logFilterBar">
88 <label for=
"logLevelSelect">QBT_TR(Log Levels:)QBT_TR[CONTEXT=ExecutionLogWidget]
</label>
89 <select multiple
size=
"1" id=
"logLevelSelect" class=
"logLevelSelect" onchange=
"window.qBittorrent.Log.logLevelChanged()">
90 <option value=
"1">QBT_TR(Normal Messages)QBT_TR[CONTEXT=ExecutionLogWidget]
</option>
91 <option value=
"2">QBT_TR(Information Messages)QBT_TR[CONTEXT=ExecutionLogWidget]
</option>
92 <option value=
"4">QBT_TR(Warning Messages)QBT_TR[CONTEXT=ExecutionLogWidget]
</option>
93 <option value=
"8">QBT_TR(Critical Messages)QBT_TR[CONTEXT=ExecutionLogWidget]
</option>
96 <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">
97 <button type=
"button" title=
"Clear input" onclick=
"javascript:document.querySelector('#filterTextInput').value='';window.qBittorrent.Log.filterTextChanged();">QBT_TR(Clear)QBT_TR[CONTEXT=ExecutionLogWidget]
</button>
100 <div id=
"logFilterSummary">
101 <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>
105 <div id=
"logContentView">
106 <div id=
"logMessageView">
107 <div id=
"logMessageTableFixedHeaderDiv" class=
"dynamicTableFixedHeaderDiv">
108 <table class=
"dynamicTable unselectable" style=
"position:relative;">
110 <tr class=
"dynamicTableHeader"></tr>
114 <div id=
"logMessageTableDiv" class=
"dynamicTableDiv">
115 <table class=
"dynamicTable unselectable">
117 <tr class=
"dynamicTableHeader"></tr>
123 <div id=
"logPeerView" class=
"invisible">
124 <div id=
"logPeerTableFixedHeaderDiv" class=
"dynamicTableFixedHeaderDiv">
125 <table class=
"dynamicTable unselectable" style=
"position:relative;">
127 <tr class=
"dynamicTableHeader"></tr>
131 <div id=
"logPeerTableDiv" class=
"dynamicTableDiv">
132 <table class=
"dynamicTable unselectable">
134 <tr class=
"dynamicTableHeader"></tr>
143 <ul id=
"logTableMenu" class=
"contextMenu">
144 <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>
145 <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>
151 window
.qBittorrent
??= {};
152 window
.qBittorrent
.Log
??= (() => {
153 const exports
= () => {
158 setCurrentTab
: setCurrentTab
,
159 getFilterText
: getFilterText
,
160 getSelectedLevels
: getSelectedLevels
,
161 logLevelChanged
: logLevelChanged
,
162 filterTextChanged
: filterTextChanged
166 let currentSelectedTab
= "main";
169 instance
: new window
.qBittorrent
.DynamicTable
.LogMessageTable(),
175 instance
: new window
.qBittorrent
.DynamicTable
.LogPeerTable(),
182 let customSyncLogDataInterval
= null;
183 let logFilterTimer
= -1;
184 let inputtedFilterText
= "";
186 let selectedLogLevels
= JSON
.parse(LocalPreferences
.get("qbt_selected_log_levels")) || ["1", "2", "4", "8"];
189 $("logLevelSelect").getElements("option").each((x
) => {
190 if (selectedLogLevels
.indexOf(x
.value
.toString()) !== -1)
196 selectBox
= new vanillaSelectBox("#logLevelSelect", {
200 all
: "QBT_TR(All)QBT_TR[CONTEXT=ExecutionLogWidget]",
201 item
: "QBT_TR(item)QBT_TR[CONTEXT=ExecutionLogWidget]",
202 items
: "QBT_TR(items)QBT_TR[CONTEXT=ExecutionLogWidget]",
203 selectAll
: "QBT_TR(Select All)QBT_TR[CONTEXT=ExecutionLogWidget]",
204 clearAll
: "QBT_TR(Clear All)QBT_TR[CONTEXT=ExecutionLogWidget]",
206 placeHolder
: "QBT_TR(Choose a log level...)QBT_TR[CONTEXT=ExecutionLogWidget]",
207 keepInlineStyles
: false
210 const logTableContextMenu
= new window
.qBittorrent
.ContextMenu
.ContextMenu({
211 targets
: ".logTableRow",
212 menu
: "logTableMenu",
215 tableInfo
[currentSelectedTab
].instance
.selectedRowsIds().forEach((rowId
) => {
216 tableInfo
[currentSelectedTab
].instance
.removeRow(rowId
);
228 tableInfo
["main"].instance
.setup("logMessageTableDiv", "logMessageTableFixedHeaderDiv", logTableContextMenu
);
229 tableInfo
["peer"].instance
.setup("logPeerTableDiv", "logPeerTableFixedHeaderDiv", logTableContextMenu
);
231 MUI
.Panels
.instances
.LogPanel
.contentEl
.setStyle("height", "100%");
232 $("logView").setStyle("height", "inherit");
237 const unload
= () => {
238 for (const table
in tableInfo
) {
239 if (!Object
.hasOwn(tableInfo
, table
))
241 resetTableTimer(table
);
246 customSyncLogDataInterval
= null;
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 customSyncLogDataInterval
? customSyncLogDataInterval
: 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
.getRowIds().length
;
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: function(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: function(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().each((rowId
) => {
421 msg
.push(tableInfo
[currentSelectedTab
].instance
.rows
.get(rowId
).full_data
[(currentSelectedTab
=== "main") ? "message" : "ip"]);
424 return msg
.join("\n");
430 Object
.freeze(window
.qBittorrent
.Log
);