Improve web interface. (#27)
This commit is contained in:
parent
7c94e663d8
commit
ae289e99dd
74 changed files with 2538 additions and 561 deletions
|
@ -189,7 +189,7 @@ Distributed under the GPL3 license. See `LICENSE` for more information.
|
||||||
[releases-url]: https://github.com/distribyted/distribyted/releases
|
[releases-url]: https://github.com/distribyted/distribyted/releases
|
||||||
[license-shield]: https://img.shields.io/github/license/distribyted/distribyted.svg?style=flat-square
|
[license-shield]: https://img.shields.io/github/license/distribyted/distribyted.svg?style=flat-square
|
||||||
[license-url]: https://github.com/distribyted/distribyted/blob/master/LICENSE
|
[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
|
[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-shield]: https://img.shields.io/coveralls/github/distribyted/distribyted?style=flat-square
|
||||||
[coveralls-url]: https://coveralls.io/github/distribyted/distribyted
|
[coveralls-url]: https://coveralls.io/github/distribyted/distribyted
|
||||||
|
|
7
assets/css/bootstrap.min.css
vendored
7
assets/css/bootstrap.min.css
vendored
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
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
6
assets/css/sleek.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
9
assets/css/solarized-dark-theme.min.css
vendored
9
assets/css/solarized-dark-theme.min.css
vendored
File diff suppressed because one or more lines are too long
6
assets/css/styles.css
Normal file
6
assets/css/styles.css
Normal file
File diff suppressed because one or more lines are too long
15
assets/css/toastify.min.css
vendored
15
assets/css/toastify.min.css
vendored
|
@ -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
BIN
assets/img/favicon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.8 KiB |
|
@ -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
|
@ -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;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})();
|
|
||||||
|
|
7
assets/js/bootstrap.bundle.min.js
vendored
7
assets/js/bootstrap.bundle.min.js
vendored
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
45
assets/js/cache_chart.js
Normal 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
176
assets/js/common.js
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
|
@ -1,21 +1,12 @@
|
||||||
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,
|
|
||||||
|
|
||||||
autoScrollEditorIntoView: true,
|
|
||||||
fontSize: "16px",
|
|
||||||
maxLines: 100,
|
|
||||||
wrap: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
function valid() {
|
|
||||||
let getYamlCodeValidationErrors = (code) => {
|
let getYamlCodeValidationErrors = (code) => {
|
||||||
var error = "";
|
var error = "";
|
||||||
try {
|
try {
|
||||||
|
@ -26,10 +17,10 @@ function valid() {
|
||||||
return error;
|
return error;
|
||||||
};
|
};
|
||||||
|
|
||||||
let code = editor.getValue();
|
let code = this._editor.getValue();
|
||||||
let error = getYamlCodeValidationErrors(code);
|
let error = getYamlCodeValidationErrors(code);
|
||||||
if (error) {
|
if (error) {
|
||||||
editor.getSession().setAnnotations([
|
this._editor.getSession().setAnnotations([
|
||||||
{
|
{
|
||||||
row: error.mark.line,
|
row: error.mark.line,
|
||||||
column: error.mark.column,
|
column: error.mark.column,
|
||||||
|
@ -40,21 +31,33 @@ function valid() {
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
editor.getSession().setAnnotations([]);
|
this._editor.getSession().setAnnotations([]);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
|
||||||
function bin2string(array) {
|
save: function () {
|
||||||
var result = "";
|
fetch("/api/config", {
|
||||||
for (var i = 0; i < array.length; ++i) {
|
method: "POST",
|
||||||
result += String.fromCharCode(array[i]);
|
body: this._editor.getValue(),
|
||||||
}
|
})
|
||||||
return result;
|
.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);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
function reload() {
|
reload: function () {
|
||||||
|
this.cleanInfo();
|
||||||
fetch("/api/reload", {
|
fetch("/api/reload", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
})
|
})
|
||||||
|
@ -62,53 +65,69 @@ function reload() {
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
return response.json();
|
return response.json();
|
||||||
} else {
|
} else {
|
||||||
toastError(
|
Distribyted.config.showInfo("Error reloading server. Response: " + response.status, "ko");
|
||||||
"Error saving configuration file. Response: " + response.status
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then(function (json) {
|
.then(function (json) {
|
||||||
toastInfo(json.message);
|
Distribyted.config.showInfo(json.message, "ok");
|
||||||
})
|
})
|
||||||
.catch(function (error) {
|
.catch(function (error) {
|
||||||
toastError("Error reloading server: " + error.message);
|
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";
|
||||||
}
|
}
|
||||||
|
|
||||||
function save() {
|
if (flag) {
|
||||||
fetch("/api/config", {
|
this._loadingInfoDom.style.display = "none";
|
||||||
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({
|
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",
|
name: "save",
|
||||||
bindKey: { win: "Ctrl-S", mac: "Command-S" },
|
bindKey: { win: "Ctrl-S", mac: "Command-S" },
|
||||||
exec: function (editor) {
|
exec: function (editor) {
|
||||||
if (valid()) {
|
if (Distribyted.config._valid()) {
|
||||||
save();
|
Distribyted.config.save();
|
||||||
} else {
|
} else {
|
||||||
toastError("Check file format errors before saving");
|
Distribyted.message.error("Check file format errors before saving");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
readOnly: false,
|
readOnly: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.on("change", () => {
|
this._editor.on("change", () => {
|
||||||
valid();
|
Distribyted.config._valid();
|
||||||
});
|
});
|
||||||
|
|
||||||
fetch("/api/config")
|
fetch("/api/config")
|
||||||
|
@ -116,14 +135,23 @@ fetch("/api/config")
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
return response.text();
|
return response.text();
|
||||||
} else {
|
} else {
|
||||||
toastError(
|
Distribyted.message.error(
|
||||||
"Error getting data from server. Response: " + response.status
|
"Error getting data from server. Response: " + response.status
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then(function (yaml) {
|
.then(function (yaml) {
|
||||||
editor.setValue(yaml);
|
Distribyted.config._editor.setValue(yaml);
|
||||||
})
|
})
|
||||||
.catch(function (error) {
|
.catch(function (error) {
|
||||||
toastError("Error getting yaml from server: " + error.message);
|
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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
31
assets/js/dashboard.js
Normal file
31
assets/js/dashboard.js
Normal 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)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
|
@ -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)
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -19,7 +19,9 @@ var GeneralChart = {
|
||||||
this._chart.update();
|
this._chart.update();
|
||||||
},
|
},
|
||||||
init: function () {
|
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, {
|
this._chart = new Chart(ctx, {
|
||||||
type: 'line',
|
type: 'line',
|
||||||
data: {
|
data: {
|
||||||
|
@ -27,24 +29,50 @@ var GeneralChart = {
|
||||||
{
|
{
|
||||||
label: 'Download Speed',
|
label: 'Download Speed',
|
||||||
fill: false,
|
fill: false,
|
||||||
backgroundColor: '#859900',
|
backgroundColor: "transparent",
|
||||||
borderColor: '#859900',
|
borderColor: "rgb(82, 136, 255)",
|
||||||
borderWidth: 2,
|
|
||||||
|
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,
|
data: this._downloadData,
|
||||||
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Upload Speed',
|
label: 'Upload Speed',
|
||||||
fill: false,
|
fill: false,
|
||||||
backgroundColor: '#839496',
|
backgroundColor: "transparent",
|
||||||
borderColor: '#839496',
|
borderColor: "rgb(82, 136, 180)",
|
||||||
borderWidth: 2,
|
|
||||||
|
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,
|
data: this._uploadData,
|
||||||
|
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
|
legend: {
|
||||||
|
display: false
|
||||||
|
},
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
layout: {
|
||||||
|
padding: {
|
||||||
|
right: 10
|
||||||
|
}
|
||||||
|
},
|
||||||
title: {
|
title: {
|
||||||
text: 'Download and Upload speed'
|
text: 'Download and Upload speed'
|
||||||
},
|
},
|
||||||
|
@ -64,6 +92,8 @@ var GeneralChart = {
|
||||||
yAxes: [{
|
yAxes: [{
|
||||||
scaleLabel: {
|
scaleLabel: {
|
||||||
display: false,
|
display: false,
|
||||||
|
color: "#eee",
|
||||||
|
zeroLineColor: "#eee",
|
||||||
},
|
},
|
||||||
type: 'linear',
|
type: 'linear',
|
||||||
ticks: {
|
ticks: {
|
||||||
|
@ -74,6 +104,34 @@ 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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
const div = document.createElement("div");
|
||||||
setInterval(function () {
|
div.className = "progress-bar " + pieceStatusClass;
|
||||||
fetchData();
|
div.setAttribute("role", "progressbar");
|
||||||
}, 2000)
|
|
||||||
|
|
||||||
|
if (pieceStatusTip) {
|
||||||
|
div.setAttribute("data-toggle", "tooltip");
|
||||||
|
div.setAttribute("data-placement", "top");
|
||||||
|
div.setAttribute("title", pieceStatusTip);
|
||||||
|
}
|
||||||
|
|
||||||
|
div.style.cssText = "width: " + percentage + "%";
|
||||||
|
|
||||||
|
return div.outerHTML;
|
||||||
|
});
|
||||||
|
|
||||||
|
return '<div class="progress mb-3">' + chunksAsHTML.join("\n"); + '</div>'
|
||||||
|
});
|
||||||
|
|
||||||
|
Handlebars.registerHelper("torrent_info", function (peers, seeders, pieceSize) {
|
||||||
const MB = 1048576;
|
const MB = 1048576;
|
||||||
|
|
||||||
function fetchData() {
|
var messages = [];
|
||||||
fetch('/api/routes')
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
|
||||||
|
this._template = tTemplate;
|
||||||
|
return tTemplate;
|
||||||
|
},
|
||||||
|
|
||||||
|
_getRoutesJson: function () {
|
||||||
|
return fetch('/api/routes')
|
||||||
.then(function (response) {
|
.then(function (response) {
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
return response.json();
|
return response.json();
|
||||||
} else {
|
} else {
|
||||||
toastError('Error getting data from server. Response: ' + response.status)
|
Distribyted.message.error('Error getting data from server. Response: ' + response.status)
|
||||||
}
|
}
|
||||||
}).then(function (routes) {
|
}).then(function (routes) {
|
||||||
routes.forEach(route => {
|
// routes.forEach(route => {
|
||||||
route.torrentStats.forEach(torrentStat => {
|
// route.torrentStats.forEach(torrentStat => {
|
||||||
fileChunks.update(torrentStat.pieceChunks, torrentStat.totalPieces, torrentStat.hash);
|
// fileChunks.update(torrentStat.pieceChunks, torrentStat.totalPieces, torrentStat.hash);
|
||||||
|
|
||||||
var download = torrentStat.downloadedBytes / torrentStat.timePassed;
|
// var download = torrentStat.downloadedBytes / torrentStat.timePassed;
|
||||||
var upload = torrentStat.uploadedBytes / torrentStat.timePassed;
|
// var upload = torrentStat.uploadedBytes / torrentStat.timePassed;
|
||||||
var seeders = torrentStat.seeders;
|
// var seeders = torrentStat.seeders;
|
||||||
var peers = torrentStat.peers;
|
// var peers = torrentStat.peers;
|
||||||
var pieceSize = torrentStat.pieceSize;
|
// var pieceSize = torrentStat.pieceSize;
|
||||||
|
|
||||||
document.getElementById("up-down-speed-text-" + torrentStat.hash).innerText =
|
// document.getElementById("up-down-speed-text-" + torrentStat.hash).innerText =
|
||||||
Humanize.ibytes(download, 1024) + "/s down, " + Humanize.ibytes(upload, 1024) + "/s up";
|
// Humanize.ibytes(download, 1024) + "/s down, " + Humanize.ibytes(upload, 1024) + "/s up";
|
||||||
document.getElementById("peers-seeders-" + torrentStat.hash).innerText =
|
// document.getElementById("peers-seeders-" + torrentStat.hash).innerText =
|
||||||
peers + " peers, " + seeders + " seeders."
|
// peers + " peers, " + seeders + " seeders."
|
||||||
document.getElementById("piece-size-" + torrentStat.hash).innerText = "Piece size: " + Humanize.bytes(pieceSize, 1024)
|
// document.getElementById("piece-size-" + torrentStat.hash).innerText = "Piece size: " + Humanize.bytes(pieceSize, 1024)
|
||||||
|
|
||||||
var className = "";
|
// var className = "";
|
||||||
|
|
||||||
if (seeders < 2) {
|
// if (seeders < 2) {
|
||||||
className = "text-danger";
|
// className = "text-danger";
|
||||||
} else if (seeders >= 2 && seeders < 4) {
|
// } else if (seeders >= 2 && seeders < 4) {
|
||||||
className = "text-warning";
|
// className = "text-warning";
|
||||||
} else {
|
// } else {
|
||||||
className = "text-success";
|
// className = "text-success";
|
||||||
}
|
// }
|
||||||
|
|
||||||
document.getElementById("peers-seeders-" + torrentStat.hash).className = className;
|
// document.getElementById("peers-seeders-" + torrentStat.hash).className = className;
|
||||||
|
|
||||||
if (pieceSize <= MB) {
|
// if (pieceSize <= MB) {
|
||||||
className = "text-success";
|
// className = "text-success";
|
||||||
} else if (pieceSize > MB && pieceSize < (MB * 4)) {
|
// } else if (pieceSize > MB && pieceSize < (MB * 4)) {
|
||||||
className = "text-warning";
|
// className = "text-warning";
|
||||||
} else {
|
// } else {
|
||||||
className = "text-danger";
|
// className = "text-danger";
|
||||||
}
|
// }
|
||||||
|
|
||||||
document.getElementById("piece-size-" + torrentStat.hash).className = className;
|
// document.getElementById("piece-size-" + torrentStat.hash).className = className;
|
||||||
});
|
// });
|
||||||
});
|
// });
|
||||||
|
return routes;
|
||||||
})
|
})
|
||||||
.catch(function (error) {
|
.catch(function (error) {
|
||||||
toastError('Error getting status info: ' + error.message)
|
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);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -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();
|
|
||||||
};
|
|
||||||
}
|
|
15
assets/js/toastify-js.min.js
vendored
15
assets/js/toastify-js.min.js
vendored
|
@ -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="✖",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
|
|
|
@ -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);
|
|
||||||
});
|
|
7
assets/plugins/bootstrap/js/bootstrap.bundle.min.js
vendored
Normal file
7
assets/plugins/bootstrap/js/bootstrap.bundle.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
assets/plugins/bootstrap/js/bootstrap.bundle.min.js.map
Normal file
1
assets/plugins/bootstrap/js/bootstrap.bundle.min.js.map
Normal file
File diff suppressed because one or more lines are too long
29
assets/plugins/handlebars/handlebars.min.js
vendored
Normal file
29
assets/plugins/handlebars/handlebars.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
2
assets/plugins/jquery/jquery-migrate-1.4.1.min.js
vendored
Normal file
2
assets/plugins/jquery/jquery-migrate-1.4.1.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1311
assets/plugins/jquery/jquery-ui-1.12.1.css
vendored
Normal file
1311
assets/plugins/jquery/jquery-ui-1.12.1.css
vendored
Normal file
File diff suppressed because it is too large
Load diff
13
assets/plugins/jquery/jquery-ui-1.12.1.min.js
vendored
Normal file
13
assets/plugins/jquery/jquery-ui-1.12.1.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
4
assets/plugins/jquery/jquery.min.js
vendored
Normal file
4
assets/plugins/jquery/jquery.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
16
assets/plugins/slimscrollbar/jquery.slimscroll.min.js
vendored
Normal file
16
assets/plugins/slimscrollbar/jquery.slimscroll.min.js
vendored
Normal 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);
|
1
assets/plugins/toastr/toastr.js.map
Normal file
1
assets/plugins/toastr/toastr.js.map
Normal file
File diff suppressed because one or more lines are too long
1
assets/plugins/toastr/toastr.min.css
vendored
Normal file
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
2
assets/plugins/toastr/toastr.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
39
assets/templates/routes.html
Normal file
39
assets/templates/routes.html
Normal 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>
|
||||||
|
|
||||||
|
{{/.}}
|
|
@ -127,7 +127,11 @@ func load(configPath string, port int, fuseAllowOther bool) error {
|
||||||
tryClose(c, mountService)
|
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) {
|
func tryClose(c *t.Client, mountService *fuse.Handler) {
|
||||||
|
|
BIN
docs/images/distribyted.gif
Normal file
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 |
|
@ -5,6 +5,7 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"sort"
|
||||||
|
|
||||||
"github.com/ajnavarro/distribyted/config"
|
"github.com/ajnavarro/distribyted/config"
|
||||||
"github.com/ajnavarro/distribyted/stats"
|
"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 {
|
var apiRoutesHandler = func(ss *stats.Torrent) gin.HandlerFunc {
|
||||||
return func(ctx *gin.Context) {
|
return func(ctx *gin.Context) {
|
||||||
stats := ss.RoutesStats()
|
s := ss.RoutesStats()
|
||||||
ctx.JSON(http.StatusOK, stats)
|
sort.Sort(stats.ByName(s))
|
||||||
|
ctx.JSON(http.StatusOK, s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -48,6 +48,12 @@ type RouteStats struct {
|
||||||
TorrentStats []*TorrentStats `json:"torrentStats"`
|
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 {
|
type stats struct {
|
||||||
totalDownloadBytes int64
|
totalDownloadBytes int64
|
||||||
downloadBytes int64
|
downloadBytes int64
|
||||||
|
|
|
@ -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>
|
|
||||||
|
|
|
@ -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>
|
|
|
@ -1,37 +1,105 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
{{template "common_header.html"}}
|
{{template "header.html" "Configuration"}}
|
||||||
<style>
|
<style>
|
||||||
#editor {
|
#editor {
|
||||||
/** Setting height is also important, otherwise editor wont showup**/
|
/** Setting height is also important, otherwise editor wont showup**/
|
||||||
height: 300px;
|
height: 400px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</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>
|
|
||||||
|
|
||||||
|
<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">×</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body">
|
<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 id="editor"></div>
|
||||||
</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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<footer class="footer mt-auto">
|
||||||
|
<div class="copyright bg-white">
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
{{template "footer.html"}}
|
||||||
|
|
||||||
{{template "common_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/js/js-yaml.min.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/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>
|
<script src="assets/js/config.js" type="text/javascript" charset="utf-8"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
Distribyted.config.loadView();
|
||||||
|
</script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
11
templates/footer.html
Normal file
11
templates/footer.html
Normal 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
14
templates/header.html
Normal 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" />
|
|
@ -1,58 +1,106 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
{{template "common_header.html"}}
|
{{template "header.html" "Dashboard"}}
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
|
||||||
{{template "navbar.html"}}
|
<body class="header-fixed sidebar-fixed sidebar-dark header-light" id="body">
|
||||||
<div class="container">
|
<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="row">
|
||||||
<div class="col">
|
<div class="col-xl-12 col-md-12">
|
||||||
<canvas id="chart-general-network" height="150"></canvas>
|
<!-- 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>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm">
|
<div class="col-xl-4 col-md-12">
|
||||||
<canvas id="chart-cache" height="20"></canvas>
|
<!-- 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>
|
</div>
|
||||||
<div class="row">
|
<div class="col-6 border-left">
|
||||||
<div class="col-sm">
|
<div class="py-4 px-4 ">
|
||||||
<h3>
|
<ul class="d-flex flex-column justify-content-between">
|
||||||
<svg class="bi bi-arrow-down" width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor"
|
<li class="mb-2"><i class="mdi mdi-checkbox-blank-circle-outline mr-2"
|
||||||
xmlns="http://www.w3.org/2000/svg">
|
style="color: #8061ef"></i>Free</li>
|
||||||
<path fill-rule="evenodd"
|
</ul>
|
||||||
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>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{template "common_footer.html"}}
|
</div>
|
||||||
<script src="assets/js/Chart.bundle.min.js" type="text/javascript" charset="utf-8"></script>
|
</div>
|
||||||
<script src="assets/js/humanize.js" type="text/javascript" charset="utf-8"></script>
|
</div>
|
||||||
<script src="assets/js/general_chart.js" type="text/javascript" charset="utf-8"></script>
|
</div>
|
||||||
<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>
|
<footer class="footer mt-auto">
|
||||||
|
<div class="copyright bg-white">
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{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>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
|
@ -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"
|
——— LEFT SIDEBAR WITH FOOTER
|
||||||
aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
=====================================
|
||||||
<span class="navbar-toggler-icon"></span>
|
-->
|
||||||
</button>
|
<aside class="left-sidebar bg-sidebar">
|
||||||
<div class="collapse navbar-collapse" id="navbarNav">
|
<div id="sidebar" class="sidebar sidebar-with-footer">
|
||||||
<ul class="navbar-nav">
|
<!-- Aplication Brand -->
|
||||||
<li class="nav-item">
|
<div class="app-brand">
|
||||||
<a class="nav-link" href="/">General</a>
|
<a href="/" title="Distribyted">
|
||||||
|
<img src="/assets/img/favicon.png" />
|
||||||
|
<span class="brand-name text-truncate">Distribyted</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<!-- 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>
|
</li>
|
||||||
<li class="nav-item">
|
{{if eq . "routes"}}
|
||||||
<a class="nav-link" href="/routes">Routes</a>
|
<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>
|
</li>
|
||||||
<li class="nav-item">
|
{{if eq . "config"}}
|
||||||
<a class="nav-link" href="/config">Config</a>
|
<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>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
|
||||||
|
<!-- <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>
|
|
@ -1,53 +1,44 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
{{template "common_header.html"}}
|
{{template "header.html" "Routes"}}
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
|
||||||
{{template "navbar.html"}}
|
|
||||||
|
|
||||||
<div class="container">
|
<body class="header-fixed sidebar-fixed sidebar-dark header-light" id="body">
|
||||||
{{if not .}}
|
<div class="wrapper">
|
||||||
<div class="alert alert-warning" role="alert">
|
{{template "navbar.html" "routes"}}
|
||||||
No routes found.
|
|
||||||
</div>
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
{{range .}}
|
<div class="page-wrapper">
|
||||||
<div class="card shadow">
|
<!-- Header -->
|
||||||
<div class="card-body">
|
<header class="main-header " id="header">
|
||||||
<h5 class="card-title">{{.Name}}</h5>
|
<nav class="navbar navbar-static-top navbar-expand-lg">
|
||||||
{{range .TorrentStats}}
|
<!-- Sidebar toggle button -->
|
||||||
<div class="card shadow">
|
<button id="sidebar-toggler" class="sidebar-toggle">
|
||||||
<div class="card-body">
|
<span class="sr-only">Toggle navigation</span>
|
||||||
<div class="row">
|
</button>
|
||||||
<div class="col">
|
</nav>
|
||||||
<h5>{{.Name}}</h5>
|
</header>
|
||||||
|
|
||||||
|
<div class="content-wrapper">
|
||||||
|
<div class="content" id="template_target">
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
<footer class="footer mt-auto">
|
||||||
<p id="up-down-speed-text-{{.Hash}}">...</p>
|
<div class="copyright bg-white">
|
||||||
<p id="peers-seeders-{{.Hash}}">...</p>
|
|
||||||
<p id="piece-size-{{.Hash}}">...</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
</footer>
|
||||||
<div id="file-chunks-{{.Hash}}" class="progress">
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
{{template "footer.html"}}
|
||||||
</div>
|
|
||||||
</div>
|
<script src="assets/js/routes.js"></script>
|
||||||
{{end}}
|
<script>
|
||||||
</div>
|
Distribyted.routes.loadView();
|
||||||
</div>
|
setInterval(function () {
|
||||||
{{end}}
|
Distribyted.routes.loadView();
|
||||||
</div>
|
}, 2000)
|
||||||
{{template "common_footer.html"}}
|
|
||||||
<script src="assets/js/humanize.js" type="text/javascript" charset="utf-8"></script>
|
</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>
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
Loading…
Reference in a new issue