[tor-commits] [ooni-probe/master] Add support for viewing test results and uploading inputs

art at torproject.org art at torproject.org
Wed Jun 19 12:32:46 UTC 2013


commit 4f51cd3107bc1f9e65be587aeb3b41f7a584e3d7
Author: Arturo Filastò <art at fuffa.org>
Date:   Thu Apr 25 13:33:46 2013 +0200

    Add support for viewing test results and uploading inputs
---
 data/ui/app/index.html                  |    1 +
 data/ui/app/libs/ng-upload/ng-upload.js |  107 +++++++++++++++++++++++++++++++
 data/ui/app/scripts/app.js              |   21 +++---
 data/ui/app/scripts/controllers.js      |   36 ++++++++---
 data/ui/app/scripts/services.js         |    9 ++-
 data/ui/app/styles/app.css              |    5 ++
 data/ui/app/views/inputs.html           |   31 +++++++++
 data/ui/app/views/sidebar.html          |    4 ++
 data/ui/app/views/test-list.html        |   38 -----------
 data/ui/app/views/test-status.html      |    7 --
 data/ui/app/views/test.html             |   42 ++++++++++++
 ooni/api/spec.py                        |    6 +-
 12 files changed, 239 insertions(+), 68 deletions(-)

diff --git a/data/ui/app/index.html b/data/ui/app/index.html
index a903ceb..c304066 100644
--- a/data/ui/app/index.html
+++ b/data/ui/app/index.html
@@ -28,6 +28,7 @@
 
   <script src="libs/angular/angular.js"></script>
   <script src="libs/angular-resource/angular-resource.js"></script>
+  <script src="libs/ng-upload/ng-upload.js"></script>
   <script src="scripts/app.js"></script>
   <script src="scripts/services.js"></script>
   <script src="scripts/controllers.js"></script>
diff --git a/data/ui/app/libs/ng-upload/ng-upload.js b/data/ui/app/libs/ng-upload/ng-upload.js
new file mode 100644
index 0000000..fae7a44
--- /dev/null
+++ b/data/ui/app/libs/ng-upload/ng-upload.js
@@ -0,0 +1,107 @@
+// Version 0.3.2
+// AngularJS simple file upload directive
+// this directive uses an iframe as a target
+// to enable the uploading of files without
+// losing focus in the ng-app.
+//
+// <div ng-app="app">
+//   <div ng-controller="mainCtrl">
+//    <form action="/uploads" ng-upload> 
+//      <input type="file" name="avatar"></input>
+//      <input type="submit" value="Upload" 
+//         upload-submit="submited(content, completed)"></input>
+//    </form>
+//  </div>
+// </div>
+//
+//  angular.module('app', ['ngUpload'])
+//    .controller('mainCtrl', function($scope) {
+//      $scope.submited = function(content, completed) {
+//        if (completed) {
+//          console.log(content);
+//        }
+//      }  
+//  });
+//
+angular.module('ngUpload', [])
+  .directive('uploadSubmit', ['$parse', function($parse) {
+    return {
+      restrict: 'AC',
+      link: function(scope, element, attrs) {
+        // Options (just 1 for now) 
+        // Each option should be prefixed with 'upload-options-' or 'uploadOptions'
+        // {
+        //    // specify whether to enable the submit button when uploading forms
+        //    enableControls: bool 
+        // }
+        var options = {};
+        options.enableControls = attrs.uploadOptionsEnableControls;
+
+        // submit the form - requires jQuery
+        var form = element.parents('form[ng-upload]') || element.parents('form.ng-upload'); 
+
+        // Retrieve the callback function
+        var fn = $parse(attrs.uploadSubmit);
+
+        if (!angular.isFunction(fn)) {
+            var message = "The expression on the ngUpload directive does not point to a valid function.";
+            throw message + "\n";
+        }
+
+        element.bind('click', function($event) {
+          // prevent default behavior of click
+          $event.preventDefault = true;
+          // create a new iframe
+          var iframe = angular.element("<iframe id='upload_iframe' name='upload_iframe' border='0' width='0' height='0' style='width: 0px; height: 0px; border: none; display: none' />");
+
+          // attach function to load event of the iframe
+          iframe.bind('load', function () {
+            // get content - requires jQuery
+            var content = iframe.contents().find('body').text();
+            // execute the upload response function in the active scope
+            scope.$apply(function () { 
+              fn(scope, { content: content, completed: true});
+            });
+            // remove iframe
+            if (content !== "") { // Fixes a bug in Google Chrome that dispose the iframe before content is ready.
+                setTimeout(function () { iframe.remove(); }, 250);
+            }
+            element.attr('disabled', null);
+            element.attr('title', 'Click to start upload.');
+          });
+
+          // add the new iframe to application
+          form.parent().append(iframe);
+
+          scope.$apply(function () { 
+            fn(scope, {content: "Please wait...", completed: false }); 
+          });
+
+          var enabled = true;
+          if (!options.enableControls) {
+              // disable the submit control on click
+              element.attr('disabled', 'disabled');
+              enabled = false;
+          }
+          // why do we need this???
+          element.attr('title', (enabled ? '[ENABLED]: ' : '[DISABLED]: ') + 'Uploading, please wait...');
+
+          form.submit();
+
+        }).attr('title', 'Click to start upload.');
+      }
+    };
+  }])
+  .directive('ngUpload', ['$parse', function ($parse) {
+    return {
+      restrict: 'AC',
+      link: function (scope, element, attrs) {
+        element.attr("target", "upload_iframe");
+        element.attr("method", "post");
+        // Append a timestamp field to the url to prevent browser caching results
+        element.attr("action", element.attr("action") + "?_t=" + new Date().getTime());
+        element.attr("enctype", "multipart/form-data");
+        element.attr("encoding", "multipart/form-data");
+      }
+    };
+  }]);
\ No newline at end of file
diff --git a/data/ui/app/scripts/app.js b/data/ui/app/scripts/app.js
index 5fb8fdf..d36a17c 100644
--- a/data/ui/app/scripts/app.js
+++ b/data/ui/app/scripts/app.js
@@ -2,28 +2,29 @@
 
 
 // Declare app level module which depends on filters, and services
