Browse Source

Merge pull request #2262 from pmzqla/webui-changes

WebUI: Misc changes
adaptive-webui-19844
sledgehammer999 10 years ago
parent
commit
861c8e9ffa
  1. 7
      src/webui/webui.qrc
  2. 2
      src/webui/www/private/index.html
  3. 3
      src/webui/www/private/login.html
  4. 18
      src/webui/www/public/css/dynamicTable.css
  5. 338
      src/webui/www/public/prop-files.html
  6. 123
      src/webui/www/public/prop-general.html
  7. 146
      src/webui/www/public/prop-trackers.html
  8. 29
      src/webui/www/public/properties.html
  9. 61
      src/webui/www/public/properties_content.html
  10. 48
      src/webui/www/public/scripts/client.js
  11. 2
      src/webui/www/public/scripts/dynamicTable.js
  12. 2
      src/webui/www/public/scripts/mocha-yc.js
  13. 4
      src/webui/www/public/scripts/mocha.js
  14. 3
      src/webui/www/public/scripts/progressbar.js
  15. 351
      src/webui/www/public/scripts/prop-files.js
  16. 86
      src/webui/www/public/scripts/prop-general.js
  17. 130
      src/webui/www/public/scripts/prop-trackers.js
  18. 23
      src/webui/www/public/transferlist.html

7
src/webui/webui.qrc

@ -30,9 +30,10 @@
<file>www/public/preferences.html</file> <file>www/public/preferences.html</file>
<file>www/public/preferences_content.html</file> <file>www/public/preferences_content.html</file>
<file>www/public/properties.html</file> <file>www/public/properties.html</file>
<file>www/public/prop-files.html</file> <file>www/public/properties_content.html</file>
<file>www/public/prop-general.html</file> <file>www/public/scripts/prop-general.js</file>
<file>www/public/prop-trackers.html</file> <file>www/public/scripts/prop-trackers.js</file>
<file>www/public/scripts/prop-files.js</file>
<file>www/public/transferlist.html</file> <file>www/public/transferlist.html</file>
<file>www/public/upload.html</file> <file>www/public/upload.html</file>
<file>www/public/uploadlimit.html</file> <file>www/public/uploadlimit.html</file>

2
src/webui/www/private/index.html

@ -3,7 +3,7 @@
<head> <head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=10; IE=9; IE=8;" /> <meta http-equiv="X-UA-Compatible" content="IE=10; IE=9; IE=8;" />
<title>qBittorrent web User Interface</title> <title>_(qBittorrent web User Interface)</title>
<link rel="stylesheet" href="css/dynamicTable.css" type="text/css" /> <link rel="stylesheet" href="css/dynamicTable.css" type="text/css" />
<link rel="stylesheet" type="text/css" href="css/style.css" /> <link rel="stylesheet" type="text/css" href="css/style.css" />
<!--<link rel="stylesheet" type="text/css" href="css/Content.css" />--> <!--<link rel="stylesheet" type="text/css" href="css/Content.css" />-->

3
src/webui/www/private/login.html

@ -3,12 +3,13 @@
<head> <head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=8" /> <meta http-equiv="X-UA-Compatible" content="IE=8" />
<title>qBittorrent web User Interface</title> <title>_(qBittorrent web User Interface)</title>
<link rel="stylesheet" type="text/css" href="/css/style.css" /> <link rel="stylesheet" type="text/css" href="/css/style.css" />
<script type="text/javascript" src="scripts/mootools-1.2-core-yc.js" charset="utf-8"></script> <script type="text/javascript" src="scripts/mootools-1.2-core-yc.js" charset="utf-8"></script>
<script type="text/javascript"> <script type="text/javascript">
window.onload = function() { window.onload = function() {
$('username').focus(); $('username').focus();
$('username').select();
} }
window.addEvent('domready', function() { window.addEvent('domready', function() {

18
src/webui/www/public/css/dynamicTable.css

@ -58,9 +58,27 @@
#transferList tr.over { #transferList tr.over {
background-color: #ee6600; background-color: #ee6600;
color: #fff; color: #fff;
}
#myTable tr:hover,
#properties #torrentFiles tr.over,
#properties #trackers tr.over,
#transferList tr.over {
cursor: pointer; cursor: pointer;
} }
#transferList img.statusIcon { #transferList img.statusIcon {
margin-bottom: -4px; margin-bottom: -4px;
} }
#trackers th,
#trackers td,
#torrentFiles th,
#torrentFiles td,
#transferList th,
#transferList td {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
max-width: 300px;
}

338
src/webui/www/public/prop-files.html

