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:
Antonio Navarro Perez 2020-05-18 19:42:23 +02:00
parent 80ed4e9e1e
commit ecd524ed3c
34 changed files with 585 additions and 7383 deletions

3
torrent/.gitignore vendored
View file

@ -1,5 +1,2 @@
_DATA _DATA
assets_vfsdata.go assets_vfsdata.go
assets
front/.cache
**/node_modules/

View file

@ -8,3 +8,4 @@ import (
) )
var Assets http.FileSystem = http.Dir("assets") var Assets http.FileSystem = http.Dir("assets")
var Templates http.FileSystem = http.Dir("templates")

7
torrent/assets/css/bootstrap.min.css vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

7
torrent/assets/js/Chart.bundle.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

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

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

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

View file

@ -4,7 +4,7 @@ function logn(n, b) {
return Math.log(n) / Math.log(b); return Math.log(n) / Math.log(b);
} }
var humanize = { var Humanize = {
bytes: function (s, base) { bytes: function (s, base) {
if (s < 10) { if (s < 10) {
return s.toFixed(0) + " B"; return s.toFixed(0) + " B";
@ -18,9 +18,7 @@ var humanize = {
if (val < 10) { if (val < 10) {
f = val.toFixed(1); f = val.toFixed(1);
} }
console.log("OUT", f + suffix);
return f + suffix; return f + suffix;
} }
}; };
export default humanize;

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

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

View file

@ -18,4 +18,13 @@ func main() {
if err != nil { if err != nil {
log.Fatalln(err) log.Fatalln(err)
} }
err := vfsgen.Generate(distribyted.Assets, vfsgen.Options{
BuildTags: "release",
VariableName: "Templates",
PackageName: "distribyted",
})
if err != nil {
log.Fatalln(err)
}
} }

View file

@ -9,8 +9,8 @@ type binaryFileSystem struct {
http.FileSystem http.FileSystem
} }
func NewBinaryFileSystem() *binaryFileSystem { func NewBinaryFileSystem(fs http.FileSystem) *binaryFileSystem {
return &binaryFileSystem{Assets} return &binaryFileSystem{fs}
} }
func (fs *binaryFileSystem) Exists(prefix string, filepath string) bool { func (fs *binaryFileSystem) Exists(prefix string, filepath string) bool {

View file

@ -3,6 +3,7 @@ package main
import ( import (
"io/ioutil" "io/ioutil"
"log" "log"
"net/http"
"os" "os"
"os/signal" "os/signal"
"syscall" "syscall"
@ -19,6 +20,7 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/goccy/go-yaml" "github.com/goccy/go-yaml"
"github.com/panjf2000/ants/v2" "github.com/panjf2000/ants/v2"
"github.com/shurcooL/httpfs/html/vfstemplate"
) )
func main() { func main() {
@ -71,7 +73,7 @@ func main() {
} }
ss := stats.NewTorrent() ss := stats.NewTorrent()
mountService := mount.NewTorrent(c, pool, ss) mountService := mount.NewHandler(c, pool, ss)
sigChan := make(chan os.Signal) sigChan := make(chan os.Signal)
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM) signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
@ -96,15 +98,25 @@ func main() {
} }
r := gin.Default() r := gin.Default()
fs := distribyted.NewBinaryFileSystem() assets := distribyted.NewBinaryFileSystem(distribyted.Assets)
file, err := fs.Open("index.html") r.Use(static.Serve("/assets", assets))
t, err := vfstemplate.ParseGlob(distribyted.Templates, nil, "*")
if err != nil { if err != nil {
log.Println("PUES SI QUE NO ESTá", err) log.Fatal(err)
} else {
log.Println("FILE", file)
} }
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) { r.GET("/api/status", func(ctx *gin.Context) {
ctx.JSON(200, gin.H{ ctx.JSON(200, gin.H{
@ -113,17 +125,12 @@ func main() {
"cacheCapacity": fc.Info().Capacity / 1024 / 1024, "cacheCapacity": fc.Info().Capacity / 1024 / 1024,
"poolCap": pool.Cap(), "poolCap": pool.Cap(),
"poolFree": pool.Free(), "poolFree": pool.Free(),
"torrentStats": ss.Global(), "torrentStats": ss.GlobalStats(),
}) })
}) })
r.GET("/api/status/:torrent", func(ctx *gin.Context) { r.GET("/api/routes", func(ctx *gin.Context) {
hash := ctx.Param("torrent") stats := ss.RoutesStats()
stats, err := ss.Torrent(hash)
if err != nil {
ctx.AbortWithError(404, err)
}
ctx.JSON(200, stats) ctx.JSON(200, stats)
}) })

View file

@ -2,13 +2,17 @@
max-cache-size: 1024 max-cache-size: 1024
metadata-folder-name: ./_DATA/metadata metadata-folder-name: ./_DATA/metadata
mountPoints: 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 - path: ./_DATA/psx
magnets: torrents:
# - 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" - 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"
- 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" - 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"

View file

@ -10,10 +10,11 @@ type Root struct {
type MountPoint struct { type MountPoint struct {
Path string `yaml:"path"` Path string `yaml:"path"`
Magnets []struct { Torrents []struct {
URI string `yaml:"uri"` MagnetURI string `yaml:"magnetUri"`
TorrentPath string `yaml:"torrentPath"`
FolderName string `yaml:"folderName,omitempty"` FolderName string `yaml:"folderName,omitempty"`
} `yaml:"magnets"` } `yaml:"torrents"`
} }
func AddDefaults(r *Root) *Root { func AddDefaults(r *Root) *Root {

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

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

@ -10,7 +10,6 @@ require (
github.com/anacrolix/missinggo/v2 v2.4.1-0.20200227072623-f02f6484f997 github.com/anacrolix/missinggo/v2 v2.4.1-0.20200227072623-f02f6484f997
github.com/anacrolix/multiless v0.0.0-20200413040533-acfd16f65d5d // indirect github.com/anacrolix/multiless v0.0.0-20200413040533-acfd16f65d5d // indirect
github.com/anacrolix/torrent v1.15.2 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/elliotchance/orderedmap v1.2.2 // indirect
github.com/fatih/color v1.9.0 // indirect github.com/fatih/color v1.9.0 // indirect
github.com/gin-contrib/static v0.0.0-20191128031702-f81c604d8ac2 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/modern-go/reflect2 v1.0.1 // indirect
github.com/mschoch/smat v0.2.0 // indirect github.com/mschoch/smat v0.2.0 // indirect
github.com/panjf2000/ants/v2 v2.3.1 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 github.com/tinylib/msgp v1.1.2 // indirect
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f // indirect golang.org/x/net v0.0.0-20200506145744-7e3656a0809f // indirect
golang.org/x/sys v0.0.0-20200509044756-6aff5f38e54f // indirect golang.org/x/sys v0.0.0-20200509044756-6aff5f38e54f // indirect

View file

@ -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/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 h1:GHRpF1pTW19a8tTFrMLUcfWwyC0pnifVo2ClaLq+hP8=
github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46/go.mod h1:uAQ5PCi+MFsC7HjREoAz1BU+Mq60+05gifQSsHSDG/8= 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/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-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/assertions v0.0.0-20190215210624-980c5ac6f3ac/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v0.0.0-20190215210624-980c5ac6f3ac/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=

View file

@ -1,6 +1,7 @@
package mount package mount
import ( import (
"fmt"
"log" "log"
"os" "os"
@ -13,7 +14,7 @@ import (
"github.com/panjf2000/ants/v2" "github.com/panjf2000/ants/v2"
) )
type Torrent struct { type Handler struct {
c *torrent.Client c *torrent.Client
s *stats.Torrent s *stats.Torrent
opts *fs.Options opts *fs.Options
@ -22,8 +23,8 @@ type Torrent struct {
servers map[string]*fuse.Server servers map[string]*fuse.Server
} }
func NewTorrent(c *torrent.Client, pool *ants.Pool, s *stats.Torrent) *Torrent { func NewHandler(c *torrent.Client, pool *ants.Pool, s *stats.Torrent) *Handler {
return &Torrent{ return &Handler{
c: c, c: c,
s: s, s: s,
opts: &fs.Options{}, 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 var torrents []*torrent.Torrent
for _, magnet := range mpc.Magnets { for _, mpcTorrent := range mpc.Torrents {
t, err := s.c.AddMagnet(magnet.URI) 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 { if err != nil {
return err return err
} }
log.Println("getting torrent info", t.Name()) log.Println("getting torrent info", t.Name())
<-t.GotInfo() <-t.GotInfo()
s.s.Add(t) s.s.Add(mpc.Path, t)
log.Println("torrent info obtained", t.Name()) log.Println("torrent info obtained", t.Name())
torrents = append(torrents, t) torrents = append(torrents, t)
@ -65,7 +80,7 @@ func (s *Torrent) Mount(mpc *config.MountPoint) error {
return nil return nil
} }
func (s *Torrent) Close() { func (s *Handler) Close() {
for path, server := range s.servers { for path, server := range s.servers {
log.Println("unmounting", path) log.Println("unmounting", path)
err := server.Unmount() err := server.Unmount()

57
torrent/package-lock.json generated Normal file
View 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=="
}
}
}

View file

@ -2,12 +2,10 @@ package stats
import ( import (
"errors" "errors"
"fmt"
"log" "log"
"time" "time"
"github.com/anacrolix/torrent" "github.com/anacrolix/torrent"
"github.com/dustin/go-humanize"
) )
var ErrTorrentNotFound = errors.New("torrent not found") var ErrTorrentNotFound = errors.New("torrent not found")
@ -23,39 +21,29 @@ const (
) )
type PieceChunk struct { type PieceChunk struct {
Status PieceStatus Status PieceStatus `json:"status"`
NumPieces int NumPieces int `json:"numPieces"`
} }
type TorrentStats struct { type TorrentStats struct {
DownloadedBytes int64 Name string `json:"name"`
UploadedBytes int64 Hash string `json:"hash"`
TimePassed float64 DownloadedBytes int64 `json:"downloadedBytes"`
PieceChunks []*PieceChunk UploadedBytes int64 `json:"uploadedBytes"`
TimePassed float64 `json:"timePassed"`
PieceChunks []*PieceChunk `json:"pieceChunks"`
TotalPieces int `json:"totalPieces"`
} }
type GlobalTorrentStats struct { type GlobalTorrentStats struct {
DownloadedBytes int64 DownloadedBytes int64 `json:"downloadedBytes"`
UploadedBytes int64 UploadedBytes int64 `json:"uploadedBytes"`
TimePassed float64 TimePassed float64 `json:"timePassed"`
} }
func (s *GlobalTorrentStats) speed(bytes int64) float64 { type RouteStats struct {
var bs float64 Name string `json:"name"`
t := s.TimePassed TorrentStats []*TorrentStats `json:"torrentStats"`
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 stats struct { type stats struct {
@ -68,6 +56,7 @@ type stats struct {
type Torrent struct { type Torrent struct {
torrents map[string]*torrent.Torrent torrents map[string]*torrent.Torrent
torrentsByRoute map[string][]*torrent.Torrent
previousStats map[string]*stats previousStats map[string]*stats
gTime time.Time gTime time.Time
@ -77,16 +66,20 @@ func NewTorrent() *Torrent {
return &Torrent{ return &Torrent{
gTime: time.Now(), gTime: time.Now(),
torrents: make(map[string]*torrent.Torrent), torrents: make(map[string]*torrent.Torrent),
torrentsByRoute: make(map[string][]*torrent.Torrent),
previousStats: make(map[string]*stats), 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.torrents[t.InfoHash().String()] = t
s.previousStats[t.InfoHash().String()] = &stats{} 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] t, ok := s.torrents[hash]
if !(ok) { if !(ok) {
return nil, ErrTorrentNotFound return nil, ErrTorrentNotFound
@ -97,16 +90,27 @@ func (s *Torrent) Torrent(hash string) (*TorrentStats, error) {
return s.stats(now, t, true), nil return s.stats(now, t, true), nil
} }
func (s *Torrent) List() []string { func (s *Torrent) RoutesStats() []*RouteStats {
var result []string now := time.Now()
for hash := range s.torrents {
result = append(result, hash) 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() now := time.Now()
var totalDownload int64 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() ts.TimePassed = now.Sub(prev.time).Seconds()
var totalPieces int
if chunks { if chunks {
var pch []*PieceChunk var pch []*PieceChunk
for _, psr := range t.PieceStateRuns() { for _, psr := range t.PieceStateRuns() {
@ -177,10 +181,14 @@ func (s *Torrent) stats(now time.Time, t *torrent.Torrent, chunks bool) *Torrent
Status: s, Status: s,
NumPieces: psr.Length, NumPieces: psr.Length,
}) })
totalPieces += psr.Length
} }
ts.PieceChunks = pch ts.PieceChunks = pch
} }
ts.Hash = t.InfoHash().String()
ts.Name = t.Name()
ts.TotalPieces = totalPieces
return ts return ts
} }

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

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

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

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

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