mirror of
https://github.com/qbittorrent/qBittorrent.git
synced 2025-01-08 11:47:30 +08:00
WebUI: Add log viewer
The javascript implementation of multi-select menu is from the source https://github.com/PhilippeMarcMeyer/vanillaSelectBox. It is licensed under the MIT License. Some minor fixes is made to pass the lint. Co-authored-by: brvphoenix <30111323+brvphoenix@users.noreply.github.com> Co-authored-by: ttyS3 <ttys3.rust@gmail.com> PR #18290.
This commit is contained in:
parent
2b20d5b260
commit
0d376e7fd6
@ -34,6 +34,7 @@ repos:
|
||||
exclude: |
|
||||
(?x)^(
|
||||
compile_commands.json |
|
||||
src/webui/www/private/css/lib/.* |
|
||||
src/webui/www/private/scripts/lib/.*
|
||||
)$
|
||||
|
||||
@ -43,6 +44,7 @@ repos:
|
||||
(?x)^(
|
||||
compile_commands.json |
|
||||
configure |
|
||||
src/webui/www/private/css/lib/.* |
|
||||
src/webui/www/private/scripts/lib/.*
|
||||
)$
|
||||
exclude_types:
|
||||
@ -53,6 +55,7 @@ repos:
|
||||
name: Check trailing whitespaces
|
||||
exclude: |
|
||||
(?x)^(
|
||||
src/webui/www/private/css/lib/.* |
|
||||
src/webui/www/private/scripts/lib/.*
|
||||
)$
|
||||
exclude_types:
|
||||
|
4
AUTHORS
4
AUTHORS
@ -29,6 +29,10 @@ Code from other projects:
|
||||
copyright: Dan Haim <negativeiq@users.sourceforge.net>
|
||||
license: BSD
|
||||
|
||||
* files src/webui/www/private/css/lib/vanillaSelectBox.css src/webui/www/private/scripts/lib/vanillaSelectBox.js
|
||||
copyright: Philippe Meyer <pmg.meyer@gmail.com>
|
||||
license: MIT
|
||||
|
||||
Images Authors:
|
||||
* files: src/icons/qbittorrent-tray.svg
|
||||
copyright: Provided by HVS <hvs linuxmail org> (raster first proposal) and Atif Afzal(@atfzl github) <atif5801@gmail.com> (vectorized and modified)
|
||||
|
1
src/webui/www/.prettierignore
Normal file
1
src/webui/www/.prettierignore
Normal file
@ -0,0 +1 @@
|
||||
private/css/lib/*.css
|
@ -3,6 +3,7 @@
|
||||
"plugins": [
|
||||
"stylelint-order"
|
||||
],
|
||||
"ignoreFiles": ["private/css/lib/*.css"],
|
||||
"rules": {
|
||||
"color-hex-length": null,
|
||||
"comment-empty-line-before": null,
|
||||
|
271
src/webui/www/private/css/lib/vanillaSelectBox.css
Normal file
271
src/webui/www/private/css/lib/vanillaSelectBox.css
Normal file
@ -0,0 +1,271 @@
|
||||
.hidden-search {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
|
||||
li[data-parent].closed{
|
||||
display:none !important;
|
||||
}
|
||||
|
||||
li[data-parent].open:not(.hidden-search){
|
||||
display:block !important;
|
||||
}
|
||||
|
||||
.vsb-menu{
|
||||
cursor:pointer;
|
||||
z-index:1000;
|
||||
display:block;
|
||||
visibility: hidden;
|
||||
position:absolute;/*Don't change*/
|
||||
border:1px solid #B2B2B2;
|
||||
background-color: #fff;
|
||||
background-clip: padding-box;
|
||||
border: 1px solid rgba(0,0,0,.15);
|
||||
box-shadow: 0 6px 12px rgba(0,0,0,.175);
|
||||
border-radius:4px;
|
||||
font-size : 11px;
|
||||
}
|
||||
|
||||
.vsb-js-search-zone{
|
||||
position:absolute;/*Don't change*/
|
||||
z-index:1001;
|
||||
width: 80%;
|
||||
min-height:1.8em;
|
||||
padding: 2px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.vsb-js-search-zone input{
|
||||
border: 1px solid grey;
|
||||
margin-left: 2px;
|
||||
width: 96%;
|
||||
border-radius: 4px;
|
||||
height: 25px !important;
|
||||
}
|
||||
|
||||
.vsb-main{
|
||||
position: relative;/*Don't change*/
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
text-align:left;
|
||||
}
|
||||
|
||||
.vsb-menu li:hover {
|
||||
background: linear-gradient(#f5f5f5, #e8e8e8);
|
||||
}
|
||||
|
||||
.vsb-menu ul{
|
||||
user-select:none;
|
||||
list-style:none;
|
||||
white-space: nowrap;
|
||||
margin:0px;
|
||||
margin-top:4px;
|
||||
padding-left:10px;
|
||||
padding-right:10px;
|
||||
padding-bottom:3px;
|
||||
color: #333;
|
||||
cursor:pointer;
|
||||
overflow-y:auto;
|
||||
}
|
||||
|
||||
li.disabled{
|
||||
cursor:not-allowed;
|
||||
opacity:0.3;
|
||||
background-color: #999;
|
||||
}
|
||||
|
||||
li.overflow{
|
||||
cursor:not-allowed;
|
||||
opacity:0.3;
|
||||
background-color: #999;
|
||||
}
|
||||
|
||||
li.short{
|
||||
overflow:hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.vsb-main button{
|
||||
min-width: 120px;
|
||||
border-radius: 0;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
z-index: 1;
|
||||
color: #333;
|
||||
background: white !important;
|
||||
border: 1px solid #999 !important;
|
||||
line-height:20px;
|
||||
font-size:14px;
|
||||
padding:6px 12px;
|
||||
}
|
||||
|
||||
.vsb-main button.disabled{
|
||||
cursor:not-allowed;
|
||||
opacity:0.65;
|
||||
}
|
||||
|
||||
.vsb-main .title {
|
||||
margin-right: 6px;
|
||||
user-select:none;
|
||||
}
|
||||
|
||||
.vsb-main li:hover {
|
||||
background: linear-gradient(#f5f5f5, #e8e8e8);
|
||||
}
|
||||
|
||||
.vsb-main ul{
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.vsb-menu li {
|
||||
font-size: 14px;
|
||||
background-color: #fff;
|
||||
min-height:1.4em;
|
||||
padding: 0.2em 2em 0.2em 1em;
|
||||
}
|
||||
|
||||
.vsb-menu li.grouped-option b {
|
||||
display: inline-block;
|
||||
font-size: 15px;
|
||||
margin-left:10px;
|
||||
transform: translate(-18px);
|
||||
}
|
||||
|
||||
.vsb-menu li.grouped-option.open span {
|
||||
display: inline-block;
|
||||
font-size: inherit;
|
||||
margin-top:-2px;
|
||||
height: 8px;
|
||||
width: 8px;
|
||||
transform: translate(-38px) rotate(45deg);
|
||||
border-bottom: 3px solid black;
|
||||
border-right: 3px solid black;
|
||||
border-radius:2px;
|
||||
}
|
||||
|
||||
.vsb-menu li.grouped-option.closed span {
|
||||
display: inline-block;
|
||||
font-size: inherit;
|
||||
height: 8px;
|
||||
width: 8px;
|
||||
transform: translate(-38px) rotate(-45deg);
|
||||
border-bottom: 3px solid black;
|
||||
border-right: 3px solid black;
|
||||
border-radius:2px;
|
||||
}
|
||||
|
||||
.vsb-menu li.grouped-option i {
|
||||
display: inline-block;
|
||||
font-size: inherit;
|
||||
float:left;
|
||||
font-weight:bold;
|
||||
margin-left:22px;
|
||||
margin-right:2px;
|
||||
height: 11px;
|
||||
width: 8px;
|
||||
border : 1px solid;
|
||||
border-radius : 3px;
|
||||
padding: 1px 3px 2px 3px;
|
||||
margin-top:0px;
|
||||
color:black;
|
||||
}
|
||||
|
||||
.vsb-menu li.grouped-option.checked i::after {
|
||||
content: "";
|
||||
display: inline-block;
|
||||
font-size: inherit;
|
||||
color: #333;
|
||||
float:left;
|
||||
margin-left:0px;
|
||||
display: inline-block;
|
||||
transform: rotate(45deg);
|
||||
height: 8px;
|
||||
width: 5px;
|
||||
border-bottom: 3px solid black;
|
||||
border-right: 3px solid black;
|
||||
}
|
||||
|
||||
.vsb-menu :not(.multi) li.active {
|
||||
margin-left:7px;
|
||||
}
|
||||
|
||||
.vsb-menu :not(.multi) li.active::before {
|
||||
content: "";
|
||||
display: inline-block;
|
||||
font-size: inherit;
|
||||
margin-left:-18px;
|
||||
transform: rotate(45deg);
|
||||
height: 10px;
|
||||
width: 5px;
|
||||
border-bottom: 3px solid black;
|
||||
border-right: 3px solid black;
|
||||
border-radius:2px;
|
||||
}
|
||||
|
||||
.vsb-menu .multi li {
|
||||
font-size: 14px;
|
||||
background-color: #fff;
|
||||
min-height:1.4em;
|
||||
padding: 0.2em 2em 0.2em 26px;
|
||||
}
|
||||
|
||||
.vsb-menu .multi li.grouped-option {
|
||||
font-size: 15px;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
|
||||
.vsb-menu .multi li.grouped-option:hover {
|
||||
font-weight: bold;
|
||||
text-decoration: underline;
|
||||
color:rgb(52, 31, 112);
|
||||
}
|
||||
|
||||
.vsb-menu .multi li:not(.grouped-option)::before{
|
||||
content: "";
|
||||
display: inline-block;
|
||||
font-size: inherit;
|
||||
float:left;
|
||||
font-weight:bold;
|
||||
margin-left:-22px;
|
||||
margin-right:2px;
|
||||
border : 1px solid;
|
||||
border-radius : 3px;
|
||||
padding : 7px;
|
||||
margin-top:0px;
|
||||
color:black;
|
||||
}
|
||||
|
||||
.vsb-menu .multi li:not(.grouped-option).active::after {
|
||||
content: "";
|
||||
display: inline-block;
|
||||
font-size: inherit;
|
||||
color: #333;
|
||||
float:left;
|
||||
margin-left:-18px;
|
||||
display: inline-block;
|
||||
transform: rotate(45deg);
|
||||
margin-top:1px;
|
||||
height: 8px;
|
||||
width: 5px;
|
||||
border-bottom: 3px solid black;
|
||||
border-right: 3px solid black;
|
||||
}
|
||||
|
||||
.caret {
|
||||
display: inline-block;
|
||||
width: 0;
|
||||
height: 0;
|
||||
margin-left: 2px;
|
||||
vertical-align: middle;
|
||||
border-top: 4px dashed;
|
||||
border-top: 4px solid;
|
||||
border-right: 4px solid transparent;
|
||||
border-left: 4px solid transparent;
|
||||
}
|
||||
|
||||
|
||||
li[data-parent]{
|
||||
padding-left: 50px !important;
|
||||
}
|
||||
|
@ -77,6 +77,7 @@
|
||||
<li><a id="speedInBrowserTitleBarLink"><img class="MyMenuIcon" src="images/checked-completed.svg" alt="QBT_TR(Speed in Title Bar)QBT_TR[CONTEXT=MainWindow]" width="16" height="16" />QBT_TR(Speed in Title Bar)QBT_TR[CONTEXT=MainWindow]</a></li>
|
||||
<li class="divider"><a id="showSearchEngineLink"><img class="MyMenuIcon" src="images/checked-completed.svg" alt="QBT_TR(Search Engine)QBT_TR[CONTEXT=MainWindow]" width="16" height="16" />QBT_TR(Search Engine)QBT_TR[CONTEXT=MainWindow]</a></li>
|
||||
<li><a id="showRssReaderLink"><img class="MyMenuIcon" src="images/checked-completed.svg" alt="QBT_TR(RSS)QBT_TR[CONTEXT=MainWindow]" width="16" height="16" />QBT_TR(RSS Reader)QBT_TR[CONTEXT=MainWindow]</a></li>
|
||||
<li><a id="showLogViewerLink"><img class="MyMenuIcon" src="images/checked-completed.svg" alt="QBT_TR(Log)QBT_TR[CONTEXT=MainWindow]" width="16" height="16" />QBT_TR(Log)QBT_TR[CONTEXT=MainWindow]</a></li>
|
||||
<li class="divider"><a id="StatisticsLink"><img class="MyMenuIcon" src="images/view-statistics.svg" alt="QBT_TR(Statistics)QBT_TR[CONTEXT=MainWindow]" width="16" height="16" />QBT_TR(Statistics)QBT_TR[CONTEXT=MainWindow]</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
@ -116,6 +117,7 @@
|
||||
<li id="transfersTabLink" class="selected"><a class="tab">QBT_TR(Transfers)QBT_TR[CONTEXT=MainWindow]</a></li>
|
||||
<li id="searchTabLink"><a class="tab">QBT_TR(Search)QBT_TR[CONTEXT=MainWindow]</a></li>
|
||||
<li id="rssTabLink"><a class="tab">QBT_TR(RSS)QBT_TR[CONTEXT=MainWindow]</a></li>
|
||||
<li id="logTabLink"><a class="tab">QBT_TR(Execution Log)QBT_TR[CONTEXT=MainWindow]</a></li>
|
||||
</ul>
|
||||
<div class="clear"></div>
|
||||
</div>
|
||||
|
@ -35,6 +35,7 @@ let serverSyncMainDataInterval = 1500;
|
||||
let customSyncMainDataInterval = null;
|
||||
let searchTabInitialized = false;
|
||||
let rssTabInitialized = false;
|
||||
let logTabInitialized = false;
|
||||
|
||||
let syncRequestInProgress = false;
|
||||
|
||||
@ -190,9 +191,21 @@ window.addEvent('load', function() {
|
||||
$("rssTabColumn").addClass("invisible");
|
||||
};
|
||||
|
||||
const buildLogTab = function() {
|
||||
new MochaUI.Column({
|
||||
id: 'logTabColumn',
|
||||
placement: 'main',
|
||||
width: null
|
||||
});
|
||||
|
||||
// start off hidden
|
||||
$('logTabColumn').addClass('invisible');
|
||||
};
|
||||
|
||||
buildTransfersTab();
|
||||
buildSearchTab();
|
||||
buildRssTab();
|
||||
buildLogTab();
|
||||
MochaUI.initializeTabs('mainWindowTabsList');
|
||||
|
||||
setCategoryFilter = function(hash) {
|
||||
@ -304,6 +317,7 @@ window.addEvent('load', function() {
|
||||
// After showing/hiding the toolbar + status bar
|
||||
let showSearchEngine = LocalPreferences.get('show_search_engine') !== "false";
|
||||
let showRssReader = LocalPreferences.get('show_rss_reader') !== "false";
|
||||
let showLogViewer = LocalPreferences.get('show_log_viewer') === 'true';
|
||||
|
||||
// After Show Top Toolbar
|
||||
MochaUI.Desktop.setDesktopSize();
|
||||
@ -912,6 +926,12 @@ window.addEvent('load', function() {
|
||||
updateTabDisplay();
|
||||
});
|
||||
|
||||
$('showLogViewerLink').addEvent('click', function(e) {
|
||||
showLogViewer = !showLogViewer;
|
||||
LocalPreferences.set('show_log_viewer', showLogViewer.toString());
|
||||
updateTabDisplay();
|
||||
});
|
||||
|
||||
const updateTabDisplay = function() {
|
||||
if (showRssReader) {
|
||||
$('showRssReaderLink').firstChild.style.opacity = '1';
|
||||
@ -941,8 +961,22 @@ window.addEvent('load', function() {
|
||||
$("transfersTabLink").click();
|
||||
}
|
||||
|
||||
if (showLogViewer) {
|
||||
$('showLogViewerLink').firstChild.style.opacity = '1';
|
||||
$('mainWindowTabs').removeClass('invisible');
|
||||
$('logTabLink').removeClass('invisible');
|
||||
if (!MochaUI.Panels.instances.LogPanel)
|
||||
addLogPanel();
|
||||
}
|
||||
else {
|
||||
$('showLogViewerLink').firstChild.style.opacity = '0';
|
||||
$('logTabLink').addClass('invisible');
|
||||
if ($('logTabLink').hasClass('selected'))
|
||||
$("transfersTabLink").click();
|
||||
}
|
||||
|
||||
// display no tabs
|
||||
if (!showRssReader && !showSearchEngine)
|
||||
if (!showRssReader && !showSearchEngine && !showLogViewer)
|
||||
$('mainWindowTabs').addClass('invisible');
|
||||
};
|
||||
|
||||
@ -954,18 +988,21 @@ window.addEvent('load', function() {
|
||||
$("filtersColumn").removeClass("invisible");
|
||||
$("filtersColumn_handle").removeClass("invisible");
|
||||
$("mainColumn").removeClass("invisible");
|
||||
$('torrentsFilterToolbar').removeClass("invisible");
|
||||
|
||||
customSyncMainDataInterval = null;
|
||||
syncData(100);
|
||||
|
||||
hideSearchTab();
|
||||
hideRssTab();
|
||||
hideLogTab();
|
||||
};
|
||||
|
||||
const hideTransfersTab = function() {
|
||||
$("filtersColumn").addClass("invisible");
|
||||
$("filtersColumn_handle").addClass("invisible");
|
||||
$("mainColumn").addClass("invisible");
|
||||
$('torrentsFilterToolbar').addClass("invisible");
|
||||
MochaUI.Desktop.resizePanels();
|
||||
};
|
||||
|
||||
@ -979,6 +1016,7 @@ window.addEvent('load', function() {
|
||||
customSyncMainDataInterval = 30000;
|
||||
hideTransfersTab();
|
||||
hideRssTab();
|
||||
hideLogTab();
|
||||
};
|
||||
|
||||
const hideSearchTab = function() {
|
||||
@ -999,14 +1037,37 @@ window.addEvent('load', function() {
|
||||
customSyncMainDataInterval = 30000;
|
||||
hideTransfersTab();
|
||||
hideSearchTab();
|
||||
hideLogTab();
|
||||
};
|
||||
|
||||
const hideRssTab = function() {
|
||||
$("rssTabColumn").addClass("invisible");
|
||||
window.qBittorrent.Rss.unload();
|
||||
window.qBittorrent.Rss && window.qBittorrent.Rss.unload();
|
||||
MochaUI.Desktop.resizePanels();
|
||||
};
|
||||
|
||||
const showLogTab = function() {
|
||||
if (!logTabInitialized) {
|
||||
window.qBittorrent.Log.init();
|
||||
logTabInitialized = true;
|
||||
}
|
||||
else {
|
||||
window.qBittorrent.Log.load();
|
||||
}
|
||||
|
||||
$('logTabColumn').removeClass('invisible');
|
||||
customSyncMainDataInterval = 30000;
|
||||
hideTransfersTab();
|
||||
hideSearchTab();
|
||||
hideRssTab();
|
||||
};
|
||||
|
||||
const hideLogTab = function() {
|
||||
$('logTabColumn').addClass('invisible');
|
||||
MochaUI.Desktop.resizePanels();
|
||||
window.qBittorrent.Log && window.qBittorrent.Log.unload();
|
||||
};
|
||||
|
||||
const addSearchPanel = function() {
|
||||
new MochaUI.Panel({
|
||||
id: 'SearchPanel',
|
||||
@ -1045,6 +1106,42 @@ window.addEvent('load', function() {
|
||||
});
|
||||
};
|
||||
|
||||
var addLogPanel = function() {
|
||||
new MochaUI.Panel({
|
||||
id: 'LogPanel',
|
||||
title: 'Log',
|
||||
header: true,
|
||||
padding: {
|
||||
top: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
left: 0
|
||||
},
|
||||
loadMethod: 'xhr',
|
||||
contentURL: 'views/log.html',
|
||||
require: {
|
||||
css: ['css/lib/vanillaSelectBox.css'],
|
||||
js: ['scripts/lib/vanillaSelectBox.js'],
|
||||
},
|
||||
tabsURL: 'views/logTabs.html',
|
||||
tabsOnload: function() {
|
||||
MochaUI.initializeTabs('panelTabs');
|
||||
|
||||
$('logMessageLink').addEvent('click', function(e) {
|
||||
window.qBittorrent.Log.setCurrentTab('main');
|
||||
});
|
||||
|
||||
$('logPeerLink').addEvent('click', function(e) {
|
||||
window.qBittorrent.Log.setCurrentTab('peer');
|
||||
});
|
||||
},
|
||||
collapsible: false,
|
||||
content: '',
|
||||
column: 'logTabColumn',
|
||||
height: null
|
||||
});
|
||||
};
|
||||
|
||||
new MochaUI.Panel({
|
||||
id: 'transferList',
|
||||
title: 'Panel',
|
||||
@ -1185,6 +1282,7 @@ window.addEvent('load', function() {
|
||||
$('transfersTabLink').addEvent('click', showTransfersTab);
|
||||
$('searchTabLink').addEvent('click', showSearchTab);
|
||||
$('rssTabLink').addEvent('click', showRssTab);
|
||||
$('logTabLink').addEvent('click', showLogTab);
|
||||
updateTabDisplay();
|
||||
|
||||
const registerDragAndDrop = () => {
|
||||
|
@ -46,6 +46,8 @@ window.qBittorrent.DynamicTable = (function() {
|
||||
SearchPluginsTable: SearchPluginsTable,
|
||||
TorrentTrackersTable: TorrentTrackersTable,
|
||||
TorrentFilesTable: TorrentFilesTable,
|
||||
LogMessageTable: LogMessageTable,
|
||||
LogPeerTable: LogPeerTable,
|
||||
RssFeedTable: RssFeedTable,
|
||||
RssArticleTable: RssArticleTable,
|
||||
RssDownloaderRulesTable: RssDownloaderRulesTable,
|
||||
@ -2610,6 +2612,153 @@ window.qBittorrent.DynamicTable = (function() {
|
||||
}
|
||||
});
|
||||
|
||||
const LogMessageTable = new Class({
|
||||
Extends: DynamicTable,
|
||||
|
||||
filterText: '',
|
||||
|
||||
filterdLength: function() {
|
||||
return this.tableBody.getElements('tr').length;
|
||||
},
|
||||
|
||||
initColumns: function() {
|
||||
this.newColumn('rowId', '', 'QBT_TR(ID)QBT_TR[CONTEXT=ExecutionLogWidget]', 50, true);
|
||||
this.newColumn('message', '', 'QBT_TR(Message)QBT_TR[CONTEXT=ExecutionLogWidget]', 350, true);
|
||||
this.newColumn('timestamp', '', 'QBT_TR(Timestamp)QBT_TR[CONTEXT=ExecutionLogWidget]', 150, true);
|
||||
this.newColumn('type', '', 'QBT_TR(Log Type)QBT_TR[CONTEXT=ExecutionLogWidget]', 100, true);
|
||||
this.initColumnsFunctions();
|
||||
},
|
||||
|
||||
initColumnsFunctions: function() {
|
||||
this.columns['timestamp'].updateTd = function(td, row) {
|
||||
const date = new Date(this.getRowValue(row) * 1000).toLocaleString();
|
||||
td.set({ 'text': date, 'title': date });
|
||||
};
|
||||
|
||||
this.columns['type'].updateTd = function(td, row) {
|
||||
//Type of the message: Log::NORMAL: 1, Log::INFO: 2, Log::WARNING: 4, Log::CRITICAL: 8
|
||||
let logLevel, addClass;
|
||||
switch (this.getRowValue(row).toInt()) {
|
||||
case 1:
|
||||
logLevel = 'QBT_TR(Normal)QBT_TR[CONTEXT=ExecutionLogWidget]';
|
||||
addClass = 'logNormal';
|
||||
break;
|
||||
case 2:
|
||||
logLevel = 'QBT_TR(Info)QBT_TR[CONTEXT=ExecutionLogWidget]';
|
||||
addClass = 'logInfo';
|
||||
break;
|
||||
case 4:
|
||||
logLevel = 'QBT_TR(Warning)QBT_TR[CONTEXT=ExecutionLogWidget]';
|
||||
addClass = 'logWarning';
|
||||
break;
|
||||
case 8:
|
||||
logLevel = 'QBT_TR(Critical)QBT_TR[CONTEXT=ExecutionLogWidget]';
|
||||
addClass = 'logCritical';
|
||||
break;
|
||||
default:
|
||||
logLevel = 'QBT_TR(Unknown)QBT_TR[CONTEXT=ExecutionLogWidget]';
|
||||
addClass = 'logUnknown';
|
||||
break;
|
||||
}
|
||||
td.set({ 'text': logLevel, 'title': logLevel });
|
||||
td.getParent('tr').set('class', 'logTableRow ' + addClass);
|
||||
};
|
||||
},
|
||||
|
||||
getFilteredAndSortedRows: function() {
|
||||
let filteredRows = [];
|
||||
const rows = this.rows.getValues();
|
||||
this.filterText = window.qBittorrent.Log.getFilterText();
|
||||
const filterTerms = (this.filterText.length > 0) ? this.filterText.toLowerCase().split(' ') : [];
|
||||
const logLevels = window.qBittorrent.Log.getSelectedLevels();
|
||||
if (filterTerms.length > 0 || logLevels.length < 4) {
|
||||
for (let i = 0; i < rows.length; ++i) {
|
||||
if (logLevels.indexOf(rows[i].full_data.type.toString()) == -1)
|
||||
continue;
|
||||
|
||||
if (filterTerms.length > 0 && !window.qBittorrent.Misc.containsAllTerms(rows[i].full_data.message, filterTerms))
|
||||
continue;
|
||||
|
||||
filteredRows.push(rows[i]);
|
||||
}
|
||||
}
|
||||
else {
|
||||
filteredRows = rows;
|
||||
}
|
||||
|
||||
filteredRows.sort(function(row1, row2) {
|
||||
const column = this.columns[this.sortedColumn];
|
||||
const res = column.compareRows(row1, row2);
|
||||
return (this.reverseSort == '0') ? res : -res;
|
||||
}.bind(this));
|
||||
|
||||
return filteredRows;
|
||||
},
|
||||
|
||||
setupCommonEvents: function() {},
|
||||
|
||||
setupTr: function(tr) {
|
||||
tr.addClass('logTableRow');
|
||||
}
|
||||
});
|
||||
|
||||
const LogPeerTable = new Class({
|
||||
Extends: LogMessageTable,
|
||||
|
||||
initColumns: function() {
|
||||
this.newColumn('rowId', '', 'QBT_TR(ID)QBT_TR[CONTEXT=ExecutionLogWidget]', 50, true);
|
||||
this.newColumn('ip', '', 'QBT_TR(IP)QBT_TR[CONTEXT=ExecutionLogWidget]', 150, true);
|
||||
this.newColumn('timestamp', '', 'QBT_TR(Timestamp)QBT_TR[CONTEXT=ExecutionLogWidget]', 150, true);
|
||||
this.newColumn('blocked', '', 'QBT_TR(Status)QBT_TR[CONTEXT=ExecutionLogWidget]', 150, true);
|
||||
this.newColumn('reason', '', 'QBT_TR(Reason)QBT_TR[CONTEXT=ExecutionLogWidget]', 150, true);
|
||||
|
||||
this.columns['timestamp'].updateTd = function(td, row) {
|
||||
const date = new Date(this.getRowValue(row) * 1000).toLocaleString();
|
||||
td.set({ 'text': date, 'title': date });
|
||||
};
|
||||
|
||||
this.columns['blocked'].updateTd = function(td, row) {
|
||||
let status, addClass;
|
||||
if (this.getRowValue(row)) {
|
||||
status = 'QBT_TR(Blocked)QBT_TR[CONTEXT=ExecutionLogWidget]';
|
||||
addClass = 'peerBlocked';
|
||||
}
|
||||
else {
|
||||
status = 'QBT_TR(Banned)QBT_TR[CONTEXT=ExecutionLogWidget]';
|
||||
addClass = 'peerBanned';
|
||||
}
|
||||
td.set({ 'text': status, 'title': status });
|
||||
td.getParent('tr').set('class', 'logTableRow ' + addClass);
|
||||
};
|
||||
},
|
||||
|
||||
getFilteredAndSortedRows: function() {
|
||||
let filteredRows = [];
|
||||
const rows = this.rows.getValues();
|
||||
this.filterText = window.qBittorrent.Log.getFilterText();
|
||||
const filterTerms = (this.filterText.length > 0) ? this.filterText.toLowerCase().split(' ') : [];
|
||||
if (filterTerms.length > 0) {
|
||||
for (let i = 0; i < rows.length; ++i) {
|
||||
if (filterTerms.length > 0 && !window.qBittorrent.Misc.containsAllTerms(rows[i].full_data.ip, filterTerms))
|
||||
continue;
|
||||
|
||||
filteredRows.push(rows[i]);
|
||||
}
|
||||
}
|
||||
else {
|
||||
filteredRows = rows;
|
||||
}
|
||||
|
||||
filteredRows.sort(function(row1, row2) {
|
||||
const column = this.columns[this.sortedColumn];
|
||||
const res = column.compareRows(row1, row2);
|
||||
return (this.reverseSort == '0') ? res : -res;
|
||||
}.bind(this));
|
||||
|
||||
return filteredRows;
|
||||
}
|
||||
});
|
||||
|
||||
return exports();
|
||||
})();
|
||||
|
||||
|
1403
src/webui/www/private/scripts/lib/vanillaSelectBox.js
Normal file
1403
src/webui/www/private/scripts/lib/vanillaSelectBox.js
Normal file
File diff suppressed because it is too large
Load Diff
427
src/webui/www/private/views/log.html
Normal file
427
src/webui/www/private/views/log.html
Normal file
@ -0,0 +1,427 @@
|
||||
<style type="text/css">
|
||||
#logTopBar {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
#logFilterBar {
|
||||
margin: .5em 0;
|
||||
}
|
||||
|
||||
#logFilterBar>label {
|
||||
font-weight: bold;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
#logFilterBar>button {
|
||||
display: inline-block;
|
||||
height: 24px;
|
||||
padding: 0 .5em;
|
||||
margin-left: .3em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#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: 1px 5px 1px 2em;
|
||||
margin-left: 20px;
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
#logFilterSummary {
|
||||
overflow: auto;
|
||||
margin: 1em 0 .5em;
|
||||
}
|
||||
|
||||
#numFilteredLogs,
|
||||
#numTotalLogs {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.logNormal {
|
||||
color: #80766e;
|
||||
}
|
||||
|
||||
.logInfo {
|
||||
color: #1781b5;
|
||||
}
|
||||
|
||||
.logWarning {
|
||||
color: #f97d1c;
|
||||
}
|
||||
|
||||
.logCritical,
|
||||
.peerBlocked {
|
||||
color: #ee3f4d;
|
||||
}
|
||||
|
||||
.vsb-main>button {
|
||||
padding: 0 12px !important;
|
||||
}
|
||||
|
||||
.contextMenu>li>a>img {
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<div id="logView">
|
||||
<div id="logTopBar">
|
||||
<div id="logFilterBar">
|
||||
<label for="logLevelSelect">QBT_TR(Log Levels:)QBT_TR[CONTEXT=ExecutionLogWidget]</label>
|
||||
<select multiple size="1" id="logLevelSelect" class="logLevelSelect" onchange="window.qBittorrent.Log.logLevelChanged()">
|
||||
<option value="1">QBT_TR(Normal Messages)QBT_TR[CONTEXT=ExecutionLogWidget]</option>
|
||||
<option value="2">QBT_TR(Information Messages)QBT_TR[CONTEXT=ExecutionLogWidget]</option>
|
||||
<option value="4">QBT_TR(Warning Messages)QBT_TR[CONTEXT=ExecutionLogWidget]</option>
|
||||
<option value="8">QBT_TR(Critical Messages)QBT_TR[CONTEXT=ExecutionLogWidget]</option>
|
||||
</select>
|
||||
|
||||
<input type="text" id="filterTextInput" onkeyup="window.qBittorrent.Log.filterTextChanged()" placeholder="QBT_TR(Filter logs)QBT_TR[CONTEXT=ExecutionLogWidget]" autocomplete="off" autocorrect="off" autocapitalize="none" />
|
||||
<button title="Clear input" onclick="javascript:document.querySelector('#filterTextInput').value='';window.qBittorrent.Log.filterTextChanged();">QBT_TR(Clear)QBT_TR[CONTEXT=ExecutionLogWidget]</button>
|
||||
</div>
|
||||
|
||||
<div id="logFilterSummary">
|
||||
<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>
|
||||
</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="QBT_TR(Copy)QBT_TR[CONTEXT=ExecutionLogWidget]" />QBT_TR(Copy)QBT_TR[CONTEXT=ExecutionLogWidget]</a></li>
|
||||
<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>
|
||||
</ul>
|
||||
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
if (window.qBittorrent === undefined) {
|
||||
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';
|
||||
let 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;
|
||||
let inputedFilterText = "";
|
||||
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.setAttribute('selected', '');
|
||||
}
|
||||
else {
|
||||
x.removeAttribute('selected');
|
||||
}
|
||||
});
|
||||
|
||||
selectBox = new vanillaSelectBox('#logLevelSelect', {
|
||||
maxHeight: 200,
|
||||
search: false,
|
||||
translations: {
|
||||
all: 'QBT_TR(All)QBT_TR[CONTEXT=ExecutionLogWidget]',
|
||||
item: 'QBT_TR(item)QBT_TR[CONTEXT=ExecutionLogWidget]',
|
||||
items: 'QBT_TR(items)QBT_TR[CONTEXT=ExecutionLogWidget]',
|
||||
selectAll: 'QBT_TR(Select All)QBT_TR[CONTEXT=ExecutionLogWidget]',
|
||||
clearAll: 'QBT_TR(Clear All)QBT_TR[CONTEXT=ExecutionLogWidget]',
|
||||
},
|
||||
placeHolder: "QBT_TR(Choose a log level...)QBT_TR[CONTEXT=ExecutionLogWidget]",
|
||||
});
|
||||
|
||||
const logTableContextMenu = new window.qBittorrent.ContextMenu.ContextMenu({
|
||||
targets: '.logTableRow',
|
||||
menu: 'logTableMenu',
|
||||
actions: {
|
||||
Clear: () => {
|
||||
tableInfo[currentSelectedTab].instance.selectedRowsIds().forEach(function(rowId) {
|
||||
tableInfo[currentSelectedTab].instance.removeRow(rowId);
|
||||
});
|
||||
|
||||
updateLableCount();
|
||||
}
|
||||
},
|
||||
offsets: {
|
||||
x: -16,
|
||||
y: -57
|
||||
}
|
||||
});
|
||||
|
||||
tableInfo['main'].instance.setup('logMessageTableDiv', 'logMessageTableFixedHeaderDiv', logTableContextMenu);
|
||||
tableInfo['peer'].instance.setup('logPeerTableDiv', 'logPeerTableFixedHeaderDiv', logTableContextMenu);
|
||||
|
||||
MUI.Panels.instances.LogPanel.contentEl.setStyle('height', '100%');
|
||||
$('logView').setStyle('height', 'inherit');
|
||||
|
||||
load();
|
||||
};
|
||||
|
||||
const unload = () => {
|
||||
for (let table in tableInfo)
|
||||
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 inputedFilterText;
|
||||
};
|
||||
|
||||
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').get('value').trim();
|
||||
if (inputedFilterText !== value) {
|
||||
inputedFilterText = value;
|
||||
logFilterChanged();
|
||||
}
|
||||
};
|
||||
|
||||
const logFilterChanged = () => {
|
||||
clearTimeout(logFilterTimer);
|
||||
logFilterTimer = setTimeout((curTab) => {
|
||||
tableInfo[curTab].instance.updateTable(false);
|
||||
updateLableCount(curTab);
|
||||
}, 400, 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);
|
||||
load();
|
||||
|
||||
if (tableInfo[currentSelectedTab].instance.filterText !== getFilterText()) {
|
||||
tableInfo[currentSelectedTab].instance.updateTable();
|
||||
}
|
||||
updateLableCount();
|
||||
};
|
||||
|
||||
const updateLableCount = (curTab) => {
|
||||
if (curTab === undefined)
|
||||
curTab = currentSelectedTab;
|
||||
|
||||
$('numFilteredLogs').set('text', tableInfo[curTab].instance.filterdLength());
|
||||
$('numTotalLogs').set('text', tableInfo[curTab].instance.getRowIds().length);
|
||||
};
|
||||
|
||||
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,
|
||||
noCache: true,
|
||||
method: 'get',
|
||||
onFailure: function(response) {
|
||||
const errorDiv = $('error_div');
|
||||
if (errorDiv)
|
||||
errorDiv.set('text', 'QBT_TR(qBittorrent client is not reachable)QBT_TR[CONTEXT=HttpServer]');
|
||||
tableInfo[curTab].progress = false;
|
||||
syncLogWithInterval(10000);
|
||||
},
|
||||
onSuccess: function(response) {
|
||||
$('error_div').set('text', '');
|
||||
|
||||
if ($('logTabColumn').hasClass('invisible'))
|
||||
return;
|
||||
|
||||
if (response.length > 0) {
|
||||
clearTimeout(logFilterTimer);
|
||||
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();
|
||||
tableInfo[curTab].instance.altRow();
|
||||
updateLableCount(curTab);
|
||||
}
|
||||
|
||||
tableInfo[curTab].progress = false;
|
||||
syncLogWithInterval(getSyncLogDataInterval());
|
||||
}
|
||||
}).send();
|
||||
};
|
||||
|
||||
new ClipboardJS('.copyLogDataToClipboard', {
|
||||
text: function() {
|
||||
let msg = [];
|
||||
tableInfo[currentSelectedTab].instance.selectedRowsIds().each(function(rowId) {
|
||||
msg.push(tableInfo[currentSelectedTab].instance.rows.get(rowId).full_data[(currentSelectedTab === 'main') ? 'message' : 'ip']);
|
||||
});
|
||||
|
||||
return msg.join('\n');
|
||||
}
|
||||
});
|
||||
|
||||
return exports();
|
||||
})();
|
||||
</script>
|
7
src/webui/www/private/views/logTabs.html
Normal file
7
src/webui/www/private/views/logTabs.html
Normal file
@ -0,0 +1,7 @@
|
||||
<div class="toolbarTabs">
|
||||
<ul id="panelTabs" class="tab-menu">
|
||||
<li id="logMessageLink" class="selected"><a title="QBT_TR(General)QBT_TR[CONTEXT=ExecutionLogWidget]">QBT_TR(General)QBT_TR[CONTEXT=ExecutionLogWidget]</a></li>
|
||||
<li id="logPeerLink"><a title="QBT_TR(Blocked IPs)QBT_TR[CONTEXT=ExecutionLogWidget]">QBT_TR(Blocked IPs)QBT_TR[CONTEXT=ExecutionLogWidget]</a></li>
|
||||
</ul>
|
||||
<div class="clear"></div>
|
||||
</div>
|
@ -12,6 +12,7 @@
|
||||
<file>private/css/noscript.css</file>
|
||||
<file>private/css/style.css</file>
|
||||
<file>private/css/Tabs.css</file>
|
||||
<file>private/css/lib/vanillaSelectBox.css</file>
|
||||
<file>private/css/Window.css</file>
|
||||
<file>private/download.html</file>
|
||||
<file>private/downloadlimit.html</file>
|
||||
@ -392,6 +393,7 @@
|
||||
<file>private/scripts/prop-trackers.js</file>
|
||||
<file>private/scripts/prop-webseeds.js</file>
|
||||
<file>private/scripts/speedslider.js</file>
|
||||
<file>private/scripts/lib/vanillaSelectBox.js</file>
|
||||
<file>private/setlocation.html</file>
|
||||
<file>private/shareratio.html</file>
|
||||
<file>private/upload.html</file>
|
||||
@ -400,6 +402,8 @@
|
||||
<file>private/views/aboutToolbar.html</file>
|
||||
<file>private/views/filters.html</file>
|
||||
<file>private/views/installsearchplugin.html</file>
|
||||
<file>private/views/log.html</file>
|
||||
<file>private/views/logTabs.html</file>
|
||||
<file>private/views/preferences.html</file>
|
||||
<file>private/views/preferencesToolbar.html</file>
|
||||
<file>private/views/properties.html</file>
|
||||
|
Loading…
Reference in New Issue
Block a user