@ -1,338 +0,0 @@
<span id="torrentFiles">
<table class="torrentTable" cellpadding="0" cellspacing="0" style="width: 100%">
<thead>
<tr>
<th style="width: 30px"><input type="checkbox" id="tristate_cb" style="display: none;" onclick="javascript:switchCBState()" /><label id="all_files_cb" class="tristate" for="tristate_cb"></label></th>
<th>_(Name)</th>
<th>_(Size)</th>
<th style="width: 90px;">_(Progress)</th>
<th>_(Priority)</th>
</tr>
</thead>
<tbody id="filesTable"></tbody>
</table>
</span>
<script type="text/javascript">
var waitingTorrentFiles=false;
var is_seed = true;
var current_hash = "";
if (!(Browser.name == "ie" && Browser.version < 9)) {
$("all_files_cb").removeClass("tristate");
$("all_files_cb").removeClass("partial");
$("all_files_cb").removeClass("checked");
$("tristate_cb").style.display = "inline";
}
var setCBState = function(state) {
if (Browser.name == "ie" && Browser.version < 9) {
if (state == "partial") {
if (!$("all_files_cb").hasClass("partial")) {
$("all_files_cb").removeClass("checked");
$("all_files_cb").addClass("partial");
}
return;
}
if (state == "checked") {
if(!$("all_files_cb").hasClass("checked")) {
$("all_files_cb").removeClass("partial");
$("all_files_cb").addClass("checked");
}
return;
}
$("all_files_cb").removeClass("partial");
$("all_files_cb").removeClass("checked");
} else {
if (state == "partial") {
$("tristate_cb").indeterminate = true;
} else if (state == "checked") {
$("tristate_cb").indeterminate = false;
$("tristate_cb").checked = true;
} else {
$("tristate_cb").indeterminate = false;
$("tristate_cb").checked = false;
}
}
}
var switchCBState = function() {
// Uncheck
if($("all_files_cb").hasClass("partial")) {
$("all_files_cb").removeClass("partial");
// Uncheck all checkboxes
$$('input.DownloadedCB').each(function(item, index) {
item.erase("checked");
setFilePriority(index, 0);
});
return;
}
if($("all_files_cb").hasClass("checked")) {
$("all_files_cb").removeClass("checked");
// Uncheck all checkboxes
$$('input.DownloadedCB').each(function(item, index) {
item.erase("checked");
setFilePriority(index, 0);
});
return;
}
// Check
$("all_files_cb").addClass("checked");
// Check all checkboxes
$$('input.DownloadedCB').each(function(item, index) {
item.set("checked", "checked");
setFilePriority(index, 1);
});
}
var allCBChecked = function() {
var CBs = $$('input.DownloadedCB');
for(var i=0; i<CBs.length; i+=1) {
var item = CBs[i];
if(!$defined(item.get('checked')) || !item.get('checked'))
return false;
}
return true;
}
var allCBUnchecked = function() {
var CBs = $$('input.DownloadedCB');
for(var i=0; i<CBs.length; i+=1) {
var item = CBs[i];
if($defined(item.get('checked')) && item.get('checked'))
return false;
}
return true;
}
var setFilePriority = function(id, priority) {
if(current_hash == "") return;
new Request({url: 'command/setFilePrio', method: 'post', data: {'hash': current_hash, 'id': id, 'priority': priority}}).send();
// Display or add combobox
if(priority > 0) {
$('comboPrio'+id).set("value", 1);
$('comboPrio'+id).removeClass("invisible");
} else {
$('comboPrio'+id).addClass("invisible");
}
}
var createDownloadedCB = function(id, downloaded) {
var CB = new Element('input');
CB.set('type', 'checkbox');
if(downloaded)
CB.set('checked', 'checked');
CB.set('id', 'cbPrio'+id);
CB.set('class', 'DownloadedCB');
CB.addEvent('change', function(e){
var checked = 0;
if($defined($('cbPrio'+id).get('checked')) && $('cbPrio'+id).get('checked'))
checked = 1;
setFilePriority(id, checked);
if(allCBChecked()) {
setCBState("checked");
} else {
if(allCBUnchecked()) {
setCBState("unchecked");
} else {
setCBState("partial");
}
}
});
return CB;
}
var createPriorityCombo = function(id, selected_prio) {
var select = new Element('select');
select.set('id', 'comboPrio'+id);
select.addEvent('change', function(e){
var new_prio = $('comboPrio'+id).get('value');
setFilePriority(id, new_prio);
});
var opt = new Element("option");
opt.set('value', '1')
opt.set('html', "_(Normal)");
if(selected_prio <= 1)
opt.setAttribute('selected', '');
opt.injectInside(select);
opt = new Element("option");
opt.set('value', '2')
opt.set('html', "_(High)");
if(selected_prio == 2)
opt.setAttribute('selected', '');
opt.injectInside(select);
opt = new Element("option");
opt.set('value', '7')
opt.set('html', "_(Maximum)");
if(selected_prio == 7)
opt.setAttribute('selected', '');
opt.injectInside(select);
if(is_seed || selected_prio < 1) {
select.addClass("invisible");
} else {
select.removeClass("invisible");
}
select.addClass("combo_priority");
return select;
}
var filesDynTable = new Class ({
initialize: function(){
},
setup: function(table){
this.table = $(table);
this.rows = new Hash();
},
removeRow: function(id){
if(this.rows.has(id)) {
var tr = this.rows.get(id);
tr.dispose();
this.rows.erase(id);
return true;
}
return false;
},
removeAllRows: function() {
this.rows.each(function(tr, id) {
this.removeRow(id);
}.bind(this));
},
updateRow: function(tr, row, id){
var tds = tr.getElements('td');
for(var i=0; i<row.length; i++) {
if(i==3) {
$('pbf_'+id).setValue(row[i].toFloat());
} else {
if(i==0) {
if(row[i] > 0)
tds[i].getChildren('input')[0].set('checked', 'checked');
else
tds[i].getChildren('input')[0].removeProperty('checked')
} else {
if(i == 4) {
if(!is_seed && row[i] > 0) {
tds[i].getChildren('select').set('value', row[i]);
$('comboPrio'+id).removeClass("invisible");
} else {
if(!$('comboPrio'+id).hasClass("invisible"))
$('comboPrio'+id).addClass("invisible");
}
} else {
tds[i].set('html', row[i]);
}
}
}
}
return true;
},
insertRow: function(id, row) {
if(this.rows.has(id)) {
var tr = this.rows.get(id);
this.updateRow(tr, row, id);
return;
}
//this.removeRow(id);
var tr = new Element('tr');
this.rows.set(id, tr);
for(var i=0; i<row.length; i++)
{
var td = new Element('td');
if(i==3) {
td.adopt(new ProgressBar(row[i].toFloat(), {'id': 'pbf_'+id, 'width':80}));
} else {
if(i == 0) {
var tree_img = new Element('img', {src: 'images/L.gif', style: 'margin-bottom: -2px'});
td.adopt(tree_img, createDownloadedCB(id,row[i]));
} else {
if(i == 4) {
td.adopt(createPriorityCombo(id,row[i]));
} else {
td.set('html', row[i]);
}
}
}
td.injectInside(tr);
}
tr.injectInside(this.table);
},
});
var loadTorrentFilesData = function() {
if(!$defined($('filesTable'))) {
// Tab changed
return;
}
var new_hash = myTable.getCurrentTorrentHash();
if(new_hash == "") {
fTable.removeAllRows();
loadTorrentFilesData.delay(1500);
return;
}
if(new_hash != current_hash) {
fTable.removeAllRows();
current_hash = new_hash;
}
var url = 'json/propertiesFiles/'+current_hash;
if (!waitingTorrentFiles) {
waitingTorrentFiles=true;
var request = new Request.JSON({
url: url,
noCache: true,
method: 'get',
onFailure: function() {
$('error_div').set('html', '_(qBittorrent client is not reachable)');
waitingTorrentFiles=false;
loadTorrentFilesData.delay(2000);
},
onSuccess: function(files) {
$('error_div').set('html', '');
if(files){
// Update Trackers data
var i=0;
files.each(function(file){
if(i == 0) {
is_seed = file.is_seed;
}
var row = new Array();
row.length = 4;
row[0] = file.priority;
row[1] = file.name;
row[2] = friendlyUnit(file.size, false);
row[3] = (file.progress*100).round(1);
if(row[3] == 100.0 && file.progress < 1.0)
row[3] = 99.9
row[4] = file.priority;
fTable.insertRow(i, row);
i++;
}.bind(this));
// Set global CB state
if(allCBChecked()) {
setCBState("checked");
} else {
if(allCBUnchecked()) {
setCBState("unchecked");
} else {
setCBState("partial");
}
}
} else {
fTable.removeAllRows();
}
waitingTorrentFiles=false;
loadTorrentFilesData.delay(1500);
}
}).send();
}
}
fTable = new filesDynTable();
fTable.setup($('filesTable'));
// Initial loading
loadTorrentFilesData();
</script>

