428 lines
14 KiB
HTML

<style type="text/css">
#logTopBar {
margin-top: 1em;
}
#logFilterBar {
margin: .5em 0;
}
#logFilterBar>label {
font-weight: bold;
margin-right: .3em;
}
#logFilterBar>button {
display: inline-block;
padding: 4px 15px;
margin-left: .3em;
}
#logView {
padding: 0 20px;
overflow: auto;
}
#logContentView {
display: block;
vertical-align: top;
}
#logMessageTableFixedHeaderDiv .dynamicTableHeader,
#logPeerTableFixedHeaderDiv .dynamicTableHeader {
cursor: default;
}
#filterTextInput {
background-image: url("../images/edit-find.svg");
background-repeat: no-repeat;
background-position: left;
background-size: 1.5em;
padding: 4px 5px 4px 2em;
margin-left: .3em;
width: 200px;
border: 1px solid var(--color-border-default);
border-radius: 3px;
}
#logFilterSummary {
overflow: auto;
margin: 1em 0 .5em;
}
#numFilteredLogs,
#numTotalLogs {
font-style: italic;
}
.dynamicTable tbody tr.logTableRowlogNormal {
color: var(--darkmode-text);
}
.dynamicTable tbody tr.logTableRowlogInfo {
color: var(--highlight-color--blue);
}
.dynamicTable tbody tr.logTableRowlogWarning {
color: var(--highlight-color--orange);
}
.dynamicTable tbody tr.logTableRowlogCritical,
.dynamicTable tbody tr.logTableRowpeerBlocked {
color: var(--highlight-color--red);
}
.vsb-main>button {
padding: 4px 12px !important;
}
</style>
<div id="logView">
<div id="logTopBar">
<div id="logFilterBar">
<label for="logLevelSelect">Log Levels:</label>
<select multiple size="1" id="logLevelSelect" class="logLevelSelect" onchange="window.qBittorrent.Log.logLevelChanged()">
<option value="1">Normal Messages</option>
<option value="2">Information Messages</option>
<option value="4">Warning Messages</option>
<option value="8">Critical Messages</option>
</select>
<input type="text" id="filterTextInput" onkeyup="window.qBittorrent.Log.filterTextChanged()" placeholder="Filter logs" aria-label="Filter logs" autocomplete="off" autocorrect="off" autocapitalize="none">
<button type="button" title="Clear input" onclick="javascript:document.querySelector('#filterTextInput').value='';window.qBittorrent.Log.filterTextChanged();">Clear</button>
</div>
<div id="logFilterSummary">
<span>Results (showing <span id="numFilteredLogs">0</span> out of <span id="numTotalLogs">0</span>):</span>
</div>
</div>
<div id="logContentView">
<div id="logMessageView">
<div id="logMessageTableFixedHeaderDiv" class="dynamicTableFixedHeaderDiv">
<table class="dynamicTable unselectable" style="position:relative;">
<thead>
<tr class="dynamicTableHeader"></tr>
</thead>
</table>
</div>
<div id="logMessageTableDiv" class="dynamicTableDiv">
<table class="dynamicTable unselectable">
<thead>
<tr class="dynamicTableHeader"></tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
<div id="logPeerView" class="invisible">
<div id="logPeerTableFixedHeaderDiv" class="dynamicTableFixedHeaderDiv">
<table class="dynamicTable unselectable" style="position:relative;">
<thead>
<tr class="dynamicTableHeader"></tr>
</thead>
</table>
</div>
<div id="logPeerTableDiv" class="dynamicTableDiv">
<table class="dynamicTable unselectable">
<thead>
<tr class="dynamicTableHeader"></tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
</div>
</div>
<ul id="logTableMenu" class="contextMenu">
<li><a href="#" class="copyLogDataToClipboard"><img src="images/edit-copy.svg" alt="Copy">Copy</a></li>
<li><a href="#Clear"><img src="images/list-remove.svg" alt="Clear">Clear</a></li>
</ul>
<script>
"use strict";
window.qBittorrent ??= {};
window.qBittorrent.Log ??= (() => {
const exports = () => {
return {
init: init,
unload: unload,
load: load,
setCurrentTab: setCurrentTab,
getFilterText: getFilterText,
getSelectedLevels: getSelectedLevels,
logLevelChanged: logLevelChanged,
filterTextChanged: filterTextChanged
};
};
let currentSelectedTab = "main";
const tableInfo = {
main: {
instance: new window.qBittorrent.DynamicTable.LogMessageTable(),
progress: false,
timer: null,
last_id: -1
},
peer: {
instance: new window.qBittorrent.DynamicTable.LogPeerTable(),
progress: false,
timer: null,
last_id: -1
}
};
let customSyncLogDataInterval = null;
let logFilterTimer = -1;
let inputtedFilterText = "";
let selectBox;
let selectedLogLevels = JSON.parse(LocalPreferences.get("qbt_selected_log_levels")) || ["1", "2", "4", "8"];
const init = () => {
$("logLevelSelect").getElements("option").each((x) => {
if (selectedLogLevels.indexOf(x.value.toString()) !== -1)
x.selected = true;
else
x.selected = false;
});
selectBox = new vanillaSelectBox("#logLevelSelect", {
maxHeight: 200,
search: false,
translations: {
all: "All",
item: "item",
items: "items",
selectAll: "Select All",
clearAll: "Clear All",
},
placeHolder: "Choose a log level...",
keepInlineStyles: false
});
const logTableContextMenu = new window.qBittorrent.ContextMenu.ContextMenu({
targets: ".logTableRow",
menu: "logTableMenu",
actions: {
Clear: () => {
tableInfo[currentSelectedTab].instance.selectedRowsIds().forEach((rowId) => {
tableInfo[currentSelectedTab].instance.removeRow(rowId);
});
updateLabelCount();
}
},
offsets: {
x: -16,
y: -57
}
});
tableInfo["main"].instance.setup("logMessageTableDiv", "logMessageTableFixedHeaderDiv", logTableContextMenu);
tableInfo["peer"].instance.setup("logPeerTableDiv", "logPeerTableFixedHeaderDiv", logTableContextMenu);
MUI.Panels.instances.LogPanel.contentEl.style.height = "100%";
$("logView").style.height = "inherit";
load();
};
const unload = () => {
for (const table in tableInfo) {
if (!Object.hasOwn(tableInfo, table))
continue;
resetTableTimer(table);
}
};
const load = () => {
customSyncLogDataInterval = null;
syncLogWithInterval(100);
};
const resetTableTimer = (curTab) => {
if (curTab === undefined)
curTab = currentSelectedTab;
clearTimeout(tableInfo[curTab].timer);
tableInfo[curTab].timer = null;
};
const syncLogWithInterval = (interval) => {
if (!tableInfo[currentSelectedTab].progress) {
clearTimeout(tableInfo[currentSelectedTab].timer);
tableInfo[currentSelectedTab].timer = syncLogData.delay(interval, null, currentSelectedTab);
}
};
const getFilterText = () => {
return inputtedFilterText;
};
const getSelectedLevels = () => {
return selectedLogLevels;
};
const getSyncLogDataInterval = () => {
return customSyncLogDataInterval ? customSyncLogDataInterval : serverSyncMainDataInterval;
};
const logLevelChanged = () => {
const value = selectBox.getResult().sort();
if (selectedLogLevels !== value) {
tableInfo[currentSelectedTab].last_id = -1;
selectedLogLevels = value;
LocalPreferences.set("qbt_selected_log_levels", JSON.stringify(selectedLogLevels));
logFilterChanged();
}
};
const filterTextChanged = () => {
const value = $("filterTextInput").value.trim();
if (inputtedFilterText !== value) {
inputtedFilterText = value;
logFilterChanged();
}
};
const logFilterChanged = () => {
clearTimeout(logFilterTimer);
logFilterTimer = setTimeout((curTab) => {
logFilterTimer = -1;
tableInfo[curTab].instance.updateTable(false);
updateLabelCount(curTab);
}, window.qBittorrent.Misc.FILTER_INPUT_DELAY, currentSelectedTab);
};
const setCurrentTab = (tab) => {
if (tab === currentSelectedTab)
return;
currentSelectedTab = tab;
if (currentSelectedTab === "main") {
selectBox.enable();
$("logMessageView").removeClass("invisible");
$("logPeerView").addClass("invisible");
resetTableTimer("peer");
}
else {
selectBox.disable();
$("logMessageView").addClass("invisible");
$("logPeerView").removeClass("invisible");
resetTableTimer("main");
}
clearTimeout(logFilterTimer);
logFilterTimer = -1;
load();
if (tableInfo[currentSelectedTab].instance.filterText !== getFilterText())
tableInfo[currentSelectedTab].instance.updateTable();
updateLabelCount();
};
const updateLabelCount = (curTab) => {
if (curTab === undefined)
curTab = currentSelectedTab;
$("numFilteredLogs").textContent = tableInfo[curTab].instance.filteredLength();
$("numTotalLogs").textContent = tableInfo[curTab].instance.getRowSize();
};
const syncLogData = (curTab) => {
if (curTab === undefined)
curTab = currentSelectedTab;
let url;
if (curTab === "main") {
url = new URI("api/v2/log/main");
url.setData({
normal: selectedLogLevels.indexOf("1") !== -1,
info: selectedLogLevels.indexOf("2") !== -1,
warning: selectedLogLevels.indexOf("4") !== -1,
critical: selectedLogLevels.indexOf("8") !== -1
});
}
else {
url = new URI("api/v2/log/peers");
}
url.setData("last_known_id", tableInfo[curTab].last_id);
tableInfo[curTab].progress = true;
new Request.JSON({
url: url,
method: "get",
noCache: true,
onFailure: function(response) {
const errorDiv = $("error_div");
if (errorDiv)
errorDiv.textContent = "qBittorrent client is not reachable";
tableInfo[curTab].progress = false;
syncLogWithInterval(10000);
},
onSuccess: function(response) {
$("error_div").textContent = "";
if ($("logTabColumn").hasClass("invisible"))
return;
if (response.length > 0) {
clearTimeout(logFilterTimer);
logFilterTimer = -1;
for (let i = 0; i < response.length; ++i) {
let row;
if (curTab === "main") {
row = {
rowId: response[i].id,
message: response[i].message,
timestamp: response[i].timestamp,
type: response[i].type,
};
}
else {
row = {
rowId: response[i].id,
ip: response[i].ip,
timestamp: response[i].timestamp,
blocked: response[i].blocked,
reason: response[i].reason,
};
}
tableInfo[curTab].instance.updateRowData(row);
tableInfo[curTab].last_id = Math.max(response[i].id.toInt(), tableInfo[curTab].last_id);
}
tableInfo[curTab].instance.updateTable();
updateLabelCount(curTab);
}
tableInfo[curTab].progress = false;
syncLogWithInterval(getSyncLogDataInterval());
}
}).send();
};
new ClipboardJS(".copyLogDataToClipboard", {
text: function() {
const msg = [];
tableInfo[currentSelectedTab].instance.selectedRowsIds().forEach((rowId) => {
msg.push(tableInfo[currentSelectedTab].instance.getRow(rowId).full_data[(currentSelectedTab === "main") ? "message" : "ip"]);
});
return msg.join("\n");
}
});
return exports();
})();
Object.freeze(window.qBittorrent.Log);
</script>