From 4f91f56f1ea9a04c3d510b4a25666c0a9420a286 Mon Sep 17 00:00:00 2001
From: Stefano Alberto Russo <stefano.russo@gmail.com>
Date: Tue, 5 May 2020 22:09:36 +0200
Subject: [PATCH] UI improvements, few changes in the models as well for the
 sake of clarity.

---
 .../webapp/code/rosetta/core_app/admin.py     |  4 +-
 .../rosetta/core_app/computing_managers.py    | 50 ++++++---------
 .../management/commands/core_app_populate.py  | 22 +++----
 .../webapp/code/rosetta/core_app/models.py    | 41 ++++++++----
 .../core_app/static/css/stylish-portfolio.css | 37 ++++++++---
 .../js/bootstrap-datetimepicker-4.17.47.js    | 14 ++---
 .../rosetta/core_app/templates/account.html   |  2 +-
 .../templates/components/computing.html       | 32 ++++++++--
 .../templates/components/container.html       | 49 +++++++++++++--
 .../core_app/templates/components/task.html   | 62 +++++++++++++++++--
 .../core_app/templates/computings.html        | 33 +++++-----
 .../core_app/templates/containers.html        | 29 +++++----
 .../core_app/templates/create_task.html       | 18 +++---
 .../templates/edit_computing_conf.html        |  1 -
 .../rosetta/core_app/templates/footer.html    | 18 +++++-
 .../core_app/templates/navigation.html        | 10 +--
 .../rosetta/core_app/templates/tasks.html     | 15 +++--
 .../webapp/code/rosetta/core_app/utils.py     | 21 +++++++
 .../webapp/code/rosetta/core_app/views.py     | 28 ++++++---
 19 files changed, 347 insertions(+), 139 deletions(-)

diff --git a/services/webapp/code/rosetta/core_app/admin.py b/services/webapp/code/rosetta/core_app/admin.py
index 21bc697..56b2fe7 100644
--- a/services/webapp/code/rosetta/core_app/admin.py
+++ b/services/webapp/code/rosetta/core_app/admin.py
@@ -1,6 +1,6 @@
 from django.contrib import admin
 
-from .models import Profile, LoginToken, Task, Container, Computing, ComputingSysConf, ComputingUserConf, Keys
+from .models import Profile, LoginToken, Task, Container, Computing, ComputingSysConf, ComputingUserConf, KeyPair
 
 admin.site.register(Profile)
 admin.site.register(LoginToken)
@@ -9,4 +9,4 @@ admin.site.register(Container)
 admin.site.register(Computing)
 admin.site.register(ComputingSysConf)
 admin.site.register(ComputingUserConf)
-admin.site.register(Keys)
+admin.site.register(KeyPair)
diff --git a/services/webapp/code/rosetta/core_app/computing_managers.py b/services/webapp/code/rosetta/core_app/computing_managers.py
index 6febb3c..89d2ae1 100644
--- a/services/webapp/code/rosetta/core_app/computing_managers.py
+++ b/services/webapp/code/rosetta/core_app/computing_managers.py
@@ -1,4 +1,4 @@
-from .models import TaskStatuses, Keys, Task
+from .models import TaskStatuses, KeyPair, Task
 from .utils import os_shell
 from .exceptions import ErrorMessage, ConsistencyException
 
@@ -164,8 +164,8 @@ class RemoteComputingManager(ComputingManager):
         user = task.computing.get_conf_param('user')
 
         # Get user keys
-        if task.computing.require_user_keys:
-            user_keys = Keys.objects.get(user=task.user, default=True)
+        if task.computing.requires_user_keys:
+            user_keys = KeyPair.objects.get(user=task.user, default=True)
         else:
             raise NotImplementedError('Remote tasks not requiring keys are not yet supported')
 
@@ -173,9 +173,7 @@ class RemoteComputingManager(ComputingManager):
         from.utils import get_webapp_conn_string
         webapp_conn_string = get_webapp_conn_string()
             
-
-        # 1) Run the container on the host (non blocking)
- 
+        # Run the container on the host (non blocking)
         if task.container.type == 'singularity':
 
             task.tid    = task.uuid
@@ -188,17 +186,10 @@ class RemoteComputingManager(ComputingManager):
                 authstring = ''
 
             run_command  = 'ssh -i {} -4 -o StrictHostKeyChecking=no {}@{} '.format(user_keys.private_key_file, user, host)
-            run_command += '/bin/bash -c \'"wget {}/api/v1/base/agent/?task_uuid={} -O /tmp/agent_{}.py &> /dev/null && export BASE_PORT=\$(python /tmp/agent_{}.py 2> /tmp/{}.log) && '.format(webapp_conn_string, task.uuid, task.uuid, task.uuid, task.uuid)
+            run_command += '/bin/bash -c \'"wget {}/api/v1/base/agent/?task_uuid={} -O \$HOME/agent_{}.py &> /dev/null && export BASE_PORT=\$(python \$HOME/agent_{}.py 2> \$HOME/{}.log) && '.format(webapp_conn_string, task.uuid, task.uuid, task.uuid, task.uuid)
             run_command += 'export SINGULARITY_NOHTTPS=true && export SINGULARITYENV_BASE_PORT=\$BASE_PORT && {} '.format(authstring)
             run_command += 'exec nohup singularity run --pid --writable-tmpfs --containall --cleanenv '
             
