First functional web interface version.
- Simplify by now all html and javascript. - Added a simple interface using go templates and plain javascript. - Improved REST interface for the use case. - Changed some properties into the config file to make it suitable for future use cases. Signed-off-by: Antonio Navarro Perez <antnavper@gmail.com>
This commit is contained in:
parent
80ed4e9e1e
commit
ecd524ed3c
34 changed files with 585 additions and 7383 deletions
3
torrent/.gitignore
vendored
3
torrent/.gitignore
vendored
|
@ -1,5 +1,2 @@
|
|||
_DATA
|
||||
assets_vfsdata.go
|
||||
assets
|
||||
front/.cache
|
||||
**/node_modules/
|
|
@ -8,3 +8,4 @@ import (
|
|||
)
|
||||
|
||||
var Assets http.FileSystem = http.Dir("assets")
|
||||
var Templates http.FileSystem = http.Dir("templates")
|
||||
|
|
7
torrent/assets/css/bootstrap.min.css
vendored
Normal file
7
torrent/assets/css/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
torrent/assets/css/bootstrap.min.css.map
Normal file
1
torrent/assets/css/bootstrap.min.css.map
Normal file
File diff suppressed because one or more lines are too long
7
torrent/assets/js/Chart.bundle.min.js
vendored
Normal file
7
torrent/assets/js/Chart.bundle.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
7
torrent/assets/js/bootstrap.bundle.min.js
vendored
Normal file
7
torrent/assets/js/bootstrap.bundle.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
torrent/assets/js/bootstrap.bundle.min.js.map
Normal file
1
torrent/assets/js/bootstrap.bundle.min.js.map
Normal file
File diff suppressed because one or more lines are too long
39
torrent/assets/js/file_chunks.js
Normal file
39
torrent/assets/js/file_chunks.js
Normal file
|
@ -0,0 +1,39 @@
|
|||
function FileChunks() {
|
||||
this.update = function (chunks, totalPieces, hash) {
|
||||
var dom = document.getElementById("file-chunks-" + hash);
|
||||
dom.innerHTML = "";
|
||||
chunks.forEach(chunk => {
|
||||
dom.appendChild(getPrintedChunk(chunk.status, chunk.numPieces, totalPieces));
|
||||
});
|
||||
};
|
||||
var pieceStatus = {
|
||||
"H": { class: "bg-warning", tooltip: "checking pieces" },
|
||||
"P": { class: "bg-info", tooltip: "" },
|
||||
"C": { class: "bg-success", tooltip: "downloaded pieces" },
|
||||
"W": { class: "bg-transparent" },
|
||||
"?": { class: "bg-danger", tooltip: "erroed pieces" },
|
||||
};
|
||||
|
||||
var getPrintedChunk = function (status, pieces, totalPieces) {
|
||||
var percentage = totalPieces * pieces / 100;
|
||||
var pcMeta = pieceStatus[status]
|
||||
var pieceStatusClass = pcMeta.class;
|
||||
var pieceStatusTip = pcMeta.tooltip;
|
||||
|
||||
var div = document.createElement("div");
|
||||
div.className = "progress-bar " + pieceStatusClass;
|
||||
div.setAttribute("role", "progressbar");
|
||||
|
||||
if (pieceStatusTip) {
|
||||
div.setAttribute("data-toggle", "tooltip");
|
||||
div.setAttribute("data-placement", "top");
|
||||
div.setAttribute("title", pieceStatusTip);
|
||||
}
|
||||
|
||||
div.style.cssText = "width: " + percentage + "%";
|
||||
|
||||
return div;
|
||||
};
|
||||
};
|
||||
|
||||
|
35
torrent/assets/js/general.js
Normal file
35
torrent/assets/js/general.js
Normal file
|
@ -0,0 +1,35 @@
|
|||
GeneralChart.init();
|
||||
var cacheChart = new SingleBarChart("chart-cache", "Cache disk");
|
||||
var workerChart = new SingleBarChart("chart-workers", "Workers");
|
||||
|
||||
fetchData();
|
||||
setInterval(function () {
|
||||
fetchData();
|
||||
}, 2000)
|
||||
|
||||
function fetchData() {
|
||||
fetch('/api/status')
|
||||
.then(function (response) {
|
||||
if (response.ok) {
|
||||
return response.json();
|
||||
} else {
|
||||
console.log('Error getting data from server. Response: ' + response.status);
|
||||
}
|
||||
}).then(function (stats) {
|
||||
var download = stats.torrentStats.downloadedBytes / stats.torrentStats.timePassed;
|
||||
var upload = stats.torrentStats.uploadedBytes / stats.torrentStats.timePassed;
|
||||
|
||||
GeneralChart.update(download, upload);
|
||||
|
||||
cacheChart.update(stats.cacheFilled, stats.cacheCapacity - stats.cacheFilled);
|
||||
workerChart.update(0, stats.poolFree - stats.poolCap);
|
||||
document.getElementById("down-speed-text").innerText =
|
||||
Humanize.bytes(download, 1024) + "/s";
|
||||
|
||||
document.getElementById("up-speed-text").innerText =
|
||||
Humanize.bytes(upload, 1024) + " /s";
|
||||
})
|
||||
.catch(function (error) {
|
||||
console.log('Error getting status info: ' + error.message);
|
||||
});
|
||||
}
|
78
torrent/assets/js/general_chart.js
Normal file
78
torrent/assets/js/general_chart.js
Normal file
|
@ -0,0 +1,78 @@
|
|||
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 ctx = document.getElementById('chart-general-network').getContext('2d');
|
||||
this._chart = new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: {
|
||||
datasets: [
|
||||
{
|
||||
label: 'Download Speed',
|
||||
fill: false,
|
||||
backgroundColor: 'rgb(255, 99, 132)',
|
||||
borderColor: 'rgb(255, 99, 132)',
|
||||
borderWidth: 1,
|
||||
data: this._downloadData,
|
||||
|
||||
},
|
||||
{
|
||||
label: 'Upload Speed',
|
||||
fill: false,
|
||||
borderWidth: 1,
|
||||
data: this._uploadData,
|
||||
|
||||
},
|
||||
]
|
||||
},
|
||||
options: {
|
||||
title: {
|
||||
text: 'Download and Upload speed'
|
||||
},
|
||||
scales: {
|
||||
xAxes: [{
|
||||
scaleLabel: {
|
||||
display: false,
|
||||
},
|
||||
gridLines: {
|
||||
display: false,
|
||||
},
|
||||
ticks: {
|
||||
display: false,
|
||||
},
|
||||
type: 'time',
|
||||
}],
|
||||
yAxes: [{
|
||||
scaleLabel: {
|
||||
display: false,
|
||||
},
|
||||
type: 'linear',
|
||||
ticks: {
|
||||
userCallback: function (tick) {
|
||||
return Humanize.bytes(tick, 1024) + "/s";
|
||||
},
|
||||
beginAtZero: true
|
||||
},
|
||||
}]
|
||||
},
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -4,7 +4,7 @@ function logn(n, b) {
|
|||
return Math.log(n) / Math.log(b);
|
||||
}
|
||||
|
||||
var humanize = {
|
||||
var Humanize = {
|
||||
bytes: function (s, base) {
|
||||
if (s < 10) {
|
||||
return s.toFixed(0) + " B";
|
||||
|
@ -18,9 +18,7 @@ var humanize = {
|
|||
if (val < 10) {
|
||||
f = val.toFixed(1);
|
||||
}
|
||||
console.log("OUT", f + suffix);
|
||||
|
||||
return f + suffix;
|
||||
}
|
||||
};
|
||||
|
||||
export default humanize;
|
32
torrent/assets/js/routes.js
Normal file
32
torrent/assets/js/routes.js
Normal file
|
@ -0,0 +1,32 @@
|
|||
var fileChunks = new FileChunks();
|
||||
|
||||
fetchData();
|
||||
setInterval(function () {
|
||||
fetchData();
|
||||
}, 2000)
|
||||
|
||||
function fetchData() {
|
||||
fetch('/api/routes')
|
||||
.then(function (response) {
|
||||
if (response.ok) {
|
||||
return response.json();
|
||||
} else {
|
||||
console.log('Error getting data from server. Response: ' + response.status);
|
||||
}
|
||||
}).then(function (routes) {
|
||||
routes.forEach(route => {
|
||||
route.torrentStats.forEach(torrentStat => {
|
||||
fileChunks.update(torrentStat.pieceChunks, torrentStat.totalPieces, torrentStat.hash);
|
||||
|
||||
var download = torrentStat.downloadedBytes / torrentStat.timePassed;
|
||||
var upload = torrentStat.uploadedBytes / torrentStat.timePassed;
|
||||
document.getElementById("up-down-speed-text-" + torrentStat.hash).innerText =
|
||||
Humanize.bytes(download, 1024) + "/s down, " + Humanize.bytes(upload, 1024) + "/s up";
|
||||
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch(function (error) {
|
||||
console.log('Error getting status info: ' + error.message);
|
||||
});
|
||||
}
|
48
torrent/assets/js/single_bar_chart.js
Normal file
48
torrent/assets/js/single_bar_chart.js
Normal file
|
@ -0,0 +1,48 @@
|
|||
function SingleBarChart(id, name) {
|
||||
var ctx = document.getElementById(id).getContext('2d');
|
||||
this._used = [];
|
||||
this._free = [];
|
||||
this._chart = new Chart(ctx, {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels:[name],
|
||||
datasets: [{
|
||||
backgroundColor: "gray",
|
||||
label: "used",
|
||||
data: this._used,
|
||||
},
|
||||
{
|
||||
backgroundColor: "green",
|
||||
label: "free",
|
||||
data: this._free,
|
||||
}],
|
||||
},
|
||||
options: {
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
animation: false,
|
||||
scales: {
|
||||
xAxes: [{
|
||||
stacked: true
|
||||
}],
|
||||
yAxes: [{
|
||||
stacked: true,
|
||||
display: true,
|
||||
ticks: {
|
||||
beginAtZero: true,
|
||||
}
|
||||
}]
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
this.update = function (used, free) {
|
||||
this._used.shift();
|
||||
this._free.shift();
|
||||
this._used.push(used);
|
||||
this._free.push(free);
|
||||
|
||||
this._chart.update();
|
||||
};
|
||||
}
|
|
@ -18,4 +18,13 @@ func main() {
|
|||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
err := vfsgen.Generate(distribyted.Assets, vfsgen.Options{
|
||||
BuildTags: "release",
|
||||
VariableName: "Templates",
|
||||
PackageName: "distribyted",
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,8 +9,8 @@ type binaryFileSystem struct {
|
|||
http.FileSystem
|
||||
}
|
||||
|
||||
func NewBinaryFileSystem() *binaryFileSystem {
|
||||
return &binaryFileSystem{Assets}
|
||||
func NewBinaryFileSystem(fs http.FileSystem) *binaryFileSystem {
|
||||
return &binaryFileSystem{fs}
|
||||
}
|
||||
|
||||
func (fs *binaryFileSystem) Exists(prefix string, filepath string) bool {
|
||||
|
|
|
@ -3,6 +3,7 @@ package main
|
|||
import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
@ -19,6 +20,7 @@ import (
|
|||
"github.com/gin-gonic/gin"
|
||||
"github.com/goccy/go-yaml"
|
||||
"github.com/panjf2000/ants/v2"
|
||||
"github.com/shurcooL/httpfs/html/vfstemplate"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
@ -71,7 +73,7 @@ func main() {
|
|||
}
|
||||
|
||||
ss := stats.NewTorrent()
|
||||
mountService := mount.NewTorrent(c, pool, ss)
|
||||
mountService := mount.NewHandler(c, pool, ss)
|
||||
|
||||
sigChan := make(chan os.Signal)
|
||||
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
|
||||
|
@ -96,15 +98,25 @@ func main() {
|
|||
}
|
||||
|
||||
r := gin.Default()
|
||||
fs := distribyted.NewBinaryFileSystem()
|
||||
file, err := fs.Open("index.html")
|
||||
assets := distribyted.NewBinaryFileSystem(distribyted.Assets)
|
||||
r.Use(static.Serve("/assets", assets))
|
||||
|
||||
t, err := vfstemplate.ParseGlob(distribyted.Templates, nil, "*")
|
||||
if err != nil {
|
||||
log.Println("PUES SI QUE NO ESTá", err)
|
||||
} else {
|
||||
log.Println("FILE", file)
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
r.Use(static.Serve("/", fs))
|
||||
r.SetHTMLTemplate(t)
|
||||
|
||||
// r.LoadHTMLGlob("templates/*")
|
||||
|
||||
r.GET("/", func(c *gin.Context) {
|
||||
c.HTML(http.StatusOK, "index.html", nil)
|
||||
})
|
||||
|
||||
r.GET("/routes", func(c *gin.Context) {
|
||||
c.HTML(http.StatusOK, "routes.html", ss.RoutesStats())
|
||||
})
|
||||
|
||||
r.GET("/api/status", func(ctx *gin.Context) {
|
||||
ctx.JSON(200, gin.H{
|
||||
|
@ -113,17 +125,12 @@ func main() {
|
|||
"cacheCapacity": fc.Info().Capacity / 1024 / 1024,
|
||||
"poolCap": pool.Cap(),
|
||||
"poolFree": pool.Free(),
|
||||
"torrentStats": ss.Global(),
|
||||
"torrentStats": ss.GlobalStats(),
|
||||
})
|
||||
})
|
||||
|
||||
r.GET("/api/status/:torrent", func(ctx *gin.Context) {
|
||||
hash := ctx.Param("torrent")
|
||||
stats, err := ss.Torrent(hash)
|
||||
if err != nil {
|
||||
ctx.AbortWithError(404, err)
|
||||
}
|
||||
|
||||
r.GET("/api/routes", func(ctx *gin.Context) {
|
||||
stats := ss.RoutesStats()
|
||||
ctx.JSON(200, stats)
|
||||
})
|
||||
|
||||
|
|
|
@ -2,13 +2,17 @@
|
|||
max-cache-size: 1024
|
||||
metadata-folder-name: ./_DATA/metadata
|
||||
mountPoints:
|
||||
- path: ./_DATA/snes
|
||||
magnets:
|
||||
- uri: "magnet:?xt=urn:btih:3F8CA07909879B3102EFE1CD43E233C48E489519&dn=677+Super+Nintendo+%2F+SNES+ROMs&tr=udp%3A%2F%2Ftracker.ccc.se%3A80&tr=http%3A%2F%2Ftracker.ccc.de%2Fannounce&tr=udp%3A%2F%2Ftracker.publicbt.com%3A80&tr=http%3A%2F%2Ftracker.publicbt.com%2Fannounce&tr=udp%3A%2F%2Ftracker.openbittorrent.com%3A80&tr=http%3A%2F%2Ftracker.openbittorrent.com%2Fannounce&tr=udp%3A%2F%2Ftracker.publicbt.com%3A80%2Fannounce&tr=http%3A%2F%2Ftracker.publicbt.com%2Fannounce&tr=http%3A%2F%2Ftracker.ccc.de%2Fannounce&tr=udp%3A%2F%2Ftracker.zer0day.to%3A1337%2Fannounce&tr=udp%3A%2F%2Ftracker.leechers-paradise.org%3A6969%2Fannounce&tr=udp%3A%2F%2Fcoppersurfer.tk%3A6969%2Fannounce"
|
||||
- path: ./_DATA/nes
|
||||
magnets:
|
||||
- uri: "magnet:?xt=urn:btih:54F1FFB2861EE322B8641CEE232538A357341578&dn=706+Nintendo+%2F+NES+ROMs&tr=udp%3A%2F%2Ftracker.publicbt.com%3A80%2Fannounce&tr=udp%3A%2F%2Ftracker.openbittorrent.com%3A80%2Fannounce&tr=udp%3A%2F%2Ffr33domtracker.h33t.com%3A3310%2Fannounce&tr=udp%3A%2F%2Ftracker.istole.it%3A80%2Fannounce&tr=http%3A%2F%2Finferno.demonoid.me%3A3417%2Fannounce&tr=udp%3A%2F%2Ftracker.zer0day.to%3A1337%2Fannounce&tr=udp%3A%2F%2Ftracker.leechers-paradise.org%3A6969%2Fannounce&tr=udp%3A%2F%2Fcoppersurfer.tk%3A6969%2Fannounce"
|
||||
- path: ./_DATA/psx
|
||||
magnets:
|
||||
# - uri: "magnet:?xt=urn:btih:3D41D4E6024AA4AB905BF0E6354D57F680C654F3&dn=Sony%20PlayStation%20%28PAL%29%20%5bRedump%5d&tr=http%3a%2f%2fbt2.t-ru.org%2fann%3fmagnet"
|
||||
- uri: "magnet:?xt=urn:btih:852299c530aaed8fa06bdf32d9bd909e0bb76fe7&tr=udp%3A%2F%2Ftracker.coppersurfer.tk%3A6969%2Fannounce&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337%2Fannounce&tr=udp%3A%2F%2Ftracker.internetwarriors.net%3A1337"
|
||||
torrents:
|
||||
- magnetUri: "magnet:?xt=urn:btih:3D41D4E6024AA4AB905BF0E6354D57F680C654F3&dn=Sony%20PlayStation%20%28PAL%29%20%5bRedump%5d&tr=http%3a%2f%2fbt2.t-ru.org%2fann%3fmagnet"
|
||||
- magnetUri: "magnet:?xt=urn:btih:852299c530aaed8fa06bdf32d9bd909e0bb76fe7&tr=udp%3A%2F%2Ftracker.coppersurfer.tk%3A6969%2Fannounce&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337%2Fannounce&tr=udp%3A%2F%2Ftracker.internetwarriors.net%3A1337"
|
||||
- path: ./_DATA/wiki
|
||||
torrents:
|
||||
- torrentPath: "/home/ajnavarro/Downloads/347B211B61AC09B07A7747C85D24CA7FBD5355C1.torrent"
|
||||
- path: ./_DATA/snes
|
||||
torrents:
|
||||
- magnetUri: "magnet:?xt=urn:btih:3F8CA07909879B3102EFE1CD43E233C48E489519&dn=677+Super+Nintendo+%2F+SNES+ROMs&tr=udp%3A%2F%2Ftracker.ccc.se%3A80&tr=http%3A%2F%2Ftracker.ccc.de%2Fannounce&tr=udp%3A%2F%2Ftracker.publicbt.com%3A80&tr=http%3A%2F%2Ftracker.publicbt.com%2Fannounce&tr=udp%3A%2F%2Ftracker.openbittorrent.com%3A80&tr=http%3A%2F%2Ftracker.openbittorrent.com%2Fannounce&tr=udp%3A%2F%2Ftracker.publicbt.com%3A80%2Fannounce&tr=http%3A%2F%2Ftracker.publicbt.com%2Fannounce&tr=http%3A%2F%2Ftracker.ccc.de%2Fannounce&tr=udp%3A%2F%2Ftracker.zer0day.to%3A1337%2Fannounce&tr=udp%3A%2F%2Ftracker.leechers-paradise.org%3A6969%2Fannounce&tr=udp%3A%2F%2Fcoppersurfer.tk%3A6969%2Fannounce"
|
||||
- path: ./_DATA/nes
|
||||
torrents:
|
||||
- magnetUri: "magnet:?xt=urn:btih:54F1FFB2861EE322B8641CEE232538A357341578&dn=706+Nintendo+%2F+NES+ROMs&tr=udp%3A%2F%2Ftracker.publicbt.com%3A80%2Fannounce&tr=udp%3A%2F%2Ftracker.openbittorrent.com%3A80%2Fannounce&tr=udp%3A%2F%2Ffr33domtracker.h33t.com%3A3310%2Fannounce&tr=udp%3A%2F%2Ftracker.istole.it%3A80%2Fannounce&tr=http%3A%2F%2Finferno.demonoid.me%3A3417%2Fannounce&tr=udp%3A%2F%2Ftracker.zer0day.to%3A1337%2Fannounce&tr=udp%3A%2F%2Ftracker.leechers-paradise.org%3A6969%2Fannounce&tr=udp%3A%2F%2Fcoppersurfer.tk%3A6969%2Fannounce"
|
||||
|
||||
|
|
|
@ -10,10 +10,11 @@ type Root struct {
|
|||
|
||||
type MountPoint struct {
|
||||
Path string `yaml:"path"`
|
||||
Magnets []struct {
|
||||
URI string `yaml:"uri"`
|
||||
Torrents []struct {
|
||||
MagnetURI string `yaml:"magnetUri"`
|
||||
TorrentPath string `yaml:"torrentPath"`
|
||||
FolderName string `yaml:"folderName,omitempty"`
|
||||
} `yaml:"magnets"`
|
||||
} `yaml:"torrents"`
|
||||
}
|
||||
|
||||
func AddDefaults(r *Root) *Root {
|
||||
|
|
7
torrent/front/css/bootstrap.min.css
vendored
7
torrent/front/css/bootstrap.min.css
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1,25 +0,0 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<!-- Required meta tags -->
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
|
||||
<!-- Bootstrap CSS -->
|
||||
<link rel="stylesheet" href="css/bootstrap.min.css"
|
||||
integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
|
||||
|
||||
<title>Hello, world!</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Hello, world!</h1>
|
||||
<div class="container">
|
||||
<canvas id="chart-general-network"></canvas>
|
||||
<canvas id="chart-file-chunks"></canvas>
|
||||
</div>
|
||||
<script src="js/main.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -1,165 +0,0 @@
|
|||
import Humanize from './humanize.js';
|
||||
import Chart from 'chart.js';
|
||||
import _ from 'chartjs-plugin-stacked100';
|
||||
|
||||
var ctx = document.getElementById('chart-general-network').getContext('2d');
|
||||
|
||||
var downloadData = [];
|
||||
var uploadData = [];
|
||||
var labels = [];
|
||||
|
||||
var chart = new Chart(ctx, {
|
||||
type: 'line',
|
||||
labels: labels,
|
||||
data: {
|
||||
datasets: [
|
||||
{
|
||||
label: 'Download Speed',
|
||||
fill: false,
|
||||
backgroundColor: 'rgb(255, 99, 132)',
|
||||
borderColor: 'rgb(255, 99, 132)',
|
||||
borderWidth: 1,
|
||||
data: downloadData,
|
||||
|
||||
},
|
||||
{
|
||||
label: 'Upload Speed',
|
||||
fill: false,
|
||||
borderWidth: 1,
|
||||
data: uploadData,
|
||||
|
||||
},
|
||||
]
|
||||
},
|
||||
options: {
|
||||
title: {
|
||||
text: 'Download and Upload speed'
|
||||
},
|
||||
scales: {
|
||||
xAxes: [{
|
||||
scaleLabel: {
|
||||
display: true,
|
||||
labelString: 'Date'
|
||||
},
|
||||
type: 'time',
|
||||
time: {
|
||||
// parser: timeFormat,
|
||||
tooltipFormat: 'll HH:mm'
|
||||
},
|
||||
|
||||
}],
|
||||
yAxes: [{
|
||||
scaleLabel: {
|
||||
display: true,
|
||||
labelString: 'value'
|
||||
},
|
||||
type: 'linear',
|
||||
ticks: {
|
||||
userCallback: function (tick) {
|
||||
return Humanize.bytes(tick, 1024) + "/s";
|
||||
},
|
||||
beginAtZero: true
|
||||
},
|
||||
}]
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
var piecesData = []
|
||||
var fileChart = new Chart(document.getElementById("chart-file-chunks"), {
|
||||
type: "horizontalBar",
|
||||
data: {
|
||||
labels: ["File"],
|
||||
datasets: piecesData,
|
||||
},
|
||||
options: {
|
||||
animation: {
|
||||
duration: 0
|
||||
},
|
||||
plugins: {
|
||||
stacked100: { enable: true }
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
setInterval(function () {
|
||||
fetch('/api/status/852299c530aaed8fa06bdf32d9bd909e0bb76fe7')
|
||||
.then(function (response) {
|
||||
if (response.ok) {
|
||||
return response.json();
|
||||
} else {
|
||||
console.log('Error getting data from server. Response: ' + response.status);
|
||||
}
|
||||
}).then(function (stats) {
|
||||
piecesData.length = 0;
|
||||
stats.PieceChunks.forEach(element => {
|
||||
var label, color;
|
||||
switch (element.Status) {
|
||||
case "H":
|
||||
label = "checking";
|
||||
color = "#8a5999";
|
||||
break;
|
||||
case "P":
|
||||
label = "partial";
|
||||
color = "#be9600";
|
||||
break;
|
||||
case "C":
|
||||
label = "complete";
|
||||
color = "#208f09";
|
||||
break;
|
||||
case "W":
|
||||
label = "waiting";
|
||||
color = "#8a5999";
|
||||
break;
|
||||
case "?":
|
||||
label = "error";
|
||||
color = "#ff5f5c";
|
||||
break;
|
||||
default:
|
||||
label = "unknown";
|
||||
color = "gray";
|
||||
break;
|
||||
}
|
||||
piecesData.push({
|
||||
label: label,
|
||||
data: [element.NumPieces],
|
||||
backgroundColor: color,
|
||||
});
|
||||
|
||||
});
|
||||
fileChart.update();
|
||||
})
|
||||
}, 2000)
|
||||
|
||||
setInterval(function () {
|
||||
fetch('/api/status')
|
||||
.then(function (response) {
|
||||
if (response.ok) {
|
||||
return response.json();
|
||||
} else {
|
||||
console.log('Error getting data from server. Response: ' + response.status);
|
||||
}
|
||||
}).then(function (stats) {
|
||||
if (downloadData.length > 20) {
|
||||
uploadData.shift();
|
||||
downloadData.shift();
|
||||
labels.shift();
|
||||
}
|
||||
|
||||
var date = new Date();
|
||||
downloadData.push({
|
||||
x: date,
|
||||
y: stats.torrentStats.DownloadedBytes / stats.torrentStats.TimePassed,
|
||||
});
|
||||
uploadData.push({
|
||||
x: date,
|
||||
y: stats.torrentStats.UploadedBytes / stats.torrentStats.TimePassed,
|
||||
});
|
||||
labels.push(date);
|
||||
chart.update();
|
||||
})
|
||||
.catch(function (error) {
|
||||
console.log('Error getting status info: ' + error.message);
|
||||
});
|
||||
}, 2000)
|
7076
torrent/front/package-lock.json
generated
7076
torrent/front/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -1,20 +0,0 @@
|
|||
{
|
||||
"name": "distribyted",
|
||||
"version": "0.1.0",
|
||||
"description": "",
|
||||
"dependencies": {
|
||||
"bootstrap": "^4.4.1",
|
||||
"bootstrap-vue": "^2.14.0",
|
||||
"chart.js": "^2.9.3",
|
||||
"chartjs-plugin-stacked100": "^0.7.1",
|
||||
"portal-vue": "^2.1.7",
|
||||
"vue": "^2.6.11"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "parcel watch index.html --out-dir ../assets",
|
||||
"build": "parcel build index.html"
|
||||
},
|
||||
"devDependencies": {
|
||||
"parcel-bundler": "^1.12.4"
|
||||
}
|
||||
}
|
|
@ -10,7 +10,6 @@ require (
|
|||
github.com/anacrolix/missinggo/v2 v2.4.1-0.20200227072623-f02f6484f997
|
||||
github.com/anacrolix/multiless v0.0.0-20200413040533-acfd16f65d5d // indirect
|
||||
github.com/anacrolix/torrent v1.15.2
|
||||
github.com/dustin/go-humanize v1.0.0
|
||||
github.com/elliotchance/orderedmap v1.2.2 // indirect
|
||||
github.com/fatih/color v1.9.0 // indirect
|
||||
github.com/gin-contrib/static v0.0.0-20191128031702-f81c604d8ac2
|
||||
|
@ -25,6 +24,7 @@ require (
|
|||
github.com/modern-go/reflect2 v1.0.1 // indirect
|
||||
github.com/mschoch/smat v0.2.0 // indirect
|
||||
github.com/panjf2000/ants/v2 v2.3.1
|
||||
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749
|
||||
github.com/tinylib/msgp v1.1.2 // indirect
|
||||
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f // indirect
|
||||
golang.org/x/sys v0.0.0-20200509044756-6aff5f38e54f // indirect
|
||||
|
|
|
@ -310,6 +310,8 @@ github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R
|
|||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46 h1:GHRpF1pTW19a8tTFrMLUcfWwyC0pnifVo2ClaLq+hP8=
|
||||
github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46/go.mod h1:uAQ5PCi+MFsC7HjREoAz1BU+Mq60+05gifQSsHSDG/8=
|
||||
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 h1:bUGsEnyNbVPw06Bs80sCeARAlK8lhwqGyi6UT8ymuGk=
|
||||
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/assertions v0.0.0-20190215210624-980c5ac6f3ac/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package mount
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
|
@ -13,7 +14,7 @@ import (
|
|||
"github.com/panjf2000/ants/v2"
|
||||
)
|
||||
|
||||
type Torrent struct {
|
||||
type Handler struct {
|
||||
c *torrent.Client
|
||||
s *stats.Torrent
|
||||
opts *fs.Options
|
||||
|
@ -22,8 +23,8 @@ type Torrent struct {
|
|||
servers map[string]*fuse.Server
|
||||
}
|
||||
|
||||
func NewTorrent(c *torrent.Client, pool *ants.Pool, s *stats.Torrent) *Torrent {
|
||||
return &Torrent{
|
||||
func NewHandler(c *torrent.Client, pool *ants.Pool, s *stats.Torrent) *Handler {
|
||||
return &Handler{
|
||||
c: c,
|
||||
s: s,
|
||||
opts: &fs.Options{},
|
||||
|
@ -32,17 +33,31 @@ func NewTorrent(c *torrent.Client, pool *ants.Pool, s *stats.Torrent) *Torrent {
|
|||
}
|
||||
}
|
||||
|
||||
func (s *Torrent) Mount(mpc *config.MountPoint) error {
|
||||
func (s *Handler) Mount(mpc *config.MountPoint) error {
|
||||
var torrents []*torrent.Torrent
|
||||
for _, magnet := range mpc.Magnets {
|
||||
t, err := s.c.AddMagnet(magnet.URI)
|
||||
for _, mpcTorrent := range mpc.Torrents {
|
||||
var t *torrent.Torrent
|
||||
var err error
|
||||
|
||||
switch {
|
||||
case mpcTorrent.MagnetURI != "":
|
||||
t, err = s.c.AddMagnet(mpcTorrent.MagnetURI)
|
||||
break
|
||||
case mpcTorrent.TorrentPath != "":
|
||||
t, err = s.c.AddTorrentFromFile(mpcTorrent.TorrentPath)
|
||||
break
|
||||
default:
|
||||
err = fmt.Errorf("no magnet URI or torrent path provided")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Println("getting torrent info", t.Name())
|
||||
<-t.GotInfo()
|
||||
|
||||
s.s.Add(t)
|
||||
s.s.Add(mpc.Path, t)
|
||||
|
||||
log.Println("torrent info obtained", t.Name())
|
||||
torrents = append(torrents, t)
|
||||
|
@ -65,7 +80,7 @@ func (s *Torrent) Mount(mpc *config.MountPoint) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *Torrent) Close() {
|
||||
func (s *Handler) Close() {
|
||||
for path, server := range s.servers {
|
||||
log.Println("unmounting", path)
|
||||
err := server.Unmount()
|
57
torrent/package-lock.json
generated
Normal file
57
torrent/package-lock.json
generated
Normal file
|
@ -0,0 +1,57 @@
|
|||
{
|
||||
"requires": true,
|
||||
"lockfileVersion": 1,
|
||||
"dependencies": {
|
||||
"chart.js": {
|
||||
"version": "2.9.3",
|
||||
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-2.9.3.tgz",
|
||||
"integrity": "sha512-+2jlOobSk52c1VU6fzkh3UwqHMdSlgH1xFv9FKMqHiNCpXsGPQa/+81AFa+i3jZ253Mq9aAycPwDjnn1XbRNNw==",
|
||||
"requires": {
|
||||
"chartjs-color": "^2.1.0",
|
||||
"moment": "^2.10.2"
|
||||
}
|
||||
},
|
||||
"chartjs-color": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/chartjs-color/-/chartjs-color-2.4.1.tgz",
|
||||
"integrity": "sha512-haqOg1+Yebys/Ts/9bLo/BqUcONQOdr/hoEr2LLTRl6C5LXctUdHxsCYfvQVg5JIxITrfCNUDr4ntqmQk9+/0w==",
|
||||
"requires": {
|
||||
"chartjs-color-string": "^0.6.0",
|
||||
"color-convert": "^1.9.3"
|
||||
}
|
||||
},
|
||||
"chartjs-color-string": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/chartjs-color-string/-/chartjs-color-string-0.6.0.tgz",
|
||||
"integrity": "sha512-TIB5OKn1hPJvO7JcteW4WY/63v6KwEdt6udfnDE9iCAZgy+V4SrbSxoIbTw/xkUIapjEI4ExGtD0+6D3KyFd7A==",
|
||||
"requires": {
|
||||
"color-name": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"color-convert": {
|
||||
"version": "1.9.3",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
||||
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
|
||||
"requires": {
|
||||
"color-name": "1.1.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"color-name": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
||||
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
|
||||
}
|
||||
}
|
||||
},
|
||||
"color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
||||
},
|
||||
"moment": {
|
||||
"version": "2.25.3",
|
||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.25.3.tgz",
|
||||
"integrity": "sha512-PuYv0PHxZvzc15Sp8ybUCoQ+xpyPWvjOuK72a5ovzp2LI32rJXOiIfyoFoYvG3s6EwwrdkMyWuRiEHSZRLJNdg=="
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,12 +2,10 @@ package stats
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/anacrolix/torrent"
|
||||
"github.com/dustin/go-humanize"
|
||||
)
|
||||
|
||||
var ErrTorrentNotFound = errors.New("torrent not found")
|
||||
|
@ -23,39 +21,29 @@ const (
|
|||
)
|
||||
|
||||
type PieceChunk struct {
|
||||
Status PieceStatus
|
||||
NumPieces int
|
||||
Status PieceStatus `json:"status"`
|
||||
NumPieces int `json:"numPieces"`
|
||||
}
|
||||
|
||||
type TorrentStats struct {
|
||||
DownloadedBytes int64
|
||||
UploadedBytes int64
|
||||
TimePassed float64
|
||||
PieceChunks []*PieceChunk
|
||||
Name string `json:"name"`
|
||||
Hash string `json:"hash"`
|
||||
DownloadedBytes int64 `json:"downloadedBytes"`
|
||||
UploadedBytes int64 `json:"uploadedBytes"`
|
||||
TimePassed float64 `json:"timePassed"`
|
||||
PieceChunks []*PieceChunk `json:"pieceChunks"`
|
||||
TotalPieces int `json:"totalPieces"`
|
||||
}
|
||||
|
||||
type GlobalTorrentStats struct {
|
||||
DownloadedBytes int64
|
||||
UploadedBytes int64
|
||||
TimePassed float64
|
||||
DownloadedBytes int64 `json:"downloadedBytes"`
|
||||
UploadedBytes int64 `json:"uploadedBytes"`
|
||||
TimePassed float64 `json:"timePassed"`
|
||||
}
|
||||
|
||||
func (s *GlobalTorrentStats) speed(bytes int64) float64 {
|
||||
var bs float64
|
||||
t := s.TimePassed
|
||||
if t != 0 {
|
||||
bs = float64(bytes) / t
|
||||
}
|
||||
|
||||
return bs
|
||||
}
|
||||
|
||||
func (s *GlobalTorrentStats) DownloadSpeed() string {
|
||||
return fmt.Sprintf(" %s/s", humanize.IBytes(uint64(s.speed(s.DownloadedBytes))))
|
||||
}
|
||||
|
||||
func (s *GlobalTorrentStats) UploadSpeed() string {
|
||||
return fmt.Sprintf(" %s/s", humanize.IBytes(uint64(s.speed(s.UploadedBytes))))
|
||||
type RouteStats struct {
|
||||
Name string `json:"name"`
|
||||
TorrentStats []*TorrentStats `json:"torrentStats"`
|
||||
}
|
||||
|
||||
type stats struct {
|
||||
|
@ -68,6 +56,7 @@ type stats struct {
|
|||
|
||||
type Torrent struct {
|
||||
torrents map[string]*torrent.Torrent
|
||||
torrentsByRoute map[string][]*torrent.Torrent
|
||||
previousStats map[string]*stats
|
||||
|
||||
gTime time.Time
|
||||
|
@ -77,16 +66,20 @@ func NewTorrent() *Torrent {
|
|||
return &Torrent{
|
||||
gTime: time.Now(),
|
||||
torrents: make(map[string]*torrent.Torrent),
|
||||
torrentsByRoute: make(map[string][]*torrent.Torrent),
|
||||
previousStats: make(map[string]*stats),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Torrent) Add(t *torrent.Torrent) {
|
||||
func (s *Torrent) Add(route string, t *torrent.Torrent) {
|
||||
s.torrents[t.InfoHash().String()] = t
|
||||
s.previousStats[t.InfoHash().String()] = &stats{}
|
||||
|
||||
tbr := s.torrentsByRoute[route]
|
||||
s.torrentsByRoute[route] = append(tbr, t)
|
||||
}
|
||||
|
||||
func (s *Torrent) Torrent(hash string) (*TorrentStats, error) {
|
||||
func (s *Torrent) Stats(hash string) (*TorrentStats, error) {
|
||||
t, ok := s.torrents[hash]
|
||||
if !(ok) {
|
||||
return nil, ErrTorrentNotFound
|
||||
|
@ -97,16 +90,27 @@ func (s *Torrent) Torrent(hash string) (*TorrentStats, error) {
|
|||
return s.stats(now, t, true), nil
|
||||
}
|
||||
|
||||
func (s *Torrent) List() []string {
|
||||
var result []string
|
||||
for hash := range s.torrents {
|
||||
result = append(result, hash)
|
||||
func (s *Torrent) RoutesStats() []*RouteStats {
|
||||
now := time.Now()
|
||||
|
||||
var out []*RouteStats
|
||||
for r, tl := range s.torrentsByRoute {
|
||||
var tStats []*TorrentStats
|
||||
for _, t := range tl {
|
||||
ts := s.stats(now, t, true)
|
||||
tStats = append(tStats, ts)
|
||||
}
|
||||
rs := &RouteStats{
|
||||
Name: r,
|
||||
TorrentStats: tStats,
|
||||
}
|
||||
out = append(out, rs)
|
||||
}
|
||||
|
||||
return result
|
||||
return out
|
||||
}
|
||||
|
||||
func (s *Torrent) Global() *GlobalTorrentStats {
|
||||
func (s *Torrent) GlobalStats() *GlobalTorrentStats {
|
||||
now := time.Now()
|
||||
|
||||
var totalDownload int64
|
||||
|
@ -155,7 +159,7 @@ func (s *Torrent) stats(now time.Time, t *torrent.Torrent, chunks bool) *Torrent
|
|||
}
|
||||
|
||||
ts.TimePassed = now.Sub(prev.time).Seconds()
|
||||
|
||||
var totalPieces int
|
||||
if chunks {
|
||||
var pch []*PieceChunk
|
||||
for _, psr := range t.PieceStateRuns() {
|
||||
|
@ -177,10 +181,14 @@ func (s *Torrent) stats(now time.Time, t *torrent.Torrent, chunks bool) *Torrent
|
|||
Status: s,
|
||||
NumPieces: psr.Length,
|
||||
})
|
||||
totalPieces += psr.Length
|
||||
}
|
||||
ts.PieceChunks = pch
|
||||
}
|
||||
|
||||
ts.Hash = t.InfoHash().String()
|
||||
ts.Name = t.Name()
|
||||
ts.TotalPieces = totalPieces
|
||||
return ts
|
||||
}
|
||||
|
||||
|
|
7
torrent/templates/common_footer.html
Normal file
7
torrent/templates/common_footer.html
Normal file
|
@ -0,0 +1,7 @@
|
|||
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"
|
||||
integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj"
|
||||
crossorigin="anonymous"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js"
|
||||
integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo"
|
||||
crossorigin="anonymous"></script>
|
||||
<script src="assets/js/bootstrap.bundle.min.js"></script>
|
8
torrent/templates/common_header.html
Normal file
8
torrent/templates/common_header.html
Normal file
|
@ -0,0 +1,8 @@
|
|||
<!-- Required meta tags -->
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
|
||||
<!-- Bootstrap CSS -->
|
||||
<link rel="stylesheet" href="assets/css/bootstrap.min.css">
|
||||
|
||||
<title>{{.Title}}</title>
|
59
torrent/templates/index.html
Normal file
59
torrent/templates/index.html
Normal file
|
@ -0,0 +1,59 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
{{template "common_header.html"}}
|
||||
</head>
|
||||
|
||||
<body>
|
||||
{{template "navbar.html"}}
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<canvas id="chart-general-network"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm">
|
||||
<canvas id="chart-cache" height="400"></canvas>
|
||||
</div>
|
||||
<div class="col-sm">
|
||||
<h3>
|
||||
<svg class="bi bi-arrow-down" width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd"
|
||||
d="M4.646 9.646a.5.5 0 01.708 0L8 12.293l2.646-2.647a.5.5 0 01.708.708l-3 3a.5.5 0 01-.708 0l-3-3a.5.5 0 010-.708z"
|
||||
clip-rule="evenodd" />
|
||||
<path fill-rule="evenodd" d="M8 2.5a.5.5 0 01.5.5v9a.5.5 0 01-1 0V3a.5.5 0 01.5-.5z"
|
||||
clip-rule="evenodd" />
|
||||
</svg>
|
||||
<span id="down-speed-text"></span>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="col-sm">
|
||||
<h3>
|
||||
<svg class="bi bi-arrow-up" width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" d="M8 3.5a.5.5 0 01.5.5v9a.5.5 0 01-1 0V4a.5.5 0 01.5-.5z"
|
||||
clip-rule="evenodd" />
|
||||
<path fill-rule="evenodd"
|
||||
d="M7.646 2.646a.5.5 0 01.708 0l3 3a.5.5 0 01-.708.708L8 3.707 5.354 6.354a.5.5 0 11-.708-.708l3-3z"
|
||||
clip-rule="evenodd" />
|
||||
</svg>
|
||||
<span id="up-speed-text"></span>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="col-sm">
|
||||
<canvas id="chart-workers" height="400"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{template "common_footer.html"}}
|
||||
<script src="assets/js/Chart.bundle.min.js"></script>
|
||||
<script src="assets/js/humanize.js"></script>
|
||||
<script src="assets/js/general_chart.js"></script>
|
||||
<script src="assets/js/single_bar_chart.js"></script>
|
||||
<script src="assets/js/general.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
17
torrent/templates/navbar.html
Normal file
17
torrent/templates/navbar.html
Normal file
|
@ -0,0 +1,17 @@
|
|||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||
<a class="navbar-brand" href="#">Distribyted</a>
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav"
|
||||
aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/">General</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/routes">Routes</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
51
torrent/templates/routes.html
Normal file
51
torrent/templates/routes.html
Normal file
|
@ -0,0 +1,51 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
{{template "common_header.html"}}
|
||||
</head>
|
||||
|
||||
<body>
|
||||
{{template "navbar.html"}}
|
||||
|
||||
<div class="container">
|
||||
{{if not .}}
|
||||
<div class="alert alert-warning" role="alert">
|
||||
No routes found.
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{range .}}
|
||||
<div class="card shadow">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">{{.Name}}</h5>
|
||||
{{range .TorrentStats}}
|
||||
<div class="card shadow">
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<h5>{{.Name}}</h5>
|
||||
</div>
|
||||
<div class="col">
|
||||
<p id="up-down-speed-text-{{.Hash}}">...</p>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div id="file-chunks-{{.Hash}}" class="progress">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
{{template "common_footer.html"}}
|
||||
<script src="assets/js/humanize.js"></script>
|
||||
<script src="assets/js/file_chunks.js"></script>
|
||||
<script src="assets/js/routes.js"></script>
|
||||
</body>
|
||||
</body>
|
||||
|
||||
</html>
|
Loading…
Reference in a new issue