remove distribyted ui
|
@ -1,4 +1,4 @@
|
||||||
FROM --platform=$BUILDPLATFORM golang:1.22 as builder
|
FROM --platform=$BUILDPLATFORM golang:1.22 AS builder
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
@ -9,16 +9,10 @@ RUN --mount=type=cache,mode=0777,target=/go/pkg/mod go mod download all
|
||||||
COPY ./pkg ./pkg
|
COPY ./pkg ./pkg
|
||||||
COPY ./src ./src
|
COPY ./src ./src
|
||||||
COPY ./cmd ./cmd
|
COPY ./cmd ./cmd
|
||||||
COPY ./assets ./assets
|
|
||||||
COPY ./templates ./templates
|
|
||||||
COPY embed.go embed.go
|
|
||||||
|
|
||||||
RUN go generate ./...
|
|
||||||
|
|
||||||
ARG TARGETOS TARGETARCH
|
ARG TARGETOS TARGETARCH
|
||||||
RUN --mount=type=cache,mode=0777,target=/go/pkg/mod CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH go build -tags timetzdata -o /tstor ./cmd/tstor/main.go
|
RUN --mount=type=cache,mode=0777,target=/go/pkg/mod CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH go build -tags timetzdata -o /tstor ./cmd/tstor/main.go
|
||||||
|
|
||||||
|
|
||||||
FROM scratch
|
FROM scratch
|
||||||
|
|
||||||
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
|
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
|
||||||
|
|
6
assets/css/sleek.min.css
vendored
Before Width: | Height: | Size: 1.8 KiB |
|
@ -1,48 +0,0 @@
|
||||||
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;
|
|
||||||
if (free < 0) {
|
|
||||||
free = 0;
|
|
||||||
}
|
|
||||||
dataset.data[1] = free;
|
|
||||||
});
|
|
||||||
|
|
||||||
this._chart.update();
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,162 +0,0 @@
|
||||||
Handlebars.registerHelper("ibytes", function (bytesSec, timePassed) {
|
|
||||||
return Humanize.ibytes(bytesSec / timePassed, 1024);
|
|
||||||
});
|
|
||||||
Handlebars.registerHelper("bytes", function (bytes) {
|
|
||||||
return Humanize.bytes(bytes, 1024);
|
|
||||||
});
|
|
||||||
|
|
||||||
var tstor = tstor || {};
|
|
||||||
|
|
||||||
tstor.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,160 +0,0 @@
|
||||||
tstor.config = {
|
|
||||||
_editor: null,
|
|
||||||
_infoDiv: document.getElementById("tstor-reload-info-text"),
|
|
||||||
_loadingInfoDom: document.getElementById("tstor-reload-info-loading"),
|
|
||||||
_valid: function () {
|
|
||||||
if (this._editor == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let getYamlCodeValidationErrors = (code) => {
|
|
||||||
var error = "";
|
|
||||||
try {
|
|
||||||
jsyaml.safeLoad(code);
|
|
||||||
} catch (e) {
|
|
||||||
error = e;
|
|
||||||
}
|
|
||||||
return error;
|
|
||||||
};
|
|
||||||
|
|
||||||
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",
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
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) {
|
|
||||||
tstor.message.info("Configuration saved");
|
|
||||||
} else {
|
|
||||||
tstor.message.error(
|
|
||||||
"Error saving configuration file. Response: " + response.status
|
|
||||||
);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(function (error) {
|
|
||||||
tstor.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 {
|
|
||||||
tstor.config.showInfo(
|
|
||||||
"Error reloading server. Response: " + response.status,
|
|
||||||
"ko"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then(function (json) {
|
|
||||||
tstor.config.showInfo(json.message, "ok");
|
|
||||||
})
|
|
||||||
.catch(function (error) {
|
|
||||||
tstor.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 (tstor.config._valid()) {
|
|
||||||
tstor.config.save();
|
|
||||||
} else {
|
|
||||||
tstor.message.error("Check file format errors before saving");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
readOnly: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
this._editor.on("change", () => {
|
|
||||||
tstor.config._valid();
|
|
||||||
});
|
|
||||||
|
|
||||||
fetch("/api/config")
|
|
||||||
.then(function (response) {
|
|
||||||
if (response.ok) {
|
|
||||||
return response.text();
|
|
||||||
} else {
|
|
||||||
tstor.message.error(
|
|
||||||
"Error getting data from server. Response: " + response.status
|
|
||||||
);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then(function (yaml) {
|
|
||||||
tstor.config._editor.setValue(yaml);
|
|
||||||
})
|
|
||||||
.catch(function (error) {
|
|
||||||
tstor.message.error("Error getting yaml from server: " + error.message);
|
|
||||||
});
|
|
||||||
|
|
||||||
var stream = new EventSource("/api/events");
|
|
||||||
stream.addEventListener("event", function (e) {
|
|
||||||
tstor.config.showInfo(e.data);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
|
@ -1,39 +0,0 @@
|
||||||
GeneralChart.init();
|
|
||||||
|
|
||||||
tstor.dashboard = {
|
|
||||||
_cacheChart: new CacheChart("main-cache-chart", "Cache disk"),
|
|
||||||
loadView: function () {
|
|
||||||
fetch("/api/status")
|
|
||||||
.then(function (response) {
|
|
||||||
if (response.ok) {
|
|
||||||
return response.json();
|
|
||||||
} else {
|
|
||||||
tstor.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);
|
|
||||||
|
|
||||||
tstor.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) {
|
|
||||||
tstor.message.error("Error getting status info: " + error.message);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
|
@ -1,138 +0,0 @@
|
||||||
var GeneralChart = {
|
|
||||||
_downloadData: [],
|
|
||||||
_uploadData: [],
|
|
||||||
_chart: null,
|
|
||||||
update: function (download, upload) {
|
|
||||||
if (this._downloadData.length > 20) {
|
|
||||||
this._uploadData.shift();
|
|
||||||
this._downloadData.shift();
|
|
||||||
}
|
|
||||||
var date = new Date();
|
|
||||||
this._downloadData.push({
|
|
||||||
x: date,
|
|
||||||
y: download,
|
|
||||||
});
|
|
||||||
this._uploadData.push({
|
|
||||||
x: date,
|
|
||||||
y: upload,
|
|
||||||
});
|
|
||||||
this._chart.update();
|
|
||||||
},
|
|
||||||
init: function () {
|
|
||||||
var domElem = document.getElementById('chart-general-network')
|
|
||||||
domElem.height = 300;
|
|
||||||
var ctx = domElem.getContext('2d');
|
|
||||||
this._chart = new Chart(ctx, {
|
|
||||||
type: 'line',
|
|
||||||
data: {
|
|
||||||
datasets: [
|
|
||||||
{
|
|
||||||
label: 'Download Speed',
|
|
||||||
fill: false,
|
|
||||||
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: "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'
|
|
||||||
},
|
|
||||||
scales: {
|
|
||||||
xAxes: [{
|
|
||||||
scaleLabel: {
|
|
||||||
display: false,
|
|
||||||
},
|
|
||||||
gridLines: {
|
|
||||||
display: false,
|
|
||||||
},
|
|
||||||
ticks: {
|
|
||||||
display: false,
|
|
||||||
},
|
|
||||||
type: 'time',
|
|
||||||
}],
|
|
||||||
yAxes: [{
|
|
||||||
scaleLabel: {
|
|
||||||
display: false,
|
|
||||||
color: "#eee",
|
|
||||||
zeroLineColor: "#eee",
|
|
||||||
},
|
|
||||||
type: 'linear',
|
|
||||||
ticks: {
|
|
||||||
userCallback: function (tick) {
|
|
||||||
return Humanize.ibytes(tick, 1024) + "/s";
|
|
||||||
},
|
|
||||||
beginAtZero: true
|
|
||||||
},
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
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,41 +0,0 @@
|
||||||
var isizes = ["B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"];
|
|
||||||
var sizes = ["B", "KB", "MB", "GB", "TB", "PB", "EB"];
|
|
||||||
|
|
||||||
function logn(n, b) {
|
|
||||||
return Math.log(n) / Math.log(b);
|
|
||||||
}
|
|
||||||
|
|
||||||
var Humanize = {
|
|
||||||
bytes: function (s, base) {
|
|
||||||
if (s < 10) {
|
|
||||||
return s.toFixed(0) + " B";
|
|
||||||
}
|
|
||||||
var e = Math.floor(logn(s, base));
|
|
||||||
var suffix = sizes[e];
|
|
||||||
var val = Math.floor(s / Math.pow(base, e) * 10 + 0.5) / 10;
|
|
||||||
|
|
||||||
var f = val.toFixed(0);
|
|
||||||
|
|
||||||
if (val < 10) {
|
|
||||||
f = val.toFixed(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return f + suffix;
|
|
||||||
},
|
|
||||||
ibytes: function (s, base) {
|
|
||||||
if (s < 10) {
|
|
||||||
return s.toFixed(0) + " B";
|
|
||||||
}
|
|
||||||
var e = Math.floor(logn(s, base));
|
|
||||||
var suffix = isizes[e];
|
|
||||||
var val = Math.floor(s / Math.pow(base, e) * 10 + 0.5) / 10;
|
|
||||||
|
|
||||||
var f = val.toFixed(0);
|
|
||||||
|
|
||||||
if (val < 10) {
|
|
||||||
f = val.toFixed(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return f + suffix;
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -1,88 +0,0 @@
|
||||||
tstor.logs = {
|
|
||||||
loadView: function () {
|
|
||||||
fetch("/api/log")
|
|
||||||
.then((response) => {
|
|
||||||
if (response.ok) {
|
|
||||||
return response.body.getReader();
|
|
||||||
} else {
|
|
||||||
response
|
|
||||||
.json()
|
|
||||||
.then((json) => {
|
|
||||||
tstor.message.error(
|
|
||||||
"Error getting logs from server. Error: " + json.error
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
tstor.message.error(
|
|
||||||
"Error getting logs from server. Error: " + error
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then((reader) => {
|
|
||||||
var decoder = new TextDecoder();
|
|
||||||
var lastString = "";
|
|
||||||
reader
|
|
||||||
.read()
|
|
||||||
.then(function processText({ done, value }) {
|
|
||||||
if (done) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const string = `${lastString}${decoder.decode(value)}`;
|
|
||||||
const lines = string.split(/\r\n|[\r\n]/g);
|
|
||||||
this.lastString = lines.pop() || "";
|
|
||||||
|
|
||||||
lines.forEach((element) => {
|
|
||||||
try {
|
|
||||||
var json = JSON.parse(element);
|
|
||||||
var properties = "";
|
|
||||||
for (let [key, value] of Object.entries(json)) {
|
|
||||||
if (
|
|
||||||
key == "level" ||
|
|
||||||
key == "component" ||
|
|
||||||
key == "message" ||
|
|
||||||
key == "time"
|
|
||||||
) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
properties += `<b>${key}</b>=${value} `;
|
|
||||||
}
|
|
||||||
|
|
||||||
var tableClass = "table-primary";
|
|
||||||
switch (json.level) {
|
|
||||||
case "info":
|
|
||||||
tableClass = "";
|
|
||||||
break;
|
|
||||||
case "error":
|
|
||||||
tableClass = "table-danger";
|
|
||||||
break;
|
|
||||||
case "warn":
|
|
||||||
tableClass = "table-warning";
|
|
||||||
break;
|
|
||||||
case "debug":
|
|
||||||
tableClass = "table-info";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
template = `<tr class="${tableClass}"><td>${new Date(
|
|
||||||
json.time * 1000
|
|
||||||
).toLocaleString()}</td><td>${json.level}</td><td>${
|
|
||||||
json.component
|
|
||||||
}</td><td>${json.message}</td><td>${properties}</td></tr>`;
|
|
||||||
document.getElementById("log_table").innerHTML += template;
|
|
||||||
} catch (err) {
|
|
||||||
// server can send some corrupted json line
|
|
||||||
console.log(err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return reader.read().then(processText);
|
|
||||||
})
|
|
||||||
.catch((err) => console.log(err));
|
|
||||||
})
|
|
||||||
.catch((err) => console.log(err));
|
|
||||||
},
|
|
||||||
};
|
|
|
@ -1,204 +0,0 @@
|
||||||
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;
|
|
||||||
|
|
||||||
const 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.outerHTML;
|
|
||||||
});
|
|
||||||
|
|
||||||
return '<div class="progress mb-3">' + chunksAsHTML.join("\n");
|
|
||||||
+"</div>";
|
|
||||||
});
|
|
||||||
|
|
||||||
Handlebars.registerHelper("torrent_info", function (peers, seeders, pieceSize) {
|
|
||||||
const MB = 1048576;
|
|
||||||
|
|
||||||
var messages = [];
|
|
||||||
|
|
||||||
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;
|
|
||||||
});
|
|
||||||
|
|
||||||
tstor.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 {
|
|
||||||
tstor.message.error(
|
|
||||||
"Error getting data from server. Response: " + response.status
|
|
||||||
);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then((t) => {
|
|
||||||
return Handlebars.compile(t);
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
tstor.message.error("Error getting routes template: " + error.message);
|
|
||||||
});
|
|
||||||
|
|
||||||
this._template = tTemplate;
|
|
||||||
return tTemplate;
|
|
||||||
},
|
|
||||||
|
|
||||||
_getRoutesJson: function () {
|
|
||||||
return fetch("/api/routes")
|
|
||||||
.then(function (response) {
|
|
||||||
if (response.ok) {
|
|
||||||
return response.json();
|
|
||||||
} else {
|
|
||||||
tstor.message.error(
|
|
||||||
"Error getting data from server. Response: " + response.status
|
|
||||||
);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then(function (routes) {
|
|
||||||
return routes;
|
|
||||||
})
|
|
||||||
.catch(function (error) {
|
|
||||||
tstor.message.error("Error getting status info: " + error.message);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
deleteTorrent: function (route, torrentHash) {
|
|
||||||
var url = "/api/routes/" + route + "/torrent/" + torrentHash;
|
|
||||||
|
|
||||||
return fetch(url, {
|
|
||||||
method: "DELETE",
|
|
||||||
})
|
|
||||||
.then(function (response) {
|
|
||||||
if (response.ok) {
|
|
||||||
tstor.message.info("Torrent deleted.");
|
|
||||||
tstor.routes.loadView();
|
|
||||||
} else {
|
|
||||||
response.json().then((json) => {
|
|
||||||
tstor.message.error(
|
|
||||||
"Error deletting torrent. Response: " + json.error
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(function (error) {
|
|
||||||
tstor.message.error("Error deletting torrent: " + error.message);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
loadView: function () {
|
|
||||||
this._getTemplate().then((t) =>
|
|
||||||
this._getRoutesJson().then((routes) => {
|
|
||||||
document.getElementById("template_target").innerHTML = t(routes);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
$("#new-magnet").submit(function (event) {
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
let route = $("#route-string :selected").val();
|
|
||||||
let magnet = $("#magnet-url").val();
|
|
||||||
|
|
||||||
let url = "/api/routes/" + route + "/torrent";
|
|
||||||
let body = JSON.stringify({ magnet: magnet });
|
|
||||||
|
|
||||||
document.getElementById("submit_magnet_loading").style = "display:block";
|
|
||||||
|
|
||||||
fetch(url, {
|
|
||||||
method: "POST",
|
|
||||||
body: body,
|
|
||||||
})
|
|
||||||
.then(function (response) {
|
|
||||||
if (response.ok) {
|
|
||||||
tstor.message.info("New magnet added.");
|
|
||||||
tstor.routes.loadView();
|
|
||||||
} else {
|
|
||||||
response
|
|
||||||
.json()
|
|
||||||
.then((json) => {
|
|
||||||
tstor.message.error(
|
|
||||||
"Error adding new magnet. Response: " + json.error
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.catch(function (error) {
|
|
||||||
tstor.message.error("Error adding new magnet: " + response.status);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(function (error) {
|
|
||||||
tstor.message.error("Error adding torrent: " + error.message);
|
|
||||||
})
|
|
||||||
.then(function () {
|
|
||||||
document.getElementById("submit_magnet_loading").style = "display:none";
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,57 +0,0 @@
|
||||||
Handlebars.registerHelper("to_date", function (timestamp) {
|
|
||||||
return new Date(timestamp * 1000).toLocaleString();
|
|
||||||
});
|
|
||||||
|
|
||||||
tstor.servers = {
|
|
||||||
_template: null,
|
|
||||||
|
|
||||||
_getTemplate: function () {
|
|
||||||
if (this._template != null) {
|
|
||||||
return this._template;
|
|
||||||
}
|
|
||||||
|
|
||||||
const tTemplate = fetch("/assets/templates/servers.html")
|
|
||||||
.then((response) => {
|
|
||||||
if (response.ok) {
|
|
||||||
return response.text();
|
|
||||||
} else {
|
|
||||||
tstor.message.error(
|
|
||||||
"Error getting data from server. Response: " + response.status
|
|
||||||
);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then((t) => {
|
|
||||||
return Handlebars.compile(t);
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
tstor.message.error("Error getting servers template: " + error.message);
|
|
||||||
});
|
|
||||||
|
|
||||||
this._template = tTemplate;
|
|
||||||
return tTemplate;
|
|
||||||
},
|
|
||||||
|
|
||||||
_getRoutesJson: function () {
|
|
||||||
return fetch("/api/servers")
|
|
||||||
.then(function (response) {
|
|
||||||
if (response.ok) {
|
|
||||||
return response.json();
|
|
||||||
} else {
|
|
||||||
tstor.message.error(
|
|
||||||
"Error getting data from server. Response: " + response.status
|
|
||||||
);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(function (error) {
|
|
||||||
tstor.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);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
},
|
|
||||||
};
|
|
7
assets/plugins/charts/Chart.min.js
vendored
29
assets/plugins/handlebars/handlebars.min.js
vendored
1311
assets/plugins/jquery/jquery-ui-1.12.1.css
vendored
13
assets/plugins/jquery/jquery-ui-1.12.1.min.js
vendored
4
assets/plugins/jquery/jquery.min.js
vendored
|
@ -1,16 +0,0 @@
|
||||||
/*! 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.min.css
vendored
2
assets/plugins/toastr/toastr.min.js
vendored
|
@ -1,59 +0,0 @@
|
||||||
{{#.}}
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-12">
|
|
||||||
<div class="card card-table-border-none">
|
|
||||||
<div
|
|
||||||
class="card-header justify-content-between card-header-border-bottom"
|
|
||||||
>
|
|
||||||
<h2>Route: {{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: 30%">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%" class="d-none d-lg-table-cell">
|
|
||||||
Peers/Seeders
|
|
||||||
</th>
|
|
||||||
<th style="width: 35%" class="d-none d-lg-table-cell">Status</th>
|
|
||||||
<th style="width: 5%">Actions</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{{#torrentStats}}
|
|
||||||
<tr>
|
|
||||||
<td>{{name}}</td>
|
|
||||||
<td>
|
|
||||||
{{ibytes downloadedBytes timePassed}} / {{ibytes uploadedBytes
|
|
||||||
timePassed}}
|
|
||||||
</td>
|
|
||||||
<td class="d-none d-lg-table-cell">
|
|
||||||
{{{torrent_info peers seeders pieceSize}}}
|
|
||||||
</td>
|
|
||||||
<td class="d-none d-lg-table-cell">
|
|
||||||
{{{torrent_status pieceChunks totalPieces}}}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<i
|
|
||||||
class="mdi mdi-delete-forever"
|
|
||||||
title="delete torrent"
|
|
||||||
onclick='tstor.routes.deleteTorrent("{{../name}}","{{hash}}")'
|
|
||||||
></i>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{{/torrentStats}}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{/.}}
|
|
|
@ -1,45 +0,0 @@
|
||||||
{{#.}}
|
|
||||||
|
|
||||||
<div class="card card-default">
|
|
||||||
<div class="card-header justify-content-between align-items-center card-header-border-bottom">
|
|
||||||
<h2>{{name}}</h2>
|
|
||||||
</div>
|
|
||||||
<div class="bg-white border rounded">
|
|
||||||
<div class="row no-gutters">
|
|
||||||
<div class="col-lg-4 col-xl-3">
|
|
||||||
<div class="profile-content-left pt-5 pb-3 px-3 px-xl-5">
|
|
||||||
<div class="contact-info pt-4">
|
|
||||||
<h5 class="text-dark mb-1">Server Info:</h5>
|
|
||||||
<p class="text-dark font-weight-medium pt-4 mb-2">State</p>
|
|
||||||
<p>{{state}}</p>
|
|
||||||
<p class="text-dark font-weight-medium pt-4 mb-2">Updated At</p>
|
|
||||||
<p>{{to_date updatedAt}}</p>
|
|
||||||
<p class="text-dark font-weight-medium pt-4 mb-2">Seeds: {{seeds}}</p>
|
|
||||||
<p class="text-dark font-weight-medium pt-4 mb-2">Peers: {{peers}}</p>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-lg-8 col-xl-9">
|
|
||||||
<div class="profile-content-right py-5">
|
|
||||||
<div class="tab-content px-3 px-xl-5">
|
|
||||||
<div class="mt-5">
|
|
||||||
<div class="form-group mb-4">
|
|
||||||
<label for="magnetUri">Magnet URI</label>
|
|
||||||
<input type="text" class="form-control" id="magnetUri" value="{{magnetUri}}">
|
|
||||||
<span class="d-block mt-1">Magnet URI pointing to the actual folder content. If content
|
|
||||||
changes, this URI will change too.</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group mb-4">
|
|
||||||
<label for="folder">Folder</label>
|
|
||||||
<input type="folder" class="form-control" id="folder" value="{{folder}}">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{/.}}
|
|
11
embed.go
|
@ -1,11 +0,0 @@
|
||||||
package tstor
|
|
||||||
|
|
||||||
import (
|
|
||||||
"embed"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:embed assets
|
|
||||||
var Assets embed.FS
|
|
||||||
|
|
||||||
//go:embed templates
|
|
||||||
var Templates embed.FS
|
|
|
@ -1 +0,0 @@
|
||||||
Gif generated using https://ezgif.com with delay=200
|
|
|
@ -1 +0,0 @@
|
||||||
tstor.com
|
|
|
@ -1 +0,0 @@
|
||||||
TBD
|
|
|
@ -1 +0,0 @@
|
||||||
TBD
|
|
|
@ -1,95 +0,0 @@
|
||||||
## Installation
|
|
||||||
|
|
||||||
### Using the binary
|
|
||||||
|
|
||||||
Get the latest release from [releases][releases-url] page or download the source code and execute `make build`.
|
|
||||||
|
|
||||||
Run the program: `./tstor-[VERSION]-[OS]-[ARCH]`
|
|
||||||
|
|
||||||
Defaults are good enough for starters, but you can change them. Here is the output of `./tstor -help`:
|
|
||||||
|
|
||||||
```text
|
|
||||||
NAME:
|
|
||||||
tstor - Torrent client with on-demand file downloading as a filesystem.
|
|
||||||
|
|
||||||
USAGE:
|
|
||||||
tstor [global options] [arguments...]
|
|
||||||
|
|
||||||
GLOBAL OPTIONS:
|
|
||||||
--config value YAML file containing tstor configuration. (default: "./tstor-data/config.yaml") [$tstor_CONFIG]
|
|
||||||
--http-port value HTTP port for web interface (default: 4444) [$tstor_HTTP_PORT]
|
|
||||||
--fuse-allow-other Allow other users to acces to all fuse mountpoints. You need to add user_allow_other flag to /etc/fuse.conf file. (default: false) [$tstor_FUSE_ALLOW_OTHER]
|
|
||||||
--help, -h show help (default: false)
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Prerequisites on windows
|
|
||||||
|
|
||||||
Download and install [WinFsp](http://www.secfs.net/winfsp/).
|
|
||||||
|
|
||||||
### Using Docker
|
|
||||||
|
|
||||||
Docker run example:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
docker run \
|
|
||||||
--rm -p 4444:4444 -p 36911:36911 \
|
|
||||||
--cap-add SYS_ADMIN \
|
|
||||||
--device /dev/fuse \
|
|
||||||
--security-opt apparmor:unconfined \
|
|
||||||
-v /tmp/mount:/tstor-data/mount:shared \
|
|
||||||
-v /tmp/metadata:/tstor-data/metadata \
|
|
||||||
-v /tmp/config:/tstor-data/config \
|
|
||||||
tstor/tstor:latest
|
|
||||||
```
|
|
||||||
|
|
||||||
Docker compose example:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
tstor:
|
|
||||||
container_name: tstor
|
|
||||||
image: tstor/tstor:latest
|
|
||||||
restart: always
|
|
||||||
ports:
|
|
||||||
- "4444:4444/tcp"
|
|
||||||
- "36911:36911/tcp"
|
|
||||||
volumes:
|
|
||||||
- /home/user/mount:/tstor-data/mount:shared
|
|
||||||
- /home/user/metadata:/tstor-data/metadata
|
|
||||||
- /home/user/config:/tstor-data/config
|
|
||||||
security_opt:
|
|
||||||
- apparmor:unconfined
|
|
||||||
devices:
|
|
||||||
- /dev/fuse
|
|
||||||
cap_add:
|
|
||||||
- SYS_ADMIN
|
|
||||||
```
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
After executing and load all torrent or magnet files, a web interface will be available at `http://localhost:4444`
|
|
||||||
It contains information about the mounted routes and torrent files like download/upload speed, leechers, seeders...
|
|
||||||
|
|
||||||
### Configuration File
|
|
||||||
|
|
||||||
You can see the default configuration file with some explanation comments [here](https://git.kmsign.ru/royalcat/tstor/blob/master/templates/config_template.yaml).
|
|
||||||
|
|
||||||
### Routes
|
|
||||||
|
|
||||||
Here there is a list of all available routes with their torrents and some info. You can add and remove torrents from here too.
|
|
||||||
|
|
||||||
![routes screen](images/tstor-routes-border-large.png)
|
|
||||||
|
|
||||||
### Servers
|
|
||||||
|
|
||||||
Servers is a way to generate magnet files from folders.
|
|
||||||
All servers configured using the config yaml file will be here.
|
|
||||||
When some data is changed on these folders, a new magnet URI will be generated.
|
|
||||||
You can share that magnet URI with anyone to share these files.
|
|
||||||
|
|
||||||
![server screen](images/tstor-server-border.png)
|
|
||||||
|
|
||||||
### Logs
|
|
||||||
|
|
||||||
You can check logs in real time from the web interface:
|
|
||||||
|
|
||||||
![logs screen](images/tstor-logs-border.png)
|
|
Before Width: | Height: | Size: 80 KiB |
Before Width: | Height: | Size: 67 KiB |
Before Width: | Height: | Size: 67 KiB |
Before Width: | Height: | Size: 55 KiB |
Before Width: | Height: | Size: 87 KiB |
Before Width: | Height: | Size: 75 KiB |
Before Width: | Height: | Size: 93 KiB |
Before Width: | Height: | Size: 73 KiB |
Before Width: | Height: | Size: 79 KiB |
Before Width: | Height: | Size: 61 KiB |
Before Width: | Height: | Size: 60 KiB |
Before Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 3.7 MiB |
Before Width: | Height: | Size: 46 KiB |
|
@ -1,36 +0,0 @@
|
||||||
tstor is an alternative torrent client.
|
|
||||||
It can expose torrent files as a standard FUSE mount or webDAV endpoint and download them on demand, allowing random reads using a fixed amount of disk space.
|
|
||||||
|
|
||||||
![tstor Screen Shot][product-screenshot]
|
|
||||||
|
|
||||||
[product-screenshot]: images/tstor.gif
|
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
### User Interfaces
|
|
||||||
|
|
||||||
tstor supports several ways to expose the files to the user or external applications:
|
|
||||||
|
|
||||||
#### Supported
|
|
||||||
|
|
||||||
- FUSE: Other applications can access to torrent files directly as a filesystem.
|
|
||||||
- WebDAV: Applications that supports WebDAV can access torrent files using this protocol. It is recommended when tstor is running in a remote machine or using docker.
|
|
||||||
- HTTP: A simple HTTP interface for all the available routes. You can acces it from `http://[HOST]:[PORT]/fs`
|
|
||||||
|
|
||||||
### _Expandable_ File Formats
|
|
||||||
|
|
||||||
tstor can show some kind of files directly as folders, making it possible for applications read only the parts that they need. Here is a list of supported, to be supported and not supported formats.
|
|
||||||
|
|
||||||
#### Supported
|
|
||||||
|
|
||||||
- zip: Able to uncompress just one file. The file is decompressed to a temporal file sequentially to make possible seek over it. The decompression stops if no one is reading it.
|
|
||||||
- rar: Thanks to [rardecode](https://github.com/nwaples/rardecode/tree/experimental) experimental branch library, it is possible to seek through rar files.
|
|
||||||
- 7zip: Thanks to [sevenzip](https://github.com/bodgit/sevenzip) library, it is possible to read `7z` files in a similar way that is done using the `zip` implementation.
|
|
||||||
|
|
||||||
#### To Be Supported
|
|
||||||
|
|
||||||
- xz: Only worth it when the file is created using blocks. Possible library [here](https://github.com/ulikunitz/xz) and [here](https://github.com/frrad/bxzf).
|
|
||||||
|
|
||||||
#### Not Supported
|
|
||||||
|
|
||||||
- gzip: As far as I know, it doesn't support random access.
|
|
|
@ -1 +0,0 @@
|
||||||
TBD
|
|
|
@ -1,45 +0,0 @@
|
||||||
site_name: tstor
|
|
||||||
site_url: https://tstor.com/
|
|
||||||
repo_url: https://git.kmsign.ru/royalcat/tstor
|
|
||||||
repo_name: tstor/tstor
|
|
||||||
|
|
||||||
nav:
|
|
||||||
- Home: index.md
|
|
||||||
- Getting Started: getting-started.md
|
|
||||||
- Tutorials: tutorials.md
|
|
||||||
- API Reference: api-reference.md
|
|
||||||
|
|
||||||
theme:
|
|
||||||
logo: images/tstor_icon.png
|
|
||||||
icon:
|
|
||||||
repo: fontawesome/brands/github
|
|
||||||
name: material
|
|
||||||
palette:
|
|
||||||
primary: white
|
|
||||||
features:
|
|
||||||
- navigation.tabs
|
|
||||||
- navigation.tabs.sticky
|
|
||||||
- navigation.sections
|
|
||||||
- navigation.instant
|
|
||||||
- navigation.tracking
|
|
||||||
- navigation.expand
|
|
||||||
- navigation.indexes
|
|
||||||
- navigation.top
|
|
||||||
- toc.integrate
|
|
||||||
|
|
||||||
edit_uri: edit/master/mkdocs/docs/
|
|
||||||
|
|
||||||
plugins:
|
|
||||||
- git-revision-date
|
|
||||||
- search
|
|
||||||
|
|
||||||
extra:
|
|
||||||
version:
|
|
||||||
default: latest
|
|
||||||
provider: mike
|
|
||||||
|
|
||||||
markdown_extensions:
|
|
||||||
- toc:
|
|
||||||
permalink: true
|
|
||||||
- pymdownx.highlight
|
|
||||||
- pymdownx.superfences
|
|
|
@ -1,5 +0,0 @@
|
||||||
mkdocs==1.3.0
|
|
||||||
mkdocs-git-revision-date-plugin==0.3.1
|
|
||||||
mkdocs-material==7.3.2
|
|
||||||
mkdocs-material-extensions==1.0.3
|
|
||||||
mike==1.1.2
|
|
|
@ -28,39 +28,10 @@ func New(fc *filecache.Cache, s *torrent.Daemon, vfs vfs.Filesystem, logPath str
|
||||||
|
|
||||||
echopprof.Register(r)
|
echopprof.Register(r)
|
||||||
|
|
||||||
// r.GET("/assets/*filepath", func(c *echo.Context) {
|
|
||||||
// c.FileFromFS(c.Request.URL.Path, http.FS(tstor.Assets))
|
|
||||||
// })
|
|
||||||
|
|
||||||
// t, err := vfstemplate.ParseGlob(http.FS(tstor.Templates), nil, "/templates/*")
|
|
||||||
// if err != nil {
|
|
||||||
// return fmt.Errorf("error parsing html: %w", err)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// r.SetHTMLTemplate(t)
|
|
||||||
|
|
||||||
// r.GET("/", indexHandler)
|
|
||||||
// // r.GET("/routes", routesHandler(ss))
|
|
||||||
// r.GET("/logs", logsHandler)
|
|
||||||
// r.GET("/servers", serversFoldersHandler())
|
|
||||||
r.Any("/graphql", echo.WrapHandler((GraphQLHandler(s, vfs))))
|
r.Any("/graphql", echo.WrapHandler((GraphQLHandler(s, vfs))))
|
||||||
|
|
||||||
// api := r.Group("/api")
|
|
||||||
// {
|
|
||||||
// api.GET("/log", apiLogHandler(logPath))
|
|
||||||
// api.GET("/status", apiStatusHandler(fc, ss))
|
|
||||||
// // api.GET("/servers", apiServersHandler(tss))
|
|
||||||
// // api.GET("/routes", apiRoutesHandler(ss))
|
|
||||||
// // api.POST("/routes/:route/torrent", apiAddTorrentHandler(s))
|
|
||||||
// // api.DELETE("/routes/:route/torrent/:torrent_hash", apiDelTorrentHandler(s))
|
|
||||||
// }
|
|
||||||
|
|
||||||
log.Info("starting webserver", "host", fmt.Sprintf("%s:%d", cfg.WebUi.IP, cfg.WebUi.Port))
|
log.Info("starting webserver", "host", fmt.Sprintf("%s:%d", cfg.WebUi.IP, cfg.WebUi.Port))
|
||||||
|
|
||||||
// if err := r.Run(fmt.Sprintf("%s:%d", cfg.WebUi.IP, cfg.WebUi.Port)); err != nil {
|
|
||||||
// return fmt.Errorf("error initializing server: %w", err)
|
|
||||||
// }
|
|
||||||
|
|
||||||
go r.Start((fmt.Sprintf("%s:%d", cfg.WebUi.IP, cfg.WebUi.Port)))
|
go r.Start((fmt.Sprintf("%s:%d", cfg.WebUi.IP, cfg.WebUi.Port)))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
<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>
|
|
|
@ -1,19 +0,0 @@
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
||||||
<title>tstor - {{.}}</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,106 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
|
|
||||||
<head>
|
|
||||||
{{template "header.html" "Dashboard"}}
|
|
||||||
</head>
|
|
||||||
|
|
||||||
|
|
||||||
<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 "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>
|
|
||||||
tstor.dashboard.loadView();
|
|
||||||
setInterval(function () {
|
|
||||||
tstor.dashboard.loadView();
|
|
||||||
}, 2000)
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
|
@ -1,77 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
|
|
||||||
<head>
|
|
||||||
{{template "header.html" "Logs"}}
|
|
||||||
</head>
|
|
||||||
|
|
||||||
|
|
||||||
<body class="header-fixed sidebar-fixed sidebar-dark header-light" id="body">
|
|
||||||
<div class="wrapper">
|
|
||||||
{{template "navbar.html" "logs"}}
|
|
||||||
|
|
||||||
<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-lg-12">
|
|
||||||
<div class="card card-default" data-scroll-height="1000"
|
|
||||||
style="height: 1000px; overflow: hidden;">
|
|
||||||
<div
|
|
||||||
class="card-header justify-content-between align-items-center card-header-border-bottom">
|
|
||||||
<h2>Logs</h2>
|
|
||||||
</div>
|
|
||||||
<div style="position: relative; overflow: hidden; width: auto; height: 100%;">
|
|
||||||
<div class="card-body"
|
|
||||||
style="overflow: hidden; width: auto; height: 100%; overflow-y: scroll; display: flex; flex-direction: column-reverse;">
|
|
||||||
<table class="table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th scope="col" style="width: 5%">Time</th>
|
|
||||||
<th scope="col" style="width: 5%">Type</th>
|
|
||||||
<th scope="col" style="width: 15%">Component</th>
|
|
||||||
<th scope="col" style="width: 40%">Message</th>
|
|
||||||
<th scope="col" style="width: 35%">Properties</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody id="log_table">
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<div class="slimScrollBar"
|
|
||||||
style="background: rgb(153, 153, 153) none repeat scroll 0% 0%; width: 5px; position: absolute; top: 0px; opacity: 0.4; display: none; border-radius: 7px; z-index: 99; right: 1px; height: 242.986px;">
|
|
||||||
</div>
|
|
||||||
<div class="slimScrollRail"
|
|
||||||
style="width: 5px; height: 100%; position: absolute; top: 0px; display: none; border-radius: 7px; background: rgb(51, 51, 51) none repeat scroll 0% 0%; opacity: 0.2; z-index: 90; right: 1px;">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="mt-3"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<footer class="footer mt-auto">
|
|
||||||
<div class="copyright bg-white">
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{template "footer.html"}}
|
|
||||||
|
|
||||||
<script src="assets/js/logs.js"></script>
|
|
||||||
<script>
|
|
||||||
tstor.logs.loadView();
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
|
@ -1,89 +0,0 @@
|
||||||
<!--
|
|
||||||
====================================
|
|
||||||
——— 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="tstor">
|
|
||||||
<img src="/assets/img/favicon.png" />
|
|
||||||
<span class="brand-name text-truncate">tstor</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>
|
|
||||||
{{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 . "served-folders"}}
|
|
||||||
<li class="active">
|
|
||||||
{{else}}
|
|
||||||
<li>
|
|
||||||
{{end}}
|
|
||||||
<a class="sidenav-item-link" href="/servers">
|
|
||||||
<i class="mdi mdi-folder-upload-outline"></i>
|
|
||||||
<span class="nav-text">Served Folders</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{{if eq . "logs"}}
|
|
||||||
<li class="active">
|
|
||||||
{{else}}
|
|
||||||
<li>
|
|
||||||
{{end}}
|
|
||||||
<a class="sidenav-item-link" href="/logs">
|
|
||||||
<i class="mdi mdi-information-outline"></i>
|
|
||||||
<span class="nav-text">Logs</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li>
|
|
||||||
<a class="sidenav-item-link" href="/fs" target="_blank">
|
|
||||||
<i class="mdi mdi-application-export"></i>
|
|
||||||
<span class="nav-text">HTTPFS</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>
|
|
|
@ -1,80 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
|
|
||||||
<head>
|
|
||||||
{{template "header.html" "Routes"}}
|
|
||||||
</head>
|
|
||||||
|
|
||||||
|
|
||||||
<body class="header-fixed sidebar-fixed sidebar-dark header-light" id="body">
|
|
||||||
<div class="wrapper">
|
|
||||||
{{template "navbar.html" "routes"}}
|
|
||||||
|
|
||||||
<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 id="template_target"></div>
|
|
||||||
|
|
||||||
<div class="card card-default">
|
|
||||||
<div class="card-header card-header-border-bottom">
|
|
||||||
<h2>Add New Magnet to a Route</h2>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<form id="new-magnet">
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Magnet Link</label>
|
|
||||||
<input type="text" id="magnet-url" class="form-control"
|
|
||||||
placeholder="Enter Valid Magnet Link">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Route</label>
|
|
||||||
<select class="form-control" id="route-string">
|
|
||||||
{{range .}}
|
|
||||||
<option value="{{.Name}}">{{.Name}}</option>
|
|
||||||
{{end}}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<button type="submit" class="btn btn-primary btn-default">Add</button>
|
|
||||||
</div>
|
|
||||||
<div class="form-footer pt-4 pt-5 mt-4 border-top">
|
|
||||||
<div class="sk-double-bounce" id="submit_magnet_loading" style="display: none;">
|
|
||||||
<div class="double-bounce1"></div>
|
|
||||||
<div class="double-bounce2"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<footer class="footer mt-auto">
|
|
||||||
<div class="copyright bg-white">
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{template "footer.html"}}
|
|
||||||
|
|
||||||
<script src="assets/js/routes.js"></script>
|
|
||||||
<script>
|
|
||||||
tstor.routes.loadView();
|
|
||||||
setInterval(function () {
|
|
||||||
tstor.routes.loadView();
|
|
||||||
}, 2000)
|
|
||||||
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
|
@ -1,47 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
|
|
||||||
<head>
|
|
||||||
{{template "header.html" "Served Folders"}}
|
|
||||||
</head>
|
|
||||||
|
|
||||||
|
|
||||||
<body class="header-fixed sidebar-fixed sidebar-dark header-light" id="body">
|
|
||||||
<div class="wrapper">
|
|
||||||
{{template "navbar.html" "served-folders"}}
|
|
||||||
|
|
||||||
<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>
|
|
||||||
<footer class="footer mt-auto">
|
|
||||||
<div class="copyright bg-white">
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{template "footer.html"}}
|
|
||||||
|
|
||||||
<script src="assets/js/servers.js"></script>
|
|
||||||
<script>
|
|
||||||
tstor.servers.loadView();
|
|
||||||
setInterval(function () {
|
|
||||||
tstor.servers.loadView();
|
|
||||||
}, 5000)
|
|
||||||
|
|
||||||
</script>
|
|
||||||
<script>
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
1
tools.go
|
@ -1,7 +1,6 @@
|
||||||
//go:build tools
|
//go:build tools
|
||||||
// +build tools
|
// +build tools
|
||||||
|
|
||||||
//go:generate go run github.com/99designs/gqlgen
|
|
||||||
package tstor
|
package tstor
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|