quartz_flow 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/bin/createdb.rb +30 -0
- data/bin/quartzflow +177 -0
- data/etc/logging.rb +12 -0
- data/etc/quartz.rb +25 -0
- data/lib/quartz_flow/authentication.rb +88 -0
- data/lib/quartz_flow/home.rb +76 -0
- data/lib/quartz_flow/mock_client.rb +247 -0
- data/lib/quartz_flow/model.rb +27 -0
- data/lib/quartz_flow/randstring.rb +10 -0
- data/lib/quartz_flow/server.rb +305 -0
- data/lib/quartz_flow/session.rb +83 -0
- data/lib/quartz_flow/settings_helper.rb +154 -0
- data/lib/quartz_flow/torrent_manager.rb +247 -0
- data/lib/quartz_flow/usagetracker.rb +335 -0
- data/lib/quartz_flow/wrappers.rb +124 -0
- data/public/bootstrap/css/bootstrap-responsive.css +1109 -0
- data/public/bootstrap/css/bootstrap-responsive.min.css +9 -0
- data/public/bootstrap/css/bootstrap.css +6167 -0
- data/public/bootstrap/css/bootstrap.min.css +9 -0
- data/public/bootstrap/img/glyphicons-halflings-white.png +0 -0
- data/public/bootstrap/img/glyphicons-halflings.png +0 -0
- data/public/bootstrap/js/bootstrap.js +2280 -0
- data/public/bootstrap/js/bootstrap.min.js +6 -0
- data/public/js/jquery-1.10.2.min.js +6 -0
- data/public/js/quartz.js +419 -0
- data/public/style.css +25 -0
- data/views/config_partial.haml +30 -0
- data/views/index.haml +21 -0
- data/views/login.haml +32 -0
- data/views/torrent_detail_partial.haml +91 -0
- data/views/torrent_table_partial.haml +76 -0
- metadata +157 -0
data/public/js/quartz.js
ADDED
@@ -0,0 +1,419 @@
|
|
1
|
+
|
2
|
+
/* Main Angular module */
|
3
|
+
var quartzModule = angular.module('quartz',[]);
|
4
|
+
|
5
|
+
/* Set up URL routes. Different URLs map to different HTML fragments (templates)*/
|
6
|
+
quartzModule.config(function($routeProvider) {
|
7
|
+
$routeProvider.
|
8
|
+
when('/', {controller: TorrentTableCtrl, templateUrl:'/torrent_table'}).
|
9
|
+
when('/details/:torrent', {controller: TorrentDetailsCtrl, templateUrl:'/torrent_detail'}).
|
10
|
+
when('/config', {controller: ConfigCtrl, templateUrl:'/config'}).
|
11
|
+
otherwise({redirectTo:'/'});
|
12
|
+
|
13
|
+
});
|
14
|
+
|
15
|
+
/* Set up function that keeps retrieving torrent data from server on a timer.
|
16
|
+
We store the data in the rootScope which means it's available in all scopes. */
|
17
|
+
quartzModule.run(function($rootScope, $timeout, $http, $window) {
|
18
|
+
$rootScope.alerts = {};
|
19
|
+
|
20
|
+
$rootScope.deleteRootscopeError = function(err){
|
21
|
+
delete $rootScope.alerts[err];
|
22
|
+
}
|
23
|
+
|
24
|
+
$rootScope.rootErrors = function() {
|
25
|
+
var rc = [];
|
26
|
+
for (var key in $rootScope.alerts) {
|
27
|
+
if ($rootScope.alerts.hasOwnProperty(key)) {
|
28
|
+
rc.push(key);
|
29
|
+
}
|
30
|
+
}
|
31
|
+
|
32
|
+
return rc;
|
33
|
+
};
|
34
|
+
|
35
|
+
// Load the list of torrent data every 1 second.
|
36
|
+
var refresh = function() {
|
37
|
+
// http://code.angularjs.org/1.0.8/docs/api/ng.$http
|
38
|
+
|
39
|
+
var msg = "Server is unreachable.";
|
40
|
+
$http.get("/torrent_data", {'timeout': 3000}).
|
41
|
+
success(function(data,status,headers,config){
|
42
|
+
$rootScope.torrents = data;
|
43
|
+
updateTableTorrentData($rootScope);
|
44
|
+
$rootScope.deleteRootscopeError(msg);
|
45
|
+
}).
|
46
|
+
error(function(data,status,headers,config){
|
47
|
+
$rootScope.torrents = [];
|
48
|
+
updateTableTorrentData($rootScope);
|
49
|
+
if ( status == 0 ){
|
50
|
+
$rootScope.alerts[msg] = 1;
|
51
|
+
} else if (data == "Authentication required" ) {
|
52
|
+
$window.location.href = '/login';
|
53
|
+
} else {
|
54
|
+
$rootScope.alerts[data] = 1;
|
55
|
+
}
|
56
|
+
});
|
57
|
+
|
58
|
+
$timeout(refresh, 1000);
|
59
|
+
}
|
60
|
+
refresh();
|
61
|
+
});
|
62
|
+
|
63
|
+
/* Controller for the torrent table view */
|
64
|
+
function TorrentTableCtrl($scope, $rootScope, $timeout, $http) {
|
65
|
+
$scope.errors = [];
|
66
|
+
|
67
|
+
|
68
|
+
var checkIframeMessages = function() {
|
69
|
+
while( iframeUploadResultMessages.length > 0 ) {
|
70
|
+
var msg = iframeUploadResultMessages.shift();
|
71
|
+
if ( msg != "@@success" ){
|
72
|
+
$scope.errors.push(msg);
|
73
|
+
}
|
74
|
+
}
|
75
|
+
|
76
|
+
$timeout(checkIframeMessages, 1000);
|
77
|
+
}
|
78
|
+
$timeout(checkIframeMessages, 1000);
|
79
|
+
|
80
|
+
$scope.deleteError = function(err){
|
81
|
+
genericDeleteError($scope, err);
|
82
|
+
}
|
83
|
+
|
84
|
+
$scope.dailyUsage = 0;
|
85
|
+
$scope.monthlyUsage = 0;
|
86
|
+
// Load the usage values
|
87
|
+
var getUsage = function() {
|
88
|
+
$http.get("/usage", {'timeout': 3000}).
|
89
|
+
success(function(data,status,headers,config){
|
90
|
+
$scope.dailyUsage = data.dailyUsage;
|
91
|
+
$scope.monthlyUsage = data.monthlyUsage;
|
92
|
+
})
|
93
|
+
|
94
|
+
$timeout(getUsage, 2000);
|
95
|
+
}
|
96
|
+
$timeout(getUsage, 2000);
|
97
|
+
|
98
|
+
$scope.getTimes = function(n){
|
99
|
+
var result = [];
|
100
|
+
for(var i = 0; i < n; i++){
|
101
|
+
result.push(i);
|
102
|
+
}
|
103
|
+
return result;
|
104
|
+
}
|
105
|
+
|
106
|
+
$scope.setPage = function(n){
|
107
|
+
$scope.currentPageIndex = n - 1;
|
108
|
+
if ( $scope.currentPageIndex < 0 )
|
109
|
+
$scope.currentPageIndex = 0;
|
110
|
+
if ( $scope.currentPageIndex >= $scope.totalPages )
|
111
|
+
$scope.currentPageIndex = $scope.totalPages - 1;
|
112
|
+
updatePages($scope);
|
113
|
+
updatePagesInfo($scope);
|
114
|
+
}
|
115
|
+
|
116
|
+
$scope.decrementPage = function(){
|
117
|
+
$scope.setPage($scope.currentPageIndex);
|
118
|
+
}
|
119
|
+
|
120
|
+
$scope.incrementPage = function(){
|
121
|
+
$scope.setPage($scope.currentPageIndex+2);
|
122
|
+
}
|
123
|
+
|
124
|
+
$scope.torrentToDownload = "";
|
125
|
+
$scope.magnetToStart = "";
|
126
|
+
|
127
|
+
$scope.downloadTorrentFile = function(){
|
128
|
+
$http.post("/download_torrent", {"url": $scope.torrentToDownload}).
|
129
|
+
success(function(data,status,headers,config){
|
130
|
+
console.log("huzzah, downloading torrent succeeded");
|
131
|
+
}).
|
132
|
+
error(function(data,status,headers,config){
|
133
|
+
console.log("Crap, downloading torrent failed: " + status + ": " + data);
|
134
|
+
$scope.errors.push(data);
|
135
|
+
});
|
136
|
+
}
|
137
|
+
|
138
|
+
$scope.startMagnetLink = function(){
|
139
|
+
$http.post("/start_magnet", {"url": $scope.magnetToStart}).
|
140
|
+
success(function(data,status,headers,config){
|
141
|
+
console.log("huzzah, starting magnet succeeded");
|
142
|
+
}).
|
143
|
+
error(function(data,status,headers,config){
|
144
|
+
console.log("Crap, starting magnet failed: " + status + ": " + data);
|
145
|
+
$scope.errors.push(data);
|
146
|
+
});
|
147
|
+
}
|
148
|
+
|
149
|
+
$scope.stateForDisplay = function(infoHash){
|
150
|
+
var torrent = $scope.torrents[infoHash];
|
151
|
+
if ( ! torrent ){
|
152
|
+
return "unknown";
|
153
|
+
}
|
154
|
+
|
155
|
+
var result = torrent.state;
|
156
|
+
|
157
|
+
if ( torrent.paused ){
|
158
|
+
result = result + " (paused)";
|
159
|
+
}
|
160
|
+
|
161
|
+
return result;
|
162
|
+
}
|
163
|
+
|
164
|
+
$scope.pauseMenuItem = function(infoHash){
|
165
|
+
var torrent = $scope.torrents[infoHash];
|
166
|
+
if ( ! torrent ) {
|
167
|
+
return "Pause";
|
168
|
+
}
|
169
|
+
var rc = ""
|
170
|
+
if (torrent.paused) {
|
171
|
+
rc = "Unpause";
|
172
|
+
} else {
|
173
|
+
rc = "Pause";
|
174
|
+
}
|
175
|
+
return rc;
|
176
|
+
}
|
177
|
+
|
178
|
+
$scope.togglePause = function(infoHash){
|
179
|
+
var torrent = $scope.torrents[infoHash];
|
180
|
+
if ( ! torrent ) {
|
181
|
+
return;
|
182
|
+
}
|
183
|
+
var url = "";
|
184
|
+
if (torrent.paused) {
|
185
|
+
url = "/unpause_torrent";
|
186
|
+
} else {
|
187
|
+
url = "/pause_torrent";
|
188
|
+
}
|
189
|
+
|
190
|
+
$http.post(url, {"infohash": infoHash}).
|
191
|
+
success(function(data,status,headers,config){
|
192
|
+
console.log("huzzah, unpausing torrent succeeded");
|
193
|
+
}).
|
194
|
+
error(function(data,status,headers,config){
|
195
|
+
console.log("Crap, unpausing torrent failed: " + status + ": " + data);
|
196
|
+
$scope.errors.push(data);
|
197
|
+
});
|
198
|
+
}
|
199
|
+
|
200
|
+
$scope.deleteTorrent = function(infoHash, deleteFiles){
|
201
|
+
var torrent = $scope.torrents[infoHash];
|
202
|
+
if ( ! torrent ) {
|
203
|
+
return;
|
204
|
+
}
|
205
|
+
|
206
|
+
$http.post("/delete_torrent", {"infohash": infoHash, "delete_files": deleteFiles}).
|
207
|
+
success(function(data,status,headers,config){
|
208
|
+
console.log("huzzah, deleting torrent succeeded");
|
209
|
+
}).
|
210
|
+
error(function(data,status,headers,config){
|
211
|
+
console.log("Crap, deleting torrent failed: " + status + ": " + data);
|
212
|
+
$scope.errors.push(data);
|
213
|
+
});
|
214
|
+
}
|
215
|
+
|
216
|
+
|
217
|
+
}
|
218
|
+
|
219
|
+
/* Controller for the torrent details view */
|
220
|
+
function TorrentDetailsCtrl($scope, $routeParams, $http) {
|
221
|
+
$scope.torrent = $scope.torrentsForTable[$routeParams.torrent]
|
222
|
+
|
223
|
+
$scope.deleteError = function(err){
|
224
|
+
genericDeleteError($scope, err);
|
225
|
+
}
|
226
|
+
|
227
|
+
$scope.applyDownloadRateLimit = function(){
|
228
|
+
$http.post("/change_torrent", {"infoHash": $scope.torrent.infoHash, "downloadRateLimit" : $scope.torrent.downloadRateLimit}).
|
229
|
+
success(function(data,status,headers,config){
|
230
|
+
console.log("huzzah, changing setting succeeded");
|
231
|
+
}).
|
232
|
+
error(function(data,status,headers,config){
|
233
|
+
$scope.errors.push(data);
|
234
|
+
});
|
235
|
+
}
|
236
|
+
|
237
|
+
}
|
238
|
+
|
239
|
+
/* Controller for the config view */
|
240
|
+
function ConfigCtrl($scope, $timeout, $http) {
|
241
|
+
$scope.deleteError = function(err){
|
242
|
+
genericDeleteError($scope, err);
|
243
|
+
}
|
244
|
+
|
245
|
+
$http.get("/global_settings").
|
246
|
+
success(function(data,status,headers,config){
|
247
|
+
$scope.globalSettings = data;
|
248
|
+
}).
|
249
|
+
error(function(data,status,headers,config){
|
250
|
+
$scope.globalSettings = {};
|
251
|
+
$scope.errors.push(data);
|
252
|
+
});
|
253
|
+
|
254
|
+
$scope.applySettings = function(){
|
255
|
+
$http.post("/global_settings", $scope.globalSettings).
|
256
|
+
success(function(data,status,headers,config){
|
257
|
+
console.log("huzzah, saving settings succeeded");
|
258
|
+
}).
|
259
|
+
error(function(data,status,headers,config){
|
260
|
+
$scope.errors.push(data);
|
261
|
+
});
|
262
|
+
}
|
263
|
+
}
|
264
|
+
|
265
|
+
/* Controller used for login */
|
266
|
+
function LoginCtrl($scope, $window, $http) {
|
267
|
+
$scope.errors = [];
|
268
|
+
$scope.login = null;
|
269
|
+
$scope.password = null;
|
270
|
+
|
271
|
+
$scope.doLogin = function(){
|
272
|
+
$http.post("/login", {"login": $scope.login, "password" : $scope.password}).
|
273
|
+
success(function(data,status,headers,config){
|
274
|
+
$window.location.href = '/';
|
275
|
+
}).
|
276
|
+
error(function(data,status,headers,config){
|
277
|
+
$scope.errors.push(data);
|
278
|
+
});
|
279
|
+
}
|
280
|
+
|
281
|
+
$scope.doLogout = function(){
|
282
|
+
$http.post("/logout").
|
283
|
+
success(function(data,status,headers,config){
|
284
|
+
$window.location.href = '/';
|
285
|
+
}).
|
286
|
+
error(function(data,status,headers,config){
|
287
|
+
$window.location.href = '/';
|
288
|
+
});
|
289
|
+
}
|
290
|
+
|
291
|
+
$scope.deleteRootscopeError = function(err){}
|
292
|
+
$scope.deleteError = function(err){
|
293
|
+
genericDeleteError($scope, err);
|
294
|
+
}
|
295
|
+
}
|
296
|
+
|
297
|
+
var torrentPropsNotToUpdate = { 'downloadRateLimit': 1 };
|
298
|
+
|
299
|
+
/* Helper used to update the $scope's list of torrent data shown in the table
|
300
|
+
from the full data retrieved from the server */
|
301
|
+
function updateTableTorrentData($scope) {
|
302
|
+
if ( ! $scope.torrents )
|
303
|
+
return;
|
304
|
+
|
305
|
+
if ( ! $scope.torrentsForTable ) {
|
306
|
+
$scope.torrentsForTable = {};
|
307
|
+
}
|
308
|
+
|
309
|
+
for (var key in $scope.torrents) {
|
310
|
+
if ($scope.torrents.hasOwnProperty(key)) {
|
311
|
+
torrent = $scope.torrents[key];
|
312
|
+
if ( ! (key in $scope.torrentsForTable)) {
|
313
|
+
// New entry
|
314
|
+
$scope.torrentsForTable[key] = clone(torrent);
|
315
|
+
}
|
316
|
+
else {
|
317
|
+
// Update entry if needed
|
318
|
+
existing = $scope.torrentsForTable[key];
|
319
|
+
for (var prop in torrent) {
|
320
|
+
if (torrent.hasOwnProperty(prop)) {
|
321
|
+
if (existing[prop] != torrent[prop] && !torrentPropsNotToUpdate[prop])
|
322
|
+
existing[prop] = torrent[prop];
|
323
|
+
}
|
324
|
+
}
|
325
|
+
}
|
326
|
+
}
|
327
|
+
}
|
328
|
+
|
329
|
+
var toDelete = [];
|
330
|
+
for (var key in $scope.torrentsForTable) {
|
331
|
+
if ( ! (key in $scope.torrents)){
|
332
|
+
// This torrent has been deleted server-side
|
333
|
+
toDelete.push(key);
|
334
|
+
}
|
335
|
+
}
|
336
|
+
for(var i = 0; i < toDelete.length; i++) {
|
337
|
+
delete $scope.torrentsForTable[toDelete[i]];
|
338
|
+
}
|
339
|
+
|
340
|
+
// Set up the current page
|
341
|
+
updatePages($scope);
|
342
|
+
}
|
343
|
+
|
344
|
+
function updatePages($scope) {
|
345
|
+
// Set up the current page
|
346
|
+
var pageSize = 5;
|
347
|
+
if( ! $scope.currentPageIndex ) {
|
348
|
+
$scope.currentPageIndex = 0;
|
349
|
+
}
|
350
|
+
$scope.torrentsList = [];
|
351
|
+
for (var key in $scope.torrentsForTable) {
|
352
|
+
$scope.torrentsList.push($scope.torrentsForTable[key]);
|
353
|
+
}
|
354
|
+
var pageStartIndex = $scope.currentPageIndex * pageSize;
|
355
|
+
var pageEndIndex = ($scope.currentPageIndex+1) * pageSize;
|
356
|
+
$scope.currentPage = $scope.torrentsList.slice(pageStartIndex, pageEndIndex);
|
357
|
+
$scope.totalPages = Math.floor(($scope.torrentsList.length - 1) / pageSize + 1);
|
358
|
+
|
359
|
+
if( ! $scope.pagesInfo || $scope.pagesInfo.length != $scope.totalPages ) {
|
360
|
+
updatePagesInfo($scope);
|
361
|
+
}
|
362
|
+
}
|
363
|
+
|
364
|
+
function updatePagesInfo($scope) {
|
365
|
+
$scope.pagesInfo = [];
|
366
|
+
for(var i = 0; i < $scope.totalPages; i++){
|
367
|
+
var style = (i == $scope.currentPageIndex ? "active" : "");
|
368
|
+
$scope.pagesInfo.push({"number": i+1, "style": style});
|
369
|
+
}
|
370
|
+
}
|
371
|
+
|
372
|
+
function clone(obj) {
|
373
|
+
result = {};
|
374
|
+
for (var prop in obj) {
|
375
|
+
if (obj.hasOwnProperty(prop)) {
|
376
|
+
result[prop] = obj[prop];
|
377
|
+
}
|
378
|
+
}
|
379
|
+
return result;
|
380
|
+
}
|
381
|
+
|
382
|
+
function genericDeleteError($scope, err) {
|
383
|
+
for(var i = 0; i < $scope.errors.length; i++) {
|
384
|
+
if ( $scope.errors[i] == err ) {
|
385
|
+
$scope.errors.splice(i,1);
|
386
|
+
break;
|
387
|
+
}
|
388
|
+
}
|
389
|
+
$scope.deleteRootscopeError(err);
|
390
|
+
}
|
391
|
+
|
392
|
+
/*********** IFRAME AJAX UPLOAD *************/
|
393
|
+
|
394
|
+
var iframeUploadResultMessages = [];
|
395
|
+
|
396
|
+
function getFrameByName(name) {
|
397
|
+
for (var i = 0; i < frames.length; i++)
|
398
|
+
if (frames[i].name == name)
|
399
|
+
return frames[i];
|
400
|
+
|
401
|
+
return null;
|
402
|
+
}
|
403
|
+
|
404
|
+
function handleIframeLoad(frameName)
|
405
|
+
{
|
406
|
+
var frame = getFrameByName(frameName);
|
407
|
+
if ( frame != null )
|
408
|
+
{
|
409
|
+
result = frame.document.getElementsByTagName("body")[0].innerHTML;
|
410
|
+
|
411
|
+
// The form's onload handler gets called when the main page is first loaded as well.
|
412
|
+
// We detect this condition by checking if the iframes contents are not empty.
|
413
|
+
if ( result.length > 0 ){
|
414
|
+
if ( iframeUploadResultMessages.length < 10 ){
|
415
|
+
iframeUploadResultMessages.push(result);
|
416
|
+
}
|
417
|
+
}
|
418
|
+
}
|
419
|
+
}
|
data/public/style.css
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
|
2
|
+
#mainheader
|
3
|
+
{
|
4
|
+
text-align: center;
|
5
|
+
/*background-color: #0f0f0f;*/
|
6
|
+
background-color: #0088cc;
|
7
|
+
color: #ffffff;
|
8
|
+
margin-top: 0px;
|
9
|
+
padding-top: 0px;
|
10
|
+
}
|
11
|
+
|
12
|
+
/* Make all iframes invisible. */
|
13
|
+
iframe
|
14
|
+
{
|
15
|
+
width: 0px;
|
16
|
+
height: 0px;
|
17
|
+
border: 0px solid #000000;
|
18
|
+
}
|
19
|
+
|
20
|
+
#logout
|
21
|
+
{
|
22
|
+
float: right;
|
23
|
+
margin-right: 2em;
|
24
|
+
margin-top: 0.4em;
|
25
|
+
}
|
@@ -0,0 +1,30 @@
|
|
1
|
+
:javascript
|
2
|
+
$('#uploadRateLabel').tooltip();
|
3
|
+
|
4
|
+
%div{ :class => "row"}
|
5
|
+
|
6
|
+
%ul{ :class => "nav nav-pills"}
|
7
|
+
%li
|
8
|
+
%a{ :href => "#" } Main
|
9
|
+
%li{ :class => "active" }
|
10
|
+
%a{ :href => "#/config" } Config
|
11
|
+
|
12
|
+
%div{ :class => "alert alert-error", "ng-repeat" => "error in errors" }
|
13
|
+
%button{ :type => "button", :class => "close", "ng-click" => "deleteError(error)"} ×
|
14
|
+
{{error}}
|
15
|
+
|
16
|
+
%form{ :class => "form-horizontal", "ng-submit" => "applySettings()"}
|
17
|
+
%div{ :class => "control-group"}
|
18
|
+
%label{ :id => "uploadRateLabel", :class => "control-label", :for => "uploadRate", "data-toggle" => "tooltip", :title => "Default rate limit to apply to new torrents"} Default Upload Rate Limit
|
19
|
+
%div{ :class => 'controls'}
|
20
|
+
%input{ :type => "text", :id => "uploadRate", :placeholder => "Enter rate like '20 KB'", "ng-model" => "globalSettings.defaultUploadRateLimit"}
|
21
|
+
%div{ :class => "control-group"}
|
22
|
+
%label{ :id => "downloadRateLabel", :class => "control-label", :for => "downloadRate", "data-toggle" => "tooltip", :title => "Default rate limit to apply to new torrents"} Default Download Rate Limit
|
23
|
+
%div{ :class => 'controls'}
|
24
|
+
%input{ :type => "text", :id => "downloadRate", :placeholder => "Enter rate like '500 KB'", "ng-model" => "globalSettings.defaultDownloadRateLimit"}
|
25
|
+
%div{ :class => "control-group"}
|
26
|
+
%label{ :id => "ratioLabel", :class => "control-label", :for => "ratio", "data-toggle" => "tooltip", :title => "Default upload ratio to apply to new torrents"} Default Upload Ratio
|
27
|
+
%div{ :class => 'controls'}
|
28
|
+
%input{ :type => "text", :id => "ratio", :placeholder => "Enter decimal number like '1.5'", "ng-model" => "globalSettings.defaultRatio"}
|
29
|
+
%button{ :class => "btn", :type => "submit" } Apply
|
30
|
+
|
data/views/index.haml
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
!!! html
|
2
|
+
%html{ "ng-app" => "quartz"}
|
3
|
+
%head
|
4
|
+
%link{ :rel => "stylesheet", :type => "text/css", :href => "/style.css"}
|
5
|
+
%link{ :rel => "stylesheet", :type => "text/css", :href => "/bootstrap/css/bootstrap.min.css"}
|
6
|
+
%script{ :src => "http://ajax.googleapis.com/ajax/libs/angularjs/1.0.8/angular.min.js" }
|
7
|
+
%script{ :src => "http://ajax.googleapis.com/ajax/libs/angularjs/1.0.8/angular-resource.js" }
|
8
|
+
%script{ :src => "/js/quartz.js" }
|
9
|
+
%title QuartzFlow
|
10
|
+
%body
|
11
|
+
%h1{ :id => "mainheader", "ng-controller" => "LoginCtrl"}
|
12
|
+
QuartzFlow
|
13
|
+
%button{ :type => "button", :class => "btn btn-mini", :id => "logout", "ng-click" => "doLogout()" } logout
|
14
|
+
|
15
|
+
%div{ :class => "container", "ng-view" => ''}
|
16
|
+
|
17
|
+
%script{ :src => "/js/jquery-1.10.2.min.js" }
|
18
|
+
%script{ :src => "/bootstrap/js/bootstrap.min.js"}
|
19
|
+
|
20
|
+
-# This is the file-upload iframe
|
21
|
+
%iframe{ :id => "uploadiframe", :name => "uploadiframe", :onload => 'handleIframeLoad("uploadiframe")' }
|
data/views/login.haml
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
!!! html
|
2
|
+
%html{ "ng-app" => ""}
|
3
|
+
%head
|
4
|
+
%link{ :rel => "stylesheet", :type => "text/css", :href => "/style.css"}
|
5
|
+
%link{ :rel => "stylesheet", :type => "text/css", :href => "/bootstrap/css/bootstrap.min.css"}
|
6
|
+
%script{ :src => "http://ajax.googleapis.com/ajax/libs/angularjs/1.0.8/angular.min.js" }
|
7
|
+
%script{ :src => "http://ajax.googleapis.com/ajax/libs/angularjs/1.0.8/angular-resource.js" }
|
8
|
+
%script{ :src => "/js/quartz.js" }
|
9
|
+
%body
|
10
|
+
%h1{ :id => "mainheader"} QuartzFlow
|
11
|
+
|
12
|
+
%div{ :class => "container", "ng-controller" => "LoginCtrl"}
|
13
|
+
%div{ :class => "alert alert-error", "ng-repeat" => "error in rootErrors()" }
|
14
|
+
%button{ :type => "button", :class => "close", "ng-click" => "deleteError(error)"} ×
|
15
|
+
{{error}}
|
16
|
+
|
17
|
+
%div{ :class => "alert alert-error", "ng-repeat" => "error in errors" }
|
18
|
+
%button{ :type => "button", :class => "close", "ng-click" => "deleteError(error)"} ×
|
19
|
+
{{error}}
|
20
|
+
|
21
|
+
%form{ :role => "form", "ng-submit" => "doLogin()" }
|
22
|
+
%div{ :class => "form-group" }
|
23
|
+
%label{ :for => "login" } Login
|
24
|
+
%input{ :type => "text", :class => "form-control", :id => "login", :placeholder => "Enter login", "ng-model" => "login" }
|
25
|
+
%div{ :class => "form-group" }
|
26
|
+
%label{ :for => "password" } Password
|
27
|
+
%input{ :type => "password", :class => "form-control", :id => "password", :placeholder => "Enter password", "ng-model" => "password" }
|
28
|
+
%button{ :class => "btn", :type => "submit" } Login
|
29
|
+
|
30
|
+
%script{ :src => "/js/jquery-1.10.2.min.js" }
|
31
|
+
%script{ :src => "/bootstrap/js/bootstrap.min.js"}
|
32
|
+
|
@@ -0,0 +1,91 @@
|
|
1
|
+
%ul{ :class => "nav nav-pills"}
|
2
|
+
%li
|
3
|
+
%a{ :href => "#" } Main
|
4
|
+
%li
|
5
|
+
%a{ :href => "#/config" } Config
|
6
|
+
|
7
|
+
%div{ :class => "alert alert-error", "ng-repeat" => "error in rootErrors()" }
|
8
|
+
%button{ :type => "button", :class => "close", "ng-click" => "deleteError(error)"} ×
|
9
|
+
{{error}}
|
10
|
+
|
11
|
+
%div{ :class => "alert alert-error", "ng-repeat" => "error in errors" }
|
12
|
+
%button{ :type => "button", :class => "close", "ng-click" => "deleteError(error)"} ×
|
13
|
+
{{error}}
|
14
|
+
|
15
|
+
%h1 {{ torrent.recommendedName }}
|
16
|
+
/%a{:class => "btn", "href" => "#" } Back
|
17
|
+
%table{ :class => "table" }
|
18
|
+
%tr
|
19
|
+
%th Size
|
20
|
+
%td {{torrent.completedBytes}}/{{torrent.dataLength}}
|
21
|
+
%tr
|
22
|
+
%th Pieces
|
23
|
+
%td {{torrent.completePieces}}/{{torrent.totalPieces}}
|
24
|
+
%tr
|
25
|
+
%th Status
|
26
|
+
%td {{torrent.state}}
|
27
|
+
%tr
|
28
|
+
%th Progress
|
29
|
+
%td {{torrent.percentComplete}}%
|
30
|
+
%tr
|
31
|
+
%th Download Rate
|
32
|
+
%td {{torrent.downloadRate}}
|
33
|
+
%tr
|
34
|
+
%th Upload Rate
|
35
|
+
%td {{torrent.uploadRate}}
|
36
|
+
%tr
|
37
|
+
%th Download Rate Limit
|
38
|
+
%td
|
39
|
+
%form{ "ng-submit" => "applyDownloadRateLimit()" }
|
40
|
+
%fieldset
|
41
|
+
%div{ :class => "input-append" }
|
42
|
+
%input{ :class => "span2", :type => "text", "ng-model" => "torrent.downloadRateLimit" }
|
43
|
+
%button{ :class => "btn", :type => "submit" } Change
|
44
|
+
%tr
|
45
|
+
%th Upload Rate Limit
|
46
|
+
%td
|
47
|
+
%form
|
48
|
+
%fieldset
|
49
|
+
%div{ :class => "input-append" }
|
50
|
+
%input{ :class => "span2", :type => "text", "ng-model" => "torrent.uploadRateLimit" }
|
51
|
+
%button{ :class => "btn", :type => "submit" } Change
|
52
|
+
%tr
|
53
|
+
%th Upload Ratio
|
54
|
+
%td
|
55
|
+
%form
|
56
|
+
%fieldset
|
57
|
+
%div{ :class => "input-append" }
|
58
|
+
%input{ :class => "span2", :type => "text", "ng-model" => "torrent.ratio" }
|
59
|
+
%button{ :class => "btn", :type => "submit" } Change
|
60
|
+
%tr
|
61
|
+
%th Bytes Uploaded
|
62
|
+
%td {{torrent.bytesUploaded}}
|
63
|
+
%tr
|
64
|
+
%th Bytes Downloaded
|
65
|
+
%td {{torrent.bytesDownloaded}}
|
66
|
+
|
67
|
+
%h2 Files ({{torrent.info.files.length}})
|
68
|
+
|
69
|
+
%table{ :class => "table" }
|
70
|
+
%tr
|
71
|
+
%th Path
|
72
|
+
%th Length
|
73
|
+
%tr{ "ng-repeat" => "file in torrent.info.files" }
|
74
|
+
%td {{file.path}}
|
75
|
+
%td {{file.length}}
|
76
|
+
|
77
|
+
%h2 Peers ({{torrent.peers.length}})
|
78
|
+
|
79
|
+
%table{ :class => "table" }
|
80
|
+
%tr
|
81
|
+
%th IP
|
82
|
+
%th Port
|
83
|
+
%th Upload Rate
|
84
|
+
%th Download Rate
|
85
|
+
%th Percent Complete
|
86
|
+
%tr{ "ng-repeat" => "peer in torrent.peers" }
|
87
|
+
%td {{peer.trackerPeer.ip}}
|
88
|
+
%td {{peer.trackerPeer.port}}
|
89
|
+
%td {{peer.uploadRate}}
|
90
|
+
%td {{peer.downloadRate}}
|
91
|
+
%td {{peer.pctComplete}}
|
@@ -0,0 +1,76 @@
|
|
1
|
+
%div{ :class => "row"}
|
2
|
+
|
3
|
+
%ul{ :class => "nav nav-pills"}
|
4
|
+
%li{ :class => "active" }
|
5
|
+
%a{ :href => "#" } Main
|
6
|
+
%li
|
7
|
+
%a{ :href => "#/config" } Config
|
8
|
+
|
9
|
+
%div{ :class => "alert alert-error", "ng-repeat" => "error in rootErrors()" }
|
10
|
+
%button{ :type => "button", :class => "close", "ng-click" => "deleteError(error)"} ×
|
11
|
+
{{error}}
|
12
|
+
|
13
|
+
%div{ :class => "alert alert-error", "ng-repeat" => "error in errors" }
|
14
|
+
%button{ :type => "button", :class => "close", "ng-click" => "deleteError(error)"} ×
|
15
|
+
{{error}}
|
16
|
+
|
17
|
+
%div{ }
|
18
|
+
%p
|
19
|
+
Usage this month: {{monthlyUsage}}, today: {{dailyUsage}}.
|
20
|
+
|
21
|
+
%div{ :class => "span4"}
|
22
|
+
%form{ "ng-submit" => "downloadTorrentFile()" }
|
23
|
+
%fieldset
|
24
|
+
%label Download using .torrent link
|
25
|
+
%div{ :class => "input-append" }
|
26
|
+
%input{ :class => "span2", :type => "text", :placeholder => "paste url", "ng-model" => "torrentToDownload" }
|
27
|
+
%button{ :class => "btn", :type => "submit" } Get
|
28
|
+
%div{ :class => "span4"}
|
29
|
+
%form{ "ng-submit" => "startMagnetLink()" }
|
30
|
+
%fieldset
|
31
|
+
%label Download using Magnet
|
32
|
+
%div{ :class => "input-append" }
|
33
|
+
%input{ :class => "span2", :type => "text", :placeholder => "paste magnet url", "ng-model" => "magnetToStart" }
|
34
|
+
%button{ :class => "btn", :type => "submit" } Get
|
35
|
+
%div{ :class => "span4"}
|
36
|
+
%form{:method => "post", :enctype => "multipart/form-data", :action => "/upload_torrent", :target => "uploadiframe", :id => "torrent_upload_form" }
|
37
|
+
%fieldset
|
38
|
+
%label Download using .torrent
|
39
|
+
%input{ :class => "span3", :type => "file", :placeholder => "browse...", :name => "torrentfile" }
|
40
|
+
%button{ :class => "btn", :type => "submit" } Upload
|
41
|
+
|
42
|
+
%table{ :class => "table table-striped table-condensed" }
|
43
|
+
%tr
|
44
|
+
%th Name
|
45
|
+
%th Size
|
46
|
+
%th Status
|
47
|
+
%th Rates
|
48
|
+
%th Progress
|
49
|
+
%th ETA
|
50
|
+
%th Actions
|
51
|
+
%tr{ "ng-repeat" => "torrent in currentPage" }
|
52
|
+
%td {{torrent.recommendedName}}
|
53
|
+
%td {{torrent.dataLength}}
|
54
|
+
%td {{stateForDisplay(torrent.infoHash)}}
|
55
|
+
%td {{torrent.downloadRate}}/{{torrent.uploadRate}}
|
56
|
+
%td {{torrent.percentComplete}}%
|
57
|
+
%td {{torrent.timeLeft}}
|
58
|
+
%td
|
59
|
+
%div{ :class => "dropdown" }
|
60
|
+
%button{ :class => "btn dropdown-toggle", "data-toggle" => "dropdown", :href => "#" } Actions
|
61
|
+
%ul{ :class => "dropdown-menu"}
|
62
|
+
%li
|
63
|
+
%a{ :href =>"#", "ng-click" => "togglePause(torrent.infoHash)" } {{pauseMenuItem(torrent.infoHash)}}
|
64
|
+
%a{ :href =>"#", "ng-click" => "deleteTorrent(torrent.infoHash, false)"} Delete Torrent
|
65
|
+
%a{ :href =>"#", "ng-click" => "deleteTorrent(torrent.infoHash, true)"} Delete Torrent and Files
|
66
|
+
%a{ :href =>"#/details/{{torrent.infoHash}}"} Details
|
67
|
+
|
68
|
+
%div{ :class => "pagination pagination-right" }
|
69
|
+
%ul
|
70
|
+
%li
|
71
|
+
%a{ :href => "", "ng-click" => "decrementPage()" } Prev
|
72
|
+
%li{ "ng-repeat" => "page in pagesInfo", :class => "{{page.style}}" }
|
73
|
+
%a{ :href => "", "ng-click" => "setPage(page.number)" } {{page.number}}
|
74
|
+
%li
|
75
|
+
%a{ :href => "", "ng-click" => "incrementPage()" } Next
|
76
|
+
|