Added TreeSearch and lots of cleaning up/improvements.
* Added watch for gulp.
* Updated README
* Various bug fixes and improvements.
Cleaning up and debugging. Gulp can now watch.
Change-Id: Ice679a326ff693fa030caf429c3c29ba94ae00cd
diff --git a/client/README.md b/client/README.md
index 2e20aad..88e5cd4 100644
--- a/client/README.md
+++ b/client/README.md
@@ -6,23 +6,51 @@
Setup
-----
-To simply run the client code, you will need the following things setup:
+###To simply run the client code:
+
+You will need the following things setup:
* A NDN backend running somewhere (The default config is pointed at a test backend)
* NDN-JS
+ Run `git submodule init ndn-js` in the client directory.
+ Then run `git submodule update`
-* Configure the config.json
+* Configure the config.json in catalog-dev
+ If it doesn't exist, you will need to copy it from the config-example.json
- + config.json is intentionally left out of the git to prevent overwriting it.
+ + config.json is intentionally left out of the git to prevent overwriting your changes.
+
+_Note: The dev code runs from catalog-dev and is not gauranteed to run across all browsers. Only the deployment code is prepared in such a way that we can promise the code will work. (Internet explorer may work but is not officially supported by either option)_
+
+###To run the deployment code:
+
+You will additionally require npm and node installed (npm is packaged with the default node installation).
+
+Run the following:
+```
+npm install -g gulp
+npm install
+gulp
+```
+
+The site will now be available in the catalog directory.
+
+If you run into issues of requiring sudo access you may need to [configure the npm prefix](http://competa.com/blog/2014/12/how-to-run-npm-without-sudo/) to point your global package repo somewhere else. Should you find yourself in a situation that you don't mind using sudo, feel free to simply just run with sudo.
+
+##Serving the site:
+
+To serve the site, point a webserver at the same directory this README file is in. Then give users the url: http://<your domain>/catalog or /catalog-dev depending on if you are running deployment code.
+
+HTTPS is not supported and will break the code as it is unless the ndn backend is running a valid certificate as well, this is due to a security rule in most browsers that restricts the ws protocol from running in https tabs/frames. All content in the https frame MUST be secure. (Aka run wss in https) (HTTPS is not officially supported)
config.json
-----------
-catalogPrefix - This is where you are doing your catalog queries. This should be the root of where we are querying in ndn.
+###Global
+* CatalogPrefix - Where should the catalog attach in the URI scheme? (Usually the root of a catalog)
+* FaceConfig - A valid NDN node location running the websocket for NDN-JS.
-faceConfig - Configure where you have your backend running and on which port it is listening. To setup a new backend, please read the readme at the root of the repo.
-
+###Retrieval
+* DemoKey - The public and private portion of an RSA in Base64. This key must be valid in the NDN Network for it to work.
+* Destinations - A list of retrieval URIs. These must be running the retrieval code or retrieval will fail.
Changing the theme
------------------
diff --git a/client/catalog-dev/background.jpg b/client/catalog-dev/background.jpg
deleted file mode 100644
index 7196c30..0000000
--- a/client/catalog-dev/background.jpg
+++ /dev/null
Binary files differ
diff --git a/client/catalog-dev/config-example.json b/client/catalog-dev/config-example.json
index 5a0455b..9c37d97 100644
--- a/client/catalog-dev/config-example.json
+++ b/client/catalog-dev/config-example.json
@@ -2,7 +2,7 @@
"global": {
"catalogPrefix": "/catalog",
"faceConfig": {
- "host": "atmos-csu.research-lan.colostate.edu",
+ "host": "atmos-nwsc.ucar.edu",
"port": 9696
}
},
@@ -12,7 +12,7 @@
"priv": "MIIEpQIBAAKCAQEAuAmnWYKE7E8G+hyy4TiTU7t91KyIGvglEeT6HWEkW4LKzXLO22a1jVS9+yP96I6vp7N5vpS1t7oXtgWuzkO+O85u6gfbvwp+67zJe2I89eHO4dmNnP4fx/j7WcCUCyzZfbyW67h5IoouoBIdQge2Xdvh9rFdex9UUhyjEZv5676zlcqlhz8xGBrJmQHsqpD9ijY1XhKBvoSIoQ0ZKkpmwVk8QYM9PbjUqzSQBj4aYXS+BPV6aRudVvyDt2DBXp2FNP0CGrosCXKnSl4Yv8BYp0k0RmFZDuJuntLb/XIvPEfMX5li7g3zHzAlIJIVSwT+FRkd3H5cECFSIZFUYIuSQQIDAQABAoIBAQCKBftzfxavn6lM5T8m+GZN0vzRBsBg8Z/jpsYKSLOayiHNKYCIPaSFpXuCIYEo6/JDJLB2xVLvwupLgkGSwm2mrvCyJkihI38Cz6iQF6I+iia9bYrupgwxzsK7klm1c+J9kXXivYxj4hyLwmoc/mnARMtYV7cTQvDbUEzgRQmPykWKBv6Y0SL1WprfiRfKIMwSqQk91ffj6whKxBLAuUdseVBmo/ivLPq0a+wDrcvaJAxSB4eIwCHzAugkRA/NoK0vG3mra0lK5jvQrcNIuffxNAnresDVDTnYRc42etjePLAhlpeK/4sjYE/wPdeP8yzLHUg/hsSpAPIjLXJNZqUBAoGBANxPmUQNf1lGHo/nLY3dVMD3+kYNnTUD8XwS81qdg8/dNyF8t+7DOdJ1j7Itb+zGA1XXAGfTm6JoUG+eKKR2OSuyZcxygpOgzxAFanXKhTWZsKbG70xNmX0sOAEhtTGsgFTEGEv977MwIlFa6n2bsp3Luj/AGmvNsOYvBDPXOklxAoGBANXZyXAaE7M5JALusLuEFxLGvWVz6TRdQ//c+FWvKrnh+nFlTlAPpDvlaPJJca8ViNevxJ2UhGtbENXAqgwTYpnAi/yQD4dATViIveK6Pn4t12mpPAlkMbbMTR8jtp5l1oHchcwe8QuEOKuTX5+STpNGlWs+tsMb12mhCpc3eO3RAoGAMxjDE2WOA8afkACuMBkFbzwUb+r4azNe7sf2aS3fRHaqMroabuYYoxdhHJItQ10pqN8U2P/bOO+4uCqWgo5o9BmMQr7MSjEh1TVsW6V8/9GFhyjcl3XoA4Ad/SU0QTEhEofomrdqwMSJMRVFDZzu8Gov6FlFx3sNbFW7Q8rHWgECgYEAq/TVz3iIgsLdvCXmosHSM9zvCpcr3FlqhmFOpseVmaamVWxajnIlY6xSuRBpg5nTUWwas4Nq/1BYtyiXE+K6lFuJtOq6Mc145EoANkIAYkHGR0Y36m1QtGaPVQzImZHV7NJAHCR9Ov90+jIk4BErca1+FKB3IWhPzLYb6ABJEyECgYEAthhzWSxPkqyiLl+2vnhdR3EEkvDX6MV6hGu4tDAf2A1Y0GSApyEaSAA31hlxu5EgneLD7Ns2HMpIfQMydB5lcwKQc9g/tVI1eRzuk6Myi+2JmPEM2BLyiX8yI+xnZlKDiZleQitCS4RQGz5HbXT70aYQIGxuvkQ/uf68jdrL6o8="
},
"destinations": [
- "/retrieve/demo"
+ "/retrieve/ucar"
]
}
}
diff --git a/client/catalog-dev/css/style.css b/client/catalog-dev/css/style.css
index 94ea89f..17ba979 100644
--- a/client/catalog-dev/css/style.css
+++ b/client/catalog-dev/css/style.css
@@ -142,6 +142,10 @@
cursor: pointer;
}
+.treeExplorer .treeExplorerNode .nodeContent {
+ display: inline-block;
+}
+
.treeExplorer .treeExplorerNode > .nodeChildren {
display: none;
}
@@ -168,6 +172,14 @@
content: "\e022"
}
+.treeExplorer .treeSearch{
+ display: none;
+}
+
+.treeExplorer .nodeContent:hover > .treeSearch {
+ display: inline-block;
+}
+
#popup {
top: 0;
left: 0;
diff --git a/client/catalog-dev/index.html b/client/catalog-dev/index.html
index 590fcfb..6f43aa0 100644
--- a/client/catalog-dev/index.html
+++ b/client/catalog-dev/index.html
@@ -22,6 +22,7 @@
<!-- Scripts -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
+<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-scrollTo/2.1.0/jquery.scrollTo.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
<script src="../ndn-js/build/ndn.min.js"></script>
<script src="js/autocomplete.js"></script>
@@ -168,7 +169,7 @@
<p>Select a destination and press submit if you are sure you want to download the selected data to the selected destination.</p>
<div class="dropdown">
<button class="btn btn-default dropdown-toggle" type="button" data-toggle="dropdown">
- Destination
+ <span id="requestDropText">Destination<span>
<span class="caret"></span>
</button>
<ul id="requestDest" class="dropdown-menu"></ul>
diff --git a/client/catalog-dev/js/catalog.js b/client/catalog-dev/js/catalog.js
index d013fda..446a7c5 100644
--- a/client/catalog-dev/js/catalog.js
+++ b/client/catalog-dev/js/catalog.js
@@ -158,14 +158,40 @@
$('#treeSearch div').treeExplorer(function(path, callback){
console.log("Tree Explorer request", path);
ga('send', 'event', 'tree', 'request');
- scope.autoComplete(path, function(list){
+ scope.autoComplete(path, function(data){
+ var list = data.next;
+ var last = (data.lastComponent === true);
console.log("Autocomplete response", list);
callback(list.map(function(element){
- return (path == "/"?"/":"") + element + "/";
+ return (path == "/"?"/":"") + element + (!last?"/":"");
}));
})
});
+ $('#treeSearch').on('click', '.treeSearch', function(){
+ var t = $(this);
+
+ scope.clearResults();
+
+ var path = t.parent().parent().attr('id');
+
+ console.log("Stringing tree search:", path);
+
+ scope.query(scope.catalog, {'??': path},
+ function(interest, data){ //Success
+ console.log("Tree search response", interest, data);
+
+ scope.name = data.getContent().toString().replace(/[\n\0]+/g,'');
+
+ scope.getResults(0);
+ },
+ function(interest){ //Failure
+ console.warn("Request failed! Timeout", interest);
+ scope.createAlert("Request timed out.\""+ interest.getName().toUri() + "\" See console for details.");
+ });
+
+ });
+
this.setupRequestForm();
}
@@ -245,7 +271,7 @@
function(interest, data){
if (data.getContent().length !== 0){
- callback(JSON.parse(data.getContent().toString().replace(/[\n\0]/g, "")).next);
+ callback(JSON.parse(data.getContent().toString().replace(/[\n\0]/g, "")));
} else {
callback([]);
}
@@ -308,6 +334,8 @@
$('#results').removeClass('hidden').slideDown();
}
+ $.scrollTo("#results", 700);
+
if ((this.results.length === this.resultCount) || (this.resultsPerPage * (index + 1) < this.results.length)){
//console.log("We already have index", index);
this.page = index;
@@ -452,7 +480,8 @@
$('#request').modal('hide')//Initial params are ok. We can close the form.
.remove('.alert') //Remove any alerts
- .find('.active').removeClass('active'); //Disable the active destination
+
+ scope.cleanRequestForm();
$(this).off(e); //Don't fire this again, the request must be regenerated
@@ -500,12 +529,13 @@
scope.face.expressInterest(interest,
function(interest, data){ //Success
console.log("Request for", prefix.toUri(), "succeeded.", interest, data);
+ scope.createAlert("Data retrieval has initiated.", "alert-success");
}, function(interest){ //Failure
- console.error("Failure to request", prefix.toUri(), interest);
- scope.createAlert("Failed to request " + prefix.toUri() + ". This means that the retrieve failed! See console for more details.");
+ console.error("Request for", prefix.toUri(), "timed out.", interest);
+ scope.createAlert("Request for " + prefix.toUri() + " timed out. This means that the retrieve failed! See console for more details.");
}
);
- }, 10000); //Wait 10 seconds
+ }, 5000); //Wait 5 seconds
scope.face.registerPrefix(retrievePrefix,
function(prefix, interest, face, interestFilterId, filter){ //On Interest
@@ -519,7 +549,6 @@
try {
face.putData(data);
console.log("Responded for", interest.getName().toUri(), data);
- scope.createAlert("Data retrieval has initiated.", "alert-success");
} catch (e) {
console.error("Failed to respond to", interest.getName().toUri(), data);
scope.createAlert("Data retrieval failed.");
@@ -644,10 +673,20 @@
}
+ Atmos.prototype.cleanRequestForm = function(){
+ $('#requestDest').prev().removeClass('btn-success').addClass('btn-default');
+ $('#requestDropText').text('Destination');
+ $('#requestDest .active').removeClass('active');
+ }
+
Atmos.prototype.setupRequestForm = function(){
+
+ var scope = this;
+
this.requestForm.find('#requestCancel').click(function(){
$('#request').unbind('submit') //Removes all event handlers.
.modal('hide'); //Hides the form.
+ scope.cleanRequestForm();
});
var dests = $(this.config['retrieval']['destinations'].reduce(function(prev, current){
@@ -660,7 +699,10 @@
this.requestForm.find('#requestDest').append(dests)
.on('click', 'a', function(e){
$('#requestDest .active').removeClass('active');
- $(this).parent().addClass('active');
+ var t = $(this);
+ t.parent().addClass('active');
+ $('#requestDropText').text(t.text());
+ $('#requestDest').prev().removeClass('btn-default').addClass('btn-success');
});
//This code will remain unused until users must use their own keys instead of the demo key.
diff --git a/client/catalog-dev/js/treeExplorer.js b/client/catalog-dev/js/treeExplorer.js
index 5037629..6b67804 100644
--- a/client/catalog-dev/js/treeExplorer.js
+++ b/client/catalog-dev/js/treeExplorer.js
@@ -27,7 +27,8 @@
var el = $('<div class="treeExplorerNode"></div>');
if (current.match(/\/$/)){
el.attr('id', path + current);
- el.append(['<a href="#' , path , current , '">' , current , '</a>'].join(""));
+ el.append(['<div class="nodeContent"><a href="#', path, current, '">', current,
+ '</a> <button class="treeSearch btn btn-success btn-xs">Search from here</button></div>'].join(""));
} else {
el.addClass('file');
el.text(current);
@@ -36,8 +37,8 @@
});
}
- tree.on('click', '.treeExplorerNode > a', function(){
- var node = $(this).parent();
+ tree.on('click', '.treeExplorerNode > .nodeContent > a', function(){
+ var node = $(this).parent().parent();
if (node.hasClass('open')){ //Are we open already?
node.removeClass('open');
diff --git a/client/gulpfile.js b/client/gulpfile.js
index cc79d11..585a791 100644
--- a/client/gulpfile.js
+++ b/client/gulpfile.js
@@ -1,3 +1,4 @@
+//Includes
var gulp = require('gulp');
var minifyHTML = require('gulp-minify-html');
var minifyCSS = require('gulp-minify-css');
@@ -5,9 +6,14 @@
var closure = require('gulp-closure-compiler-service');
var clean = require('gulp-clean');
+//Globs
+var cssGlob = ['./catalog-dev/css/style.css', './catalog-dev/css/cubeLoader.css'];
+var jsGlob = './catalog-dev/js/*.js';
+var htmlGlob = './catalog-dev/index.html';
+
gulp.task('minify-html', function() {
- return gulp.src(['./catalog-dev/index.html'])
+ return gulp.src(htmlGlob)
.pipe(minifyHTML())
.pipe(gulp.dest('./catalog/'));
@@ -15,21 +21,21 @@
gulp.task('minify-js', function() {
- return gulp.src('./catalog-dev/js/*.js')
+ return gulp.src(jsGlob, {base: 'catalog-dev'})
.pipe(sourcemaps.init())
.pipe(closure())
- .pipe(sourcemaps.write('../../catalog-dev/js'))
- .pipe(gulp.dest('./catalog/js'));
+ .pipe(sourcemaps.write({sourceRoot: '/catalog-dev', includeContent: false}))
+ .pipe(gulp.dest('./catalog'));
});
gulp.task('minify-css', function() {
- return gulp.src(['./catalog-dev/css/style.css', './catalog-dev/css/cubeLoader.css'])
+ return gulp.src(cssGlob, {base: 'catalog-dev'})
.pipe(sourcemaps.init())
.pipe(minifyCSS())
- .pipe(sourcemaps.write('../../catalog-dev/css'))
- .pipe(gulp.dest('./catalog/css'));
+ .pipe(sourcemaps.write({sourceRoot: '/catalog-dev', includeContent: false}))
+ .pipe(gulp.dest('./catalog'));
});
@@ -50,5 +56,14 @@
});
+gulp.task('watch', ['default'], function(){
+
+ gulp.watch(cssGlob, ['minify-css']);
+ gulp.watch(jsGlob, ['minify-js']);
+ gulp.watch(htmlGlob, ['minify-html']);
+ gulp.watch(['./catalog-dev/config.json', './catalog-dev/css/*.min.css'], ['copy']);
+
+});
+
gulp.task('default', ['minify-html', 'minify-css', 'minify-js', 'copy']);