-            # ssh -i /rosetta/.ssh/id_rsa -4 -o StrictHostKeyChecking=no slurmclusterworker-one
-            # "wget 172.21.0.2:8080/api/v1/base/agent/?task_uuid=15a4320a-88b6-4ffc-8dd0-c80f9d18b292 -O /tmp/agent_15a4320a-88b6-4ffc-8dd0-c80f9d18b292.py &> /dev/null &&
-            # export BASE_PORT=\$(python /tmp/agent_15a4320a-88b6-4ffc-8dd0-c80f9d18b292.py) && export SINGULARITY_NOHTTPS=true && export SINGULARITYENV_BASE_PORT=\$BASE_PORT &&  export SINGULARITYENV_AUTH_PASS=testpass &&  
-            # exec nohup singularity run --pid --writable-tmpfs --containall --cleanenv
-            # docker://dregistry:5000/rosetta/metadesktop &> /tmp/15a4320a-88b6-4ffc-8dd0-c80f9d18b292.log & echo $!"
-            
-            
             # Set registry
             if task.container.registry == 'docker_local':
                 # Get local Docker registry conn string
@@ -210,7 +201,7 @@ class RemoteComputingManager(ComputingManager):
             else:
                 raise NotImplementedError('Registry {} not supported'.format(task.container.registry))
     
-            run_command+='{}{} &>> /tmp/{}.log & echo \$!"\''.format(registry, task.container.image, task.uuid)
+            run_command+='{}{} &>> \$HOME/{}.log & echo \$!"\''.format(registry, task.container.image, task.uuid)
             
         else:
             raise NotImplementedError('Container {} not supported'.format(task.container.type))
@@ -240,8 +231,8 @@ class RemoteComputingManager(ComputingManager):
     def _stop_task(self, task, **kwargs):
 
         # Get user keys
-        if task.computing.require_user_keys:
-            user_keys = Keys.objects.get(user=task.user, default=True)
+        if task.computing.requires_user_keys:
+            user_keys = KeyPair.objects.get(user=task.user, default=True)
         else:
             raise NotImplementedError('Remote tasks not requiring keys are not yet supported')
 
@@ -265,8 +256,8 @@ class RemoteComputingManager(ComputingManager):
     def _get_task_log(self, task, **kwargs):
         
         # Get user keys
-        if task.computing.require_user_keys:
-            user_keys = Keys.objects.get(user=task.user, default=True)
+        if task.computing.requires_user_keys:
+            user_keys = KeyPair.objects.get(user=task.user, default=True)
         else:
             raise NotImplementedError('Remote tasks not requiring keys are not yet supported')
 
@@ -275,7 +266,7 @@ class RemoteComputingManager(ComputingManager):
         user = task.computing.get_conf_param('user')
 
         # Stop the task remotely
-        view_log_command = 'ssh -i {} -4 -o StrictHostKeyChecking=no {}@{} \'/bin/bash -c "cat /tmp/{}.log"\''.format(user_keys.private_key_file, user, host, task.uuid)
+        view_log_command = 'ssh -i {} -4 -o StrictHostKeyChecking=no {}@{} \'/bin/bash -c "cat \$HOME/{}.log"\''.format(user_keys.private_key_file, user, host, task.uuid)
 
         out = os_shell(view_log_command, capture=True)
         if out.exit_code != 0:
@@ -290,13 +281,13 @@ class SlurmComputingManager(ComputingManager):
     def _start_task(self, task, **kwargs):
         logger.debug('Starting a remote task "{}"'.format(task.computing))
 
-        # Get computing host #Key Error ATM
+        # Get computing host
         host = task.computing.get_conf_param('master')
         user = task.computing.get_conf_param('user')
         
         # Get user keys
-        if task.computing.require_user_keys:
-            user_keys = Keys.objects.get(user=task.user, default=True)
+        if task.computing.requires_user_keys:
+            user_keys = KeyPair.objects.get(user=task.user, default=True)
         else:
             raise NotImplementedError('Remote tasks not requiring keys are not yet supported')
 
@@ -323,8 +314,7 @@ class SlurmComputingManager(ComputingManager):
         # Set output and error files
         sbatch_args += ' --output=\$HOME/{}.log --error=\$HOME/{}.log '.format(task.uuid, task.uuid)
 
-
-        # 1) Run the container on the host (non blocking)
+        # Submit the job
         if task.container.type == 'singularity':
 
             if not task.container.supports_dynamic_ports:
@@ -348,7 +338,7 @@ class SlurmComputingManager(ComputingManager):
             run_command += 'export SINGULARITY_NOHTTPS=true && export SINGULARITYENV_BASE_PORT=\\\\\\$BASE_PORT && {} '.format(authstring)
             run_command += 'exec nohup singularity run {} --pid --writable-tmpfs --containall --cleanenv '.format(bindings)
             
-            # Double to escape for python six for shell (double times three as \\\ escapes a single slash in shell)
+            # Double to escape for Pythom, six for shell (double times three as \\\ escapes a single slash in shell)
 
             # Set registry
             if task.container.registry == 'docker_local':
@@ -399,8 +389,8 @@ class SlurmComputingManager(ComputingManager):
     def _stop_task(self, task, **kwargs):
         
         # Get user keys
-        if task.computing.require_user_keys:
-            user_keys = Keys.objects.get(user=task.user, default=True)
+        if task.computing.requires_user_keys:
+            user_keys = KeyPair.objects.get(user=task.user, default=True)
         else:
             raise NotImplementedError('Remote tasks not requiring keys are not yet supported')
 
@@ -423,8 +413,8 @@ class SlurmComputingManager(ComputingManager):
     def _get_task_log(self, task, **kwargs):
         
         # Get user keys
