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.
@@ -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)"} &times;
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)"} &times;
15
+ {{error}}
16
+
17
+ %div{ :class => "alert alert-error", "ng-repeat" => "error in errors" }
18
+ %button{ :type => "button", :class => "close", "ng-click" => "deleteError(error)"} &times;
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)"} &times;
9
+ {{error}}
10
+
11
+ %div{ :class => "alert alert-error", "ng-repeat" => "error in errors" }
12
+ %button{ :type => "button", :class => "close", "ng-click" => "deleteError(error)"} &times;
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)"} &times;
11
+ {{error}}
12
+
13
+ %div{ :class => "alert alert-error", "ng-repeat" => "error in errors" }
14
+ %button{ :type => "button", :class => "close", "ng-click" => "deleteError(error)"} &times;
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
+