123
src/webui/www/public/prop-general.html

@ -1,123 +0,0 @@
<fieldset>
<legend><b>_(Transfer)</b></legend>
<table>
<tr><td style="text-align:right; padding: 4px;">_(Uploaded:)</td><td style="padding-right: 20px;" id="total_uploaded">0 Kb</td><td style="text-align:right; padding: 4px;">_(UP limit:)</td><td style="padding-right: 20px;" id="up_limit">xx</td><td style="text-align:right; padding: 4px;">_(Share ratio:)</td><td id="share_ratio">xx</td></tr>
<tr><td style="text-align:right; padding: 4px;">_(Downloaded:)</td><td style="padding-right: 20px;" id="total_downloaded">0 Kb</td><td style="text-align:right; padding: 4px;">_(DL limit:)</td><td style="padding-right: 20px;" id="dl_limit">xx</td><td style="text-align:right; padding: 4px;">_(Connections:)</td><td id="nb_connections">xx</td></tr>
<tr><td style="text-align:right; padding: 4px;">_(Wasted:)</td><td style="padding-right: 20px;" id="total_wasted">0 Kb</td><td style="text-align:right; padding: 4px;">_(Time active:)</td><td style="padding-right: 20px;" id="time_elapsed">xx</td><td></td></tr>
</table>
</fieldset>
<fieldset>
<legend><b>_(Information)</b></legend>
<table>
<tr><td style="text-align:right; padding: 4px;">_(Save path:)</td><td id="save_path">xxx</td></tr>
<tr><td style="text-align:right; padding: 4px;">_(Created on:)</td><td id="creation_date">xxx</td></tr>
<tr><td style="text-align:right; padding: 4px;">_(Pieces size:)</td><td id="piece_size">xxx</td></tr>
<tr><td style="text-align:right; padding: 4px;">_(Torrent hash:)</td><td id="torrent_hash">xxx</td></tr>
<tr><td style="vertical-align: top; padding: 4px; text-align:right;">
<br/>
_(Comment:)
</td><td>
<textarea name="comment" id="comment" rows="15" cols="80">
xxx
</textarea>
</td></tr>
</table>
<br/>
</fieldset>
<!--
static information: save_path, creation_date, torrent_hash, comment
dynamic information: total_downloaded, total_uploaded, total_wasted, up_limit, dl_limit, time_elapsed, share_ratio, nb_connections
-->
<script type="text/javascript">
var waiting=false;
var clearData = function() {
$('torrent_hash').set('html', '');
$('save_path').set('html', '');
$('creation_date').set('html', '');
$('piece_size').set('html', '');
$('comment').set('html', '');
$('total_uploaded').set('html', '');
$('total_downloaded').set('html', '');
$('total_wasted').set('html', '');
$('up_limit').set('html', '');
$('dl_limit').set('html', '');
$('time_elapsed').set('html', '');
$('nb_connections').set('html', '');
$('share_ratio').set('html', '');
}
var loadData = function() {
if(!$defined($('torrent_hash'))) {
// Tab changed
return;
}
var current_hash = myTable.getCurrentTorrentHash();
if(current_hash == "") {
clearData();
loadData.delay(1500);
return;
}
// Display hash
$('torrent_hash').set('html', current_hash);
var url = 'json/propertiesGeneral/'+current_hash;
if (!waiting) {
waiting=true;
var request = new Request.JSON({
url: url,
noCache: true,
method: 'get',
onFailure: function() {
$('error_div').set('html', '_(qBittorrent client is not reachable)');
waiting=false;
loadData.delay(2000);
},
onSuccess: function(data) {
$('error_div').set('html', '');
if(data){
var temp;
// Update Torrent data
$('save_path').set('html', data.save_path);
temp = data.creation_date;
var timestamp = "_(Unknown)";
if (temp != -1)
timestamp = new Date(data.creation_date*1000).toISOString();
$('creation_date').set('html', timestamp);
$('piece_size').set('html', friendlyUnit(data.piece_size));
$('comment').set('html', data.comment);
$('total_uploaded').set('html', friendlyUnit(data.total_uploaded) +
" (" + friendlyUnit(data.total_uploaded_session) +
" (" + "_(this session)" + ")");
$('total_downloaded').set('html', friendlyUnit(data.total_downloaded) +
" (" + friendlyUnit(data.total_downloaded_session) +
" (" + "_(this session)" + ")");
$('total_wasted').set('html', data.total_wasted);
temp = data.up_limit;
$('up_limit').set('html', temp == -1 ? "∞" : temp);
temp = data.dl_limit;
$('dl_limit').set('html', temp == -1 ? "∞" : temp);
temp = friendlyDuration(status.active_time);
if (status.is_seed)
temp += " (" + "_(Seeded for %1)".replace("%1", status.seeding_time) + ")";
$('time_elapsed').set('html', temp);
temp = data.nb_connections + " (" + "_(%1 max)".replace("%1", status.nb_connections_limit) + ")";
$('nb_connections').set('html', temp);
$('share_ratio').set('html', data.share_ratio.toFixed(2));
} else {
clearData();
}
waiting=false;
loadData.delay(1500);
}
}).send();
}
}
// Initial loading
loadData();
</script>

146
src/webui/www/public/prop-trackers.html

@ -1,146 +0,0 @@
<span id="trackers">
<table class="torrentTable" cellpadding="0" cellspacing="0" style="width: 100%">
<thead>
<tr>
<th>_(URL) <img src="theme/list-add" id="addTrackersPlus"/></th>
<th>_(Status)</th>
<th>_(Peers)</th>
<th>_(Message)</th>
</tr>
</thead>
<tbody id="trackersTable"></tbody>
</table>
</span>
<script type="text/javascript">
var trackersDynTable = new Class ({
initialize: function(){
},
setup: function(table){
this.table = $(table);
this.rows = new Hash();
},
removeRow: function(url){
if(this.rows.has(url)) {
var tr = this.rows.get(url);
tr.dispose();
this.rows.erase(url);
return true;
}
return false;
},
removeAllRows: function() {
this.rows.each(function(tr, url) {
this.removeRow(url);
}.bind(this));
},
updateRow: function(tr, row){
var tds = tr.getElements('td');
for(var i=0; i<row.length; i++) {
tds[i].set('html', row[i]);
}
return true;
},
insertRow: function(row) {
var url = row[0];
if(this.rows.has(url)) {
var tr = this.rows.get(url);
this.updateRow(tr, row);
return;
}
//this.removeRow(id);
var tr = new Element('tr');
this.rows.set(url, tr);
for(var i=0; i<row.length; i++)
{
var td = new Element('td');
td.set('html', row[i]);
td.injectInside(tr);
}
tr.injectInside(this.table);
},
});
var waitingTrackers=false;
var current_hash = "";
var loadTrackersData = function() {
if(!$defined($('trackersTable'))) {
// Tab changed
return;
}
var new_hash = myTable.getCurrentTorrentHash();
if(new_hash == "") {
tTable.removeAllRows();
loadTrackersData.delay(1500);
return;
}
if(new_hash != current_hash) {
tTable.removeAllRows();
current_hash = new_hash;
}
var url = 'json/propertiesTrackers/'+current_hash;
if (!waitingTrackers) {
waitingTrackers=true;
var request = new Request.JSON({
url: url,
noCache: true,
method: 'get',
onFailure: function() {
$('error_div').set('html', '_(qBittorrent client is not reachable)');
waitingTrackers=false;
loadTrackersData.delay(2000);
},
onSuccess: function(trackers) {
$('error_div').set('html', '');
if(trackers){
// Update Trackers data
trackers.each(function(tracker){
var row = new Array();
row.length = 4;
row[0] = tracker.url;
row[1] = tracker.status;
row[2] = tracker.num_peers;
row[3] = tracker.msg;
tTable.insertRow(row);
});
} else {
tTable.removeAllRows();
}
waitingTrackers=false;
loadTrackersData.delay(1500);
}
}).send();
}
}
tTable = new trackersDynTable();
tTable.setup($('trackersTable'));
// Initial loading
loadTrackersData();
// Add trackers code
$('addTrackersPlus').addEvent('click', function addTrackerDlg() {
if(current_hash.length == 0) return;
new MochaUI.Window({
id: 'trackersPage',
title: "_(Trackers addition dialog)",
loadMethod: 'iframe',
contentURL:'addtrackers.html?hash='+current_hash,
scrollbars: true,
resizable: false,
maximizable: false,
closable: true,
paddingVertical: 0,
paddingHorizontal: 0,
width: 500,
height: 250
});
});
</script>

29
src/webui/www/public/properties.html

@ -1,32 +1,3 @@
<script type="text/javascript">
MochaUI.initializeTabs('propertiesTabs');
/*MochaUI.updateContent({
'element': $('properties'),
'url': 'prop-general.html'
});*/
$('PropGeneralLink').addEvent('click', function(e){
MochaUI.updateContent({
'element': $('propertiesPanel'),
'url': 'prop-general.html'
});
});
$('PropTrackersLink').addEvent('click', function(e){
MochaUI.updateContent({
'element': $('propertiesPanel'),
'url': 'prop-trackers.html'
});
});
$('PropFilesLink').addEvent('click', function(e){
MochaUI.updateContent({
'element': $('propertiesPanel'),
'url': 'prop-files.html'
});
});
</script>
<div class="toolbarTabs"> <div class="toolbarTabs">
<ul id="propertiesTabs" class="tab-menu"> <ul id="propertiesTabs" class="tab-menu">
<li id="PropGeneralLink" class="selected"><a>_(General)</a></li> <li id="PropGeneralLink" class="selected"><a>_(General)</a></li>

61
src/webui/www/public/properties_content.html

@ -0,0 +1,61 @@
<div id="prop_general">
<fieldset>
<legend><b>_(Transfer)</b></legend>
<table>
<tr><td style="text-align:right; padding: 4px;">_(Uploaded:)</td><td style="padding-right: 20px;" id="total_uploaded"></td><td style="text-align:right; padding: 4px;">_(UP limit:)</td><td style="padding-right: 20px;" id="up_limit"></td><td style="text-align:right; padding: 4px;">_(Share ratio:)</td><td id="share_ratio"></td></tr>
<tr><td style="text-align:right; padding: 4px;">_(Downloaded:)</td><td style="padding-right: 20px;" id="total_downloaded"></td><td style="text-align:right; padding: 4px;">_(DL limit:)</td><td style="padding-right: 20px;" id="dl_limit"></td><td style="text-align:right; padding: 4px;">_(Connections:)</td><td id="nb_connections"></td></tr>
<tr><td style="text-align:right; padding: 4px;">_(Wasted:)</td><td style="padding-right: 20px;" id="total_wasted"></td><td style="text-align:right; padding: 4px;">_(Time active:)</td><td style="padding-right: 20px;" id="time_elapsed"></td><td></td></tr>
</table>
</fieldset>
<fieldset>
<legend><b>_(Information)</b></legend>
<table>
<tr><td style="text-align:right; padding: 4px;">_(Save path:)</td><td id="save_path"></td></tr>
<tr><td style="text-align:right; padding: 4px;">_(Created on:)</td><td id="creation_date"></td></tr>
<tr><td style="text-align:right; padding: 4px;">_(Pieces size:)</td><td id="piece_size"></td></tr>
<tr><td style="text-align:right; padding: 4px;">_(Torrent hash:)</td><td id="torrent_hash"></td></tr>
<tr><td style="vertical-align: top; padding: 4px; text-align:right;">
<br/>
_(Comment:)
</td><td>
<textarea name="comment" id="comment" rows="15" cols="80" readonly>
</textarea>
</td></tr>
</table>
<br/>
</fieldset>
</div>
<div id="prop_trackers" class="invisible">
<div id="trackers">
<table class="torrentTable" cellpadding="0" cellspacing="0" style="width: 100%">
<thead>
<tr>
<th>_(URL) <img src="theme/list-add" id="addTrackersPlus"/></th>
<th style="width: 250px;">_(Status)</th>
<th style="width: 150px;">_(Peers)</th>
<th style="width: 200px;">_(Message)</th>
</tr>
</thead>
<tbody id="trackersTable"></tbody>
</table>
</div>
</div>
<div id="prop_files" class="invisible">
<div id="torrentFiles">
<table class="torrentTable" cellpadding="0" cellspacing="0" style="width: 100%">
<thead>
<tr>
<th style="width: 30px; border-right: 0"><input type="checkbox" id="tristate_cb" style="display: none;" onclick="javascript:switchCBState()" /><label id="all_files_cb" class="tristate" for="tristate_cb"></label></th>
<th>_(Name)</th>
<th style="width: 150px;">_(Size)</th>
<th style="width: 90px;">_(Progress)</th>
<th style="width: 150px; border-right: 0">_(Priority)</th>
</tr>
</thead>
<tbody id="filesTable"></tbody>
</table>
</div>
</div>

48
src/webui/www/public/scripts/client.js

@ -24,6 +24,8 @@
myTable = new dynamicTable(); myTable = new dynamicTable();
var updatePropertiesPanel = function(){};
var stateToImg = function (state) { var stateToImg = function (state) {
if (state == "pausedUP" || state == "pausedDL") { if (state == "pausedUP" || state == "pausedDL") {
state = "paused"; state = "paused";
@ -54,6 +56,7 @@ var loadTorrentsInfo = function () {
method : 'get', method : 'get',
onFailure : function () { onFailure : function () {
$('error_div').set('html', '_(qBittorrent client is not reachable)'); $('error_div').set('html', '_(qBittorrent client is not reachable)');
clearTimeout(loadTorrentsInfoTimer);
loadTorrentsInfoTimer = loadTorrentsInfo.delay(2000); loadTorrentsInfoTimer = loadTorrentsInfo.delay(2000);
}, },
onSuccess : function (events) { onSuccess : function (events) {
@ -136,6 +139,7 @@ var loadTorrentsInfo = function () {
myTable.altRow(); myTable.altRow();
} }
clearTimeout(loadTorrentsInfoTimer);
loadTorrentsInfoTimer = loadTorrentsInfo.delay(1500); loadTorrentsInfoTimer = loadTorrentsInfo.delay(1500);
} }
}).send(); }).send();
@ -143,7 +147,7 @@ var loadTorrentsInfo = function () {
var updateTransferList = function() { var updateTransferList = function() {
clearTimeout(loadTorrentsInfoTimer); clearTimeout(loadTorrentsInfoTimer);
loadTorrentsInfoTimer = loadTorrentsInfo(); loadTorrentsInfo();
} }
window.addEvent('load', function () { window.addEvent('load', function () {
@ -231,6 +235,7 @@ window.addEvent('load', function () {
method : 'get', method : 'get',
onFailure : function () { onFailure : function () {
$('error_div').set('html', '_(qBittorrent client is not reachable)'); $('error_div').set('html', '_(qBittorrent client is not reachable)');
clearTimeout(loadTransferInfoTimer);
loadTransferInfoTimer = loadTransferInfo.delay(4000); loadTransferInfoTimer = loadTransferInfo.delay(4000);
}, },
onSuccess : function (info) { onSuccess : function (info) {
@ -245,6 +250,7 @@ window.addEvent('load', function () {
document.title = "_(D:%1 U:%2)".replace("%1", friendlyUnit(info.dl_info_speed, true)).replace("%2", friendlyUnit(info.up_info_speed, true)); document.title = "_(D:%1 U:%2)".replace("%1", friendlyUnit(info.dl_info_speed, true)).replace("%2", friendlyUnit(info.up_info_speed, true));
else else
document.title = "_(qBittorrent web User Interface)"; document.title = "_(qBittorrent web User Interface)";
clearTimeout(loadTransferInfoTimer);
loadTransferInfoTimer = loadTransferInfo.delay(3000); loadTransferInfoTimer = loadTransferInfo.delay(3000);
} }
} }
@ -253,7 +259,7 @@ window.addEvent('load', function () {
var updateTransferInfo = function() { var updateTransferInfo = function() {
clearTimeout(loadTransferInfoTimer); clearTimeout(loadTransferInfoTimer);
loadTransferInfoTimer = loadTransferInfo(); loadTransferInfo();
} }
// Start fetching data now // Start fetching data now
@ -311,11 +317,45 @@ window.addEvent('load', function () {
bottom : 0, bottom : 0,
left : 0 left : 0
}, },
contentURL : 'prop-general.html', contentURL : 'properties_content.html',
require : { require : {
css : ['css/Tabs.css'] css : ['css/Tabs.css'],
js : ['scripts/prop-general.js', 'scripts/prop-trackers.js', 'scripts/prop-files.js'],
}, },
tabsURL : 'properties.html', tabsURL : 'properties.html',
tabsOnload : function() {
MochaUI.initializeTabs('propertiesTabs');
updatePropertiesPanel = function() {
if (!$('prop_general').hasClass('invisible'))
updateTorrentData();
else if (!$('prop_trackers').hasClass('invisible'))
updateTrackersData();
else if (!$('prop_files').hasClass('invisible'))
updateTorrentFilesData();
}
$('PropGeneralLink').addEvent('click', function(e){
$('prop_general').removeClass("invisible");
$('prop_trackers').addClass("invisible");
$('prop_files').addClass("invisible");
updatePropertiesPanel();
});
$('PropTrackersLink').addEvent('click', function(e){
$('prop_trackers').removeClass("invisible");
$('prop_general').addClass("invisible");
$('prop_files').addClass("invisible");
updatePropertiesPanel();
});
$('PropFilesLink').addEvent('click', function(e){
$('prop_files').removeClass("invisible");
$('prop_general').addClass("invisible");
$('prop_trackers').addClass("invisible");
updatePropertiesPanel();
});
},
column : 'mainColumn', column : 'mainColumn',
height : prop_h height : prop_h
}); });

2
src/webui/www/public/scripts/dynamicTable.js

@ -220,7 +220,7 @@ var dynamicTable = new Class({
temptr.addClass('selected'); temptr.addClass('selected');
} }
this.cur[0] = id; this.cur[0] = id;
// TODO: Warn Properties panel updatePropertiesPanel();
} }
} }
return false; return false;

2
src/webui/www/public/scripts/mocha-yc.js

File diff suppressed because one or more lines are too long

4
src/webui/www/public/scripts/mocha.js

@ -163,6 +163,8 @@ MUI.extend({
new MUI.Require({ new MUI.Require({
js: options.require.js, js: options.require.js,
onload: function(){ onload: function(){
if (!$defined(options.require.onload))
return;
if (Browser.Engine.presto){ if (Browser.Engine.presto){
options.require.onload.delay(100); options.require.onload.delay(100);
} }
@ -235,7 +237,7 @@ MUI.extend({
var getTitle = new RegExp("<title>[\n\r\s]*(.*)[\n\r\s]*</title>", "gmi"); var getTitle = new RegExp("<title>[\n\r\s]*(.*)[\n\r\s]*</title>", "gmi");
var error = getTitle.exec(response.responseText); var error = getTitle.exec(response.responseText);
if (!error) error = 'Unknown'; if (!error) error = 'Unknown';
contentContainer.set('html', '<h3>Error: ' + error[1] + '</h3>'); contentContainer.set('html', '<h3>Error: ' + error + '</h3>');
if (args.recipient == 'window'){ if (args.recipient == 'window'){
instance.hideSpinner(); instance.hideSpinner();
} }

3
src/webui/www/public/scripts/progressbar.js

@ -19,7 +19,8 @@ var ProgressBar = new Class({
'border': '1px solid #000', 'border': '1px solid #000',
'width': vals.width, 'width': vals.width,
'height': vals.height, 'height': vals.height,
'position': 'relative' 'position': 'relative',
'margin': '0 auto'
} }
}); });
obj.vals = vals; obj.vals = vals;

351
src/webui/www/public/scripts/prop-files.js

@ -0,0 +1,351 @@
var is_seed = true;
var current_hash = "";
if (!(Browser.name == "ie" && Browser.version < 9)) {
$("all_files_cb").removeClass("tristate");
$("all_files_cb").removeClass("partial");
$("all_files_cb").removeClass("checked");
$("tristate_cb").style.display = "inline";
}
var setCBState = function(state) {
if (Browser.name == "ie" && Browser.version < 9) {
if (state == "partial") {
if (!$("all_files_cb").hasClass("partial")) {
$("all_files_cb").removeClass("checked");
$("all_files_cb").addClass("partial");
}
return;
}
if (state == "checked") {
if (!$("all_files_cb").hasClass("checked")) {
$("all_files_cb").removeClass("partial");
$("all_files_cb").addClass("checked");
}
return;
}
$("all_files_cb").removeClass("partial");
$("all_files_cb").removeClass("checked");
}
else {
if (state == "partial") {
$("tristate_cb").indeterminate = true;
}
else if (state == "checked") {
$("tristate_cb").indeterminate = false;
$("tristate_cb").checked = true;
}
else {
$("tristate_cb").indeterminate = false;
$("tristate_cb").checked = false;
}
}
}
var switchCBState = function() {
// Uncheck
if ($("all_files_cb").hasClass("partial")) {
$("all_files_cb").removeClass("partial");
// Uncheck all checkboxes
$$('input.DownloadedCB').each(function(item, index) {
item.erase("checked");
setFilePriority(index, 0);
});
return;
}
if ($("all_files_cb").hasClass("checked")) {
$("all_files_cb").removeClass("checked");
// Uncheck all checkboxes
$$('input.DownloadedCB').each(function(item, index) {
item.erase("checked");
setFilePriority(index, 0);
});
return;
}
// Check
$("all_files_cb").addClass("checked");
// Check all checkboxes
$$('input.DownloadedCB').each(function(item, index) {
item.set("checked", "checked");
setFilePriority(index, 1);
});
}
var allCBChecked = function() {
var CBs = $$('input.DownloadedCB');
for (var i = 0; i < CBs.length; i += 1) {
var item = CBs[i];
if (!$defined(item.get('checked')) || !item.get('checked'))
return false;
}
return true;
}
var allCBUnchecked = function() {
var CBs = $$('input.DownloadedCB');
for (var i = 0; i < CBs.length; i += 1) {
var item = CBs[i];
if ($defined(item.get('checked')) && item.get('checked'))
return false;
}
return true;
}
var setFilePriority = function(id, priority) {
if (current_hash == "") return;
new Request({
url: 'command/setFilePrio',
method: 'post',
data: {
'hash': current_hash,
'id': id,
'priority': priority
}
}).send();
// Display or add combobox
if (priority > 0) {
$('comboPrio' + id).set("value", 1);
$('comboPrio' + id).removeClass("invisible");
}
else {
$('comboPrio' + id).addClass("invisible");
}
}
var createDownloadedCB = function(id, downloaded) {
var CB = new Element('input');
CB.set('type', 'checkbox');
if (downloaded)
CB.set('checked', 'checked');
CB.set('id', 'cbPrio' + id);
CB.set('class', 'DownloadedCB');
CB.addEvent('change', function(e) {
var checked = 0;
if ($defined($('cbPrio' + id).get('checked')) && $('cbPrio' + id).get('checked'))
checked = 1;
setFilePriority(id, checked);
if (allCBChecked()) {
setCBState("checked");
}
else {
if (allCBUnchecked()) {
setCBState("unchecked");
}
else {
setCBState("partial");
}
}
});
return CB;
}
var createPriorityCombo = function(id, selected_prio) {
var select = new Element('select');
select.set('id', 'comboPrio' + id);
select.addEvent('change', function(e) {
var new_prio = $('comboPrio' + id).get('value');
setFilePriority(id, new_prio);
});
var opt = new Element("option");
opt.set('value', '1')
opt.set('html', "_(Normal)");
if (selected_prio <= 1)
opt.setAttribute('selected', '');
opt.injectInside(select);
opt = new Element("option");
opt.set('value', '2')
opt.set('html', "_(High)");
if (selected_prio == 2)
opt.setAttribute('selected', '');
opt.injectInside(select);
opt = new Element("option");
opt.set('value', '7')
opt.set('html', "_(Maximum)");
if (selected_prio == 7)
opt.setAttribute('selected', '');
opt.injectInside(select);
if (is_seed || selected_prio < 1) {
select.addClass("invisible");
}
else {
select.removeClass("invisible");
}
select.addClass("combo_priority");
return select;
}
var filesDynTable = new Class({
initialize: function() {},
setup: function(table) {
this.table = $(table);
this.rows = new Hash();
},
removeRow: function(id) {
if (this.rows.has(id)) {
var tr = this.rows.get(id);
tr.dispose();
this.rows.erase(id);
return true;
}
return false;
},
removeAllRows: function() {
this.rows.each(function(tr, id) {
this.removeRow(id);
}.bind(this));
},
updateRow: function(tr, row, id) {
var tds = tr.getElements('td');
for (var i = 0; i < row.length; i++) {
if (i == 3) {
$('pbf_' + id).setValue(row[i].toFloat());
}
else {
if (i == 0) {
if (row[i] > 0)
tds[i].getChildren('input')[0].set('checked', 'checked');
else
tds[i].getChildren('input')[0].removeProperty('checked')
}
else {
if (i == 4) {
if (!is_seed && row[i] > 0) {
tds[i].getChildren('select').set('value', row[i]);
$('comboPrio' + id).removeClass("invisible");
}
else {
if (!$('comboPrio' + id).hasClass("invisible"))
$('comboPrio' + id).addClass("invisible");
}
}
else {
tds[i].set('html', row[i]);
}
}
}
}
return true;
},
insertRow: function(id, row) {
if (this.rows.has(id)) {
var tr = this.rows.get(id);
this.updateRow(tr, row, id);
return;
}
//this.removeRow(id);
var tr = new Element('tr');
this.rows.set(id, tr);
for (var i = 0; i < row.length; i++) {
var td = new Element('td');
if (i == 3) {
td.adopt(new ProgressBar(row[i].toFloat(), {
'id': 'pbf_' + id,
'width': 80
}));
}
else {
if (i == 0) {
var tree_img = new Element('img', {
src: 'images/L.gif',
style: 'margin-bottom: -2px'
});
td.adopt(tree_img, createDownloadedCB(id, row[i]));
}
else {
if (i == 4) {
td.adopt(createPriorityCombo(id, row[i]));
}
else {
td.set('html', row[i]);
}
}
}
td.injectInside(tr);
}
tr.injectInside(this.table);
},
});
var loadTorrentFilesDataTimer;
var loadTorrentFilesData = function() {
if ($('prop_files').hasClass('invisible')) {
// Tab changed, don't do anything
return;
}
var new_hash = myTable.getCurrentTorrentHash();
if (new_hash == "") {
fTable.removeAllRows();
clearTimeout(loadTorrentFilesDataTimer);
loadTorrentFilesDataTimer = loadTorrentFilesData.delay(5000);
return;
}
if (new_hash != current_hash) {
fTable.removeAllRows();
current_hash = new_hash;
}
var url = 'json/propertiesFiles/' + current_hash;
var request = new Request.JSON({
url: url,
noCache: true,
method: 'get',
onFailure: function() {
$('error_div').set('html', '_(qBittorrent client is not reachable)');
clearTimeout(loadTorrentFilesDataTimer);
loadTorrentFilesDataTimer = loadTorrentFilesData.delay(10000);
},
onSuccess: function(files) {
$('error_div').set('html', '');
if (files) {
// Update Trackers data
var i = 0;
files.each(function(file) {
if (i == 0) {
is_seed = file.is_seed;
}
var row = new Array();
row.length = 4;
row[0] = file.priority;
row[1] = file.name;
row[2] = friendlyUnit(file.size, false);
row[3] = (file.progress * 100).round(1);
if (row[3] == 100.0 && file.progress < 1.0)
row[3] = 99.9
row[4] = file.priority;
fTable.insertRow(i, row);
i++;
}.bind(this));
// Set global CB state
if (allCBChecked()) {
setCBState("checked");
}
else {
if (allCBUnchecked()) {
setCBState("unchecked");
}
else {
setCBState("partial");
}
}
}
else {
fTable.removeAllRows();
}
clearTimeout(loadTorrentFilesDataTimer);
loadTorrentFilesDataTimer = loadTorrentFilesData.delay(5000);
}
}).send();
}
var updateTorrentFilesData = function() {
clearTimeout(loadTorrentFilesDataTimer);
loadTorrentFilesData();
}
fTable = new filesDynTable();
fTable.setup($('filesTable'));

86
src/webui/www/public/scripts/prop-general.js

@ -0,0 +1,86 @@
var clearData = function() {
$('torrent_hash').set('html', '');
$('save_path').set('html', '');
$('creation_date').set('html', '');
$('piece_size').set('html', '');
$('comment').set('html', '');
$('total_uploaded').set('html', '');
$('total_downloaded').set('html', '');
$('total_wasted').set('html', '');
$('up_limit').set('html', '');
$('dl_limit').set('html', '');
$('time_elapsed').set('html', '');
$('nb_connections').set('html', '');
$('share_ratio').set('html', '');
}
var loadTorrentDataTimer;
var loadTorrentData = function() {
if ($('prop_general').hasClass('invisible')) {
// Tab changed, don't do anything
return;
}
var current_hash = myTable.getCurrentTorrentHash();
if (current_hash == "") {
clearData();
clearTimeout(loadTorrentDataTimer);
loadTorrentDataTimer = loadTorrentData.delay(5000);
return;
}
// Display hash
$('torrent_hash').set('html', current_hash);
var url = 'json/propertiesGeneral/' + current_hash;
var request = new Request.JSON({
url: url,
noCache: true,
method: 'get',
onFailure: function() {
$('error_div').set('html', '_(qBittorrent client is not reachable)');
clearTimeout(loadTorrentDataTimer);
loadTorrentDataTimer = loadTorrentData.delay(10000);
},
onSuccess: function(data) {
$('error_div').set('html', '');
if (data) {
var temp;
// Update Torrent data
$('save_path').set('html', data.save_path);
temp = data.creation_date;
var timestamp = "_(Unknown)";
if (temp != -1)
timestamp = new Date(data.creation_date * 1000).toISOString();
$('creation_date').set('html', timestamp);
$('piece_size').set('html', friendlyUnit(data.piece_size));
$('comment').set('html', data.comment);
$('total_uploaded').set('html', friendlyUnit(data.total_uploaded) +
" (" + friendlyUnit(data.total_uploaded_session) +
" _(this session)" + ")");
$('total_downloaded').set('html', friendlyUnit(data.total_downloaded) +
" (" + friendlyUnit(data.total_downloaded_session) +
" _(this session)" + ")");
$('total_wasted').set('html', data.total_wasted);
temp = data.up_limit;
$('up_limit').set('html', temp == -1 ? "∞" : temp);
temp = data.dl_limit;
$('dl_limit').set('html', temp == -1 ? "∞" : temp);
temp = friendlyDuration(status.active_time);
if (status.is_seed)
temp += " (" + "_(Seeded for %1)".replace("%1", status.seeding_time) + ")";
$('time_elapsed').set('html', temp);
temp = data.nb_connections + " (" + "_(%1 max)".replace("%1", status.nb_connections_limit) + ")";
$('nb_connections').set('html', temp);
$('share_ratio').set('html', data.share_ratio.toFixed(2));
}
else {
clearData();
}
clearTimeout(loadTorrentDataTimer);
loadTorrentDataTimer = loadTorrentData.delay(5000);
}
}).send();
}
var updateTorrentData = function() {
clearTimeout(loadTorrentDataTimer);
loadTorrentData();
}

130
src/webui/www/public/scripts/prop-trackers.js

@ -0,0 +1,130 @@
var trackersDynTable = new Class({
initialize: function() {},
setup: function(table) {
this.table = $(table);
this.rows = new Hash();
},
removeRow: function(url) {
if (this.rows.has(url)) {
var tr = this.rows.get(url);
tr.dispose();
this.rows.erase(url);
return true;
}
return false;
},
removeAllRows: function() {
this.rows.each(function(tr, url) {
this.removeRow(url);
}.bind(this));
},
updateRow: function(tr, row) {
var tds = tr.getElements('td');
for (var i = 0; i < row.length; i++) {
tds[i].set('html', row[i]);
}
return true;
},
insertRow: function(row) {
var url = row[0];
if (this.rows.has(url)) {
var tr = this.rows.get(url);
this.updateRow(tr, row);
return;
}
//this.removeRow(id);
var tr = new Element('tr');
this.rows.set(url, tr);
for (var i = 0; i < row.length; i++) {
var td = new Element('td');
td.set('html', row[i]);
td.injectInside(tr);
}
tr.injectInside(this.table);
},
});
var current_hash = "";
var loadTrackersDataTimer;
var loadTrackersData = function() {
if ($('prop_trackers').hasClass('invisible')) {
// Tab changed, don't do anything
return;
}
var new_hash = myTable.getCurrentTorrentHash();
if (new_hash == "") {
tTable.removeAllRows();
clearTimeout(loadTrackersDataTimer);
loadTrackersDataTimer = loadTrackersData.delay(10000);
return;
}
if (new_hash != current_hash) {
tTable.removeAllRows();
current_hash = new_hash;
}
var url = 'json/propertiesTrackers/' + current_hash;
var request = new Request.JSON({
url: url,
noCache: true,
method: 'get',
onFailure: function() {
$('error_div').set('html', '_(qBittorrent client is not reachable)');
clearTimeout(loadTrackersDataTimer);
loadTrackersDataTimer = loadTrackersData.delay(20000);
},
onSuccess: function(trackers) {
$('error_div').set('html', '');
if (trackers) {
// Update Trackers data
trackers.each(function(tracker) {
var row = new Array();
row.length = 4;
row[0] = tracker.url;
row[1] = tracker.status;
row[2] = tracker.num_peers;
row[3] = tracker.msg;
tTable.insertRow(row);
});
}
else {
tTable.removeAllRows();
}
clearTimeout(loadTrackersDataTimer);
loadTrackersDataTimer = loadTrackersData.delay(10000);
}
}).send();
}
var updateTrackersData = function() {
clearTimeout(loadTrackersDataTimer);
loadTrackersData();
}
tTable = new trackersDynTable();
tTable.setup($('trackersTable'));
// Add trackers code
$('addTrackersPlus').addEvent('click', function addTrackerDlg() {
if (current_hash.length == 0) return;
new MochaUI.Window({
id: 'trackersPage',
title: "_(Trackers addition dialog)",
loadMethod: 'iframe',
contentURL: 'addtrackers.html?hash=' + current_hash,
scrollbars: true,
resizable: false,
maximizable: false,
closable: true,
paddingVertical: 0,
paddingHorizontal: 0,
width: 500,
height: 250
});
});

23
src/webui/www/public/transferlist.html

@ -1,18 +1,17 @@
<table class="torrentTable" cellpadding="0" cellspacing="0"> <table class="torrentTable" cellpadding="0" cellspacing="0">
<thead> <thead>
<tr> <tr>
<th style="width: 0"></th> <th style="width: 16px"></th>
<th onClick="setSortedColumn('name');" style="cursor: pointer;">_(Name)</th> <th onClick="setSortedColumn('name');" style="min-width: 200px; cursor: pointer">_(Name)</th>
<th id='prioHeader' onClick="setSortedColumn('priority');" style="cursor: pointer;">#</th> <th id='prioHeader' onClick="setSortedColumn('priority');" style="width: 90px; cursor: pointer">#</th>
<th onClick="setSortedColumn('size');" style="cursor: pointer;">_(Size)</th> <th onClick="setSortedColumn('size');" style="width: 100px; cursor: pointer">_(Size)</th>
<th style="width: 90px;cursor: pointer;" onClick="setSortedColumn('progress');">_(Done)</th> <th onClick="setSortedColumn('progress');" style="width: 80px; cursor: pointer">_(Done)</th>
<th onClick="setSortedColumn('num_seeds');" style="cursor: pointer;">_(Seeds)</th> <th onClick="setSortedColumn('num_seeds');" style="width: 100px; cursor: pointer">_(Seeds)</th>
<th onClick="setSortedColumn('num_leechs');" style="cursor: pointer;">_(Peers)</th> <th onClick="setSortedColumn('num_leechs');" style="width: 100px; cursor: pointer">_(Peers)</th>
<th onClick="setSortedColumn('dlspeed');" style="cursor: pointer;">_(Down Speed)</th> <th onClick="setSortedColumn('dlspeed');" style="width: 100px; cursor: pointer">_(Down Speed)</th>
<th onClick="setSortedColumn('upspeed');" style="cursor: pointer;">_(Up Speed)</th> <th onClick="setSortedColumn('upspeed');" style="width: 100px; cursor: pointer">_(Up Speed)</th>
<th onClick="setSortedColumn('eta');" style="cursor: pointer;">_(ETA)</th> <th onClick="setSortedColumn('eta');" style="width: 100px; cursor: pointer">_(ETA)</th>
<th onClick="setSortedColumn('ratio');" style="cursor: pointer;">_(Ratio)</th> <th onClick="setSortedColumn('ratio');" style="width: 100px; cursor: pointer">_(Ratio)</th>
</tr> </tr>
</thead> </thead>
<tbody id="myTable"></tbody> <tbody id="myTable"></tbody>

Loading…
Cancel
Save