diff --git a/services/webapp/code/rosetta/core_app/management/commands/core_app_populate.py b/services/webapp/code/rosetta/core_app/management/commands/core_app_populate.py index feadcdfb88fd27dce06dde834639558d9ad8b1b2..3d74c37525d72f5e5e6724031f69302a61381ab9 100644 --- a/services/webapp/code/rosetta/core_app/management/commands/core_app_populate.py +++ b/services/webapp/code/rosetta/core_app/management/commands/core_app_populate.py @@ -48,9 +48,9 @@ class Command(BaseCommand): To change it, head to the <a href="/admin">admin</a> page and edit the <code>Text</code> model. <br/><br/> The default installation provides a test user register with email <code>testuser@rosetta.platform</code> - and password <code>testpass</code>, which you can use to login on the menu on the right or using the link - below and give Rosetta a try immediately. If you run with the default docker-compose file (i.e. you just - run <code>rosetta/setup</code>), then you will also have a few demo computing resources you can play with + and password <code>testpass</code>, which you can use to login on the menu on the rightand give Rosetta + a try immediately. If you run with the default docker-compose file (i.e. you just run + <code>rosetta/setup</code>), then you will also have a few demo computing resources you can play with out-of-the-box, including a small Slurm cluster. Otherwise, you will need to setup your own computing resources either platform-wide or as user. </div> diff --git a/services/webapp/code/rosetta/core_app/templates/components/container.html b/services/webapp/code/rosetta/core_app/templates/components/container.html index c5838cff06e888a6699b304a1db7298191ceb8f4..ccdb78f9b903c5d593ade644f80430161f567057 100644 --- a/services/webapp/code/rosetta/core_app/templates/components/container.html +++ b/services/webapp/code/rosetta/core_app/templates/components/container.html @@ -112,7 +112,7 @@ </div> <div style="margin-bottom:13px; margin-top: 3px; text-align:center"> - <a href="/create_task?task_container_uuid={{ container.uuid }}" class="btn btn-light" style="border: #c0c0c0 1px solid"> <i class="fa fa-play" style="color:green"></i></a> + <a href="/create_task?task_container_uuid={{ container.uuid }}&step=two" class="btn btn-light" style="border: #c0c0c0 1px solid"> <i class="fa fa-play" style="color:green"></i></a> </div> diff --git a/services/webapp/code/rosetta/core_app/templates/containers.html b/services/webapp/code/rosetta/core_app/templates/containers.html index badcfc47297efde80fde575c684c210bb6b6c974..22f1299d124cfb99226cf4560ac34936f5e58010 100644 --- a/services/webapp/code/rosetta/core_app/templates/containers.html +++ b/services/webapp/code/rosetta/core_app/templates/containers.html @@ -12,15 +12,24 @@ {% if data.container %} <h1><a href="/containers">Containers</a> <span style="font-size:18px"> / {{ data.container.name }}</span></h1> {% else %} + {% if data.mode == 'new_task' %} + <h1>New task</h1> + <hr/> + <h3>Step 1: choose software container.</h3> + <br/> + {% else %} <h1>Containers</h1> - <hr/> + {% endif %} + <div class="form-filter" style="margin-bottom:20px"> <form action="" method="POST"> + <input type="hidden" name="mode" value="{{data.mode}}"> + <input type="text" class="form-control" id="search_text" name="search_text" placeholder="Search..." style="width:200px; margin:0; display:inline" value="{{data.search_text}}" autofocus> <select class="form-control" id="search_type" name="search_type" style="width:120px; margin:0; display:inline"> diff --git a/services/webapp/code/rosetta/core_app/templates/create_task.html b/services/webapp/code/rosetta/core_app/templates/create_task.html index 3bfd578400fade9c634358cf97042f5c9479f839..8de1770a55a30a11171de3af8118e2a6a69252b2 100644 --- a/services/webapp/code/rosetta/core_app/templates/create_task.html +++ b/services/webapp/code/rosetta/core_app/templates/create_task.html @@ -11,45 +11,49 @@ <h1>New Task</h1> <hr> - {% if data.step == 'one' %} - <h3>Step 1: name, container and computing.</h3> - - <br/> - + {% if data.step == 'two' %} + <h3>Step 2: set task name and computing.</h3> + {% elif data.step == 'three' %} + <h3>Step 3: add authentication and computing details.</h3> + {% endif %} + + <br/> + + + <div style="width:300px; float:left; border: #e0e0e0 solid 1px; margin:10px; background:#f8f8f8; margin-bottom:15px"> + <div style="padding:10px; margin-top:5px; text-align:center; border-bottom: {{data.task_container.color}} solid 10px; "> + <a href="?uuid={{ container.uuid }}">{{ data.task_container.name }}</a> + {% if data.task_container.type == 'docker' %}<img src="/static/img/docker-logo.svg" style="height:18px; width:18px; margin-bottom:4px" />{% endif %} + {% if data.task_container.type == 'singularity' %}<img src="/static/img/singularity-logo.svg" style="height:18px; width:18px; margin-bottom:4px" />{% endif %} + </div> + <div style="padding:10px; height: 64px; vertical-align: middle;"> + <b>Image:</b> {{ data.task_container.image_name }}<br/> + <b>Version:</b> {{ data.task_container.image_tag }} + </div> + </div> + + {% if data.step == 'two' %} + <form action="/create_task/" method="POST"> {% csrf_token %} - <input type="hidden" name="step" value="one" /> + <input type="hidden" name="step" value="{{ data.next_step }}" /> + <input type="hidden" name="task_container_uuid" value="{{data.task_container.uuid}}"> + <div style="width:300px; float:left; border: #e0e0e0 solid 0px; margin:10px; background:#f8f8f8; margin-bottom:15px"> - <table class="dashboard" style="max-width:700px"> + <table style="max-width:100%; border: #e0e0e0 solid 1px; margin:0"> <tr><td colspan="2"></td></tr> <tr> <td><b>Task name </b></td> <td> - <input type="text" name="task_name" value="" placeholder="" size="23" required /> + <input type="text" name="task_name" value="" placeholder="" size="" required /> </td> </tr> - <tr> - <td><b>Task container</b></td><td> - {% if data.task_container %} - <select name="task_container_uuid"> - <option value="{{data.task_container.uuid}}" selected>{{data.task_container.name}} ({{data.task_container.type.title}})</option> - </select> - {% else %} - <select name="task_container_uuid" > - {% for container in data.containers %} - <option value="{{container.uuid}}">{{container.name}} ({{container.type.title}})</option> - {% endfor %} - </select> - {% endif %} - </td> - </tr> - <tr> - <td><b>Computing resource</b></td><td> + <td><b>Computing</b></td><td> <select name="task_computing_uuid" > {% for computing in data.computings %}} <option value="{{ computing.uuid }}">{{ computing.name}}</option> @@ -59,38 +63,30 @@ </tr> <tr> - <td colspan=2 align=center style="padding:20px"> + <td colspan=2 align=center style="padding:9px"> <input type="submit" value="Next"> </td> </tr> </table> + </div> </form> - {% elif data.step == 'two' %} - - <h3>Step 2: add authentication and computing details</h3> - - <br/> - <table class="dashboard" style="max-width:700px"> - <tr><td colspan="2"></td></tr> + {% elif data.step == 'three' %} + + + <div style="width:300px; float:left; border: #e0e0e0 solid 0px; margin:10px; background:#f8f8f8; margin-bottom:15px"> + <table style="width:100%; height:126px; border: #e0e0e0 solid 1px; margin:0"> - <tr> + <tr valign="bottom"> <td><b>Task name </b></td> <td> - <input type="text" name="task_name_RECAP" value="{{ data.task_name }}" placeholder="" size="23" disabled /> + <input type="text" name="task_name_RECAP" value="{{ data.task_name }}" placeholder="" size="" disabled /> </td> </tr> - <tr> - <td><b>Task container</b></td><td> - <select name="task_container_uuid_RECAP"> - <option value="" selected>{{data.task_container.name}} ({{data.task_container.type.title}})</option> - </select> - </td> - </tr> - <tr> - <td><b>Computing resource</b></td><td> + <tr valign="top"> + <td><b>Computing</b></td><td> <select name="task_computing_uuid_RECAP" > <option value="">{{ data.task_computing.name}}</option> </select> @@ -98,8 +94,10 @@ </tr> </table> + </div> + + <div style="width:640px; float:left; border: #e0e0e0 solid 0px; margin:10px; background:#f8f8f8; margin-bottom:15px"> - <br> {% if data.task_container.type == 'singularity' and not data.task_container.supports_dynamic_ports %} <div> <p style="font-size:15px; max-width:700px; margin-bottom:20px; margin-left:5px"> <i class="fa fa-exclamation-triangle" style="color:orange"></i> This container does not support dynamic ports and you are running it with Singularity, without network insulation. This means that if the container port is already occupied, it will not be able to start. @@ -114,7 +112,8 @@ <form action="/create_task/" method="POST"> {% csrf_token %} - <input type="hidden" name="step" value="two" /> + <input type="hidden" name="task_container_uuid" value="{{data.task_container.uuid}}"> + <input type="hidden" name="step" value="{{ data.next_step }}" /> <input type="hidden" name="task_name" value="{{ data.task_name }}" /> <input type="hidden" name="task_container_uuid" value="{{ data.task_container.uuid }}" /> <input type="hidden" name="task_computing_uuid" value="{{ data.task_computing.uuid }}" /> @@ -180,7 +179,7 @@ </td> </tr> --> - {% if data.task_computing.type == 'slurm' %} + {% if data.task_computing.access_method == 'slurm+ssh' %} <tr> <td><b>Computing options</b></td> <td> @@ -207,6 +206,7 @@ </tr> </table> </form> + </div> {% else %} diff --git a/services/webapp/code/rosetta/core_app/templates/main.html b/services/webapp/code/rosetta/core_app/templates/main.html index 6f9656ec38393be2ecd5f8a1137d843f979ad96f..a18904a676002628988980c7605c19c19cee2c18 100644 --- a/services/webapp/code/rosetta/core_app/templates/main.html +++ b/services/webapp/code/rosetta/core_app/templates/main.html @@ -24,18 +24,7 @@ </div> {% endif %} </div> - </div> - - <div style="display:table-row"> - <div class="text-vertical-bottom"> - {% if user.is_authenticated %} - <a href="/tasks" class="btn btn-dark btn-lg">Tasks</a> - <a href="/containers" class="btn btn-dark btn-lg">Containers</a> - {% else %} - <a href="/login" class="btn btn-dark btn-lg">Log In</a> - {% endif %} - </div> - </div> + </div> </header> {% include "footer.html" %} diff --git a/services/webapp/code/rosetta/core_app/templates/tasks.html b/services/webapp/code/rosetta/core_app/templates/tasks.html index 2f25d246ccd76cb89683f33bf3a38f9aadcb2d12..62179792dd8a2fbdd79e0ed7faa33c233238988c 100644 --- a/services/webapp/code/rosetta/core_app/templates/tasks.html +++ b/services/webapp/code/rosetta/core_app/templates/tasks.html @@ -28,7 +28,7 @@ {% if not data.task and not data.tasks %} <div class="row" style="padding:10px; padding-left:15px"> - <i>Nothing here. To create a new task, choose a container from the "containers" menu entry and run it by hitting the play button.</i> + <a href="/create_task">New task...</a> </div> {% endif %} diff --git a/services/webapp/code/rosetta/core_app/views.py b/services/webapp/code/rosetta/core_app/views.py index cedc871b02c9b89acad99384d2f64b3f7fe5158b..067bd8ef88275af7e015c3e8ac77c7c05c1d0e34 100644 --- a/services/webapp/code/rosetta/core_app/views.py +++ b/services/webapp/code/rosetta/core_app/views.py @@ -402,35 +402,12 @@ def create_task(request): data['profile'] = Profile.objects.get(user=request.user) data['title'] = 'New Task' - # Step if any - step = request.POST.get('step', None) - - # Container uuid if any - container_uuid = request.GET.get('task_container_uuid', None) - if container_uuid: - try: - data['task_container'] = Container.objects.get(uuid=container_uuid, user=request.user) - except Container.DoesNotExist: - data['task_container'] = Container.objects.get(uuid=container_uuid, user=None) - else: - # Get containers - data['containers'] = list(Container.objects.filter(user=None)) + list(Container.objects.filter(user=request.user)) - - # Get computings - data['computings'] = list(Computing.objects.filter(user=None)) + list(Computing.objects.filter(user=request.user)) - - - # Handle step - if step: - - # Task name - task_name = request.POST.get('task_name', None) - if not task_name: - raise ErrorMessage('Missing task name') - data['task_name'] = task_name - - # Task container + # Get task container helper function + def get_task_container(request): task_container_uuid = request.POST.get('task_container_uuid', None) + if not task_container_uuid: + # At the second step the task uuid is set via a GET request + task_container_uuid = request.GET.get('task_container_uuid', None) try: task_container = Container.objects.get(uuid=task_container_uuid, user=None) except Container.DoesNotExist: @@ -438,9 +415,10 @@ def create_task(request): task_container = Container.objects.get(uuid=task_container_uuid, user=request.user) except Container.DoesNotExist: raise Exception('Consistency error, container with uuid "{}" does not exists or user "{}" does not have access rights'.format(task_container_uuid, request.user.email)) - data['task_container'] = task_container + return task_container - # Task computing + # Get task computing helper function + def get_task_computing(request): task_computing_uuid = request.POST.get('task_computing_uuid', None) try: task_computing = Computing.objects.get(uuid=task_computing_uuid, user=None) @@ -450,94 +428,139 @@ def create_task(request): except Computing.DoesNotExist: raise Exception('Consistency error, computing with uuid "{}" does not exists or user "{}" does not have access rights'.format(task_computing_uuid, request.user.email)) task_computing.attach_user_conf(request.user) - data['task_computing'] = task_computing - - # Handle step one/two - if step == 'one': - - # Set step and task uuid - data['step'] = 'two' - - elif step == 'two': - - # Generate the task uuid - task_uuid = str(uuid.uuid4()) - - # Create the task object - task = Task(uuid = task_uuid, - user = request.user, - name = task_name, - status = TaskStatuses.created, - container = task_container, - computing = task_computing) - - # Add auth - task.auth_user = request.POST.get('auth_user', None) - task.auth_pass = request.POST.get('auth_password', None) - task.access_method = request.POST.get('access_method', None) - task_base_port = request.POST.get('task_base_port', None) - - if task_base_port: - task.port = task_base_port - - # Checks - if task.auth_pass and len(task.auth_pass) < 6: - raise ErrorMessage('Task password must be at least 6 chars') - - # Computing options # TODO: This is hardcoded thinking about Slurm and Singularity - computing_cpus = request.POST.get('computing_cpus', None) - computing_memory = request.POST.get('computing_memory', None) - computing_partition = request.POST.get('computing_partition', None) - extra_binds = request.POST.get('extra_binds', None) - - computing_options = {} - if computing_cpus: - try: - int(computing_cpus) - except: - raise Exception('Cannot convert computing_cpus to int') - computing_options['cpus'] = int(computing_cpus) - - if computing_memory: - computing_options['memory'] = computing_memory + return task_computing + + # Get task name helper function + def get_task_name(request): + task_name = request.POST.get('task_name', None) + if not task_name: + raise ErrorMessage('Missing task name') + return task_name + + # Get step if any, check both POST and GET + step = request.POST.get('step', None) + if not step: + step = request.GET.get('step', None) - if computing_partition: - computing_options['partition'] = computing_partition - - if computing_options: - task.computing_options = computing_options - - # Attach user config to computing - task.computing.attach_user_conf(task.user) - # Set port if not dynamic ports - if not task.container.supports_dynamic_ports: - if task.container.ports: - task.port = task.container.port + # Handle the various steps + if not step: + + # Step one is assumed: chose software container + return HttpResponseRedirect('/containers/?mode=new_task') + + elif step == 'two': - # Set extra binds if any: - task.extra_binds = extra_binds + # Get software container + data['task_container'] = get_task_container(request) - # Save the task before starting it, or the computing manager will not be able to work properly - task.save() - - # Start the task + # List all computing resources + data['computings'] = list(Computing.objects.filter(user=None)) + list(Computing.objects.filter(user=request.user)) + + data['step'] = 'two' + data['next_step'] = 'three' + + elif step == 'three': + + # Get software container + data['task_container'] = get_task_container(request) + + # Get computing resource + data['task_computing'] = get_task_computing(request) + + # Get task name + data['task_name'] = get_task_name(request) + + # Set current and next step + data['step'] = 'three' + data['next_step'] = 'last' + + + elif step == 'last': + + # Get software container + data['task_container'] = get_task_container(request) + + # Get computing resource + data['task_computing'] = get_task_computing(request) + + # Get task name + data['task_name'] = get_task_name(request) + + # Generate the task uuid + task_uuid = str(uuid.uuid4()) + + # Create the task object + task = Task(uuid = task_uuid, + user = request.user, + name = data['task_name'], + status = TaskStatuses.created, + container = data['task_container'], + computing = data['task_computing']) + + # Add auth + task.auth_user = request.POST.get('auth_user', None) + task.auth_pass = request.POST.get('auth_password', None) + task.access_method = request.POST.get('access_method', None) + task_base_port = request.POST.get('task_base_port', None) + + if task_base_port: + task.port = task_base_port + + # Checks + if task.auth_pass and len(task.auth_pass) < 6: + raise ErrorMessage('Task password must be at least 6 chars') + + # Computing options # TODO: This is hardcoded thinking about Slurm and Singularity + computing_cpus = request.POST.get('computing_cpus', None) + computing_memory = request.POST.get('computing_memory', None) + computing_partition = request.POST.get('computing_partition', None) + extra_binds = request.POST.get('extra_binds', None) + + computing_options = {} + if computing_cpus: try: - task.computing.manager.start_task(task) + int(computing_cpus) except: - # Delete the task if could not start it - task.delete() - - # ..and re-raise - raise + raise Exception('Cannot convert computing_cpus to int') + computing_options['cpus'] = int(computing_cpus) - # Set step - data['step'] = 'created' + if computing_memory: + computing_options['memory'] = computing_memory - else: + if computing_partition: + computing_options['partition'] = computing_partition - # Set step - data['step'] = 'one' + if computing_options: + task.computing_options = computing_options + + # Attach user config to computing + task.computing.attach_user_conf(task.user) + + # Set port if not dynamic ports + if not task.container.supports_dynamic_ports: + if task.container.ports: + task.port = task.container.port + + # Set extra binds if any: + task.extra_binds = extra_binds + + # Save the task before starting it, or the computing manager will not be able to work properly + task.save() + + # Start the task + try: + task.computing.manager.start_task(task) + except: + # Delete the task if could not start it + task.delete() + + # ..and re-raise + raise + + # Set step + data['step'] = 'created' + return render(request, 'create_task.html', {'data': data}) @@ -608,10 +631,15 @@ def containers(request): search_text = request.POST.get('search_text', '') search_type = request.POST.get('search_type', 'All') - # Set bak to page data + # Set back to page data data['search_type'] = search_type data['search_text'] = search_text + # Are we using this page as first step of a new task? + data['mode'] = request.GET.get('mode', None) + if not data['mode']: + data['mode'] = request.POST.get('mode', None) + # Do we have to operate on a specific container? if uuid: