Improve web interface. (#27)

This commit is contained in:
Antonio Navarro Perez 2021-01-02 20:09:05 +01:00 committed by GitHub
parent 7c94e663d8
commit ae289e99dd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
74 changed files with 2538 additions and 561 deletions

View file

@ -189,7 +189,7 @@ Distributed under the GPL3 license. See `LICENSE` for more information.
[releases-url]: https://github.com/distribyted/distribyted/releases
[license-shield]: https://img.shields.io/github/license/distribyted/distribyted.svg?style=flat-square
[license-url]: https://github.com/distribyted/distribyted/blob/master/LICENSE
[product-screenshot]: docs/images/distribyted_demo.gif
[product-screenshot]: docs/images/distribyted.gif
[example-config]: https://github.com/distribyted/distribyted/blob/master/examples/conf_example.yaml
[coveralls-shield]: https://img.shields.io/coveralls/github/distribyted/distribyted?style=flat-square
[coveralls-url]: https://coveralls.io/github/distribyted/distribyted

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

144
assets/css/sleek.css.map Normal file

File diff suppressed because one or more lines are too long

6
assets/css/sleek.min.css vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

6
assets/css/styles.css Normal file

File diff suppressed because one or more lines are too long

View file

@ -1,15 +0,0 @@
/**
* Minified by jsDelivr using clean-css v4.2.3.
* Original file: /npm/toastify-js@1.9.3/src/toastify.css
*
* Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files
*/
/*!
* Toastify js 1.9.3
* https://github.com/apvarun/toastify-js
* @license MIT licensed
*
* Copyright (C) 2018 Varun A P
*/
.toastify{padding:12px 20px;color:#fff;display:inline-block;box-shadow:0 3px 6px -1px rgba(0,0,0,.12),0 10px 36px -4px rgba(77,96,232,.3);background:-webkit-linear-gradient(315deg,#73a5ff,#5477f5);background:linear-gradient(135deg,#73a5ff,#5477f5);position:fixed;opacity:0;transition:all .4s cubic-bezier(.215,.61,.355,1);border-radius:2px;cursor:pointer;text-decoration:none;max-width:calc(50% - 20px);z-index:2147483647}.toastify.on{opacity:1}.toast-close{opacity:.4;padding:0 5px}.toastify-right{right:15px}.toastify-left{left:15px}.toastify-top{top:-150px}.toastify-bottom{bottom:-150px}.toastify-rounded{border-radius:25px}.toastify-avatar{width:1.5em;height:1.5em;margin:-7px 5px;border-radius:2px}.toastify-center{margin-left:auto;margin-right:auto;left:0;right:0;max-width:fit-content;max-width:-moz-fit-content}@media only screen and (max-width:360px){.toastify-left,.toastify-right{margin-left:auto;margin-right:auto;left:0;right:0;max-width:fit-content}}
/*# sourceMappingURL=/sm/40f738e33ed5dbe7907b48c3be4b63e977eab6cb49c8df4f76f3edc3f1f2fb0d.map */

BIN
assets/img/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -1,8 +0,0 @@
ace.define("ace/ext/rtl",["require","exports","module","ace/editor","ace/config"],function(e,t,n){"use strict";function s(e,t){var n=t.getSelection().lead;t.session.$bidiHandler.isRtlLine(n.row)&&n.column===0&&(t.session.$bidiHandler.isMoveLeftOperation&&n.row>0?t.getSelection().moveCursorTo(n.row-1,t.session.getLine(n.row-1).length):t.getSelection().isEmpty()?n.column+=1:n.setPosition(n.row,n.column+1))}function o(e){e.editor.session.$bidiHandler.isMoveLeftOperation=/gotoleft|selectleft|backspace|removewordleft/.test(e.command.name)}function u(e,t){var n=t.session;n.$bidiHandler.currentRow=null;if(n.$bidiHandler.isRtlLine(e.start.row)&&e.action==="insert"&&e.lines.length>1)for(var r=e.start.row;r<e.end.row;r++)n.getLine(r+1).charAt(0)!==n.$bidiHandler.RLE&&(n.doc.$lines[r+1]=n.$bidiHandler.RLE+n.getLine(r+1))}function a(e,t){var n=t.session,r=n.$bidiHandler,i=t.$textLayer.$lines.cells,s=t.layerConfig.width-t.layerConfig.padding+"px";i.forEach(function(e){var t=e.element.style;r&&r.isRtlLine(e.row)?(t.direction="rtl",t.textAlign="right",t.width=s):(t.direction="",t.textAlign="",t.width="")})}function f(e){function n(e){var t=e.element.style;t.direction=t.textAlign=t.width=""}var t=e.$textLayer.$lines;t.cells.forEach(n),t.cellCache.forEach(n)}var r=[{name:"leftToRight",bindKey:{win:"Ctrl-Alt-Shift-L",mac:"Command-Alt-Shift-L"},exec:function(e){e.session.$bidiHandler.setRtlDirection(e,!1)},readOnly:!0},{name:"rightToLeft",bindKey:{win:"Ctrl-Alt-Shift-R",mac:"Command-Alt-Shift-R"},exec:function(e){e.session.$bidiHandler.setRtlDirection(e,!0)},readOnly:!0}],i=e("../editor").Editor;e("../config").defineOptions(i.prototype,"editor",{rtlText:{set:function(e){e?(this.on("change",u),this.on("changeSelection",s),this.renderer.on("afterRender",a),this.commands.on("exec",o),this.commands.addCommands(r)):(this.off("change",u),this.off("changeSelection",s),this.renderer.off("afterRender",a),this.commands.off("exec",o),this.commands.removeCommands(r),f(this.renderer)),this.renderer.updateFull()}},rtl:{set:function(e){this.session.$bidiHandler.$isRtl=e,e?(this.setOption("rtlText",!1),this.renderer.on("afterRender",a),this.session.$bidiHandler.seenBidi=!0):(this.renderer.off("afterRender",a),f(this.renderer)),this.renderer.updateFull()}}})}); (function() {
ace.require(["ace/ext/rtl"], function(m) {
if (typeof module == "object" && typeof exports == "object" && module) {
module.exports = m;
}
});
})();

File diff suppressed because one or more lines are too long

View file

@ -1,8 +0,0 @@
ace.define("ace/theme/solarized_dark",["require","exports","module","ace/lib/dom"],function(e,t,n){t.isDark=!0,t.cssClass="ace-solarized-dark",t.cssText=".ace-solarized-dark .ace_gutter {background: #01313f;color: #d0edf7}.ace-solarized-dark .ace_print-margin {width: 1px;background: #33555E}.ace-solarized-dark {background-color: #002B36;color: #93A1A1}.ace-solarized-dark .ace_entity.ace_other.ace_attribute-name,.ace-solarized-dark .ace_storage {color: #93A1A1}.ace-solarized-dark .ace_cursor,.ace-solarized-dark .ace_string.ace_regexp {color: #D30102}.ace-solarized-dark .ace_marker-layer .ace_active-line,.ace-solarized-dark .ace_marker-layer .ace_selection {background: rgba(255, 255, 255, 0.1)}.ace-solarized-dark.ace_multiselect .ace_selection.ace_start {box-shadow: 0 0 3px 0px #002B36;}.ace-solarized-dark .ace_marker-layer .ace_step {background: rgb(102, 82, 0)}.ace-solarized-dark .ace_marker-layer .ace_bracket {margin: -1px 0 0 -1px;border: 1px solid rgba(147, 161, 161, 0.50)}.ace-solarized-dark .ace_gutter-active-line {background-color: #0d3440}.ace-solarized-dark .ace_marker-layer .ace_selected-word {border: 1px solid #073642}.ace-solarized-dark .ace_invisible {color: rgba(147, 161, 161, 0.50)}.ace-solarized-dark .ace_keyword,.ace-solarized-dark .ace_meta,.ace-solarized-dark .ace_support.ace_class,.ace-solarized-dark .ace_support.ace_type {color: #859900}.ace-solarized-dark .ace_constant.ace_character,.ace-solarized-dark .ace_constant.ace_other {color: #CB4B16}.ace-solarized-dark .ace_constant.ace_language {color: #B58900}.ace-solarized-dark .ace_constant.ace_numeric {color: #D33682}.ace-solarized-dark .ace_fold {background-color: #268BD2;border-color: #93A1A1}.ace-solarized-dark .ace_entity.ace_name.ace_function,.ace-solarized-dark .ace_entity.ace_name.ace_tag,.ace-solarized-dark .ace_support.ace_function,.ace-solarized-dark .ace_variable,.ace-solarized-dark .ace_variable.ace_language {color: #268BD2}.ace-solarized-dark .ace_string {color: #2AA198}.ace-solarized-dark .ace_comment {font-style: italic;color: #657B83}.ace-solarized-dark .ace_indent-guide {background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAEklEQVQImWNg0Db1ZVCxc/sPAAd4AlUHlLenAAAAAElFTkSuQmCC) right repeat-y}";var r=e("../lib/dom");r.importCssString(t.cssText,t.cssClass)}); (function() {
ace.require(["ace/theme/solarized_dark"], function(m) {
if (typeof module == "object" && typeof exports == "object" && module) {
module.exports = m;
}
});
})();

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

45
assets/js/cache_chart.js Normal file
View file

@ -0,0 +1,45 @@
function CacheChart(id, name) {
var ctx = document.getElementById(id).getContext('2d');
this._chart = new Chart(ctx, {
type: "doughnut",
data: {
labels: ["used", "free"],
datasets: [
{
label: ["used", "free"],
data: [0, 0],
backgroundColor: ["#4c84ff", "#8061ef"],
borderWidth: 1
}
]
},
options: {
animation: false,
responsive: true,
maintainAspectRatio: false,
legend: {
display: false
},
cutoutPercentage: 75,
tooltips: {
titleFontColor: "#888",
bodyFontColor: "#555",
titleFontSize: 12,
bodyFontSize: 14,
backgroundColor: "rgba(256,256,256,0.95)",
displayColors: true,
borderColor: "rgba(220, 220, 220, 0.9)",
borderWidth: 2
}
}
});
this.update = function (used, free) {
this._chart.data.datasets.forEach((dataset) => {
dataset.data[0] = used;
dataset.data[1] = free;
});
this._chart.update();
};
}

176
assets/js/common.js Normal file
View file

@ -0,0 +1,176 @@
Handlebars.registerHelper('ibytes', function (bytesSec, timePassed) {
return Humanize.ibytes(bytesSec / timePassed, 1024);
});
Handlebars.registerHelper('bytes', function (bytes) {
return Humanize.bytes(bytes, 1024);
});
var Distribyted = Distribyted || {};
Distribyted.message = {
_toastr: function () {
toastr.options = {
closeButton: true,
debug: false,
newestOnTop: false,
progressBar: true,
positionClass: "toast-top-right",
preventDuplicates: false,
onclick: null,
showDuration: "300",
hideDuration: "1000",
timeOut: "5000",
extendedTimeOut: "1000",
showEasing: "swing",
hideEasing: "linear",
showMethod: "fadeIn",
hideMethod: "fadeOut"
};
return toastr;
},
error: function (message) {
this._toastr().error(message);
},
info: function (message) {
this._toastr().info(message);
}
}
$(document).ready(function () {
"use strict";
/*======== 1. SCROLLBAR SIDEBAR ========*/
var sidebarScrollbar = $(".sidebar-scrollbar");
if (sidebarScrollbar.length != 0) {
sidebarScrollbar.slimScroll({
opacity: 0,
height: "100%",
color: "#808080",
size: "5px",
touchScrollStep: 50
})
.mouseover(function () {
$(this)
.next(".slimScrollBar")
.css("opacity", 0.5);
});
}
/*======== 2. MOBILE OVERLAY ========*/
if ($(window).width() < 768) {
$(".sidebar-toggle").on("click", function () {
$("body").css("overflow", "hidden");
$('body').prepend('<div class="mobile-sticky-body-overlay"></div>')
});
$(document).on("click", '.mobile-sticky-body-overlay', function (e) {
$(this).remove();
$("#body").removeClass("sidebar-mobile-in").addClass("sidebar-mobile-out");
$("body").css("overflow", "auto");
});
}
/*======== 3. SIDEBAR MENU ========*/
var sidebar = $(".sidebar")
if (sidebar.length != 0) {
$(".sidebar .nav > .has-sub > a").click(function () {
$(this).parent().siblings().removeClass('expand')
$(this).parent().toggleClass('expand')
})
$(".sidebar .nav > .has-sub .has-sub > a").click(function () {
$(this).parent().toggleClass('expand')
})
}
/*======== 4. SIDEBAR TOGGLE FOR MOBILE ========*/
if ($(window).width() < 768) {
$(document).on("click", ".sidebar-toggle", function (e) {
e.preventDefault();
var min = "sidebar-mobile-in",
min_out = "sidebar-mobile-out",
body = "#body";
$(body).hasClass(min)
? $(body)
.removeClass(min)
.addClass(min_out)
: $(body)
.addClass(min)
.removeClass(min_out)
});
}
/*======== 5. SIDEBAR TOGGLE FOR VARIOUS SIDEBAR LAYOUT ========*/
var body = $("#body");
if ($(window).width() >= 768) {
if (typeof window.isMinified === "undefined") {
window.isMinified = false;
}
if (typeof window.isCollapsed === "undefined") {
window.isCollapsed = false;
}
$("#sidebar-toggler").on("click", function () {
if (
body.hasClass("sidebar-fixed-offcanvas") ||
body.hasClass("sidebar-static-offcanvas")
) {
$(this)
.addClass("sidebar-offcanvas-toggle")
.removeClass("sidebar-toggle");
if (window.isCollapsed === false) {
body.addClass("sidebar-collapse");
window.isCollapsed = true;
window.isMinified = false;
} else {
body.removeClass("sidebar-collapse");
body.addClass("sidebar-collapse-out");
setTimeout(function () {
body.removeClass("sidebar-collapse-out");
}, 300);
window.isCollapsed = false;
}
}
if (
body.hasClass("sidebar-fixed") ||
body.hasClass("sidebar-static")
) {
$(this)
.addClass("sidebar-toggle")
.removeClass("sidebar-offcanvas-toggle");
if (window.isMinified === false) {
body
.removeClass("sidebar-collapse sidebar-minified-out")
.addClass("sidebar-minified");
window.isMinified = true;
window.isCollapsed = false;
} else {
body.removeClass("sidebar-minified");
body.addClass("sidebar-minified-out");
window.isMinified = false;
}
}
});
}
if ($(window).width() >= 768 && $(window).width() < 992) {
if (
body.hasClass("sidebar-fixed") ||
body.hasClass("sidebar-static")
) {
body
.removeClass("sidebar-collapse sidebar-minified-out")
.addClass("sidebar-minified");
window.isMinified = true;
}
}
});

View file

@ -1,129 +1,157 @@
var langTools = ace.require("ace/ext/language_tools");
Distribyted.config = {
_editor: null,
_infoDiv: document.getElementById("distribyted-reload-info-text"),
_loadingInfoDom: document.getElementById("distribyted-reload-info-loading"),
_valid: function () {
if (this._editor == null) {
return false
}
var editor = ace.edit("editor");
editor.setTheme("ace/theme/solarized_dark");
editor.getSession().setMode("ace/mode/yaml");
editor.setShowPrintMargin(false);
editor.setOptions({
enableBasicAutocompletion: true,
enableSnippets: true,
enableLiveAutocompletion: false,
let getYamlCodeValidationErrors = (code) => {
var error = "";
try {
jsyaml.safeLoad(code);
} catch (e) {
error = e;
}
return error;
};
autoScrollEditorIntoView: true,
fontSize: "16px",
maxLines: 100,
wrap: true,
});
let code = this._editor.getValue();
let error = getYamlCodeValidationErrors(code);
if (error) {
this._editor.getSession().setAnnotations([
{
row: error.mark.line,
column: error.mark.column,
text: error.reason,
type: "error",
},
]);
function valid() {
let getYamlCodeValidationErrors = (code) => {
var error = "";
try {
jsyaml.safeLoad(code);
} catch (e) {
error = e;
return false;
} else {
this._editor.getSession().setAnnotations([]);
return true;
}
},
save: function () {
fetch("/api/config", {
method: "POST",
body: this._editor.getValue(),
})
.then(function (response) {
if (response.ok) {
Distribyted.message.info("Configuration saved");
} else {
Distribyted.message.error(
"Error saving configuration file. Response: " + response.status
);
}
})
.catch(function (error) {
Distribyted.message.error("Error saving configuration file: " + error.message);
});
},
reload: function () {
this.cleanInfo();
fetch("/api/reload", {
method: "POST",
})
.then(function (response) {
if (response.ok) {
return response.json();
} else {
Distribyted.config.showInfo("Error reloading server. Response: " + response.status, "ko");
}
})
.then(function (json) {
Distribyted.config.showInfo(json.message, "ok");
})
.catch(function (error) {
Distribyted.message.error("Error reloading server: " + error.message);
});
},
cleanInfo: function () {
this._loadingInfoDom.style.display = "block";
this._infoDiv.innerText = ""
},
showInfo: function (message, flag) {
const li = document.createElement("li");
li.innerText = message;
li.className = "list-group-item";
if (flag == "ok") {
li.className += " list-group-item-success";
} else if (flag == "ko") {
li.className += " list-group-item-danger";
}
if (flag) {
this._loadingInfoDom.style.display = "none";
}
this._infoDiv.appendChild(li);
},
loadView: function () {
this._editor = ace.edit("editor");
this._editor.getSession().setMode("ace/mode/yaml");
this._editor.setShowPrintMargin(false);
this._editor.setOptions({
enableBasicAutocompletion: true,
enableSnippets: true,
enableLiveAutocompletion: false,
autoScrollEditorIntoView: true,
fontSize: "16px",
maxLines: 100,
wrap: true,
});
this._editor.commands.addCommand({
name: "save",
bindKey: { win: "Ctrl-S", mac: "Command-S" },
exec: function (editor) {
if (Distribyted.config._valid()) {
Distribyted.config.save();
} else {
Distribyted.message.error("Check file format errors before saving");
}
},
readOnly: false,
});
this._editor.on("change", () => {
Distribyted.config._valid();
});
fetch("/api/config")
.then(function (response) {
if (response.ok) {
return response.text();
} else {
Distribyted.message.error(
"Error getting data from server. Response: " + response.status
);
}
})
.then(function (yaml) {
Distribyted.config._editor.setValue(yaml);
})
.catch(function (error) {
Distribyted.message.error("Error getting yaml from server: " + error.message);
});
var stream = new EventSource("/api/events");
stream.addEventListener("event", function (e) {
Distribyted.config.showInfo(e.data);
});
}
return error;
};
let code = editor.getValue();
let error = getYamlCodeValidationErrors(code);
if (error) {
editor.getSession().setAnnotations([
{
row: error.mark.line,
column: error.mark.column,
text: error.reason,
type: "error",
},
]);
return false;
} else {
editor.getSession().setAnnotations([]);
return true;
}
}
function bin2string(array) {
var result = "";
for (var i = 0; i < array.length; ++i) {
result += String.fromCharCode(array[i]);
}
return result;
}
function reload() {
fetch("/api/reload", {
method: "POST",
})
.then(function (response) {
if (response.ok) {
return response.json();
} else {
toastError(
"Error saving configuration file. Response: " + response.status
);
}
})
.then(function (json) {
toastInfo(json.message);
})
.catch(function (error) {
toastError("Error reloading server: " + error.message);
});
}
function save() {
fetch("/api/config", {
method: "POST",
body: editor.getValue(),
})
.then(function (response) {
if (response.ok) {
toastInfo("Configuration saved");
} else {
toastError(
"Error saving configuration file. Response: " + response.status
);
}
})
.catch(function (error) {
toastError("Error saving configuration file: " + error.message);
});
}
editor.commands.addCommand({
name: "save",
bindKey: { win: "Ctrl-S", mac: "Command-S" },
exec: function (editor) {
if (valid()) {
save();
} else {
toastError("Check file format errors before saving");
}
},
readOnly: false,
});
editor.on("change", () => {
valid();
});
fetch("/api/config")
.then(function (response) {
if (response.ok) {
return response.text();
} else {
toastError(
"Error getting data from server. Response: " + response.status
);
}
})
.then(function (yaml) {
editor.setValue(yaml);
})
.catch(function (error) {
toastError("Error getting yaml from server: " + error.message);
});

31
assets/js/dashboard.js Normal file
View file

@ -0,0 +1,31 @@
GeneralChart.init();
Distribyted.dashboard = {
_cacheChart: new CacheChart("main-cache-chart", "Cache disk"),
loadView: function () {
fetch('/api/status')
.then(function (response) {
if (response.ok) {
return response.json();
} else {
Distribyted.message.error('Error getting data from server. Response: ' + response.status)
}
}).then(function (stats) {
var download = stats.torrentStats.downloadedBytes / stats.torrentStats.timePassed;
var upload = stats.torrentStats.uploadedBytes / stats.torrentStats.timePassed;
GeneralChart.update(download, upload);
Distribyted.dashboard._cacheChart.update(stats.cacheFilled, stats.cacheCapacity - stats.cacheFilled);
document.getElementById("general-download-speed").innerText =
Humanize.ibytes(download, 1024) + "/s";
document.getElementById("general-upload-speed").innerText =
Humanize.ibytes(upload, 1024) + "/s";
})
.catch(function (error) {
Distribyted.message.error('Error getting status info: ' + error.message)
});
}
}

View file

@ -1,39 +0,0 @@
function FileChunks() {
this.update = function (chunks, totalPieces, hash) {
var dom = document.getElementById("file-chunks-" + hash);
dom.innerHTML = "";
chunks.forEach(chunk => {
dom.appendChild(getPrintedChunk(chunk.status, chunk.numPieces, totalPieces));
});
};
var pieceStatus = {
"H": { class: "bg-warning", tooltip: "checking pieces" },
"P": { class: "bg-info", tooltip: "" },
"C": { class: "bg-success", tooltip: "downloaded pieces" },
"W": { class: "bg-transparent" },
"?": { class: "bg-danger", tooltip: "erroed pieces" },
};
var getPrintedChunk = function (status, pieces, totalPieces) {
var percentage = totalPieces * pieces / 100;
var pcMeta = pieceStatus[status]
var pieceStatusClass = pcMeta.class;
var pieceStatusTip = pcMeta.tooltip;
var div = document.createElement("div");
div.className = "progress-bar " + pieceStatusClass;
div.setAttribute("role", "progressbar");
if (pieceStatusTip) {
div.setAttribute("data-toggle", "tooltip");
div.setAttribute("data-placement", "top");
div.setAttribute("title", pieceStatusTip);
}
div.style.cssText = "width: " + percentage + "%";
return div;
};
};

View file

@ -1,33 +0,0 @@
GeneralChart.init();
var cacheChart = new SingleBarChart("chart-cache", "Cache disk");
fetchData();
setInterval(function () {
fetchData();
}, 2000)
function fetchData() {
fetch('/api/status')
.then(function (response) {
if (response.ok) {
return response.json();
} else {
toastError('Error getting data from server. Response: ' + response.status)
}
}).then(function (stats) {
var download = stats.torrentStats.downloadedBytes / stats.torrentStats.timePassed;
var upload = stats.torrentStats.uploadedBytes / stats.torrentStats.timePassed;
GeneralChart.update(download, upload);
cacheChart.update(stats.cacheFilled, stats.cacheCapacity - stats.cacheFilled);
document.getElementById("down-speed-text").innerText =
Humanize.ibytes(download, 1024) + "/s";
document.getElementById("up-speed-text").innerText =
Humanize.ibytes(upload, 1024) + " /s";
})
.catch(function (error) {
toastError('Error getting status info: ' + error.message)
});
}

View file

@ -19,7 +19,9 @@ var GeneralChart = {
this._chart.update();
},
init: function () {
var ctx = document.getElementById('chart-general-network').getContext('2d');
var domElem = document.getElementById('chart-general-network')
domElem.height = 300;
var ctx = domElem.getContext('2d');
this._chart = new Chart(ctx, {
type: 'line',
data: {
@ -27,24 +29,50 @@ var GeneralChart = {
{
label: 'Download Speed',
fill: false,
backgroundColor: '#859900',
borderColor: '#859900',
borderWidth: 2,
backgroundColor: "transparent",
borderColor: "rgb(82, 136, 255)",
lineTension: 0.3,
pointRadius: 5,
pointBackgroundColor: "rgba(255,255,255,1)",
pointHoverBackgroundColor: "rgba(255,255,255,1)",
pointBorderWidth: 2,
pointHoverRadius: 8,
pointHoverBorderWidth: 1,
data: this._downloadData,
},
{
label: 'Upload Speed',
fill: false,
backgroundColor: '#839496',
borderColor: '#839496',
borderWidth: 2,
backgroundColor: "transparent",
borderColor: "rgb(82, 136, 180)",
lineTension: 0.3,
pointRadius: 5,
pointBackgroundColor: "rgba(255,255,255,1)",
pointHoverBackgroundColor: "rgba(255,255,255,1)",
pointBorderWidth: 2,
pointHoverRadius: 8,
pointHoverBorderWidth: 1,
data: this._uploadData,
},
]
},
options: {
legend: {
display: false
},
responsive: true,
maintainAspectRatio: false,
layout: {
padding: {
right: 10
}
},
title: {
text: 'Download and Upload speed'
},
@ -64,6 +92,8 @@ var GeneralChart = {
yAxes: [{
scaleLabel: {
display: false,
color: "#eee",
zeroLineColor: "#eee",
},
type: 'linear',
ticks: {
@ -74,7 +104,35 @@ var GeneralChart = {
},
}]
},
tooltips: {
callbacks: {
label: function (tooltipItem, data) {
var label = data.datasets[tooltipItem.datasetIndex].label || '';
if (label) {
label += ': ';
}
return Humanize.ibytes(tooltipItem.yLabel, 1024) + "/s";
}
},
responsive: true,
intersect: false,
enabled: true,
titleFontColor: "#888",
bodyFontColor: "#555",
titleFontSize: 12,
bodyFontSize: 18,
backgroundColor: "rgba(256,256,256,0.95)",
xPadding: 20,
yPadding: 10,
displayColors: false,
borderColor: "rgba(220, 220, 220, 0.9)",
borderWidth: 2,
caretSize: 10,
caretPadding: 15
}
}
});
}
}
}

View file

@ -1,62 +1,171 @@
var fileChunks = new FileChunks();
Handlebars.registerHelper("torrent_status", function (chunks, totalPieces) {
const pieceStatus = {
"H": { class: "bg-warning", tooltip: "checking pieces" },
"P": { class: "bg-info", tooltip: "" },
"C": { class: "bg-success", tooltip: "downloaded pieces" },
"W": { class: "bg-transparent" },
"?": { class: "bg-danger", tooltip: "erroed pieces" },
};
const chunksAsHTML = chunks.map(chunk => {
const percentage = totalPieces * chunk.numPieces / 100;
const pcMeta = pieceStatus[chunk.status]
const pieceStatusClass = pcMeta.class;
const pieceStatusTip = pcMeta.tooltip;
fetchData();
setInterval(function () {
fetchData();
}, 2000)
const div = document.createElement("div");
div.className = "progress-bar " + pieceStatusClass;
div.setAttribute("role", "progressbar");
const MB = 1048576;
if (pieceStatusTip) {
div.setAttribute("data-toggle", "tooltip");
div.setAttribute("data-placement", "top");
div.setAttribute("title", pieceStatusTip);
}
function fetchData() {
fetch('/api/routes')
.then(function (response) {
if (response.ok) {
return response.json();
} else {
toastError('Error getting data from server. Response: ' + response.status)
}
}).then(function (routes) {
routes.forEach(route => {
route.torrentStats.forEach(torrentStat => {
fileChunks.update(torrentStat.pieceChunks, torrentStat.totalPieces, torrentStat.hash);
div.style.cssText = "width: " + percentage + "%";
var download = torrentStat.downloadedBytes / torrentStat.timePassed;
var upload = torrentStat.uploadedBytes / torrentStat.timePassed;
var seeders = torrentStat.seeders;
var peers = torrentStat.peers;
var pieceSize = torrentStat.pieceSize;
return div.outerHTML;
});
document.getElementById("up-down-speed-text-" + torrentStat.hash).innerText =
Humanize.ibytes(download, 1024) + "/s down, " + Humanize.ibytes(upload, 1024) + "/s up";
document.getElementById("peers-seeders-" + torrentStat.hash).innerText =
peers + " peers, " + seeders + " seeders."
document.getElementById("piece-size-" + torrentStat.hash).innerText = "Piece size: " + Humanize.bytes(pieceSize, 1024)
return '<div class="progress mb-3">' + chunksAsHTML.join("\n"); + '</div>'
});
var className = "";
Handlebars.registerHelper("torrent_info", function (peers, seeders, pieceSize) {
const MB = 1048576;
if (seeders < 2) {
className = "text-danger";
} else if (seeders >= 2 && seeders < 4) {
className = "text-warning";
} else {
className = "text-success";
}
var messages = [];
document.getElementById("peers-seeders-" + torrentStat.hash).className = className;
var errorLevels = [];
const seedersMsg = "- Number of seeders is too low (" + seeders + ")."
if (seeders < 2) {
errorLevels[0] = 2;
messages.push(seedersMsg);
} else if (seeders >= 2 && seeders < 4) {
errorLevels[0] = 1;
messages.push(seedersMsg);
} else {
errorLevels[0] = 0;
}
if (pieceSize <= MB) {
className = "text-success";
} else if (pieceSize > MB && pieceSize < (MB * 4)) {
className = "text-warning";
} else {
className = "text-danger";
}
const pieceSizeMsg = "- Piece size is too big (" + Humanize.bytes(pieceSize, 1024) + "). Recommended size is 1MB or less."
if (pieceSize <= MB) {
errorLevels[1] = 0;
} else if (pieceSize > MB && pieceSize < (MB * 4)) {
errorLevels[1] = 1;
messages.push(pieceSizeMsg);
} else {
errorLevels[2] = 2;
messages.push(pieceSizeMsg);
}
document.getElementById("piece-size-" + torrentStat.hash).className = className;
});
const level = ["text-success", "text-warning", "text-danger"];
const icon = ["mdi-check", "mdi-alert", "mdi-alert-octagram"];
const div = document.createElement("div");
const i = document.createElement("i");
const errIndex = Math.max(...errorLevels);
i.className = "mdi " + icon[errIndex];
i.title = messages.join("\n");
const text = document.createTextNode(peers + "/" + seeders + " (" + Humanize.bytes(pieceSize, 1024) + " chunks) ");
div.className = level[errIndex];
div.appendChild(text);
div.appendChild(i);
return div.outerHTML;
});
Distribyted.routes = {
_template: null,
_getTemplate: function () {
if (this._template != null) {
return this._template
}
const tTemplate = fetch('/assets/templates/routes.html')
.then((response) => {
if (response.ok) {
return response.text();
} else {
Distribyted.message.error('Error getting data from server. Response: ' + response.status);
}
})
.then((t) => {
return Handlebars.compile(t);
})
.catch(error => {
Distribyted.message.error('Error getting routes template: ' + error.message);
});
})
.catch(function (error) {
toastError('Error getting status info: ' + error.message)
});
this._template = tTemplate;
return tTemplate;
},
_getRoutesJson: function () {
return fetch('/api/routes')
.then(function (response) {
if (response.ok) {
return response.json();
} else {
Distribyted.message.error('Error getting data from server. Response: ' + response.status)
}
}).then(function (routes) {
// routes.forEach(route => {
// route.torrentStats.forEach(torrentStat => {
// fileChunks.update(torrentStat.pieceChunks, torrentStat.totalPieces, torrentStat.hash);
// var download = torrentStat.downloadedBytes / torrentStat.timePassed;
// var upload = torrentStat.uploadedBytes / torrentStat.timePassed;
// var seeders = torrentStat.seeders;
// var peers = torrentStat.peers;
// var pieceSize = torrentStat.pieceSize;
// document.getElementById("up-down-speed-text-" + torrentStat.hash).innerText =
// Humanize.ibytes(download, 1024) + "/s down, " + Humanize.ibytes(upload, 1024) + "/s up";
// document.getElementById("peers-seeders-" + torrentStat.hash).innerText =
// peers + " peers, " + seeders + " seeders."
// document.getElementById("piece-size-" + torrentStat.hash).innerText = "Piece size: " + Humanize.bytes(pieceSize, 1024)
// var className = "";
// if (seeders < 2) {
// className = "text-danger";
// } else if (seeders >= 2 && seeders < 4) {
// className = "text-warning";
// } else {
// className = "text-success";
// }
// document.getElementById("peers-seeders-" + torrentStat.hash).className = className;
// if (pieceSize <= MB) {
// className = "text-success";
// } else if (pieceSize > MB && pieceSize < (MB * 4)) {
// className = "text-warning";
// } else {
// className = "text-danger";
// }
// document.getElementById("piece-size-" + torrentStat.hash).className = className;
// });
// });
return routes;
})
.catch(function (error) {
Distribyted.message.error('Error getting status info: ' + error.message)
});
},
loadView: function () {
this._getTemplate()
.then(t =>
this._getRoutesJson().then(routes => {
document.getElementById('template_target').innerHTML = t(routes);
})
);
}
}

View file

@ -1,48 +0,0 @@
function SingleBarChart(id, name) {
var ctx = document.getElementById(id).getContext('2d');
this._used = [];
this._free = [];
this._chart = new Chart(ctx, {
type: 'horizontalBar',
data: {
labels:[name],
datasets: [{
backgroundColor: "#839496",
label: "used",
data: this._used,
},
{
backgroundColor: "#859900",
label: "free",
data: this._free,
}],
},
options: {
legend: {
display: false,
},
animation: false,
scales: {
xAxes: [{
stacked: true
}],
yAxes: [{
stacked: true,
display: true,
ticks: {
beginAtZero: true,
}
}]
}
},
});
this.update = function (used, free) {
this._used.shift();
this._free.shift();
this._used.push(used);
this._free.push(free);
this._chart.update();
};
}

View file

@ -1,15 +0,0 @@
/**
* Minified by jsDelivr using Terser v5.3.0.
* Original file: /npm/toastify-js@1.9.3/src/toastify.js
*
* Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files
*/
/*!
* Toastify js 1.9.3
* https://github.com/apvarun/toastify-js
* @license MIT licensed
*
* Copyright (C) 2018 Varun A P
*/
!function(t,o){"object"==typeof module&&module.exports?module.exports=o():t.Toastify=o()}(this,(function(t){var o=function(t){return new o.lib.init(t)};function i(t,o){return o.offset[t]?isNaN(o.offset[t])?o.offset[t]:o.offset[t]+"px":"0px"}function s(t,o){return!(!t||"string"!=typeof o)&&!!(t.className&&t.className.trim().split(/\s+/gi).indexOf(o)>-1)}return o.lib=o.prototype={toastify:"1.9.3",constructor:o,init:function(t){return t||(t={}),this.options={},this.toastElement=null,this.options.text=t.text||"Hi there!",this.options.node=t.node,this.options.duration=0===t.duration?0:t.duration||3e3,this.options.selector=t.selector,this.options.callback=t.callback||function(){},this.options.destination=t.destination,this.options.newWindow=t.newWindow||!1,this.options.close=t.close||!1,this.options.gravity="bottom"===t.gravity?"toastify-bottom":"toastify-top",this.options.positionLeft=t.positionLeft||!1,this.options.position=t.position||"",this.options.backgroundColor=t.backgroundColor,this.options.avatar=t.avatar||"",this.options.className=t.className||"",this.options.stopOnFocus=void 0===t.stopOnFocus||t.stopOnFocus,this.options.onClick=t.onClick,this.options.offset=t.offset||{x:0,y:0},this},buildToast:function(){if(!this.options)throw"Toastify is not initialized";var t=document.createElement("div");if(t.className="toastify on "+this.options.className,this.options.position?t.className+=" toastify-"+this.options.position:!0===this.options.positionLeft?(t.className+=" toastify-left",console.warn("Property `positionLeft` will be depreciated in further versions. Please use `position` instead.")):t.className+=" toastify-right",t.className+=" "+this.options.gravity,this.options.backgroundColor&&(t.style.background=this.options.backgroundColor),this.options.node&&this.options.node.nodeType===Node.ELEMENT_NODE)t.appendChild(this.options.node);else if(t.innerHTML=this.options.text,""!==this.options.avatar){var o=document.createElement("img");o.src=this.options.avatar,o.className="toastify-avatar","left"==this.options.position||!0===this.options.positionLeft?t.appendChild(o):t.insertAdjacentElement("afterbegin",o)}if(!0===this.options.close){var s=document.createElement("span");s.innerHTML="&#10006;",s.className="toast-close",s.addEventListener("click",function(t){t.stopPropagation(),this.removeElement(this.toastElement),window.clearTimeout(this.toastElement.timeOutValue)}.bind(this));var n=window.innerWidth>0?window.innerWidth:screen.width;("left"==this.options.position||!0===this.options.positionLeft)&&n>360?t.insertAdjacentElement("afterbegin",s):t.appendChild(s)}if(this.options.stopOnFocus&&this.options.duration>0){var e=this;t.addEventListener("mouseover",(function(o){window.clearTimeout(t.timeOutValue)})),t.addEventListener("mouseleave",(function(){t.timeOutValue=window.setTimeout((function(){e.removeElement(t)}),e.options.duration)}))}if(void 0!==this.options.destination&&t.addEventListener("click",function(t){t.stopPropagation(),!0===this.options.newWindow?window.open(this.options.destination,"_blank"):window.location=this.options.destination}.bind(this)),"function"==typeof this.options.onClick&&void 0===this.options.destination&&t.addEventListener("click",function(t){t.stopPropagation(),this.options.onClick()}.bind(this)),"object"==typeof this.options.offset){var a=i("x",this.options),p=i("y",this.options),r="left"==this.options.position?a:"-"+a,l="toastify-top"==this.options.gravity?p:"-"+p;t.style.transform="translate("+r+","+l+")"}return t},showToast:function(){var t;if(this.toastElement=this.buildToast(),!(t=void 0===this.options.selector?document.body:document.getElementById(this.options.selector)))throw"Root element is not defined";return t.insertBefore(this.toastElement,t.firstChild),o.reposition(),this.options.duration>0&&(this.toastElement.timeOutValue=window.setTimeout(function(){this.removeElement(this.toastElement)}.bind(this),this.options.duration)),this},hideToast:function(){this.toastElement.timeOutValue&&clearTimeout(this.toastElement.timeOutValue),this.removeElement(this.toastElement)},removeElement:function(t){t.className=t.className.replace(" on",""),window.setTimeout(function(){this.options.node&&this.options.node.parentNode&&this.options.node.parentNode.removeChild(this.options.node),t.parentNode&&t.parentNode.removeChild(t),this.options.callback.call(t),o.reposition()}.bind(this),400)}},o.reposition=function(){for(var t,o={top:15,bottom:15},i={top:15,bottom:15},n={top:15,bottom:15},e=document.getElementsByClassName("toastify"),a=0;a<e.length;a++){t=!0===s(e[a],"toastify-top")?"toastify-top":"toastify-bottom";var p=e[a].offsetHeight;t=t.substr(9,t.length-1);(window.innerWidth>0?window.innerWidth:screen.width)<=360?(e[a].style[t]=n[t]+"px",n[t]+=p+15):!0===s(e[a],"toastify-left")?(e[a].style[t]=o[t]+"px",o[t]+=p+15):(e[a].style[t]=i[t]+"px",i[t]+=p+15)}return this},o.lib.init.prototype=o.lib,o}));
//# sourceMappingURL=/sm/b2692762f762f02a544ce708819ce22427514c155203d0627f14174806ee9f38.map

View file

@ -1,29 +0,0 @@
function toast(msg, color, time) {
Toastify({
text: msg,
duration: time,
newWindow: true,
close: true,
gravity: "top",
position: "right",
backgroundColor: color,
stopOnFocus: true,
}).showToast();
}
function toastError(msg) {
toast(msg, "red", 5000);
}
function toastInfo(msg) {
toast(msg, "green", 5000);
}
function toastMessage(msg) {
toast(msg, "grey", 10000);
}
var stream = new EventSource("/api/events");
stream.addEventListener("event", function (e) {
toastMessage(e.data);
});

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1311
assets/plugins/jquery/jquery-ui-1.12.1.css vendored Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

4
assets/plugins/jquery/jquery.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,16 @@
/*! Copyright (c) 2011 Piotr Rochala (http://rocha.la)
* Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
* and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
*
* Version: 1.3.8
*
*/
(function(e){e.fn.extend({slimScroll:function(f){var a=e.extend({width:"auto",height:"250px",size:"7px",color:"#000",position:"right",distance:"1px",start:"top",opacity:.4,alwaysVisible:!1,disableFadeOut:!1,railVisible:!1,railColor:"#333",railOpacity:.2,railDraggable:!0,railClass:"slimScrollRail",barClass:"slimScrollBar",wrapperClass:"slimScrollDiv",allowPageScroll:!1,wheelStep:20,touchScrollStep:200,borderRadius:"7px",railBorderRadius:"7px"},f);this.each(function(){function v(d){if(r){d=d||window.event;
var c=0;d.wheelDelta&&(c=-d.wheelDelta/120);d.detail&&(c=d.detail/3);e(d.target||d.srcTarget||d.srcElement).closest("."+a.wrapperClass).is(b.parent())&&n(c,!0);d.preventDefault&&!k&&d.preventDefault();k||(d.returnValue=!1)}}function n(d,g,e){k=!1;var f=b.outerHeight()-c.outerHeight();g&&(g=parseInt(c.css("top"))+d*parseInt(a.wheelStep)/100*c.outerHeight(),g=Math.min(Math.max(g,0),f),g=0<d?Math.ceil(g):Math.floor(g),c.css({top:g+"px"}));l=parseInt(c.css("top"))/(b.outerHeight()-c.outerHeight());g=
l*(b[0].scrollHeight-b.outerHeight());e&&(g=d,d=g/b[0].scrollHeight*b.outerHeight(),d=Math.min(Math.max(d,0),f),c.css({top:d+"px"}));b.scrollTop(g);b.trigger("slimscrolling",~~g);w();p()}function x(){u=Math.max(b.outerHeight()/b[0].scrollHeight*b.outerHeight(),30);c.css({height:u+"px"});var a=u==b.outerHeight()?"none":"block";c.css({display:a})}function w(){x();clearTimeout(B);l==~~l?(k=a.allowPageScroll,C!=l&&b.trigger("slimscroll",0==~~l?"top":"bottom")):k=!1;C=l;u>=b.outerHeight()?k=!0:(c.stop(!0,
!0).fadeIn("fast"),a.railVisible&&m.stop(!0,!0).fadeIn("fast"))}function p(){a.alwaysVisible||(B=setTimeout(function(){a.disableFadeOut&&r||y||z||(c.fadeOut("slow"),m.fadeOut("slow"))},1E3))}var r,y,z,B,A,u,l,C,k=!1,b=e(this);if(b.parent().hasClass(a.wrapperClass)){var q=b.scrollTop(),c=b.siblings("."+a.barClass),m=b.siblings("."+a.railClass);x();if(e.isPlainObject(f)){if("height"in f&&"auto"==f.height){b.parent().css("height","auto");b.css("height","auto");var h=b.parent().parent().height();b.parent().css("height",
h);b.css("height",h)}else"height"in f&&(h=f.height,b.parent().css("height",h),b.css("height",h));if("scrollTo"in f)q=parseInt(a.scrollTo);else if("scrollBy"in f)q+=parseInt(a.scrollBy);else if("destroy"in f){c.remove();m.remove();b.unwrap();return}n(q,!1,!0)}}else if(!(e.isPlainObject(f)&&"destroy"in f)){a.height="auto"==a.height?b.parent().height():a.height;q=e("<div></div>").addClass(a.wrapperClass).css({position:"relative",overflow:"hidden",width:a.width,height:a.height});b.css({overflow:"hidden",
width:a.width,height:a.height});var m=e("<div></div>").addClass(a.railClass).css({width:a.size,height:"100%",position:"absolute",top:0,display:a.alwaysVisible&&a.railVisible?"block":"none","border-radius":a.railBorderRadius,background:a.railColor,opacity:a.railOpacity,zIndex:90}),c=e("<div></div>").addClass(a.barClass).css({background:a.color,width:a.size,position:"absolute",top:0,opacity:a.opacity,display:a.alwaysVisible?"block":"none","border-radius":a.borderRadius,BorderRadius:a.borderRadius,MozBorderRadius:a.borderRadius,
WebkitBorderRadius:a.borderRadius,zIndex:99}),h="right"==a.position?{right:a.distance}:{left:a.distance};m.css(h);c.css(h);b.wrap(q);b.parent().append(c);b.parent().append(m);a.railDraggable&&c.bind("mousedown",function(a){var b=e(document);z=!0;t=parseFloat(c.css("top"));pageY=a.pageY;b.bind("mousemove.slimscroll",function(a){currTop=t+a.pageY-pageY;c.css("top",currTop);n(0,c.position().top,!1)});b.bind("mouseup.slimscroll",function(a){z=!1;p();b.unbind(".slimscroll")});return!1}).bind("selectstart.slimscroll",
function(a){a.stopPropagation();a.preventDefault();return!1});m.hover(function(){w()},function(){p()});c.hover(function(){y=!0},function(){y=!1});b.hover(function(){r=!0;w();p()},function(){r=!1;p()});b.bind("touchstart",function(a,b){a.originalEvent.touches.length&&(A=a.originalEvent.touches[0].pageY)});b.bind("touchmove",function(b){k||b.originalEvent.preventDefault();b.originalEvent.touches.length&&(n((A-b.originalEvent.touches[0].pageY)/a.touchScrollStep,!0),A=b.originalEvent.touches[0].pageY)});
x();"bottom"===a.start?(c.css({top:b.outerHeight()-c.outerHeight()}),n(0,!0)):"top"!==a.start&&(n(e(a.start).position().top,null,!0),a.alwaysVisible||c.hide());window.addEventListener?(this.addEventListener("DOMMouseScroll",v,!1),this.addEventListener("mousewheel",v,!1)):document.attachEvent("onmousewheel",v)}});return this}});e.fn.extend({slimscroll:e.fn.slimScroll})})(jQuery);

File diff suppressed because one or more lines are too long

1
assets/plugins/toastr/toastr.min.css vendored Normal file

File diff suppressed because one or more lines are too long

2
assets/plugins/toastr/toastr.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,39 @@
{{#.}}
<div class="row">
<div class="col-12">
<!-- Recent Order Table -->
<div class="card card-table-border-none">
<div class="card-header justify-content-between">
<h2>{{name}}</h2>
</div>
<div class="card-body pt-0 pb-5">
<table class="table card-table table-responsive table-responsive-large" style="width:100%">
<thead>
<tr>
<th style="width: 35%">Name</th>
<th style="width: 15%"><i class="mdi mdi-arrow-down"></i> / <i class="mdi mdi-arrow-up"></i>
</th>
<th style="width: 15%">Peers/Seeders</th>
<th style="width: 35%" class="d-none d-lg-table-cell">Status</th>
</tr>
</thead>
<tbody>
{{#torrentStats}}
<tr>
<td>{{name}}</td>
<td>{{ibytes downloadedBytes timePassed}} / {{ibytes
uploadedBytes timePassed}}</td>
<td>{{{torrent_info peers seeders pieceSize}}}</td>
<td class="d-none d-lg-table-cell">
{{{torrent_status pieceChunks totalPieces}}}
</td>
</tr>
{{/torrentStats}}
</tbody>
</table>
</div>
</div>
</div>
</div>
{{/.}}

View file

@ -127,7 +127,11 @@ func load(configPath string, port int, fuseAllowOther bool) error {
tryClose(c, mountService)
}()
return http.New(fc, ss, ch, port)
err = http.New(fc, ss, ch, port)
logrus.WithError(err).Error("error initializing HTTP server")
return err
}
func tryClose(c *t.Client, mountService *fuse.Handler) {

BIN
docs/images/distribyted.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 103 KiB

View file

@ -5,6 +5,7 @@ import (
"io"
"io/ioutil"
"net/http"
"sort"
"github.com/ajnavarro/distribyted/config"
"github.com/ajnavarro/distribyted/stats"
@ -26,8 +27,9 @@ var apiStatusHandler = func(fc *filecache.Cache, ss *stats.Torrent) gin.HandlerF
var apiRoutesHandler = func(ss *stats.Torrent) gin.HandlerFunc {
return func(ctx *gin.Context) {
stats := ss.RoutesStats()
ctx.JSON(http.StatusOK, stats)
s := ss.RoutesStats()
sort.Sort(stats.ByName(s))
ctx.JSON(http.StatusOK, s)
}
}

View file

@ -48,6 +48,12 @@ type RouteStats struct {
TorrentStats []*TorrentStats `json:"torrentStats"`
}
type ByName []*RouteStats
func (a ByName) Len() int { return len(a) }
func (a ByName) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a ByName) Less(i, j int) bool { return a[i].Name < a[j].Name }
type stats struct {
totalDownloadBytes int64
downloadBytes int64

View file

@ -1,10 +0,0 @@
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"
integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj"
crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js"
integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo"
crossorigin="anonymous"></script>
<script src="assets/js/bootstrap.bundle.min.js"></script>
<script src="assets/js/toastify-js.min.js"></script>
<script src="assets/js/toasts.js"></script>

View file

@ -1,10 +0,0 @@
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="assets/css/toastify.min.css">
<link rel="stylesheet" href="assets/css/bootstrap.min.css">
<link rel="stylesheet" href="assets/css/solarized-dark-theme.min.css">
<title>{{.Title}}</title>

View file

@ -1,37 +1,105 @@
<!DOCTYPE html>
<html>
<head>
{{template "common_header.html"}}
{{template "header.html" "Configuration"}}
<style>
#editor {
/** Setting height is also important, otherwise editor wont showup**/
height: 300px;
height: 400px;
}
</style>
</head>
<body>
{{template "navbar.html"}}
<div class="container">
<div class="panel panel-default">
<div class="panel-heading">
<button type="button" class="btn btn-light" onclick="javascript:save();">Save</button>
<button type="button" class="btn btn-light" onclick="javascript:reload();">Reload server</button>
</div>
<div class="panel-body">
<div id="editor"></div>
</div>
</div>
</div>
{{template "common_footer.html"}}
<body class="header-fixed sidebar-fixed sidebar-dark header-light" id="body">
<div class="wrapper">
{{template "navbar.html" "config"}}
<div class="page-wrapper">
<!-- Header -->
<header class="main-header " id="header">
<nav class="navbar navbar-static-top navbar-expand-lg">
<!-- Sidebar toggle button -->
<button id="sidebar-toggler" class="sidebar-toggle">
<span class="sr-only">Toggle navigation</span>
</button>
</nav>
</header>
<div class="content-wrapper">
<div class="content">
<div class="row">
<!-- Large Modal -->
<div class="modal fade" id="distribyted-reload-info-modal" tabindex="-1" role="dialog"
aria-labelledby="distribyted-reload-info-modal" aria-hidden="true">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="distribyted-reload-info-modal-title">Reload status</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<ul id="distribyted-reload-info-text" class="list-group"></ul>
<div class="card-body d-flex align-items-center justify-content-center">
<div class="sk-wave" id="distribyted-reload-info-loading"
style="display:none">
<div class="rect1"></div>
<div class="rect2"></div>
<div class="rect3"></div>
<div class="rect4"></div>
<div class="rect5"></div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-danger btn-pill"
data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<div class="col-lg-12">
<div class="card card-default">
<div class="card-header card-header-border-bottom">
<h2>Yaml Editor</h2>
</div>
<div class="card-body">
<form>
<div class="form-group">
<div id="editor"></div>
</div>
<div class="form-footer pt-4 pt-5 mt-4 border-top">
<button type="button" class="btn btn-primary btn-default"
onclick="javascript:Distribyted.config.save()">Save</button>
<button type="button" class="btn btn-secondary btn-default"
data-toggle="modal" data-target="#distribyted-reload-info-modal"
onclick="javascript:Distribyted.config.reload()">Reload server</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<footer class="footer mt-auto">
<div class="copyright bg-white">
</div>
</footer>
</div>
{{template "footer.html"}}
<script src="assets/plugins/ace/ace.js" type="text/javascript" charset="utf-8"></script>
<script src="assets/plugins/ace/ext-language_tools.js" type="text/javascript" charset="utf-8"></script>
<script src="assets/plugins/js-yaml/js-yaml.min.js" type="text/javascript" charset="utf-8"></script>
<script src="assets/js/config.js" type="text/javascript" charset="utf-8"></script>
<script>
Distribyted.config.loadView();
</script>
<script src="assets/js/js-yaml.min.js" type="text/javascript" charset="utf-8"></script>
<script src="assets/js/ace/ace.js" type="text/javascript" charset="utf-8"></script>
<script src="assets/js/ace/ext-language_tools.js" type="text/javascript" charset="utf-8"></script>
<script src="assets/js/config.js" type="text/javascript" charset="utf-8"></script>
</body>
</html>

11
templates/footer.html Normal file
View file

@ -0,0 +1,11 @@
<script src="assets/plugins/jquery/jquery.min.js"></script>
<script src="assets/plugins/slimscrollbar/jquery.slimscroll.min.js"></script>
<script src="assets/plugins/charts/Chart.min.js"></script>
<script src="assets/plugins/bootstrap/js/bootstrap.bundle.min.js"></script>
<script src="assets/plugins/toastr/toastr.min.js"></script>
<script src="assets/plugins/handlebars/handlebars.min.js"></script>
<script src="assets/js/humanize.js"></script>
<script src="assets/js/common.js"></script>

14
templates/header.html Normal file
View file

@ -0,0 +1,14 @@
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Distribyted - {{.}}</title>
<!-- GOOGLE FONTS -->
<link href="https://fonts.googleapis.com/css?family=Montserrat:400,500|Poppins:400,500,600,700|Roboto:400,500"
rel="stylesheet" />
<link href="https://cdn.materialdesignicons.com/4.4.95/css/materialdesignicons.min.css" rel="stylesheet" />
<!-- SLEEK CSS -->
<link id="sleek-css" rel="stylesheet" href="assets/css/sleek.min.css" />
<link href="assets/plugins/toastr/toastr.min.css" rel="stylesheet" />
<!-- FAVICON -->
<link href="assets/img/favicon.png" rel="shortcut icon" />

View file

@ -1,58 +1,106 @@
<!DOCTYPE html>
<html>
<head>
{{template "common_header.html"}}
{{template "header.html" "Dashboard"}}
</head>
<body>
{{template "navbar.html"}}
<div class="container">
<div class="row">
<div class="col">
<canvas id="chart-general-network" height="150"></canvas>
</div>
</div>
<div class="row">
<div class="col-sm">
<canvas id="chart-cache" height="20"></canvas>
</div>
</div>
<div class="row">
<div class="col-sm">
<h3>
<svg class="bi bi-arrow-down" width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor"
xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd"
d="M4.646 9.646a.5.5 0 01.708 0L8 12.293l2.646-2.647a.5.5 0 01.708.708l-3 3a.5.5 0 01-.708 0l-3-3a.5.5 0 010-.708z"
clip-rule="evenodd" />
<path fill-rule="evenodd" d="M8 2.5a.5.5 0 01.5.5v9a.5.5 0 01-1 0V3a.5.5 0 01.5-.5z"
clip-rule="evenodd" />
</svg>
<span id="down-speed-text"></span>
</h3>
</div>
<div class="col-sm">
<h3>
<svg class="bi bi-arrow-up" width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor"
xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" d="M8 3.5a.5.5 0 01.5.5v9a.5.5 0 01-1 0V4a.5.5 0 01.5-.5z"
clip-rule="evenodd" />
<path fill-rule="evenodd"
d="M7.646 2.646a.5.5 0 01.708 0l3 3a.5.5 0 01-.708.708L8 3.707 5.354 6.354a.5.5 0 11-.708-.708l3-3z"
clip-rule="evenodd" />
</svg>
<span id="up-speed-text"></span>
</h3>
<body class="header-fixed sidebar-fixed sidebar-dark header-light" id="body">
<div class="wrapper">
{{template "navbar.html" "dashboard"}}
<div class="page-wrapper">
<!-- Header -->
<header class="main-header " id="header">
<nav class="navbar navbar-static-top navbar-expand-lg">
<!-- Sidebar toggle button -->
<button id="sidebar-toggler" class="sidebar-toggle">
<span class="sr-only">Toggle navigation</span>
</button>
</nav>
</header>
<div class="content-wrapper">
<div class="content">
<div class="row">
<div class="col-xl-12 col-md-12">
<!-- Sales Graph -->
<div class="card card-default" data-scroll-height="675">
<div class="card-header">
<h2>Download/Upload speed</h2>
</div>
<div class="card-body">
<canvas id="chart-general-network" class="chartjs"></canvas>
</div>
<div class="card-footer d-flex flex-wrap bg-white p-0">
<div class="col-6 px-0">
<div class="text-center p-4">
<h4 id="general-download-speed">...</h4>
<p class="mt-2">Download Speed</p>
</div>
</div>
<div class="col-6 px-0">
<div class="text-center p-4 border-left">
<h4 id="general-upload-speed">...</h4>
<p class="mt-2">Upload Speed</p>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-xl-4 col-md-12">
<!-- Doughnut Chart -->
<div class="card card-default" data-scroll-height="675">
<div class="card-header justify-content-center">
<h2>Main cache</h2>
</div>
<div class="card-body">
<canvas id="main-cache-chart"></canvas>
</div>
<div class="card-footer d-flex flex-wrap bg-white p-0">
<div class="col-6">
<div class="py-4 px-4">
<ul class="d-flex flex-column justify-content-between">
<li class="mb-2"><i class="mdi mdi-checkbox-blank-circle-outline mr-2"
style="color: #4c84ff"></i>Used</li>
</ul>
</div>
</div>
<div class="col-6 border-left">
<div class="py-4 px-4 ">
<ul class="d-flex flex-column justify-content-between">
<li class="mb-2"><i class="mdi mdi-checkbox-blank-circle-outline mr-2"
style="color: #8061ef"></i>Free</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<footer class="footer mt-auto">
<div class="copyright bg-white">
</div>
</footer>
</div>
</div>
{{template "common_footer.html"}}
<script src="assets/js/Chart.bundle.min.js" type="text/javascript" charset="utf-8"></script>
<script src="assets/js/humanize.js" type="text/javascript" charset="utf-8"></script>
<script src="assets/js/general_chart.js" type="text/javascript" charset="utf-8"></script>
<script src="assets/js/single_bar_chart.js" type="text/javascript" charset="utf-8"></script>
<script src="assets/js/general.js" type="text/javascript" charset="utf-8"></script>
{{template "footer.html"}}
<script src="assets/js/general_chart.js"></script>
<script src="assets/js/cache_chart.js"></script>
<script src="assets/js/dashboard.js"></script>
<script>
Distribyted.dashboard.loadView();
setInterval(function () {
Distribyted.dashboard.loadView();
}, 2000)
</script>
</body>
</html>

View file

@ -1,20 +1,72 @@
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<a class="navbar-brand" href="#">Distribyted</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav"
aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" href="/">General</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/routes">Routes</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/config">Config</a>
</li>
</ul>
<!--
====================================
——— LEFT SIDEBAR WITH FOOTER
=====================================
-->
<aside class="left-sidebar bg-sidebar">
<div id="sidebar" class="sidebar sidebar-with-footer">
<!-- Aplication Brand -->
<div class="app-brand">
<a href="/" title="Distribyted">
<img src="/assets/img/favicon.png" />
<span class="brand-name text-truncate">Distribyted</span>
</a>
</div>
</nav>
<!-- begin sidebar scrollbar -->
<div class="sidebar-scrollbar">
<!-- sidebar menu -->
<ul class="nav sidebar-inner" id="sidebar-menu">
{{if eq . "dashboard"}}
<li class="active">
{{else}}
<li>
{{end}}
<a class="sidenav-item-link" href="/">
<i class="mdi mdi-view-dashboard-outline"></i>
<span class="nav-text">Dashboard</span>
</a>
</li>
{{if eq . "routes"}}
<li class="active">
{{else}}
<li>
{{end}}
<a class="sidenav-item-link" href="/routes">
<i class="mdi mdi-folder-multiple-outline"></i>
<span class="nav-text">Routes</span>
</a>
</li>
{{if eq . "config"}}
<li class="active">
{{else}}
<li>
{{end}}
<a class="sidenav-item-link" href="/config">
<i class="mdi mdi-book-open-page-variant"></i>
<span class="nav-text">Yaml Config</span>
</a>
</li>
</ul>
</div>
<!-- <div class="sidebar-footer">
<hr class="separator mb-0" />
<div class="sidebar-footer-content">
<h6 class="text-uppercase">
Main cache <span class="float-right">40%</span>
</h6>
<div class="progress progress-xs">
<div class="progress-bar active" style="width: 40%;" role="progressbar"></div>
</div>
<h6 class="text-uppercase">
/path/pepe cache <span class="float-right">65%</span>
</h6>
<div class="progress progress-xs">
<div class="progress-bar active" style="width: 65%;" role="progressbar"></div>
</div>
</div>
</div>
</div> -->
</aside>

View file

@ -1,53 +1,44 @@
<!DOCTYPE html>
<html>
<head>
{{template "common_header.html"}}
{{template "header.html" "Routes"}}
</head>
<body>
{{template "navbar.html"}}
<div class="container">
{{if not .}}
<div class="alert alert-warning" role="alert">
No routes found.
</div>
{{end}}
<body class="header-fixed sidebar-fixed sidebar-dark header-light" id="body">
<div class="wrapper">
{{template "navbar.html" "routes"}}
{{range .}}
<div class="card shadow">
<div class="card-body">
<h5 class="card-title">{{.Name}}</h5>
{{range .TorrentStats}}
<div class="card shadow">
<div class="card-body">
<div class="row">
<div class="col">
<h5>{{.Name}}</h5>
</div>
<div class="col">
<p id="up-down-speed-text-{{.Hash}}">...</p>
<p id="peers-seeders-{{.Hash}}">...</p>
<p id="piece-size-{{.Hash}}">...</p>
</div>
<div class="col">
<div id="file-chunks-{{.Hash}}" class="progress">
</div>
</div>
</div>
</div>
<div class="page-wrapper">
<!-- Header -->
<header class="main-header " id="header">
<nav class="navbar navbar-static-top navbar-expand-lg">
<!-- Sidebar toggle button -->
<button id="sidebar-toggler" class="sidebar-toggle">
<span class="sr-only">Toggle navigation</span>
</button>
</nav>
</header>
<div class="content-wrapper">
<div class="content" id="template_target">
</div>
{{end}}
<footer class="footer mt-auto">
<div class="copyright bg-white">
</div>
</footer>
</div>
</div>
{{end}}
</div>
{{template "common_footer.html"}}
<script src="assets/js/humanize.js" type="text/javascript" charset="utf-8"></script>
<script src="assets/js/file_chunks.js" type="text/javascript" charset="utf-8"></script>
<script src="assets/js/routes.js" type="text/javascript" charset="utf-8"></script>
</body>
{{template "footer.html"}}
<script src="assets/js/routes.js"></script>
<script>
Distribyted.routes.loadView();
setInterval(function () {
Distribyted.routes.loadView();
}, 2000)
</script>
</body>
</html>