├── .gitignore ├── LICENSE ├── README.adoc ├── couch.provider.js ├── couchUpdateViews.js ├── doc ├── curlExamples.txt └── install.adoc ├── examples ├── curl │ ├── 0.getExecutionServers.sh │ ├── 1.createDocumentExample.sh │ ├── 2.addData.sh │ ├── 3.submit.sh │ ├── 4.jobstatus.sh │ ├── 5.getProcessedData.sh │ ├── 6.getDocument.sh │ └── 7.getUserDocuments.sh ├── data │ ├── example.json │ └── pic.jpg └── node │ ├── createNewJob.js │ └── package.json ├── hapitest.js ├── migrateUp.js ├── package-lock.json ├── package.json ├── setUpDevelopment.sh ├── src ├── clusterpost-app │ ├── README.md │ ├── get.js │ ├── index.js │ ├── package-lock.json │ ├── package.json │ ├── post.js │ └── test.js ├── clusterpost-auth │ ├── index.js │ ├── package-lock.json │ └── package.json ├── clusterpost-execution │ ├── README.md │ ├── conf.json.in │ ├── engine_awsecs.js │ ├── engine_lsf.js │ ├── engine_pbs.js │ ├── engine_slurm.js │ ├── engine_unix.js │ ├── executionserver.methods.js │ ├── index.js │ ├── index.js.in │ ├── jobdelete.js │ ├── jobkill.js │ ├── jobstatus.js │ ├── jobsubmit.js │ ├── package-lock.json │ ├── package.json │ ├── postinstall.js │ └── request_token.js ├── clusterpost-fs │ ├── .gitignore │ ├── index.js │ ├── package-lock.json │ └── package.json ├── clusterpost-lib │ ├── README.md │ ├── index.js │ ├── package-lock.json │ └── package.json ├── clusterpost-list-react │ ├── .babelrc │ ├── .editorconfig │ ├── .eslintrc │ ├── .gitignore │ ├── .travis.yml │ ├── README.md │ ├── package-lock.json │ ├── package.json │ ├── rollup.config.js │ └── src │ │ ├── .eslintrc │ │ ├── clusterpost-dashboard.js │ │ ├── clusterpost-jobs.js │ │ ├── clusterpost-service.js │ │ ├── clusterpost-software.js │ │ ├── clusterpost-tokens.js │ │ ├── index.js │ │ └── test.js ├── clusterpost-list │ ├── Gruntfile.js │ ├── LICENSE │ ├── README.md │ ├── dist │ │ ├── clusterpost-list.min.css │ │ └── clusterpost-list.min.js │ ├── package.json │ └── src │ │ ├── clusterpost-app.directive.html │ │ ├── clusterpost-app.directive.js │ │ ├── clusterpost-es-admin.directive.html │ │ ├── clusterpost-es-admin.directive.js │ │ ├── clusterpost-jobs.directive.html │ │ ├── clusterpost-jobs.directive.js │ │ ├── clusterpost-list.directive.js │ │ ├── clusterpost-list.module.js │ │ └── clusterpost-list.service.js ├── clusterpost-model │ ├── index.js │ ├── package-lock.json │ └── package.json ├── clusterpost-provider │ ├── README.md │ ├── clusterprovider.methods.js │ ├── couchUpdateViews.js │ ├── cronprovider.js │ ├── dataprovider.handlers.js │ ├── dataprovider.routes.js │ ├── executionserver.handlers.js │ ├── executionserver.routes.js │ ├── index.js │ ├── package-lock.json │ ├── package.json │ └── views │ │ ├── getJob.json │ │ ├── searchJob.json │ │ └── software.json ├── clusterpost-server │ ├── README.md │ ├── checkConfiguration.js │ ├── conf.default.json │ ├── index.js │ ├── migrateUp.js.in │ ├── package-lock.json │ └── package.json ├── clusterpost-static │ ├── clusterpost-public │ │ ├── .browserlistrc │ │ ├── .gitignore │ │ ├── README.md │ │ ├── package-lock.json │ │ ├── package.json │ │ ├── public │ │ │ ├── favicon.ico │ │ │ ├── images │ │ │ │ ├── fibers.png │ │ │ │ ├── icosahedron.png │ │ │ │ ├── segmentation.png │ │ │ │ └── subcortical.png │ │ │ ├── index.html │ │ │ └── manifest.json │ │ └── src │ │ │ ├── App.js │ │ │ ├── App.test.js │ │ │ ├── bootstrap.min.css │ │ │ ├── index.css │ │ │ ├── index.js │ │ │ ├── nav-bar.js │ │ │ ├── redux │ │ │ ├── navbar-reducer.js │ │ │ ├── reducers.js │ │ │ └── store.js │ │ │ └── serviceWorker.js │ ├── index.js │ ├── package-lock.json │ └── package.json ├── couch-provider │ ├── README.md │ ├── couch.provider.js │ ├── index.js │ ├── package-lock.json │ ├── package.json │ └── test.js ├── couch-update-views │ ├── README.md │ ├── couchUpdateViews.js.in │ ├── index.js │ ├── migrateUp.js │ ├── package-lock.json │ ├── package.json │ ├── postinstall.js │ ├── test.js │ ├── updateDesignDocument.js │ ├── views │ │ └── searchUser.json │ ├── views2 │ │ └── searchUser.json │ └── views3 │ │ └── searchUser3.json ├── hapi-jwt-couch-lib │ ├── .gitignore │ ├── README.md │ ├── index.js │ ├── package-lock.json │ ├── package.json │ ├── test.js │ └── testserver.js ├── hapi-jwt-couch │ ├── README.md │ ├── couchUpdateViews.js │ ├── index.js │ ├── jwtauth.handlers.js │ ├── jwtauth.routes.js │ ├── package-lock.json │ ├── package.json │ ├── test │ │ ├── server.js │ │ └── test.js │ └── views │ │ └── user.json └── react-hapi-jwt-auth │ ├── .babelrc │ ├── .editorconfig │ ├── .eslintrc │ ├── .gitignore │ ├── .travis.yml │ ├── README.md │ ├── package-lock.json │ ├── package.json │ ├── rollup.config.js │ └── src │ ├── .eslintrc │ ├── index.js │ ├── jwt-auth-interceptor.js │ ├── jwt-auth-profile.js │ ├── jwt-auth-reducer.js │ ├── jwt-auth-service.js │ ├── jwt-auth-users.js │ ├── jwt-auth.js │ ├── styles.css │ └── test.js └── test ├── conf.test.json ├── createNewJob.js ├── createNewJobOuputDir.js ├── createNewJobRemoteData.js ├── data └── gravitational-waves-simulation.jpg ├── downloadTokenTest.js ├── package-lock.json ├── package.json ├── sharedJobTest.js ├── test.js ├── tokensTest.js ├── userAuthTest.js └── userResetTest.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | conf.my.*.json 3 | 4 | all.log 5 | 6 | *.gz 7 | 8 | server/all.log 9 | conf.my.json 10 | .token 11 | conf.production.json 12 | conf.test.json 13 | bower_components 14 | -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | 2 | = Clusterpost 3 | 4 | Execute jobs in remote computing grids using a REST api. 5 | Data transfer, job execution and monitoring are all handled by clusterpost. 6 | 7 | Clusterpost uses https://nodejs.org/en/[node] with http://hapijs.com/[Hapijs] in the server side application plus https://couchdb.apache.org/[couchdb] for storage. 8 | 9 | Cluster post is easy to deploy and will integrate well with existing applications. 10 | 11 | Follow the guide described in doc/ to deploy the application at your site and the computing grid you would like to use. 12 | 13 | == Supported computing grids 14 | 15 | Load Sharing Facility (LSF) job scheduler. -------------------------------------------------------------------------------- /couchUpdateViews.js: -------------------------------------------------------------------------------- 1 | var couchUpdateViews = require('couch-update-views'); 2 | couchUpdateViews.couchUpdateViews(); -------------------------------------------------------------------------------- /doc/curlExamples.txt: -------------------------------------------------------------------------------- 1 | 2 | Create Job: 3 | 4 | curl -H Content-Type: application/json -X POST localhost:8180/dataprovider/ -d {"type": "job", "executable":"recon-all", "userEmail": "juanprietob@gmail.com"} 5 | 6 | Add data: 7 | curl -X PUT -F file=@pic.jpg localhost:8180/dataprovider/52f388e8f3e8045a9f6260e86c0d0bd7/pic.jpg 8 | 9 | Get job document: 10 | curl localhost:8180/dataprovider/52f388e8f3e8045a9f6260e86c0d0bd7 11 | 12 | Get job data: 13 | curl localhost:8180/dataprovider/52f388e8f3e8045a9f6260e86c0d0bd7/pic.jpg 14 | 15 | -------------------------------------------------------------------------------- /examples/curl/0.getExecutionServers.sh: -------------------------------------------------------------------------------- 1 | 2 | echo "Get the execution servers available, you can change this parameter in the example.json file in case you want to submit your yob to another server. " 3 | command="curl localhost:8180/executionserver" 4 | echo $command 5 | eval $command -------------------------------------------------------------------------------- /examples/curl/1.createDocumentExample.sh: -------------------------------------------------------------------------------- 1 | 2 | 3 | echo Creating job: 4 | 5 | command="curl -H 'Content-Type: application/json' -X POST localhost:8180/dataprovider --data @../data/example.json" 6 | 7 | echo $command 8 | eval $command 9 | 10 | -------------------------------------------------------------------------------- /examples/curl/2.addData.sh: -------------------------------------------------------------------------------- 1 | 2 | 3 | if [ ! -z "$1" ]; 4 | then 5 | echo "Add data:" 6 | command="curl -H 'Content-Type: application/octet-stream' -X PUT --data-binary @pic.jpg localhost:8180/dataprovider/$1/pic.jpg" 7 | echo $command 8 | eval $command 9 | else 10 | echo "Use the document id : _id to add data to process the job: bash 2.addData.sh _id" 11 | fi 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /examples/curl/3.submit.sh: -------------------------------------------------------------------------------- 1 | if [ ! -z "$1" ]; 2 | then 3 | 4 | echo "Launch the job execution" 5 | command="curl -X POST localhost:8180/executionserver/"$1 6 | echo $command 7 | eval $command 8 | 9 | else 10 | echo "Use the document id to submit the job: bash 5.submit.sh _id" 11 | fi -------------------------------------------------------------------------------- /examples/curl/4.jobstatus.sh: -------------------------------------------------------------------------------- 1 | if [ ! -z "$1" ]; 2 | then 3 | 4 | echo "Check for the job status:" 5 | command="curl localhost:8180/executionserver/"$1 6 | echo $command 7 | eval $command 8 | 9 | else 10 | echo "Use the document id to check for a particular job status: bash 6.submit.sh _id" 11 | fi 12 | -------------------------------------------------------------------------------- /examples/curl/5.getProcessedData.sh: -------------------------------------------------------------------------------- 1 | 2 | if [ ! -z "$1" ]; 3 | then 4 | 5 | processeddata="pic.eps" 6 | 7 | echo "Get the test job data:" 8 | command='curl localhost:8180/dataprovider/'$1'/'$processeddata > $processeddata 9 | echo $command 10 | eval $command 11 | 12 | else 13 | echo "Use the document id : _id to get the processeddata sh 5.getProcessedData.sh _id" 14 | fi -------------------------------------------------------------------------------- /examples/curl/6.getDocument.sh: -------------------------------------------------------------------------------- 1 | 2 | if [ ! -z "$1" ]; 3 | then 4 | 5 | echo Get job document: 6 | command='curl localhost:8180/dataprovider/'$1 7 | echo $command 8 | eval $command 9 | 10 | else 11 | echo "Use the document id : _id to add get the job document created: sh getDocument.sh _id" 12 | fi -------------------------------------------------------------------------------- /examples/curl/7.getUserDocuments.sh: -------------------------------------------------------------------------------- 1 | 2 | echo "Get user document: optionally specify the status as well as a query parameter jobstatus=[RUN, FAIL, UPLOADING]" 3 | command='curl localhost:8180/dataprovider/user?userEmail=juanprietob@gmail.com' 4 | echo $command 5 | eval $command 6 | -------------------------------------------------------------------------------- /examples/data/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "executable": "convert", 3 | "parameters": [ 4 | { 5 | "flag": "", 6 | "name": "pic.jpg" 7 | }, 8 | { 9 | "flag": "", 10 | "name": "pic.eps" 11 | } 12 | ], 13 | "inputs": [ 14 | { 15 | "name": "pic.jpg" 16 | } 17 | ], 18 | "outputs": [ 19 | { 20 | "type": "file", 21 | "name": "pic.eps" 22 | } 23 | ], 24 | "type": "job", 25 | "userEmail": "juanprietob@gmail.com", 26 | "executionserver" : "testserver" 27 | } -------------------------------------------------------------------------------- /examples/data/pic.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juanprietob/clusterpost/0c50e24c70724cd4e7aa877ec1afa68f7e8e729e/examples/data/pic.jpg -------------------------------------------------------------------------------- /examples/node/createNewJob.js: -------------------------------------------------------------------------------- 1 | 2 | var request = require('request'); 3 | var fs = require('fs'); 4 | var Promise = require('bluebird'); 5 | var path = require('path'); 6 | 7 | const createJob = function(job){ 8 | return new Promise(function(resolve, reject){ 9 | var options = { 10 | url : "http://localhost:8180/dataprovider", 11 | method: "POST", 12 | json: job 13 | } 14 | 15 | request(options, function(err, res, body){ 16 | if(err) reject(err); 17 | 18 | resolve(body); 19 | } 20 | }); 21 | 22 | } 23 | 24 | const uploadfile = function(params){ 25 | 26 | var filename = params.filename; 27 | var id = params.id; 28 | 29 | return new Promise(function(resolve, reject){ 30 | 31 | try{ 32 | var options = { 33 | url : "http://localhost:8180/dataprovider/" + id + "/" + path.basename(filename), 34 | method: "PUT", 35 | headers:{ 36 | "Content-Type": "application/octet-stream" 37 | } 38 | } 39 | 40 | var stream = fs.createReadStream(filename); 41 | 42 | stream.pipe(request(options, function(err, res, body){ 43 | if(err) resolve(err); 44 | 45 | resolve(body); 46 | 47 | }) 48 | ); 49 | }catch(e){ 50 | reject(e); 51 | } 52 | 53 | }); 54 | } 55 | 56 | const submitJob = function(job){ 57 | return new Promise(function(resolve, reject){ 58 | try{ 59 | var options = { 60 | url : "http://localhost:8180/executionserver/" + id, 61 | method: "POST" 62 | } 63 | 64 | request(options, function(err, res, body){ 65 | if(err) reject(err); 66 | resolve(body); 67 | }); 68 | }catch(e){ 69 | reject(e); 70 | } 71 | }); 72 | 73 | } 74 | 75 | var job = { 76 | "executable": "convert", 77 | "parameters": [ 78 | { 79 | "flag": "", 80 | "name": "pic.jpg" 81 | }, 82 | { 83 | "flag": "", 84 | "name": "pic.eps" 85 | } 86 | ], 87 | "inputs": [ 88 | { 89 | "name": "pic.jpg" 90 | } 91 | ], 92 | "outputs": [ 93 | { 94 | "type": "file", 95 | "name": "pic.eps" 96 | } 97 | ], 98 | "type": "job", 99 | "userEmail": "juanprietob@gmail.com", 100 | "executionserver" : "testserver" 101 | }; 102 | 103 | 104 | var inputs = [ 105 | "../data/pic.jpg" 106 | ]; 107 | 108 | //Create the job first 109 | createJob(job) 110 | .then(function(body){ 111 | 112 | //Upload the data 113 | 114 | var doc = body; 115 | var params = []; 116 | for(var i = 0; i < inputs.length; i++){ 117 | params.push({ 118 | filename: inputs[i], 119 | id: doc.id 120 | }); 121 | } 122 | 123 | return Promise.map(params, uploadfile, {concurrency: 1}) 124 | .then(function(allupload){ 125 | console.log("Documents uploaded", allupload); 126 | return doc; 127 | }); 128 | 129 | }) 130 | .then(function(doc){ 131 | //submit the job 132 | return submitJob(doc); 133 | }) 134 | .then(console.log) 135 | .catch(console.error); 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | -------------------------------------------------------------------------------- /examples/node/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "examplesclusterpost", 3 | "version": "1.0.0", 4 | "description": "Use examples for clusterpost", 5 | "main": "createNewJob.js", 6 | "dependencies" : { 7 | "bluebird": "^3.3.0", 8 | "request": "^2.69.0", 9 | "underscore": "^1.8.3" 10 | }, 11 | "scripts": { 12 | "test": "echo \"Error: no test specified\" && exit 1" 13 | }, 14 | "keywords": [ 15 | "examples" 16 | ], 17 | "author": "juanprietob@gmail.com", 18 | "license": "Apache-2.0" 19 | } 20 | -------------------------------------------------------------------------------- /hapitest.js: -------------------------------------------------------------------------------- 1 | const Hapi = require('hapi'); 2 | const server = new Hapi.Server(); 3 | var fs= require('fs'); 4 | var path = require('path'); 5 | 6 | 7 | server.connection({ 8 | host: 'localhost', 9 | port: 8000 10 | }); 11 | const getConfigFile = function (env, base_directory) { 12 | try { 13 | // Try to load the user's personal configuration file 14 | return require(base_directory + '/conf.my.' + env + '.json'); 15 | } catch (e) { 16 | // Else, read the default configuration file 17 | return require(base_directory + '/conf.' + env + '.json'); 18 | } 19 | }; 20 | 21 | 22 | var env = process.env.NODE_ENV; 23 | if(!env){ env = 'test' }; 24 | 25 | var conf = getConfigFile(env, "./"); 26 | console.log('conf',conf.testshinytooth.database); 27 | server.register( 28 | [require('h2o2'),{ register: require('./index'), options: conf }]//This is loading couch-provider 29 | , function (err) { 30 | 31 | if (err) { 32 | console.log('Failed to load h2o2'); 33 | } 34 | server.route({ 35 | method: 'GET', 36 | path: '/test/{docId}/{docName}', 37 | handler: function(request, reply){ 38 | // var route; 39 | console.log('handler test server', request.params.docId); 40 | var name = request.params.docName; 41 | server.methods.couchprovider.getDocument(request.params.docId) 42 | .then(function(doc){ 43 | 44 | var uri = server.methods.couchprovider.getDocumentURIAttachment(doc, name); 45 | reply.proxy(uri); 46 | }); 47 | } 48 | }); 49 | 50 | server.start(function (err) { 51 | console.log('Server started at:'+ server.info.uri); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /migrateUp.js: -------------------------------------------------------------------------------- 1 | var cuv = require('couch-update-views'); 2 | 3 | var env = process.env.NODE_ENV; 4 | 5 | if(!env) throw 'Please set NODE_ENV variable.'; 6 | 7 | 8 | const getConfigFile = function () { 9 | try { 10 | // Try to load the user's personal configuration file 11 | return require(process.cwd() + '/conf.my.' + env + '.json'); 12 | } catch (e) { 13 | // Else, read the default configuration file 14 | return require(process.cwd() + '/conf.' + env + '.json'); 15 | } 16 | } 17 | 18 | var conf = getConfigFile(); 19 | 20 | var clusterpostProvider = conf.plugins['clusterpost-provider']; 21 | var clusterjobs = clusterpostProvider.dataproviders[clusterpostProvider.default.dataprovider] 22 | var couchdb = clusterjobs.hostname + '/' + clusterjobs.database; 23 | 24 | var views = path.join('./src/clusterpost-server/', './views'); 25 | 26 | cuv.migrateUp(couchdb, views) 27 | .then(function(res){ 28 | console.log(res); 29 | process.exit(0); 30 | }) 31 | .catch(function(err){ 32 | console.log(error); 33 | process.exit(1); 34 | }); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "clusterpost", 3 | "version": "1.0.1", 4 | "description": "Suite to run a server, post job documents and run tasks in remote computing grids", 5 | "main": "index.js", 6 | "directories": { 7 | "doc": "doc", 8 | "example": "examples", 9 | "test": "test" 10 | }, 11 | "scripts": { 12 | "test": "echo \"Error: no test specified\" && exit 1", 13 | "start": "NODE_ENV=production node index.js" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/juanprietob/clusterpost.git" 18 | }, 19 | "keywords": [ 20 | "Cluster", 21 | "computing", 22 | "REST", 23 | "tasks", 24 | "execution" 25 | ], 26 | "author": "juanprietob@gmail.com", 27 | "license": "Apache-2.0", 28 | "bugs": { 29 | "url": "https://github.com/juanprietob/clusterpost/issues" 30 | }, 31 | "homepage": "https://github.com/juanprietob/clusterpost#readme", 32 | "dependencies": { 33 | "clusterpost-server": "file:src/clusterpost-server", 34 | "couch-update-views": "file:src/couch-update-views", 35 | "joi": "^10.6.0" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /setUpDevelopment.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | npm link src/clusterpost-auth 4 | npm link src/clusterpost-fs 5 | npm link src/clusterpost-lib 6 | npm link src/clusterpost-list 7 | npm link src/clusterpost-model 8 | npm link src/clusterpost-provider 9 | npm link src/clusterpost-static 10 | npm link src/couch-provider 11 | npm link src/couch-update-views 12 | npm link src/hapi-jwt-couch 13 | npm link src/hapi-jwt-couch-lib 14 | 15 | ln -s src/clusterpost-server/index.js index.js -------------------------------------------------------------------------------- /src/clusterpost-app/README.md: -------------------------------------------------------------------------------- 1 | # clusterpost-'app' 2 | 3 | Retrieve your running tasks from the clusterpost-server directly to your desktop. 4 | 5 | ## Installation 6 | 7 | ---- 8 | npm install clusterpost-app 9 | ---- 10 | 11 | ## Create script 12 | 13 | Create a script named `clusterpostapp.js` with the following content. 14 | 15 | ---- 16 | require(clusterpost-app); 17 | ---- 18 | 19 | ## Running the script 20 | 21 | ---- 22 | `node clusterpostapp.js get` 23 | ---- 24 | or 25 | ---- 26 | `node clusterpostapp.js post` 27 | ---- 28 | 29 | 30 | ### get: 31 | 32 | ---- 33 | Help: Download tasks from the server. 34 | Optional parameters: 35 | --dir Output directory, default: ./out 36 | --status one of [DONE, RUN, FAIL, EXIT, UPLOADING, CREATE], if this flag is provided, the job information will only be printed. By default, the behavior is status 'DONE' and download the results. 37 | --delete, default false, when downloading jobs with status 'DONE', the jobs will be deleted upon completion 38 | --j job id, default: all ids 39 | --executable executable, default: all executables 40 | ---- 41 | 42 | ### post: 43 | 44 | --- 45 | Help: Submit tasks to the server. Parses a command line, uploads the data and runs the task. 46 | 47 | Optional parameters: 48 | --parse_cli 'command as you would run it locally in your computer. It will only print the job' 49 | --parse_cli_submit 'Parses cli and submits the task' 50 | --executionserver 'name of computing grid, uses the first one by default' 51 | --- 52 | 53 | 54 | -------------------------------------------------------------------------------- /src/clusterpost-app/index.js: -------------------------------------------------------------------------------- 1 | var argv = require('minimist')(process.argv.slice(2)); 2 | 3 | const help = function(){ 4 | console.error("Help: Helper app for cli operations for clusterpost"); 5 | console.error("Options:"); 6 | console.error("get"); 7 | console.error("post"); 8 | console.error("\nExample:"); 9 | console.error("node index.js post --help"); 10 | } 11 | 12 | if(argv._[0] == 'get'){ 13 | require('./get'); 14 | }else if(argv._[0] == 'post'){ 15 | require('./post'); 16 | }else{ 17 | help(); 18 | process.exit(1); 19 | } -------------------------------------------------------------------------------- /src/clusterpost-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "clusterpost-app", 3 | "version": "1.3.1", 4 | "description": "Performs tasks in your desktop. Retrieve finished jobs. ", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "lab -v 6000 test.js" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+ssh://git@github.com/juanprietob/clusterpost.git" 12 | }, 13 | "keywords": [ 14 | "cluster", 15 | "job", 16 | "scheduler" 17 | ], 18 | "author": "juanprietob@gmail.com", 19 | "license": "Apache-2.0", 20 | "bugs": { 21 | "url": "https://github.com/juanprietob/clusterpost/issues" 22 | }, 23 | "homepage": "https://github.com/juanprietob/clusterpost#readme", 24 | "dependencies": { 25 | "bluebird": "^3.4.7", 26 | "clusterpost-lib": "^2.4.0", 27 | "minimist": "^1.2.0", 28 | "underscore": "^1.9.1" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/clusterpost-app/post.js: -------------------------------------------------------------------------------- 1 | var clusterpost = require("clusterpost-lib"); 2 | var path = require('path'); 3 | var Promise = require('bluebird'); 4 | var argv = require('minimist')(process.argv.slice(2)); 5 | const _ = require('underscore'); 6 | const os = require('os'); 7 | 8 | 9 | var agentoptions = { 10 | rejectUnauthorized: false 11 | } 12 | 13 | clusterpost.setAgentOptions(agentoptions); 14 | 15 | const help = function(){ 16 | console.error("Help: Submit tasks to the server. Parses a command line, uploads the data and runs the task."); 17 | console.error("\nOptional parameters:"); 18 | console.error("--parse_cli 'command as you would run it locally in your computer. It will only print the job'"); 19 | console.error("--parse_cli_submit 'Parses cli and submits the task'"); 20 | console.error("--executionserver 'name of computing grid, uses the first one by default'"); 21 | } 22 | 23 | if(argv["h"] || argv["help"]){ 24 | help(); 25 | process.exit(1); 26 | } 27 | 28 | var config_codename = 'clusterpost'; 29 | if(argv["config_codename"]){ 30 | config_codename = argv["config_codename"]; 31 | } 32 | 33 | clusterpost.start(path.join(os.homedir(), '.' + config_codename + '.json')) 34 | .then(function(){ 35 | if(argv["parse_cli"]){ 36 | var args = process.argv.slice(process.argv.indexOf('--parse_cli') + 1); 37 | return clusterpost.parseCLI(args); 38 | }else if(argv["parse_cli_submit"]){ 39 | var args = process.argv.slice(process.argv.indexOf('--parse_cli_submit') + 1); 40 | return clusterpost.parseCLIAndSubmit(args, argv["executionserver"]); 41 | } 42 | }) 43 | .then(function(res){ 44 | console.log(JSON.stringify(res, null, 4)); 45 | }) 46 | .catch(console.error) 47 | -------------------------------------------------------------------------------- /src/clusterpost-app/test.js: -------------------------------------------------------------------------------- 1 | 2 | var request = require('request'); 3 | var fs = require('fs'); 4 | var Promise = require('bluebird'); 5 | var path = require('path'); 6 | 7 | const Joi = require('@hapi/joi'); 8 | const Lab = require('lab'); 9 | const lab = exports.lab = Lab.script(); 10 | const clustermodel = require("clusterpost-model"); 11 | const clusterpost = require("clusterpost-lib"); 12 | 13 | const getConfigFile = function (env, base_directory) { 14 | try { 15 | // Try to load the user's personal configuration file 16 | return require(base_directory + '/conf.my.' + env + '.json'); 17 | } catch (e) { 18 | // Else, read the default configuration file 19 | return require(base_directory + '/conf.' + env + '.json'); 20 | } 21 | }; 22 | 23 | 24 | var env = process.env.NODE_ENV; 25 | 26 | if(!env) throw "Please set NODE_ENV variable."; 27 | 28 | var conf = getConfigFile(env, "./"); 29 | 30 | var agentOptions = {}; 31 | 32 | if(conf.tls && conf.tls.cert){ 33 | agentOptions.ca = fs.readFileSync(conf.tls.cert); 34 | } 35 | 36 | var getClusterPostServer = function(){ 37 | return conf.uri 38 | } 39 | 40 | clusterpost.setClusterPostServer(conf.uri); 41 | 42 | var joiokres = Joi.object().keys({ 43 | ok: Joi.boolean().valid(true), 44 | id: Joi.string(), 45 | rev: Joi.string() 46 | }); 47 | 48 | lab.experiment("Test clusterpost auth jwt", function(){ 49 | 50 | var user = { 51 | email: "algiedi85@gmail.com", 52 | name: "Alpha Capricorni", 53 | password: "Some808Password!" 54 | } 55 | 56 | lab.test('returns true when new user is created.', function(){ 57 | 58 | return clusterpost.createUser(user) 59 | .then(function(res){ 60 | Joi.assert(res.token, Joi.string().required()); 61 | }); 62 | 63 | }); 64 | 65 | lab.test('returns true if same user fails to be created.', function(){ 66 | return clusterpost.createUser(user) 67 | .then(function(res){ 68 | Joi.assert(res.token, Joi.object().keys({ 69 | statusCode: Joi.number().valid(409), 70 | error: Joi.string(), 71 | message: Joi.string() 72 | })); 73 | }); 74 | }); 75 | 76 | var token = ""; 77 | 78 | lab.test('returns true when user is login.', function(){ 79 | 80 | var user = { 81 | email: "algiedi85@gmail.com", 82 | password: "Some808Password!" 83 | } 84 | 85 | return clusterpost.userLogin(user) 86 | .then(function(res){ 87 | Joi.assert(res.token, Joi.string().required()) 88 | token = "Bearer " + res.token; 89 | }); 90 | 91 | }); 92 | 93 | lab.test('returns true when unauthorized user access api.', function(){ 94 | 95 | return clusterpost.getExecutionServers() 96 | .then(function(res){ 97 | Joi.assert(res, Joi.object().keys({ 98 | statusCode: Joi.number().valid(403), 99 | error: Joi.string(), 100 | message: Joi.string() 101 | })); 102 | }); 103 | 104 | }); 105 | 106 | lab.test('returns true when unauthorized user access api.', function(){ 107 | 108 | return clusterpost.getUser() 109 | .then(function(res){ 110 | 111 | Joi.assert(res, Joi.object().keys({ 112 | _id: Joi.string(), 113 | _rev: Joi.string(), 114 | name: Joi.string(), 115 | email: Joi.string().email(), 116 | type: Joi.string(), 117 | scope: Joi.array().items(Joi.string()) 118 | })); 119 | }); 120 | 121 | }); 122 | 123 | lab.test('returns true when valid user deletes itself.', function(){ 124 | 125 | return clusterpost.deleteUser() 126 | .then(function(res){ 127 | Joi.assert(res, joiokres); 128 | }); 129 | 130 | }); 131 | 132 | 133 | }); -------------------------------------------------------------------------------- /src/clusterpost-auth/index.js: -------------------------------------------------------------------------------- 1 | var Boom = require('boom'); 2 | var Promise = require('bluebird'); 3 | 4 | exports.register = function (server, conf, next) { 5 | 6 | const validate = function(req, decodedToken, callback){ 7 | var exs = server.methods.executionserver.getExecutionServer(decodedToken.executionserver); 8 | if(exs){ 9 | exs.scope = ['executionserver']; 10 | callback(undefined, true, exs); 11 | }else{ 12 | callback(Boom.unauthorized(exs)); 13 | } 14 | } 15 | 16 | conf.validate = validate; 17 | 18 | server.register({ 19 | register: require('hapi-jwt-couch'), 20 | options: conf 21 | }, function(err){ 22 | 23 | if(err){ 24 | throw err; 25 | } 26 | 27 | server.method({ 28 | name: 'clusterpostauth.verify', 29 | method: function(token){ 30 | return Promise.resolve(server.methods.jwtauth.verify(token)); 31 | }, 32 | options: {} 33 | }); 34 | 35 | }); 36 | 37 | return next(); 38 | 39 | }; 40 | 41 | exports.register.attributes = { 42 | pkg: require('./package.json') 43 | }; -------------------------------------------------------------------------------- /src/clusterpost-auth/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "clusterpost-auth", 3 | "version": "2.1.0", 4 | "description": "Login and authenticate users for the clusterpost application", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [ 10 | "cluster", 11 | "computing", 12 | "login" 13 | ], 14 | "author": "juanprietob@gmail.com", 15 | "license": "Apache-2.0", 16 | "dependencies": { 17 | "bluebird": "^3.5.4", 18 | "boom": "^3.1.2", 19 | "hapi-jwt-couch": "^2.0.1" 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "git+ssh://git@github.com/juanprietob/clusterpost.git" 24 | }, 25 | "bugs": { 26 | "url": "https://github.com/juanprietob/clusterpost/issues" 27 | }, 28 | "homepage": "https://github.com/juanprietob/clusterpost#readme" 29 | } 30 | -------------------------------------------------------------------------------- /src/clusterpost-execution/conf.json.in: -------------------------------------------------------------------------------- 1 | { 2 | "uri": "https://localhost:8180", 3 | "engine" : "engine_unix", 4 | "storagedir" : "./clusterpost_storage" 5 | } -------------------------------------------------------------------------------- /src/clusterpost-execution/engine_lsf.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = function (conf) { 3 | 4 | var fs = require('fs'); 5 | var Promise = require('bluebird'); 6 | var _ = require("underscore"); 7 | var spawn = require('child_process').spawn; 8 | var path = require('path'); 9 | var Joi = require('@hapi/joi'); 10 | 11 | var executionmethods = require('./executionserver.methods')(conf); 12 | var clustermodel = require('clusterpost-model'); 13 | 14 | var handler = {}; 15 | 16 | handler.submitJob = function(doc, cwd){ 17 | 18 | Joi.assert(doc, clustermodel.job); 19 | 20 | return new Promise(function(resolve, reject){ 21 | var command = 'bsub'; 22 | var parameters = doc.parameters; 23 | 24 | var params = []; 25 | 26 | if(doc.jobparameters){ 27 | for(var i = 0; i < doc.jobparameters.length; i++){ 28 | var param = doc.jobparameters[i]; 29 | if(_.isObject(param)){ 30 | if(param.flag){ 31 | params.push(param.flag); 32 | } 33 | if(param.name){ 34 | params.push(param.name); 35 | } 36 | }else{ 37 | params.push(param); 38 | } 39 | } 40 | } 41 | 42 | params.push("-cwd"); 43 | params.push(cwd); 44 | params.push("-e"); 45 | params.push(path.join(cwd, "stderr.err")); 46 | params.push("-o"); 47 | params.push(path.join(cwd, "stdout.out")); 48 | params.push("-u"); 49 | params.push(doc.userEmail); 50 | params.push("-J"); 51 | params.push(doc.userEmail); 52 | 53 | 54 | params.push(doc.executable); 55 | if(parameters){ 56 | for(var i = 0; i < parameters.length; i++){ 57 | var param = parameters[i]; 58 | if(_.isObject(param)){ 59 | if(param.flag){ 60 | params.push(param.flag); 61 | } 62 | if(param.name){ 63 | params.push(param.name); 64 | } 65 | }else{ 66 | params.push(param); 67 | } 68 | } 69 | } 70 | 71 | try{ 72 | const runcommand = spawn(command, params); 73 | 74 | var allerror = ""; 75 | runcommand.stderr.on('data', function(data){ 76 | allerror += data; 77 | }); 78 | 79 | var alldata = ""; 80 | runcommand.stdout.on('data', function(data){ 81 | alldata += data; 82 | }); 83 | 84 | //"sample: Job <898104> is submitted to default queue " 85 | runcommand.on('close', function(code){ 86 | if(code){ 87 | resolve({ 88 | status: 'FAIL', 89 | error: allerror + alldata 90 | }); 91 | }else{ 92 | var ind = alldata.indexOf('<') + 1; 93 | var jobid = alldata.substr(ind, alldata.indexOf('>') - ind); 94 | 95 | resolve({ 96 | jobid : Number.parseInt(jobid), 97 | status: 'RUN' 98 | }); 99 | } 100 | }); 101 | 102 | }catch(e){ 103 | reject({ 104 | status: "FAIL", 105 | error: e 106 | }); 107 | } 108 | 109 | }); 110 | 111 | } 112 | 113 | handler.getJobStatus = function(doc){ 114 | 115 | Joi.assert(doc.jobstatus, clustermodel.jobstatus); 116 | 117 | return new Promise(function(resolve, reject){ 118 | 119 | try{ 120 | 121 | var jobid = doc.jobstatus.jobid; 122 | if(!jobid && doc.jobstatus.stat){ 123 | try{ 124 | jobid = doc.jobstatus.stat.split("\n")[1].split(" ")[0]; 125 | }catch(e){ 126 | console.error(e); 127 | } 128 | } 129 | var params = ["-J", doc.userEmail, jobid]; 130 | 131 | const ps = spawn('bjobs', params); 132 | 133 | var allerror = ""; 134 | ps.stderr.on('data', function(data){ 135 | allerror += data; 136 | }); 137 | 138 | var alldata = ""; 139 | ps.stdout.on('data', function(data){ 140 | alldata += data; 141 | }); 142 | 143 | //"sample success: 898104 jprieto DONE day killdevil-l donor_pool2 *gmail.com Feb 14 11:16" 144 | //"sample fail: Job <8981> is not found" 145 | 146 | ps.on('close', function(code){ 147 | 148 | if(alldata && alldata.indexOf('DONE') !== -1 || allerror && allerror.indexOf('is not found') !== -1){ 149 | resolve({ 150 | status: 'DONE', 151 | stat: alldata 152 | }); 153 | }else if(alldata && alldata.indexOf('EXIT') !== -1){ 154 | resolve({ 155 | status: 'EXIT', 156 | stat: alldata 157 | }); 158 | }else if(code || allerror){ 159 | resolve({ 160 | status: 'FAIL', 161 | error: allerror 162 | }); 163 | }else{ 164 | resolve({ 165 | jobid: jobid, 166 | status: 'RUN', 167 | stat: alldata 168 | }); 169 | } 170 | 171 | }); 172 | 173 | }catch(e){ 174 | reject(e); 175 | } 176 | 177 | }); 178 | } 179 | 180 | handler.killJob = function(doc){ 181 | 182 | Joi.assert(doc.jobstatus, clustermodel.jobstatus); 183 | 184 | return new Promise(function(resolve, reject){ 185 | 186 | try{ 187 | 188 | var jobid = doc.jobstatus.jobid; 189 | var params = ["-J", doc.userEmail, jobid]; 190 | 191 | const kill = spawn('bkill', params); 192 | 193 | var allerror = ""; 194 | kill.stderr.on('data', function(data){ 195 | allerror += data; 196 | }); 197 | 198 | var alldata = ""; 199 | kill.stdout.on('data', function(data){ 200 | alldata += data; 201 | }); 202 | 203 | kill.on('close', function(code){ 204 | resolve({ 205 | status: 'EXIT', 206 | stat: allerror + alldata 207 | }); 208 | }); 209 | 210 | 211 | }catch(e){ 212 | reject(e); 213 | } 214 | 215 | }); 216 | 217 | } 218 | 219 | return handler; 220 | } 221 | -------------------------------------------------------------------------------- /src/clusterpost-execution/engine_unix.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = function (conf) { 3 | 4 | var fs = require('fs'); 5 | var Promise = require('bluebird'); 6 | var _ = require("underscore"); 7 | var spawn = require('child_process').spawn; 8 | var path = require('path'); 9 | var Joi = require('@hapi/joi'); 10 | 11 | var executionmethods = require('./executionserver.methods')(conf); 12 | var clustermodel = require('clusterpost-model'); 13 | 14 | var handler = {}; 15 | 16 | handler.submitJob = function(doc, cwd){ 17 | 18 | Joi.assert(doc, clustermodel.job); 19 | 20 | return new Promise(function(resolve, reject){ 21 | var command = doc.executable; 22 | var parameters = _.flatten(_.map(doc.parameters, (param)=>{ 23 | if(_.isObject(param)){ 24 | return _.compact([param.flag, param.name]); 25 | }else{ 26 | return param; 27 | } 28 | })) 29 | 30 | try{ 31 | 32 | fs.writeFileSync(path.join(cwd, "stdout.out"), command + " " + parameters.join(" ")); 33 | 34 | var out = fs.openSync(path.join(cwd, "stdout.out"), 'a'); 35 | var err = fs.openSync(path.join(cwd, "stderr.err"), 'a'); 36 | 37 | var detached = true; 38 | 39 | if(conf.detached !== undefined){ 40 | detached = conf.detached; 41 | } 42 | 43 | const runcommand = spawn(command, parameters, { 44 | cwd: cwd, 45 | stdio: [ 'ignore', out, err ] 46 | }); 47 | 48 | runcommand.on('error', function (err) { 49 | reject({ 50 | status: "FAIL", 51 | error: err 52 | }); 53 | }); 54 | 55 | if(detached){ 56 | runcommand.unref(); 57 | 58 | if(runcommand.pid){ 59 | resolve({ 60 | jobid : runcommand.pid, 61 | status: "RUN" 62 | }); 63 | }else{ 64 | reject({ 65 | status: "FAIL", 66 | error: "nopid" 67 | }); 68 | } 69 | }else{ 70 | 71 | runcommand.on('close', function(code){ 72 | if(code){ 73 | reject({ 74 | status: "FAIL" 75 | }); 76 | }else{ 77 | resolve({ 78 | status: 'UPLOADING' 79 | }); 80 | } 81 | }); 82 | } 83 | }catch(e){ 84 | reject({ 85 | status: "FAIL", 86 | error: e 87 | }); 88 | } 89 | 90 | }); 91 | 92 | } 93 | 94 | handler.getJobStatus = function(doc){ 95 | 96 | return new Promise(function(resolve, reject){ 97 | 98 | try{ 99 | 100 | Joi.assert(doc.jobstatus, clustermodel.jobstatus); 101 | Joi.assert(doc.jobstatus.jobid, Joi.number().required(), "Please execute the job first."); 102 | 103 | var jobid = doc.jobstatus.jobid; 104 | var params = [jobid]; 105 | 106 | const ps = spawn('ps', params); 107 | 108 | var allerror = ""; 109 | ps.stderr.on('data', function(data){ 110 | allerror += data; 111 | }); 112 | 113 | var alldata = ""; 114 | ps.stdout.on('data', function(data){ 115 | alldata += data; 116 | }); 117 | 118 | ps.on('close', function(code){ 119 | var lines = alldata.split('\n'); 120 | if(lines.length > 1){ 121 | if(code && lines[1] === ''){ 122 | resolve({ 123 | status: 'DONE' 124 | }); 125 | }else{ 126 | resolve({ 127 | jobid: jobid, 128 | status: 'RUN', 129 | stat: lines[1] 130 | }); 131 | } 132 | }else{ 133 | resolve({ 134 | status: 'FAIL', 135 | error: allerror 136 | }); 137 | } 138 | 139 | }); 140 | 141 | }catch(e){ 142 | if(conf.run_only){ 143 | resolve({ 144 | status: 'DONE' 145 | }); 146 | }else{ 147 | reject(e); 148 | } 149 | } 150 | 151 | }); 152 | } 153 | 154 | handler.killJob = function(doc){ 155 | 156 | Joi.assert(doc.jobstatus, clustermodel.jobstatus); 157 | 158 | return new Promise(function(resolve, reject){ 159 | 160 | try{ 161 | 162 | var jobid = doc.jobstatus.jobid; 163 | var params = ['-9', jobid]; 164 | 165 | const kill = spawn('kill', params); 166 | 167 | var allerror = ""; 168 | kill.stderr.on('data', function(data){ 169 | allerror += data; 170 | }); 171 | 172 | var alldata = ""; 173 | kill.stdout.on('data', function(data){ 174 | alldata += data; 175 | }); 176 | 177 | kill.on('close', function(code){ 178 | var stat = alldata; 179 | if(code){ 180 | stat+= allerror; 181 | } 182 | resolve({ 183 | status: 'EXIT', 184 | stat: stat 185 | }); 186 | }); 187 | 188 | 189 | }catch(e){ 190 | reject(e); 191 | } 192 | 193 | }); 194 | 195 | } 196 | 197 | return handler; 198 | } -------------------------------------------------------------------------------- /src/clusterpost-execution/index.js.in: -------------------------------------------------------------------------------- 1 | var es = require("clusterpost-execution"); -------------------------------------------------------------------------------- /src/clusterpost-execution/jobdelete.js: -------------------------------------------------------------------------------- 1 | 2 | var fs = require('fs'); 3 | var path = require('path'); 4 | 5 | module.exports = function(jobid, conf, doc){ 6 | 7 | var executionmethods = require('./executionserver.methods')(conf); 8 | 9 | var promdoc; 10 | 11 | if(doc){ 12 | promdoc = Promise.resolve(doc); 13 | }else{ 14 | promdoc = executionmethods.getDocument(jobid) 15 | .catch(function(err){ 16 | console.log("Job deleted already in the db.", jobid); 17 | return null; 18 | }); 19 | } 20 | 21 | return promdoc 22 | .then(function(doc){ 23 | if(doc){ 24 | return require(path.join(__dirname, "jobkill"))(doc, conf); 25 | } 26 | }) 27 | .then(function(){ 28 | var cwd = path.join(conf.storagedir, jobid); 29 | 30 | try{ 31 | executionmethods.deleteFolderRecursive(cwd); 32 | var compressed = cwd + ".tar.gz"; 33 | var compressedstat; 34 | try{ 35 | compressedstat = fs.statSync(compressed); 36 | }catch(e){ 37 | //does not exist 38 | compressedstat = undefined; 39 | } 40 | if(compressedstat){ 41 | fs.unlinkSync(compressed); 42 | } 43 | }catch(e){ 44 | return { 45 | error: e 46 | } 47 | } 48 | 49 | 50 | return { 51 | status: "Folder deleted " + jobid 52 | } 53 | }); 54 | 55 | 56 | } -------------------------------------------------------------------------------- /src/clusterpost-execution/jobkill.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = function(doc, conf){ 3 | 4 | var executionmethods = require('./executionserver.methods')(conf); 5 | var clusterengine = require("./" + conf.engine)(conf); 6 | 7 | if(doc.jobstatus){ 8 | return clusterengine.killJob(doc) 9 | .then(function(status){ 10 | if(doc.jobstatus.status === "RUN" || doc.jobstatus.status === "KILL"){ 11 | doc.jobstatus.status = status.status; 12 | return executionmethods.uploadDocumentDataProvider(doc) 13 | .then(function(){ 14 | return status; 15 | }); 16 | } 17 | }); 18 | 19 | }else{ 20 | return { 21 | status: 'The job is not running' 22 | }; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/clusterpost-execution/jobstatus.js: -------------------------------------------------------------------------------- 1 | 2 | var _ = require('underscore'); 3 | 4 | module.exports = function(doc, conf){ 5 | 6 | var executionmethods = require('./executionserver.methods')(conf); 7 | var clusterengine = require("./" + conf.engine)(conf); 8 | 9 | const allUpload = function(allupload){ 10 | return executionmethods.getDocument(doc._id) 11 | .then(function(docupdated){ 12 | 13 | docupdated.jobstatus.uploadstatus = allupload; 14 | var alluploadstatus = true; 15 | for(var i = 0; i < allupload.length; i++){ 16 | if(!(allupload[i].ok || allupload[i].statusCode == 409)){ 17 | alluploadstatus = false; 18 | } 19 | } 20 | if(alluploadstatus){ 21 | docupdated.jobstatus.status = "DONE"; 22 | }else{ 23 | docupdated.jobstatus.status = "FAIL"; 24 | } 25 | docupdated.timestampend = new Date(); 26 | return executionmethods.uploadDocumentDataProvider(docupdated); 27 | }); 28 | } 29 | if(conf.engine == "engine_awsecs"){ 30 | if(doc){ 31 | return clusterengine.getJobStatus(doc) 32 | .then(function(status){ 33 | if(status.status === 'FAIL'){ 34 | doc.jobstatus = _.extend(doc.jobstatus, status); 35 | return executionmethods.uploadDocumentDataProvider(doc) 36 | .then(function(){ 37 | return status; 38 | }); 39 | } 40 | return status; 41 | }) 42 | }else{ 43 | return clusterengine.checkGPUNodes(); 44 | } 45 | }else{ 46 | if(doc){ 47 | if(doc.jobstatus.status === "UPLOADING"){ 48 | return executionmethods.checkAllDocumentOutputs(doc) 49 | .then(allUpload); 50 | }else{ 51 | return clusterengine.getJobStatus(doc) 52 | .then(function(status){ 53 | if(status.status === 'DONE' || status.status === 'EXIT'){ 54 | doc.jobstatus.status = "UPLOADING"; 55 | //Set the new status 56 | return executionmethods.uploadDocumentDataProvider(doc) 57 | .then(function(res){ 58 | //update revision 59 | doc._rev = res.rev; 60 | return doc; 61 | }) 62 | .then(function(doc){ 63 | //upload all outputs 64 | return executionmethods.setAllDocumentOutputs(doc) 65 | }) 66 | .then(allUpload); 67 | }else{ 68 | doc.jobstatus = _.extend(doc.jobstatus, status); 69 | return executionmethods.uploadDocumentDataProvider(doc) 70 | .then(function(){ 71 | return status; 72 | }); 73 | } 74 | }); 75 | } 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /src/clusterpost-execution/jobsubmit.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | var _ = require('underscore'); 4 | 5 | module.exports = function(doc, force, conf){ 6 | 7 | var executionmethods = require('./executionserver.methods')(conf); 8 | var clusterengine = require("./" + conf.engine)(conf); 9 | 10 | var cwd = executionmethods.createDirectoryCWD(doc); 11 | executionmethods.createOutputDirs(doc); 12 | 13 | const submitJob = function(subdoc){ 14 | return executionmethods.getAllDocumentInputs(subdoc, cwd) 15 | .bind({}) 16 | .then(function(downloadstatus){ 17 | this.downloadstatus = downloadstatus; 18 | var isago = true; 19 | for(var i = 0; i < downloadstatus.length; i++){ 20 | if(!downloadstatus[i].status){ 21 | isago = false; 22 | } 23 | } 24 | if(isago){ 25 | subdoc.timestampstart = new Date(); 26 | return clusterengine.submitJob(subdoc, cwd) 27 | .catch(function(e){ 28 | return e; 29 | }); 30 | } 31 | return { 32 | status: "DOWNLOADING", 33 | error: 'Unable to retrieve all the input data' 34 | } 35 | 36 | }) 37 | .then(function(jobstatus){ 38 | if(conf.run_only){ 39 | subdoc.jobstatus.status = jobstatus.status 40 | return executionmethods.uploadDocumentDataProvider(subdoc); 41 | }else{ 42 | subdoc.jobstatus = jobstatus; 43 | _.extend(subdoc.jobstatus, this); 44 | return executionmethods.uploadDocumentDataProvider(subdoc); 45 | } 46 | }); 47 | 48 | } 49 | 50 | var sjprom; 51 | 52 | if (doc.jobstatus.status === 'CREATE' || doc.jobstatus.status === 'QUEUE' || doc.jobstatus.status === 'DOWNLOADING' || doc.jobstatus.status === 'FAIL'){ 53 | sjprom = submitJob(doc) 54 | .catch(function(e){ 55 | console.error(e) 56 | return e 57 | }); 58 | } else { 59 | sjprom = clusterengine.getJobStatus(doc) 60 | .then(function(status){ 61 | if(status.status !== 'RUN' && force){ 62 | if(doc.jobstatus.uploadstatus){ 63 | delete doc.jobstatus.uploadstatus; 64 | } 65 | return submitJob(doc); 66 | } 67 | doc.jobstatus = status; 68 | if(!conf.run_only){ 69 | return executionmethods.uploadDocumentDataProvider(doc); 70 | } 71 | else{ 72 | console.log(status); 73 | } 74 | }) 75 | .catch(function(e){ 76 | console.error(e); 77 | return e; 78 | }); 79 | } 80 | 81 | return sjprom; 82 | } 83 | -------------------------------------------------------------------------------- /src/clusterpost-execution/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "clusterpost-execution", 3 | "version": "4.5.7", 4 | "description": "Download data from the data provider to the local disk. Submit tasks to the job manager. Check if the task has finished and upload the results back to the data provider", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "postinstall": "node postinstall.js" 9 | }, 10 | "bin": { 11 | "cpex": "./index.js" 12 | }, 13 | "keywords": [ 14 | "Cluster", 15 | "submit", 16 | "job", 17 | "data transfer" 18 | ], 19 | "author": "juanprietob@gmail.com", 20 | "license": "Apache-2.0", 21 | "dependencies": { 22 | "@aws-sdk/client-auto-scaling": "^3.18.0", 23 | "@aws-sdk/client-ecs": "^3.8.0", 24 | "@hapi/joi": "^15.0.3", 25 | "bluebird": "^3.3.0", 26 | "chalk": "^4.1.0", 27 | "clusterpost-lib": "^2.9.1", 28 | "clusterpost-model": "^1.16.1", 29 | "minimist": "^1.2.5", 30 | "node-crontab": "0.0.8", 31 | "node-targz": "0.0.3", 32 | "underscore": "^1.8.3", 33 | "xml2js": "^0.4.17" 34 | }, 35 | "repository": { 36 | "type": "git", 37 | "url": "git+ssh://git@github.com/juanprietob/clusterpost.git" 38 | }, 39 | "bugs": { 40 | "url": "https://github.com/juanprietob/clusterpost/issues" 41 | }, 42 | "homepage": "https://github.com/juanprietob/clusterpost#readme" 43 | } 44 | -------------------------------------------------------------------------------- /src/clusterpost-execution/postinstall.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const os = require('os'); 4 | const chalk = require('chalk'); 5 | 6 | var cwd = process.cwd(); 7 | var defaultconfig = path.join(__dirname, "conf.json.in"); 8 | 9 | var user_conf_dir = path.join(os.homedir(), '.clusterpost-execution'); 10 | var user_conf = path.join(user_conf_dir, 'conf.json'); 11 | 12 | try{ 13 | if(!fs.existsSync(user_conf_dir)){ 14 | fs.mkdirSync(user_conf_dir, {recursive: true}); 15 | } 16 | 17 | try{ 18 | var stats = fs.statSync(user_conf); 19 | console.log(chalk.green("Your configuration file is at"), chalk.green(user_conf)); 20 | }catch(e){ 21 | console.log(chalk.green("Generating default configuration file at"), chalk.green(user_conf)); 22 | console.log(chalk.green("Please edit this file with your configuration parameters.")); 23 | console.log(chalk.green("The token.json should be saved into this directory as well.")); 24 | fs.writeFileSync(user_conf, fs.readFileSync(defaultconfig)); 25 | } 26 | }catch(e){ 27 | console.error(chalk.red("Installation was not able to write config file at"), chalk.green(user_conf)) 28 | } 29 | -------------------------------------------------------------------------------- /src/clusterpost-execution/request_token.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const clusterpost = require("clusterpost-lib"); 4 | const clustermodel = require("clusterpost-model"); 5 | const Joi = require('joi'); 6 | const path = require('path'); 7 | const argv = require('minimist')(process.argv.slice(2)); 8 | const fs = require('fs'); 9 | const chalk = require('chalk'); 10 | 11 | var agentoptions = { 12 | rejectUnauthorized: false 13 | } 14 | 15 | clusterpost.setAgentOptions(agentoptions); 16 | 17 | var help = function(){ 18 | console.log(chalk.cyan("help: Request token from server")); 19 | console.log(chalk.green("Options:")); 20 | console.log(chalk.green("--es ")); 21 | console.log(chalk.green("--out ")); 22 | } 23 | 24 | 25 | var executionserver = argv["es"]; 26 | var out = argv["out"]; 27 | 28 | if (!out){ 29 | out = "~/.clusterpost-execution/token.json" 30 | } 31 | 32 | out_dir = path.dirname(out) 33 | 34 | if(!fs.existsSync(out_dir)){ 35 | fs.mkdirSync(out_dir); 36 | } 37 | 38 | if(!executionserver || argv["h"] || argv["help"]){ 39 | help(); 40 | process.exit(); 41 | } 42 | 43 | clusterpost.start() 44 | .then(function(){ 45 | return clusterpost.getExecutionServerToken({executionserver}) 46 | .then(function(token){ 47 | token = token[0] 48 | Joi.assert(token, clustermodel.executionservertoken); 49 | console.log("Writing token to:" out); 50 | fs.writeFileSync(out, JSON.stringify(token)); 51 | }) 52 | }) 53 | .catch(console.error) -------------------------------------------------------------------------------- /src/clusterpost-fs/.gitignore: -------------------------------------------------------------------------------- 1 | CLIMB 2 | CLIMB-registration 3 | -------------------------------------------------------------------------------- /src/clusterpost-fs/index.js: -------------------------------------------------------------------------------- 1 | exports.plugin = {}; 2 | exports.plugin.register = async function (server, conf) { 3 | 4 | var _ = require('underscore'); 5 | var fs = require('fs'); 6 | var path = require('path'); 7 | var Boom = require('boom'); 8 | 9 | _.each(conf.links, function(dir, key){ 10 | 11 | var currentpath = path.join(__dirname, key); 12 | 13 | try{ 14 | fs.statSync(currentpath); 15 | }catch(e){ 16 | try{ 17 | fs.linkSync(dir, currentpath); 18 | }catch(e){ 19 | console.error(e); 20 | } 21 | } 22 | 23 | }); 24 | 25 | server.route({ 26 | path: '/dataprovider-fs/{path*}', 27 | method: 'GET', 28 | config: { 29 | auth: { 30 | strategy: 'token', 31 | scope: ['clusterpost', 'executionserver'] 32 | }, 33 | handler: { 34 | directory: { path: __dirname, listing: false, index: true } 35 | }, 36 | description: 'This route serves static folder content for clusterpost. Everything inside this folder will be directly accessible under this route.' 37 | } 38 | }); 39 | 40 | }; 41 | 42 | exports.plugin.pkg = require('./package.json'); 43 | -------------------------------------------------------------------------------- /src/clusterpost-fs/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "clusterpost-fs", 3 | "version": "1.0.3", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "boom": { 8 | "version": "4.3.1", 9 | "resolved": "https://registry.npmjs.org/boom/-/boom-4.3.1.tgz", 10 | "integrity": "sha1-T4owBctKfjiJ90kDD9JbluAdLjE=", 11 | "requires": { 12 | "hoek": "4.x.x" 13 | } 14 | }, 15 | "hoek": { 16 | "version": "4.2.1", 17 | "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.1.tgz", 18 | "integrity": "sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA==" 19 | }, 20 | "underscore": { 21 | "version": "1.8.3", 22 | "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz", 23 | "integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=" 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/clusterpost-fs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "clusterpost-fs", 3 | "version": "1.0.3", 4 | "description": "Serves a local filesystem using a route ", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "lab -v test.js" 8 | }, 9 | "keywords": [ 10 | "Filesystem", 11 | "clusterpost" 12 | ], 13 | "author": "juanprietob@gmail.com", 14 | "license": "Apache-2.0", 15 | "dependencies": { 16 | "boom": "^4.3.1", 17 | "underscore": "^1.8.3" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/clusterpost-lib/README.md: -------------------------------------------------------------------------------- 1 | # clusterpost-lib 2 | 3 | It implements the API to submit jobs to [clusterpost-server](https://www.npmjs.com/package/clusterpost-server). 4 | This library is used by [clusterpost-app](https://www.npmjs.com/package/clusterpost-app) which implements 5 | submit and retrieve functionalities from the server. 6 | 7 | ## New way to submit jobs: 8 | 9 | --- 10 | var clusterpost = require("clusterpost-lib"); 11 | 12 | # The config file is is optional, if not provided. It will store the info in your home folder. 13 | var configfile = './conf.jobCreation.json'; 14 | 15 | 16 | clusterpost.start(configfile) 17 | .then(function(res){ 18 | 19 | return clusterpost.parseCLIFromStringAndSubmit("SomeExpensiveTask --img ./data/gravitational-waves-simulation.jpg --img2 ./data/gravitational-waves-simulation.jpg -out ./data.nii.gz") 20 | .then(function(id){ 21 | console.log(jobid); 22 | }); 23 | }); 24 | --- 25 | 26 | 27 | ## An example to submit a job (The old way): 28 | 29 | ---- 30 | var clusterpost = require("clusterpost-lib"); 31 | 32 | var inputfiles = []; 33 | 34 | inputfiles.push(path.join(inputdir, "/some/path/img.jpg")); 35 | inputfiles.push(path.join(inputdir, "/some/path/img1.jpg")); 36 | inputfiles.push(path.join(inputdir, "/some/path/img2.jpg")); 37 | 38 | 39 | var job = { 40 | "executable": "aVeryExpensiveTask", 41 | "parameters": [ 42 | { 43 | "flag": "--img", 44 | "name": "img.jpg" 45 | }, 46 | { 47 | "flag": "--mask", 48 | "name": "img1.jpg" 49 | }, 50 | { 51 | "flag": "-labelValue", 52 | "name": "6" 53 | }, 54 | { 55 | "flag": "--labelImg", 56 | "name": "pvec.nii.gz" 57 | }, 58 | { 59 | "flag": "--outDir", 60 | "name": "./" 61 | } 62 | ], 63 | "inputs": [ 64 | { 65 | "name": "img.jpg" 66 | }, 67 | { 68 | "name": "img1.jpg" 69 | }, 70 | { 71 | "name": "img2.jpg" 72 | } 73 | ], 74 | "outputs": [ 75 | { 76 | "type": "tar.gz", 77 | "name": "./" 78 | }, 79 | { 80 | "type": "file", 81 | "name": "stdout.out" 82 | }, 83 | { 84 | "type": "file", 85 | "name": "stderr.err" 86 | } 87 | ], 88 | "type": "job", 89 | "userEmail": "juanprietob@gmail.com" 90 | }; 91 | 92 | var agentoptions = { 93 | rejectUnauthorized: false //You can also read a certificate here if you want to use https connection and remove this 94 | } 95 | clusterpost.setAgentOptions(agentoptions); 96 | 97 | clusterpost.start() 98 | .then(function(res){ 99 | return clusterpost.getExecutionServers(); 100 | }) 101 | .then(function(res){ 102 | job.executionserver = res[0].name; //Or select the computing grid where you want to submit your job. 103 | return clusterpost.createAndSubmitJob(job, inputfiles) 104 | }) 105 | .then(function(jobid){ 106 | console.log(jobid);//Job id of the task submitted 107 | }) 108 | .catch(console.error) 109 | ---- 110 | 111 | 112 | Once your job is submitted, you can retrieve the results with the following script 113 | 114 | ---- 115 | var clusterpost = require("clusterpost-lib"); 116 | 117 | clusterpost.setClusterPostServer("https://localhost:8180"); 118 | 119 | var agentoptions = { 120 | rejectUnauthorized: false 121 | } 122 | 123 | clusterpost.setAgentOptions(agentoptions); 124 | 125 | clusterpost.userLogin({ 126 | "email": "your@email.com", 127 | "password": "passwd" 128 | }) 129 | .then(function(res){ 130 | return clusterpost.getJobs("aVeryExpensiveTask"); 131 | }) 132 | .then(function(res){ 133 | console.log(res); 134 | }) 135 | .catch(console.error) 136 | ---- 137 | -------------------------------------------------------------------------------- /src/clusterpost-lib/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "clusterpost-lib", 3 | "version": "2.9.1", 4 | "description": "Library implementing the API of clusterpost-server application", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+ssh://git@github.com/juanprietob/clusterpost.git" 12 | }, 13 | "keywords": [ 14 | "clusterpost", 15 | "library", 16 | "implementation", 17 | "API" 18 | ], 19 | "author": "juanprietob@gmail.com", 20 | "license": "Apache-2.0", 21 | "bugs": { 22 | "url": "https://github.com/juanprietob/clusterpost/issues" 23 | }, 24 | "homepage": "https://github.com/juanprietob/clusterpost#readme", 25 | "dependencies": { 26 | "@hapi/joi": "^15.0.3", 27 | "bluebird": "^3.4.1", 28 | "clusterpost-model": "^1.15.0", 29 | "extract-zip": "^2.0.1", 30 | "hapi-jwt-couch-lib": "^1.1.3", 31 | "jsonwebtoken": "^7.4.0", 32 | "prompt": "^1.0.0", 33 | "request": "^2.73.0", 34 | "underscore": "^1.8.3" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/clusterpost-list-react/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "modules": false 5 | }], 6 | "stage-0", 7 | "react" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /src/clusterpost-list-react/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /src/clusterpost-list-react/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": [ 4 | "standard", 5 | "standard-react" 6 | ], 7 | "env": { 8 | "es6": true 9 | }, 10 | "plugins": [ 11 | "react" 12 | ], 13 | "parserOptions": { 14 | "sourceType": "module" 15 | }, 16 | "rules": { 17 | // don't force es6 functions to include space before paren 18 | "space-before-function-paren": 0, 19 | 20 | // allow specifying true explicitly for boolean props 21 | "react/jsx-boolean-value": 0 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/clusterpost-list-react/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # See https://help.github.com/ignore-files/ for more about ignoring files. 3 | 4 | # dependencies 5 | node_modules 6 | 7 | # builds 8 | build 9 | dist 10 | .rpt2_cache 11 | 12 | # misc 13 | .DS_Store 14 | .env 15 | .env.local 16 | .env.development.local 17 | .env.test.local 18 | .env.production.local 19 | 20 | npm-debug.log* 21 | yarn-debug.log* 22 | yarn-error.log* 23 | -------------------------------------------------------------------------------- /src/clusterpost-list-react/.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 9 4 | - 8 5 | -------------------------------------------------------------------------------- /src/clusterpost-list-react/README.md: -------------------------------------------------------------------------------- 1 | # clusterpost-list-react 2 | 3 | > clusterpost jobs view 4 | 5 | [![NPM](https://img.shields.io/npm/v/clusterpost-list-react.svg)](https://www.npmjs.com/package/clusterpost-list-react) [![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com) 6 | 7 | ## Install 8 | 9 | ```bash 10 | npm install --save clusterpost-list-react 11 | ``` 12 | 13 | ## Usage 14 | 15 | ```jsx 16 | import React, { Component } from 'react' 17 | 18 | import MyComponent from 'clusterpost-list-react' 19 | 20 | class Example extends Component { 21 | render () { 22 | return ( 23 | 24 | ) 25 | } 26 | } 27 | ``` 28 | 29 | ## License 30 | 31 | MIT © [juanprietob](https://github.com/juanprietob) 32 | -------------------------------------------------------------------------------- /src/clusterpost-list-react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "clusterpost-list-react", 3 | "version": "1.6.1", 4 | "description": "clusterpost jobs view", 5 | "author": "juanprietob", 6 | "license": "MIT", 7 | "repository": "juanprietob/clusterpost-list-react", 8 | "main": "dist/index.js", 9 | "module": "dist/index.es.js", 10 | "jsnext:main": "dist/index.es.js", 11 | "engines": { 12 | "node": ">=8", 13 | "npm": ">=5" 14 | }, 15 | "scripts": { 16 | "test": "cross-env CI=1 react-scripts test --env=jsdom", 17 | "test:watch": "react-scripts test --env=jsdom", 18 | "build": "rollup -c", 19 | "start": "rollup -c -w", 20 | "prepare": "npm run build", 21 | "predeploy": "cd example && npm install && npm run build", 22 | "deploy": "gh-pages -d example/build" 23 | }, 24 | "peerDependencies": { 25 | "prop-types": "^15.5.4", 26 | "react": "^15.0.0 || ^16.0.0", 27 | "react-dom": "^15.0.0 || ^16.0.0" 28 | }, 29 | "devDependencies": { 30 | "@svgr/rollup": "^2.4.1", 31 | "babel-core": "^6.26.3", 32 | "babel-eslint": "^8.2.5", 33 | "babel-plugin-external-helpers": "^6.22.0", 34 | "babel-preset-env": "^1.7.0", 35 | "babel-preset-react": "^6.24.1", 36 | "babel-preset-stage-0": "^6.24.1", 37 | "cross-env": "^5.1.4", 38 | "eslint": "^5.0.1", 39 | "eslint-config-standard": "^11.0.0", 40 | "eslint-config-standard-react": "^6.0.0", 41 | "eslint-plugin-import": "^2.13.0", 42 | "eslint-plugin-node": "^7.0.1", 43 | "eslint-plugin-promise": "^4.0.0", 44 | "eslint-plugin-react": "^7.10.0", 45 | "eslint-plugin-standard": "^3.1.0", 46 | "gh-pages": "^1.2.0", 47 | "react": "^16.8.6", 48 | "react-dom": "^16.8.6", 49 | "react-scripts": "^1.1.4", 50 | "rollup": "^1.12.3", 51 | "rollup-plugin-babel": "^3.0.7", 52 | "rollup-plugin-commonjs": "^9.1.3", 53 | "rollup-plugin-json": "^4.0.0", 54 | "rollup-plugin-node-resolve": "^3.4.0", 55 | "rollup-plugin-peer-deps-external": "^2.2.0", 56 | "rollup-plugin-postcss": "^1.6.2", 57 | "rollup-plugin-url": "^1.4.0" 58 | }, 59 | "files": [ 60 | "dist" 61 | ], 62 | "dependencies": { 63 | "axios": "^0.21.1", 64 | "d3": "^5.9.2", 65 | "query-string": "^6.5.0", 66 | "react-d3-library": "^1.1.8", 67 | "react-feather": "^1.1.6", 68 | "react-json-view": "^1.19.1", 69 | "recharts": "^1.6.2", 70 | "underscore": "^1.9.1" 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/clusterpost-list-react/rollup.config.js: -------------------------------------------------------------------------------- 1 | import babel from 'rollup-plugin-babel' 2 | import commonjs from 'rollup-plugin-commonjs' 3 | import external from 'rollup-plugin-peer-deps-external' 4 | import postcss from 'rollup-plugin-postcss' 5 | import resolve from 'rollup-plugin-node-resolve' 6 | import url from 'rollup-plugin-url' 7 | import svgr from '@svgr/rollup' 8 | 9 | import pkg from './package.json' 10 | 11 | import json from 'rollup-plugin-json' 12 | import rollupNodeResolve from 'rollup-plugin-node-resolve' 13 | 14 | export default { 15 | input: 'src/index.js', 16 | external: ['react-redux', 'react-router-dom', 'react-jwt-auth'], 17 | output: [ 18 | { 19 | file: pkg.main, 20 | format: 'cjs', 21 | sourcemap: true 22 | }, 23 | { 24 | file: pkg.module, 25 | format: 'es', 26 | sourcemap: true 27 | } 28 | ], 29 | plugins: [ 30 | external(), 31 | postcss({ 32 | modules: true 33 | }), 34 | url(), 35 | svgr(), 36 | babel({ 37 | exclude: 'node_modules/**', 38 | plugins: [ 'external-helpers' ] 39 | }), 40 | resolve({ preferBuiltins: true, mainFields: ['browser'] }), 41 | commonjs({ 42 | namedExports: { 43 | "node_modules/recharts-scale/lib/index.js": ["getNiceTickValues", "getTickValuesFixedDomain"] 44 | } 45 | }), 46 | json() 47 | ] 48 | } 49 | -------------------------------------------------------------------------------- /src/clusterpost-list-react/src/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "jest": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/clusterpost-list-react/src/clusterpost-dashboard.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import PropTypes from 'prop-types'; 4 | 5 | import ClusterpostService from './clusterpost-service'; 6 | import _ from 'underscore'; 7 | 8 | import { connect } from "react-redux"; 9 | import { withRouter } from 'react-router-dom'; 10 | import {Download} from 'react-feather'; 11 | import { 12 | BarChart, Bar, Cell, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer 13 | } from 'recharts'; 14 | 15 | class ClusterpostDashboard extends Component { 16 | 17 | constructor(props) { 18 | super(props); 19 | 20 | this.state = { 21 | jobCount: [{}], 22 | colors: {'CREATE': this.getStyle('.text-primary', 'color'), 23 | 'QUEUE': 'rgb(184, 221, 19)', 24 | 'DOWNLOADING': 'rgb(255, 165, 0)', 25 | 'RUN': this.getStyle('.text-info', 'color'), 26 | 'FAIL': this.getStyle('.text-danger', 'color'), 27 | 'KILL': 'rgb(193, 19, 100)', 28 | 'UPLOADING': 'rgb(19, 193, 85)', 29 | 'EXIT': this.getStyle('.text-warning', 'color'), 30 | 'DONE': this.getStyle('.text-success', 'color'), 31 | 'DELETE': 'rgb(196, 15, 15)' 32 | } 33 | }; 34 | 35 | } 36 | 37 | componentDidMount(){ 38 | this.setState({...this.state}); 39 | this.clusterpost = new ClusterpostService(); 40 | this.clusterpost.setHttp(this.props.http); 41 | 42 | const self = this; 43 | 44 | this.getJobCount() 45 | .then(function(){ 46 | self.startTimer(); 47 | }) 48 | } 49 | 50 | startTimer(){ 51 | var self = this; 52 | setTimeout(function(){ 53 | self.getJobCount() 54 | .then(function(){ 55 | self.startTimer() 56 | }); 57 | }, 6000000); 58 | } 59 | 60 | getJobCount(){ 61 | const self = this; 62 | 63 | return this.clusterpost.getJobCount() 64 | .then(function(res){ 65 | self.setState({...self.state, jobCount: _.map(res.data, (jc)=>{return {[jc.key]: jc.value}})}); 66 | }); 67 | } 68 | 69 | getStyle(selector, style){ 70 | for (var i = 0; i < document.styleSheets.length; i++){ 71 | var mysheet = document.styleSheets[i]; 72 | var myrules = mysheet.cssRules ? mysheet.cssRules : mysheet.rules; 73 | 74 | for (var j = 0; j < myrules.length; j++){ 75 | if (myrules[j].selectorText && myrules[j].selectorText.toLowerCase() === selector){ 76 | return myrules[j].style[style]; 77 | } 78 | } 79 | } 80 | } 81 | 82 | drawBarChart(){ 83 | 84 | const {colors, jobCount} = this.state; 85 | 86 | var data = jobCount; 87 | 88 | return ( 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | ); 107 | } 108 | 109 | render() { 110 | const {data} = this.state; 111 | const self = this; 112 | 113 | return ( 114 |
115 |
116 |
Running tasks
117 |
{this.container = container}}> 118 | {this.drawBarChart()} 119 |
120 |
121 |
122 | ); 123 | } 124 | } 125 | 126 | const mapStateToProps = (state, ownProps) => { 127 | return { 128 | http: state.jwtAuthReducer.http 129 | } 130 | } 131 | 132 | export default withRouter(connect(mapStateToProps)(ClusterpostDashboard)); -------------------------------------------------------------------------------- /src/clusterpost-list-react/src/clusterpost-tokens.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import PropTypes from 'prop-types'; 4 | 5 | import ClusterpostService from './clusterpost-service'; 6 | 7 | import _ from 'underscore'; 8 | 9 | import { connect } from "react-redux"; 10 | import { withRouter } from 'react-router-dom'; 11 | import {Download} from 'react-feather'; 12 | 13 | class ClusterpostTokens extends Component { 14 | 15 | constructor(props) { 16 | super(props); 17 | 18 | this.state = { 19 | tokens: {} 20 | }; 21 | 22 | } 23 | 24 | componentDidMount(){ 25 | 26 | const self = this; 27 | 28 | this.clusterpostService = new ClusterpostService(); 29 | this.clusterpostService.setHttp(this.props.http); 30 | 31 | this.clusterpostService.getExecutionServerTokens() 32 | .then(function(res){ 33 | self.setState({...self.state, tokens: res.data}); 34 | }) 35 | } 36 | 37 | downloadToken(token){ 38 | var filename = "token.json"; 39 | 40 | console.log(token) 41 | 42 | var bb = new Blob([JSON.stringify(token)], {type: 'application/json'}); 43 | 44 | var pom = document.createElement('a'); 45 | 46 | document.body.appendChild(pom); 47 | 48 | pom.setAttribute('href', window.URL.createObjectURL(bb)); 49 | pom.setAttribute('download', filename); 50 | 51 | pom.dataset.downloadurl = ['application/json', pom.download, pom.href].join(':'); 52 | pom.draggable = true; 53 | pom.classList.add('dragout'); 54 | 55 | pom.click(); 56 | } 57 | 58 | getRows(){ 59 | var self = this; 60 | const {tokens} = self.state; 61 | 62 | return _.map(tokens, function(token){ 63 | return 64 | {token.executionserver} 65 | 66 | 67 | }); 68 | } 69 | 70 | render() { 71 | const self = this; 72 | return ( 73 |
74 |
75 |
Execution servers
76 |
77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | {self.getRows()} 89 | 90 |
NameToken download
91 |
92 |
93 |
94 | ); 95 | } 96 | } 97 | 98 | const mapStateToProps = (state, ownProps) => { 99 | return { 100 | http: state.jwtAuthReducer.http 101 | } 102 | } 103 | 104 | export default connect(mapStateToProps)(ClusterpostTokens); -------------------------------------------------------------------------------- /src/clusterpost-list-react/src/index.js: -------------------------------------------------------------------------------- 1 | 2 | export {default as ClusterpostJobs} from './clusterpost-jobs' 3 | export {default as ClusterpostTokens} from './clusterpost-tokens' 4 | export {default as ClusterpostService} from './clusterpost-service' 5 | export {default as ClusterpostDashboard} from './clusterpost-dashboard' 6 | export {default as ClusterpostSoftware} from './clusterpost-software' -------------------------------------------------------------------------------- /src/clusterpost-list-react/src/test.js: -------------------------------------------------------------------------------- 1 | import ExampleComponent from './' 2 | 3 | describe('ExampleComponent', () => { 4 | it('is truthy', () => { 5 | expect(ExampleComponent).toBeTruthy() 6 | }) 7 | }) 8 | -------------------------------------------------------------------------------- /src/clusterpost-list/Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | var name = 'clusterpost-list'; 3 | grunt.initConfig({ 4 | pkg: grunt.file.readJSON('package.json'), 5 | ngtemplates: { 6 | 'clusterpost-list': { 7 | src: './src/**.html', 8 | dest: './dist/clusterpost-list.templates.js' 9 | } 10 | }, 11 | assets:{ 12 | appJS: [ 13 | 'node_modules/jsonformatter/dist/json-formatter.min.js', 14 | './src/clusterpost-list.module.js', 15 | './src/clusterpost-list.service.js', 16 | './src/clusterpost-jobs.directive.js', 17 | './src/clusterpost-app.directive.js', 18 | './src/clusterpost-es-admin.directive.js', 19 | './dist/clusterpost-list.templates.js' 20 | ] 21 | }, 22 | concat: { 23 | prod: { //target 24 | files: { 25 | './dist/clusterpost-list.min.js' : '<%= assets.appJS %>' 26 | } 27 | }, 28 | dev: { 29 | options: { 30 | sourceMap: true 31 | }, 32 | files: { 33 | './dist/clusterpost-list.min.js' : '<%= assets.appJS %>' 34 | } 35 | } 36 | }, 37 | ngAnnotate: { 38 | options: { 39 | singleQuotes: true 40 | }, 41 | app: { 42 | files: { 43 | './dist/clusterpost-list.min.js': './dist/clusterpost-list.min.js' 44 | } 45 | } 46 | }, 47 | uglify: { 48 | prod: { 49 | files: { 50 | './dist/clusterpost-list.min.js': ['./dist/clusterpost-list.min.js'] 51 | } 52 | }, 53 | dev: { 54 | options: { 55 | mangle: false, 56 | compress: false, 57 | sourceMap: true, 58 | sourceMapIncludeSources: true, 59 | sourceMapIn: './dist/clusterpost-list.min.js.map' 60 | }, 61 | files: { //target 62 | './dist/clusterpost-list.min.js': ['./dist/clusterpost-list.min.js'] 63 | } 64 | } 65 | }, 66 | cssmin: { 67 | options: { 68 | shorthandCompacting: false, 69 | roundingPrecision: -1 70 | }, 71 | target: { 72 | files: { 73 | 'dist/clusterpost-list.min.css': ['src/*.css', './node_modules/jsonformatter/dist/json-formatter.min.css'] 74 | } 75 | } 76 | }, 77 | clean: { 78 | dev: ['./dist/clusterpost-list.templates.js'], 79 | prod: ['./dist/clusterpost-list.templates.js', 'dist/clusterpost-list.min.js.map'] 80 | } 81 | }); 82 | 83 | //load grunt tasks 84 | grunt.loadNpmTasks('grunt-contrib-concat'); 85 | grunt.loadNpmTasks('grunt-contrib-uglify'); 86 | grunt.loadNpmTasks('grunt-contrib-clean'); 87 | grunt.loadNpmTasks('grunt-ng-annotate'); 88 | grunt.loadNpmTasks('grunt-angular-templates'); 89 | grunt.loadNpmTasks('grunt-contrib-cssmin'); 90 | 91 | 92 | //register grunt default task 93 | grunt.registerTask('default', [ 'ngtemplates', 'concat:prod', 'ngAnnotate', 'uglify:prod', 'cssmin', 'clean:prod']); 94 | //register dev task 95 | grunt.registerTask('dev', [ 'ngtemplates', 'concat:dev', 'ngAnnotate', 'uglify:dev', 'cssmin', 'clean:dev']); 96 | } -------------------------------------------------------------------------------- /src/clusterpost-list/README.md: -------------------------------------------------------------------------------- 1 | # clusterpost-front -------------------------------------------------------------------------------- /src/clusterpost-list/dist/clusterpost-list.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * jsonformatter 3 | * 4 | * Version: 0.6.0 - 2016-04-28T02:57:03.661Z 5 | * License: Apache-2.0 6 | */.json-formatter-dark.json-formatter-row,.json-formatter-row{font-family:monospace}.json-formatter-dark.json-formatter-row .toggler.open:after,.json-formatter-row .toggler.open:after{transform:rotate(90deg)}.json-formatter-row,.json-formatter-row a,.json-formatter-row a:hover{color:#000;text-decoration:none}.json-formatter-row .json-formatter-row{margin-left:1em}.json-formatter-row .children.empty{opacity:.5;margin-left:1em}.json-formatter-row .children.empty.object:after{content:"No properties"}.json-formatter-row .children.empty.array:after{content:"[]"}.json-formatter-row .string{color:green;white-space:pre;word-wrap:break-word}.json-formatter-row .number{color:#00f}.json-formatter-row .boolean{color:red}.json-formatter-row .null{color:#855A00}.json-formatter-row .undefined{color:#ca0b69}.json-formatter-row .function{color:#FF20ED}.json-formatter-row .date{background-color:rgba(0,0,0,.05)}.json-formatter-row .url{text-decoration:underline;color:#00f;cursor:pointer}.json-formatter-row .bracket{color:#00f}.json-formatter-row .key{color:#00008B;cursor:pointer}.json-formatter-row .constructor-name{cursor:pointer}.json-formatter-row .toggler{font-size:.8em;line-height:1.2em;vertical-align:middle;opacity:.6;cursor:pointer}.json-formatter-row .toggler:after{display:inline-block;transition:transform .1s ease-in;content:"►"}.json-formatter-row>a>.thumbnail-text{opacity:0;transition:opacity .15s ease-in;font-style:italic}.json-formatter-row:hover>a>.thumbnail-text{opacity:.6}.json-formatter-dark.json-formatter-row,.json-formatter-dark.json-formatter-row a,.json-formatter-dark.json-formatter-row a:hover{color:#fff;text-decoration:none}.json-formatter-dark.json-formatter-row .json-formatter-row{margin-left:1em}.json-formatter-dark.json-formatter-row .children.empty{opacity:.5;margin-left:1em}.json-formatter-dark.json-formatter-row .children.empty.object:after{content:"No properties"}.json-formatter-dark.json-formatter-row .children.empty.array:after{content:"[]"}.json-formatter-dark.json-formatter-row .string{color:#31F031;white-space:pre;word-wrap:break-word}.json-formatter-dark.json-formatter-row .number{color:#66C2FF}.json-formatter-dark.json-formatter-row .boolean{color:#EC4242}.json-formatter-dark.json-formatter-row .null{color:#EEC97D}.json-formatter-dark.json-formatter-row .undefined{color:#ef8fbe}.json-formatter-dark.json-formatter-row .function{color:#FD48CB}.json-formatter-dark.json-formatter-row .date{background-color:rgba(255,255,255,.05)}.json-formatter-dark.json-formatter-row .url{text-decoration:underline;color:#027BFF;cursor:pointer}.json-formatter-dark.json-formatter-row .bracket{color:#9494FF}.json-formatter-dark.json-formatter-row .key{color:#23A0DB;cursor:pointer}.json-formatter-dark.json-formatter-row .constructor-name{cursor:pointer}.json-formatter-dark.json-formatter-row .toggler{font-size:.8em;line-height:1.2em;vertical-align:middle;opacity:.6;cursor:pointer}.json-formatter-dark.json-formatter-row .toggler:after{display:inline-block;transition:transform .1s ease-in;content:"►"}.json-formatter-dark.json-formatter-row>a>.thumbnail-text{opacity:0;transition:opacity .15s ease-in;font-style:italic}.json-formatter-dark.json-formatter-row:hover>a>.thumbnail-text{opacity:.6} -------------------------------------------------------------------------------- /src/clusterpost-list/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "clusterpost-list", 3 | "version": "2.4.5", 4 | "description": "List jobs running in clusterpost", 5 | "main": "clusterpost-list.module.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+ssh://git@github.com/NIRALUser/clusterpost.git" 12 | }, 13 | "keywords": [ 14 | "clusterpost", 15 | "list", 16 | "front-end" 17 | ], 18 | "author": "juanprietob@gmail.com", 19 | "license": "Apache-2.0", 20 | "bugs": { 21 | "url": "https://github.com/NIRALUser/clusterpost/issues" 22 | }, 23 | "homepage": "https://github.com/NIRALUser/clusterpost#readme", 24 | "devDependencies": { 25 | "grunt": "^1.0.1", 26 | "grunt-angular-templates": "^1.1.0", 27 | "grunt-contrib-clean": "^1.0.0", 28 | "grunt-contrib-concat": "^1.0.1", 29 | "grunt-contrib-cssmin": "^1.0.2", 30 | "grunt-contrib-uglify": "^2.0.0", 31 | "grunt-ng-annotate": "^2.0.2" 32 | }, 33 | "dependencies": { 34 | "angular": "^1.5.8", 35 | "angular-bootstrap": "^0.12.2", 36 | "angular-route": "^1.5.8", 37 | "angular-smart-table": "^2.1.8", 38 | "jsonformatter": "^0.6.0", 39 | "underscore": "^1.8.3" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/clusterpost-list/src/clusterpost-es-admin.directive.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 26 | 27 | 28 |
Execution server nameDownload
{{row.executionserver}}
24 |
25 |
29 |
30 |
31 |
32 | 33 |
34 |
35 | 36 |
37 |
38 |
39 |
-------------------------------------------------------------------------------- /src/clusterpost-list/src/clusterpost-es-admin.directive.js: -------------------------------------------------------------------------------- 1 | 2 | angular.module('clusterpost-list') 3 | .directive('clusterpostEsAdmin', function($routeParams,$location, clusterpostService){ 4 | 5 | function link($scope, $element, $attrs){ 6 | 7 | 8 | clusterpostService.getExecutionServerTokens() 9 | .then(function(res){ 10 | $scope.tokens = res.data; 11 | }) 12 | 13 | $scope.downloadToken = function(token){ 14 | 15 | var filename = "token.json"; 16 | var bb = new Blob([angular.toJson(token)], {type: 'text/plain'}); 17 | 18 | var pom = document.createElement('a'); 19 | 20 | $element.append(pom); 21 | 22 | pom.setAttribute('href', window.URL.createObjectURL(bb)); 23 | pom.setAttribute('download', filename); 24 | 25 | pom.dataset.downloadurl = ['text/plain', pom.download, pom.href].join(':'); 26 | pom.draggable = true; 27 | pom.classList.add('dragout'); 28 | 29 | pom.click(); 30 | } 31 | 32 | } 33 | 34 | return { 35 | restrict : 'E', 36 | link : link, 37 | scope:{ 38 | jobCallback: '=', 39 | appName: '=', 40 | downloadCallback: '=' 41 | }, 42 | templateUrl: './src/clusterpost-es-admin.directive.html' 43 | } 44 | 45 | }); 46 | -------------------------------------------------------------------------------- /src/clusterpost-list/src/clusterpost-list.module.js: -------------------------------------------------------------------------------- 1 | angular.module('clusterpost-list', 2 | ['ui.bootstrap', 3 | 'smart-table', 4 | 'jsonFormatter', 5 | 'jwt-user-login']); -------------------------------------------------------------------------------- /src/clusterpost-list/src/clusterpost-list.service.js: -------------------------------------------------------------------------------- 1 | angular.module('clusterpost-list') 2 | .factory('clusterpostService', function ($q, $http, $location) { 3 | return { 4 | getExecutionServers: function () { 5 | return $http({ 6 | method: 'GET', 7 | url: '/executionserver' 8 | 9 | }); 10 | }, 11 | getJobStatus: function (id) { 12 | return $http({ 13 | method: 'GET', 14 | url: '/executionserver/' + id 15 | }); 16 | }, 17 | submitJob: function (id,force) { 18 | return $http({ 19 | method: 'POST', 20 | url: '/executionserver/' + id, 21 | data: { 22 | force: force 23 | } 24 | }); 25 | }, 26 | killJob: function (id) { 27 | return $http({ 28 | method: 'DELETE', 29 | url: '/executionserver/' + id 30 | 31 | }); 32 | }, 33 | createJob: function(job){ 34 | return $http({ 35 | method: 'POST', 36 | url: '/dataprovider', 37 | data: job 38 | 39 | }); 40 | }, 41 | getAllJobs: function(executable){ 42 | return $http({ 43 | method: 'GET', 44 | url: '/dataprovider', 45 | params: { 46 | executable: executable 47 | } 48 | }); 49 | }, 50 | updateJob: function(job){ 51 | return $http({ 52 | method: 'PUT', 53 | url: '/dataprovider', 54 | data: job 55 | 56 | }); 57 | }, 58 | getJob: function(id){ 59 | return $http({ 60 | method: 'GET', 61 | url: '/dataprovider/' + id 62 | }); 63 | }, 64 | getJobDownload: function(id){ 65 | return $http({ 66 | method: 'GET', 67 | url: '/dataprovider/download/job/' + id, 68 | responseType: 'blob' 69 | }) 70 | .then(function(res){ 71 | return $http({ 72 | method: 'DELETE', 73 | url: '/dataprovider/download/job/' + id 74 | }) 75 | .then(function(){ 76 | return res; 77 | }) 78 | }) 79 | }, 80 | //For the response type check https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/responseType, "text", "arraybuffer", "blob", "json" 81 | getAttachment: function(id, filename, responseType){ 82 | return $http({ 83 | method: 'GET', 84 | url: '/dataprovider/' + id + '/' + encodeURIComponent(filename), 85 | responseType: responseType 86 | }); 87 | }, 88 | getAttachmentDowloadToken: function(id, filename, expires){ 89 | return $http({ 90 | method: 'GET', 91 | url: '/dataprovider/download/' + id + '/' + encodeURIComponent(filename), 92 | params: { 93 | expires: expires 94 | } 95 | }); 96 | }, 97 | getDownloadAttachmentURL: function(id, filename){ 98 | return this.getAttachmentDowloadToken(id, filename) 99 | .then(function(res){ 100 | return '/dataprovider/download/' + res.data.token; 101 | }); 102 | }, 103 | addAttachment: function(id, filename, data){ 104 | return $http({ 105 | method: 'PUT', 106 | url: '/dataprovider/' + id + '/' + filename, 107 | data: data 108 | 109 | }); 110 | }, 111 | addAttachments: function(id, filenameArray, dataArray){ 112 | var service = this; 113 | var addAttachmentsRec = function(id, filenameArray, dataArray, index, resArray){ 114 | return service.addAttachment(id, filenameArray[index], dataArray[index]) 115 | .then(function (res) { 116 | resArray.push(res); 117 | index++; 118 | if(index < filenameArray.length && index < dataArray.length){ 119 | return addAttachmentsRec(id, filenameArray, dataArray, index, resArray); 120 | } 121 | return resArray; 122 | }) 123 | } 124 | return addAttachmentsRec(id, filenameArray, dataArray, 0, []); 125 | }, 126 | getJobUser: function(email, jobstatus, executable){ 127 | return $http({ 128 | method: 'GET', 129 | url: '/dataprovider/user', 130 | params: { 131 | userEmail: email, 132 | jobstatus: jobstatus, 133 | executable: executable 134 | } 135 | }); 136 | }, 137 | getUserJobs: function(params){ 138 | return $http({ 139 | method: 'GET', 140 | url: '/dataprovider/user', 141 | params: params 142 | }); 143 | }, 144 | deleteJob: function(id){ 145 | return $http({ 146 | method: 'DELETE', 147 | url: '/dataprovider/' + id 148 | }) 149 | }, 150 | createAndSubmitJob: function(job, filenameArray, dataArray){ 151 | var service = this; 152 | return service.createJob(job) 153 | .then(function(res){ 154 | var doc = res.data; 155 | var job_id = doc.id; 156 | return service.addAttachments(job_id, filenameArray, dataArray) 157 | .then(function(res){ 158 | return service.submitJob(job_id); 159 | }); 160 | }); 161 | }, 162 | getExecutionServerTokens: function(){ 163 | return $http({ 164 | method: 'GET', 165 | url: '/executionserver/tokens' 166 | }) 167 | } 168 | } 169 | }); -------------------------------------------------------------------------------- /src/clusterpost-model/index.js: -------------------------------------------------------------------------------- 1 | var Joi = require('@hapi/joi'); 2 | 3 | exports.parameter = Joi.object().keys({ 4 | flag: Joi.string().allow('').optional(), 5 | name: Joi.string().allow('') 6 | }); 7 | 8 | exports.output = Joi.object().keys({ 9 | type: Joi.string().valid('file', 'directory', 'tar.gz'), 10 | name: Joi.string(), 11 | local_storage: Joi.object({ 12 | target_path: Joi.string() 13 | }).optional(), 14 | local : Joi.object({ 15 | "useDefault": Joi.boolean().optional(), 16 | "key": Joi.string().optional(), 17 | "uri": Joi.string().optional() 18 | }).xor("useDefault", "key").optional() 19 | }); 20 | 21 | exports.input = Joi.object().keys({ 22 | name: Joi.string(), 23 | remote : Joi.object().keys({ 24 | serverCodename: Joi.string().optional(), 25 | uri: Joi.string() 26 | }).optional(), 27 | local_storage: Joi.boolean().optional(), 28 | type: Joi.string().optional(), 29 | local : Joi.object({ 30 | "useDefault": Joi.boolean().optional(), 31 | "key": Joi.string().optional(), 32 | "uri": Joi.string().optional() 33 | }).xor("useDefault", "key").optional() 34 | }); 35 | 36 | exports.jobpost = Joi.object().keys({ 37 | type: Joi.string().required(), 38 | userEmail: Joi.string().required(), 39 | executionserver: Joi.string().required(), 40 | jobparameters: Joi.array().items(exports.parameter, Joi.string(), Joi.number()).optional(), 41 | executable: Joi.string().required(), 42 | parameters: Joi.array().items(exports.parameter, Joi.string(), Joi.number()).min(1), 43 | inputs: Joi.array().items(exports.input).min(1).optional(), 44 | outputs: Joi.array().items(exports.output).min(1), 45 | outputdir: Joi.string().optional(), 46 | name: Joi.string().optional(), 47 | scope: Joi.array().items(Joi.string()).optional(), 48 | version: Joi.string().optional(), 49 | data: Joi.object().optional() 50 | }); 51 | 52 | exports.jobstatus = Joi.object().keys({ 53 | status: Joi.string().valid('CREATE', 'QUEUE', 'DOWNLOADING', 'RUN', 'FAIL', 'KILL', 'UPLOADING', 'EXIT', 'DONE', 'DELETE'), 54 | jobid: Joi.optional(), 55 | stat: Joi.optional(), 56 | error: Joi.optional(), 57 | downloadstatus: Joi.array().items(Joi.object()).optional(), 58 | uploadstatus: Joi.array().items(Joi.object()).optional() 59 | }); 60 | 61 | exports.job = Joi.object().keys({ 62 | _id: Joi.string().required(), 63 | _rev: Joi.string().required(), 64 | type: Joi.string().required(), 65 | userEmail: Joi.string().email().required(), 66 | timestamp: Joi.date().required(), 67 | timestampstart: Joi.date().optional(), 68 | timestampend: Joi.date().optional(), 69 | jobstatus: exports.jobstatus.required(), 70 | executable: Joi.string().required(), 71 | executionserver: Joi.string().required(), 72 | jobparameters: Joi.array().items(exports.parameter, Joi.string(), Joi.number()).optional(), 73 | parameters: Joi.array().items(exports.parameter, Joi.string(), Joi.number()).optional(), 74 | inputs: Joi.array().items(exports.input).min(1).optional(), 75 | outputs: Joi.array().items(exports.output).min(1), 76 | outputdir: Joi.string().optional(), 77 | name: Joi.string().optional(), 78 | _attachments: Joi.optional(), 79 | attachments: Joi.optional(), 80 | scope: Joi.array().items(Joi.string()).optional(), 81 | version: Joi.string().optional(), 82 | data: Joi.object().optional() 83 | }); 84 | 85 | exports.executionservertoken = Joi.object().keys({ 86 | executionserver: Joi.string(), 87 | token: Joi.string(), 88 | name: Joi.string().optional() 89 | }) 90 | 91 | exports.softwarepost = Joi.object().keys({ 92 | name: Joi.string(), 93 | description: Joi.string(), 94 | command: Joi.string(), 95 | patterns: Joi.array(), 96 | type: Joi.string().allow("software"), 97 | docker: Joi.string().optional(), 98 | cpus: Joi.number().optional(), 99 | mem: Joi.number().optional(), 100 | gpu: Joi.boolean().optional(), 101 | group: Joi.string().optional(), 102 | subgroup: Joi.string().optional(), 103 | _id: Joi.string().optional(), 104 | _rev: Joi.string().optional() 105 | }) 106 | 107 | exports.software = Joi.object().keys({ 108 | name: Joi.string(), 109 | description: Joi.string(), 110 | command: Joi.string(), 111 | patterns: Joi.array(), 112 | type: Joi.string().allow("software"), 113 | docker: Joi.string().optional(), 114 | cpus: Joi.number().optional(), 115 | mem: Joi.number().optional(), 116 | gpu: Joi.boolean().optional(), 117 | group: Joi.string().optional(), 118 | subgroup: Joi.string().optional(), 119 | _id: Joi.string().required(), 120 | _rev: Joi.string().required() 121 | }) -------------------------------------------------------------------------------- /src/clusterpost-model/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "clusterpost-model", 3 | "version": "1.17.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@hapi/address": { 8 | "version": "2.0.0", 9 | "resolved": "https://registry.npmjs.org/@hapi/address/-/address-2.0.0.tgz", 10 | "integrity": "sha512-mV6T0IYqb0xL1UALPFplXYQmR0twnXG0M6jUswpquqT2sD12BOiCiLy3EvMp/Fy7s3DZElC4/aPjEjo2jeZpvw==" 11 | }, 12 | "@hapi/hoek": { 13 | "version": "6.2.3", 14 | "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-6.2.3.tgz", 15 | "integrity": "sha512-CtV9cp35+6Sfh6OfB+AYBozNIorZ6npNJjfO8InIyh/iFQI7uBW9bIApYoYf6TWq9w9BArecw2DDJf7oK+VlRw==" 16 | }, 17 | "@hapi/joi": { 18 | "version": "15.0.3", 19 | "resolved": "https://registry.npmjs.org/@hapi/joi/-/joi-15.0.3.tgz", 20 | "integrity": "sha512-z6CesJ2YBwgVCi+ci8SI8zixoj8bGFn/vZb9MBPbSyoxsS2PnWYjHcyTM17VLK6tx64YVK38SDIh10hJypB+ig==", 21 | "requires": { 22 | "@hapi/address": "2.x.x", 23 | "@hapi/hoek": "6.x.x", 24 | "@hapi/topo": "3.x.x" 25 | } 26 | }, 27 | "@hapi/topo": { 28 | "version": "3.1.0", 29 | "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-3.1.0.tgz", 30 | "integrity": "sha512-gZDI/eXOIk8kP2PkUKjWu9RW8GGVd2Hkgjxyr/S7Z+JF+0mr7bAlbw+DkTRxnD580o8Kqxlnba9wvqp5aOHBww==", 31 | "requires": { 32 | "@hapi/hoek": "6.x.x" 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/clusterpost-model/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "clusterpost-model", 3 | "version": "1.17.0", 4 | "description": "Contains the Joi descriptions of the clusterpost server application", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [ 10 | "Joi", 11 | "clusterpost" 12 | ], 13 | "dependencies": { 14 | "@hapi/joi": "^15.0.3" 15 | }, 16 | "devDependencies": {}, 17 | "author": "juanprietob@gmail.com", 18 | "license": "Apache-2.0", 19 | "repository": { 20 | "type": "git", 21 | "url": "git+ssh://git@github.com/juanprietob/clusterpost.git" 22 | }, 23 | "bugs": { 24 | "url": "https://github.com/juanprietob/clusterpost/issues" 25 | }, 26 | "homepage": "https://github.com/juanprietob/clusterpost#readme" 27 | } 28 | -------------------------------------------------------------------------------- /src/clusterpost-provider/README.md: -------------------------------------------------------------------------------- 1 | # clusterpost-provider 2 | 3 | This is an [Hapi](http://hapijs.com/) plugin to Execute jobs in remote computing grids using a REST api. Data transfer, job execution and monitoring are all handled by clusterpost. 4 | 5 | Clusterpost uses node with Hapijs in the server side application plus couchdb for storage. 6 | 7 | Cluster post is easy to deploy and will integrate well with existing applications. 8 | 9 | To install the server application check the documentation in [clusterpost-server](https://www.npmjs.com/package/clusterpost-server) 10 | 11 | This is a sample configuration. It requires [couch-provider](https://www.npmjs.com/package/couch-provider) to manage access to couchdb. The namespace 'clusterprovider' is used to discover the REST api for couchdb 12 | in the hapi server application. 13 | The configuration for 'clusterpost-provider' contains a set of access credentials to computing grids. 14 | For more information about the type of computing grids that are supported check [clusterpost-execution]() 15 | 16 | This package depends on [hapi-jwt-couch](https://www.npmjs.com/package/hapi-jwt-couch), 17 | for the route authentication and encryption of tokens. 18 | The algorithm section has the parameters to encrypt the tokens that are emmited for the clusterpost-execution. 19 | 20 | ---- 21 | var obj_config = { 22 | "hapi-jwt-couch": {"Configuration for hapi-jwt-couch"} 23 | "couch-provider": { 24 | "default" : "clusterjobstest", 25 | "clusterjobstest" : { 26 | "hostname": "http://localhost:5984", 27 | "database": "clusterjobstest" 28 | }, 29 | "namespace": ["clusterprovider"] 30 | }, 31 | "clusterpost-provider":{ 32 | "algorithm": { 33 | "algorithm": "HS256", 34 | "expiresIn": "7d" 35 | }, 36 | local_storage: "./local_storage", 37 | "executionservers" : { 38 | "testserver" : { 39 | "hostname" : "localhost", 40 | "user" : "username", 41 | "identityfile" : "~/.ssh/id_rsa", 42 | "sourcedir" : "/path/to/install/clusterpost-execution/" 43 | } 44 | } 45 | } 46 | } 47 | ---- 48 | 49 | -------------------------------------------------------------------------------- /src/clusterpost-provider/clusterprovider.methods.js: -------------------------------------------------------------------------------- 1 | var request = require('request'); 2 | var _ = require('underscore'); 3 | var Promise = require('bluebird'); 4 | var Stream = require('stream'); 5 | var Boom = require('boom'); 6 | 7 | module.exports = function (server, conf) { 8 | 9 | var clustermodel = require('clusterpost-model'); 10 | var Joi = require('@hapi/joi'); 11 | 12 | const isJobDocument = function(doc){ 13 | Joi.assert(doc, clustermodel.job); 14 | return Promise.resolve(doc); 15 | } 16 | 17 | server.method({ 18 | name: 'clusterprovider.isJobDocument', 19 | method: isJobDocument, 20 | options: {} 21 | }); 22 | 23 | const validateUserScopes = function(userscope, docscope){ 24 | if(userscope && docscope){ 25 | var intersection = _.intersection(userscope, docscope); 26 | return intersection.length > 0; 27 | } 28 | return false; 29 | } 30 | 31 | const validateJobOwnership = function(doc, credentials){ 32 | return new Promise(function(resolve, reject){ 33 | if(doc.userEmail === credentials.email || credentials.scope.indexOf('admin') >= 0 || (credentials.scope.indexOf("executionserver") >= 0) || validateUserScopes(credentials.scope, doc.scope)){ 34 | resolve(doc); 35 | }else{ 36 | reject(Boom.unauthorized("You are not allowed to access this job document!")); 37 | } 38 | }); 39 | } 40 | 41 | server.method({ 42 | name: 'clusterprovider.validateJobOwnership', 43 | method: validateJobOwnership, 44 | options: {} 45 | }); 46 | 47 | /* 48 | * Download and save attachment from DB 49 | * 50 | */ 51 | const downloadAttachment = function(options, filename){ 52 | Joi.assert(filename, Joi.string()) 53 | return new Promise(function(resolve, reject){ 54 | 55 | try{ 56 | 57 | var writestream = fs.createWriteStream(filename); 58 | request(options).pipe(writestream); 59 | 60 | writestream.on('finish', function(err){ 61 | if(err){ 62 | reject({ 63 | "path" : filename, 64 | "status" : false, 65 | "error": err 66 | }); 67 | }else{ 68 | resolve({ 69 | "path" : filename, 70 | "status" : true 71 | }); 72 | } 73 | }); 74 | 75 | }catch(e){ 76 | reject(e); 77 | } 78 | }); 79 | } 80 | 81 | server.method({ 82 | name: 'clusterprovider.downloadAttachment', 83 | method: downloadAttachment, 84 | options: {} 85 | }); 86 | } 87 | -------------------------------------------------------------------------------- /src/clusterpost-provider/couchUpdateViews.js: -------------------------------------------------------------------------------- 1 | var couchUpdateViews = require('couch-update-views'); 2 | couchUpdateViews.couchUpdateViews(); -------------------------------------------------------------------------------- /src/clusterpost-provider/executionserver.routes.js: -------------------------------------------------------------------------------- 1 | module.exports = function (server, conf) { 2 | 3 | var handlers = require('./executionserver.handlers')(server, conf); 4 | var Joi = require('@hapi/joi'); 5 | var clustermodel = require('clusterpost-model'); 6 | 7 | 8 | server.route({ 9 | method: 'GET', 10 | path: "/executionserver", 11 | config: { 12 | auth: { 13 | strategy: 'token', 14 | scope: ['clusterpost'] 15 | }, 16 | handler: handlers.getExecutionServers, 17 | response: { 18 | schema: Joi.array().items(Joi.object().keys({ 19 | name: Joi.string(), 20 | queues: Joi.array().items(Joi.string()).optional(), 21 | info: Joi.object().optional() 22 | })) 23 | }, 24 | description: 'Get execution servers code names' 25 | } 26 | }); 27 | 28 | server.route({ 29 | method: 'POST', 30 | path: "/executionserver/{id}", 31 | config: { 32 | auth: { 33 | strategy: 'token', 34 | scope: ['clusterpost'] 35 | }, 36 | handler: handlers.submitJob, 37 | validate: { 38 | params: { 39 | id: Joi.string().alphanum().required() 40 | }, 41 | query: false, 42 | payload: Joi.object().keys({ 43 | force: Joi.boolean() 44 | }).optional() 45 | }, 46 | description: 'Start job execution' 47 | } 48 | }); 49 | 50 | server.route({ 51 | method: 'DELETE', 52 | path: "/executionserver/{id}", 53 | config: { 54 | auth: { 55 | strategy: 'token', 56 | scope: ['clusterpost'] 57 | }, 58 | handler: handlers.killJob, 59 | validate: { 60 | params: { 61 | id: Joi.string().alphanum().required() 62 | }, 63 | query: false, 64 | payload: false 65 | }, 66 | description: 'Kill a running job' 67 | } 68 | }); 69 | 70 | server.route({ 71 | method: 'GET', 72 | path: "/executionserver/{id}", 73 | config: { 74 | auth: { 75 | strategy: 'token', 76 | scope: ['clusterpost'] 77 | }, 78 | handler: handlers.jobStatus, 79 | validate:{ 80 | params: { 81 | id: Joi.string().alphanum().required() 82 | }, 83 | query: false, 84 | payload: false 85 | }, 86 | description: 'Update job status' 87 | } 88 | }); 89 | 90 | server.route({ 91 | method: 'GET', 92 | path: "/executionserver/deletequeue", 93 | config: { 94 | auth: { 95 | strategy: 'token', 96 | scope: ['clusterpost', 'executionserver'] 97 | }, 98 | handler: handlers.getDeleteQueue, 99 | validate:{ 100 | params: null, 101 | query: false, 102 | payload: false 103 | }, 104 | response: { 105 | schema: Joi.array().items(clustermodel.job) 106 | }, 107 | description: 'Get delete queue for remote execution server' 108 | } 109 | }); 110 | 111 | server.route({ 112 | method: 'GET', 113 | path: "/executionserver/tokens", 114 | config: { 115 | auth: { 116 | strategy: 'token', 117 | scope: ['admin'] 118 | }, 119 | handler: handlers.getExecutionServerTokens, 120 | validate:{ 121 | params: null, 122 | query: Joi.object().keys({ 123 | executionserver: Joi.string() 124 | }).optional(), 125 | payload: false 126 | }, 127 | response: { 128 | schema: Joi.array().items(clustermodel.executionservertoken) 129 | }, 130 | description: 'Get tokens for the remote execution servers' 131 | } 132 | }); 133 | 134 | server.route({ 135 | method: 'POST', 136 | path: '/executionserver/uploadSoftware', 137 | config: { 138 | auth: { 139 | strategy: 'token', 140 | scope: ['admin'] 141 | }, 142 | handler: handlers.uploadSoftware, 143 | validate: { 144 | query: false, 145 | payload: clustermodel.softwarepost, 146 | params: null 147 | }, 148 | } 149 | }); 150 | 151 | server.route({ 152 | method: 'GET', 153 | path: '/executionserver/getSoftware', 154 | config: { 155 | auth: { 156 | strategy: 'token', 157 | scope: ['admin', 'executionserver', 'clusterpost'] 158 | }, 159 | handler: handlers.getSoftware, 160 | validate: { 161 | query: Joi.object().keys({ 162 | _id: Joi.string().optional() 163 | }), 164 | payload: null, 165 | params: null 166 | }, 167 | response: { 168 | schema: Joi.array().items(clustermodel.software) 169 | } 170 | } 171 | }); 172 | 173 | 174 | server.route({ 175 | method: 'DELETE', 176 | path: '/executionserver/deleteSoftware', 177 | config: { 178 | auth: { 179 | strategy: 'token', 180 | scope: ['admin'] 181 | }, 182 | handler: handlers.deleteSoftware, 183 | validate: { 184 | query: false, 185 | params: null, 186 | payload: clustermodel.software 187 | }, 188 | description: "delete a softwate from the database" 189 | } 190 | }) 191 | } 192 | 193 | -------------------------------------------------------------------------------- /src/clusterpost-provider/index.js: -------------------------------------------------------------------------------- 1 | exports.plugin = {}; 2 | exports.plugin.register = async function (server, conf) { 3 | 4 | require('./dataprovider.routes')(server, conf); 5 | require('./executionserver.routes')(server, conf); 6 | require('./clusterprovider.methods')(server, conf); 7 | require('./cronprovider')(server, conf); 8 | 9 | var cluster = server.methods.getCluster(); 10 | if(!cluster || cluster && cluster.worker.id === 1){ 11 | 12 | server.methods.executionserver.startExecutionServers() 13 | .then(function(){ 14 | return server.methods.executionserver.startTunnels(); 15 | }) 16 | .then(function(){ 17 | console.log("Execution servers started."); 18 | }); 19 | } 20 | 21 | }; 22 | 23 | exports.plugin.pkg = require('./package.json'); 24 | -------------------------------------------------------------------------------- /src/clusterpost-provider/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "clusterpost-provider", 3 | "version": "4.8.3", 4 | "description": "Transfer job description and data to couchdb. Submit task to the computing grid", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [ 10 | "cluster", 11 | "computing" 12 | ], 13 | "author": "juanprietob@gmail.com", 14 | "license": "Apache-2.0", 15 | "dependencies": { 16 | "@hapi/joi": "^15.0.3", 17 | "archiver": "^5.3.0", 18 | "bluebird": "^3.3.0", 19 | "boom": "^3.1.2", 20 | "clusterpost-model": "^1.17.0", 21 | "couch-provider": "^2.1.0", 22 | "couch-update-views": "^1.0.10", 23 | "find-process": "^1.1.1", 24 | "joi": "^14.3.1", 25 | "linkedlist": "^1.0.1", 26 | "node-crontab": "0.0.8", 27 | "node-targz": "^0.2.0", 28 | "request": "^2.69.0", 29 | "underscore": "^1.8.3" 30 | }, 31 | "repository": { 32 | "type": "git", 33 | "url": "git+ssh://git@github.com/juanprietob/clusterpost.git" 34 | }, 35 | "bugs": { 36 | "url": "https://github.com/juanprietob/clusterpost/issues" 37 | }, 38 | "homepage": "https://github.com/juanprietob/clusterpost#readme" 39 | } 40 | -------------------------------------------------------------------------------- /src/clusterpost-provider/views/getJob.json: -------------------------------------------------------------------------------- 1 | { 2 | "_id": "_design/getJob", 3 | "language": "javascript", 4 | "views": { 5 | "status": { 6 | "map": "function(doc) {\n\tif(doc.type === 'job' && doc.jobstatus && doc.jobstatus.status){\n\t\t emit(doc._id, doc.jobstatus);\n\t}\n}" 7 | }, 8 | "count": { 9 | "map": "function(doc) {\n\tif(doc.type === 'job' && doc.jobstatus && doc.jobstatus.status){\n\t\t emit(doc.jobstatus.status, 1);\n\t}\n}", 10 | "reduce": "_sum" 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /src/clusterpost-provider/views/searchJob.json: -------------------------------------------------------------------------------- 1 | { 2 | "_id": "_design/searchJob", 3 | "language": "javascript", 4 | "views": { 5 | "useremail": { 6 | "map": "function(doc) {\n\tif(doc.type === 'job'){\n\t\t emit(doc.userEmail, doc.jobstatus);\n\t}\n}" 7 | }, 8 | "useremailjobstatus": { 9 | "map": "function(doc) {\n\tif(doc.type === 'job' && doc.jobstatus && doc.jobstatus.status){\n\t\t emit([doc.userEmail, doc.jobstatus.status], doc._id);\n\t}\n}" 10 | }, 11 | "jobstatus": { 12 | "map": "function(doc) {\n\tif(doc.type === 'job' && doc.jobstatus && doc.jobstatus.status){\n\t\t emit(doc.jobstatus.status, { _id : doc._id, executionserver : doc.executionserver});\n\t}\n}" 13 | }, 14 | "useremailexecutable": { 15 | "map": "function(doc) {\n\tif(doc.type === 'job'){\n\t\t emit([doc.userEmail, doc.executable], doc._id);\n\t}\n}" 16 | }, 17 | "executable": { 18 | "map": "function(doc) {\n\tif(doc.type === 'job'){\n\t\t emit(doc.executable, doc.userEmail);\n\t}\n}" 19 | }, 20 | "useremailjobstatusexecutable": { 21 | "map": "function(doc) {\n\tif(doc.type === 'job' && doc.jobstatus && doc.jobstatus.status){\n\t\t emit([doc.userEmail, doc.jobstatus.status, doc.executable], doc._id);\n\t}\n}" 22 | }, 23 | "executionserverjobstatus": { 24 | "map": "function(doc) {\n\tif(doc.type === 'job' && doc.jobstatus && doc.jobstatus.status){\n\t\t emit([doc.executionserver, doc.jobstatus.status], { _id : doc._id });\n\t}\n}" 25 | }, 26 | "scope": { 27 | "map": "function(doc) {\n\tif(doc.type === 'job' && doc.scope){\n\t\tdoc.scope.forEach(function(sc){\n\t\t\temit(sc, doc.jobstatus);\n\t\t})\n\t}\n}" 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /src/clusterpost-provider/views/software.json: -------------------------------------------------------------------------------- 1 | { 2 | "_id": "_design/software", 3 | "views": { 4 | "softwarePatterns": { 5 | "map": "function (doc) {\n if(doc.type == 'software'){\n emit(doc.command, doc.patterns)\n }\n}" 6 | } 7 | }, 8 | "language": "javascript" 9 | } -------------------------------------------------------------------------------- /src/clusterpost-server/checkConfiguration.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | 4 | var cwd = process.cwd(); 5 | var installdir = path.join(process.cwd(), "../../"); 6 | 7 | var defaultconfig = fs.readFileSync(path.join(cwd, "conf.default.json")); 8 | 9 | try{ 10 | var stats = fs.statSync(path.join(installdir, 'conf.production.json')); 11 | }catch(e){ 12 | console.log("Generating default 'production' configuration file..."); 13 | console.log("Please edit this file with your configuration parameters."); 14 | fs.writeFileSync(path.join(installdir, 'conf.production.json'), defaultconfig); 15 | } 16 | 17 | try{ 18 | var stats = fs.statSync(path.join(installdir, 'conf.test.json')); 19 | }catch(e){ 20 | console.log("Generating default 'test' configuration file..."); 21 | console.log("Please edit this file with your configuration parameters."); 22 | fs.writeFileSync(path.join(installdir, 'conf.test.json'), defaultconfig); 23 | } 24 | 25 | try{ 26 | 27 | var migrateUp = fs.readFileSync(path.join(cwd, "migrateUp.js.in")); 28 | migrateUp.replace("@DIRNAME@", cwd); 29 | 30 | fs.writeFileSync(path.join(installdir, 'migrateUp.js'), migrateUp); 31 | 32 | }catch(e){ 33 | 34 | } 35 | 36 | try{ 37 | 38 | var stats = fs.statSync(path.join(installdir, 'index.js')); 39 | 40 | }catch(e){ 41 | 42 | console.log("Generating default 'index.js' start script"); 43 | console.log("Please edit this file and add your own plugins"); 44 | 45 | var index = fs.readFileSync(path.join(cwd, "index.js")); 46 | fs.writeFileSync(path.join(installdir, 'index.js'), index); 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/clusterpost-server/conf.default.json: -------------------------------------------------------------------------------- 1 | { 2 | "host": "localhost", 3 | "port": 8180, 4 | "plugins": { 5 | "@hapi/vision": {}, 6 | "@hapi/inert": {}, 7 | "lout": {}, 8 | "@hapi/h2o2": {}, 9 | "@juanprietob/hapi-auth-jwt": {}, 10 | "hapi-jwt-couch": { 11 | "privateKey": "some_private_key", 12 | "saltRounds": 10, 13 | "algorithm": { 14 | "algorithm": "HS256" 15 | }, 16 | "algorithms": { 17 | "algorithms": [ "HS256" ] 18 | }, 19 | "mailer": { 20 | "nodemailer": "nodemailer-stub-transport", 21 | "from": "clusterpost server ", 22 | "message": "Hello @USERNAME@,
Somebody asked me to send you a link to reset your password, hopefully it was you.
Follow this link to reset your password.
The link will expire in 30 minutes.
Bye." 23 | }, 24 | "userdb" : { 25 | "hostname": "http://localhost:5984", 26 | "database": "clusterjobs" 27 | } 28 | }, 29 | "couch-provider": { 30 | "default" : "clusterjobs", 31 | "clusterjobs" : { 32 | "hostname": "http://localhost:5984", 33 | "database": "clusterjobs" 34 | }, 35 | "namespace": "clusterprovider" 36 | }, 37 | "clusterpost-provider": { 38 | "local_storage" : "./local_storage", 39 | "executionservers" : { 40 | "some_name": { 41 | "remote": true 42 | } 43 | } 44 | }, 45 | "clusterpost-static": { 46 | 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/clusterpost-server/index.js: -------------------------------------------------------------------------------- 1 | var Hapi = require('@hapi/hapi'); 2 | var fs = require('fs'); 3 | var good = require('@hapi/good'); 4 | var path = require('path'); 5 | var _ = require('underscore'); 6 | 7 | var env = process.env.NODE_ENV; 8 | 9 | if(!env) throw "Please set NODE_ENV variable."; 10 | 11 | 12 | const getConfigFile = function () { 13 | try { 14 | // Try to load the user's personal configuration file 15 | return require(process.cwd() + '/conf.my.' + env + '.json'); 16 | } catch (e) { 17 | // Else, read the default configuration file 18 | return require(process.cwd() + '/conf.' + env + '.json'); 19 | } 20 | } 21 | 22 | const startServer = async (cluster) => { 23 | 24 | var conf = getConfigFile(); 25 | 26 | var tls; 27 | if(conf.tls && conf.tls.key && conf.tls.cert){ 28 | tls = { 29 | key: fs.readFileSync(conf.tls.key), 30 | cert: fs.readFileSync(conf.tls.cert) 31 | }; 32 | } 33 | var server_options = { 34 | host: conf.host, 35 | port: conf.port, 36 | tls: tls 37 | } 38 | if(process.env.NODE_ENV == 'test'){ 39 | server_options.routes = { 40 | "cors": true 41 | } 42 | } 43 | var server = new Hapi.Server(server_options); 44 | 45 | var plugins = _.map(conf.plugins, function(options, pluginName){ 46 | return { 47 | plugin: require(pluginName), 48 | options: options 49 | } 50 | }); 51 | 52 | plugins.push({ 53 | plugin: good, 54 | options: { 55 | reporters: { 56 | myConsoleReporter: [{ 57 | module: '@hapi/good-squeeze', 58 | name: 'Squeeze', 59 | args: [{ log: '*', response: '*' }] 60 | }, 61 | { 62 | module: '@hapi/good-console' 63 | }, 'stdout'], 64 | myFileReporter: [{ 65 | module: '@hapi/good-squeeze', 66 | name: 'Squeeze', 67 | args: [{ ops: '*' }] 68 | }, { 69 | module: '@hapi/good-squeeze', 70 | name: 'SafeJson' 71 | }] 72 | } 73 | } 74 | }); 75 | 76 | 77 | server.method({ 78 | name: 'getCluster', 79 | method: function(){ 80 | return cluster; 81 | }, 82 | options: {} 83 | }); 84 | 85 | await server.register(plugins); 86 | await server.start(); 87 | console.log(`Server running at: ${server.info.uri}`); 88 | 89 | } 90 | 91 | if(env === 'production'){ 92 | const cluster = require('cluster'); 93 | const numCPUs = require('os').cpus().length; 94 | 95 | if (cluster.isMaster) { 96 | // Fork workers. 97 | for (var i = 0; i < 1; i++) { 98 | cluster.fork(); 99 | } 100 | 101 | cluster.on('exit', (worker, code, signal) => { 102 | console.log("worker ", worker.process.pid,"died"); 103 | }); 104 | 105 | } else { 106 | startServer(cluster); 107 | } 108 | }else{ 109 | 110 | startServer(); 111 | } 112 | -------------------------------------------------------------------------------- /src/clusterpost-server/migrateUp.js.in: -------------------------------------------------------------------------------- 1 | var cuv = require('couch-update-views'); 2 | var Promise = require('bluebird'); 3 | 4 | var env = process.env.NODE_ENV; 5 | 6 | if(!env) throw 'Please set NODE_ENV variable.'; 7 | 8 | 9 | const getConfigFile = function () { 10 | try { 11 | // Try to load the user's personal configuration file 12 | return require(process.cwd() + '/conf.my.' + env + '.json'); 13 | } catch (e) { 14 | // Else, read the default configuration file 15 | return require(process.cwd() + '/conf.' + env + '.json'); 16 | } 17 | } 18 | 19 | var conf = getConfigFile(); 20 | 21 | var couchprovider = conf.plugins['couch-provider']; 22 | 23 | //migration of clusterpost-provider 24 | var views = path.join('./', 'node_modules', 'clusterpost-provider', 'views'); 25 | var allupdate = []; 26 | 27 | Object.keys(couchprovider).forEach(function(key){ 28 | if(key!=='default' && key!==='namespace'){ 29 | var couchdb = couchprovider[key].hostname + '/' + couchprovider[key].database; 30 | 31 | allupdate.push(cuv.migrateUp(couchdb, views)); 32 | 33 | } 34 | }); 35 | 36 | 37 | //Migration of userdb 38 | var userdb = var couchprovider = conf.plugins['clusterpost-auth'].userdb; 39 | allupdate.push(cuv.migrateUp(userdb.hostname + '/' + userdb.database, path.join('./', 'node_modules', 'clusterpost-auth', 'views'))); 40 | 41 | Promise.all(allupdate) 42 | .then(function(res){ 43 | console.log(res); 44 | process.exit(0); 45 | }) 46 | .catch(function(err){ 47 | console.log(error); 48 | process.exit(1); 49 | }); -------------------------------------------------------------------------------- /src/clusterpost-server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "clusterpost-server", 3 | "version": "2.4.2", 4 | "description": "Server side application using REST api. Create/post 'job' document describing task and input data, run tasks on remote computing grids and retrive outputs. All process is done automatically.", 5 | "main": "index.js", 6 | "dependencies": { 7 | "@hapi/boom": "^7.4.2", 8 | "@hapi/good": "^8.2.0", 9 | "@hapi/good-console": "^8.1.0", 10 | "@hapi/good-squeeze": "^5.2.0", 11 | "@hapi/h2o2": "^8.3.0", 12 | "@hapi/hapi": "^20.0.0", 13 | "@hapi/inert": "^5.2.0", 14 | "@hapi/vision": "^5.5.2", 15 | "@juanprietob/hapi-auth-jwt": "github:juanprietob/hapi-auth-jwt#master", 16 | "bcrypt": "^5.0.0", 17 | "bluebird": "^3.5.4", 18 | "clusterpost-model": "^1.14.1", 19 | "clusterpost-provider": "^4.4.3", 20 | "clusterpost-static": "file:../clusterpost-static", 21 | "couch-provider": "^3.0.1", 22 | "couch-update-views": "1.0.5", 23 | "hapi-jwt-couch": "^4.0.0", 24 | "jsonwebtoken": "^8.5.1", 25 | "lout": "^11.2.3", 26 | "node-crontab": "0.0.8", 27 | "nodemailer": "^4.0.1", 28 | "nodemailer-stub-transport": "^1.1.0", 29 | "request": "^2.88.0", 30 | "underscore": "^1.9.1" 31 | }, 32 | "devDependencies": {}, 33 | "scripts": { 34 | "test": "echo \"Error: no test specified\" && exit 1", 35 | "postinstall": "node ./checkConfiguration.js" 36 | }, 37 | "repository": { 38 | "type": "git", 39 | "url": "git+ssh://git@github.com/juanprietob/clusterpost.git" 40 | }, 41 | "keywords": [ 42 | "cluster", 43 | "computing", 44 | "REST" 45 | ], 46 | "author": "juanprietob@gmail.com", 47 | "license": "Apache-2.0", 48 | "bugs": { 49 | "url": "https://github.com/juanprietob/clusterpost/issues" 50 | }, 51 | "homepage": "https://github.com/juanprietob/clusterpost#readme" 52 | } 53 | -------------------------------------------------------------------------------- /src/clusterpost-static/clusterpost-public/.browserlistrc: -------------------------------------------------------------------------------- 1 | { 2 | "browserslist": { 3 | "production": [ 4 | ">0.2%", 5 | "not dead", 6 | "not op_mini all" 7 | ], 8 | "development": [ 9 | "last 1 chrome version", 10 | "last 1 firefox version", 11 | "last 1 safari version" 12 | ] 13 | } 14 | } -------------------------------------------------------------------------------- /src/clusterpost-static/clusterpost-public/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /src/clusterpost-static/clusterpost-public/README.md: -------------------------------------------------------------------------------- 1 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 2 | 3 | ## Available Scripts 4 | 5 | In the project directory, you can run: 6 | 7 | ### `npm start` 8 | 9 | Runs the app in the development mode.
10 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 11 | 12 | The page will reload if you make edits.
13 | You will also see any lint errors in the console. 14 | 15 | ### `npm test` 16 | 17 | Launches the test runner in the interactive watch mode.
18 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 19 | 20 | ### `npm run build` 21 | 22 | Builds the app for production to the `build` folder.
23 | It correctly bundles React in production mode and optimizes the build for the best performance. 24 | 25 | The build is minified and the filenames include the hashes.
26 | Your app is ready to be deployed! 27 | 28 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 29 | 30 | ### `npm run eject` 31 | 32 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 33 | 34 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 35 | 36 | Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 37 | 38 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 39 | 40 | ## Learn More 41 | 42 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 43 | 44 | To learn React, check out the [React documentation](https://reactjs.org/). 45 | 46 | ### Code Splitting 47 | 48 | This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting 49 | 50 | ### Analyzing the Bundle Size 51 | 52 | This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size 53 | 54 | ### Making a Progressive Web App 55 | 56 | This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app 57 | 58 | ### Advanced Configuration 59 | 60 | This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration 61 | 62 | ### Deployment 63 | 64 | This section has moved here: https://facebook.github.io/create-react-app/docs/deployment 65 | 66 | ### `npm run build` fails to minify 67 | 68 | This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify 69 | -------------------------------------------------------------------------------- /src/clusterpost-static/clusterpost-public/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "clusterpost-public", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "axios": "^0.21.1", 7 | "bootstrap": "^4.3.1", 8 | "clusterpost-list-react": "file:../../clusterpost-list-react", 9 | "react": "^16.8.6", 10 | "react-bootstrap": "^1.0.0-beta.8", 11 | "react-dom": "^16.8.6", 12 | "react-feather": "^1.1.6", 13 | "react-hapi-jwt-auth": "^1.1.4", 14 | "react-redux": "^7.0.3", 15 | "react-router-dom": "^5.0.0", 16 | "react-scripts": "^4.0.3", 17 | "redux": "^4.0.1" 18 | }, 19 | "scripts": { 20 | "start": "react-scripts start", 21 | "build": "PUBLIC_URL=/public/ react-scripts build", 22 | "test": "react-scripts test", 23 | "eject": "react-scripts eject" 24 | }, 25 | "eslintConfig": { 26 | "extends": "react-app" 27 | }, 28 | "browserslist": { 29 | "production": [ 30 | ">0.2%", 31 | "not dead", 32 | "not op_mini all" 33 | ], 34 | "development": [ 35 | "last 1 chrome version", 36 | "last 1 firefox version", 37 | "last 1 safari version" 38 | ] 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/clusterpost-static/clusterpost-public/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juanprietob/clusterpost/0c50e24c70724cd4e7aa877ec1afa68f7e8e729e/src/clusterpost-static/clusterpost-public/public/favicon.ico -------------------------------------------------------------------------------- /src/clusterpost-static/clusterpost-public/public/images/fibers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juanprietob/clusterpost/0c50e24c70724cd4e7aa877ec1afa68f7e8e729e/src/clusterpost-static/clusterpost-public/public/images/fibers.png -------------------------------------------------------------------------------- /src/clusterpost-static/clusterpost-public/public/images/icosahedron.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juanprietob/clusterpost/0c50e24c70724cd4e7aa877ec1afa68f7e8e729e/src/clusterpost-static/clusterpost-public/public/images/icosahedron.png -------------------------------------------------------------------------------- /src/clusterpost-static/clusterpost-public/public/images/segmentation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juanprietob/clusterpost/0c50e24c70724cd4e7aa877ec1afa68f7e8e729e/src/clusterpost-static/clusterpost-public/public/images/segmentation.png -------------------------------------------------------------------------------- /src/clusterpost-static/clusterpost-public/public/images/subcortical.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juanprietob/clusterpost/0c50e24c70724cd4e7aa877ec1afa68f7e8e729e/src/clusterpost-static/clusterpost-public/public/images/subcortical.png -------------------------------------------------------------------------------- /src/clusterpost-static/clusterpost-public/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 22 | React App 23 | 24 | 25 | 26 |
27 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/clusterpost-static/clusterpost-public/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /src/clusterpost-static/clusterpost-public/src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /src/clusterpost-static/clusterpost-public/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 4 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /src/clusterpost-static/clusterpost-public/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | 4 | import App from './App'; 5 | import * as serviceWorker from './serviceWorker'; 6 | 7 | import { BrowserRouter } from 'react-router-dom'; 8 | 9 | import { Provider } from 'react-redux'; 10 | import store from "./redux/store"; 11 | 12 | ReactDOM.render( 13 | 14 | 15 | 16 | 17 | , document.getElementById('root')); 18 | 19 | // If you want your app to work offline and load faster, you can change 20 | // unregister() to register() below. Note this comes with some pitfalls. 21 | // Learn more about service workers: https://bit.ly/CRA-PWA 22 | serviceWorker.unregister(); -------------------------------------------------------------------------------- /src/clusterpost-static/clusterpost-public/src/nav-bar.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { Link } from 'react-router-dom'; 4 | import store from "./redux/store"; 5 | import {withRouter} from 'react-router-dom'; 6 | import {Home, User, Users, Cpu, Settings, LogOut, LogIn, Box} from 'react-feather'; 7 | import NavDropdown from 'react-bootstrap/NavDropdown'; 8 | import Navbar from 'react-bootstrap/Navbar'; 9 | import Nav from 'react-bootstrap/Nav'; 10 | 11 | class NavBar extends Component{ 12 | constructor(props) { 13 | super(props); 14 | 15 | this.state = { 16 | showLogin: false 17 | } 18 | } 19 | 20 | getComputing(){ 21 | const {user} = this.props; 22 | if(user && user.scope && user.scope.indexOf('clusterpost') != -1){ 23 | return Computing 24 | } 25 | } 26 | 27 | getSoftware(){ 28 | const {user} = this.props; 29 | if(user && user.scope && user.scope.indexOf('clusterpost') != -1){ 30 | return Software 31 | } 32 | } 33 | 34 | getSettings(){ 35 | const {user, history} = this.props; 36 | 37 | var strSettings = ; 38 | 39 | 40 | if(user && user.scope && user.scope.indexOf('default') != -1){ 41 | return Settings} id="basic-nav-dropdown"> 42 | {history.push('/admin/users')}}> Users 43 | 44 | {history.push('/admin/servers')}}> Computing 45 | 46 | } 47 | } 48 | 49 | onUserLogin(){ 50 | this.props.userLogin(!this.state.showLogin); 51 | this.setState({...this.state, showLogin: !this.state.showLogin}); 52 | this.props.history.push('/login'); 53 | } 54 | 55 | getUserDropDown(){ 56 | const {user, history} = this.props; 57 | 58 | if(user && user.scope && user.scope.indexOf('default') != -1){ 59 | return } id="basic-nav-dropdown"> 60 | {history.push('/user')}}> Profile 61 | 62 | {history.push('/logout')}}> Logout 63 | 64 | }else{ 65 | return } id="basic-nav-dropdown"> 66 | Login 67 | 68 | } 69 | } 70 | 71 | render() { 72 | const self = this; 73 | const {user} = self.props; 74 | 75 | return ( 76 | Clusterpost 77 | 78 | 79 | 90 | 91 | ); 92 | 93 | } 94 | } 95 | 96 | const mapStateToProps = (state, ownProps) => { 97 | return { 98 | http: state.jwtAuthReducer.http, 99 | user: state.jwtAuthReducer.user 100 | } 101 | } 102 | 103 | const mapDispatchToProps = (dispatch) => { 104 | return { 105 | userLogin: (showLogin) => { 106 | dispatch({ 107 | type: 'user-login', 108 | showLogin: showLogin 109 | }); 110 | } 111 | } 112 | } 113 | 114 | export default withRouter(connect(mapStateToProps, mapDispatchToProps)(NavBar)); 115 | 116 | // const httpFactory = http => ({ 117 | // type: 'http-factory', 118 | // http: http 119 | // }); 120 | 121 | // export default connect(mapStateToProps, {httpFactory})(NavBar); -------------------------------------------------------------------------------- /src/clusterpost-static/clusterpost-public/src/redux/navbar-reducer.js: -------------------------------------------------------------------------------- 1 | 2 | const initialState = { 3 | uri: '/', 4 | queryParams: '', 5 | http: '' 6 | }; 7 | 8 | const navbarReducer = (state = initialState, action) => { 9 | switch (action.type) { 10 | case 'user-login':{ 11 | return { 12 | ...state, 13 | showLogin: action.showLogin 14 | } 15 | } 16 | default: { 17 | return state; 18 | } 19 | } 20 | }; 21 | 22 | export default navbarReducer; -------------------------------------------------------------------------------- /src/clusterpost-static/clusterpost-public/src/redux/reducers.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from "redux"; 2 | import navbarReducer from "./navbar-reducer"; 3 | import {jwtAuthReducer} from "react-hapi-jwt-auth" 4 | 5 | export default combineReducers({ navbarReducer, jwtAuthReducer }); 6 | -------------------------------------------------------------------------------- /src/clusterpost-static/clusterpost-public/src/redux/store.js: -------------------------------------------------------------------------------- 1 | import { createStore } from "redux"; 2 | import rootReducer from "./reducers"; 3 | 4 | export default createStore(rootReducer); -------------------------------------------------------------------------------- /src/clusterpost-static/clusterpost-public/src/serviceWorker.js: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read https://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.1/8 is considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 20 | ) 21 | ); 22 | 23 | export function register(config) { 24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 25 | // The URL constructor is available in all browsers that support SW. 26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); 27 | if (publicUrl.origin !== window.location.origin) { 28 | // Our service worker won't work if PUBLIC_URL is on a different origin 29 | // from what our page is served on. This might happen if a CDN is used to 30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 31 | return; 32 | } 33 | 34 | window.addEventListener('load', () => { 35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 36 | 37 | if (isLocalhost) { 38 | // This is running on localhost. Let's check if a service worker still exists or not. 39 | checkValidServiceWorker(swUrl, config); 40 | 41 | // Add some additional logging to localhost, pointing developers to the 42 | // service worker/PWA documentation. 43 | navigator.serviceWorker.ready.then(() => { 44 | console.log( 45 | 'This web app is being served cache-first by a service ' + 46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA' 47 | ); 48 | }); 49 | } else { 50 | // Is not localhost. Just register service worker 51 | registerValidSW(swUrl, config); 52 | } 53 | }); 54 | } 55 | } 56 | 57 | function registerValidSW(swUrl, config) { 58 | navigator.serviceWorker 59 | .register(swUrl) 60 | .then(registration => { 61 | registration.onupdatefound = () => { 62 | const installingWorker = registration.installing; 63 | if (installingWorker == null) { 64 | return; 65 | } 66 | installingWorker.onstatechange = () => { 67 | if (installingWorker.state === 'installed') { 68 | if (navigator.serviceWorker.controller) { 69 | // At this point, the updated precached content has been fetched, 70 | // but the previous service worker will still serve the older 71 | // content until all client tabs are closed. 72 | console.log( 73 | 'New content is available and will be used when all ' + 74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' 75 | ); 76 | 77 | // Execute callback 78 | if (config && config.onUpdate) { 79 | config.onUpdate(registration); 80 | } 81 | } else { 82 | // At this point, everything has been precached. 83 | // It's the perfect time to display a 84 | // "Content is cached for offline use." message. 85 | console.log('Content is cached for offline use.'); 86 | 87 | // Execute callback 88 | if (config && config.onSuccess) { 89 | config.onSuccess(registration); 90 | } 91 | } 92 | } 93 | }; 94 | }; 95 | }) 96 | .catch(error => { 97 | console.error('Error during service worker registration:', error); 98 | }); 99 | } 100 | 101 | function checkValidServiceWorker(swUrl, config) { 102 | // Check if the service worker can be found. If it can't reload the page. 103 | fetch(swUrl) 104 | .then(response => { 105 | // Ensure service worker exists, and that we really are getting a JS file. 106 | const contentType = response.headers.get('content-type'); 107 | if ( 108 | response.status === 404 || 109 | (contentType != null && contentType.indexOf('javascript') === -1) 110 | ) { 111 | // No service worker found. Probably a different app. Reload the page. 112 | navigator.serviceWorker.ready.then(registration => { 113 | registration.unregister().then(() => { 114 | window.location.reload(); 115 | }); 116 | }); 117 | } else { 118 | // Service worker found. Proceed as normal. 119 | registerValidSW(swUrl, config); 120 | } 121 | }) 122 | .catch(() => { 123 | console.log( 124 | 'No internet connection found. App is running in offline mode.' 125 | ); 126 | }); 127 | } 128 | 129 | export function unregister() { 130 | if ('serviceWorker' in navigator) { 131 | navigator.serviceWorker.ready.then(registration => { 132 | registration.unregister(); 133 | }); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/clusterpost-static/index.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | 4 | exports.plugin = {}; 5 | exports.plugin.register = async function (server, options) { 6 | server.path(__dirname); 7 | 8 | server.route({ 9 | path: '/', 10 | method: '*', 11 | handler: function (request, reply) { 12 | return reply.redirect('/public'); 13 | } 14 | }); 15 | 16 | server.route({ 17 | path: '/public/{path*}', 18 | method: 'GET', 19 | config: { 20 | handler: { 21 | directory: { path: './clusterpost-public/build', listing: false, index: true } 22 | }, 23 | description: 'This route serves the static website of clusterpost.' 24 | } 25 | }); 26 | 27 | }; 28 | 29 | exports.plugin.pkg = require('./package.json'); -------------------------------------------------------------------------------- /src/clusterpost-static/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "clusterpost-static", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1 5 | } 6 | -------------------------------------------------------------------------------- /src/clusterpost-static/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "clusterpost-static", 3 | "version": "1.0.0", 4 | "description": "Public route for clusterpost-static", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "lab -v test.js" 8 | }, 9 | "author": "juanprietob@gmail.com", 10 | "license": "Apache-2.0", 11 | "dependencies": {} 12 | } 13 | -------------------------------------------------------------------------------- /src/couch-provider/index.js: -------------------------------------------------------------------------------- 1 | exports.plugin = {}; 2 | 3 | const couchProvider = require('./couch.provider'); 4 | exports.couchProvider = couchProvider; 5 | 6 | exports.plugin.register = async function (server, conf) { 7 | 8 | couchProvider.setConfiguration(conf); 9 | var namespace = 'couchprovider'; 10 | 11 | if(conf.namespace){ 12 | namespace = conf.namespace; 13 | } 14 | 15 | var addNameSpace = function(namespace){ 16 | server.method({ 17 | name: namespace + '.getCouchDBServer', 18 | method: couchProvider.getCouchDBServer, 19 | options: {} 20 | }); 21 | 22 | server.method({ 23 | name: namespace + '.uploadDocuments', 24 | method: couchProvider.uploadDocuments, 25 | options: {} 26 | }); 27 | 28 | server.method({ 29 | name: namespace + '.getDocument', 30 | method: couchProvider.getDocument, 31 | options: {} 32 | }); 33 | 34 | server.method({ 35 | name: namespace + '.deleteDocument', 36 | method: couchProvider.deleteDocument, 37 | options: {} 38 | }); 39 | 40 | server.method({ 41 | name: namespace + '.addDocumentAttachment', 42 | method: couchProvider.addDocumentAttachment, 43 | options: {} 44 | }); 45 | 46 | server.method({ 47 | name: namespace + '.getDocumentStreamAttachment', 48 | method: couchProvider.getDocumentStreamAttachment, 49 | options: {} 50 | }); 51 | 52 | server.method({ 53 | name: namespace + '.getDocumentStreamAttachmentUri', 54 | method: couchProvider.getDocumentStreamAttachmentUri, 55 | options: {} 56 | }); 57 | 58 | server.method({ 59 | name: namespace + '.getDocumentAttachment', 60 | method: couchProvider.getDocumentAttachment, 61 | options: {} 62 | }); 63 | 64 | server.method({ 65 | name: namespace + '.getView', 66 | method: couchProvider.getView, 67 | options: {} 68 | }); 69 | 70 | server.method({ 71 | name: namespace + '.getViewQs', 72 | method: couchProvider.getViewQs, 73 | options: {} 74 | }); 75 | 76 | server.method({ 77 | name: namespace + '.mkdirp', 78 | method: couchProvider.mkdirp, 79 | options: {} 80 | }); 81 | 82 | server.method({ 83 | name: namespace + '.removeDirectorySync', 84 | method: couchProvider.removeDirectorySync, 85 | options: {} 86 | }); 87 | 88 | console.info('couch-provider namespace', namespace, 'initialized.'); 89 | } 90 | 91 | 92 | if(Array.isArray(namespace)){ 93 | namespace.forEach(function(ns){ 94 | addNameSpace(ns); 95 | }); 96 | }else{ 97 | addNameSpace(namespace); 98 | } 99 | } 100 | 101 | exports.plugin.pkg = require('./package.json'); 102 | -------------------------------------------------------------------------------- /src/couch-provider/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "couch-provider", 3 | "version": "2.3.1", 4 | "description": "Provide the implementation for different request methos to couchdb server. It implements, POST, GET, DELETE, PUT methods for documents and views", 5 | "main": "index.js", 6 | "dependencies": { 7 | "@hapi/joi": "^15.0.3", 8 | "bluebird": "^3.3.0", 9 | "concat-stream": "^1.6.2", 10 | "mkdirp": "^0.5.1", 11 | "request": "^2.88.0", 12 | "underscore": "^1.8.3" 13 | }, 14 | "scripts": { 15 | "test": "lab -v test.js" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git+ssh://git@github.com/juanprietob/clusterpost.git" 20 | }, 21 | "keywords": [ 22 | "Couchdb", 23 | "fs", 24 | "file system", 25 | "attachment", 26 | "Hapi", 27 | "server", 28 | "interface" 29 | ], 30 | "author": "juanprietob@gmail.com", 31 | "license": "Apache-2.0", 32 | "bugs": { 33 | "url": "https://github.com/juanprietob/clusterpost/issues" 34 | }, 35 | "homepage": "https://github.com/juanprietob/clusterpost#readme", 36 | "devDependencies": { 37 | "lab": "^10.9.0" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/couch-update-views/README.md: -------------------------------------------------------------------------------- 1 | # couch-update-views 2 | 3 | 4 | - Do you use git and would like to maintain a copy of your couchdb view's code in your repository? 5 | - Do you have multiple couchdb to maintain (production, development)? 6 | 7 | couch-update-views allows you to synchronize design views from a local directory to your couch database. 8 | It will also help you update the JSON document of a view in your local directory with the view's content in the database. 9 | 10 | 11 | ## Installing couch-update-views 12 | 13 | ---- 14 | npm install couch-update-views 15 | ---- 16 | 17 | ## Running couch-update-views: 18 | 19 | ### Generate a script 20 | 21 | Name your script, ex: couchUpdateViews.js and add the following lines: 22 | 23 | ---- 24 | var couchUpdateViews = require('couch-update-views'); 25 | couchUpdateViews.couchUpdateViews(); 26 | ---- 27 | 28 | You should see the following output: 29 | 30 | ---- 31 | node couchUpdateViews.js --migrate | --update 32 | Options: 33 | --migrate Migrate design documents in couchdb. The 'design views' in couchdb are updated with the contents of the 'viewsDir' folder if they differ. 34 | --update Update the design view document stored in 'viewsDirs' with the document stored in 'couchDB' 35 | --viewsDir Directory with desgin views documents JSON files. (required) 36 | --couchDB CouchDB URL. (required) 37 | ---- 38 | 39 | ### Synchronize the DB with the folder content 40 | 41 | ---- 42 | node couchUpdateViews.js --migrate --viewsDir /path/to/views/folder --couchDB http://localhost:5984/dbname 43 | ---- 44 | 45 | If the dbname does not exist, it will create the db for you and add all the views for you. 46 | 47 | ### Update a view 48 | 49 | Generate your view using couchdb utils. If you are running couchdb locally and using the default port visit: 50 | 51 | ---- 52 | http://localhost:5984/_utils/database.html?dbname/_temp_view 53 | ---- 54 | 55 | - Write the view's code: 56 | 57 | In the 'Map Function' box add: 58 | 59 | ---- 60 | function(doc){ 61 | if(doc.type === "user"){ 62 | emit(doc.email, doc.name); 63 | } 64 | } 65 | ---- 66 | 67 | - Save the view using 'Save As...' button 68 | 69 | Design document: _design/searchUser 70 | 71 | View Name: email 72 | 73 | - Update the view's content in your local folder: 74 | 75 | ---- 76 | node couchUpdateViews.js --viewsDir /path/to/views/folder --couchDB http://localhost:5984/dbname --update searchUser 77 | ---- 78 | 79 | The output of this command yields a file named 'searchUser.json' located at '--viewsDir' folder. 80 | The content of the file should look like: 81 | 82 | ---- 83 | { 84 | "_id": "_design/searchUser", 85 | "language": "javascript", 86 | "views": { 87 | "email": { 88 | "map": "function(doc) {\n\tif(doc.type === \"user\"){\n\t\temit(doc.email, doc.name);\n\t}\n}" 89 | } 90 | } 91 | } 92 | ---- 93 | 94 | ## Using couch-update-views w/o command line 95 | 96 | ## Synchronize the DB with the folder content 97 | 98 | ---- 99 | var couchUpdateViews = require('couch-update-views'); 100 | couchUpdateViews.migrateUp('http://localhost:5984/dbname', '/path/to/views')//DB URL, your local folder with views 101 | .then(function(res){ 102 | console.log(res);//result of the operation 103 | }); 104 | ---- 105 | 106 | ### Update a view 107 | 108 | ---- 109 | var couchUpdateViews = require('couch-update-views'); 110 | couchUpdateViews.updateDesignDocument('http://localhost:5984/dbname', '/path/to/views', 'searchUser')//DB URL, local folder, view name 111 | .then(function(res){ 112 | console.log(res);//result of operation 113 | }); 114 | ---- 115 | 116 | 117 | ## Use case example when starting your server application 118 | 119 | In this example, I'm using [Hapi](hapijs.com) as my server. 120 | The plugin configuration has the couchdb url. 121 | 122 | ---- 123 | conf = { 124 | "couchdb": "http://localhost:5984/somedb", 125 | "dirname": "/local/path/to/views" 126 | } 127 | ---- 128 | 129 | ---- 130 | 131 | module.exports = function (server, conf) { 132 | 133 | var couchUpdateViews = require('couch-update-views'); 134 | var path = require('path'); 135 | 136 | /* 137 | * @params couchdb, url of couchdb 138 | * @params dirname, path to directory containing the json documents of views 139 | * @params test, boolean to specify if it should test for differences in the view. If true, a message will be print indicating that there are differences * in the documents. If false or undefined, whenever there are differences in the document, the view will be pushed to couchdb. 140 | */ 141 | couchUpdateViews.migrateUp(conf.couchdb, conf.dirname, true) 142 | .then(function(res){ 143 | console.log(res);//result of the operation 144 | }); 145 | 146 | //Other server logic, routes etc. 147 | } 148 | 149 | 150 | ---- -------------------------------------------------------------------------------- /src/couch-update-views/couchUpdateViews.js.in: -------------------------------------------------------------------------------- 1 | var couchUpdateViews = require('couch-update-views'); 2 | couchUpdateViews.couchUpdateViews(); -------------------------------------------------------------------------------- /src/couch-update-views/index.js: -------------------------------------------------------------------------------- 1 | 2 | var path = require('path'); 3 | 4 | const run = function(migrate, update, viewsDir, couchDB){ 5 | 6 | var couchUpdateViews; 7 | 8 | if(migrate){ 9 | couchUpdateViews = require(path.join(__dirname, "migrateUp"))(couchDB, viewsDir); 10 | }else{ 11 | couchUpdateViews = require(path.join(__dirname, "updateDesignDocument"))(couchDB, viewsDir, update); 12 | } 13 | 14 | 15 | return couchUpdateViews 16 | .then(function(res){ 17 | console.log(res); 18 | process.exit(0); 19 | }) 20 | .catch(function(err){ 21 | console.error(err); 22 | process.exit(1); 23 | }); 24 | } 25 | 26 | const help = function(){ 27 | console.error("help: To run couch-update-views: ") 28 | console.error(process.argv[0] + " " + process.argv[1] + " --migrate | --update "); 29 | console.error("Options:"); 30 | console.error("--migrate Migrate design documents in couchdb. The 'design views' in couchdb are updated with the contents of the 'viewsDir' folder if they differ."); 31 | console.error("--update Update the design view document stored in 'viewsDirs' with the document stored in 'couchDB'"); 32 | console.error("--viewsDir Directory with desgin views documents JSON files. (required)"); 33 | console.error("--couchDB CouchDB URL. (required)"); 34 | } 35 | 36 | exports.couchUpdateViews = function(){ 37 | var argv = require('minimist')(process.argv.slice(2)); 38 | 39 | var _migrate = argv["migrate"]; 40 | if(argv["migrateUp"]){ 41 | _migrate = argv["migrateUp"]; 42 | } 43 | var _update = argv["update"]; 44 | 45 | var _viewsDir = argv["viewsDir"]; 46 | 47 | if(argv["views"]){ 48 | _viewsDir = argv["views"]; 49 | } 50 | 51 | var _couchDB = argv["couchDB"]; 52 | 53 | if(argv["couch"]){ 54 | _couchDB = argv["couch"]; 55 | } 56 | 57 | if(argv["couchdb"]){ 58 | _couchDB = argv["couchdb"]; 59 | } 60 | 61 | if(!_migrate && !_update || !_viewsDir || !_couchDB){ 62 | help(); 63 | process.exit(1); 64 | } 65 | 66 | run(_migrate, _update, _viewsDir, _couchDB) 67 | .then(function(){ 68 | process.exit(0); 69 | }); 70 | 71 | } 72 | 73 | exports.migrateUp = require(path.join(__dirname, "migrateUp")); 74 | exports.updateDesignDocument = require(path.join(__dirname, "updateDesignDocument")); -------------------------------------------------------------------------------- /src/couch-update-views/migrateUp.js: -------------------------------------------------------------------------------- 1 | 2 | var request = require('request'); 3 | var fs = require('fs'); 4 | var Promise = require('bluebird'); 5 | var path = require('path'); 6 | 7 | module.exports = function(dburl, viewsdir, test){ 8 | 9 | const createDB = function(url){ 10 | return new Promise(function(resolve, reject){ 11 | request.put(url, function(err, res, body){ 12 | if(err){ 13 | reject(err.message); 14 | }else{ 15 | try{ 16 | if(JSON.parse(body).error === "not_found"){ 17 | request.put(url, function(err, res, body){ 18 | resolve(JSON.parse(body)); 19 | }); 20 | }else{ 21 | resolve(JSON.parse(body)); 22 | } 23 | }catch(e){ 24 | console.error(url); 25 | console.error(e); 26 | reject(e); 27 | } 28 | } 29 | }); 30 | }); 31 | } 32 | 33 | const updateDocuments = function(url, viewsdir){ 34 | 35 | var fileviews = fs.readdirSync(viewsdir); 36 | 37 | var testView = function(file){ 38 | return new Promise(function(resolve, reject){ 39 | try{ 40 | if(file.indexOf(".json") === -1){ 41 | reject(file); 42 | }else{ 43 | fs.readFile(path.join(viewsdir, file), function (err, data) { 44 | if (err) throw err; 45 | 46 | var designdoc = JSON.parse(data); 47 | 48 | var options = { 49 | uri: url + "/" + designdoc._id 50 | } 51 | 52 | request(options, function(err, res, body){ 53 | var couchdesigndoc = JSON.parse(body); 54 | 55 | if(JSON.stringify(designdoc.views) !== JSON.stringify(couchdesigndoc.views)){ 56 | 57 | if(test && couchdesigndoc.error !== 'not_found'){ 58 | 59 | console.info("You have two different design documents in the views directory and the couchdb instance:"); 60 | console.info("You should 'migrateUp' the DB or 'update' the document in the view's folder to get rid of this message."); 61 | 62 | console.info("Run the following command to migrate the view in couchdb"); 63 | console.info("node couchUpdateViews.js --migrate --viewsDir", viewsdir, "--couchDB", url); 64 | 65 | console.info("Run the following command to update in your directory"); 66 | console.info("node couchUpdateViews.js --update", path.basename(file),"--viewsDir", viewsdir, "--couchDB", url); 67 | 68 | resolve({ 69 | message: "Documents differ.", 70 | couchDB: url, 71 | view: viewsdir+file 72 | }); 73 | }else{ 74 | 75 | if(couchdesigndoc.error === 'not_found'){ 76 | console.info("Design document not found."); 77 | } 78 | 79 | console.info("Deploying design document: ", designdoc); 80 | 81 | var uri = url + "/" + designdoc._id; 82 | if(couchdesigndoc._rev){ 83 | designdoc._rev = couchdesigndoc._rev; 84 | uri += "?rev="+designdoc._rev; 85 | } 86 | 87 | var options = { 88 | uri : uri, 89 | method : 'PUT', 90 | json : designdoc 91 | } 92 | 93 | request(options, function(err, res, body){ 94 | if(err){ 95 | reject(err.message); 96 | }else{ 97 | resolve(body); 98 | } 99 | }); 100 | } 101 | 102 | 103 | }else{ 104 | resolve({ 105 | message: "No changes in document.", 106 | couchDB: url, 107 | view: viewsdir+file 108 | }); 109 | } 110 | }); 111 | }); 112 | } 113 | }catch(e){ 114 | reject(e); 115 | } 116 | }); 117 | } 118 | 119 | return Promise.map(fileviews, testView); 120 | } 121 | 122 | return createDB(dburl) 123 | .then(function(result){ 124 | return updateDocuments(dburl, viewsdir); 125 | }); 126 | } 127 | -------------------------------------------------------------------------------- /src/couch-update-views/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "couch-update-views", 3 | "version": "1.0.10", 4 | "description": "Update design views in a couchdb instance from json documents store locally. Update the local json document with the design view in couchdb.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "lab -v test.js", 8 | "postinstall": "node postinstall.js" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+ssh://git@github.com/juanprietob/clusterpost.git" 13 | }, 14 | "keywords": [ 15 | "couchdb", 16 | "design", 17 | "view", 18 | "update", 19 | "version", 20 | "deploy" 21 | ], 22 | "author": "juanprietob@gmail.com", 23 | "license": "Apache-2.0", 24 | "bugs": { 25 | "url": "https://github.com/juanprietob/clusterpost/issues" 26 | }, 27 | "homepage": "https://github.com/juanprietob/clusterpost#readme", 28 | "dependencies": { 29 | "bluebird": "^3.4.0", 30 | "joi": "^9.0.0-3", 31 | "lab": "^10.7.1", 32 | "request": "^2.72.0" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/couch-update-views/postinstall.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | 4 | var cwd = process.cwd(); 5 | var installdir = path.join(process.cwd(), "../../"); 6 | 7 | try{ 8 | var stats = fs.statSync(path.join(installdir, 'couchUpdateViews.js')); 9 | }catch(e){ 10 | console.log("Generating default 'couchUpdateViews.js' script..."); 11 | fs.writeFileSync(path.join(installdir, 'couchUpdateViews.js'), fs.readFileSync(path.join(cwd, "couchUpdateViews.js.in"))); 12 | } -------------------------------------------------------------------------------- /src/couch-update-views/test.js: -------------------------------------------------------------------------------- 1 | 2 | var request = require('request'); 3 | var fs = require('fs'); 4 | var Promise = require('bluebird'); 5 | var path = require('path'); 6 | var os = require('os') 7 | 8 | const Joi = require('@hapi/joi'); 9 | const Lab = require('lab'); 10 | const lab = exports.lab = Lab.script(); 11 | 12 | 13 | var viewsDir = path.join(process.cwd(), 'views'); 14 | var viewsDir2 = path.join(process.cwd(), 'views2'); 15 | var viewsDir3 = path.join(process.cwd(), 'views3'); 16 | var couchdb = 'http://localhost:5984/testcouchupdateviews' 17 | console.log('Running test for couchdb: ', couchdb, 'and viewsdir: ', viewsDir); 18 | 19 | lab.experiment("Test couch-update-views", function(){ 20 | 21 | lab.test('returns true when migration is done. test DB is created and design doc is updated', function(){ 22 | 23 | console.log("Testing migrateUp..."); 24 | 25 | return require("./migrateUp")(couchdb, viewsDir) 26 | .then(function(res){ 27 | 28 | Joi.assert(res, Joi.array().items(Joi.object().keys({ 29 | ok: Joi.boolean().valid(true), 30 | id: Joi.string(), 31 | rev: Joi.string() 32 | }))); 33 | 34 | }); 35 | }); 36 | 37 | lab.test('returns true if the documents are different, a help message is printed for the user.', function(){ 38 | return require("./migrateUp")(couchdb, viewsDir2, true) 39 | .then(function(res){ 40 | Joi.assert(res, Joi.array().items(Joi.object().keys({ 41 | message: Joi.string().valid("Documents differ."), 42 | couchDB: Joi.string(), 43 | view: Joi.string() 44 | }))); 45 | }); 46 | }); 47 | 48 | lab.test('returns true if the document is deployed while testing because the design document is not found', function(){ 49 | return require("./migrateUp")(couchdb, viewsDir3, true) 50 | .then(function(res){ 51 | Joi.assert(res, Joi.array().items(Joi.object().keys({ 52 | ok: Joi.boolean().valid(true), 53 | id: Joi.string(), 54 | rev: Joi.string() 55 | }))); 56 | }); 57 | }); 58 | 59 | lab.test('returns true when document is NOT updated because is the same.', function(){ 60 | return require("./migrateUp")(couchdb, viewsDir) 61 | .then(function(res){ 62 | Joi.assert(res, Joi.array().items(Joi.object().keys({ 63 | message: Joi.string().valid("No changes in document."), 64 | couchDB: Joi.string(), 65 | view: Joi.string() 66 | }))); 67 | }); 68 | }); 69 | 70 | lab.test('returns true when document is updated in temp folder.', function(){ 71 | console.log("Testing updateDesignDocument..."); 72 | return require("./updateDesignDocument")(couchdb, os.tmpdir(), 'user') 73 | .then(function(res){ 74 | Joi.assert(res, Joi.object().keys({ 75 | ok: Joi.boolean().valid(true), 76 | id: Joi.string() 77 | })); 78 | }); 79 | }); 80 | 81 | lab.test('returns true when db is deleted.', function(done){ 82 | var options = { 83 | url: couchdb, 84 | method: 'DELETE', 85 | } 86 | request(options, function(err, res, body){ 87 | if(err){ 88 | done(err); 89 | }else{ 90 | done(); 91 | } 92 | }) 93 | }); 94 | 95 | 96 | lab.test('returns true when migration is done. test DB is created and design doc is updated', function(done){ 97 | console.log("Testing module entry point..."); 98 | 99 | var couchUpdateViews = require("./index.js"); 100 | 101 | Joi.assert(couchUpdateViews, Joi.object().keys({ 102 | couchUpdateViews: Joi.func().arity(0), 103 | migrateUp: Joi.func().arity(3), 104 | updateDesignDocument: Joi.func().arity(3), 105 | })); 106 | 107 | done(); 108 | 109 | }); 110 | 111 | }); -------------------------------------------------------------------------------- /src/couch-update-views/updateDesignDocument.js: -------------------------------------------------------------------------------- 1 | 2 | var Promise = require('bluebird'); 3 | var request = require('request'); 4 | var fs = require('fs'); 5 | var path = require('path'); 6 | 7 | module.exports = function(couchdb, viewsDir, design){ 8 | 9 | design = design.replace('.json', ''); 10 | var uri = couchdb + "/_design/" + design; 11 | 12 | return new Promise(function(resolve, reject){ 13 | request(uri, function(err, res, body){ 14 | if(err) { 15 | reject(err); 16 | }else{ 17 | var jsonbody = JSON.parse(body); 18 | delete jsonbody._rev; 19 | var jsonstring = JSON.stringify(jsonbody, null, 4); 20 | var viewfilename = path.join(viewsDir, design + '.json'); 21 | try{ 22 | var write = fs.writeFileSync(viewfilename, jsonstring); 23 | resolve({ 24 | ok: true, 25 | id: viewfilename 26 | }); 27 | }catch(err){ 28 | reject(err); 29 | } 30 | } 31 | }); 32 | }); 33 | } -------------------------------------------------------------------------------- /src/couch-update-views/views/searchUser.json: -------------------------------------------------------------------------------- 1 | { 2 | "_id": "_design/searchUser", 3 | "language": "javascript", 4 | "views": { 5 | "email": { 6 | "map": "function(doc) {\n\tif(doc.type === \"user\"){\n\t\temit(doc.email, doc.name);\n\t}\n}" 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /src/couch-update-views/views2/searchUser.json: -------------------------------------------------------------------------------- 1 | { 2 | "_id": "_design/searchUser", 3 | "language": "javascript", 4 | "views": { 5 | "email": { 6 | "map": "function(doc) {\n\tif(doc.type === \"user\"){\n\t\temit(doc.email, doc.name);\n\t}\n}" 7 | }, 8 | "name": { 9 | "map": "function(doc) {\n\tif(doc.type === \"user\"){\n\t\temit(doc.name, doc.email);\n\t}\n}" 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /src/couch-update-views/views3/searchUser3.json: -------------------------------------------------------------------------------- 1 | { 2 | "_id": "_design/searchUser3", 3 | "language": "javascript", 4 | "views": { 5 | "email": { 6 | "map": "function(doc) {\n\tif(doc.type === \"user\"){\n\t\temit(doc.email, doc.name);\n\t}\n}" 7 | }, 8 | "name": { 9 | "map": "function(doc) {\n\tif(doc.type === \"user\"){\n\t\temit(doc.name, doc.email);\n\t}\n}" 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /src/hapi-jwt-couch-lib/.gitignore: -------------------------------------------------------------------------------- 1 | conf.my.test.json 2 | -------------------------------------------------------------------------------- /src/hapi-jwt-couch-lib/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hapi-jwt-couch-lib", 3 | "version": "1.1.4", 4 | "description": "API implementation to send requests to the hapi server and authenticate users, create, delete, etc.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "lab -v test.js" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+ssh://git@github.com/juanprietob/clusterpost.git" 12 | }, 13 | "keywords": [ 14 | "Hapi", 15 | "JWT", 16 | "couchdb", 17 | "auth" 18 | ], 19 | "author": "juanprietob@gmail.com", 20 | "license": "Apache-2.0", 21 | "bugs": { 22 | "url": "https://github.com/juanprietob/clusterpost/issues" 23 | }, 24 | "homepage": "https://github.com/juanprietob/clusterpost#readme", 25 | "devDependencies": { 26 | "hapi": "^16.6.2", 27 | "hapi-auth-jwt": "^4.0.0", 28 | "lab": "^14.3.1" 29 | }, 30 | "dependencies": { 31 | "@hapi/joi": "^15.0.3", 32 | "bluebird": "^3.5.1", 33 | "jsonwebtoken": "^8.0.1", 34 | "prompt": "^1.0.0", 35 | "request": "^2.88.0", 36 | "underscore": "^1.8.3" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/hapi-jwt-couch-lib/testserver.js: -------------------------------------------------------------------------------- 1 | var Hapi = require('hapi'); 2 | 3 | const getConfigFile = function () { 4 | try { 5 | // Try to load the user's personal configuration file 6 | return require(process.cwd() + '/conf.my.test.json'); 7 | } catch (e) { 8 | // Else, read the default configuration file 9 | return require(process.cwd() + '/conf.test.json'); 10 | } 11 | } 12 | var conf = getConfigFile(); 13 | 14 | var server = new Hapi.Server(); 15 | 16 | server.connection({ 17 | host: conf.host, 18 | port: conf.port 19 | }); 20 | 21 | var plugins = []; 22 | 23 | Object.keys(conf.plugins).forEach(function(pluginName){ 24 | var plugin = {}; 25 | plugin.register = require(pluginName); 26 | plugin.options = conf.plugins[pluginName]; 27 | plugins.push(plugin); 28 | }); 29 | 30 | server.register(plugins, function(err){ 31 | if (err) { 32 | throw err; // something bad happened loading the plugin 33 | } 34 | 35 | }); 36 | 37 | server.start(function () { 38 | server.connections.forEach(function(connection){ 39 | console.log('info', 'server is listening port: ' + connection.info.uri); 40 | }); 41 | }); -------------------------------------------------------------------------------- /src/hapi-jwt-couch/README.md: -------------------------------------------------------------------------------- 1 | # hapi-jwt-couch 2 | 3 | Hapi plugin to validate users using [hapi-auth-jwt](https://github.com/ryanfitz/hapi-auth-jwt), storing user information and encrypted passwords 4 | in a couchdb instance. 5 | 6 | This plugin also provides a 'recover my password' option by setting up an email account using [nodemailer](https://github.com/nodemailer/nodemailer). 7 | 8 | Edit the "message" portion of the configuration. The strings @USERNAME@, @SERVER@ and @TOKEN@ are replaced before sending the email. 9 | 10 | ## Usage 11 | 12 | ---- 13 | npm install hapi-jwt-couch 14 | ---- 15 | 16 | ### Hapi plugin 17 | 18 | The values "user", "password" and "login" are optional. The default values are shown in this example. 19 | 20 | ---- 21 | const Hapi = require('hapi'); 22 | cont Joi = require('@hapi/joi'); 23 | 24 | var password = Joi.string().regex(/^(?=.*[\d])(?=.*[A-Z])(?=.*[a-z])[\w\d!@#$%_-]{6,40}$/); 25 | 26 | var hapijwtcouch = {}; 27 | hapijwtcouch.register = require("hapi-jwt-couch"); 28 | hapijwtcouch.options = { 29 | "privateKey": "SomeRandomKey123", 30 | "saltRounds": 10, 31 | "algorithm": { 32 | "algorithm": "HS256", 33 | "expiresIn": "7d" 34 | }, 35 | "validateOptions": { 36 | "algorithms": [ "HS256" ] 37 | }, 38 | "mailer": { 39 | "nodemailer": { 40 | host: 'smtp.gmail.com', 41 | port: 465, 42 | secure: true, // use SSL 43 | auth: { 44 | user: 'hapi.jwt.couch@gmail.com', 45 | pass: 'pass' 46 | } 47 | }, 48 | "from": "Hapi jwt couch ", 49 | "message": "Hello @USERNAME@,
Somebody asked me to send you a link to reset your password, hopefully it was you.
Follow this link to reset your password.
The link will expire in 30 minutes.
Bye.", 50 | "uri": "http://your.public.ip" 51 | }, 52 | "userdb" : { 53 | "hostname": "http://localhost:5984", 54 | "database": "hapijwtcouch" 55 | }, 56 | "password" = password, 57 | "user" = Joi.object().keys({ 58 | "name": Joi.string().required(), 59 | "email": Joi.string().email().required(), 60 | "password": password 61 | }), 62 | "login": Joi.object().keys({ 63 | "email": Joi.string().email().required(), 64 | "password": password 65 | }) 66 | }; 67 | 68 | 69 | var hapiauth = {}; 70 | hapiauth.register = require("hapi-auth-jwt"); 71 | hapiauth.options = {}; 72 | 73 | 74 | var plugins = [hapiauth, hapijwtcouch]; 75 | 76 | var server = new Hapi.Server(); 77 | server.connection({ 78 | port: "3000" 79 | }); 80 | 81 | server.register(plugins, function(err){ 82 | if (err) { 83 | throw err; // something bad happened loading the plugin 84 | } 85 | 86 | server.start(function (err) { 87 | 88 | console.log("server running", server.info.uri); 89 | 90 | }); 91 | }); 92 | ---- 93 | 94 | ## Create your own Hapi plugin and extend it with your own validation function 95 | 96 | You can extend this plugin by adding your own validation function. You may also change the validation for user, password and login Joi objects. 97 | 98 | The Joi objects shown here for password, user and login are used by default. 99 | 100 | ---- 101 | 102 | const Promise = require('bluebird'); 103 | 104 | exports.register = function (server, conf, next) { 105 | 106 | //The validation function has this signature and the return value must be a Promise. 107 | const validate = function(req, decodedToken){ 108 | //validate your decoded token, the resulting object must have the field 'scope' 109 | if(validationTrue){ 110 | return Promise.resolve({ 111 | "scope": ["custom_scope"] 112 | }); 113 | }else{ 114 | return Promise.reject("Not validated"); 115 | } 116 | } 117 | 118 | try{ 119 | server.methods.jwtauth.addValidationFunction(validate); 120 | }catch(e){ 121 | console.error(e); 122 | } 123 | 124 | //Additional logic for your plugin 125 | 126 | return next(); 127 | 128 | }; 129 | 130 | exports.register.attributes = { 131 | pkg: require('./package.json') 132 | }; 133 | 134 | ---- 135 | 136 | ## Testing 137 | 138 | Start the test server 139 | 140 | ---- 141 | node test/server.js 142 | ---- 143 | 144 | Run all tests 145 | 146 | ---- 147 | npm test 148 | ---- 149 | 150 | -------------------------------------------------------------------------------- /src/hapi-jwt-couch/couchUpdateViews.js: -------------------------------------------------------------------------------- 1 | var couchUpdateViews = require('couch-update-views'); 2 | couchUpdateViews.couchUpdateViews(); -------------------------------------------------------------------------------- /src/hapi-jwt-couch/index.js: -------------------------------------------------------------------------------- 1 | exports.plugin = {}; 2 | exports.plugin.register = async function (server, conf) { 3 | 4 | require('./jwtauth.routes')(server, conf); 5 | }; 6 | 7 | exports.plugin.pkg = require('./package.json'); -------------------------------------------------------------------------------- /src/hapi-jwt-couch/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hapi-jwt-couch", 3 | "version": "4.0.4", 4 | "description": "Simple user authentication for Hapi server using JWT and storing user data and encrypted passwords in couchdb.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "lab -v -m 60000 ./test/test.js" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+ssh://git@github.com/juanprietob/clusterpost.git" 12 | }, 13 | "keywords": [ 14 | "Hapi", 15 | "jwt", 16 | "couchdb", 17 | "user", 18 | "encryption", 19 | "password" 20 | ], 21 | "author": "juanprietob@gmail.com", 22 | "license": "Apache-2.0", 23 | "bugs": { 24 | "url": "https://github.com/juanprietob/clusterpost/issues" 25 | }, 26 | "homepage": "https://github.com/juanprietob/clusterpost#readme", 27 | "devDependencies": { 28 | "hapi": "^13.0.0", 29 | "lab": "^10.8.2", 30 | "nodemailer-stub-transport": "^1.1.0" 31 | }, 32 | "dependencies": { 33 | "@hapi/boom": "^7.4.2", 34 | "@hapi/joi": "^15.0.3", 35 | "bcrypt": "^3.0.5", 36 | "couch-provider": "^2.1.2", 37 | "couch-update-views": "^1.0.5", 38 | "jsonwebtoken": "^7.4.3", 39 | "nodemailer": "^2.3.2", 40 | "underscore": "^1.8.3" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/hapi-jwt-couch/test/server.js: -------------------------------------------------------------------------------- 1 | const Hapi = require('hapi'); 2 | 3 | var hapiauth = {}; 4 | hapiauth.register = require("hapi-auth-jwt"); 5 | hapiauth.options = {}; 6 | 7 | 8 | 9 | var hapijwtcouch = {}; 10 | hapijwtcouch.register = require("../index");//require(hapi-jwt-couch) 11 | hapijwtcouch.options = { 12 | "privateKey": "SomeRandomKey123", 13 | "saltRounds": 10, 14 | "algorithm": { 15 | "algorithm": "HS256" 16 | }, 17 | "algorithms": { 18 | "algorithms": [ "HS256" ] 19 | }, 20 | "mailer": { 21 | "nodemailer": "nodemailer-stub-transport", 22 | "from": "Clusterpost " 23 | }, 24 | "userdb" : { 25 | "hostname": "http://localhost:5984", 26 | "database": "hapijwtcouch" 27 | } 28 | }; 29 | 30 | var plugins = [hapiauth, hapijwtcouch]; 31 | 32 | var server = new Hapi.Server(); 33 | server.connection({ 34 | port: "3000" 35 | }); 36 | 37 | 38 | plugins.push({ 39 | register: require('good'), 40 | options: { 41 | reporters: [ 42 | { 43 | reporter: require('good-console'), 44 | events: { log: '*', response: '*' } 45 | }, { 46 | reporter: require('good-file'), 47 | events: { ops: '*' }, 48 | config: 'all.log' 49 | }] 50 | } 51 | }); 52 | 53 | server.register(plugins, function(err){ 54 | if (err) { 55 | throw err; // something bad happened loading the plugin 56 | } 57 | 58 | server.start(function (err) { 59 | 60 | console.log("server running", server.info.uri); 61 | 62 | }); 63 | }); 64 | -------------------------------------------------------------------------------- /src/hapi-jwt-couch/views/user.json: -------------------------------------------------------------------------------- 1 | { 2 | "_id": "_design/user", 3 | "language": "javascript", 4 | "views": { 5 | "hash": { 6 | "map": "function(doc) {\n\tif(doc.type === \"user\"){\n\t\temit(doc.email, doc.password);\n\t}\n}" 7 | }, 8 | "info": { 9 | "map": "function(doc) {\n\tif(doc.type === \"user\"){\n\t\tvar tempdoc = {};\n\t\tObject.keys(doc).forEach(function(key){\n\t\t\tif(key !== \"password\"){\n\t\t\t\ttempdoc[key] = doc[key];\n\t\t\t}\n\t\t});\n\t\temit(doc.email, tempdoc);\n\t}\n}" 10 | }, 11 | "scopes": { 12 | "map": "function(doc) {\n\tif(doc.type === \"scopes\"){\n\t\temit(doc._id, doc.scopes);\n\t}\n}" 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /src/react-hapi-jwt-auth/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "modules": false 5 | }], 6 | "stage-0", 7 | "react" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /src/react-hapi-jwt-auth/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /src/react-hapi-jwt-auth/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": [ 4 | "standard", 5 | "standard-react" 6 | ], 7 | "env": { 8 | "es6": true 9 | }, 10 | "plugins": [ 11 | "react" 12 | ], 13 | "parserOptions": { 14 | "sourceType": "module" 15 | }, 16 | "rules": { 17 | // don't force es6 functions to include space before paren 18 | "space-before-function-paren": 0, 19 | 20 | // allow specifying true explicitly for boolean props 21 | "react/jsx-boolean-value": 0 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/react-hapi-jwt-auth/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # See https://help.github.com/ignore-files/ for more about ignoring files. 3 | 4 | # dependencies 5 | node_modules 6 | 7 | # builds 8 | build 9 | dist 10 | .rpt2_cache 11 | 12 | # misc 13 | .DS_Store 14 | .env 15 | .env.local 16 | .env.development.local 17 | .env.test.local 18 | .env.production.local 19 | 20 | npm-debug.log* 21 | yarn-debug.log* 22 | yarn-error.log* 23 | -------------------------------------------------------------------------------- /src/react-hapi-jwt-auth/.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 9 4 | - 8 5 | -------------------------------------------------------------------------------- /src/react-hapi-jwt-auth/README.md: -------------------------------------------------------------------------------- 1 | # react-jwt-auth 2 | 3 | > Login using JWT components 4 | 5 | [![NPM](https://img.shields.io/npm/v/react-jwt-auth.svg)](https://www.npmjs.com/package/react-jwt-auth) [![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com) 6 | 7 | ## Install 8 | 9 | ```bash 10 | npm install --save react-jwt-auth 11 | ``` 12 | 13 | ## Usage 14 | 15 | ```jsx 16 | import React, { Component } from 'react' 17 | 18 | import MyComponent from 'react-jwt-auth' 19 | 20 | class Example extends Component { 21 | render () { 22 | return ( 23 | 24 | ) 25 | } 26 | } 27 | ``` 28 | 29 | ## License 30 | 31 | MIT © [juanprietob](https://github.com/juanprietob) 32 | -------------------------------------------------------------------------------- /src/react-hapi-jwt-auth/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-hapi-jwt-auth", 3 | "version": "1.1.1", 4 | "description": "Login using JWT. A react component that uses the api from hapi-jwt-couch", 5 | "author": "juanprietob", 6 | "license": "MIT", 7 | "repository": "juanprietob/clusterpost", 8 | "main": "dist/index.js", 9 | "module": "dist/index.es.js", 10 | "jsnext:main": "dist/index.es.js", 11 | "engines": { 12 | "node": ">=8", 13 | "npm": ">=5" 14 | }, 15 | "scripts": { 16 | "test": "cross-env CI=1 react-scripts test --env=jsdom", 17 | "test:watch": "react-scripts test --env=jsdom", 18 | "build": "rollup -c", 19 | "start": "rollup -c -w", 20 | "prepare": "npm run build", 21 | "predeploy": "cd example && npm install && npm run build", 22 | "deploy": "gh-pages -d example/build" 23 | }, 24 | "peerDependencies": { 25 | "prop-types": "^15.5.4", 26 | "react": "^15.0.0 || ^16.0.0", 27 | "react-dom": "^15.0.0 || ^16.0.0", 28 | "react-redux": "^7.0.3", 29 | "react-router-dom": "^5.0.1", 30 | "react-bootstrap": "^1.0.0-beta.9" 31 | }, 32 | "devDependencies": { 33 | "@svgr/rollup": "^2.4.1", 34 | "babel-core": "^6.26.3", 35 | "babel-eslint": "^8.2.5", 36 | "babel-plugin-external-helpers": "^6.22.0", 37 | "babel-preset-env": "^1.7.0", 38 | "babel-preset-react": "^6.24.1", 39 | "babel-preset-stage-0": "^6.24.1", 40 | "cross-env": "^5.1.4", 41 | "eslint": "^5.0.1", 42 | "eslint-config-standard": "^11.0.0", 43 | "eslint-config-standard-react": "^6.0.0", 44 | "eslint-plugin-import": "^2.13.0", 45 | "eslint-plugin-node": "^7.0.1", 46 | "eslint-plugin-promise": "^4.0.0", 47 | "eslint-plugin-react": "^7.10.0", 48 | "eslint-plugin-standard": "^3.1.0", 49 | "gh-pages": "^1.2.0", 50 | "react-scripts": "^1.1.4", 51 | "rollup": "^0.64.1", 52 | "rollup-plugin-babel": "^3.0.7", 53 | "rollup-plugin-commonjs": "^9.1.3", 54 | "rollup-plugin-json": "^4.0.0", 55 | "rollup-plugin-node-resolve": "^3.4.0", 56 | "rollup-plugin-peer-deps-external": "^2.2.0", 57 | "rollup-plugin-postcss": "^1.6.2", 58 | "rollup-plugin-url": "^1.4.0" 59 | }, 60 | "files": [ 61 | "dist" 62 | ], 63 | "dependencies": { 64 | "axios": "^0.18.0", 65 | "react-feather": "^1.1.6", 66 | "underscore": "^1.9.1" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/react-hapi-jwt-auth/rollup.config.js: -------------------------------------------------------------------------------- 1 | import babel from 'rollup-plugin-babel' 2 | import commonjs from 'rollup-plugin-commonjs' 3 | import external from 'rollup-plugin-peer-deps-external' 4 | import postcss from 'rollup-plugin-postcss' 5 | import resolve from 'rollup-plugin-node-resolve' 6 | import url from 'rollup-plugin-url' 7 | import svgr from '@svgr/rollup' 8 | 9 | import pkg from './package.json' 10 | 11 | import json from 'rollup-plugin-json' 12 | import rollupNodeResolve from 'rollup-plugin-node-resolve' 13 | 14 | export default { 15 | input: 'src/index.js', 16 | external: ['react-redux', 'react-router-dom', 'react-bootstrap'], 17 | output: [ 18 | { 19 | file: pkg.main, 20 | format: 'cjs', 21 | sourcemap: true 22 | }, 23 | { 24 | file: pkg.module, 25 | format: 'es', 26 | sourcemap: true 27 | } 28 | ], 29 | plugins: [ 30 | external(), 31 | postcss({ 32 | modules: true 33 | }), 34 | url(), 35 | svgr(), 36 | babel({ 37 | exclude: 'node_modules/**', 38 | plugins: [ 'external-helpers' ] 39 | }), 40 | resolve(), 41 | commonjs(), 42 | rollupNodeResolve({ jsnext: true, preferBuiltins: true, browser: true }), 43 | json() 44 | ] 45 | } 46 | -------------------------------------------------------------------------------- /src/react-hapi-jwt-auth/src/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "jest": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/react-hapi-jwt-auth/src/index.js: -------------------------------------------------------------------------------- 1 | 2 | export {default as JWTAuth} from './jwt-auth' 3 | export {default as JWTAuthService} from './jwt-auth-service'; 4 | export {default as JWTAuthInterceptor} from './jwt-auth-interceptor'; 5 | export {default as jwtAuthReducer} from './jwt-auth-reducer' 6 | export {default as JWTAuthUsers} from './jwt-auth-users' 7 | export {default as JWTAuthProfile} from './jwt-auth-profile' 8 | -------------------------------------------------------------------------------- /src/react-hapi-jwt-auth/src/jwt-auth-interceptor.js: -------------------------------------------------------------------------------- 1 | 2 | export default class JWTAuthInterceptor{ 3 | 4 | constructor(){ 5 | this.http = {}; 6 | } 7 | 8 | setHttp(http){ 9 | this.http = http; 10 | } 11 | 12 | update(){ 13 | if(this.http.interceptors){ 14 | this.http.interceptors.request.use(function (config) { 15 | // do something on success 16 | var token = localStorage.getItem('clusterpost_token'); 17 | if(token){ 18 | config.headers.authorization = "Bearer " + token; 19 | } 20 | return config; 21 | }, function (error) { 22 | // Do something with request error 23 | return Promise.reject(error); 24 | }); 25 | 26 | // Add a response interceptor 27 | this.http.interceptors.response.use(function (response) { 28 | // Do something with response data 29 | return response; 30 | }, function (error) { 31 | // Do something with response error 32 | return Promise.reject(error); 33 | }); 34 | } 35 | } 36 | 37 | } -------------------------------------------------------------------------------- /src/react-hapi-jwt-auth/src/jwt-auth-profile.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import PropTypes from 'prop-types'; 4 | 5 | import JWTAuthService from './jwt-auth-service'; 6 | 7 | import _ from 'underscore'; 8 | 9 | import { connect } from "react-redux"; 10 | import { withRouter } from 'react-router-dom'; 11 | import {UserCheck, User} from 'react-feather'; 12 | 13 | class JWTAuthProfile extends Component { 14 | 15 | constructor(props) { 16 | super(props); 17 | 18 | this.state = { 19 | user: {}, 20 | btnClass: "btn btn-primary", 21 | updating: false 22 | }; 23 | 24 | } 25 | 26 | componentDidMount(){ 27 | this.jwtauth = new JWTAuthService(); 28 | this.jwtauth.setHttp(this.props.http); 29 | 30 | const self = this; 31 | this.jwtauth.getUser() 32 | .then(function(res){ 33 | self.setState({...self.state, user: res}) 34 | }) 35 | } 36 | 37 | componentWillReceiveProps(newProps){ 38 | if(newProps.user != this.props.user){ 39 | this.setState({user: newProps.user }) 40 | } 41 | } 42 | 43 | hasScope(user, scope){ 44 | return user.scope.indexOf(scope) >= 0; 45 | } 46 | 47 | getUserPermissions(user){ 48 | return _.map(user.scope, function(sc){ 49 | return
  • {sc}
  • 50 | }) 51 | } 52 | 53 | updateUser(){ 54 | const {user} = this.state; 55 | const self = this; 56 | delete user.scope; 57 | this.jwtauth.updateSelf(user) 58 | .then(function(res){ 59 | self.setState({...self.state, btnClass:"btn btn-success", updating: true}); 60 | setTimeout(function(){ 61 | self.setState({...self.state, btnClass:"btn btn-primary", updating: false}); 62 | }, 3000); 63 | return self.jwtauth.getUser(); 64 | }) 65 | .then(function(res){ 66 | self.props.userFactory(res); 67 | }); 68 | 69 | } 70 | 71 | updateUserName(e){ 72 | if(e && e.target){ 73 | const {user} = this.state; 74 | user.name = e.target.value; 75 | this.setState({...this, user: user}); 76 | } 77 | } 78 | 79 | render() { 80 | 81 | var {user, btnClass, updating} = this.state; 82 | 83 | const self = this; 84 | 85 | return (
    86 |

    User Profile

    87 |
    88 |
    89 |
    90 | 91 |
    92 | 93 |
    94 |
    95 |
    96 | 97 |
    98 | 99 |
    100 |
    101 |
    102 | 103 |
    104 |
      105 | {self.getUserPermissions(user)} 106 |
    107 |
    108 |
    109 |
    110 |
    111 |
    112 |
    113 | 114 |
    115 |
    116 |
    117 |
    118 |
    119 | ); 120 | } 121 | } 122 | 123 | const mapStateToProps = (state, ownProps) => { 124 | return { 125 | http: state.jwtAuthReducer.http, 126 | user: state.jwtAuthReducer.user 127 | } 128 | } 129 | 130 | const mapDispatchToProps = (dispatch) => { 131 | return { 132 | userFactory: user => { 133 | dispatch({ 134 | type: 'user-factory', 135 | user: user 136 | }); 137 | } 138 | } 139 | } 140 | 141 | export default withRouter(connect(mapStateToProps, mapDispatchToProps)(JWTAuthProfile)); -------------------------------------------------------------------------------- /src/react-hapi-jwt-auth/src/jwt-auth-reducer.js: -------------------------------------------------------------------------------- 1 | 2 | const initialState = { 3 | http: {} 4 | }; 5 | 6 | const jwtAuthReducer = (state = initialState, action) => { 7 | switch (action.type) { 8 | case 'http-factory':{ 9 | return { 10 | ...state, 11 | http: action.http 12 | } 13 | } 14 | case 'user-factory':{ 15 | return { 16 | ...state, 17 | user: action.user 18 | } 19 | } 20 | default: { 21 | return state; 22 | } 23 | } 24 | }; 25 | 26 | export default jwtAuthReducer; -------------------------------------------------------------------------------- /src/react-hapi-jwt-auth/src/jwt-auth-service.js: -------------------------------------------------------------------------------- 1 | 2 | export default class JWTAuthService{ 3 | 4 | constructor(){ 5 | this.http = null; 6 | } 7 | 8 | setHttp(http){ 9 | this.http = http; 10 | } 11 | 12 | createUser(user){ 13 | return this.http({ 14 | method: 'POST', 15 | url: '/auth/user', 16 | data: user 17 | }) 18 | .then(function(res){ 19 | localStorage.setItem('clusterpost_token', res.data.token); 20 | }); 21 | } 22 | 23 | getUsers(){ 24 | return this.http({ 25 | method: 'GET', 26 | url: '/auth/users' 27 | }); 28 | } 29 | 30 | getUser(){ 31 | return this.http.get('/auth/user') 32 | .then(function(res){ 33 | var user = res.data; 34 | return user; 35 | }); 36 | } 37 | 38 | deleteSelf(){ 39 | return this.http({ 40 | method: 'DELETE', 41 | url: '/auth/user' 42 | }); 43 | } 44 | 45 | login(user){ 46 | return this.http({ 47 | method: 'POST', 48 | url: '/auth/login', 49 | data: user 50 | }) 51 | .then(function(res){ 52 | localStorage.setItem('clusterpost_token', res.data.token) 53 | return true; 54 | }); 55 | } 56 | 57 | updatePassword(password, token){ 58 | return this.http({ 59 | method: 'PUT', 60 | url: '/auth/login', 61 | data: password, 62 | headers: { 63 | authorization: "Bearer " + token 64 | } 65 | }) 66 | .then(function(res){ 67 | localStorage.setItem('clusterpost_token', res.data.token); 68 | }); 69 | } 70 | 71 | sendRecoverPassword(email){ 72 | return this.http({ 73 | method: 'POST', 74 | url: '/auth/reset', 75 | data: email 76 | }); 77 | } 78 | 79 | logout(){ 80 | localStorage.removeItem('clusterpost_token'); 81 | return Promise.resolve(); 82 | } 83 | 84 | updateUser(user){ 85 | return this.http({ 86 | method: 'PUT', 87 | url: '/auth/users', 88 | data: user 89 | }); 90 | } 91 | 92 | updateSelf(user){ 93 | return this.http({ 94 | method: 'PUT', 95 | url: '/auth/user', 96 | data: user 97 | }); 98 | } 99 | 100 | deleteUser(user){ 101 | return this.http({ 102 | method: 'DELETE', 103 | url: '/auth/users', 104 | data: user 105 | }); 106 | } 107 | 108 | getScopes(){ 109 | return this.http({ 110 | method: 'GET', 111 | url: '/auth/scopes' 112 | }); 113 | } 114 | 115 | updateScopes(scopes){ 116 | return this.http({ 117 | method: 'PUT', 118 | url: '/auth/scopes', 119 | data: scopes 120 | }); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/react-hapi-jwt-auth/src/styles.css: -------------------------------------------------------------------------------- 1 | /* add css styles here (optional) */ 2 | 3 | .test { 4 | display: inline-block; 5 | margin: 2em auto; 6 | border: 2px solid #000; 7 | font-size: 2em; 8 | } 9 | -------------------------------------------------------------------------------- /src/react-hapi-jwt-auth/src/test.js: -------------------------------------------------------------------------------- 1 | import ExampleComponent from './' 2 | 3 | describe('ExampleComponent', () => { 4 | it('is truthy', () => { 5 | expect(ExampleComponent).toBeTruthy() 6 | }) 7 | }) 8 | -------------------------------------------------------------------------------- /test/conf.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "uri": "http://localhost:8180", 3 | "couchdb": "http://localhost:5984/clusterjobs" 4 | } 5 | -------------------------------------------------------------------------------- /test/createNewJob.js: -------------------------------------------------------------------------------- 1 | 2 | var request = require('request'); 3 | var fs = require('fs'); 4 | var Promise = require('bluebird'); 5 | var path = require('path'); 6 | 7 | var job = { 8 | "executable": "ComponentSizeRLE", 9 | "parameters": [ 10 | { 11 | "flag": "--i", 12 | "name": "atlas_DD_040_t1w.nrrd" 13 | }, 14 | { 15 | "flag": "--m", 16 | "name": "DD_040_seg.nii.gz" 17 | }, 18 | { 19 | "flag": "--outputHistogram", 20 | "name": "outputHistogram.csv" 21 | }, 22 | { 23 | "flag": "--outputRLE", 24 | "name": "outputRLE.csv" 25 | } 26 | ], 27 | "inputs": [ 28 | { 29 | "name": "atlas_DD_040_t1w.nrrd" 30 | }, 31 | { 32 | "name": "DD_040_seg.nii.gz" 33 | } 34 | ], 35 | "outputs": [ 36 | { 37 | "type": "file", 38 | "name": "outputHistogram.csv" 39 | }, 40 | { 41 | "type": "file", 42 | "name": "outputRLE.csv" 43 | } 44 | ], 45 | "type": "job", 46 | "userEmail": "juanprietob@gmail.com", 47 | "executionserver" : "testserver" 48 | }; 49 | 50 | 51 | var inputs = [ 52 | "/tools/atlas/canine/legMuscle/atlas_image/atlas_DD_040_t1w.nrrd", 53 | "/tools/atlas/canine/legMuscle/atlas_image/segmentation/DD_040_seg.nii.gz" 54 | ]; 55 | 56 | var uploadfile = function(params){ 57 | 58 | var filename = params.filename; 59 | var id = params.id; 60 | 61 | return new Promise(function(resolve, reject){ 62 | 63 | try{ 64 | var options = { 65 | url : "http://localhost:8180/dataprovider/" + id + "/" + path.basename(filename), 66 | method: "PUT", 67 | headers:{ 68 | "Content-Type": "application/octet-stream" 69 | } 70 | } 71 | 72 | var stream = fs.createReadStream(filename); 73 | 74 | stream.pipe(request(options, function(err, res, body){ 75 | if(err) resolve(err); 76 | 77 | resolve(body); 78 | 79 | }) 80 | ); 81 | }catch(e){ 82 | reject(e); 83 | } 84 | 85 | }); 86 | } 87 | 88 | var options = { 89 | url : "http://localhost:8180/dataprovider", 90 | method: "POST", 91 | json: job 92 | } 93 | 94 | request(options, function(err, res, body){ 95 | if(err) throw err; 96 | 97 | var doc = body; 98 | var params = []; 99 | for(var i = 0; i < inputs.length; i++){ 100 | params.push({ 101 | filename: inputs[i], 102 | id: doc.id 103 | }); 104 | } 105 | 106 | Promise.map(params, uploadfile, {concurrency: 1}) 107 | .then(function(allupload){ 108 | console.log(allupload); 109 | }) 110 | .catch(console.error); 111 | 112 | 113 | 114 | }); -------------------------------------------------------------------------------- /test/createNewJobOuputDir.js: -------------------------------------------------------------------------------- 1 | 2 | var request = require('request'); 3 | var fs = require('fs'); 4 | var Promise = require('bluebird'); 5 | var path = require('path'); 6 | 7 | var job = { 8 | "executable": "ComponentSizeRLE", 9 | "parameters": [ 10 | { 11 | "flag": "--i", 12 | "name": "atlas_DD_040_t1w.nrrd" 13 | }, 14 | { 15 | "flag": "--m", 16 | "name": "DD_040_seg.nii.gz" 17 | }, 18 | { 19 | "flag": "--outputHistogram", 20 | "name": "outputHistogram.csv" 21 | }, 22 | { 23 | "flag": "--outputRLE", 24 | "name": "outputRLE.csv" 25 | } 26 | ], 27 | "inputs": [ 28 | { 29 | "name": "atlas_DD_040_t1w.nrrd" 30 | }, 31 | { 32 | "name": "DD_040_seg.nii.gz" 33 | } 34 | ], 35 | "outputs": [ 36 | { 37 | "type": "file", 38 | "name": "outputHistogram.csv" 39 | }, 40 | { 41 | "type": "file", 42 | "name": "outputRLE.csv" 43 | }, 44 | { 45 | "type": "directory", 46 | "name": "cwd" 47 | } 48 | ], 49 | "type": "job", 50 | "userEmail": "juanprietob@gmail.com", 51 | "executionserver" : "testserver" 52 | }; 53 | 54 | 55 | var inputs = [ 56 | "/Users/prieto/NetBeansProjects/UNC/data/canine/atlas_DD_040_t1w.nrrd", 57 | "/Users/prieto/NetBeansProjects/UNC/data/canine/DD_040_seg.nii.gz" 58 | ]; 59 | 60 | var uploadfile = function(params){ 61 | 62 | var filename = params.filename; 63 | var id = params.id; 64 | 65 | return new Promise(function(resolve, reject){ 66 | 67 | try{ 68 | var options = { 69 | url : "http://localhost:8180/dataprovider/" + id + "/" + path.basename(filename), 70 | method: "PUT", 71 | headers:{ 72 | "Content-Type": "application/octet-stream" 73 | } 74 | } 75 | 76 | var stream = fs.createReadStream(filename); 77 | 78 | stream.pipe(request(options, function(err, res, body){ 79 | if(err) resolve(err); 80 | 81 | resolve(body); 82 | 83 | }) 84 | ); 85 | }catch(e){ 86 | reject(e); 87 | } 88 | 89 | }); 90 | } 91 | 92 | var options = { 93 | url : "http://localhost:8180/dataprovider", 94 | method: "POST", 95 | json: job 96 | } 97 | 98 | request(options, function(err, res, body){ 99 | if(err) throw err; 100 | 101 | var doc = body; 102 | var params = []; 103 | for(var i = 0; i < inputs.length; i++){ 104 | params.push({ 105 | filename: inputs[i], 106 | id: doc.id 107 | }); 108 | } 109 | 110 | Promise.map(params, uploadfile, {concurrency: 1}) 111 | .then(function(allupload){ 112 | console.log(allupload); 113 | }) 114 | .catch(console.error); 115 | 116 | }); -------------------------------------------------------------------------------- /test/createNewJobRemoteData.js: -------------------------------------------------------------------------------- 1 | 2 | var request = require('request'); 3 | var fs = require('fs'); 4 | var Promise = require('bluebird'); 5 | var path = require('path'); 6 | 7 | var job = { 8 | "executable": "ComponentSizeRLE", 9 | "parameters": [ 10 | { 11 | "flag": "--i", 12 | "name": "atlas_DD_040_t1w.nrrd" 13 | }, 14 | { 15 | "flag": "--m", 16 | "name": "DD_040_seg.nii.gz" 17 | }, 18 | { 19 | "flag": "--outputHistogram", 20 | "name": "outputHistogram.csv" 21 | }, 22 | { 23 | "flag": "--outputRLE", 24 | "name": "outputRLE.csv" 25 | } 26 | ], 27 | "inputs": [ 28 | { 29 | "name": "atlas_DD_040_t1w.nrrd" 30 | }, 31 | { 32 | "name": "DD_040_seg.nii.gz", 33 | "remote" : { 34 | "serverCodename" : "COUCHDB", 35 | "uri" : "52f388e8f3e8045a9f6260e86c0f6c4a/52f388e8f3e8045a9f6260e86c0f6c4a.nii.gz" 36 | } 37 | } 38 | ], 39 | "outputs": [ 40 | { 41 | "type": "file", 42 | "name": "outputHistogram.csv" 43 | }, 44 | { 45 | "type": "file", 46 | "name": "outputRLE.csv" 47 | } 48 | ], 49 | "type": "job", 50 | "userEmail": "juanprietob@gmail.com", 51 | "executionserver" : "killdevil" 52 | }; 53 | 54 | 55 | var inputs = [ 56 | "/Users/prieto/NetBeansProjects/UNC/data/canine/atlas_DD_040_t1w.nrrd" 57 | ]; 58 | 59 | var uploadfile = function(params){ 60 | 61 | var filename = params.filename; 62 | var id = params.id; 63 | 64 | return new Promise(function(resolve, reject){ 65 | 66 | try{ 67 | var options = { 68 | url : "http://localhost:8180/dataprovider/" + id + "/" + path.basename(filename), 69 | method: "PUT", 70 | headers:{ 71 | "Content-Type": "application/octet-stream" 72 | } 73 | } 74 | 75 | var stream = fs.createReadStream(filename); 76 | 77 | stream.pipe(request(options, function(err, res, body){ 78 | if(err) resolve(err); 79 | 80 | resolve(body); 81 | 82 | }) 83 | ); 84 | }catch(e){ 85 | reject(e); 86 | } 87 | 88 | }); 89 | } 90 | 91 | var options = { 92 | url : "http://localhost:8180/dataprovider", 93 | method: "POST", 94 | json: job 95 | } 96 | 97 | request(options, function(err, res, body){ 98 | if(err) throw err; 99 | 100 | var doc = body; 101 | var params = []; 102 | for(var i = 0; i < inputs.length; i++){ 103 | params.push({ 104 | filename: inputs[i], 105 | id: doc.id 106 | }); 107 | } 108 | 109 | Promise.map(params, uploadfile, {concurrency: 1}) 110 | .then(function(allupload){ 111 | console.log(allupload); 112 | }) 113 | .catch(console.error); 114 | 115 | 116 | 117 | }); -------------------------------------------------------------------------------- /test/data/gravitational-waves-simulation.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juanprietob/clusterpost/0c50e24c70724cd4e7aa877ec1afa68f7e8e729e/test/data/gravitational-waves-simulation.jpg -------------------------------------------------------------------------------- /test/downloadTokenTest.js: -------------------------------------------------------------------------------- 1 | 2 | var request = require('request'); 3 | var fs = require('fs'); 4 | var Promise = require('bluebird'); 5 | var path = require('path'); 6 | var _ = require('underscore'); 7 | var qs = require('querystring'); 8 | 9 | const Joi = require('@hapi/joi'); 10 | const Lab = require('lab'); 11 | const lab = exports.lab = Lab.script(); 12 | 13 | var clusterpost = require("clusterpost-lib"); 14 | var clustermodel = require('clusterpost-model'); 15 | 16 | var inputs = [ 17 | "./data/gravitational-waves-simulation.jpg", 18 | "./data/gravitational-waves-simulation.jpg", 19 | "./data/gravitational-waves-simulation.jpg" 20 | ]; 21 | var names = [ 22 | "./data/folder/1.jpg", 23 | "./data/folder/2.jpg", 24 | "./data/folder/folder1/3.jpg" 25 | ]; 26 | var jobid; 27 | 28 | var joiokres = Joi.object().keys({ 29 | ok: Joi.boolean().valid(true), 30 | id: Joi.string(), 31 | rev: Joi.string() 32 | }); 33 | 34 | var job = { 35 | "executable": "cksum", 36 | "parameters": [ 37 | { 38 | "flag": "", 39 | "name": "./data/folder/1.jpg" 40 | } 41 | ], 42 | "inputs": [ 43 | { 44 | "name": "./data/folder/1.jpg" 45 | }, 46 | { 47 | "name": "./data/folder/folder1/3.jpg" 48 | } 49 | ], 50 | "outputs": [ 51 | { 52 | "type": "directory", 53 | "name": "./" 54 | }, 55 | { 56 | "type": "tar.gz", 57 | "name": "./" 58 | }, 59 | { 60 | "type": "file", 61 | "name": "stdout.out" 62 | }, 63 | { 64 | "type": "file", 65 | "name": "stderr.err" 66 | } 67 | ], 68 | "type": "job" 69 | }; 70 | 71 | var configfile = './conf.test.execution.json'; 72 | 73 | lab.experiment("Test clusterpost", function(){ 74 | 75 | 76 | lab.test('returns true when starts', function(){ 77 | 78 | return clusterpost.start(configfile) 79 | .then(function(){ 80 | return true; 81 | }) 82 | 83 | }); 84 | 85 | lab.test('returns true job is created', function(){ 86 | 87 | return clusterpost.createAndSubmitJob(job, inputs, names) 88 | .then(function(id){ 89 | Joi.assert(id, Joi.string()); 90 | jobid = id; 91 | }) 92 | 93 | }); 94 | 95 | 96 | lab.test('returns true if get attachment output stream is valid using a download token', function(){ 97 | 98 | return clusterpost.getDownloadToken(jobid, "./data/folder/folder1/3.jpg") 99 | .then(function(res){ 100 | Joi.assert(res.token, Joi.string()); 101 | return clusterpost.downloadAttachment(res.token); 102 | }); 103 | }); 104 | 105 | lab.test('returns true if job is downloaded', function(){ 106 | return clusterpost.downloadJob(jobid, 'temp.tar.gz') 107 | .then(function(status){ 108 | console.log(status); 109 | }); 110 | }); 111 | 112 | lab.test('returns true if the document is deleted', function(){ 113 | return clusterpost.deleteJob(jobid) 114 | .then(function(res){ 115 | Joi.assert(res.status, Joi.string().valid("DELETE")); 116 | }); 117 | }); 118 | 119 | }); -------------------------------------------------------------------------------- /test/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "clusterpost-test", 3 | "version": "1.0.0", 4 | "description": "Test the API of clusterpost", 5 | "main": "test.js", 6 | "dependencies": { 7 | "@hapi/joi": "^15.0.3", 8 | "bluebird": "^3.5.4", 9 | "joi": "^14.3.1", 10 | "lab": "^18.0.2", 11 | "request": "^2.88.0", 12 | "underscore": "^1.9.1" 13 | }, 14 | "scripts": { 15 | "test": "lab -v -m 120000 test.js", 16 | "testes": "lab -v -m 120000 tokensTest.js", 17 | "testcmd": "lab -v -m 120000 createJobsTest.js", 18 | "testdtoken": "lab -v -m 120000 downloadTokenTest.js", 19 | "testundef": "lab -v -m 120000 undefinedTest.js" 20 | }, 21 | "keywords": [ 22 | "test" 23 | ], 24 | "author": "juanprietob@gmail.com", 25 | "license": "Apache-2.0", 26 | "devDependencies": { 27 | "prompt": "^1.0.0" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /test/tokensTest.js: -------------------------------------------------------------------------------- 1 | 2 | var request = require('request'); 3 | var fs = require('fs'); 4 | var Promise = require('bluebird'); 5 | var path = require('path'); 6 | var _ = require('underscore'); 7 | var qs = require('querystring'); 8 | 9 | const Joi = require('@hapi/joi'); 10 | const Lab = require('lab'); 11 | const lab = exports.lab = Lab.script(); 12 | 13 | var clusterpost = require("clusterpost-lib"); 14 | var clustermodel = require('clusterpost-model'); 15 | var prompt = require('prompt'); 16 | 17 | configfile = './conf.test.execution.json'; 18 | 19 | lab.experiment("Test clusterpost", function(){ 20 | 21 | lab.test('returns true when clusterpost starts as regular user', function(){ 22 | return clusterpost.start(configfile) 23 | .then(function(res){ 24 | return true; 25 | }); 26 | }); 27 | 28 | lab.test('returns true if tokens are not fetch as regular user', function(){ 29 | return clusterpost.getExecutionServerToken() 30 | .then(function(res){ 31 | Joi.assert(res.statusCode, 403); 32 | }); 33 | }); 34 | 35 | lab.test('returns true if token is fetch as admin user', function(){ 36 | return clusterpost.promptUsernamePassword() 37 | .then(function(user){ 38 | return clusterpost.userLogin(user) 39 | }) 40 | .then(function(){ 41 | return new Promise(function(resolve, reject){ 42 | schema = { 43 | properties: { 44 | executionserver: { 45 | message: 'Type the name of the execution server in the hapi configuration', 46 | required: true 47 | } 48 | } 49 | }; 50 | prompt.start(); 51 | prompt.get(schema, function (err, result) { 52 | if(err){ 53 | reject(err) 54 | }else{ 55 | resolve(result); 56 | } 57 | }); 58 | }); 59 | }) 60 | .then(function(res){ 61 | return clusterpost.getExecutionServerToken(res); 62 | }) 63 | .then(function(res){ 64 | Joi.assert(res, Joi.array().items(clustermodel.executionservertoken)); 65 | }); 66 | }); 67 | }); -------------------------------------------------------------------------------- /test/userAuthTest.js: -------------------------------------------------------------------------------- 1 | 2 | var request = require('request'); 3 | var fs = require('fs'); 4 | var Promise = require('bluebird'); 5 | var path = require('path'); 6 | 7 | const Joi = require('@hapi/joi'); 8 | const Lab = require('lab'); 9 | const lab = exports.lab = Lab.script(); 10 | const clustermodel = require("clusterpost-model"); 11 | const clusterpost = require("clusterpost-lib"); 12 | 13 | const getConfigFile = function (env, base_directory) { 14 | try { 15 | // Try to load the user's personal configuration file 16 | return require(base_directory + '/conf.my.' + env + '.json'); 17 | } catch (e) { 18 | // Else, read the default configuration file 19 | return require(base_directory + '/conf.' + env + '.json'); 20 | } 21 | }; 22 | 23 | 24 | var env = process.env.NODE_ENV; 25 | 26 | if(!env) throw "Please set NODE_ENV variable."; 27 | 28 | var conf = getConfigFile(env, "./"); 29 | 30 | var agentOptions = {}; 31 | 32 | if(conf.tls && conf.tls.cert){ 33 | agentOptions.ca = fs.readFileSync(conf.tls.cert); 34 | } 35 | 36 | var getClusterPostServer = function(){ 37 | return conf.uri 38 | } 39 | 40 | clusterpost.setClusterPostServer(conf.uri); 41 | 42 | var joiokres = Joi.object().keys({ 43 | ok: Joi.boolean().valid(true), 44 | id: Joi.string(), 45 | rev: Joi.string() 46 | }); 47 | 48 | lab.experiment("Test clusterpost auth jwt", function(){ 49 | 50 | var user = { 51 | email: "algiedi85@gmail.com", 52 | name: "Alpha Capricorni", 53 | password: "Some808Password!" 54 | } 55 | 56 | lab.test('returns true when new user is created.', function(){ 57 | 58 | return clusterpost.createUser(user) 59 | .then(function(res){ 60 | Joi.assert(res.token, Joi.string().required()); 61 | }); 62 | 63 | }); 64 | 65 | lab.test('returns true if same user fails to be created.', function(){ 66 | return clusterpost.createUser(user) 67 | .then(function(res){ 68 | Joi.assert(res.token, Joi.object().keys({ 69 | statusCode: Joi.number().valid(409), 70 | error: Joi.string(), 71 | message: Joi.string() 72 | })); 73 | }); 74 | }); 75 | 76 | var token = ""; 77 | 78 | lab.test('returns true when user is login.', function(){ 79 | 80 | var user = { 81 | email: "algiedi85@gmail.com", 82 | password: "Some808Password!" 83 | } 84 | 85 | return clusterpost.userLogin(user) 86 | .then(function(res){ 87 | Joi.assert(res.token, Joi.string().required()) 88 | console.log(res.token); 89 | token = "Bearer " + res.token; 90 | }); 91 | 92 | }); 93 | 94 | lab.test('returns true when unauthorized user access api.', function(){ 95 | 96 | return clusterpost.getExecutionServers() 97 | .then(function(res){ 98 | Joi.assert(res, Joi.object().keys({ 99 | statusCode: Joi.number().valid(403), 100 | error: Joi.string(), 101 | message: Joi.string() 102 | })); 103 | }); 104 | 105 | }); 106 | 107 | lab.test('returns true when authorized user access api.', function(){ 108 | 109 | return clusterpost.getUser() 110 | .then(function(res){ 111 | 112 | Joi.assert(res, Joi.object().keys({ 113 | _id: Joi.string(), 114 | _rev: Joi.string(), 115 | name: Joi.string(), 116 | email: Joi.string().email(), 117 | type: Joi.string(), 118 | scope: Joi.array().items(Joi.string()) 119 | })); 120 | }); 121 | 122 | }); 123 | 124 | lab.test('returns true when valid user deletes itself.', function(){ 125 | 126 | return clusterpost.deleteUser() 127 | .then(function(res){ 128 | Joi.assert(res, joiokres); 129 | }); 130 | 131 | }); 132 | 133 | 134 | }); -------------------------------------------------------------------------------- /test/userResetTest.js: -------------------------------------------------------------------------------- 1 | 2 | var request = require('request'); 3 | var fs = require('fs'); 4 | var Promise = require('bluebird'); 5 | var path = require('path'); 6 | 7 | const Joi = require('@hapi/joi'); 8 | const Lab = require('lab'); 9 | const lab = exports.lab = Lab.script(); 10 | 11 | const getConfigFile = function (env, base_directory) { 12 | try { 13 | // Try to load the user's personal configuration file 14 | return require(base_directory + '/conf.my.' + env + '.json'); 15 | } catch (e) { 16 | // Else, read the default configuration file 17 | return require(base_directory + '/conf.' + env + '.json'); 18 | } 19 | }; 20 | 21 | 22 | var env = process.env.NODE_ENV; 23 | 24 | if(!env) throw "Please set NODE_ENV variable."; 25 | 26 | var conf = getConfigFile(env, "./"); 27 | 28 | var agentOptions = {}; 29 | 30 | if(conf.tls && conf.tls.cert){ 31 | agentOptions.ca = fs.readFileSync(conf.tls.cert); 32 | } 33 | 34 | var getClusterPostServer = function(){ 35 | return conf.uri 36 | } 37 | 38 | var joiokres = Joi.object().keys({ 39 | ok: Joi.boolean().valid(true), 40 | id: Joi.string(), 41 | rev: Joi.string() 42 | }); 43 | 44 | var resetPassword = function(user){ 45 | return new Promise(function(resolve, reject){ 46 | var options = { 47 | url: getClusterPostServer() + "/auth/reset", 48 | method: 'POST', 49 | json: user, 50 | agentOptions: agentOptions 51 | } 52 | 53 | request(options, function(err, res, body){ 54 | if(err){ 55 | reject(err); 56 | }else{ 57 | resolve(body); 58 | } 59 | }); 60 | }); 61 | } 62 | 63 | lab.experiment("Test clusterpost auth jwt", function(){ 64 | 65 | var user = { 66 | email: "algiedi85@gmail.com" 67 | } 68 | 69 | 70 | lab.test('returns true when an email is sent to the user with a link to reset the password', function(){ 71 | 72 | return resetPassword(user) 73 | .then(function(res){ 74 | console.log(res); 75 | }); 76 | 77 | }); 78 | 79 | }); --------------------------------------------------------------------------------