-        if task.computing.require_user_keys:
-            user_keys = Keys.objects.get(user=task.user, default=True)
+        if task.computing.requires_user_keys:
+            user_keys = KeyPair.objects.get(user=task.user, default=True)
         else:
             raise NotImplementedError('Remote tasks not requiring keys are not yet supported')
 
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 9d4e5d5..8f04ffe 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
@@ -1,6 +1,6 @@
 from django.core.management.base import BaseCommand
 from django.contrib.auth.models import User
-from ...models import Profile, Container, Computing, ComputingSysConf, ComputingUserConf, Keys
+from ...models import Profile, Container, Computing, ComputingSysConf, ComputingUserConf, KeyPair
 
 class Command(BaseCommand):
     help = 'Adds the admin superuser with \'a\' password.'
@@ -34,7 +34,7 @@ class Command(BaseCommand):
 
             # Create default keys
             print('Creating testuser defualt keys')
-            Keys.objects.create(user = testuser,
+            KeyPair.objects.create(user = testuser,
                                 default = True,
                                 private_key_file = '/rosetta/.ssh/id_rsa',
                                 public_key_file = '/rosetta/.ssh/id_rsa.pub')
@@ -112,9 +112,9 @@ class Command(BaseCommand):
             Computing.objects.create(user = None,
                                      name = 'Local',
                                      type = 'local',
-                                     require_sys_conf  = False,
-                                     require_user_conf = False,
-                                     require_user_keys = False)
+                                     requires_sys_conf  = False,
+                                     requires_user_conf = False,
+                                     requires_user_keys = False)
 
 
             #==============================
@@ -123,9 +123,9 @@ class Command(BaseCommand):
             demo_remote_auth_computing = Computing.objects.create(user = None,
                                                              name = 'Demo remote',
                                                              type = 'remote',
-                                                             require_sys_conf  = True,
-                                                             require_user_conf = True,
-                                                             require_user_keys = True)
+                                                             requires_sys_conf  = True,
+                                                             requires_user_conf = True,
+                                                             requires_user_keys = True)
     
             ComputingSysConf.objects.create(computing = demo_remote_auth_computing,
                                             data      = {'host': 'slurmclusterworker-one'})
