├── .gitignore ├── DBOperations ├── DBCommandLineTool.py └── DBInsertion.py ├── DBScript ├── CMD2WEB.sqlite └── DBSetup.sql ├── LICENSE ├── README.md ├── docs ├── cmd2web.js ├── simple.html └── site.css ├── ex_configs ├── apache_conf.yaml ├── example_config.yaml ├── sample_grep_config.json └── tabix_config.json ├── flaskapp.wsgi ├── requirements.txt ├── src ├── UploadFileTool.py ├── client.py ├── cmd2web.py ├── database_connection.py ├── server.py ├── settings.py ├── stix_client.py └── tabix_client.py ├── test ├── data │ ├── bad.json │ ├── test_commas.txt │ ├── test_config.json │ └── test_tabs.txt └── func │ └── cmd2web_test.sh └── web_client ├── cmd2web.js ├── simple.html └── site.css /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | -------------------------------------------------------------------------------- /DBOperations/DBCommandLineTool.py: -------------------------------------------------------------------------------- 1 | import click 2 | from flask import Flask,request 3 | import os 4 | import random 5 | from DBInsertion import DBInsertion 6 | from datetime import datetime,timedelta 7 | current_directory= os.path.dirname(__file__) 8 | database_file_path = os.path.join(current_directory, "../DBScript/CMD2WEB.sqlite") 9 | database_object = DBInsertion(database_file_path) 10 | @click.group() 11 | # @click.option('-i', '--input', type=click.File('r')) 12 | def cli(): 13 | """Command line interface for database interaction.""" 14 | pass 15 | 16 | 17 | @cli.command() 18 | @click.option('--gname', help='The group name.') 19 | @click.option('--gtype', help='Type of the group.') 20 | @click.option('--restricted', help='If the group is restricted group. 1 if group is restricted ,0 if not restricted.') 21 | def createGroup(gname, gtype,restricted): 22 | """Description:Create a new group. \n 23 | Input parameters required: \n 24 | - gname - Group Name \n 25 | - gytpe - Group Type \n 26 | - restricted - Boolean indicating whether group is restricted \n 27 | Example Usage: python DBCommandLineTool.py creategroup --gname=DummyGroup --gtype=Test --restricted=1 """ 28 | group_name = gname 29 | group_type = gtype 30 | 31 | if (group_name != None and restricted != None): 32 | # insert 33 | print(group_name, group_type, restricted) 34 | database_object.insert_group(group_name, group_type, restricted) 35 | print("Group {0} created".format(group_name)) 36 | 37 | else: 38 | print(group_name, group_type, restricted) 39 | print("Parameter missing") 40 | # click.echo('Hello %s! - %s! - %d' % gname, gtype,restricted) 41 | 42 | 43 | @cli.command() 44 | @click.option('--gid', help='The group id.') 45 | @click.option('--token', help='Token for the user associated to a group.Format (mm-dd-yyyy).') 46 | @click.option('--expiry', help='Expiry date for the token.') 47 | @click.option('--email', help='Email id of the user.') 48 | def createKeyByGroupID(gid, token, expiry, email): 49 | """Description:Create new token by group id.\n 50 | Input parameters required: \n 51 | - gid - Group ID \n 52 | - token - Token for the user \n 53 | - expiry - Token expiry date \n 54 | - email - Email id of the user\n 55 | Example Usage: python DBCommandLineTool.py createkeybygroupid --gid=9 --token=122344434 --expiry=04-27-2019 --email=ro@colorado.edu""" 56 | 57 | group_id = gid 58 | token = token 59 | expiry = expiry 60 | user_email = email 61 | if(expiry==None): 62 | expiry = getNewDate() 63 | 64 | if(token==None): 65 | token = generateNewToken() 66 | 67 | if (group_id != None and token != None and expiry != None and user_email != None): 68 | database_object.insert_key(group_id, token, expiry, user_email) 69 | print("Token:{0} inserted for the user:{1} with expiry:{2}".format(token, user_email, expiry)) 70 | else: 71 | print("Parameter missing") 72 | # click.echo('Hello %s! - %s! - %s! - %s!' % gid, token, expiry, email) 73 | 74 | 75 | # Generate new date 76 | def getNewDate(): 77 | newdate = datetime.now() + timedelta(days=365) 78 | expiry = newdate.strftime('%m-%d-%Y') 79 | return expiry 80 | 81 | # Generate new random token 82 | def generateNewToken(): 83 | new_token= random.randint(10000000,99999999) 84 | if(database_object.check_token_exists(new_token)): 85 | return generateNewToken() 86 | else: 87 | return new_token 88 | 89 | @cli.command() 90 | @click.option('--gname', help='The group name.') 91 | @click.option('--token', help='Token for the user associated to a group.') 92 | @click.option('--expiry', help='Expiry date for the token. Format (mm-dd-yyyy).') 93 | @click.option('--email', help='Email id of the user.') 94 | def createKeyByGroupName(gname, token, expiry, email): 95 | """Description:Create new token by group name. \n 96 | Input parameters required: \n 97 | - gname - Group Name \n 98 | - token - Token for the user \n 99 | - expiry - Token expiry date \n 100 | - email - Email id of the user\n 101 | Example Usage: python DBCommandLineTool.py createkeybygroupname --gname=DummyGroup --token=122344435 --expiry=05-27-2019 --email=rom@colorado.edu """ 102 | group_name = gname 103 | token = token 104 | expiry = expiry 105 | user_email = email 106 | if(expiry==None): 107 | expiry = getNewDate() 108 | 109 | if(token==None): 110 | token = generateNewToken() 111 | group_id = None 112 | if (group_name != None): 113 | # get group id 114 | group_id = database_object.get_group_name_from_id(group_name) 115 | else: 116 | return "No group name" 117 | if (group_id != None and token != None and expiry != None and user_email != None): 118 | database_object.insert_key(group_id, token, expiry, user_email) 119 | print("Token:{0} inserted for the user:{1} with expiry:{2}".format(token, user_email, expiry)) 120 | else: 121 | print("Parameter missing") 122 | 123 | @cli.command() 124 | @click.option('--gname', help='The group name.') 125 | def deleteGroup(gname): 126 | """Description:Delete group by name.\n 127 | Input parameters required: \n 128 | - gname - Group Name \n 129 | Example Usage: python DBCommandLineTool.py deletegroup --gname=DummyGroup""" 130 | group_name = gname 131 | if (group_name != None): 132 | database_object.delete_group(group_name) 133 | print("Deleted group {0}".format(group_name)) 134 | else: 135 | print("Check group info") 136 | # click.echo('Hello %s! - %s! - %s! - %s!' % gname, token, expiry, email) 137 | 138 | 139 | @cli.command() 140 | @click.option('--gname', help='The group name.') 141 | def deleteKeyByGroup(gname): 142 | """Description:Delete key by group name.\n 143 | Input parameters required: \n 144 | - gname - Group Name \n 145 | Example Usage: python DBCommandLineTool.py deletekeybygroup --gname=DummyGroup""" 146 | group_name = gname 147 | if (group_name != None): 148 | database_object.delete_group_keys(group_name) 149 | print("Deleted keys for group {0}".format(group_name)) 150 | else: 151 | print("Check group info") 152 | # click.echo('Hello %s! - %s! - %s! - %s!' % gname, token, expiry, email) 153 | 154 | @cli.command() 155 | @click.option('--email', help='The user email.') 156 | def deleteKeyByUser(email): 157 | """Description:Delete key by group user.\n 158 | Input parameters required: \n 159 | - email - email id of the user \n 160 | Example Usage: python DBCommandLineTool.py deletekeybyuser --email=rom@colorado.edu""" 161 | user_email = email 162 | if (user_email != None): 163 | database_object.delete_user_keys(user_email) 164 | print("Deleted keys for user {0}".format(user_email)) 165 | else: 166 | print("Check group info") 167 | # click.echo('Hello %s! - %s! - %s! - %s!' % gname, token, expiry, email) 168 | 169 | @cli.command() 170 | @click.option('--email', help='The user email.') 171 | def getKeyByUser(email): 172 | """Description:Get Keys by User.\n 173 | Input parameters required: \n 174 | - email - email id of the user \n 175 | Example Usage: python DBCommandLineTool.py getkeybyuser --email=rom2@colorado.edu""" 176 | user_email = email 177 | if (user_email != None): 178 | result = database_object.get_user_keys(user_email) 179 | print(result) 180 | else: 181 | print("Check group info") 182 | 183 | @cli.command() 184 | @click.option('--gname', help='The Group name.') 185 | def getKeyByGroupName(gname): 186 | """Description:Get keys by Group.\n 187 | Input parameters required: \n 188 | - gname - Group name \n 189 | Example Usage: python DBCommandLineTool.py getkeybygroupname --gname=DummyGroup""" 190 | group_name = gname 191 | if (group_name != None): 192 | result = database_object.get_user_keys_by_group_name(group_name) 193 | print(result) 194 | else: 195 | print("Check group info") 196 | 197 | @cli.command() 198 | def getGroupList(): 199 | """Description:Get all the groups.\n 200 | Input parameters required: \n 201 | None \n 202 | Example Usage: python DBCommandLineTool.py getgrouplist """ 203 | result = database_object.get_group_list() 204 | print(result) 205 | 206 | @cli.command() 207 | def getKeyList(): 208 | """Description:Get all the keys.\n 209 | Input parameters required: \n 210 | None \n 211 | Example Usage: python DBCommandLineTool.py getkeylist """ 212 | result = database_object.get_key_list() 213 | print(result) 214 | 215 | 216 | if __name__ == '__main__': 217 | cli() -------------------------------------------------------------------------------- /DBOperations/DBInsertion.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | import pandas as pd 3 | from sqlite3 import Error 4 | class DBInsertion: 5 | def __init__(self,db_file): 6 | try: 7 | self.conn = sqlite3.connect(db_file,check_same_thread=False) 8 | # return self.conn 9 | except Error as e: 10 | print(e) 11 | 12 | # Close connection when deconstructor is called. 13 | def __del__(self): 14 | self.conn.close() 15 | 16 | #Insert new group 17 | def insert_group(self,group_name,group_type,restricted): 18 | cur = self.conn.cursor() 19 | cur.execute("Insert into Groups(GroupName,GroupType,RestrictedGroup) values('{0}','{1}',{2})".format(group_name,group_type,restricted)) 20 | self.conn.commit() 21 | 22 | def get_group_name_from_id(self,gid): 23 | cur = self.conn.cursor() 24 | cur.execute("select GroupID from Groups where GroupName='{0}'".format(gid)) 25 | rows = cur.fetchall() 26 | if(len(rows) > 0): 27 | return rows[0][0] 28 | else: 29 | # //Error Record does not exist for the gid 30 | return "Group ID does not exist"; 31 | 32 | #Insert new key in the Keys table for a particular user with expiry date 33 | def insert_key(self,group_id,token,expiry,user_email): 34 | cur = self.conn.cursor() 35 | cur.execute("Insert into Keys(GroupID,Token,Expiry,UserEmail) values({0},'{1}','{2}','{3}')".format(group_id,token,expiry,user_email)) 36 | self.conn.commit() 37 | 38 | #Delete the group. Should keys be deleted for that group? 39 | def delete_group(self,group_name): 40 | cur = self.conn.cursor() 41 | cur.execute("Delete from Groups where GroupName='{0}'".format(group_name)) 42 | self.conn.commit() 43 | 44 | #Delete the keys belonging to a particular group 45 | def delete_group_keys(self,group_name): 46 | cur = self.conn.cursor() 47 | cur.execute("Delete from Keys where GroupID in (select GroupID from Groups where GroupName='{0}')".format(group_name)) 48 | self.conn.commit() 49 | 50 | #Delete the keys for a particular user 51 | def delete_user_keys(self,user_email): 52 | cur = self.conn.cursor() 53 | cur.execute("Delete from Keys where UserEmail ='{0}'".format(user_email)) 54 | self.conn.commit() 55 | 56 | # Get all keys for a user based on user id 57 | def get_user_keys(self, user_email): 58 | query="Select * from Keys where UserEmail ='{0}'".format(user_email) 59 | data = pd.read_sql_query(query, self.conn) 60 | return data 61 | # self.conn.commit() 62 | 63 | # Get all keys for a user based on group name 64 | def get_user_keys_by_group_name(self,group_name): 65 | query = "Select * from Keys where GroupID in (select GroupID from Groups where GroupName='{0}')".format(group_name) 66 | data = pd.read_sql_query(query, self.conn) 67 | return data 68 | 69 | # Get groups list 70 | def get_group_list(self): 71 | query = "Select * from Groups" 72 | data = pd.read_sql_query(query, self.conn) 73 | return data 74 | 75 | # Get keys list 76 | def get_key_list(self): 77 | query = "Select * from Keys" 78 | data = pd.read_sql_query(query, self.conn) 79 | return data 80 | 81 | # Check token 82 | def check_token_exists(self,token): 83 | cur = self.conn.cursor() 84 | query='select Token from Keys where Token={0}'.format(token) 85 | cur.execute(query) 86 | rows = cur.fetchall() 87 | if len(rows) > 0: 88 | return True 89 | else: 90 | return False -------------------------------------------------------------------------------- /DBScript/CMD2WEB.sqlite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ryanlayer/cmd2web/d8cc66d24909a73deda105377dceacad1d6568c2/DBScript/CMD2WEB.sqlite -------------------------------------------------------------------------------- /DBScript/DBSetup.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS Groups ( 2 | GroupID integer PRIMARY KEY, 3 | GroupName text, 4 | GroupType text, 5 | RestrictedGroup integer 6 | ); 7 | CREATE TABLE IF NOT EXISTS Keys ( 8 | GroupID integer, 9 | Token text, 10 | Expiry text, 11 | UserEmail text, 12 | FOREIGN KEY (GroupID) REFERENCES Groups (GroupID) 13 | ); -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Ryan Layer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | `cmd2web` is a simple framework for enabling a web interface to command 2 | line programs. From this interface, data and methods can be easily accessed 3 | through either a dynamic website or a programmatic interface. This package 4 | includes a server and client javascript and python interfaces. 5 | 6 | # Server 7 | 8 | To stand up a server, you define a mapping between command line parameters and 9 | URL query parameters in a JSON config file. Each command line instance is 10 | referred to as a service, and a server can host multiple services. 11 | 12 | In this example, we will consider a server with just one service to the `tabix` 13 | program (https://github.com/samtools/htslib) and the Repeat Masker track from 14 | UCSC genome browser (based on 15 | http://hgdownload.soe.ucsc.edu/goldenPath/hg19/database/rmsk.txt.gz ) 16 | 17 | ## Install Tools 18 | Installation Steps: 19 | Tabix and bgzip 20 | ``` 21 | Download latest tabix jar from the link:https://sourceforge.net/projects/samtools/files/tabix/ 22 | tar -xjf tabix-0.2.6.tar.bz2 23 | cd tabix-0.2.6/ 24 | make 25 | Make install 26 | ``` 27 | Bedtools: 28 | Run the following lines in the terminal to install bedtools: 29 | ``` 30 | Ubuntu 31 | apt-get install bedtools 32 | ``` 33 | ``` 34 | Mac 35 | brew tap homebrew/science 36 | brew install bedtools 37 | ``` 38 | 39 | 40 | After downloading just extract the .gz file and use head command to see content of the file: 41 | 42 | Extract the bgzip file: 43 | ``` 44 | bgzip -d rmsk.txt.gz 45 | Head rmsk.txt 46 | mv rmsk.txt rmsk.bed 47 | ``` 48 | For tabix to work start position and end position should be sorted: 49 | Here column 6 is the chromosome and column 7,8 is the starting and the ending position 50 | ``` 51 | sort -k6,6 -k7,7n -k8,8n rmsk.bed >> sortedrmsk.bed 52 | ``` 53 | Once the file is created, use bgzip to compress the .bed file: 54 | ``` 55 | Bgzip sortedrmsk.bed 56 | ``` 57 | It will create sortedrmsk.bed.gz file which can be used with the tabix. 58 | To create index file(.tbi) using tabix use the following command: 59 | ``` 60 | tabix -p bed sortedrmsk.bed.gz -s6 -b7 -e8 61 | ```` 62 | Parameters in the command: 63 | ``` 64 | -p bed - Tells about the type of input file 65 | Sortedrmsk.bed.gz - Input file 66 | -s6 - Tells about the column of the sequence (In this case 6th column) 67 | -b7 - Tells about the column having starting position of sequence ( In this case 7th column) 68 | -e8 - Tells about the column having ending position of sequence ( In this case 8th column) 69 | ``` 70 | To test from terminal, use the following command: 71 | ``` 72 | tabix sortedextrarmsk.bed.gz -s6 -b7 -e8 chr1:10000-20000 73 | ``` 74 | Currently the application assumes that the columns in data file starts with the sequence. To convert the downloaded file to that, run the following commands: 75 | ``` 76 | Extract the bgzip file: 77 | bgzip -d rmsk.txt.gz 78 | ``` 79 | Put columns in new file 80 | ``` 81 | cut -f6,7,8,9,10,11,12,13,14,15,16,17 rmsk.txt >> rmsk.bed 82 | ``` 83 | Sort the file 84 | ``` 85 | Sort -k1,1 -k2,2n -k3,3n rmsk.bed >> sortedrmsk.bed 86 | ``` 87 | Alternatively bedtools can be installed to sort the file: 88 | 89 | Run the following lines in the terminal to install bedtools: 90 | ``` 91 | Ubuntu 92 | apt-get install bedtools 93 | ``` 94 | ``` 95 | Mac 96 | brew tap homebrew/science 97 | brew install bedtools 98 | ``` 99 | Once it is installed, use the following command to sort the input file 100 | ``` 101 | sortBed -i rmsk.bed 102 | ``` 103 | Use bgzip to compress the file: 104 | ``` 105 | Bgzip -c rmsk.bed >> rmsk.bed.gz 106 | ``` 107 | Use tabix to generate .tbi file 108 | ``` 109 | Tabix -p bed rmsk.bed.gz 110 | ``` 111 | Use tabix to filter by chromosome: 112 | ``` 113 | Tabix rmsk.bed.gz chr1:10000-20000 114 | ``` 115 | From the `tabix` manual: 116 | ``` 117 | Tabix indexes a TAB-delimited genome position file in.tab.bgz and creates an 118 | index file ( in.tab.bgz.tbi or in.tab.bgz.csi ) when region is absent from the 119 | command-line. The input data file must be position sorted and compressed by 120 | bgzip which has a gzip(1) like interface. 121 | 122 | After indexing, tabix is able to quickly retrieve data lines overlapping 123 | regions specified in the format "chr:beginPos-endPos". (Coordinates specified 124 | in this region format are 1-based and inclusive.) 125 | ``` 126 | 127 | To use `tabix` on the command line, you pass the file name and the region of 128 | interest and all intervals in the file that overlap that region are returned. 129 | ``` 130 | $ tabix rmsk.bed.gz chr1:10000-20000 131 | chr1 10000 10468 (CCCTAA)n Simple_repeat Simple_repeat 132 | chr1 10468 11447 TAR1 Satellite telo 133 | chr1 11503 11675 L1MC LINE L1 134 | chr1 11677 11780 MER5B DNA hAT-Charlie 135 | chr1 15264 15355 MIR3 SINE MIR 136 | chr1 16712 16749 (TGG)n Simple_repeat Simple_repeat 137 | chr1 18906 19048 L2a LINE L2 138 | chr1 19947 20405 L3 LINE CR1 139 | ``` 140 | 141 | The URL for this service looks very similar to the command line invocation: 142 | ``` 143 | $ curl "http://127.0.0.1:8080/?service=rmsk&chromosome=chr1&start=10000&end=20000" 144 | { "success": 1, 145 | "result": [ 146 | [ "chr1", "10000", "10468", "(CCCTAA)n", "Simple_repeat", "Simple_repeat" ], 147 | [ "chr1", "10468", "11447", "TAR1", "Satellite", "telo" ], 148 | [ "chr1", "11503", "11675", "L1MC", "LINE", "L1" ], 149 | [ "chr1", "11677", "11780", "MER5B", "DNA", "hAT-Charlie" ], 150 | [ "chr1", "15264", "15355", "MIR3", "SINE", "MIR" ], 151 | [ "chr1", "16712", "16749", "(TGG)n", "Simple_repeat", "Simple_repeat" ], 152 | [ "chr1", "18906", "19048", "L2a", "LINE", "L2" ], 153 | [ "chr1", "19947", "20405", "L3", "LINE", "CR1" ] 154 | ] 155 | } 156 | ``` 157 | 158 | ## Server config 159 | Config file has been updated from json to yaml file. 160 | A skeleton of the config is: 161 | ``` 162 | - 163 | name : 164 | arguments : 165 | - 166 | name : 167 | fixed : 168 | type : 169 | value : 170 | command : 171 | - 172 | output : 173 | type : 174 | sep : 175 | ``` 176 | 177 | 178 | Each element in the config is described below. 179 | 180 | ### name 181 | Since a server can host many services, the first step is to define the service 182 | name. Requests will use the `service` URL query attribute to the name of the 183 | service. The server will return an exception for any requests that do not have 184 | service specified: 185 | ``` 186 | $ curl "http://127.0.0.1:8080/?chromosome=chr1&start=10000&end=20000" 187 | {"exception": "No service specified", "success": 0} 188 | ``` 189 | 190 | Here the name is `rmsk`: 191 | ``` 192 | "name" : "rmsk" 193 | ``` 194 | 195 | ### arguments 196 | 197 | This command has five arguments: 198 | 1. path to the executable (`tabix`) 199 | 2. file of interest (`rmsk.bed.gz`) 200 | 3. chromosome (`chr1`) 201 | 4. start (`10000`) 202 | 5. end (`20000`) 203 | 204 | Each of these attributes is specified in a `arguments` array. Attributes can 205 | be fixed (`"fixed" : "true"`) or variable ("fixed" : "false)". The value of 206 | the variable attributes will be defined in a users' query, and the fixed 207 | attributes are defined by the `value` field. Each attribute has a type that is 208 | checked by the server before executing any commands. Currently `string`, 209 | `integer`, and `float` are supported. 210 | 211 | The arguments array for this service in raw yaml is: 212 | 213 | ``` 214 | arguments : 215 | - 216 | name : file 217 | fixed : 'true' 218 | type : string 219 | value : /data/rmsk.bed.gz 220 | - 221 | name : chromosome 222 | fixed : 'false' 223 | type : string 224 | - 225 | name : start 226 | fixed : 'false' 227 | type : integer 228 | - 229 | name : end 230 | fixed : 'false' 231 | type : integer 232 | ``` 233 | 234 | 235 | When a user requests this service, the server fills a variable table with both 236 | fixed and variable attributes. The server will return an exception for requests 237 | that do not provide precisely the set of variable arguments. 238 | ``` 239 | $ curl "http://127.0.0.1:8080/?service=rmsk&chromosome=chr1&start=10000" 240 | {"exception": "Argument mismatch", "success": 0} 241 | ``` 242 | The server will also return an exception for requests with type mismatches. 243 | ``` 244 | $ curl "http://127.0.0.1:8080/?service=rmsk&chromosome=chr1&start=10000&end=not_int" 245 | {"exception": "Type mismatch for argument end. Expected integer", "success": 0} 246 | ``` 247 | 248 | ### command 249 | After the variable table is filled, the server will construct the command by 250 | replaced variables names with value in the variable table by the same name. 251 | Note that variables in the command start with '$', but the names of the 252 | attributes do not. To improve security, it is highly recommended to split the 253 | command into individual strings. 254 | ``` 255 | "command": 256 | [ 257 | "/usr/bin/tabix", 258 | "$file", 259 | "$chromosome:$start-$end" 260 | ], 261 | ``` 262 | 263 | The constructed command is then executed locally on the server. While it is 264 | beyond the scope of this example, we highly recommend running commands through 265 | a virtual machine such as docker. 266 | 267 | The server will return an exception for any command that does not return with a 268 | zero exit code. Commands also have a maximum runtime that is specified by the 269 | `--timeout` option when starting the server. The server will return an 270 | exception if a command exceeds this limit. 271 | 272 | ### output 273 | 274 | The last step is to define the output type. Current `text_stream` and `file` 275 | are supported. 276 | ``` 277 | "output" : 278 | { 279 | "type" : "text_stream", 280 | "sep" : "\t" 281 | } 282 | ``` 283 | 284 | For `text_stream` output, the server returns a JSON array or arrays. Output is split 285 | by line then by the value given in the `sep` field. 286 | 287 | ## staring the server 288 | 289 | ``` 290 | python server.py --config tabix_config.yaml 291 | ``` 292 | 293 | ``` 294 | usage: server.py [-h] --config CONFIG [--port PORT] [--host HOST] 295 | [--timeout TIMEOUT] 296 | 297 | command to web server. 298 | 299 | optional arguments: 300 | -h, --help show this help message and exit 301 | --config CONFIG Configuration file. 302 | --port PORT Port to run on (default 8080) 303 | --host HOST Server hos (default 127.0.0.1) 304 | --timeout TIMEOUT Max runtime (sec) for a command (default 10) 305 | ``` 306 | 307 | In addition to serving requests, the server also advertises the services it 308 | supports along with input and output requirements. This information allows for 309 | the development of more general clients interfaces and can be accessed at the 310 | `\info` endpoint. 311 | 312 | ``` 313 | $ curl "http://127.0.0.1:8080/info" 314 | { 315 | "rmsk": { 316 | "name": "rmsk", 317 | "output": { "type": "text_stream" }, 318 | "inputs": [ 319 | { "name": "chromosome", "type": "string" }, 320 | { "name": "start", "type": "integer" }, 321 | { "name": "end", "type": "integer" } 322 | ] 323 | } 324 | } 325 | ``` 326 | 327 | # Client 328 | 329 | Any method that can make web request can be a client. For example, `curl` works 330 | great. 331 | ``` 332 | $ curl "http://127.0.0.1:8080/?service=rmsk&chromosome=chr1&start=10000&end=20000" 333 | { "success": 1, 334 | "result": [ 335 | [ "chr1", "10000", "10468", "(CCCTAA)n", "Simple_repeat", "Simple_repeat" ], 336 | [ "chr1", "10468", "11447", "TAR1", "Satellite", "telo" ], 337 | [ "chr1", "11503", "11675", "L1MC", "LINE", "L1" ], 338 | [ "chr1", "11677", "11780", "MER5B", "DNA", "hAT-Charlie" ], 339 | [ "chr1", "15264", "15355", "MIR3", "SINE", "MIR" ], 340 | [ "chr1", "16712", "16749", "(TGG)n", "Simple_repeat", "Simple_repeat" ], 341 | [ "chr1", "18906", "19048", "L2a", "LINE", "L2" ], 342 | [ "chr1", "19947", "20405", "L3", "LINE", "CR1" ] 343 | ] 344 | } 345 | ``` 346 | 347 | This package also provides a python API that is tightly coupled with the 348 | server. For example, exceptions returned by the server are then raised as an 349 | exception by the API. The client will also perform local type checking on all 350 | service requests before submitting them to the server. 351 | 352 | The client has two steps 353 | 1. connecting to the server with `cmd2web.Client.connect(host, port)` 354 | 2. running a query with `run(service_name, **kwargs)` where the `kwargs` map to 355 | the services variable arguments 356 | 357 | To use the service defined above: 358 | ``` 359 | import cmd2web 360 | s = cmd2web.Client.connect('127.0.0.1','8080') 361 | R = R = s.run('rmsk', chromosome="chr1", start=10000, end=20000) 362 | for r in R: 363 | print('\t'.join(r)) 364 | ``` 365 | 366 | # Installation 367 | 368 | The easiest way to install `cmd2web` is with `virtualenv`: 369 | ``` 370 | virtualenv -p /usr/local/bin/python3.6 cmd2web_env 371 | source cmd2web_env/bin/activate 372 | pip install flask requests numpy Cython cyvcf2 pyyaml pyopenssl flask-cors mod_wsgi werkzeug pandas 373 | ``` 374 | 375 | ### Apache Setup 376 | 377 | To Install Apache:- 378 | ``` 379 | sudo apt-get update 380 | sudo apt-get install apache2 381 | ``` 382 | 383 | To Add new site we need to add new configuration for our website in /etc/apache/sites-available: 384 | 385 | sudo nano /etc/apache/sites-available/myweb.conf 386 | ``` 387 | 388 | # Add machine's IP address (use ifconfig command) 389 | ServerName localhost 390 | # Give an alias to to start your website url with 391 | # Using cmd2web, it means in URL you have to write localhost to access the application. The path after that is path to your wsgi file. Change it accordingly. 392 | WSGIScriptAlias / somepath/flaskapp.wsgi 393 | # Mention the directory which should be accessible by your application. This will be directory which has all the files. 394 | 395 | # set permissions as per apache2.conf file 396 | Options FollowSymLinks 397 | AllowOverride None 398 | Require all granted 399 | 400 | ErrorLog ${APACHE_LOG_DIR}/error.log 401 | LogLevel warn 402 | CustomLog ${APACHE_LOG_DIR}/access.log combined 403 | 404 | 405 | ``` 406 | 407 | create your flaskapp.wsgi file at your preferred location: 408 | ``` 409 | nano flaskapp.wsgi 410 | ``` 411 | ``` 412 | #!/usr/bin/python 413 | import sys,os 414 | import logging 415 | #Path to your virtualenv 416 | activate_this = 'PATH_TO_VIRTUAL_ENV/activate_this.py' 417 | with open(activate_this) as file_: 418 | exec(file_.read(), dict(__file__=activate_this)) 419 | logging.basicConfig(stream=sys.stderr) 420 | #Location where your server.py file is present. 421 | sys.path.insert(0,"PATH_TO_SRC_DIRECTORY") 422 | 423 | from server import app as application 424 | ``` 425 | 426 | Enable site: 427 | ``` 428 | sudo a2ensite flaskapp 429 | ``` 430 | Add the site to https: 431 | 432 | For encrypting a web connection we need certificate from CA (certificate authority) or we can use self signed certificates. Let's create a self signed certificate using the following command. 433 | 434 | ``` 435 | openssl req -x509 -newkey rsa:2048 -keyout mykey.key -out mycert.pem -days 365 -nodes 436 | sudo cp mycert.pem /etc/ssl/certs 437 | sudo cp mykey.key /etc/ssl/private 438 | ``` 439 | Enable ssl on the server: 440 | ``` 441 | sudo a2enmod ssl 442 | ``` 443 | 444 | Once we create key, we have to add our site and the key to the /etc/apache2/sites-available/default-ssl.conf 445 | 446 | ``` 447 | sudo nano /etc/apache2/sites-available/default-ssl.conf 448 | ``` 449 | 450 | Add directory access and path to your website : 451 | ``` 452 | # Give an alias to to start your website url with 453 | # Using cmd2web, it means in URL you have to write localhost to access the application. The path after that is path to your wsgi file. Change it accordingly. 454 | WSGIScriptAlias / PATH_TO_WSGI/flaskapp.wsgi 455 | # Mention the directory which should be accessible by your application. This will be directory which has all the files. 456 | 457 | # set permissions as per apache2.conf file 458 | Options FollowSymLinks 459 | AllowOverride None 460 | Require all granted 461 | 462 | ``` 463 | 464 | Locate SSLCertificateFile & SSLCertificateKeyFile and replace the path with the key and certificate file: 465 | ``` 466 | SSLCertificateFile /etc/ssl/certs/cmd2web.pem 467 | SSLCertificateKeyFile /etc/ssl/private/cmd2web.key 468 | ``` 469 | 470 | Enable the SSL site: 471 | ``` 472 | sudo a2ensite default-ssl.conf 473 | ``` 474 | 475 | Restart the apache server: 476 | ``` 477 | sudo service apache2 restart 478 | ``` 479 | 480 | To access site: 481 | ``` 482 | HTTP: 483 | http://localhost/?service=rmsk&chromosome=chr1&start=10000 484 | 485 | HTTPS: 486 | https://localhost/?service=rmsk&chromosome=chr1&start=10000 487 | 488 | Server address: 489 | http://localhost/ 490 | ``` 491 | 492 | # Setting Server: 493 | Currently if there is apache_conf.yaml file, then apache server will be the default server. If on some system that file is not present, then a normal python server can be used. 494 | 495 | # DB Setup 496 | 497 | Tables 498 | ``` 499 | 1. Groups - It has a list of all the groups with the following fields: 500 | Name Type Primary Key Description 501 | - GroupID Integer Yes Group ID of the group 502 | - GroupName Text No Group Name of the group 503 | - GroupType Text No Type of the group 504 | - RestrictedGroup Integer No Boolean indicating if group is restricted (1 means restricted) 505 | 506 | 2. Keys - It has a list of all the keys belonging to a particular user associated to a group. 507 | Name Type Foreign Key Description 508 | - GroupID Integer Yes Group ID of the group 509 | - Token Text No Token for the user 510 | - Expiry Text No Expiry date of token. Format mm-dd-yyyy 511 | - UserEmail Text No Email ID of the user 512 | ``` 513 | 514 | Every Service has a group associated to it. It can either have a value or can be blank. A group if mentioned is a restricted group. If a user calls a service which is restricted, the user needs to pass a token associated with that group provided by the admin. The results are displayed on passing the correct token otherwise an error is displayed. 515 | 516 | # Command Line Database application for the Admin 517 | 518 | The admin has following methods available (DBCommandLineTool.py): 519 | 520 | To get a list of all the commands available: 521 | ``` 522 | Go to the directory having file DBCommandLineTool.py 523 | Run the following command: 524 | python DBCommandLineTool.py --help 525 | 526 | Output- 527 | Usage: DBCommandLineTool.py [OPTIONS] COMMAND [ARGS]... 528 | 529 | Command line interface for database interaction. 530 | 531 | Options: 532 | --help Show this message and exit. 533 | 534 | Commands: 535 | creategroup Create new group. 536 | createkeybygroupid Create new token by group id. 537 | createkeybygroupname Create new token by group name. 538 | deletegroup Delete group by name. 539 | deletekeybygroup Delete key by group name. 540 | deletekeybyuser Delete key by group user. 541 | getkeybygroupname Get keys by Group. 542 | getkeybyuser Get Keys by User. 543 | 544 | ``` 545 | 546 | Click and pandas python library is used to create the command line application and to display the results. 547 | All the commands require a user to pass the input parameters. A user can use the help command to find all input parameters required: 548 | ``` 549 | 1. CreateGroup Command 550 | 551 | Input- python DBCommandLineTool.py creategroup --help 552 | 553 | Output- 554 | Usage: DBCommandLineTool.py creategroup [OPTIONS] 555 | For eg: python DBCommandLineTool.py creategroup --gname=DummyGroup --gtype=Test --restricted=1 556 | 557 | Description:Create a new group. 558 | 559 | Input parameters required: 560 | 561 | - gname - Group Name 562 | 563 | - gytpe - Group Type 564 | 565 | - restricted - Boolean indicating whether group is restricted 566 | 567 | Options: 568 | --gname TEXT The group name. 569 | --gtype TEXT Type of the group. 570 | --restricted TEXT If the group is restricted group. 1 if group is 571 | restricted ,0 if not restricted. 572 | --help Show this message and exit. 573 | 574 | 575 | 576 | 2. CreateKeyByGroupID Command 577 | 578 | Input- python DBCommandLineTool.py createkeybygroupid --help 579 | 580 | Output- 581 | Usage: DBCommandLineTool.py createkeybygroupid [OPTIONS] 582 | For eg: python DBCommandLineTool.py createkeybygroupid --gid=9 --token=122344434 --expiry=04-27-2019 --email=ro@colorado.edu 583 | 584 | Description:Create new token by group id. 585 | 586 | Input parameters required: 587 | 588 | - gid - Group ID 589 | 590 | - token - Token for the user 591 | 592 | - expiry - Token expiry date 593 | 594 | - email - Email id of the user 595 | 596 | Options: 597 | --gid TEXT The group id. 598 | --token TEXT Token for the user associated to a group.Format (mm-dd-yyyy). 599 | --expiry TEXT Expiry date for the token. 600 | --email TEXT Email id of the user. 601 | --help Show this message and exit. 602 | 603 | Note: The expiry and token command is optional. If not passed random token will be generated and a new date with expiry after a year will be generated. 604 | 605 | 3. CreateKeyByGroupName Command 606 | 607 | Input-python DBCommandLineTool.py createkeybygroupname --help 608 | 609 | Output- 610 | Usage: DBCommandLineTool.py createkeybygroupname [OPTIONS] 611 | Foe eg: python DBCommandLineTool.py createkeybygroupname --gname=DummyGroup --token=122344435 --expiry=05-27-2019 --email=rom@colorado.edu 612 | 613 | Description:Create new token by group name. 614 | 615 | Input parameters required: 616 | 617 | - gname - Group Name 618 | 619 | - token - Token for the user 620 | 621 | - expiry - Token expiry date 622 | 623 | - email - Email id of the user 624 | 625 | Options: 626 | --gname TEXT The group name. 627 | --token TEXT Token for the user associated to a group. 628 | --expiry TEXT Expiry date for the token. Format (mm-dd-yyyy). 629 | --email TEXT Email id of the user. 630 | --help Show this message and exit. 631 | 632 | Note: The expiry and token command is optional. If not passed random token will be generated and a new date with expiry after a year will be generated. 633 | 634 | 4. DeleteGroup Command 635 | 636 | Input-python DBCommandLineTool.py deletegroup --help 637 | 638 | Output- 639 | Usage: DBCommandLineTool.py deletegroup [OPTIONS] 640 | For eg: python DBCommandLineTool.py deletegroup --gname=DummyGroup 641 | 642 | Description:Delete group by name. 643 | 644 | Input parameters required: 645 | 646 | - gname - Group Name 647 | 648 | Options: 649 | --gname TEXT The group name. 650 | --help Show this message and exit. 651 | 652 | 653 | 5. DeleteKeyByGroup Command 654 | 655 | Input- python DBCommandLineTool.py deletekeybygroup --help 656 | 657 | Output- 658 | Usage: DBCommandLineTool.py deletekeybygroup [OPTIONS] 659 | For eg:python DBCommandLineTool.py deletekeybygroup --gname=DummyGroup 660 | 661 | Description:Delete key by group name. 662 | 663 | Input parameters required: 664 | 665 | - gname - Group Name 666 | 667 | Options: 668 | --gname TEXT The group name. 669 | --help Show this message and exit. 670 | 671 | 672 | 6. DeleteKeyByUser Command 673 | 674 | Input-python DBCommandLineTool.py deletekeybyuser --help 675 | 676 | Output- 677 | Usage: DBCommandLineTool.py deletekeybyuser [OPTIONS] 678 | For eg: python DBCommandLineTool.py deletekeybyuser --email=rom@colorado.edu 679 | 680 | Description:Delete key by group user. 681 | 682 | Input parameters required: 683 | 684 | - email - email id of the user 685 | 686 | Options: 687 | --email TEXT The user email. 688 | --help Show this message and exit. 689 | 690 | 691 | 7. GetKeyByGroupName Command 692 | 693 | Input- python DBCommandLineTool.py getkeybygroupname --help 694 | 695 | Output- 696 | Usage: DBCommandLineTool.py getkeybygroupname [OPTIONS] 697 | For eg: python DBCommandLineTool.py getkeybygroupname --gname=DummyGroup 698 | 699 | Description:Get keys by Group. 700 | 701 | Input parameters required: 702 | 703 | - gname - Group name 704 | 705 | Options: 706 | --gname TEXT The Group name. 707 | --help Show this message and exit. 708 | 709 | 8. GetKeyByUser Command 710 | 711 | Input-python DBCommandLineTool.py getkeybyuser --help 712 | 713 | Output- 714 | Usage: DBCommandLineTool.py getkeybyuser [OPTIONS] 715 | For eg:python DBCommandLineTool.py getkeybyuser --email=rom2@colorado.edu 716 | 717 | Description:Get Keys by User. 718 | 719 | Input parameters required: 720 | 721 | - email - email id of the user 722 | 723 | Options: 724 | --email TEXT The user email. 725 | --help Show this message and exit. 726 | 727 | 9. GetGroupList Command 728 | 729 | Input- python DBCommandLineTool.py getgrouplist --help 730 | 731 | Output- 732 | Usage: DBCommandLineTool.py getgrouplist [OPTIONS] 733 | For eg:python DBCommandLineTool.py getgrouplist 734 | 735 | Description:Get all the groups. 736 | 737 | Input parameters required: 738 | 739 | None 740 | 741 | Options: 742 | --help Show this message and exit. 743 | 744 | 10. GetKeyList Command 745 | 746 | Input- python DBCommandLineTool.py getgrouplist --help 747 | 748 | Output- 749 | Usage: DBCommandLineTool.py getkeylist [OPTIONS] 750 | For eg:python DBCommandLineTool.py getkeylist 751 | 752 | Description:Get all the keys. 753 | 754 | Input parameters required: 755 | 756 | None 757 | 758 | 759 | Options: 760 | --help Show this message and exit. 761 | 762 | 763 | 764 | ``` 765 | # File Upload Service 766 | We need to add the service to the config file. There is a separate endpoint created for the fileupload with "/f" endpoint. 767 | File fixed attribute is set to false for this service. 768 | For eg: 769 | 770 | ``` 771 | name : simpleFileGrep 772 | arguments : 773 | - 774 | name : file 775 | fixed : 'false' 776 | type : string 777 | - 778 | name : pattern 779 | fixed : 'false' 780 | type : string 781 | command : 782 | - /bin/grep 783 | - $pattern 784 | - $file 785 | output : 786 | type : text_stream 787 | sep : \t 788 | ``` 789 | 790 | The file will be uploaded to the tmp folder and will be deleted once processing is complete. 791 | I have implemented it in 3 ways: 792 | 793 | ## 1. Curl Command 794 | 795 | For eg: If a user wants to call simpleFileGrep service and wants to upload test1.txt file. 796 | 797 | Apache 798 | curl -X POST -F file=@"test1.txt" "http://localhost/f?service=simpleFileGrep&pattern=zip" 799 | 800 | Python 801 | curl -X POST -F file=@"test1.txt" "http://127.0.0.1:8080/f?service=simpleFileGrep&pattern=zip" 802 | 803 | ----------------------------------------------------------------------------- 804 | ## 2. File Upload Command Line Application 805 | 806 | The admin has following methods available (DBCommandLineTool.py): 807 | 808 | To get a list of all the commands available: 809 | ``` 810 | Go to the directory having file UploadFileTool.py 811 | Run the following command: 812 | python UploadFileTool.py --help 813 | 814 | Usage: UploadFileTool.py [OPTIONS] COMMAND [ARGS]... 815 | 816 | Command line interface for database interaction. 817 | 818 | Options: 819 | --help Show this message and exit. 820 | 821 | Commands: 822 | uploadruncommand Description: Pass your own input file to a service. 823 | 824 | ``` 825 | 826 | A user can use the help command to find all input parameters required: 827 | ``` 828 | 1. uploadruncommand Command 829 | 830 | Input- python UploadFileTool.py uploadruncommand --help 831 | 832 | Output- 833 | Usage: UploadFileTool.py uploadruncommand [OPTIONS] 834 | 835 | Description: Pass your own input file to a service. 836 | 837 | Input parameters required: 838 | 839 | - url - URL with the parameters 840 | 841 | - filepath - Path of the input file 842 | 843 | Example Usage: python UploadFileTool.py uploadRunCommand 844 | --url='http://127.0.0.1:8080/f?service=simpleFileGrep&pattern=zip' 845 | --filepath=/home/rohit/test3.txt 846 | 847 | Options: 848 | --url TEXT The url with parameters 849 | --filepath TEXT Path of the input file. 850 | --help Show this message and exit. 851 | ``` 852 | 853 | ----------------------------------------------------------------------------- 854 | ## 3. Web Version 855 | 856 | Apache 857 | https://localhost/webapp 858 | 859 | Python 860 | http://localhost:8080/webapp 861 | 862 | If the file fixed is set to false for the service selected, then the file upload box will appear and user can upload any file from the system. 863 | 864 | 865 | 866 | -------------------------------------------------------------------------------- /docs/cmd2web.js: -------------------------------------------------------------------------------- 1 | var server_url = ''; 2 | var server_info = null; 3 | var server_result = null; 4 | var selectedService = null; 5 | 6 | $.urlParam = function(name){ 7 | var results = new RegExp('[\?&]' + 8 | name + 9 | '=([^&#]*)').exec(window.location.href); 10 | if (results==null){ 11 | return null; 12 | } else { 13 | return results[1] || 0; 14 | } 15 | } 16 | 17 | $(document).ready(function() { 18 | server_param = $.urlParam('server'); 19 | 20 | if (server_param){ 21 | server_url = decodeURIComponent(server_param); 22 | $('#server_text').val(server_url); 23 | connect_to_server(); 24 | } 25 | 26 | }) 27 | 28 | function setServer() { 29 | 30 | server_url = $('#server_text').val(); 31 | 32 | if (server_url.length == 0) { 33 | return 34 | } 35 | 36 | connect_to_server(); 37 | } 38 | 39 | 40 | function connect_to_server() { 41 | 42 | var get_server_info_promise = get_server_info(server_url + '/info').then(function(data) { 43 | server_info = data 44 | }); 45 | 46 | Promise.all([get_server_info_promise]).then( 47 | function(values) { 48 | setInputForm(); 49 | } 50 | ); 51 | } 52 | 53 | function get_server_info(url) { 54 | return new Promise( function(resolve, reject) { 55 | $.get(url, 56 | function (data) { 57 | resolve( JSON.parse(data) ); 58 | } 59 | ); 60 | }); 61 | } 62 | 63 | function setInputForm(){ 64 | 65 | $('#services').empty(); 66 | 67 | $('#services').append(''); 68 | var serviceSelect = document.getElementById('service-select'); 69 | 70 | 71 | for (var service in server_info) { 72 | serviceSelect.options[serviceSelect.options.length] = new Option(service, service); 73 | } 74 | 75 | $('#services').append(''); 76 | 77 | 78 | service_param = $.urlParam('service'); 79 | 80 | if (service_param) { 81 | if ( service_param in server_info) { 82 | selectedService = decodeURIComponent(service_param); 83 | $("#set-service").val(selectedService).change(); 84 | load_service(); 85 | } 86 | } 87 | 88 | } 89 | 90 | function setService() { 91 | 92 | $('#input').empty(); 93 | $('#output').empty(); 94 | 95 | var serviceSelect = document.getElementById('service-select'); 96 | selectedService = serviceSelect.options[serviceSelect.selectedIndex].value; 97 | 98 | load_service(); 99 | } 100 | 101 | 102 | function load_service() { 103 | //console.log(selectedService); 104 | var all_vals = true; 105 | for (var i = 0; i < server_info[selectedService].inputs.length; ++i){ 106 | //console.log(server_info[selectedService].inputs[i]); 107 | input_str = ''; 111 | $('#input').append(input_str); 112 | 113 | var this_input = $.urlParam(server_info[selectedService].inputs[i].name); 114 | if (this_input) { 115 | var this_val = decodeURIComponent(this_input); 116 | $('#' + server_info[selectedService].inputs[i].name ).val(this_val); 117 | } else { 118 | all_vals = false; 119 | } 120 | } 121 | 122 | $('#input').append(''); 123 | 124 | if (all_vals) { 125 | getData(); 126 | } 127 | } 128 | 129 | function getData() { 130 | 131 | var serviceSelect = document.getElementById('service-select'); 132 | var selectedService = serviceSelect.options[serviceSelect.selectedIndex].value; 133 | 134 | $('#loading').append('loading...'); 135 | 136 | $('#output').empty(); 137 | 138 | url_params = "?service=" + selectedService; 139 | 140 | console.log(server_info[selectedService]); 141 | for (var i = 0; i < server_info[selectedService].inputs.length; ++i){ 142 | var inputName = server_info[selectedService].inputs[i].name; 143 | var inputValue = document.getElementById(inputName).value; 144 | url_params += '&' + inputName + '=' + inputValue 145 | } 146 | 147 | var get_data_promise = get_data(server_url + url_params).then(function(data) { 148 | server_result = data; 149 | }); 150 | 151 | Promise.all([get_data_promise]).then( 152 | function(values) { 153 | showData(); 154 | console.log('DONE 1'); 155 | } 156 | ); 157 | } 158 | 159 | function get_data(url) { 160 | return new Promise( function(resolve, reject) { 161 | $.get(url, 162 | function (data) { 163 | console.log(data); 164 | resolve( JSON.parse(data) ); 165 | console.log('DONE'); 166 | } 167 | ); 168 | }); 169 | } 170 | 171 | function showData() { 172 | if (server_result.success != 1) { 173 | $('#loading').empty(); 174 | $('#output').append('Command did not complete successfully'); 175 | } else { 176 | console.log('0'); 177 | $('#loading').empty(); 178 | var result = server_result.result; 179 | $('#output').append(''); 180 | for (var i = 0; i < result.length; ++i){ 181 | row = ''; 182 | for (var j = 0; j < result[i].length; ++j){ 183 | row += ''; 184 | } 185 | row += ''; 186 | $('#output_table').append(row); 187 | } 188 | $('#output').append('
' + result[i][j] + '
'); 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /docs/simple.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 |
15 | 16 |
17 |
18 | 19 |
20 |
21 |
22 |
23 |
24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /docs/site.css: -------------------------------------------------------------------------------- 1 | body 2 | { 3 | font-family: Arial, sans-serif; 4 | font-size: 11pt; 5 | } 6 | 7 | input[type="text"] 8 | { 9 | font-size: 11pt; 10 | } 11 | 12 | select 13 | { 14 | font-size: 11pt; 15 | } 16 | 17 | button 18 | { 19 | background-color: #555555; 20 | color: white; 21 | font-size: 11pt; 22 | } 23 | 24 | 25 | #server 26 | { 27 | padding-top: 10px; 28 | padding-bottom: 10px; 29 | display: grid; 30 | width: 25%; 31 | } 32 | 33 | #services 34 | { 35 | padding-top: 10px; 36 | padding-bottom: 10px; 37 | display: grid; 38 | width: 25%; 39 | } 40 | 41 | #input 42 | { 43 | padding-top: 10px; 44 | padding-bottom: 10px; 45 | display: grid; 46 | width: 25%; 47 | } 48 | 49 | #output td, #output th 50 | { 51 | border: 0px; 52 | padding: 5px; 53 | } 54 | 55 | #output tr:nth-child(even) 56 | { 57 | background-color: #f2f2f2; 58 | } 59 | 60 | #output tr:hover 61 | { 62 | background-color: #ddd; 63 | } 64 | 65 | -------------------------------------------------------------------------------- /ex_configs/apache_conf.yaml: -------------------------------------------------------------------------------- 1 | External_Server : yes 2 | Default_Apache_Conf : 3 | Default_Config_File : ../ex_configs/example_config.yaml 4 | Timeout : 10 5 | No_Access_Control_Header : no -------------------------------------------------------------------------------- /ex_configs/example_config.yaml: -------------------------------------------------------------------------------- 1 | Groups: 2 | Long-Running : 3 | - rmsk 4 | - newrmsk 5 | - oldrmsk 6 | Short-Running : 7 | - grep 8 | - head 9 | - tail 10 | Services: 11 | - 12 | name : simpleGrep 13 | arguments : 14 | - 15 | name : file 16 | fixed : 'true' 17 | type : string 18 | value : /usr/share/dict/words 19 | - 20 | name : pattern 21 | fixed : 'false' 22 | type : string 23 | command : 24 | - /bin/grep 25 | - $pattern 26 | - $file 27 | output : 28 | type : text_stream 29 | sep : \t 30 | - 31 | name : simpleFileGrep 32 | arguments : 33 | - 34 | name : file 35 | fixed : 'false' 36 | type : string 37 | - 38 | name : pattern 39 | fixed : 'false' 40 | type : string 41 | command : 42 | - /bin/grep 43 | - $pattern 44 | - $file 45 | output : 46 | type : text_stream 47 | sep : \t 48 | 49 | - 50 | name : simpleRepeat 51 | arguments : 52 | - 53 | name : file 54 | fixed : 'true' 55 | type : string 56 | value : /Users/rl/ref/repeats/ucsc/simpleRepeat.bed.gz 57 | - 58 | name : chromosome 59 | fixed : 'false' 60 | type : string 61 | - 62 | name : start 63 | fixed : 'false' 64 | type : integer 65 | - 66 | name : end 67 | fixed : 'false' 68 | type : integer 69 | command : 70 | - /usr/local/bin/tabix 71 | - $file 72 | - $chromosome:$start-$end 73 | output : 74 | type : text_stream 75 | sep : \t 76 | - 77 | name : chainSelf 78 | arguments : 79 | - 80 | name : file 81 | fixed : 'true' 82 | type : string 83 | value : /Users/rl/ref/repeats/ucsc/chainSelf.bed.gz 84 | - 85 | name : chromosome 86 | fixed : 'false' 87 | type : string 88 | - 89 | name : start 90 | fixed : 'false' 91 | type : integer 92 | - 93 | name : end 94 | fixed : 'false' 95 | type : integer 96 | command : 97 | - /usr/local/bin/tabix 98 | - $file 99 | - $chromosome:$start-$end 100 | output : 101 | type : text_stream 102 | sep : \t 103 | - 104 | name : rmsk 105 | group : 106 | - Restricted 107 | - SimpleGroup 108 | arguments : 109 | - 110 | name : file 111 | fixed : 'true' 112 | type : string 113 | value : /home/rohit/Documents/Academics/sem4/IndependentStudy/repo/cmd2web/data/sortedrmsk.bed.gz 114 | - 115 | name : chromosome 116 | fixed : 'false' 117 | type : string 118 | - 119 | name : start 120 | fixed : 'false' 121 | type : integer 122 | - 123 | name : end 124 | fixed : 'false' 125 | type : integer 126 | command : 127 | - /usr/local/bin/tabix 128 | - $file 129 | - $chromosome:$start-$end 130 | output : 131 | type : text_stream 132 | sep : \t -------------------------------------------------------------------------------- /ex_configs/sample_grep_config.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name" : "simpleGrep", 4 | "arguments" : 5 | [ 6 | { 7 | "name" : "file", 8 | "fixed" : "true", 9 | "type" : "string", 10 | "value" : "/usr/share/dict/words" 11 | }, 12 | { 13 | "name" : "pattern", 14 | "fixed" : "false", 15 | "type" : "string" 16 | } 17 | ], 18 | "command": 19 | [ 20 | "/usr/bin/grep", 21 | "$pattern", 22 | "$file" 23 | ], 24 | 25 | "output" : 26 | { 27 | "type" : "text_stream", 28 | "sep" : "\t" 29 | } 30 | }, 31 | { 32 | "name" : "simpleHead", 33 | "arguments" : 34 | [ 35 | { 36 | "name" : "file", 37 | "fixed" : "true", 38 | "type" : "string", 39 | "value" : "/usr/share/dict/words" 40 | } 41 | ], 42 | "command": 43 | [ 44 | "/usr/bin/head", 45 | "$file" 46 | ], 47 | 48 | "output" : 49 | { 50 | "type" : "text_stream", 51 | "sep" : "\t" 52 | } 53 | } 54 | 55 | ] -------------------------------------------------------------------------------- /ex_configs/tabix_config.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name" : "simpleRepeat", 4 | "arguments" : 5 | [ 6 | { 7 | "name" : "file", 8 | "fixed" : "true", 9 | "type" : "string", 10 | "value" : "/Users/rl/ref/repeats/ucsc/simpleRepeat.bed.gz" 11 | }, 12 | { 13 | "name" : "chromosome", 14 | "fixed" : "false", 15 | "type" : "string" 16 | }, 17 | { 18 | "name" : "start", 19 | "fixed" : "false", 20 | "type" : "integer" 21 | }, 22 | { 23 | "name" : "end", 24 | "fixed" : "false", 25 | "type" : "integer" 26 | } 27 | ], 28 | 29 | "command": 30 | [ 31 | "/Users/rl/bin/tabix", 32 | "$file", 33 | "$chromosome:$start-$end" 34 | ], 35 | 36 | "output" : 37 | { 38 | "type" : "text_stream", 39 | "sep" : "\t" 40 | } 41 | }, 42 | { 43 | "name" : "chainSelf", 44 | "arguments" : 45 | [ 46 | { 47 | "name" : "file", 48 | "fixed" : "true", 49 | "type" : "string", 50 | "value" : "/Users/rl/ref/repeats/ucsc/chainSelf.bed.gz" 51 | }, 52 | { 53 | "name" : "chromosome", 54 | "fixed" : "false", 55 | "type" : "string" 56 | }, 57 | { 58 | "name" : "start", 59 | "fixed" : "false", 60 | "type" : "integer" 61 | }, 62 | { 63 | "name" : "end", 64 | "fixed" : "false", 65 | "type" : "integer" 66 | } 67 | 68 | ], 69 | 70 | "command": 71 | [ 72 | "/Users/rl/bin/tabix", 73 | "$file", 74 | "$chromosome:$start-$end" 75 | ], 76 | 77 | 78 | "output" : 79 | { 80 | "type" : "text_stream", 81 | "sep" : "\t" 82 | } 83 | }, 84 | { 85 | "name" : "rmsk", 86 | "arguments" : 87 | [ 88 | { 89 | "name" : "file", 90 | "fixed" : "true", 91 | "type" : "string", 92 | "value" : "/Users/rl/ref/repeats/ucsc/rmsk.bed.gz" 93 | }, 94 | { 95 | "name" : "chromosome", 96 | "fixed" : "false", 97 | "type" : "string" 98 | }, 99 | { 100 | "name" : "start", 101 | "fixed" : "false", 102 | "type" : "integer" 103 | }, 104 | { 105 | "name" : "end", 106 | "fixed" : "false", 107 | "type" : "integer" 108 | } 109 | ], 110 | 111 | "command": 112 | [ 113 | "/Users/rl/bin/tabix", 114 | "$file", 115 | "$chromosome:$start-$end" 116 | ], 117 | 118 | 119 | "output" : 120 | { 121 | "type" : "text_stream", 122 | "sep" : "\t" 123 | } 124 | } 125 | ] 126 | -------------------------------------------------------------------------------- /flaskapp.wsgi: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import sys,os 3 | import logging 4 | activate_this = '/home/rohit/Documents/Academics/sem4/IndependentStudy/forkedrepo/cmd2web/venv/bin/activate_this.py' 5 | with open(activate_this) as file_: 6 | exec(file_.read(), dict(__file__=activate_this)) 7 | logging.basicConfig(stream=sys.stderr) 8 | sys.path.insert(0,"/home/rohit/Documents/Academics/sem4/IndependentStudy/forkedrepo/cmd2web/src") 9 | 10 | from server import app as application 11 | application.secret_key = 'Add your secret key' 12 | 13 | # def application(environ, start_response): 14 | # for key in ['my_config_file']: 15 | # os.environ[key] = environ.get(key, '') 16 | # from server import app as _application 17 | 18 | # return _application(environ, start_response) 19 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | certifi==2019.11.28 2 | chardet==3.0.4 3 | Click==7.0 4 | Flask==1.1.1 5 | Flask-Cors==3.0.8 6 | idna==2.8 7 | itsdangerous==1.1.0 8 | Jinja2==2.10.3 9 | MarkupSafe==1.1.1 10 | PyYAML==5.2 11 | requests==2.22.0 12 | six==1.13.0 13 | urllib3==1.25.7 14 | Werkzeug==0.16.0 15 | -------------------------------------------------------------------------------- /src/UploadFileTool.py: -------------------------------------------------------------------------------- 1 | import click 2 | import requests 3 | 4 | 5 | @click.group() 6 | # @click.option('-i', '--input', type=click.File('r')) 7 | def cli(): 8 | """Command line interface for database interaction.""" 9 | pass 10 | 11 | 12 | @cli.command() 13 | @click.option('--url', help='The url with parameters') 14 | @click.option('--filepath', help='Path of the input file.') 15 | def uploadRunCommand(url, filepath): 16 | 17 | """Description: Pass your own input file to a service. \n 18 | Input parameters required: \n 19 | - url - URL with the parameters \n 20 | - filepath - Path of the input file \n 21 | Example Usage: python UploadFileTool.py uploadRunCommand --url='http://127.0.0.1:8080/f?service=simpleFileGrep&pattern=zip' --filepath=/home/rohit/test3.txt """ 22 | 23 | if (url != None and filepath != None): 24 | f = open(filepath) 25 | r = requests.post(url=url, files={'file': f}) 26 | print(r.json()) 27 | 28 | else: 29 | print(url,filepath) 30 | print("Parameter missing") 31 | # click.echo('Hello %s! - %s! - %d' % gname, gtype,restricted) 32 | 33 | 34 | if __name__ == '__main__': 35 | cli() -------------------------------------------------------------------------------- /src/client.py: -------------------------------------------------------------------------------- 1 | import cmd2web 2 | import os 3 | from pathlib import Path 4 | apache_server = False 5 | 6 | 7 | current_directory= os.path.dirname(__file__) 8 | apache_config_file_path=os.path.join(current_directory, '../ex_configs/apache_conf.yaml') 9 | apache_file = Path(apache_config_file_path) 10 | if apache_file.is_file(): 11 | apache_server = True 12 | else: 13 | apache_server = False 14 | 15 | if(apache_server): 16 | s = cmd2web.Client.connect('http://localhost') 17 | else: 18 | s = cmd2web.Client.connect('http://127.0.0.1:8080') 19 | 20 | try: 21 | R = s.run('simpleGrep', 22 | pattern='zip') 23 | 24 | except Exception as e: 25 | print(str(e)) 26 | 27 | print(R) 28 | -------------------------------------------------------------------------------- /src/cmd2web.py: -------------------------------------------------------------------------------- 1 | 2 | import json 3 | import sys 4 | import re 5 | import random 6 | from flask import Flask,send_file 7 | import requests 8 | import logging 9 | import settings 10 | #from logtest import setup_logging 11 | #setup_logging() 12 | logger = logging.getLogger(__name__) 13 | #{{{def test_required(name, required, config): 14 | def test_required(name, required, config): 15 | for r in required: 16 | if r not in config: 17 | sys.exit('ERROR in ' + name + ' definition. "' + r + \ 18 | '" not defined.') 19 | #}}} 20 | 21 | #{{{class Argument: 22 | class Argument: 23 | 24 | #{{{ 25 | @staticmethod 26 | def type_check(test_value, type): 27 | if type == 'string': 28 | return True 29 | 30 | elif type == 'integer': 31 | try: 32 | int(test_value) 33 | except ValueError: 34 | return False 35 | return True 36 | 37 | elif type == 'float': 38 | try: 39 | float(test_value) 40 | except ValueError: 41 | return False 42 | return True 43 | return False 44 | 45 | #}}} 46 | 47 | #{{{def load(config): 48 | @staticmethod 49 | def load(config): 50 | required = ['name', 'type', 'fixed'] 51 | test_required('argument', required, config) 52 | 53 | if config['fixed'] not in ['true', 'false']: 54 | sys.exit('ERROR in argument definition. "' + \ 55 | config['fixed'] + '" is not a valid value for fixed.') 56 | 57 | fixed = False 58 | value = None 59 | if config['fixed'] == 'true': 60 | required = ['value'] 61 | test_required('argument', required, config) 62 | value = config['value'] 63 | fixed = True 64 | 65 | 66 | if config['type'] not in ['string', 'integer', 'float']: 67 | sys.exit('ERROR in argument definition. Type "' + \ 68 | config['type'] + \ 69 | '" is not supported.') 70 | 71 | return Argument(config['name'], 72 | config['type'], 73 | fixed, 74 | value) 75 | 76 | #}}} 77 | 78 | #{{{ def __init__(self, name, dtype, fixed, value): 79 | def __init__(self, name, dtype, fixed, value): 80 | 81 | self.name = name 82 | 83 | if dtype not in ['string', 'integer', 'float']: 84 | sys.exit('ERROR in argument definition. Type "' + \ 85 | dtype + \ 86 | '" is not supported.') 87 | 88 | self.fixed = fixed 89 | self.value = value 90 | self.type = dtype 91 | #}}} 92 | 93 | #{{{def __str__(self): 94 | def __str__(self): 95 | return ' '.join([ 'name:' + self.name, 96 | 'fixed:' + str(self.fixed), 97 | 'value:' + str(self.value), 98 | 'type:' + str(self.type) ]) 99 | #}}} 100 | 101 | #{{{def get_info(self): 102 | def get_info(self): 103 | if self.fixed: 104 | return None 105 | 106 | argument_info = {} 107 | argument_info['name'] = self.name 108 | argument_info['type'] = self.type 109 | 110 | return argument_info 111 | #}}} 112 | 113 | #{{{def copy(self): 114 | def copy(self): 115 | return Argument(self.name, self.type, self.fixed, self.value) 116 | #}}} 117 | 118 | #{{{ def type_test(self, test_value): 119 | def type_test(self, test_value): 120 | return Argument.type_check(test_value, self.type) 121 | # if self.type == 'string': 122 | # return True 123 | # 124 | # elif self.type == 'integer': 125 | # try: 126 | # int(test_value) 127 | # except ValueError: 128 | # return False 129 | # return True 130 | # 131 | # elif self.type == 'float': 132 | # try: 133 | # float(test_value) 134 | # except ValueError: 135 | # return False 136 | # return True 137 | # return False 138 | #}}} 139 | #}}} 140 | 141 | #{{{class Output: 142 | class Output: 143 | #{{{def load(config): 144 | @staticmethod 145 | def load(config): 146 | required = ['type'] 147 | test_required('output', required, config) 148 | 149 | supported_types = ['text_stream', 'file'] 150 | 151 | otype = config['type'] 152 | if otype not in supported_types: 153 | sys.exit('ERROR in output definition. "' + otype + \ 154 | '" is not a supported type (' + \ 155 | ' '.join(supported_types) + ')') 156 | 157 | if otype == 'text_stream': 158 | sep = '\t' 159 | if 'sep' in config: 160 | sep = config['sep'] 161 | return Output(otype, sep=sep) 162 | 163 | if otype == 'file': 164 | required = ['value', 'mimetype'] 165 | test_required('output file', required, config) 166 | value = config['value'] 167 | mimetype = config['mimetype'] 168 | return Output(otype, value=value, mimetype=mimetype) 169 | #}}} 170 | 171 | #{{{def __init__(self, otype, value=None, mimetype=None, sep=None): 172 | def __init__(self, otype, value=None, mimetype=None, sep=None): 173 | self.type = otype 174 | self.value = value 175 | self.mimetype = mimetype 176 | self.sep = sep 177 | #}}} 178 | 179 | #{{{def get_info(self): 180 | def get_info(self): 181 | output_info = {} 182 | output_info['type'] = self.type 183 | 184 | if self.type == 'file': 185 | output_info['mimetype'] = self.mimetype 186 | 187 | return output_info 188 | #}}} 189 | 190 | #{{{def __str__(self): 191 | def __str__(self): 192 | return ' '.join([ 'type:' + self.type, 193 | 'value:' + str(self.value), 194 | 'mimetype:' + str(self.mimetype), 195 | 'sep:' + str(self.sep)]) 196 | #}}} 197 | 198 | #{{{def copy(self): 199 | def copy(self): 200 | return Output(self.type, self.value, self.mimetype, self.sep) 201 | #}}} 202 | 203 | #}}} 204 | 205 | #{{{class Service: 206 | class Service: 207 | logger.info("Inside service class of cm2web from my CGI script.") 208 | sys.stderr.write("Inside service class of cm2web from my CGI script.\n\n\n") 209 | #{{{def replace_variable(field, variable_table): 210 | @staticmethod 211 | def replace_variable(field, variable_table): 212 | if field == None: 213 | return None 214 | 215 | for v in re.findall(r'\$\w+', field): 216 | if v not in variable_table: 217 | variable_table[v] = \ 218 | str(random.randint(0,sys.maxint)) 219 | field = field.replace(v, variable_table[v]) 220 | return field 221 | #}}} 222 | 223 | #{{{uef load(config): 224 | @staticmethod 225 | def load(config): 226 | required = ['name', 'command', 'arguments', 'output'] 227 | test_required('service', required, config) 228 | group = config.get('group',"None") 229 | 230 | name = config['name'] 231 | command = config['command'] 232 | 233 | arguments = [] 234 | for argument_config in config['arguments']: 235 | argument = Argument.load(argument_config) 236 | arguments.append(argument) 237 | 238 | output = Output.load(config['output']) 239 | if(group != "None"): 240 | return Service(name, command, arguments, output,group) 241 | else: 242 | return Service(name, command, arguments, output) 243 | #}}} 244 | 245 | #{{{ def __init__(self, name, command, arguments, output): 246 | def __init__(self, name, command, arguments, output, group='None'): 247 | self.name = name 248 | if(group!='None'): 249 | self.group =group 250 | self.command = command 251 | self.arguments = arguments 252 | self.output = output 253 | self.variable_table = {} 254 | #}}} 255 | 256 | #{{{def get_info(self): 257 | def get_info(self): 258 | service_info = {} 259 | service_info['name'] = self.name 260 | service_info['output'] = self.output.get_info() 261 | if (hasattr(self, 'group')): 262 | service_info['group'] = True 263 | else: 264 | service_info['group'] = False 265 | service_info['inputs'] = [] 266 | for argument in self.arguments: 267 | argument_info = argument.get_info() 268 | if argument_info != None: 269 | service_info['inputs'].append(argument_info) 270 | 271 | return service_info 272 | #}}} 273 | 274 | #{{{def make_cmd(self, args): 275 | def make_cmd(self, args): 276 | # //pair flags with parameter in optional case. Replace_variable..Field might be array 277 | for arg in self.arguments: 278 | if arg.fixed: 279 | self.variable_table['$' + arg.name] = arg.value 280 | else: 281 | input_val = args.get(arg.name) 282 | if arg.type_test(input_val): 283 | self.variable_table['$' + arg.name] = input_val 284 | else: 285 | raise Exception('Type mismatch for argument ' + \ 286 | arg.name + \ 287 | '. Expected ' + \ 288 | arg.type) 289 | #DO not call replace variable on something that is optional and not provided. 290 | for i in range(len(self.command)): 291 | self.command[i] = Service.replace_variable(self.command[i], 292 | self.variable_table) 293 | return self.command 294 | #}}} 295 | 296 | #{{{ def args_match(self, args): 297 | def args_match(self, args): 298 | 299 | variable_args = [] 300 | for argument in self.arguments: 301 | if not argument.fixed: 302 | variable_args.append(argument.name) 303 | 304 | extra = False 305 | for arg in args: 306 | if arg == 'service' or arg == 'token': 307 | continue 308 | if arg not in variable_args: 309 | extra = True 310 | break 311 | 312 | if extra: 313 | return False 314 | 315 | all_there = True 316 | for arg in variable_args: 317 | if arg not in args: 318 | all_there = False 319 | 320 | if not all_there: 321 | return False 322 | 323 | return True 324 | #}}} 325 | 326 | #{{{ def process_result(self, out_file_name): 327 | def process_result(self, out_file_name): 328 | sys.stderr.write("\n\n\nInside process result {0}\n\n\n".format(out_file_name)) 329 | if self.output.type == 'text_stream': 330 | result = {"success": 1} 331 | out = [] 332 | for line in open(out_file_name, 'r'): 333 | out.append(line.rstrip().split(self.output.sep)) 334 | result['result'] = out 335 | return json.dumps(result) 336 | elif self.output.type == 'file': 337 | self.output.value = Service.replace_variable(self.output.value, 338 | self.variable_table) 339 | return send_file(self.output.value, 340 | self.output.mimetype) 341 | #}}} 342 | 343 | #{{{def copy(self): 344 | def copy(self): 345 | name = self.name 346 | command = self.command[:] 347 | arguments = [] 348 | 349 | for argument in self.arguments: 350 | arguments.append( argument.copy() ) 351 | 352 | output = self.output.copy() 353 | group="None" 354 | if(hasattr(self, 'group')): 355 | group = self.group 356 | 357 | return Service(name, command, arguments, output,group) 358 | #}}} 359 | 360 | #}}} 361 | 362 | #{{{class Server: 363 | class Server: 364 | #{{{def error(msg): 365 | @staticmethod 366 | def error(msg): 367 | err = { 'exception' : msg, "success": 0} 368 | return json.dumps(err) 369 | #}}} 370 | 371 | #{{{def load(config_file): 372 | @staticmethod 373 | def load(config_file): 374 | sys.stderr.write("hello there from inside load method ..{0}..\n\n\n ".format(config_file)) 375 | # try: 376 | # f = open(config_file, 'r') 377 | # except ExceInside service class of cm2web from my CGI script.ption as e: 378 | # sys.exit('ERROR loading config file. "' + str(e) + '"') 379 | 380 | try: 381 | print("Trying to open file",config_file); 382 | server_config = settings.Settings(type= "Services",filepath = config_file) 383 | # server_config = json.load(f) 384 | except Exception as e: 385 | sys.exit('ERROR loading server config "' + str(e) + '"') 386 | 387 | # f.close() 388 | 389 | services = {} 390 | for service_config in server_config: 391 | service = Service.load(service_config) 392 | services[service.name] = service 393 | 394 | return Server(services) 395 | #}}} 396 | 397 | #{{{def __init__(self, services): 398 | def __init__(self, services): 399 | self.services = services 400 | #}}} 401 | 402 | #{{{def get_info(self): 403 | def get_info(self): 404 | server_info = {} 405 | for service in self.services: 406 | server_info[service] = self.services[service].get_info() 407 | return server_info 408 | #}}} 409 | 410 | #{{{def has_service(self, service): 411 | def has_service(self, service): 412 | return service in self.services 413 | #}}} 414 | #}}} 415 | 416 | #{{{ class Client: 417 | class Client: 418 | #{{{class Service: 419 | class Service: 420 | #{{{ def load(config): 421 | @staticmethod 422 | def load(config): 423 | name = config['name'] 424 | output = config['output']['type'] 425 | inputs = {} 426 | for i in config['inputs']: 427 | inputs[i['name']] = i['type'] 428 | 429 | return Client.Service(name, inputs, output) 430 | #}}} 431 | 432 | #{{{def __init__(self, name, inputs, output): 433 | def __init__(self, name, inputs, output): 434 | self.name = name 435 | self.inputs = inputs 436 | self.output = output 437 | #}}} 438 | #}}} 439 | 440 | #{{{ def connect(url): 441 | @staticmethod 442 | def connect(url): 443 | c = Client(url) 444 | return c 445 | #}}} 446 | 447 | #{{{ def __init__(self, url): 448 | def __init__(self, url): 449 | self.server_url = url 450 | 451 | self.service_def = None 452 | try: 453 | r = requests.get(url + '/info') 454 | self.service_def = json.loads(r.text) 455 | except requests.exceptions.ConnectionError: 456 | raise requests.exceptions.ConnectionError 457 | 458 | self.services = {} 459 | 460 | for service in self.service_def: 461 | s = Client.Service.load(self.service_def[service]) 462 | self.services[s.name] = s 463 | #}}} 464 | 465 | #{{{def run(self, service, **kwargs): 466 | def run(self, service, **kwargs): 467 | if service not in self.service_def: 468 | raise Exception('Service "' + service + '" not supported.') 469 | 470 | for i in self.services[service].inputs: 471 | if i not in kwargs: 472 | raise Exception('Missing argument "' + i + '".') 473 | 474 | payload = {'service' : service} 475 | for kwarg in kwargs: 476 | if kwarg not in self.services[service].inputs: 477 | raise Exception('Invalid argument "' + kwarg + '".') 478 | 479 | if not Argument.type_check(kwargs[kwarg], 480 | self.services[service].inputs[kwarg]): 481 | raise Exception('Invalid type for ' + \ 482 | kwarg + '. Expected ' + \ 483 | self.services[service].inputs[kwarg]) 484 | payload[kwarg] = kwargs[kwarg] 485 | 486 | r = requests.get(self.server_url, params=payload) 487 | 488 | j = json.loads(r.text) 489 | 490 | if j['success'] != 1: 491 | message = 'Error.' 492 | if 'message' in j: 493 | message += " " + j['message'] 494 | if 'exception' in j: 495 | message += " " + j['exception'] 496 | raise Exception(message) 497 | return j['result'] 498 | #}}} 499 | #}}} 500 | -------------------------------------------------------------------------------- /src/database_connection.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | from sqlite3 import Error 3 | import cmd2web 4 | import sys 5 | import datetime 6 | class DatabaseConnection: 7 | def __init__(self,db_file): 8 | try: 9 | self.conn = sqlite3.connect(db_file) 10 | # return self.conn 11 | except Error as e: 12 | print(e) 13 | # return None 14 | 15 | def close(self): 16 | self.conn.close() 17 | 18 | def get_restricted_access(self,group_list): 19 | cur = self.conn.cursor() 20 | query='SELECT RestrictedGroup FROM Groups WHERE GroupName IN (%s)' % ','.join('"{}"'.format(i) for i in group_list) 21 | sys.stderr.write("\n\n\nQuery: {0}\n\n\n".format(query)) 22 | cur.execute(query) 23 | 24 | rows = cur.fetchall() 25 | give_access=False 26 | if(len(rows) > 0): 27 | for i in rows: 28 | if i[0]==1: 29 | give_access=True 30 | break 31 | else: 32 | give_access=False 33 | if(give_access==True): 34 | return True 35 | else: 36 | return False 37 | # [restricted_acess]=rows[0] 38 | # # 0 means restricted group so token will be needed 39 | # if(restricted_acess == 1): 40 | # return True 41 | # else: 42 | # return False 43 | 44 | else: 45 | # //Error Record does not exist for the servicename 46 | return cmd2web.Server.error('Record does not exist for the service') 47 | 48 | def check_token_access(self,group_list,token): 49 | cur = self.conn.cursor() 50 | query='select k.groupID, k.Expiry from Keys k join Groups s on s.GroupID = k.GroupID where s.GroupName in (%s)' % ','.join('"{}"'.format(i) for i in group_list) + ' and k.token={0}'.format(token) 51 | sys.stderr.write("\n\n\nQuery: {0}\n\n\n".format(query)) 52 | cur.execute(query) 53 | 54 | rows = cur.fetchall() 55 | if(len(rows) > 0): 56 | group_id, date_expiry_str = rows[0] 57 | format_str = "%m-%d-%Y" 58 | expiry_date = datetime.datetime.strptime(date_expiry_str, format_str) 59 | current_date = datetime.datetime.now() 60 | if(current_date<=expiry_date): 61 | return True 62 | else: 63 | return cmd2web.Server.error('Wrong or expired token. Access Denied') 64 | 65 | else: 66 | # //Error 67 | return False 68 | 69 | 70 | # if __name__ == '__main__': 71 | # s = DatabaseConnection("../DBScript/CMD2WEB.sqlite") 72 | # print(s.get_restricted_access("rmsk")) 73 | # print(s.get_restricted_access("rmsk2")) 74 | # print(s.check_token_access("rmsk","12223453445")) -------------------------------------------------------------------------------- /src/server.py: -------------------------------------------------------------------------------- 1 | #!//usr/bin/env python 2 | import subprocess 3 | from database_connection import DatabaseConnection 4 | import json 5 | import sys 6 | from flask import Flask,request,send_file,abort,Response 7 | import argparse 8 | import io 9 | import re 10 | import random 11 | import logging 12 | import cmd2web 13 | import os 14 | import settings 15 | #from OpenSSL import SSL 16 | from flask_cors import CORS, cross_origin 17 | from flask import render_template 18 | from pathlib import Path 19 | from werkzeug.utils import secure_filename 20 | #from logtest import setup_logging 21 | #setup_logging() 22 | current_directory= os.path.dirname(__file__) 23 | template_dir=os.path.join(current_directory, '../web_client') 24 | static_dir=os.path.join(current_directory, '../web_client') 25 | app = Flask(__name__,template_folder=template_dir,static_url_path="/web_client", static_folder=static_dir) 26 | app.debug = True 27 | CORS(app) 28 | config = None 29 | server = None 30 | timeout=10 31 | apache_server = False 32 | logger = logging.getLogger(__name__) 33 | logger.info("Starting the application.") 34 | app.config['UPLOAD_FOLDER']='/tmp' 35 | app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 36 | apache_config_file_path=os.path.join(current_directory, '../ex_configs/apache_conf.yaml') 37 | apache_file = Path(apache_config_file_path) 38 | if apache_file.is_file(): 39 | logger.info("Using Apache server.") 40 | apache_server = True 41 | else: 42 | logger.info("Using python server.") 43 | apache_server = False 44 | '''Get the boolean value for apache server''' 45 | 46 | add_accesss_control = True 47 | # myenvvar = os.environ["my_config_file"] 48 | # sys.stderr.write("hello there from my CGI script.{0}\n".format(myenvvar)) 49 | @app.after_request 50 | def after_request(response): 51 | if add_accesss_control: 52 | response.headers.add('Access-Control-Allow-Origin', '*') 53 | response.headers.add('Access-Control-Allow-Headers', 54 | 'Content-Type,Authorization') 55 | response.headers.add('Access-Control-Allow-Methods', 56 | 'GET,PUT,POST,DELETE,OPTIONS') 57 | return response 58 | 59 | @app.route('/info') 60 | def info(): 61 | logger.info("Getting information about the aplication.") 62 | sys.stderr.write("hello there from info method my CGI script.======={0}=======\n".format(server)) 63 | return json.dumps(server.get_info()) 64 | 65 | @app.route("/f",methods=['POST','PUT']) 66 | def file_upload(): 67 | if request.method == 'POST': 68 | grep_pattern = request.args.get('pattern') 69 | f = request.files['file'] 70 | filename = secure_filename(f.filename) 71 | filepath=os.path.join(app.config['UPLOAD_FOLDER'], filename) 72 | f.save(filepath) 73 | argument = request.args.copy() 74 | argument['file']=filepath 75 | result = operation_check(argument) 76 | os.remove(filepath) 77 | return result 78 | 79 | @app.route("/webapp") 80 | def index(): 81 | return render_template("simple.html"); 82 | 83 | def operation_check(argument): 84 | database_file_path = os.path.join(current_directory, "../DBScript/CMD2WEB.sqlite") 85 | database_object = DatabaseConnection(database_file_path) 86 | 87 | service = request.args.get('service') 88 | 89 | if not service: 90 | logger.error("No service specified.") 91 | return cmd2web.Server.error('No service specified') 92 | 93 | if not server.has_service(service): 94 | logger.error("Unknown service.") 95 | return cmd2web.Server.error('Unknown service') 96 | 97 | if not server.services[service].args_match(argument): 98 | logger.error('Argument mismatch') 99 | return cmd2web.Server.error('Argument mismatch') 100 | 101 | service_instance = server.services[service].copy() 102 | sys.stderr.write("\n\n\nService Instance: {0}\n\n\n".format(service_instance)) 103 | if (hasattr(service_instance, 'group')): 104 | restricted = database_object.get_restricted_access(service_instance.group) 105 | if (restricted == True): 106 | # Check if token is present in parameter 107 | token = request.args.get('token'); 108 | if not token: 109 | logger.error("Access restricted without token.") 110 | return cmd2web.Server.error('Access restricted without token.') 111 | token_access = database_object.check_token_access(service_instance.group, token) 112 | if (token_access == True): 113 | return process_service(service_instance,argument) 114 | else: 115 | logger.error("Wrong or expired token. Access Denied.") 116 | return cmd2web.Server.error('Wrong or expired token. Access Denied') 117 | else: 118 | return process_service(service_instance,argument) 119 | else: 120 | return process_service(service_instance,argument) 121 | 122 | @app.route('/') 123 | def service(): 124 | #Note: Add the path to the settings file 125 | database_file_path = os.path.join(current_directory, "../DBScript/CMD2WEB.sqlite") 126 | database_object = DatabaseConnection(database_file_path) 127 | 128 | service = request.args.get('service') 129 | 130 | if not service: 131 | logger.error("No service specified.") 132 | return cmd2web.Server.error('No service specified') 133 | 134 | if not server.has_service(service): 135 | logger.error("Unknown service.") 136 | return cmd2web.Server.error('Unknown service') 137 | 138 | if not server.services[service].args_match(request.args): 139 | logger.error('Argument mismatch') 140 | return cmd2web.Server.error('Argument mismatch') 141 | 142 | service_instance = server.services[service].copy() 143 | sys.stderr.write("\n\n\nService Instance: {0}\n\n\n".format(service_instance)) 144 | if(hasattr(service_instance, 'group')): 145 | restricted = database_object.get_restricted_access(service_instance.group) 146 | if(restricted == True): 147 | #Check if token is present in parameter 148 | token = request.args.get('token'); 149 | if not token: 150 | logger.error("Access restricted without token.") 151 | return cmd2web.Server.error('Access restricted without token.') 152 | token_access = database_object.check_token_access(service_instance.group,token) 153 | if(token_access == True): 154 | return process_service(service_instance,request.args) 155 | else: 156 | logger.error("Wrong or expired token. Access Denied.") 157 | return cmd2web.Server.error('Wrong or expired token. Access Denied') 158 | else: 159 | return process_service(service_instance,request.args) 160 | else: 161 | return process_service(service_instance,request.args) 162 | 163 | ##################Extra for Apache server - start############# 164 | 165 | def process_service(service_instance,argument): 166 | try: 167 | cmd = service_instance.make_cmd(argument) 168 | except Exception as e: 169 | return cmd2web.Server.error(str(e)) 170 | 171 | # print(' '.join(cmd)) 172 | 173 | out_file_name = '/tmp/' + str(random.randint(0,sys.maxsize)) + '.out' 174 | f = open(out_file_name, 'w') 175 | try: 176 | proc = subprocess.check_call(cmd, 177 | stderr=None, 178 | stdout=f, 179 | timeout=timeout) 180 | sys.stderr.write("\n\n\Command: {0}\n\n\n".format(cmd)) 181 | # res = subprocess.check_output(cmd,stderr=sys.stderr) 182 | # sys.stderr.write("\n\n\nResult: {0}\n\n\n".format(res)) 183 | except subprocess.TimeoutExpired as e: 184 | print('Time Out') 185 | logger.error("Time limit for current request exceed.") 186 | return cmd2web.Server.error('Time limit for current request exceed.') 187 | except Exception as e: 188 | return cmd2web.Server.error(str(e)) 189 | 190 | f.close() 191 | sys.stderr.write("\n\n\nOut file name after processing: {0}\n\n\n".format(out_file_name)) 192 | return service_instance.process_result(out_file_name) 193 | 194 | def apache_conf(): 195 | external_server = settings.Settings(type= "External_Server",filepath = apache_config_file_path) 196 | sys.stderr.write("hello external_server from my CGI script-----{0}----\n".format(external_server)) 197 | if(external_server): 198 | args = settings.Settings(type= "Default_Apache_Conf",filepath = apache_config_file_path) 199 | timeout = args['Timeout'] 200 | sys.stderr.write("hello there from inside main method my CGI script.===={0}==={1}===={2}===\n".format(timeout,args['Default_Config_File'],args['No_Access_Control_Header'])) 201 | server = cmd2web.Server.load(args['Default_Config_File']) 202 | if args['No_Access_Control_Header']: 203 | add_accesss_control = False 204 | return server 205 | 206 | if(apache_server): 207 | #Setting config object for the apache server. 208 | server = apache_conf() 209 | ##################Extra for Apache server - end############# 210 | 211 | 212 | if __name__ == '__main__': 213 | sys.stderr.write("hello there from inside main method my CGI script.==============\n") 214 | parser = argparse.ArgumentParser(description='command to web server.') 215 | 216 | # parser.add_argument('--config', 217 | # dest='config', 218 | # required=True, 219 | # help='Configuration file.') 220 | parser.add_argument('--config', 221 | dest='config', 222 | required=True, 223 | help='Configuration file.') 224 | 225 | parser.add_argument('--port', 226 | dest='port', 227 | type=int, 228 | default="8080", 229 | help='Port to run on (default 8080)') 230 | 231 | parser.add_argument('--host', 232 | dest='host', 233 | default="127.0.0.1", 234 | help='Server hos (default 127.0.0.1)') 235 | 236 | parser.add_argument('--timeout', 237 | dest='timeout', 238 | type=int, 239 | default=10, 240 | help='Max runtime (sec) for a command (default 10)') 241 | 242 | parser.add_argument('--ssl_key', 243 | dest='ssl_key', 244 | help='Path to the SSL key') 245 | 246 | parser.add_argument('--ssl_cert', 247 | dest='ssl_cert', 248 | help='Path to the SSL key') 249 | 250 | parser.add_argument('--no_access_control_header', 251 | dest='no_access_control_header', 252 | action='store_true', 253 | help='Path to the SSL key') 254 | 255 | args = parser.parse_args() 256 | timeout = args.timeout 257 | # app.logger.info('testing info log'+args.config) 258 | server = cmd2web.Server.load(args.config) 259 | # server = cmd2web.Server.load('../ex_configs/example_config.yaml') 260 | 261 | if args.no_access_control_header: 262 | add_accesss_control = False 263 | 264 | 265 | if args.ssl_key and args.ssl_cert: 266 | context = SSL.Context(SSL.SSLv23_METHOD) 267 | context.use_privatekey_file('yourserver.key') 268 | context.use_certificate_file('yourserver.crt') 269 | try: 270 | app.run(host=args.host, port=args.port, ssl_context=context) 271 | except Exception as e: 272 | sys.exit('ERROR starting the server. "' + str(e) + '"') 273 | else: 274 | try: 275 | app.run(host=args.host, port=args.port) 276 | except Exception as e: 277 | sys.exit('ERROR starting the server. "' + str(e) + '"') 278 | -------------------------------------------------------------------------------- /src/settings.py: -------------------------------------------------------------------------------- 1 | import os 2 | import yaml 3 | import sys 4 | 5 | class Settings(): 6 | def __new__(self, type=None, filepath=None): 7 | try: 8 | current_directory= os.path.dirname(__file__) 9 | complete_file_path=os.path.join(current_directory, filepath) 10 | f = open(complete_file_path, 'r') 11 | self.all_file = yaml.safe_load(f) 12 | f.close() 13 | except Exception as e: 14 | sys.exit('ERROR loading config file. "' + str(e) + '"') 15 | try: 16 | if type: 17 | self.settings = self.all_file[type] 18 | else: 19 | self.settings = self.all_file 20 | return self.settings 21 | except Exception as e: 22 | sys.exit('ERROR processing config file. "' + str(e) + '"') 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/stix_client.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from cyvcf2 import VCF 3 | import argparse 4 | import cmd2web 5 | 6 | parser = argparse.ArgumentParser(description='Annotate VCF with STIX ws.') 7 | 8 | parser.add_argument('--host', 9 | dest='host', 10 | default="http://127.0.0.1:8080", 11 | help='Server address(default http://127.0.0.1:8080)') 12 | 13 | parser.add_argument('--vcf', 14 | dest='vcf_file_name', 15 | required=True, 16 | help='Name of VCF to annotate') 17 | 18 | args = parser.parse_args() 19 | 20 | vcf = VCF(args.vcf_file_name) 21 | 22 | s = cmd2web.Client.connect(args.host) 23 | 24 | vcf.add_info_to_header({'ID': 'STIX_NONZERO', 25 | 'Description': 'The number of samples in cohort with non-zero evidence', 26 | 'Type':'Integer', 'Number': '1'}) 27 | 28 | print (str(vcf.raw_header), end='', flush=True) 29 | 30 | for v in vcf: 31 | chrom = v.CHROM 32 | start = v.POS 33 | end = v.INFO.get('END') 34 | svtype = v.INFO.get('SVTYPE') 35 | cipos = v.INFO.get('CIPOS') 36 | ciend = v.INFO.get('CIEND') 37 | 38 | if None in [chrom, start, end, svtype]: 39 | continue 40 | 41 | if cipos == None: 42 | cipos = [0,0] 43 | 44 | if ciend == None: 45 | ciend = [0,0] 46 | 47 | if svtype not in ['DEL', 'DUP', 'INV']: 48 | continue 49 | 50 | #print [chrom, start, end, svtype, cipos, ciend] 51 | try: 52 | R = s.run('1kg', 53 | type=svtype, 54 | left_chrom=chrom, left_start=start+cipos[0], left_end=start+cipos[1], 55 | right_chrom=chrom, right_start=end+ciend[0], right_end=end+ciend[1]) 56 | except Exception as e: 57 | print(str(v), end='', flush=True) 58 | continue 59 | 60 | zero_one = [int(x) for x in R[0][2].split(':')] 61 | more_depths = [int(x) for x in R[0][4].split(':')] 62 | non_zero_depths = zero_one[1] + sum(more_depths) 63 | v.INFO["STIX_NONZERO"] = str(non_zero_depths) 64 | print(str(v), end='', flush=True) 65 | -------------------------------------------------------------------------------- /src/tabix_client.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from cyvcf2 import VCF 3 | import argparse 4 | import cmd2web 5 | 6 | parser = argparse.ArgumentParser(description='Annotate VCF with STIX ws.') 7 | 8 | parser.add_argument('--host', 9 | dest='host', 10 | default="http://127.0.0.1:8080", 11 | help='Server address(default http://127.0.0.1:8080)') 12 | 13 | parser.add_argument('--chromosome', 14 | dest='chromosome', 15 | required=True) 16 | 17 | parser.add_argument('--start', 18 | dest='start', 19 | required=True) 20 | 21 | parser.add_argument('--end', 22 | dest='end', 23 | required=True) 24 | 25 | parser.add_argument('--service', 26 | dest='service', 27 | required=True) 28 | 29 | 30 | args = parser.parse_args() 31 | 32 | s = cmd2web.Client.connect(args.host) 33 | 34 | try: 35 | R = s.run(args.service, 36 | chromosome=args.chromosome, 37 | start=args.start, 38 | end=args.end) 39 | except Exception as e: 40 | print(str(e)) 41 | 42 | for r in R: 43 | print('\t'.join(r)) 44 | -------------------------------------------------------------------------------- /test/data/bad.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name" : "ls" 4 | "arguments": [], 5 | "command" : [ "ls" ], 6 | "output" : 7 | { 8 | "type" : "text_stream", 9 | "sep" : " " 10 | } 11 | } 12 | ] 13 | -------------------------------------------------------------------------------- /test/data/test_commas.txt: -------------------------------------------------------------------------------- 1 | A,B,C,D 2 | E,F,G,H 3 | -------------------------------------------------------------------------------- /test/data/test_config.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name" : "tabs", 4 | "arguments" : 5 | [ 6 | { 7 | "name" : "file", 8 | "fixed" : "true", 9 | "type" : "string", 10 | "value" : "../data/test_tabs.txt" 11 | } 12 | ], 13 | "command" : [ "cat", "$file" ], 14 | "output" : 15 | { 16 | "type" : "text_stream", 17 | "sep" : "\t" 18 | } 19 | 20 | }, 21 | { 22 | "name" : "commas", 23 | "arguments" : 24 | [ 25 | { 26 | "name" : "file", 27 | "fixed" : "true", 28 | "type" : "string", 29 | "value" : "../data/test_commas.txt" 30 | } 31 | ], 32 | "command" : [ "cat", "$file" ], 33 | "output" : 34 | { 35 | "type" : "text_stream", 36 | "sep" : "," 37 | } 38 | } 39 | 40 | ] 41 | -------------------------------------------------------------------------------- /test/data/test_tabs.txt: -------------------------------------------------------------------------------- 1 | A B C D 2 | E F G H 3 | -------------------------------------------------------------------------------- /test/func/cmd2web_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #curl -s http://127.0.0.1:8080/info > test.json 3 | # 4 | #services=` curl -s http://127.0.0.1:8080/info | jq -r 'keys | join(" ")' ` 5 | # 6 | # 7 | #for service in services; do 8 | #echo $service 9 | #done 10 | #curl -s http://127.0.0.1:8080/?service=chainSelf 11 | 12 | test -e ssshtest || wget -q https://raw.githubusercontent.com/ryanlayer/ssshtest/master/ssshtest 13 | . ssshtest 14 | 15 | server=../../src/server.py 16 | test_config=../data/test_config.json 17 | bad_config=../data/bad.json 18 | test_port=8569 19 | 20 | run test_no_config_given \ 21 | $server 22 | assert_exit_code 2 23 | 24 | run test_on_reserve_port \ 25 | $server --config $test_config --port 80 26 | assert_exit_code 1 27 | assert_in_stderr "ERROR starting the server" 28 | assert_in_stderr "[Errno 13] Permission denied" 29 | 30 | run bad_json_config \ 31 | $server --config $bad_config 32 | assert_exit_code 1 33 | assert_in_stderr "ERROR loading config file." 34 | 35 | BGREEN='\033[1;32m' 36 | NC='\033[0m' # No Color 37 | echo 38 | echo -e "${BGREEN}Starting test server${NC}" 39 | echo 40 | $server --config $test_config --port $test_port 1> /dev/null 2> /dev/null & 41 | 42 | sleep 5 43 | 44 | export server_pid=$! 45 | server_ret=$? 46 | 47 | if [ "$server_ret" -ne "0" ]; then 48 | BRED='\033[1;31m' 49 | echo -e "${BRED}***************************************************${NC}" 50 | echo -e "${BRED}* ${NC}" 51 | echo -e "${BRED}* Could not start the server, with command ${NC}" 52 | echo -e "${BRED}* $server --config $test_config --port $test_port ${NC}" 53 | echo -e "${BRED}* Exiting tests ${NC}" 54 | echo -e "${BRED}* ${NC}" 55 | echo -e "${BRED}***************************************************${NC}" 56 | exit 57 | fi 58 | 59 | function tear_down { 60 | echo 61 | echo -e "${BGREEN}Shutting down test server${NC}" 62 | echo 63 | kill $server_pid 64 | } 65 | 66 | run test_running_server_exception \ 67 | curl "http://127.0.0.1:$test_port" 68 | assert_exit_code 0 69 | assert_in_stdout '{"exception": "No service specified", "success": 0}' 70 | 71 | run test_get_info \ 72 | curl "http://127.0.0.1:$test_port/info" 73 | assert_exit_code 0 74 | assert_in_stdout '{"tabs": {"name": "tabs", "output": {"type": "text_stream"}, "inputs": []}, "commas": {"name": "commas", "output": {"type": "text_stream"}, "inputs": []}}' 75 | 76 | run test_get_info \ 77 | curl "http://127.0.0.1:$test_port/?service=tabs" 78 | assert_exit_code 0 79 | assert_in_stdout '{"success": 1, "result": [["A", "B", "C", "D"], ["E", "F", "G", "H"]]}' 80 | 81 | run test_get_info \ 82 | curl "http://127.0.0.1:$test_port/?service=commas" 83 | assert_exit_code 0 84 | assert_in_stdout '{"success": 1, "result": [["A", "B", "C", "D"], ["E", "F", "G", "H"]]}' 85 | 86 | -------------------------------------------------------------------------------- /web_client/cmd2web.js: -------------------------------------------------------------------------------- 1 | var server_url = ''; 2 | var server_info = null; 3 | var server_result = null; 4 | var selectedService = null; 5 | 6 | $(document).on("click","#submit_btn",function(e) { 7 | e.preventDefault(); 8 | var formData = $("#form1").submit(function (e) { 9 | return; 10 | }) 11 | var formData = new FormData(formData[0]); 12 | $.ajax({ 13 | url: $('#form1').attr('action'), 14 | type: 'POST', 15 | data: formData, 16 | success: function (response) { 17 | server_result = JSON.parse(response); 18 | showData(); 19 | console.log(response); 20 | }, 21 | contentType: false, 22 | processData: false, 23 | cache: false 24 | }, false) 25 | }); 26 | 27 | function setForm() { 28 | 29 | 30 | var formData = $("#form1").submit(function (e) { 31 | return; 32 | }) 33 | 34 | var formData = new FormData(formData[0]); 35 | $.ajax({ 36 | url: $('#form1').attr('action'), 37 | type: 'POST', 38 | data: formData, 39 | success: function (response) { 40 | server_result = JSON.parse(response); 41 | showData(); 42 | console.log(response); 43 | }, 44 | contentType: false, 45 | processData: false, 46 | cache: false 47 | }, false) 48 | }; 49 | 50 | $.urlParam = function(name){ 51 | var results = new RegExp('[\?&]' + 52 | name + 53 | '=([^&#]*)').exec(window.location.href); 54 | if (results==null){ 55 | return null; 56 | } else { 57 | return results[1] || 0; 58 | } 59 | } 60 | 61 | $(document).ready(function() { 62 | server_param = $.urlParam('server'); 63 | 64 | if (server_param){ 65 | server_url = decodeURIComponent(server_param); 66 | $('#server_text').val(server_url); 67 | connect_to_server(); 68 | } 69 | 70 | }) 71 | 72 | function setServer() { 73 | 74 | server_url = $('#server_text').val(); 75 | 76 | if (server_url.length == 0) { 77 | return 78 | } 79 | 80 | connect_to_server(); 81 | } 82 | 83 | 84 | function connect_to_server() { 85 | 86 | var get_server_info_promise = get_server_info(server_url + '/info').then(function(data) { 87 | server_info = data 88 | }); 89 | 90 | Promise.all([get_server_info_promise]).then( 91 | function(values) { 92 | setInputForm(); 93 | } 94 | ); 95 | } 96 | 97 | function get_server_info(url) { 98 | return new Promise( function(resolve, reject) { 99 | $.get(url, 100 | function (data) { 101 | resolve( JSON.parse(data) ); 102 | } 103 | ); 104 | }); 105 | } 106 | 107 | function setInputForm(){ 108 | 109 | $('#services').empty(); 110 | 111 | $('#services').append(''); 112 | var serviceSelect = document.getElementById('service-select'); 113 | 114 | 115 | for (var service in server_info) { 116 | serviceSelect.options[serviceSelect.options.length] = new Option(service, service); 117 | } 118 | 119 | $('#services').append(''); 120 | 121 | 122 | service_param = $.urlParam('service'); 123 | 124 | if (service_param) { 125 | if ( service_param in server_info) { 126 | selectedService = decodeURIComponent(service_param); 127 | $("#set-service").val(selectedService).change(); 128 | load_service(); 129 | } 130 | } 131 | 132 | } 133 | 134 | function setService() { 135 | 136 | $('#input').empty(); 137 | $('#output').empty(); 138 | 139 | var serviceSelect = document.getElementById('service-select'); 140 | selectedService = serviceSelect.options[serviceSelect.selectedIndex].value; 141 | 142 | load_service(); 143 | } 144 | 145 | 146 | function load_service() { 147 | var file_upload= false; 148 | var all_vals = true; 149 | for (var i = 0; i < server_info[selectedService].inputs.length; ++i){ 150 | if(server_info[selectedService].inputs[i].name == "file"){ 151 | file_upload=true; 152 | } 153 | } 154 | if(file_upload){ 155 | var action = "/f"; 156 | var form_str = '
'; 157 | $('#input').append(form_str); 158 | for (var i = 0; i < server_info[selectedService].inputs.length; ++i){ 159 | //console.log(server_info[selectedService].inputs[i]); 160 | if(server_info[selectedService].inputs[i].name == "file"){ 161 | var input_str = ''; 165 | } 166 | else{ 167 | var input_str = ''; 171 | } 172 | 173 | 174 | $('#form1').append(input_str); 175 | 176 | var this_input = $.urlParam(server_info[selectedService].inputs[i].name); 177 | if (this_input) { 178 | var this_val = decodeURIComponent(this_input); 179 | $('#' + server_info[selectedService].inputs[i].name ).val(this_val); 180 | } else { 181 | all_vals = false; 182 | } 183 | } 184 | 185 | $('#form1').append(''); 186 | $('#form1').append(''); 187 | $('#input').append('
'); 188 | } 189 | else{ 190 | for (var i = 0; i < server_info[selectedService].inputs.length; ++i){ 191 | //console.log(server_info[selectedService].inputs[i]); 192 | 193 | input_str = ''; 197 | 198 | $('#input').append(input_str); 199 | 200 | var this_input = $.urlParam(server_info[selectedService].inputs[i].name); 201 | if (this_input) { 202 | var this_val = decodeURIComponent(this_input); 203 | $('#' + server_info[selectedService].inputs[i].name ).val(this_val); 204 | } else { 205 | all_vals = false; 206 | } 207 | } 208 | token_str = ''; 210 | if(file_upload){ 211 | if(server_info[selectedService].group){ 212 | $('#form1').append(token_str); 213 | } 214 | } 215 | else{ 216 | if(server_info[selectedService].group){ 217 | $('#input').append(token_str); 218 | } 219 | } 220 | 221 | $('#input').append(''); 222 | 223 | } 224 | 225 | 226 | 227 | if (all_vals) { 228 | getData(); 229 | } 230 | } 231 | 232 | function getData() { 233 | var file_upload= false; 234 | var serviceSelect = document.getElementById('service-select'); 235 | var selectedService = serviceSelect.options[serviceSelect.selectedIndex].value; 236 | 237 | $('#loading').append('loading...'); 238 | 239 | $('#output').empty(); 240 | 241 | url_params = "?service=" + selectedService; 242 | 243 | console.log(server_info[selectedService]); 244 | 245 | for (var i = 0; i < server_info[selectedService].inputs.length; ++i){ 246 | if(server_info[selectedService].inputs[i].name == "file"){ 247 | file_upload=true; 248 | } 249 | } 250 | for (var i = 0; i < server_info[selectedService].inputs.length; ++i){ 251 | if(server_info[selectedService].inputs[i].name != "file"){ 252 | var inputName = server_info[selectedService].inputs[i].name; 253 | var inputValue = document.getElementById(inputName).value; 254 | url_params += '&' + inputName + '=' + inputValue 255 | } 256 | } 257 | if(server_info[selectedService].group){ 258 | //It has group so token needs to be passed 259 | //check if token present 260 | var tokenVal = document.getElementById("token").value; 261 | url_params += '&' + "token" + '=' + tokenVal 262 | } 263 | if(file_upload){ 264 | var form_elem = document.getElementById('form1'); 265 | form_elem.action = form_elem.action+url_params; 266 | // var submit_elem = document.getElementById('submit_btn'); 267 | // submit_elem.click(); 268 | setForm(); 269 | } 270 | else{ 271 | var get_data_promise = get_data(server_url + url_params).then(function(data) { 272 | server_result = data; 273 | }); 274 | 275 | Promise.all([get_data_promise]).then( 276 | function(values) { 277 | showData(); 278 | console.log('DONE 1'); 279 | } 280 | ); 281 | } 282 | 283 | } 284 | 285 | 286 | function get_data(url) { 287 | return new Promise( function(resolve, reject) { 288 | $.get(url, 289 | function (data) { 290 | console.log(data); 291 | resolve( JSON.parse(data) ); 292 | console.log('DONE'); 293 | } 294 | ); 295 | }); 296 | } 297 | 298 | 299 | // request('GET', 'http://google.com') 300 | // .then(function (e) { 301 | // console.log(e.target.response); 302 | // }, function (e) { 303 | // // handle errors 304 | // }); 305 | function showData() { 306 | if (server_result.success != 1) { 307 | $('#loading').empty(); 308 | $('#output').append('Command did not complete successfully'); 309 | $('#exception').append(server_result.exception); 310 | } else { 311 | console.log('0'); 312 | $('#loading').empty(); 313 | var result = server_result.result; 314 | $('#output').append(''); 315 | for (var i = 0; i < result.length; ++i){ 316 | row = ''; 317 | for (var j = 0; j < result[i].length; ++j){ 318 | row += ''; 319 | } 320 | row += ''; 321 | $('#output_table').append(row); 322 | } 323 | $('#output').append('
' + result[i][j] + '
'); 324 | } 325 | } 326 | -------------------------------------------------------------------------------- /web_client/simple.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |

Command to Web Interface

12 | 13 |
14 | 15 | 16 |
17 | 18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /web_client/site.css: -------------------------------------------------------------------------------- 1 | body 2 | { 3 | font-family: Arial, sans-serif; 4 | font-size: 11pt; 5 | } 6 | 7 | input[type="text"] 8 | { 9 | font-size: 11pt; 10 | } 11 | 12 | select 13 | { 14 | font-size: 11pt; 15 | } 16 | 17 | button 18 | { 19 | background-color: #555555; 20 | color: white; 21 | font-size: 11pt; 22 | } 23 | 24 | 25 | #server 26 | { 27 | padding-top: 10px; 28 | padding-bottom: 10px; 29 | display: grid; 30 | width: 25%; 31 | } 32 | 33 | #services 34 | { 35 | padding-top: 10px; 36 | padding-bottom: 10px; 37 | display: grid; 38 | width: 25%; 39 | } 40 | 41 | #input 42 | { 43 | padding-top: 10px; 44 | padding-bottom: 10px; 45 | display: grid; 46 | width: 25%; 47 | } 48 | 49 | #output td, #output th 50 | { 51 | border: 0px; 52 | padding: 5px; 53 | } 54 | 55 | #output tr:nth-child(even) 56 | { 57 | background-color: #f2f2f2; 58 | } 59 | 60 | #output tr:hover 61 | { 62 | background-color: #ddd; 63 | } 64 | 65 | --------------------------------------------------------------------------------