-var ooniprobe = angular.module('ooniprobe', ['ooniprobe.services']).
+var ooniprobe = angular.module('ooniprobe', ['ngUpload', 'ooniprobe.services']).
   config(['$routeProvider', function($routeProvider) {
-    $routeProvider.when('/test-status',
+
+    $routeProvider.when('/inputs',
       {
-        templateUrl: 'views/test-status.html',
-        controller: 'PageCtrl'
+        templateUrl: 'views/inputs.html',
+        controller: 'InputsCtrl'
       }
     );
 
-    $routeProvider.when('/test-list',
+    $routeProvider.when('/settings',
       {
-        templateUrl: 'views/test-list.html',
-        controller: 'TestListCtrl'
+        templateUrl: 'views/settings.html',
+        controller: 'SettingsCtrl'
       }
     );
 
     $routeProvider.when('/test/:testID',
       {
-        templateUrl: 'views/test-list.html',
-        controller: 'TestListCtrl'
+        templateUrl: 'views/test.html',
+        controller: 'TestCtrl'
       }
     );
 
-    $routeProvider.otherwise({redirectTo: '/test-status'});
+    $routeProvider.otherwise({redirectTo: '/settings'});
   }]);
diff --git a/data/ui/app/scripts/controllers.js b/data/ui/app/scripts/controllers.js
index 489dfd2..5ba24e1 100644
--- a/data/ui/app/scripts/controllers.js
+++ b/data/ui/app/scripts/controllers.js
@@ -3,16 +3,16 @@
 ooniprobe.controller('PageCtrl', ['$scope', function($scope) {
 }]);
 
-ooniprobe.controller('TestListCtrl', ['$scope', '$routeParams', 'testStatus',
-                     function($scope, $routeParams, testStatus) {
+ooniprobe.controller('SettingsCtrl', ['$scope',
+                     function($scope) {
+}]);
 
-  var testID = $routeParams['testID'];
-  $scope.updateTestStatus = function() {
-    testStatus(testID).success(function(testDetails){
-      $scope.testDetails = testDetails;
-    });
+ooniprobe.controller('InputsCtrl', ['$scope', 'Inputs',
+                     function($scope, Inputs) {
+  $scope.inputs = Inputs.query();
+  $scope.uploadComplete = function(contents, completed) {
+    return;
   }
-  $scope.updateTestStatus();
 
 }]);
 
@@ -30,13 +30,31 @@ ooniprobe.controller('SideBarCtrl', ['$scope', 'listTests', '$location',
 
 }]);
 
+ooniprobe.controller('TestCtrl', ['$scope', '$routeParams', 'testStatus', 'Inputs',
+                     function($scope, $routeParams, testStatus, Inputs) {
+
+  var testID = $routeParams['testID'];
+
+  $scope.inputs = Inputs.query();
+
+  $scope.updateTestStatus = function() {
+    testStatus(testID).success(function(testDetails){
+      $scope.testDetails = testDetails;
+    });
+  }
+  $scope.updateTestStatus();
+
+
+}]);
+
 ooniprobe.controller('TestBoxCtrl', ['$scope', 'startTest',
                      function($scope, startTest) {
 
   $scope.startTest = function() {
     var options = {};
 
-    angular.forEach($scope.testDetails.arguments, function(option, key){
+    angular.forEach($scope.testDetails.arguments,
+                    function(option, key) {
       options[key] = option.value;
     });
 
diff --git a/data/ui/app/scripts/services.js b/data/ui/app/scripts/services.js
index 3d6721d..ea013ee 100644
--- a/data/ui/app/scripts/services.js
+++ b/data/ui/app/scripts/services.js
@@ -2,7 +2,7 @@
 
 angular.module('ooniprobe.services', ['ngResource']).
   factory('listTests', ['$resource',
-          function($resource){
+          function($resource) {
     return $resource('/test');
 }]).
   factory('testStatus', ['$http', function($http){
@@ -10,11 +10,16 @@ angular.module('ooniprobe.services', ['ngResource']).
       return $http.get('/test/' + testID);
     }
 }]).
-  factory('startTest', ['$http', function($http){
+  factory('startTest', ['$http',
+          function($http) {
     return function(testID, options) {
       return $http.post('/test/' + testID + '/start', options);
     }
 }]).
+  factory('Inputs', ['$resource',
+          function($resource) {
+    return $resource('/inputs');
+}]).
   factory('status', ['$resource',
     function($resource) {
     return $resource('/status');
diff --git a/data/ui/app/styles/app.css b/data/ui/app/styles/app.css
index 5fe454f..b4a0fb8 100644
--- a/data/ui/app/styles/app.css
+++ b/data/ui/app/styles/app.css
@@ -14,3 +14,8 @@
   background-color: rgb(240, 240, 240);
 }
 
+.testResult {
+  height: 200px;
+  overflow-y: scroll;
+  overflow-x: hidden;
+}
diff --git a/data/ui/app/views/inputs.html b/data/ui/app/views/inputs.html
new file mode 100644
index 0000000..767e00e
--- /dev/null
+++ b/data/ui/app/views/inputs.html
@@ -0,0 +1,31 @@
+<div class="row">
+  <div class="span8">
+    <h2>Inputs</h2>
+    <ul class="unstyled">
+      <li ng-repeat="input in inputs">{{input.filename}}
+        <!-- button class="btn btn-small btn-danger" ng-click="input.$delete()">delete</button -->
+      </li>
+    </ul>
+    <form ng-upload action="/inputs">
+
+      <h4>Add file</h4>
+      <label>file</label>
+      <input type="file" name="file" />
+      <br/>
+      <button class="btn"
+              upload-submit="uploadComplete(contents, completed)">Upload</button>
+
+      <!-- h4>Add filename</h4>
+      <label>filename</label>
+      <input type="text" ng-model="fileName" />
+
+      <label>content</label>
+      <textarea ng-model="fileContent"></textarea>
+      <br/>
+      <button class="btn">Add</button -->
+    </form>
+
+
+  </div>
+</div>
+
diff --git a/data/ui/app/views/settings.html b/data/ui/app/views/settings.html
new file mode 100644
index 0000000..e69de29
diff --git a/data/ui/app/views/sidebar.html b/data/ui/app/views/sidebar.html
index 172a51e..5ab4630 100644
--- a/data/ui/app/views/sidebar.html
+++ b/data/ui/app/views/sidebar.html
@@ -3,4 +3,8 @@
   <li ng-repeat="test in test_list" ng-class="{'active': testSelected(test.id)}">
     <a href="#/test/{{test.id}}">{{test.name}}</a>
   </li>
+
+  <li class="nav-header">Configuration</li>
+  <li><a href="#/inputs">Inputs</a></li>
+  <li><a href="#/settings">Settings</a></li>
 </ul>
diff --git a/data/ui/app/views/test-list.html b/data/ui/app/views/test-list.html
deleted file mode 100644
index ffcf3fb..0000000
--- a/data/ui/app/views/test-list.html
+++ /dev/null
@@ -1,38 +0,0 @@
-<div class="row">
-  <div class="span8">
-    <h2>{{testDetails.name}}</h2>
-    <div class="netTest" ng-controller="TestBoxCtrl">
-      version: <span class="badge badge-success">{{testDetails.version}}</span>
-      <p>{{testDetails.description}}</p>
-      <form name="testOptions">
-        <div ng-repeat="(name, options) in testDetails.arguments">
-          <div ng-switch on="options.type">
-
-            <div ng-switch-when="file">
-              <label>{{name}}</label>
-              <input type="file" name="{{name}}">
-            </div>
-
-            <div ng-switch-default>
-              <label>{{name}}</label>
-              <input ng-model="testDetails.arguments[name].value" type="{{options.type}}"
-                   value="{{options.default}}">
-            </div>
-
-          </div>
-        </div>
-      </form>
-      <button class="btn btn-primary" ng-click="startTest()">Start Test</button>
-    </div>
-  </div>
-</div>
-
-<div class="row">
-  <div class="span8">
-    <h3>Test results</h3>
-    <div class="testResult" ng-repeat="result in testDetails.results">
-      <h4>{{result.name}}</h4>
-      <pre>{{result.content}}</pre>
-    </div>
-  </div>
-</div>
diff --git a/data/ui/app/views/test-status.html b/data/ui/app/views/test-status.html
deleted file mode 100644
index a457119..0000000
--- a/data/ui/app/views/test-status.html
+++ /dev/null
@@ -1,7 +0,0 @@
-<h2>Test Status</h2>
-<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
-tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
-quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
-consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
-cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
-proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
diff --git a/data/ui/app/views/test.html b/data/ui/app/views/test.html
new file mode 100644
index 0000000..f92461a
--- /dev/null
+++ b/data/ui/app/views/test.html
@@ -0,0 +1,42 @@
+<div class="row">
+  <div class="span8">
+    <h2>{{testDetails.name}}</h2>
+    <div class="netTest" ng-controller="TestBoxCtrl">
+      version: <span class="badge badge-success">{{testDetails.version}}</span>
+      <p>{{testDetails.description}}</p>
+      <form name="testOptions">
+        <div ng-repeat="(name, options) in testDetails.arguments">
+          <div ng-switch on="options.type">
+
+            <div ng-switch-when="file">
+              <label>{{name}}</label>
+              <select ng-model="testDetails.arguments[name].value">
+                <option ng-repeat="input in inputs" value="input.filename">{{input.filename}}</option>
+              </select>
+            </div>
+
+            <div ng-switch-default>
+              <label>{{name}}</label>
+              <input ng-model="testDetails.arguments[name].value" type="{{options.type}}"
+                   value="{{options.default}}">
+            </div>
+
+          </div>
+        </div>
+      </form>
+      <button class="btn btn-primary" ng-click="startTest()">Start Test</button>
+    </div>
+  </div>
+</div>
+
+<div class="row">
+  <div class="span8">
+    <h3>Test results</h3>
+    <button class="btn" ng-click="updateTestStatus()">
+      <i class="icon-refresh"></i>Reload</button>
+    <div ng-repeat="result in testDetails.results">
+      <h4>{{result.name}}</h4>
+      <pre class="testResult">{{result.content}}</pre>
+    </div>
+  </div>
+</div>
diff --git a/ooni/api/spec.py b/ooni/api/spec.py
index ec4cf4b..39df2fd 100644
--- a/ooni/api/spec.py
+++ b/ooni/api/spec.py
@@ -48,14 +48,15 @@ class Inputs(ORequestHandler):
         self.write(input_list)
 
     def post(self):
-        filename = self.get_argument("fullname", None)
+        input_file = self.request.files.get("file")[0]
+        filename = input_file['filename']
+
         if not filename or not re.match('(\w.*\.\w.*).*', filename):
             raise InvalidInputFilename
 
         if os.path.exists(filename):
             raise FilenameExists
 
-        input_file = self.request.files.get("input_file")
         content_type = input_file["content_type"]
         body = input_file["body"]
 
@@ -120,6 +121,7 @@ def get_test_results(test_id):
                 test_content = ''.join(f.readlines())
             test_results.append({'name': test_result,
                                  'content': test_content})
+    test_results.reverse()
     return test_results
 
 class TestStatus(ORequestHandler):





More information about the tor-commits mailing list