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: 2px;
47 background-size: 1.5em;
48 padding: 2px 2px 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=
"search" id=
"filterTextInput" oninput=
"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.getElementById('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 for (const option
of $("logLevelSelect").options
)
192 option
.toggleAttribute("selected", selectedLogLevels
.includes(option
.value
));
194 selectBox
= new vanillaSelectBox("#logLevelSelect", {
198 all
: "QBT_TR(All)QBT_TR[CONTEXT=ExecutionLogWidget]",
199 item
: "QBT_TR(item)QBT_TR[CONTEXT=ExecutionLogWidget]",
200 items
: "QBT_TR(items)QBT_TR[CONTEXT=ExecutionLogWidget]",
201 selectAll
: "QBT_TR(Select All)QBT_TR[CONTEXT=ExecutionLogWidget]",
202 clearAll
: "QBT_TR(Clear All)QBT_TR[CONTEXT=ExecutionLogWidget]",
204 placeHolder
: "QBT_TR(Choose a log level...)QBT_TR[CONTEXT=ExecutionLogWidget]",
205 keepInlineStyles
: false
208 const logTableContextMenu
= new window
.qBittorrent
.ContextMenu
.ContextMenu({
209 targets
: ":is(#logMessageView, #logPeerView) tr",
210 menu
: "logTableMenu",
213 tableInfo
[currentSelectedTab
].instance
.selectedRowsIds().forEach((rowId
) => {
214 tableInfo
[currentSelectedTab
].instance
.removeRow(rowId
);
226 tableInfo
["main"].instance
.setup("logMessageTableDiv", "logMessageTableFixedHeaderDiv", logTableContextMenu
);
227 tableInfo
["peer"].instance
.setup("logPeerTableDiv", "logPeerTableFixedHeaderDiv", logTableContextMenu
);
229 MUI
.Panels
.instances
.LogPanel
.contentEl
.style
.height
= "100%";
234 const unload
= () => {
235 for (const table
in tableInfo
) {
236 if (!Object
.hasOwn(tableInfo
, table
))
238 resetTableTimer(table
);
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 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").classList
.remove("invisible");
310 $("logPeerView").classList
.add("invisible");
311 resetTableTimer("peer");
315 $("logMessageView").classList
.add("invisible");
316 $("logPeerView").classList
.remove("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
.getRowSize();
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
.includes("1"),
347 info
: selectedLogLevels
.includes("2"),
348 warning
: selectedLogLevels
.includes("4"),
349 critical
: selectedLogLevels
.includes("8")
353 url
= new URI("api/v2/log/peers");
356 url
.setData("last_known_id", tableInfo
[curTab
].last_id
);
357 tableInfo
[curTab
].progress
= true;
363 .then(async (response
) => {
365 const errorDiv
= $("error_div");
367 errorDiv
.textContent
= "QBT_TR(qBittorrent client is not reachable)QBT_TR[CONTEXT=HttpServer]";
368 tableInfo
[curTab
].progress
= false;
369 syncLogWithInterval(10000);
373 $("error_div").textContent
= "";
375 if ($("logTabColumn").classList
.contains("invisible"))
378 const responseJSON
= await response
.json();
379 if (responseJSON
.length
> 0) {
380 clearTimeout(logFilterTimer
);
383 for (let i
= 0; i
< responseJSON
.length
; ++i
) {
385 if (curTab
=== "main") {
387 rowId
: responseJSON
[i
].id
,
388 message
: responseJSON
[i
].message
,
389 timestamp
: responseJSON
[i
].timestamp
,
390 type
: responseJSON
[i
].type
,
395 rowId
: responseJSON
[i
].id
,
396 ip
: responseJSON
[i
].ip
,
397 timestamp
: responseJSON
[i
].timestamp
,
398 blocked
: responseJSON
[i
].blocked
,
399 reason
: responseJSON
[i
].reason
,
402 tableInfo
[curTab
].instance
.updateRowData(row
);
403 tableInfo
[curTab
].last_id
= Math
.max(Number(responseJSON
[i
].id
), tableInfo
[curTab
].last_id
);
406 tableInfo
[curTab
].instance
.updateTable();
407 updateLabelCount(curTab
);
410 tableInfo
[curTab
].progress
= false;
411 syncLogWithInterval(getSyncLogDataInterval());
415 new ClipboardJS(".copyLogDataToClipboard", {
418 tableInfo
[currentSelectedTab
].instance
.selectedRowsIds().forEach((rowId
) => {
419 msg
.push(tableInfo
[currentSelectedTab
].instance
.getRow(rowId
).full_data
[(currentSelectedTab
=== "main") ? "message" : "ip"]);
422 return msg
.join("\n");
428 Object
.freeze(window
.qBittorrent
.Log
);