@@ -141,9 +141,9 @@ class Command(BaseCommand):
             demo_slurm_computing = Computing.objects.create(user = None,
                                                             name = 'Demo Slurm',
                                                             type = 'slurm',
-                                                            require_sys_conf  = True,
-                                                            require_user_conf = True,
-                                                            require_user_keys = True)
+                                                            requires_sys_conf  = True,
+                                                            requires_user_conf = True,
+                                                            requires_user_keys = True)
     
             # Create demo slurm sys computing conf
             ComputingSysConf.objects.create(computing = demo_slurm_computing,
diff --git a/services/webapp/code/rosetta/core_app/models.py b/services/webapp/code/rosetta/core_app/models.py
index 8ee8a36..aa06992 100644
--- a/services/webapp/code/rosetta/core_app/models.py
+++ b/services/webapp/code/rosetta/core_app/models.py
@@ -3,7 +3,7 @@ from django.conf import settings
 from django.db import models
 from django.contrib.auth.models import User
 from django.utils import timezone
-from .utils import os_shell
+from .utils import os_shell, color_map, hash_string_to_int
 
 if 'sqlite' in settings.DATABASES['default']['ENGINE']:
     from .fields import JSONField
@@ -96,7 +96,11 @@ class Container(models.Model):
     def id(self):
         return str(self.uuid).split('-')[0]
 
-
+    @ property
+    def color(self):
+        string_int_hash = hash_string_to_int(self.name + self.type + self.image)
+        color_map_index = string_int_hash % len(color_map)
+        return color_map[color_map_index]
 
 #=========================
 #  Computing resources
@@ -111,16 +115,19 @@ class Computing(models.Model):
     name = models.CharField('Computing Name', max_length=255, blank=False, null=False)
     type = models.CharField('Computing Type', max_length=255, blank=False, null=False)
 
-    require_sys_conf  = models.BooleanField(default=False)
-    require_user_conf = models.BooleanField(default=False)
-    require_user_keys = models.BooleanField(default=False)
+    requires_sys_conf  = models.BooleanField(default=False)
+    requires_user_conf = models.BooleanField(default=False)
+    requires_user_keys = models.BooleanField(default=False)
+
+    supports_docker  = models.BooleanField(default=False)
+    supports_singularity  = models.BooleanField(default=False)
 
 
     def __str__(self):
         if self.user:
-            return str('Computing Resource "{}" of user "{}"'.format(self.name, self.user))
+            return str('Computing "{}" of user "{}"'.format(self.name, self.user))
         else:
-            return str('Computing Resource "{}"'.format(self.name))
+            return str('Computing "{}"'.format(self.name))
 
 
     @property
@@ -172,6 +179,12 @@ class Computing(models.Model):
         ComputingManager = getattr(computing_managers, '{}ComputingManager'.format(self.type.title()))
         return ComputingManager()
 
+    @ property
+    def color(self):
+        string_int_hash = hash_string_to_int(self.name)
+        color_map_index = string_int_hash % len(color_map)
+        return color_map[color_map_index]
+
 
 
 class ComputingSysConf(models.Model):
@@ -201,7 +214,7 @@ class ComputingUserConf(models.Model):
 
     @property
     def id(self):
-        return str('Computing sys conf for {} with id "{}" of user "{}"'.format(self.computing, self.id, self.user))
+        return str('Computing user conf for {} with id "{}" of user "{}"'.format(self.computing, self.id, self.user))
 
 
 
@@ -274,13 +287,19 @@ class Task(models.Model):
     def __str__(self):
         return str('Task "{}" of user "{}" running on "{}" in status "{}" created at "{}"'.format(self.name, self.user, self.computing, self.status, self.created))
 
+    @ property
+    def color(self):
+        string_int_hash = hash_string_to_int(self.name)
+        color_map_index = string_int_hash % len(color_map)
+        return color_map[color_map_index]
+
 
 
 #=========================
-#  Keys 
+#  KeyPair 
 #=========================
 
-class Keys(models.Model):
+class KeyPair(models.Model):
 
     uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
     user = models.ForeignKey(User, related_name='+', on_delete=models.CASCADE, null=False)  
@@ -292,7 +311,7 @@ class Keys(models.Model):
 
 
     def __str__(self):
-        return str('Keys with id "{}" of user "{}"'.format(self.id, self.user))
+        return str('KeyPair with id "{}" of user "{}"'.format(self.id, self.user))
 
 
     @property
diff --git a/services/webapp/code/rosetta/core_app/static/css/stylish-portfolio.css b/services/webapp/code/rosetta/core_app/static/css/stylish-portfolio.css
index 4c5823f..ce18de9 100644
--- a/services/webapp/code/rosetta/core_app/static/css/stylish-portfolio.css
+++ b/services/webapp/code/rosetta/core_app/static/css/stylish-portfolio.css
@@ -13,9 +13,12 @@ body {
     font-size: 1.0em;
 }
 
-/*body {
-    font-family: "Source Sans Pro","Helvetica Neue",Helvetica,Arial,sans-serif;
-}*/
+body {
+    /*font-family: "Source Sans Pro","Helvetica Neue",Helvetica,Arial,sans-serif;*/
+    /*color: #585858*/
+    color: #484848
+    
+}
 
 
 .form-control {
@@ -54,12 +57,17 @@ body {
 
 /* Custom Button Styles */
 
-.btn-dark {
+.btn-dark, .btn-dark-disabled {
     border-radius: 0;
     color: #fff;
     background-color: rgba(0,0,0,0.4);
 }
 
+.btn-disabled {
+    border-radius: 0;
+    color: #f5f5f5;
+    background-color: #c8c8c8;
+}
 
 .btn-blue {
     border-radius: 0;
@@ -70,8 +78,24 @@ body {
 
 }
 
+.btn-connect {
+    border-radius: 0;
+    color: #fff;
+    background-color: rgba(0,95,144,0.8);
+}
+
+.btn-connect:hover {
+    color: #fff;
+    background-color: rgba(0,95,144,1.0);
+}
 
+.btn-disabled:hover {
+    color: #f5f5f5;
+}
 
+.btn-dark-disabled:hover {
+    color: #fff;
+}
 
 .btn-dark:hover,
 .btn-dark:focus,
@@ -431,9 +455,6 @@ th, td {
     font-weight:700;
 }
 
-
-
-
 .centerbox-error-notfilled {
     background-color: #ffffff;
     text-align: center;
@@ -522,7 +543,7 @@ th, td {
 /*   Dashboard       */
 /*-------------------*/
 .dashboard table {
-	/*border: 1px #c0c0c0 solid;*/
+	border: 1px #e0e0e0 solid;
 	background: #F8F8F8;
 	padding: 10px;
 } 
diff --git a/services/webapp/code/rosetta/core_app/static/js/bootstrap-datetimepicker-4.17.47.js b/services/webapp/code/rosetta/core_app/static/js/bootstrap-datetimepicker-4.17.47.js
index 62c1d30..e5f7735 100755
--- a/services/webapp/code/rosetta/core_app/static/js/bootstrap-datetimepicker-4.17.47.js
+++ b/services/webapp/code/rosetta/core_app/static/js/bootstrap-datetimepicker-4.17.47.js
@@ -1274,10 +1274,10 @@
                 var handler = null,
                     index,
                     index2,
-                    pressedKeys = [],
+                    pressedKeyPair = [],
                     pressedModifiers = {},
                     currentKey = e.which,
-                    keyBindKeys,
+                    keyBindKeyPair,
                     allModifiersPressed,
                     pressed = 'p';
 
@@ -1285,7 +1285,7 @@
 
                 for (index in keyState) {
                     if (keyState.hasOwnProperty(index) && keyState[index] === pressed) {
-                        pressedKeys.push(index);
+                        pressedKeyPair.push(index);
                         if (parseInt(index, 10) !== currentKey) {
                             pressedModifiers[index] = true;
                         }
@@ -1294,11 +1294,11 @@
 
                 for (index in options.keyBinds) {
                     if (options.keyBinds.hasOwnProperty(index) && typeof (options.keyBinds[index]) === 'function') {
-                        keyBindKeys = index.split(' ');
-                        if (keyBindKeys.length === pressedKeys.length && keyMap[currentKey] === keyBindKeys[keyBindKeys.length - 1]) {
+                        keyBindKeyPair = index.split(' ');
+                        if (keyBindKeyPair.length === pressedKeyPair.length && keyMap[currentKey] === keyBindKeyPair[keyBindKeyPair.length - 1]) {
                             allModifiersPressed = true;
-                            for (index2 = keyBindKeys.length - 2; index2 >= 0; index2--) {
-                                if (!(keyMap[keyBindKeys[index2]] in pressedModifiers)) {
+                            for (index2 = keyBindKeyPair.length - 2; index2 >= 0; index2--) {
+                                if (!(keyMap[keyBindKeyPair[index2]] in pressedModifiers)) {
                                     allModifiersPressed = false;
                                     break;
                                 }
diff --git a/services/webapp/code/rosetta/core_app/templates/account.html b/services/webapp/code/rosetta/core_app/templates/account.html
index 865dbac..1e30829 100644
--- a/services/webapp/code/rosetta/core_app/templates/account.html
+++ b/services/webapp/code/rosetta/core_app/templates/account.html
@@ -86,7 +86,7 @@
       </table>
 
       <br />
-       <h3>Keys</h3>
+       <h3>KeyPair</h3>
        <table class="dashboard">
        
        <tr>
diff --git a/services/webapp/code/rosetta/core_app/templates/components/computing.html b/services/webapp/code/rosetta/core_app/templates/components/computing.html
index 38b624e..8945613 100644
--- a/services/webapp/code/rosetta/core_app/templates/components/computing.html
+++ b/services/webapp/code/rosetta/core_app/templates/components/computing.html
@@ -1,8 +1,10 @@
-      <table class="dashboard" style="max-width:500px">
+
+      <div style="float:left; width:500px; margin:10px; margin-bottom:20px">
+      <table class="dashboard" style="width:100%">
 
        <tr>
-        <td><b>ID</b></td>
-        <td><a href="?uuid={{ computing.uuid }}">{{ computing.id }}</a></td>
+        <td colspan="2" style="background:{{ computing.color }}; height:30px">
+        </td>
        </tr>
 
        <tr>
@@ -10,6 +12,11 @@
         <td>{{ computing.name }}</td>
        </tr>
 
+       <tr>
+        <td width="110px"><b>ID</b></td>
+        <td>{{ computing.id }}</a></td>
+       </tr>
+
        <tr>
         <td><b>Type</b></td>
         <td>{{ computing.type }}</td>
@@ -20,6 +27,23 @@
         <td>{% if computing.user %}{{ computing.user }}{% else %}platform{% endif %}</td>
        </tr>
 
+       <tr>
+        <td><b>Require</b></td>
+        <td>
+          Sys conf: {{ computing.requires_sys_conf }} <br/>
+          User conf: {{ computing.requires_user_conf }} <br/>
+          User keys: {{ computing.requires_user_keys }}
+        </td>
+       </tr>
+
+       <tr>
+        <td><b>Supports</b></td>
+        <td>
+          Docker: {{ computing.supports_docker }} <br/>
+          Singularity: {{ computing.supports_singularity }}
+        </td>
+       </tr>
+
        <tr>
         <td><b>Sys Conf</b></td>
         <td>{{ computing.sys_conf_data_json }} {% if request.user.is_superuser %} &nbsp;[<a href="/edit_computing_conf?type=sys&computing_uuid={{ computing.uuid}}">Edit</a>] {% endif %}</td>
@@ -38,4 +62,4 @@
        </tr>
        {% endif %}
       </table>
-      <br/>
\ No newline at end of file
+      </div>
\ No newline at end of file
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 072d1a4..352a5de 100644
--- a/services/webapp/code/rosetta/core_app/templates/components/container.html
+++ b/services/webapp/code/rosetta/core_app/templates/components/container.html
@@ -1,8 +1,12 @@
-      <table class="dashboard">
 
+
+      {% if details %}      
+
+      <table class="dashboard" style="margin:10px">
+        
        <tr>
-        <td><b>ID</b></td>
-        <td><a href="?uuid={{ container.uuid }}">{{ container.id }}</a></td>
+        <td colspan="2" style="background:{{ container.color }}; height:25px">
+        </td>
        </tr>
 
        <tr>
@@ -10,6 +14,11 @@
         <td>{{ container.name }}</td>
        </tr>
 
+       <tr>
+        <td><b>ID</b></td>
+        <td>{{ container.id }}</td>
+       </tr>
+
        <tr>
         <td><b>Image</b></td>
         <td>{{ container.image }}</td>
@@ -74,4 +83,36 @@
        </tr>
        {% endif %}
       </table>
-      <br/>
\ No newline at end of file
+      <br/>
+      
+      
+      {% else %}
+      <div style="width:300px; float:left; border: #e0e0e0 solid 1px; margin:10px; background:#f8f8f8; margin-bottom:20px">
+        <div style="background:{{container.color}}; height:24px"></div>
+        
+        <div style="padding:10px; text-align:center; border-bottom: #e0e0e0 solid 1px; ">
+        <a href="?uuid={{ container.uuid }}">{{ container.name }}</a>
+        &nbsp; <a href="/create_task?container_uuid={{ container.uuid }}"><i class="fa fa-play" style="color:green"></i></a>
+        </div>
+        
+        <div style="padding:10px;">
+        Type: {{ container.type }}<br/>
+        Image: {{ container.image }}
+        </div>
+        
+        <div style="margin-bottom:10px; text-align:center">
+        
+        </div>    
+      
+      
+      
+      </div>
+      
+      {% endif  %}
+
+
+
+
+
+
+      
\ No newline at end of file
diff --git a/services/webapp/code/rosetta/core_app/templates/components/task.html b/services/webapp/code/rosetta/core_app/templates/components/task.html
index 4756d59..c0920e4 100644
--- a/services/webapp/code/rosetta/core_app/templates/components/task.html
+++ b/services/webapp/code/rosetta/core_app/templates/components/task.html
@@ -1,14 +1,18 @@
-          
+
+
+          {% if details %}      
+
+         
           <table class="dashboard">
 
            <tr>
-            <td><b>ID</b></td>
-            <td><a href="?uuid={{ task.uuid }}">{{ task.id }}</a></td>
+            <td><b>Name</b></td>
+            <td>{{ task.name }}</td>
            </tr>
 
            <tr>
-            <td><b>Name</b></td>
-            <td>{{ task.name }}</td>
+            <td><b>ID</b></td>
+            <td>{{ task.id }}</td>
            </tr>
 
            <tr>
@@ -85,4 +89,50 @@
            </tr>
 
           </table>
-          <br/>
\ No newline at end of file
+          
+          {% else %}
+          <div style="width:300px; float:left; border: #e0e0e0 solid 1px; margin:10px; background:#f8f8f8; margin-bottom:20px">
+            <!-- <div>
+            <div style="background:{{task.container.color}}; height:24px; width:149px; float:left"></div>
+            <div style="background:{{task.computing.color}}; height:24px; width:149px; float:left"></div>
+            </div> -->
+            
+            <!-- <div style="background:{{task.color}}; height:24px;"></div> -->
+            
+            <div style="padding:10px; text-align:center; border-bottom: #e0e0e0 solid 1px; ">
+            <a href="?uuid={{ task.uuid }}"><b>{{ task.name }}</b></a>
+            </div>
+            
+            <div style="padding:10px;">
+            <!-- <b>Container:</b> {{ task.container.name }} <span style="font-size:14px; background-color:{{task.container.color}}">&nbsp;&nbsp;&nbsp;&nbsp;</span> <br/>
+            <b>Computing:</b> {{ task.computing.name }} <span style="font-size:14px; background-color:{{task.computing.color}}">&nbsp;&nbsp;&nbsp;&nbsp;</span><br/> -->
+
+            <b>Container:</b> {{ task.container.name }}
+            <a href="/containers/?uuid={{ task.container.uuid }}" style="color:{{task.container.color}}"><i class="fa fa-external-link" ></i></a> <br/>
+            
+            <b>Computing:</b> {{ task.computing.name }}
+            <a href="/computings/" style="color:{{task.computing.color}}"><i class="fa fa-external-link" ></i></a><br/>            
+            
+            
+            {% if task.status == "running" %}
+             <b>Status:</b> <font color="green">running</font>
+            {% else %}
+             <b>Status:</b> {{ task.status }}
+            {% endif %}
+            </div>
+            
+            <div style="margin-bottom:10px; text-align:center; padding:5px">
+            {% if task.status == "running" %}
+            <a href="?uuid={{task.uuid}}&action=connect" class="btn btn-connect">Connect</a>
+            {% else %}
+            <a href="?uuid={{task.uuid}}&action=connect" class="btn btn-disabled">Connect</a>
+            {% endif %}            
+            
+            
+            </div>    
+          
+          
+          
+          </div>
+          
+          {% endif  %}
\ No newline at end of file
diff --git a/services/webapp/code/rosetta/core_app/templates/computings.html b/services/webapp/code/rosetta/core_app/templates/computings.html
index 56271a1..efaa389 100644
--- a/services/webapp/code/rosetta/core_app/templates/computings.html
+++ b/services/webapp/code/rosetta/core_app/templates/computings.html
@@ -10,31 +10,32 @@
     <div class="span8 offset2">
       
       {% if data.computing %}
-      <h1><a href="/computings">Computing List</a> &gt; {{ data.computing.id }}  </h1>      
+      <h1><a href="/computings">Computing</a> &gt; {{ data.computing.id }}  </h1>      
       {% else %}
-      <h1>Computing List</h1>
+      <h1>Computing</h1>
       {% endif %}
       
       <hr/>
-
+      <div class="row" style="padding:5px">
       {% if data.computing %}
-      {% include "components/computing.html" with computing=data.computing %}
-      {% else %}
-      
+      {% include "components/computing.html" with computing=data.computing details=True %}
+      {% else %}      
       {% for computing in data.computings %}
       {% include "components/computing.html" with computing=computing %}
-      <br />
       {% endfor %}
-  
-      
-      <!-- <br />
-      <a href="/add_computing">Add new...</a> -->
-      
       {% endif %}
-      <br />
-      <br />
-      <br />
-      <br />
+      </div>
+      
+      <!-- <div class="row" style="padding:10px; padding-left:15px">   
+      <a href="/add_container">Add new...</a>
+      </div> -->
+
+      <br/>
+      <br/>
+      <br/>
+      <br/>
+      <br/>
+      <br/>
 
     </div>
   </div>
diff --git a/services/webapp/code/rosetta/core_app/templates/containers.html b/services/webapp/code/rosetta/core_app/templates/containers.html
index 2c461e8..5ed6480 100644
--- a/services/webapp/code/rosetta/core_app/templates/containers.html
+++ b/services/webapp/code/rosetta/core_app/templates/containers.html
@@ -10,29 +10,32 @@
     <div class="span8 offset2">
       
       {% if data.container %}
-      <h1><a href="/containers">Container List</a> &gt; {{ data.container.id }}  </h1>      
+      <h1><a href="/containers">Containers</a> <span style="font-size:18px"> / {{ data.container.name }}</span></h1>
       {% else %}
-      <h1>Container List</h1>
+      <h1>Containers</h1>
       {% endif %}
       
       <hr/>
-
+      <div class="row" style="padding:5px">
       {% if data.container %}
-      {% include "components/container.html" with container=data.container %}
+      {% include "components/container.html" with container=data.container details=True %}
       {% else %} 
       {% for container in data.containers %}
       {% include "components/container.html" with container=container %}
-      <br />
       {% endfor %}
-
-      <br />
-      <a href="/add_container">Add new...</a>
-      
       {% endif %}
-      <br />
-      <br />
-      <br />
-      <br />
+      </div>
+      
+      <div class="row" style="padding:10px; padding-left:15px">   
+      <a href="/add_container">Add new...</a>
+      </div>
+        
+      <br/>
+      <br/>
+      <br/>
+      <br/>
+      <br/>
+      <br/>
 
     </div>
   </div>
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 3e3ca9d..84545a0 100644
--- a/services/webapp/code/rosetta/core_app/templates/create_task.html
+++ b/services/webapp/code/rosetta/core_app/templates/create_task.html
@@ -34,16 +34,18 @@
 
           <tr>
             <td><b>Task container</b></td><td>
+              {% if data.container %}
+              <select name="task_container_uuid">
+              <option value="{{data.container.uuid}}" selected>{{data.container.name}} ({{data.container.type}})</option>
+              </select>
+              {% else %}
               <select name="task_container_uuid" >
-              <!-- <option value="metadesktop" selected>Meta Desktop</option>
-              <option value="astroccok">Astrocook</option>
-              <option value="gadgetviewer">Gadget Viewer</option> -->
               {% for container in data.containers %}
               <option value="{{container.uuid}}">{{container.name}} ({{container.type}})</option>
-              {% endfor %}              
-              
+              {% endfor %}
               </select>
-              &nbsp; | <a href="/add_container">Add new...</a>          
+              {% endif %}
+              <!--  &nbsp; | <a href="/add_container">Add new...</a>  -->  
             </td>
            </tr>
            
@@ -51,10 +53,10 @@
             <td><b>Computing resource</b></td><td>
               <select name="task_computing" >
               {% for computing in data.computings %}}
-              <option value="{{ computing.uuid }}">{{ computing.name}} ({% if computing.user %}{{ computing.user }}{% else %}platform{% endif %})</option>
+              <option value="{{ computing.uuid }}">{{ computing.name}}</option>  <!--  ({% if computing.user %}{{ computing.user }}{% else %}platform{% endif %})  -->
               {% endfor %}
               </select>
-              &nbsp; | <a href="/add_computing">Add new...</a>
+              <!--  &nbsp; | <a href="/add_computing">Add new...</a>-->
             </td>
            </tr>
 
diff --git a/services/webapp/code/rosetta/core_app/templates/edit_computing_conf.html b/services/webapp/code/rosetta/core_app/templates/edit_computing_conf.html
index efcbca3..82092ba 100644
--- a/services/webapp/code/rosetta/core_app/templates/edit_computing_conf.html
+++ b/services/webapp/code/rosetta/core_app/templates/edit_computing_conf.html
@@ -11,7 +11,6 @@
       <h1>Edit computing conf</h1> 
       <hr>
 
-
       <h4>Edit the configuration in JSON format for {{ data.computing }}</h4> 
       
       <br/>
diff --git a/services/webapp/code/rosetta/core_app/templates/footer.html b/services/webapp/code/rosetta/core_app/templates/footer.html
index f7c760f..6e4daa4 100644
--- a/services/webapp/code/rosetta/core_app/templates/footer.html
+++ b/services/webapp/code/rosetta/core_app/templates/footer.html
@@ -31,8 +31,24 @@
             }
         });
     });
-    </script>
 
+    function stringToColour(str) {
+
+        // str to hash
+        for (var i = 0, hash = 0; i < str.length; hash = str.charCodeAt(i++) + ((hash << 5) - hash));
+
+        // int/hash to hex
+        for (var i = 0, colour = "#"; i < 3; colour += ("00" + ((hash >> i++ * 8) & 0xFF).toString(16)).slice(-2));
+
+        colour_rgb = hexToRgb(colour)
+
+        return "rgba(" + colour_rgb.r + "," + colour_rgb.g + "," + colour_rgb.b + ",0.3)"    
+        
+    }
+
+    </script>
+    
+    
 </body>
 
 </html>
diff --git a/services/webapp/code/rosetta/core_app/templates/navigation.html b/services/webapp/code/rosetta/core_app/templates/navigation.html
index b18e4f6..0693114 100644
--- a/services/webapp/code/rosetta/core_app/templates/navigation.html
+++ b/services/webapp/code/rosetta/core_app/templates/navigation.html
@@ -25,14 +25,16 @@
                 <a href="/account" onclick = $("#menu-close").click(); >Account</a>
             </li>        
             <li>
-                <a href="/tasks" onclick = $("#menu-close").click(); >Tasks</a>
+                <a href="/containers" onclick = $("#menu-close").click(); >Containers</a>
             </li>
             <li>
                 <a href="/computings" onclick = $("#menu-close").click(); >Computing</a>
-            </li>
+            </li>            
             <li>
-                <a href="/containers" onclick = $("#menu-close").click(); >Containers</a>
-            </li>           
+                <a href="/tasks" onclick = $("#menu-close").click(); >Tasks</a>
+            </li>
+
+       
             {% else %}
             <li>
               <center>
diff --git a/services/webapp/code/rosetta/core_app/templates/tasks.html b/services/webapp/code/rosetta/core_app/templates/tasks.html
index 46e808c..cf14bdc 100644
--- a/services/webapp/code/rosetta/core_app/templates/tasks.html
+++ b/services/webapp/code/rosetta/core_app/templates/tasks.html
@@ -9,24 +9,29 @@
   <div class="dashboard">
     <div class="span8 offset2">
       
-
       {% if data.task %}
-      <h1><a href="/tasks">Task List</a> &gt; {{ data.task.id }}  </h1>      
+      <h1><a href="/tasks">Tasks</a> <span style="font-size:18px"> / {{ data.task.name }}</span></h1>      
       {% else %}
-      <h1>Task List</h1>
+      <h1>Tasks</h1>
       {% endif %}
 
       <hr>
+      <div class="row" style="padding:5px">
       {% if data.task %}
       {% include "components/task.html" with task=data.task details=True %}
       {% else %}
       {% for task in data.tasks %}
       {% include "components/task.html" with task=task %}
       {% endfor %}
-      <br />
+      {% endif %}
+      </div>
+
+      {% if not data.task %}
+      <div class="row" style="padding:10px; padding-left:15px">   
       <a href="/create_task">Create new...</a>
+      </div>
       {% endif %}
-           
+
       <br/>
       <br/>
       <br/>
diff --git a/services/webapp/code/rosetta/core_app/utils.py b/services/webapp/code/rosetta/core_app/utils.py
index 6d1bfb8..2cb1455 100644
--- a/services/webapp/code/rosetta/core_app/utils.py
+++ b/services/webapp/code/rosetta/core_app/utils.py
@@ -1,4 +1,5 @@
 import os
+import hashlib
 import traceback
 import hashlib
 import random
@@ -14,6 +15,20 @@ from .exceptions import ErrorMessage
 logger = logging.getLogger(__name__)
 
 
+# Colormap (See https://bhaskarvk.github.io/colormap/reference/colormap.html)
+color_map = ["#440154", "#440558", "#450a5c", "#450e60", "#451465", "#461969",
+             "#461d6d", "#462372", "#472775", "#472c7a", "#46307c", "#45337d",
+             "#433880", "#423c81", "#404184", "#3f4686", "#3d4a88", "#3c4f8a",
+             "#3b518b", "#39558b", "#37598c", "#365c8c", "#34608c", "#33638d",
+             "#31678d", "#2f6b8d", "#2d6e8e", "#2c718e", "#2b748e", "#29788e",
+             "#287c8e", "#277f8e", "#25848d", "#24878d", "#238b8d", "#218f8d",
+             "#21918d", "#22958b", "#23988a", "#239b89", "#249f87", "#25a186",
+             "#25a584", "#26a883", "#27ab82", "#29ae80", "#2eb17d", "#35b479",
+             "#3cb875", "#42bb72", "#49be6e", "#4ec16b", "#55c467", "#5cc863",
+             "#61c960", "#6bcc5a", "#72ce55", "#7cd04f", "#85d349", "#8dd544",
+             "#97d73e", "#9ed93a", "#a8db34", "#b0dd31", "#b8de30", "#c3df2e",
+             "#cbe02d", "#d6e22b", "#e1e329", "#eae428", "#f5e626", "#fde725"]
+
 #======================
 #  Utility functions
 #======================
@@ -465,6 +480,12 @@ def get_tunnel_host():
     tunnel_host = os.environ.get('ROSETTA_TUNNEL_HOST', 'localhost')
     return tunnel_host
 
+def hash_string_to_int(string):
+    #int_hash = 0 
+    #for char in string:
+    #    int_hash += ord(char)
+    #return int_hash
+    return int(hashlib.sha1(string.encode('utf8')).hexdigest(), 16) #% (10 ** 8)
 
 
 
diff --git a/services/webapp/code/rosetta/core_app/views.py b/services/webapp/code/rosetta/core_app/views.py
index db53296..07b87b6 100644
--- a/services/webapp/code/rosetta/core_app/views.py
+++ b/services/webapp/code/rosetta/core_app/views.py
@@ -8,7 +8,7 @@ from django.contrib.auth import authenticate, login, logout
 from django.http import HttpResponse, HttpResponseRedirect
 from django.contrib.auth.models import User
 from django.shortcuts import redirect
-from .models import Profile, LoginToken, Task, TaskStatuses, Container, Computing, Keys, ComputingSysConf, ComputingUserConf
+from .models import Profile, LoginToken, Task, TaskStatuses, Container, Computing, KeyPair, ComputingSysConf, ComputingUserConf
 from .utils import send_email, format_exception, timezonize, os_shell, booleanize, debug_param, get_tunnel_host, random_username
 from .decorators import public_view, private_view
 from .exceptions import ErrorMessage
@@ -186,7 +186,7 @@ def register_view(request):
                 
             
             # Create key objects
-            Keys.objects.create(user = user,
+            KeyPair.objects.create(user = user,
                                 default = True,
                                 private_key_file = '/data/resources/keys/{}_id_rsa'.format(user.username),
                                 public_key_file = '/data/resources/keys/{}_id_rsa.pub'.format(user.username))
@@ -250,7 +250,7 @@ def account(request):
         edit = None
 
     # Set data.default_public_key
-    with open(Keys.objects.get(user=request.user, default=True).public_key_file) as f:
+    with open(KeyPair.objects.get(user=request.user, default=True).public_key_file) as f:
         data['default_public_key'] = f.read()
 
     # Edit values
@@ -456,13 +456,25 @@ def create_task(request):
     data['profile'] = Profile.objects.get(user=request.user)
     data['title']   = 'New Task'
 
-    # Get containers and computings 
-    data['containers'] = list(Container.objects.filter(user=None)) + list(Container.objects.filter(user=request.user))
-    data['computings'] = list(Computing.objects.filter(user=None)) + list(Computing.objects.filter(user=request.user))
-
     # Step if any
     step = request.POST.get('step', None)
 
+    # Container uuid if any
+    container_uuid = request.GET.get('container_uuid', None)
+    if container_uuid:
+        try:
+            data['container'] = Container.objects.get(uuid=container_uuid, user=request.user)
+        except Container.DoesNotExist:
+            data['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))
+
+        
+
     if step == 'one':
 
         # We have a step one submitted, get the first tab parameters
@@ -857,6 +869,8 @@ def edit_computing_conf(request):
             computingSysConf.data = new_conf_data
             computingSysConf.save()
             data['saved'] = True
+            return HttpResponseRedirect('/computings')
+
 
         # Dump conf data for the webpage
         if computingSysConf.data:
-- 
GitLab