Display External IP Address in status bar
[qBittorrent.git] / src / webui / www / private / views / log.html
blobda5471502e9f60461b78c71ddd53f41e49d068e7
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" 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>
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 $("logLevelSelect").getElements("option").each((x) => {
192 if (selectedLogLevels.indexOf(x.value.toString()) !== -1)
193 x.selected = true;
194 else
195 x.selected = false;
198 selectBox = new vanillaSelectBox("#logLevelSelect", {
199 maxHeight: 200,
200 search: false,
201 translations: {
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",
215 actions: {
216 Clear: () => {
217 tableInfo[currentSelectedTab].instance.selectedRowsIds().forEach((rowId) => {
218 tableInfo[currentSelectedTab].instance.removeRow(rowId);
221 updateLabelCount();
224 offsets: {
225 x: 3,
226 y: -90
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%";
235 load();
238 const unload = () => {
239 for (const table in tableInfo) {
240 if (!Object.hasOwn(tableInfo, table))
241 continue;
242 resetTableTimer(table);
246 const load = () => {
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));
284 logFilterChanged();
288 const filterTextChanged = () => {
289 const value = $("filterTextInput").value.trim();
290 if (inputtedFilterText !== value) {
291 inputtedFilterText = value;
292 logFilterChanged();
296 const logFilterChanged = () => {
297 clearTimeout(logFilterTimer);
298 logFilterTimer = setTimeout((curTab) => {
299 logFilterTimer = -1;
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)
308 return;
310 currentSelectedTab = tab;
311 if (currentSelectedTab === "main") {
312 selectBox.enable();
313 $("logMessageView").removeClass("invisible");
314 $("logPeerView").addClass("invisible");
315 resetTableTimer("peer");
317 else {
318 selectBox.disable();
319 $("logMessageView").addClass("invisible");
320 $("logPeerView").removeClass("invisible");
321 resetTableTimer("main");
324 clearTimeout(logFilterTimer);
325 logFilterTimer = -1;
326 load();
328 if (tableInfo[currentSelectedTab].instance.filterText !== getFilterText())
329 tableInfo[currentSelectedTab].instance.updateTable();
331 updateLabelCount();
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;
346 let url;
347 if (curTab === "main") {
348 url = new URI("api/v2/log/main");
349 url.setData({
350 normal: selectedLogLevels.indexOf("1") !== -1,
351 info: selectedLogLevels.indexOf("2") !== -1,
352 warning: selectedLogLevels.indexOf("4") !== -1,
353 critical: selectedLogLevels.indexOf("8") !== -1
356 else {
357 url = new URI("api/v2/log/peers");
360 url.setData("last_known_id", tableInfo[curTab].last_id);
361 tableInfo[curTab].progress = true;
363 new Request.JSON({
364 url: url,
365 method: "get",
366 noCache: true,
367 onFailure: (response) => {
368 const errorDiv = $("error_div");
369 if (errorDiv)
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"))
378 return;
380 if (response.length > 0) {
381 clearTimeout(logFilterTimer);
382 logFilterTimer = -1;
384 for (let i = 0; i < response.length; ++i) {
385 let row;
386 if (curTab === "main") {
387 row = {
388 rowId: response[i].id,
389 message: response[i].message,
390 timestamp: response[i].timestamp,
391 type: response[i].type,
394 else {
395 row = {
396 rowId: response[i].id,
397 ip: response[i].ip,
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());
414 }).send();
417 new ClipboardJS(".copyLogDataToClipboard", {
418 text: () => {
419 const msg = [];
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");
428 return exports();
429 })();
430 Object.freeze(window.qBittorrent.Log);
431 </script>