WebUI: use proper event for handling text changes
[qBittorrent.git] / src / webui / www / private / views / log.html
blob6590f0ef5d9440b9f39a65e4b4048113ca8a5eb2
1 <style type="text/css">
2 #logTopBar {
3 flex-shrink: 0;
4 margin-top: 1em;
7 #logFilterBar {
8 align-items: center;
9 display: flex;
10 flex-wrap: wrap;
11 gap: 2px;
12 height: 24px;
15 #logFilterBar>label {
16 font-weight: bold;
17 margin-right: .3em;
20 #logFilterBar>button {
21 display: inline-block;
22 padding: 2px 12px;
25 #logView {
26 display: flex;
27 flex-direction: column;
28 height: 100%;
29 padding: 0 20px;
30 overflow: auto;
33 #logContentView {
34 flex-grow: 1;
35 overflow: auto;
38 #logMessageTableFixedHeaderDiv .dynamicTableHeader,
39 #logPeerTableFixedHeaderDiv .dynamicTableHeader {
40 cursor: default;
43 #filterTextInput {
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;
49 margin-left: .3em;
50 width: 237px;
51 border: 1px solid var(--color-border-default);
52 border-radius: 3px;
55 #logFilterSummary {
56 overflow: auto;
57 margin: 1em 0 .5em;
60 #numFilteredLogs,
61 #numTotalLogs {
62 font-style: italic;
65 .logTableRowlogNormal {
66 color: var(--color-text-default);
69 .logTableRowlogInfo {
70 color: var(--color-text-blue);
73 .logTableRowlogWarning {
74 color: var(--color-text-orange);
77 .logTableRowlogCritical,
78 .logTableRowpeerBlocked {
79 color: var(--color-text-red);
82 .vsb-main>button {
83 padding: 2px 12px !important;
86 </style>
88 <div id="logView">
89 <div id="logTopBar">
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>
97 </select>
99 <input type="text" 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.querySelector('#filterTextInput').value='';window.qBittorrent.Log.filterTextChanged();">QBT_TR(Clear)QBT_TR[CONTEXT=ExecutionLogWidget]</button>
101 </div>
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>
105 </div>
106 </div>
108 <div id="logContentView">
109 <div id="logMessageView">
110 <div id="logMessageTableFixedHeaderDiv" class="dynamicTableFixedHeaderDiv">
111 <table class="dynamicTable unselectable" style="position:relative;">
112 <thead>
113 <tr class="dynamicTableHeader"></tr>
114 </thead>
115 </table>
116 </div>
117 <div id="logMessageTableDiv" class="dynamicTableDiv">
118 <table class="dynamicTable unselectable">
119 <thead>
120 <tr class="dynamicTableHeader"></tr>
121 </thead>
122 <tbody></tbody>
123 </table>
124 </div>
125 </div>
126 <div id="logPeerView" class="invisible">
127 <div id="logPeerTableFixedHeaderDiv" class="dynamicTableFixedHeaderDiv">
128 <table class="dynamicTable unselectable" style="position:relative;">
129 <thead>
130 <tr class="dynamicTableHeader"></tr>
131 </thead>
132 </table>
133 </div>
134 <div id="logPeerTableDiv" class="dynamicTableDiv">
135 <table class="dynamicTable unselectable">
136 <thead>
137 <tr class="dynamicTableHeader"></tr>
138 </thead>
139 <tbody></tbody>
140 </table>
141 </div>
142 </div>
143 </div>
144 </div>
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>
149 </ul>
151 <script>
152 "use strict";
154 window.qBittorrent ??= {};
155 window.qBittorrent.Log ??= (() => {
156 const exports = () => {
157 return {
158 init: init,
159 unload: unload,
160 load: load,
161 setCurrentTab: setCurrentTab,
162 getFilterText: getFilterText,
163 getSelectedLevels: getSelectedLevels,
164 logLevelChanged: logLevelChanged,
165 filterTextChanged: filterTextChanged
169 let currentSelectedTab = "main";
170 const tableInfo = {
171 main: {
172 instance: new window.qBittorrent.DynamicTable.LogMessageTable(),
173 progress: false,
174 timer: null,
175 last_id: -1
177 peer: {
178 instance: new window.qBittorrent.DynamicTable.LogPeerTable(),
179 progress: false,
180 timer: null,
181 last_id: -1
185 let logFilterTimer = -1;
186 let inputtedFilterText = "";
187 let selectBox;
188 let selectedLogLevels = JSON.parse(LocalPreferences.get("qbt_selected_log_levels")) || ["1", "2", "4", "8"];
190 const init = () => {
191 for (const option of $("logLevelSelect").options)
192 option.setAttribute("selected", selectedLogLevels.includes(option.value));
194 selectBox = new vanillaSelectBox("#logLevelSelect", {
195 maxHeight: 200,
196 search: false,
197 translations: {
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",
211 actions: {
212 Clear: () => {
213 tableInfo[currentSelectedTab].instance.selectedRowsIds().forEach((rowId) => {
214 tableInfo[currentSelectedTab].instance.removeRow(rowId);
217 updateLabelCount();
220 offsets: {
221 x: 3,
222 y: -90
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%";
231 load();
234 const unload = () => {
235 for (const table in tableInfo) {
236 if (!Object.hasOwn(tableInfo, table))
237 continue;
238 resetTableTimer(table);
242 const load = () => {
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));
280 logFilterChanged();
284 const filterTextChanged = () => {
285 const value = $("filterTextInput").value.trim();
286 if (inputtedFilterText !== value) {
287 inputtedFilterText = value;
288 logFilterChanged();
292 const logFilterChanged = () => {
293 clearTimeout(logFilterTimer);
294 logFilterTimer = setTimeout((curTab) => {
295 logFilterTimer = -1;
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)
304 return;
306 currentSelectedTab = tab;
307 if (currentSelectedTab === "main") {
308 selectBox.enable();
309 $("logMessageView").removeClass("invisible");
310 $("logPeerView").addClass("invisible");
311 resetTableTimer("peer");
313 else {
314 selectBox.disable();
315 $("logMessageView").addClass("invisible");
316 $("logPeerView").removeClass("invisible");
317 resetTableTimer("main");
320 clearTimeout(logFilterTimer);
321 logFilterTimer = -1;
322 load();
324 if (tableInfo[currentSelectedTab].instance.filterText !== getFilterText())
325 tableInfo[currentSelectedTab].instance.updateTable();
327 updateLabelCount();
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;
342 let url;
343 if (curTab === "main") {
344 url = new URI("api/v2/log/main");
345 url.setData({
346 normal: selectedLogLevels.includes("1"),
347 info: selectedLogLevels.includes("2"),
348 warning: selectedLogLevels.includes("4"),
349 critical: selectedLogLevels.includes("8")
352 else {
353 url = new URI("api/v2/log/peers");
356 url.setData("last_known_id", tableInfo[curTab].last_id);
357 tableInfo[curTab].progress = true;
359 new Request.JSON({
360 url: url,
361 method: "get",
362 noCache: true,
363 onFailure: (response) => {
364 const errorDiv = $("error_div");
365 if (errorDiv)
366 errorDiv.textContent = "QBT_TR(qBittorrent client is not reachable)QBT_TR[CONTEXT=HttpServer]";
367 tableInfo[curTab].progress = false;
368 syncLogWithInterval(10000);
370 onSuccess: (response) => {
371 $("error_div").textContent = "";
373 if ($("logTabColumn").hasClass("invisible"))
374 return;
376 if (response.length > 0) {
377 clearTimeout(logFilterTimer);
378 logFilterTimer = -1;
380 for (let i = 0; i < response.length; ++i) {
381 let row;
382 if (curTab === "main") {
383 row = {
384 rowId: response[i].id,
385 message: response[i].message,
386 timestamp: response[i].timestamp,
387 type: response[i].type,
390 else {
391 row = {
392 rowId: response[i].id,
393 ip: response[i].ip,
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(Number(response[i].id), tableInfo[curTab].last_id);
403 tableInfo[curTab].instance.updateTable();
404 updateLabelCount(curTab);
407 tableInfo[curTab].progress = false;
408 syncLogWithInterval(getSyncLogDataInterval());
410 }).send();
413 new ClipboardJS(".copyLogDataToClipboard", {
414 text: () => {
415 const msg = [];
416 tableInfo[currentSelectedTab].instance.selectedRowsIds().forEach((rowId) => {
417 msg.push(tableInfo[currentSelectedTab].instance.getRow(rowId).full_data[(currentSelectedTab === "main") ? "message" : "ip"]);
420 return msg.join("\n");
424 return exports();
425 })();
426 Object.freeze(window.qBittorrent.Log);
427 </script>