remove distribyted ui
All checks were successful
docker / build-docker (linux/arm64) (push) Successful in 1m48s
docker / build-docker (linux/amd64) (push) Successful in 1m54s

This commit is contained in:
royalcat 2024-07-09 00:24:42 +03:00
parent 0371af3344
commit 93892a6f1d
62 changed files with 1 additions and 3254 deletions

View file

@ -1,4 +1,4 @@
FROM --platform=$BUILDPLATFORM golang:1.22 as builder
FROM --platform=$BUILDPLATFORM golang:1.22 AS builder
WORKDIR /app
@ -9,16 +9,10 @@ RUN --mount=type=cache,mode=0777,target=/go/pkg/mod go mod download all
COPY ./pkg ./pkg
COPY ./src ./src
COPY ./cmd ./cmd
COPY ./assets ./assets
COPY ./templates ./templates
COPY embed.go embed.go
RUN go generate ./...
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
FROM scratch
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -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();
};
}

View file

@ -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;
}
}
});

View file

@ -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);
});
},
};

View file

@ -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);
});
},
};

View file

@ -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
}
}
});
}
}

View file

@ -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;
}
};

View file

@ -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));
},
};

View file

@ -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";
});
});

View file

@ -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);
})
);
},
};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -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);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -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>
{{/.}}

View file

@ -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>
{{/.}}

View file

@ -1,11 +0,0 @@
package tstor
import (
"embed"
)
//go:embed assets
var Assets embed.FS
//go:embed templates
var Templates embed.FS

View file

@ -1 +0,0 @@
Gif generated using https://ezgif.com with delay=200

View file

@ -1 +0,0 @@
tstor.com

View file

@ -1 +0,0 @@
TBD

View file

@ -1 +0,0 @@
TBD

View file

@ -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)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

View file

@ -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.

View file

@ -1 +0,0 @@
TBD

View file

@ -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

View file

@ -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

View file

@ -28,39 +28,10 @@ func New(fc *filecache.Cache, s *torrent.Daemon, vfs vfs.Filesystem, logPath str
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))))
// 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))
// 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)))
return nil

View file

@ -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>

View file

@ -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" />

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -1,7 +1,6 @@
//go:build tools
// +build tools
//go:generate go run github.com/99designs/gqlgen
package tstor
import (