mirror of
https://github.com/zoriya/flood.git
synced 2026-06-02 11:06:35 +00:00
Add speed graphs
This commit is contained in:
+56
-60
@@ -1,90 +1,86 @@
|
||||
var gulp = require('gulp'),
|
||||
sass = require('gulp-sass'),
|
||||
autoprefixer = require('gulp-autoprefixer'),
|
||||
watch = require('gulp-watch'),
|
||||
notify = require('gulp-notify'),
|
||||
browserSync = require('browser-sync'),
|
||||
browserify = require('browserify'),
|
||||
watchify = require('watchify'),
|
||||
reactify = require('reactify'),
|
||||
source = require('vinyl-source-stream'),
|
||||
svgmin = require('gulp-svgmin');
|
||||
sass = require('gulp-sass'),
|
||||
autoprefixer = require('gulp-autoprefixer'),
|
||||
watch = require('gulp-watch'),
|
||||
notify = require('gulp-notify'),
|
||||
browserSync = require('browser-sync'),
|
||||
browserify = require('browserify'),
|
||||
watchify = require('watchify'),
|
||||
reactify = require('reactify'),
|
||||
sourcemaps = require('gulp-sourcemaps'),
|
||||
source = require('vinyl-source-stream'),
|
||||
svgmin = require('gulp-svgmin');
|
||||
|
||||
var supportedBrowsers = ['last 2 versions', '> 1%', 'ie >= 8', 'Firefox ESR', 'Opera >= 12'],
|
||||
jsFiles = [];
|
||||
jsFiles = [];
|
||||
|
||||
var sourceDir = './source/',
|
||||
destDir = './dist/public/';
|
||||
destDir = './dist/public/';
|
||||
|
||||
var reload = browserSync.reload;
|
||||
|
||||
function handleErrors() {
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
notify.onError({
|
||||
title: "Compile Error",
|
||||
message: "<%= error.message %>"
|
||||
}).apply(this, args);
|
||||
this.emit('end');
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
notify.onError({
|
||||
title: "Compile Error",
|
||||
message: "<%= error.message %>"
|
||||
}).apply(this, args);
|
||||
this.emit('end');
|
||||
}
|
||||
|
||||
gulp.task('browser-sync', function() {
|
||||
|
||||
return browserSync.init({
|
||||
port: 3001
|
||||
});
|
||||
return browserSync.init({
|
||||
port: 3001
|
||||
});
|
||||
});
|
||||
|
||||
gulp.task('styles', function() {
|
||||
|
||||
return gulp.src(sourceDir + 'sass/style.scss')
|
||||
.pipe(sass({
|
||||
errLogToConsole: true
|
||||
}))
|
||||
.pipe(autoprefixer({
|
||||
browsers: supportedBrowsers,
|
||||
map: true
|
||||
}))
|
||||
.pipe(browserSync.reload({
|
||||
stream: true
|
||||
}))
|
||||
.pipe(gulp.dest(destDir + 'stylesheets'));
|
||||
return gulp.src(sourceDir + 'sass/style.scss')
|
||||
.pipe(sass({
|
||||
errLogToConsole: true
|
||||
}))
|
||||
.pipe(autoprefixer({
|
||||
browsers: supportedBrowsers,
|
||||
map: true
|
||||
}))
|
||||
.pipe(browserSync.reload({
|
||||
stream: true
|
||||
}))
|
||||
.pipe(gulp.dest(destDir + 'stylesheets'));
|
||||
});
|
||||
|
||||
gulp.task('scripts', function() {
|
||||
var bundler = browserify({
|
||||
entries: [sourceDir + '/scripts/app.js'],
|
||||
cache: {},
|
||||
packageCache: {},
|
||||
fullPaths: true
|
||||
});
|
||||
|
||||
var bundler = browserify({
|
||||
entries: [sourceDir + '/scripts/app.js'],
|
||||
cache: {},
|
||||
packageCache: {},
|
||||
fullPaths: true
|
||||
});
|
||||
bundler.transform(reactify);
|
||||
function rebundle() {
|
||||
var stream = bundler.bundle();
|
||||
return stream.on('error', handleErrors)
|
||||
.pipe(source('app.js'))
|
||||
.pipe(gulp.dest(destDir + 'scripts/'));
|
||||
}
|
||||
|
||||
bundler.transform(reactify);
|
||||
bundler.on('update', function() {
|
||||
rebundle();
|
||||
});
|
||||
|
||||
function rebundle() {
|
||||
var stream = bundler.bundle();
|
||||
return stream.on('error', handleErrors)
|
||||
.pipe(source('app.js'))
|
||||
.pipe(gulp.dest(destDir + 'scripts/'));
|
||||
}
|
||||
|
||||
bundler.on('update', function() {
|
||||
rebundle();
|
||||
});
|
||||
|
||||
return rebundle();
|
||||
return rebundle();
|
||||
});
|
||||
|
||||
gulp.task('svg', function() {
|
||||
|
||||
return gulp.src(sourceDir + '/images/*.svg')
|
||||
.pipe(svgmin())
|
||||
.pipe(gulp.dest(sourceDir + '/images'));
|
||||
return gulp.src(sourceDir + '/images/*.svg')
|
||||
.pipe(svgmin())
|
||||
.pipe(gulp.dest(sourceDir + '/images'));
|
||||
});
|
||||
|
||||
gulp.task('watch', function () {
|
||||
gulp.watch(sourceDir + 'sass/**/*.scss', ['styles']);
|
||||
gulp.watch(sourceDir + 'scripts/**/*.js', ['scripts', reload]);
|
||||
gulp.watch(sourceDir + 'sass/**/*.scss', ['styles']);
|
||||
gulp.watch(sourceDir + 'scripts/**/*.js', ['scripts', reload]);
|
||||
});
|
||||
|
||||
gulp.task('default', ['scripts', 'styles', 'watch', 'browser-sync']);
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
"dependencies": {
|
||||
"body-parser": "~1.12.0",
|
||||
"cookie-parser": "~1.3.4",
|
||||
"d3": "^3.5.6",
|
||||
"debug": "~2.1.1",
|
||||
"es6-promise": "^2.0.1",
|
||||
"express": "~4.12.2",
|
||||
@@ -30,6 +31,7 @@
|
||||
"gulp-autoprefixer": "^2.1.0",
|
||||
"gulp-notify": "^2.2.0",
|
||||
"gulp-sass": "^1.3.3",
|
||||
"gulp-sourcemaps": "^1.5.2",
|
||||
"gulp-svgmin": "^1.1.1",
|
||||
"gulp-watch": "^4.2.1",
|
||||
"jquery": "^2.1.3",
|
||||
|
||||
@@ -5,13 +5,11 @@ var TorrentStore = require('../stores/TorrentStore');
|
||||
var UIStore = require('../stores/UIStore');
|
||||
var TorrentList = require('./torrent-list/TorrentList');
|
||||
var TorrentListHeader = require('./torrent-list/TorrentListHeader');
|
||||
var Modals = require('./modals/Modals');
|
||||
|
||||
var FloodApp = React.createClass({
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
modal: null,
|
||||
sortCriteria: {
|
||||
direction: 'asc',
|
||||
property: 'name'
|
||||
@@ -21,19 +19,16 @@ var FloodApp = React.createClass({
|
||||
|
||||
componentDidMount: function() {
|
||||
TorrentStore.addSortChangeListener(this._onSortChange);
|
||||
UIStore.addModalChangeListener(this._onModalChange);
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
TorrentStore.removeSortChangeListener(this._onSortChange);
|
||||
UIStore.removeModalChangeListener(this._onModalChange);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
|
||||
return (
|
||||
<div className="flood">
|
||||
<Modals type={this.state.modal} />
|
||||
<FilterBar />
|
||||
<main className="main">
|
||||
<ActionBar />
|
||||
@@ -47,12 +42,6 @@ var FloodApp = React.createClass({
|
||||
this.setState({
|
||||
sortCriteria: TorrentStore.getSortCriteria()
|
||||
});
|
||||
},
|
||||
|
||||
_onModalChange: function() {
|
||||
this.setState({
|
||||
modal: UIStore.getActiveModal()
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@@ -50,7 +50,9 @@ var FilterBar = React.createClass({
|
||||
},
|
||||
|
||||
_start: function() {
|
||||
TorrentActions.start(this.state.selectedTorrents);
|
||||
TorrentActions.start({
|
||||
hash: this.state.selectedTorrents
|
||||
});
|
||||
},
|
||||
|
||||
_stop: function() {
|
||||
|
||||
@@ -26,13 +26,19 @@ var SortDropdown = React.createClass({
|
||||
<div className="dropdown__content__header">Add Torrent</div>
|
||||
<div className="dropdown__content__container">
|
||||
<div className="form__row">
|
||||
<label className="form__label">
|
||||
Torrents
|
||||
</label>
|
||||
<input className="textbox"
|
||||
onChange={this._handleUrlChange}
|
||||
placeholder="Torrent URL"
|
||||
placeholder="Torrent URLs"
|
||||
value={this.state.url}
|
||||
type="text" />
|
||||
</div>
|
||||
<div className="form__row">
|
||||
<label className="form__label">
|
||||
Destination
|
||||
</label>
|
||||
<input className="textbox"
|
||||
onChange={this._handleDestinationChange}
|
||||
placeholder="Destination"
|
||||
@@ -40,7 +46,7 @@ var SortDropdown = React.createClass({
|
||||
type="text" />
|
||||
</div>
|
||||
<div className="form__row">
|
||||
<button className="button" onClick={this._handleAddTorrent}>Add Torrent</button>
|
||||
<button className="button button--primary" onClick={this._handleAddTorrent}>Add Torrent</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,6 +2,7 @@ var React = require('react');
|
||||
var ClientStore = require('../../stores/ClientStore');
|
||||
var Icon = require('../icons/Icon');
|
||||
var format = require('../../helpers/formatData');
|
||||
var LineChart = require('./LineChart');
|
||||
|
||||
var getClientStats = function() {
|
||||
return {
|
||||
@@ -14,20 +15,28 @@ var ClientStats = React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
clientStats: {
|
||||
speed: {
|
||||
currentSpeed: {
|
||||
upload: 0,
|
||||
download: 0
|
||||
},
|
||||
historicalSpeed: {
|
||||
download: [],
|
||||
upload: []
|
||||
},
|
||||
transferred: {
|
||||
upload: 0,
|
||||
download: 0
|
||||
}
|
||||
}
|
||||
},
|
||||
sidebarWidth: 0
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
ClientStore.addChangeListener(this._onChange);
|
||||
this.setState({
|
||||
sidebarWidth: React.findDOMNode(this).offsetWidth
|
||||
});
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
@@ -35,10 +44,9 @@ var ClientStats = React.createClass({
|
||||
},
|
||||
|
||||
render: function() {
|
||||
|
||||
var uploadSpeed = format.data(this.state.clientStats.speed.upload, '/s');
|
||||
var uploadSpeed = format.data(this.state.clientStats.currentSpeed.upload, '/s');
|
||||
var uploadTotal = format.data(this.state.clientStats.transferred.upload);
|
||||
var downloadSpeed = format.data(this.state.clientStats.speed.download, '/s');
|
||||
var downloadSpeed = format.data(this.state.clientStats.currentSpeed.download, '/s');
|
||||
var downloadTotal = format.data(this.state.clientStats.transferred.download);
|
||||
|
||||
return (
|
||||
@@ -60,6 +68,12 @@ var ClientStats = React.createClass({
|
||||
<em className="unit">{downloadTotal.unit}</em> Downloaded
|
||||
</div>
|
||||
</div>
|
||||
<LineChart
|
||||
data={this.state.clientStats.historicalSpeed.download}
|
||||
height={100}
|
||||
id="graph--download"
|
||||
slug="graph--download"
|
||||
width={this.state.sidebarWidth} />
|
||||
</div>
|
||||
<div className="client-stat client-stat--upload">
|
||||
<span className="client-stat__icon">
|
||||
@@ -78,6 +92,12 @@ var ClientStats = React.createClass({
|
||||
<em className="unit">{uploadTotal.unit}</em> Uploaded
|
||||
</div>
|
||||
</div>
|
||||
<LineChart
|
||||
data={this.state.clientStats.historicalSpeed.upload}
|
||||
height={100}
|
||||
id="graph--upload"
|
||||
slug="graph--upload"
|
||||
width={this.state.sidebarWidth} />
|
||||
</div>
|
||||
<button className="client-stats client-stat--limits">
|
||||
<Icon icon="limits" /> Limits
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
var React = require('react');
|
||||
var d3 = require('d3');
|
||||
|
||||
var LineChart = React.createClass({
|
||||
|
||||
componentWillUpdate: function() {
|
||||
var graph = d3.select('#' + this.props.id);
|
||||
var lineData = this.props.data;
|
||||
var margin = {
|
||||
bottom: 10,
|
||||
top: 10
|
||||
};
|
||||
var width = this.props.width;
|
||||
var height = this.props.height;
|
||||
|
||||
var xRange = d3
|
||||
.scale
|
||||
.linear()
|
||||
.range([0, width])
|
||||
.domain([
|
||||
d3.min(lineData, function(d) {
|
||||
return d.x;
|
||||
}),
|
||||
d3.max(lineData, function(d) {
|
||||
return d.x;
|
||||
})
|
||||
]);
|
||||
|
||||
var yRange = d3
|
||||
.scale
|
||||
.linear()
|
||||
.range([height - margin.bottom - margin.top, 0])
|
||||
.domain([
|
||||
d3.min(lineData, function(d) {
|
||||
return d.y;
|
||||
}),
|
||||
d3.max(lineData, function(d) {
|
||||
return d.y;
|
||||
})
|
||||
]);
|
||||
|
||||
// debugger;
|
||||
|
||||
var lineFunc = d3
|
||||
.svg
|
||||
.line()
|
||||
.x(function(d) {
|
||||
return xRange(d.x);
|
||||
})
|
||||
.y(function(d) {
|
||||
return yRange(d.y);
|
||||
})
|
||||
.interpolate('basis');
|
||||
|
||||
var areaFunc = d3
|
||||
.svg
|
||||
.area()
|
||||
.x(function(d) {
|
||||
return xRange(d.x);
|
||||
})
|
||||
.y0(height)
|
||||
.y1(function(d) {
|
||||
return yRange(d.y);
|
||||
})
|
||||
.interpolate('basis');
|
||||
|
||||
var points = lineFunc(lineData);
|
||||
var area = areaFunc(lineData);
|
||||
|
||||
graph
|
||||
.select('g')
|
||||
.remove();
|
||||
|
||||
graph
|
||||
.append('g')
|
||||
.append('svg:path')
|
||||
.attr('class', 'graph--area')
|
||||
.attr('d', area)
|
||||
.attr('transform', 'translate(0,' + margin.top + ')');;
|
||||
|
||||
graph
|
||||
.select('g')
|
||||
.append('svg:path')
|
||||
.attr('class', 'graph--line')
|
||||
.attr('d', points)
|
||||
.attr('transform', 'translate(0,' + margin.top + ')');
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<svg className="graph" id={this.props.id}>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id={this.props.slug + '--gradient'}
|
||||
x1="0%"
|
||||
y1="0%"
|
||||
x2="0%"
|
||||
y2="100%">
|
||||
<stop className={this.props.slug + '--gradient--top'} offset="0%"/>
|
||||
<stop className={this.props.slug + '--gradient--bottom'} offset="100%"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = LineChart;
|
||||
@@ -1,65 +0,0 @@
|
||||
var React = require('react');
|
||||
var Icon = require('../icons/Icon');
|
||||
var TorrentActions = require('../../actions/TorrentActions');
|
||||
var UIActions = require('../../actions/UIActions');
|
||||
|
||||
var Modal = React.createClass({
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
url: '',
|
||||
destination: ''
|
||||
}
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<aside className="modal__window" onClick={this.props.clickHandler}>
|
||||
<header className="modal__header modal__header--toggle">
|
||||
<h1>Add Torrent</h1>
|
||||
</header>
|
||||
<div className="modal__content">
|
||||
<div className="form__row">
|
||||
<input className="textbox"
|
||||
onChange={this._onUrlChange}
|
||||
placeholder="Torrent URL"
|
||||
value={this.state.url}
|
||||
type="text" />
|
||||
</div>
|
||||
<div className="form__row">
|
||||
<input className="textbox"
|
||||
onChange={this._onDestinationChange}
|
||||
placeholder="Destination"
|
||||
value={this.state.destination}
|
||||
type="text" />
|
||||
</div>
|
||||
<div className="form__row">
|
||||
<button className="button" onClick={this._onAddTorrent}>Add Torrent</button>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
);
|
||||
},
|
||||
|
||||
_onDestinationChange: function(event) {
|
||||
this.setState({
|
||||
destination: event.target.value
|
||||
})
|
||||
},
|
||||
|
||||
_onUrlChange: function(event) {
|
||||
this.setState({
|
||||
url: event.target.value
|
||||
})
|
||||
},
|
||||
|
||||
_onAdd: function() {
|
||||
TorrentActions.add({
|
||||
method: 'url',
|
||||
url: this.state.url,
|
||||
destination: this.state.destination
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = Modal;
|
||||
@@ -4,72 +4,110 @@ var ClientConstants = require('../constants/ClientConstants');
|
||||
var $ = require('jquery');
|
||||
var assign = require('object-assign');
|
||||
|
||||
var _historyLength = 20;
|
||||
var _stats = {};
|
||||
var _uploadSpeedHistory = [];
|
||||
var _downloadSpeedHistory = [];
|
||||
|
||||
var ClientStore = assign({}, EventEmitter.prototype, {
|
||||
getStats: function() {
|
||||
return _stats;
|
||||
},
|
||||
|
||||
getStats: function() {
|
||||
return _stats;
|
||||
},
|
||||
emitChange: function() {
|
||||
this.emit(ClientConstants.CLIENT_STATS_CHANGE);
|
||||
},
|
||||
|
||||
emitChange: function() {
|
||||
this.emit(ClientConstants.CLIENT_STATS_CHANGE);
|
||||
},
|
||||
|
||||
addChangeListener: function(callback) {
|
||||
this.on(ClientConstants.CLIENT_STATS_CHANGE, callback);
|
||||
},
|
||||
|
||||
removeChangeListener: function(callback) {
|
||||
this.removeListener(ClientConstants.CLIENT_STATS_CHANGE, callback);
|
||||
}
|
||||
addChangeListener: function(callback) {
|
||||
this.on(ClientConstants.CLIENT_STATS_CHANGE, callback);
|
||||
},
|
||||
|
||||
removeChangeListener: function(callback) {
|
||||
this.removeListener(ClientConstants.CLIENT_STATS_CHANGE, callback);
|
||||
}
|
||||
});
|
||||
|
||||
var dispatcherIndex = AppDispatcher.register(function(action) {
|
||||
var text;
|
||||
|
||||
var text;
|
||||
switch(action.actionType) {
|
||||
|
||||
switch(action.actionType) {
|
||||
case ClientConstants.ADD_TORRENT:
|
||||
getClientStats();
|
||||
break;
|
||||
|
||||
case ClientConstants.ADD_TORRENT:
|
||||
getClientStats();
|
||||
break;
|
||||
|
||||
case ClientConstants.REMOVE_TORRENT:
|
||||
getClientStats();
|
||||
break;
|
||||
}
|
||||
case ClientConstants.REMOVE_TORRENT:
|
||||
getClientStats();
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
var addHistory = function(uploadSpeed, downloadSpeed) {
|
||||
var index = 0;
|
||||
|
||||
console.log(uploadSpeed, downloadSpeed);
|
||||
|
||||
while (index < _historyLength) {
|
||||
if (index < _historyLength - 1) {
|
||||
if (_uploadSpeedHistory[index] != null && _uploadSpeedHistory[index].x != null) {
|
||||
_uploadSpeedHistory[index].y = _uploadSpeedHistory[index + 1].y;
|
||||
_downloadSpeedHistory[index].y = _downloadSpeedHistory[index + 1].y;
|
||||
} else {
|
||||
_uploadSpeedHistory[index] = {
|
||||
x: index,
|
||||
y: 0
|
||||
}
|
||||
_downloadSpeedHistory[index] = {
|
||||
x: index,
|
||||
y: 0
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_uploadSpeedHistory[index] = {
|
||||
x: index,
|
||||
y: uploadSpeed
|
||||
}
|
||||
_downloadSpeedHistory[index] = {
|
||||
x: index,
|
||||
y: downloadSpeed
|
||||
}
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
var getClientStats = function(callback) {
|
||||
$.ajax({
|
||||
url: '/client/stats',
|
||||
dataType: 'json',
|
||||
|
||||
success: function(data) {
|
||||
addHistory(data.uploadRate, data.downloadRate);
|
||||
|
||||
$.ajax({
|
||||
url: '/client/stats',
|
||||
dataType: 'json',
|
||||
_stats = {
|
||||
currentSpeed: {
|
||||
upload: data.uploadRate,
|
||||
download: data.downloadRate
|
||||
},
|
||||
historicalSpeed: {
|
||||
upload: _uploadSpeedHistory,
|
||||
download: _downloadSpeedHistory
|
||||
},
|
||||
transferred: {
|
||||
upload: data.uploadTotal,
|
||||
download: data.downloadTotal
|
||||
}
|
||||
};
|
||||
|
||||
success: function(data) {
|
||||
ClientStore.emitChange();
|
||||
|
||||
_stats = {
|
||||
speed: {
|
||||
upload: data.uploadRate,
|
||||
download: data.downloadRate
|
||||
},
|
||||
transferred: {
|
||||
upload: data.uploadTotal,
|
||||
download: data.downloadTotal
|
||||
}
|
||||
};
|
||||
}.bind(this),
|
||||
|
||||
ClientStore.emitChange();
|
||||
|
||||
}.bind(this),
|
||||
|
||||
error: function(xhr, status, err) {
|
||||
console.error('/client/stats', status, err.toString());
|
||||
}.bind(this)
|
||||
});
|
||||
error: function(xhr, status, err) {
|
||||
console.error('/client/stats', status, err.toString());
|
||||
}.bind(this)
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user