Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision

Target

Select target project
  • exact/Rosetta
1 result
Select Git revision
Show changes
Commits on Source (5)
Showing
with 630 additions and 528 deletions
......@@ -12,7 +12,7 @@ if [[ "x$1" == "xnocache" ]] ; then
NOCACHE=true
SERVICE=""
elif [[ "x$2" == "xnocache" ]] ; then
NOCACHE=true
NOCACHE=true
SERVICE=$1
else
if [[ "x$NOCACHE" == "x" ]] ; then
......@@ -29,20 +29,20 @@ else
fi
if [[ "x$SERVICE" == "x" ]] ; then
# Build all services
NOCACHE=$NOCACHE rosetta/build base_ubuntu18.04
NOCACHE=$NOCACHE rosetta/build base_ubuntu22.04
NOCACHE=$NOCACHE rosetta/build slurmbase
NOCACHE=$NOCACHE rosetta/build slurmcluster
NOCACHE=$NOCACHE rosetta/build slurmclustermaster
NOCACHE=$NOCACHE rosetta/build slurmclusterworker
NOCACHE=$NOCACHE rosetta/build standaloneworker
NOCACHE=$NOCACHE rosetta/build slurmcluster
NOCACHE=$NOCACHE rosetta/build slurmclustermaster
NOCACHE=$NOCACHE rosetta/build slurmclusterworker
NOCACHE=$NOCACHE rosetta/build standaloneworker
NOCACHE=$NOCACHE rosetta/build dregistry
NOCACHE=$NOCACHE rosetta/build webapp
NOCACHE=$NOCACHE rosetta/build postgres
NOCACHE=$NOCACHE rosetta/build proxy
else
# Build a specific image
......
......@@ -14,7 +14,7 @@ fi
if [[ "x$2" != "x" ]] ; then
tail -f -n 1000 data/$1/log/$2.log
else
docker-compose logs -f $1
docker-compose logs -f $1
fi
......@@ -14,7 +14,7 @@ elif [[ $# -gt 2 ]] ; then
echo "Use double quotes to wrap commands with spaces"
exit 1
else
COMMAND=$2
if [[ "x$COMMAND" == "x" ]] ; then
echo ""
......
......@@ -10,16 +10,16 @@ if [[ $# -eq 0 ]] ; then
declare -a container_names
OUT=$(rosetta/ps)
while read -r line; do
if [[ $line == *"Up"* ]]; then
container_name=$(echo $line | cut -d ' ' -f1)
container_names+=($container_name);
fi
done <<< "$OUT"
for container_name in ${container_names[@]}
do
echo ""
......@@ -27,7 +27,7 @@ if [[ $# -eq 0 ]] ; then
docker-compose exec $container_name /bin/bash -c "supervisorctl status"
done
echo ""
else
docker-compose exec $@ /bin/bash -c "supervisorctl status"
fi
......@@ -9,9 +9,9 @@ logger = logging.getLogger(__name__)
class RosettaOIDCAuthenticationBackend(OIDCAuthenticationBackend):
def create_user(self, claims):
# Call parent user creation function
user = super(RosettaOIDCAuthenticationBackend, self).create_user(claims)
......@@ -27,12 +27,12 @@ class RosettaOIDCAuthenticationBackend(OIDCAuthenticationBackend):
class RosettaOIDCAuthenticationCallbackView(OIDCAuthenticationCallbackView):
def login_success(self):
# Call parent login_success but do not return
super(RosettaOIDCAuthenticationCallbackView, self).login_success()
logger.debug('Trying to get cookie-based post login redirect')
post_login_page = self.request.COOKIES.get('post_login_redirect')
if post_login_page:
......@@ -44,3 +44,4 @@ class RosettaOIDCAuthenticationCallbackView(OIDCAuthenticationCallbackView):
logger.debug('No cookie-based post login redirect found, redirecting to "%s"', self.success_url)
return HttpResponseRedirect(self.success_url)
......@@ -2,7 +2,7 @@ import os
from django.conf import settings
def export_vars(request):
data = {}
# Set open id connect enabled or not
if settings.OIDC_RP_CLIENT_ID:
data['OPENID_ENABLED'] = True
......@@ -19,6 +19,6 @@ def export_vars(request):
if settings.INVITATION_CODE:
data['INVITATION_CODE_ENABLED'] = True
else:
data['INVITATION_CODE_ENABLED'] = False
return data
\ No newline at end of file
data['INVITATION_CODE_ENABLED'] = False
return data
......@@ -51,7 +51,7 @@ def public_view(wrapped_view):
error_text = str(e)
else:
# Log the exception
# Log the exception
logger.error(format_exception(e))
# Raise the exception if we are in debug mode
......@@ -109,13 +109,13 @@ def private_view(wrapped_view):
error_text = str(e)
else:
# Log the exception
# Log the exception
logger.error(format_exception(e))
# Raise the exception if we are in debug mode
if settings.DEBUG:
raise
# Otherwise, mask it
else:
error_text = 'something went wrong ({})'.format(e)
......@@ -137,3 +137,4 @@ def private_view(wrapped_view):
response.set_cookie('post_login_redirect', request.build_absolute_uri())
return response
return private_view_wrapper
......@@ -3,4 +3,4 @@ class ErrorMessage(Exception):
pass
class ConsistencyException(Exception):
pass
\ No newline at end of file
pass
......@@ -26,4 +26,4 @@ class JSONField(Field):
def value_to_string(self, obj):
return self.value_from_object(obj)
# Credits: https://medium.com/@philamersune/using-postgresql-jsonfield-in-sqlite-95ad4ad2e5f1
\ No newline at end of file
# Credits: https://medium.com/@philamersune/using-postgresql-jsonfield-in-sqlite-95ad4ad2e5f1
......@@ -8,13 +8,13 @@ class Command(BaseCommand):
def handle(self, *args, **options):
#=====================
#=====================
# Testuser
#=====================
try:
testuser = User.objects.get(username='testuser')
print('Not creating test user as it already exists')
except User.DoesNotExist:
print('Creating test user with default password')
testuser = User.objects.create_user('testuser', 'testuser@rosetta.platform', 'testpass')
......@@ -22,7 +22,7 @@ class Command(BaseCommand):
testuser.is_staff = True
testuser.is_admin=True
testuser.is_superuser=True
testuser.save()
testuser.save()
print('Creating testuser profile')
Profile.objects.create(user=testuser, auth='local', authtoken='129aac94-284a-4476-953c-ffa4349b4a50')
......@@ -32,23 +32,23 @@ class Command(BaseCommand):
default = True,
private_key_file = '/rosetta/.ssh/id_rsa',
public_key_file = '/rosetta/.ssh/id_rsa.pub')
#=====================
#=====================
# Platform keys
#=====================
# TODO: create a different pair
try:
KeyPair.objects.get(user=None, default=True)
print('Not creating default platform keys as they already exist')
except KeyPair.DoesNotExist:
print('Creating platform default keys')
KeyPair.objects.create(user = None,
default = True,
private_key_file = '/rosetta/.ssh/id_rsa',
public_key_file = '/rosetta/.ssh/id_rsa.pub')
#=====================
# Default home page
......@@ -60,7 +60,7 @@ class Command(BaseCommand):
<h1>&nbsp;&nbsp;Rosetta <img src="/static/img/emoji_u1f6f0.png" style="height:84px; width:64px; padding-bottom:20px"></h1>
<h2 style="margin-top:10px; margin-left:25px; margin-right:25px; font-weight:100; line-height: 30px;"><i>A container-centric Science Platform<br></i></h2>
</div>
</div>
</div>
<div class="container">
<div class="dashboard">
<div class="span8 offset2" style="margin: 30px auto; max-width:800px">
......@@ -69,7 +69,7 @@ class Command(BaseCommand):
This is the default main page content loaded after populating the platform with the default/demo data.
To change it, head to the <a href="/admin">admin</a> section and edit the <code>Page</code> model with id "main".
<br/><br/>
A test user with admin rights registered with email <code>testuser@rosetta.platform</code> and password
A test user with admin rights registered with email <code>testuser@rosetta.platform</code> and password
<code>testpass</code> has been created as well, which you can use to login on the menu on the right and give Rosetta
immediately a try. If you are using the default docker-compose file (i.e. you just ran <code>rosetta/setup</code>),
then you will also have a few demo computing and storage resources (beside the internal one) already available
......@@ -79,10 +79,10 @@ class Command(BaseCommand):
<br />
You can also create custom pages and access them under <code>/pages/page_id</code> should you need to provide
your users informations about the platform and its storage and computing resources. For example, see this
demo extra <a href="/pages/help">help page</a>.
demo extra <a href="/pages/help">help page</a>.
</div>
</div>
</div>
</div>
</header>
'''
home_page = Page.objects.filter(id='main')
......@@ -96,15 +96,15 @@ class Command(BaseCommand):
<h1>Help!</h1>
<hr>
<p>
This is a demo extra page (a help page, in this case). Here you could for example provide the instructions on how to set up SSH-based
This is a demo extra page (a help page, in this case). Here you could for example provide the instructions on how to set up SSH-based
computing resources using user keys, or who to contact to join a specific group to access its software and computing resources.
</p>
<p>
In general, the part of the URL following the <code>/pages/</code> path is parsed as the page id,
In general, the part of the URL following the <code>/pages/</code> path is parsed as the page id,
so that if a page with that id exists in the database, its content will show up here.
You can use this system for creating a mini-website inside the platform
to provide help, news and informations on your deployment. Or you can just ignore the whole thing and leave a plain logo in the main page.
You can use this system for creating a mini-website inside the platform
to provide help, news and informations on your deployment. Or you can just ignore the whole thing and leave a plain logo in the main page.
</p>
'''
......@@ -117,16 +117,16 @@ to provide help, news and informations on your deployment. Or you can just ignor
#=====================
#=====================
# Platform containers
#=====================
#=====================
platform_containers = Container.objects.filter(user=None)
if platform_containers:
print('Not creating public containers as they already exist')
else:
print('Creating platform containers...')
# Minimal Desktop
Container.objects.create(user = None,
name = 'Minimal Desktop',
......@@ -157,8 +157,8 @@ to provide help, news and informations on your deployment. Or you can just ignor
supports_custom_interface_port = True,
supports_interface_auth = True,
interface_auth_user = None)
# Jupyter Notebook
Container.objects.create(user = None,
name = 'Jupyter Notebook',
......@@ -178,7 +178,7 @@ to provide help, news and informations on your deployment. Or you can just ignor
# Official Jupyter containers
for tag in ['lab-3.2.2', 'lab-3.1.17']:
Container.objects.create(user = None,
name = 'Jupyter Data Science Lab',
description = 'The official Jupyter Lab. The Data Science variant, which includes libraries for data analysis from the Julia, Python, and R communities.',
......@@ -192,7 +192,7 @@ to provide help, news and informations on your deployment. Or you can just ignor
interface_transport = 'tcp/ip',
supports_custom_interface_port = True,
supports_interface_auth = True)
for arch in ['amd64', 'arm64']:
Container.objects.create(user = None,
name = 'Jupyter Lab',
......@@ -225,9 +225,9 @@ to provide help, news and informations on your deployment. Or you can just ignor
supports_interface_auth = True,
interface_auth_user = 'metauser')
#=====================
#=====================
# Testuser containers
#=====================
#=====================
#testuser_containers = Container.objects.filter(user=testuser)
#if testuser_containers:
# print('Not creating testuser private containers as they already exist')
......@@ -250,9 +250,9 @@ to provide help, news and informations on your deployment. Or you can just ignor
# supports_interface_auth = False)
#=====================
#=====================
# Computing resources
#=====================
#=====================
computing_resources = Computing.objects.all()
if computing_resources:
print('Not creating demo computing resources as they already exist')
......@@ -270,7 +270,7 @@ to provide help, news and informations on your deployment. Or you can just ignor
wms = None,
container_engines = ['docker'])
# Demo standalone computing plus conf
demo_standalone_computing = Computing.objects.create(name = 'Demo Standalone',
description = 'A demo standalone computing resource.',
......@@ -311,22 +311,22 @@ to provide help, news and informations on your deployment. Or you can just ignor
wms = 'slurm',
conf = {'host': 'slurmclustermaster', 'default_partition': 'partition1'},
container_engines = ['singularity'])
# Add testuser extra conf for this computing resource
testuser.profile.add_extra_conf(conf_type = 'computing_user', object=demo_slurm_computing, value= 'testuser')
#=====================
#=====================
# Storages
#=====================
#=====================
storages = Storage.objects.all()
if storages:
print('Not creating demo storage resources as they already exist')
else:
print('Creating demo storage resources...')
# Get demo computing resources
demo_computing_resources = []
try:
try:
demo_slurm_computing = Computing.objects.get(name='Demo Cluster')
demo_computing_resources.append(demo_slurm_computing)
except:
......@@ -336,8 +336,8 @@ to provide help, news and informations on your deployment. Or you can just ignor
demo_computing_resources.append(demo_standalone_computing)
except:
pass
for computing in demo_computing_resources:
# Demo shared storage
Storage.objects.create(computing = computing,
......@@ -348,7 +348,7 @@ to provide help, news and informations on your deployment. Or you can just ignor
auth_mode = 'user_keys',
base_path = '/shared/data/shared',
bind_path = '/storages/shared')
# Demo personal storage
Storage.objects.create(computing = computing,
access_through_computing = True,
......@@ -359,3 +359,4 @@ to provide help, news and informations on your deployment. Or you can just ignor
base_path = '/shared/data/users/$SSH_USER',
bind_path = '/storages/personal')
......@@ -37,7 +37,7 @@ class TaskStatuses(object):
# using an UUID pointing to some other model instead of the value in future, should this be necessary.
#=========================
# Profile
# Profile
#=========================
class Profile(models.Model):
......@@ -68,25 +68,25 @@ class Profile(models.Model):
self.extra_confs = {}
self.extra_confs[str(uuid.uuid4())] = {'type': conf_type, 'object_uuid': str(object.uuid), 'value': value}
self.save()
def get_extra_conf(self, conf_type, object=None):
if self.extra_confs:
for extra_conf in self.extra_confs:
if conf_type == self.extra_confs[extra_conf]['type']:
if object:
#logger.debug("{} vs {}".format(self.extra_confs[extra_conf]['object_uuid'], str(object.uuid)))
if self.extra_confs[extra_conf]['object_uuid'] == str(object.uuid):
return self.extra_confs[extra_conf]['value']
return self.extra_confs[extra_conf]['value']
else:
return self.extra_confs[extra_conf]['value']
return None
#=========================
# Login Token
# Login Token
#=========================
class LoginToken(models.Model):
......@@ -106,7 +106,7 @@ class LoginToken(models.Model):
class Container(models.Model):
uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
user = models.ForeignKey(User, related_name='containers', on_delete=models.CASCADE, blank=True, null=True)
user = models.ForeignKey(User, related_name='containers', on_delete=models.CASCADE, blank=True, null=True)
# If a container has no user, it will be available to anyone. Can be created, edited and deleted only by admins.
group = models.ForeignKey(Group, related_name='containers', on_delete=models.CASCADE, blank=True, null=True)
# If a container has no group, it will be available to anyone. Can be created, edited and deleted only by admins.
......@@ -114,25 +114,25 @@ class Container(models.Model):
# Generic attributes
name = models.CharField('Name', max_length=255, blank=False, null=False)
description = models.TextField('Description', blank=True, null=True)
# Registry
registry = models.CharField('Registry', max_length=255, blank=False, null=False)
# Image name
image_name = models.CharField('Image', max_length=255, blank=False, null=False)
# Image identifiers
image_tag = models.CharField('Tag', max_length=255, blank=False, null=False, default='latest')
image_arch = models.CharField('Architecture', max_length=36, blank=True, null=True)
image_os = models.CharField('Operating system', max_length=36, blank=True, null=True)
# -- OR --
image_digest = models.CharField('SHA 256 digest', max_length=96, blank=True, null=True)
# TODO: do we want more control with respect to kernel, CPUs, instruction sets?
# TODO: do we want more control with respect to kernel, CPUs, instruction sets?
# requires = i.e. kernel > 3, intel, AVX2
# Port, protocol and transport for the container interface
interface_port = models.IntegerField('Interface port', blank=True, null=True)
interface_port = models.IntegerField('Interface port', blank=True, null=True)
interface_protocol = models.CharField('Interface protocol', max_length=36, blank=True, null=True)
interface_transport = models.CharField('Interface transport', max_length=36, blank=True, null=True)
......@@ -156,7 +156,7 @@ class Container(models.Model):
# Check that digest starts with sha256:
if self.image_digest and not self.image_digest.startswith('sha256:'):
raise ValueError('The digest field must start with "sha256:"')
super(Container, self).save(*args, **kwargs)
@property
......@@ -183,7 +183,7 @@ class Computing(models.Model):
uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
group = models.ForeignKey(Group, related_name='computings', on_delete=models.CASCADE, blank=True, null=True)
# If a compute resource has no group, it will be available to anyone. Can be created, edited and deleted only by admins.
name = models.CharField('Name', max_length=255, blank=False, null=False)
description = models.TextField('Description', blank=True, null=True)
......@@ -195,16 +195,16 @@ class Computing(models.Model):
access_mode = models.CharField('Access (control) mode', max_length=36, blank=False, null=False)
auth_mode = models.CharField('Auth mode', max_length=36, blank=False, null=False)
wms = models.CharField('Workload management system', max_length=36, blank=True, null=True)
# Supported container engines (e.g. ['docker', 'singularity', 'podman'])
container_engines = JSONField('Container engines/runtimes', blank=False, null=False)
#container_runtime = models.CharField('Container runtimes', max_length=256, blank=False, null=False)
# Supported architectures (i.e. 386 for amd64), as list: ['386']
supported_archs = JSONField('Supported architectures', blank=True, null=True)
supported_archs = JSONField('Supported architectures', blank=True, null=True)
# Emulated architectures, by container runtime: {'docker': ['arm64/v7', 'arm64/v8']
emulated_archs = JSONField('Emulated architectures', blank=True, null=True)
# Emulated architectures, by container runtime: {'docker': ['arm64/v7', 'arm64/v8']
emulated_archs = JSONField('Emulated architectures', blank=True, null=True)
# Conf
conf = JSONField(blank=True, null=True)
......@@ -233,23 +233,23 @@ class Computing(models.Model):
@property
def default_container_engine(self):
return self.container_engines[0]
#=======================
# Computing manager
#=======================
@property
def manager(self):
from . import computing_managers
# Hash table mapping
managers_mapping = {}
managers_mapping['cluster'+'ssh+cli'+'user_keys'+'slurm'] = computing_managers.SlurmSSHClusterComputingManager
managers_mapping['standalone'+'ssh+cli'+'user_keys'+'None'] = computing_managers.SSHStandaloneComputingManager
managers_mapping['standalone'+'ssh+cli'+'platform_keys'+'None'] = computing_managers.SSHStandaloneComputingManager
managers_mapping['standalone'+'ssh+cli'+'platform_keys'+'None'] = computing_managers.SSHStandaloneComputingManager
managers_mapping['standalone'+'internal'+'internal'+'None'] = computing_managers.InternalStandaloneComputingManager
# Instantiate the computing manager and return (if not already done)
try:
return self._manager
......@@ -262,10 +262,10 @@ class Computing(models.Model):
else:
return self._manager
#=========================
# Tasks
# Tasks
#=========================
class Task(models.Model):
......@@ -277,16 +277,16 @@ class Task(models.Model):
# Task management
status = models.CharField('Status', max_length=36, blank=True, null=True)
created = models.DateTimeField('Created on', default=timezone.now)
process_id = models.CharField('Process ID', max_length=64, blank=True, null=True) # i.e. Singularity PID
job_id = models.CharField('Job ID', max_length=64, blank=True, null=True) # i.e. Slurm job id
process_id = models.CharField('Process ID', max_length=64, blank=True, null=True) # i.e. Singularity PID
job_id = models.CharField('Job ID', max_length=64, blank=True, null=True) # i.e. Slurm job id
# How to reach the task interface. The IP has to be intended either as the container IP if this is directly
# reachable (i.e. using a Docker or Kubernetes network) or as the host IP address, depending on the
# computing resource and its computing manager/WMS/container runtime. The port is to be intended
# as the port where the task interface is exposed on its IP address.
interface_ip = models.CharField('Interface IP address', max_length=36, blank=True, null=True)
interface_port = models.IntegerField('Interface port', blank=True, null=True)
interface_port = models.IntegerField('Interface port', blank=True, null=True)
# Task access
requires_tcp_tunnel = models.BooleanField('Requires TCP tunnel')
tcp_tunnel_port = models.IntegerField('TCP tunnel port', blank=True, null=True)
......@@ -301,13 +301,13 @@ class Task(models.Model):
# Computing options
# TODO: add the option for selecting the runtime as advanced option when creating the task?
computing_options = JSONField('Computing options', blank=True, null=True) # i.e. CPUs, RAM, cluster partition etc. TODO: why here?
class Meta:
ordering = ['-created']
def save(self, *args, **kwargs):
try:
getattr(TaskStatuses, str(self.status))
except AttributeError:
......@@ -318,25 +318,25 @@ class Task(models.Model):
def update_status(self):
if self.computing == 'local':
check_command = 'sudo docker inspect --format \'{{.State.Status}}\' ' + self.tid # or, .State.Running
out = os_shell(check_command, capture=True)
logger.debug('Status: "{}"'.format(out.stdout))
if out.exit_code != 0:
if out.exit_code != 0:
if (('No such' in out.stderr) and (self.tid in out.stderr)):
logger.debug('Task "{}" is not running in reality'.format(self.tid))
self.status = TaskStatuses.exited
else:
if out.stdout == 'running':
self.status = TaskStatuses.running
elif out.stdout == 'exited':
self.status = TaskStatuses.exited
else:
raise Exception('Unknown task status: "{}"'.format(out.stdout))
self.save()
self.save()
def __str__(self):
......@@ -351,11 +351,11 @@ class Task(models.Model):
string_int_hash = hash_string_to_int(self.name)
color_map_index = string_int_hash % len(color_map)
return color_map[color_map_index]
@property
def sharable_link(self):
return 'https://{}/t/{}'.format(settings.ROSETTA_HOST, self.short_uuid)
@property
def tcp_tunnel_host(self):
return get_rosetta_tasks_tunnel_host()
......@@ -369,61 +369,65 @@ class Task(models.Model):
#=========================
class Storage(models.Model):
uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
group = models.ForeignKey(Group, related_name='storages', on_delete=models.CASCADE, blank=True, null=True)
name = models.CharField('Name', max_length=255, blank=False, null=False)
#description = models.TextField('Description', blank=True, null=True)
# Storage type
type = models.CharField('Type', max_length=255, blank=False, null=False)
# Access and auth mode
# Access and auth mode
access_mode = models.CharField('Access (control) mode', max_length=36, blank=False, null=False)
auth_mode = models.CharField('Auth mode', max_length=36, blank=False, null=False)
# Paths
base_path = models.CharField('Base path', max_length=4096, blank=False, null=False)
bind_path = models.CharField('Bind path', max_length=4096, blank=True, null=True)
base_path = models.CharField('Base path', max_length=4096, blank=False, null=False)
bind_path = models.CharField('Bind path', max_length=4096, blank=True, null=True)
# Link with a computing resource
computing = models.ForeignKey(Computing, related_name='storages', on_delete=models.CASCADE, blank=True, null=True) # Make optional?
access_through_computing = models.BooleanField('Access through linked computing resource?', default=False)
# If the above is linked, some configuration can be taken from the linked computing resource (i.e. the hostname)
# Configuration
conf = JSONField(blank=True, null=True)
# Include as browsable in the file manager?
browsable = models.BooleanField('Browsable in the file manager?', default=True)
def save(self, *args, **kwargs):
if self.access_mode == 'internal' and self.browsable:
raise ValueError('A storage with "internal" access mode cannot be marked as browsable since it is not yet supported by the file manager')
super(Storage, self).save(*args, **kwargs)
class Meta:
ordering = ['name']
def __str__(self):
if self.group:
return str('Storage "{}" of group "{}"'.format(self.id, self.group))
else:
return str('Storage "{}"'.format(self.id))
@property
def id(self):
return (self.name if not self.computing else '{}:{}'.format(self.computing.name,self.name))
#=========================
# KeyPair
# KeyPair
#=========================
class KeyPair(models.Model):
uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
user = models.ForeignKey(User, related_name='key_pairs', on_delete=models.CASCADE, blank=True, null=True)
user = models.ForeignKey(User, related_name='key_pairs', on_delete=models.CASCADE, blank=True, null=True)
private_key_file = models.CharField('Private key file', max_length=4096, blank=False, null=False)
public_key_file = models.CharField('Public key file', max_length=4096, blank=False, null=False)
......@@ -436,12 +440,12 @@ class KeyPair(models.Model):
return str('KeyPair of user "{}" (default={})'.format( self.user.email, self.default))
else:
return str('KeyPair of user Platform (default={})'.format(self.default))
#=========================
# Page
# Page
#=========================
class Page(models.Model):
......@@ -457,3 +461,4 @@ class Page(models.Model):
@keyframes spin {
to {
transform: rotate(360deg);
}
}
#navigate-away-loader {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
background-color: rgba(0, 0, 0, 0.5);
z-index: 2000;
align-items: center;
justify-content: center;
color: white;
transition: opacity 0.4s ease-in-out;
visibility: hidden;
pointer-events: none;
display: flex;
& > svg {
animation: spin 1.5s infinite linear;
opacity: 0.5;
}
}
function showNavigationLoader() {
var navigationLoader = document.querySelector("#navigate-away-loader")
navigationLoader.style.visibility = "visible"
navigationLoader.style.opacity = 1
navigationLoader.style.pointerEvents = "all"
document.querySelector("#navigate-away-loader > svg").style.animation =
"spin 1.5s infinite linear"
}
function hideNavigationLoader() {
var navigationLoader = document.querySelector("#navigate-away-loader")
navigationLoader.style.visibility = "hidden"
navigationLoader.style.opacity = 0
navigationLoader.style.pointerEvents = "none"
}
window.addEventListener("beforeunload", function (e) {
showNavigationLoader()
return true
})
window.addEventListener("pageshow", function (e) {
hideNavigationLoader()
})
{% load static %}
{% load static %}
{% include "header.html" %}
{% include "navigation.html" %}
{% include "logo.html" %}
......@@ -23,24 +23,24 @@
{{data.user.username}}
</td>
</tr>
<tr>
<td>
<b>Email</b>
</td>
<td>
{% if data.edit == 'email' %}
{% if data.edit == 'email' %}
<table class="frametab"><tr><td>
<input type='hidden' name='edit' value='email' />
<input type="email" value="{{data.user.email}}" name='value' style='width:95%' required autofocus />
</td><td>
<input type="submit" value="Go">
</td></tr></table>
</td></tr></table>
{% else %}
{{data.user.email}}{% if data.user.profile.auth == 'local' %} | <a href="/account/?edit=email">Change</a>{% endif %}
{% endif %}
</td>
</tr>
</tr>
{% if data.user.profile.auth == 'local' %}
<tr>
......@@ -48,19 +48,19 @@
<b>Password</b>
</td>
<td>
{% if data.edit == 'password' %}
{% if data.edit == 'password' %}
<table class="frametab"><tr><td>
<input type='hidden' name='edit' value='password' />
<input type="password" name='value' style='width:95%' required autofocus />
</td><td>
<input type="submit" value="Go">
</td></tr></table>
</td></tr></table>
{% else %}
******* | <a href="/account/?edit=password">Change</a>
{% endif %}
</td>
</tr>
{% endif %}
{% endif %}
<tr>
......@@ -75,24 +75,24 @@
{% else %}
{{ data.user.profile.auth }}
{% endif %}
</td>
</td>
</tr>
</table>
<br />
<h3>Profile</h3>
<table class="dashboard">
<tr>
<td>
<b>Time Zone</b>
</td>
<td>
{% if data.edit == 'timezone' %}
{% if data.edit == 'timezone' %}
{% include "components/tzselect.html" %}
<input type='hidden' name='edit' value='timezone' />
<input type="submit" value="Go">
......@@ -101,7 +101,7 @@
{% endif %}
</td>
</tr>
<tr>
<td valign=top>
......@@ -117,23 +117,23 @@
{% endif %}
{% endfor %}
{% endif %}
: <code>{{conf_data.value}}</code> | <a href='?delete_extra_conf_uuid={{conf_uuid}}'>delete</a>
<br/>
<br/>
{% endfor %}
<span style="margin:3px"><a href="/add_profile_conf" style="line-height:2em">Add new...</a></span>
</td>
</tr>
</tr>
</table>
<br />
<h3>KeyPair</h3>
<table class="dashboard">
<tr>
<td valign="top">
<b>Default public key</b>
......@@ -145,7 +145,7 @@
</table>
</form>
<div style="margin-left:10px; margin-top:40px">
{% if data.user.profile.auth == 'oidc' %}
<form action="{% url 'oidc_logout' %}" method="post">
......@@ -155,18 +155,18 @@
{% else %}
<form action="/logout/" method="get">
<input type="submit" value="logout">
</form>
</form>
{% endif %}
</div>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>
</div>
</div>
</div>
......@@ -178,3 +178,4 @@
{% load static %}
{% load static %}
{% include "header.html" %}
{% include "navigation.html" %}
{% include "logo.html" %}
......@@ -6,19 +6,19 @@
<div class="container">
<div class="dashboard">
<div class="span8 offset2">
<h1>Add extra profile configuration</h1>
<h1>Add extra profile configuration</h1>
<hr>
<h4>Chose the configuration type and add the values</h4>
<h4>Chose the configuration type and add the values</h4>
<br/>
<form action="/add_profile_conf/" method="POST">
{% csrf_token %}
<table class="dashboard" style="max-width:430px">
<tr>
<td>
{%if data.conf_type %}
<select name="conf_type">
......@@ -29,7 +29,7 @@
{% for conf_type in data.conf_types %}
<option value="{{conf_type}}">{{conf_type}}</option>
{% endfor %}
</select>
</select>
{% endif %}
</td>
......@@ -44,11 +44,11 @@
{% for computing in data.computings %}
<option value="{{computing.uuid}}">{{computing.name}}</option>
{% endfor %}
</select>
</select>
{% endif %}
</td>
{% endif %}
{% if data.last_step %}
<td colspan=2 align=center style="padding:20px">
......@@ -62,11 +62,10 @@
<input type="submit" value="Next">
</td>
{% endif %}
</table>
</form>
<br/>
<br/>
<br/>
<br/>
......@@ -75,7 +74,8 @@
<br/>
<br/>
<br/>
<br/>
</div>
</div>
</div>
......@@ -87,3 +87,4 @@
{% load static %}
{% load static %}
{% include "header.html" %}
{% include "navigation.html" %}
{% include "logo.html" %}
......@@ -6,24 +6,24 @@
<div class="container">
<div class="dashboard">
<div class="span8 offset2">
<h1>Add software container</h1>
<h1>Add software container</h1>
<hr>
{% if not data.added %}
Here you can add a new software container on the platform. You can add containers from image registries
as <a href="https://hub.docker.com/">Docker Hub </a>or by importing Git repositories, provided that they
are compatible with <a href="https://mybinder.readthedocs.io/en/latest/introduction.html">Binder</a> specifications.
are compatible with <a href="https://mybinder.readthedocs.io/en/latest/introduction.html">Binder</a> specifications.
<br/>
<br/>
{% if data.new_container_from == 'registry' %}
<div style="font-size:1.1em; background:whitesmoke; display:inline-block; padding:2px 15px 2px 15px">New container from registry</div>
<div style="font-size:1.1em; background:white; display:inline-block; padding:2px 15px 2px 15px"><a href="?new_container_from=repository">New container from Git repository</a></div>
<div style="font-size:1.1em; background:white; display:inline-block; padding:2px 15px 2px 15px"><a href="?new_container_from=repository">New container from Git repository</a></div>
<hr style="margin-top:0;">
<h4>Basics</h4>
<h4>Basics</h4>
<form action="#" method="POST">
{% csrf_token %}
......@@ -43,7 +43,7 @@
<textarea name="container_description" rows="3" cols="22"></textarea>
</td>
</tr>
<tr>
<td><b>Registry</b></td><td>
<input type="text" name="container_registry" value="docker.io" size="23" required />
......@@ -68,7 +68,7 @@
<h4>Interface </h4>
<h4>Interface </h4>
<table class="dashboard" style="width:400px; margin-bottom:25px">
<tr>
......@@ -80,7 +80,7 @@
<td><b>Interface protocol</b></td>
<td>
{% if request.user.profile.is_power_user %}
<input type="text" value="http" name="container_interface_protocol" size="5" />
<input type="text" value="http" name="container_interface_protocol" size="5" />
{% else %}
<select name="container_interface_protocol" >
<option value="http" selected>http</option>
......@@ -91,13 +91,13 @@
</tr>
</table>
<a href="javascript:void(0);" id="show_button" onclick="toggle_visibility('advanced_div')">Advanced...</a>
<div id="advanced_div" style="display:none; width:400px;">
<h4>Advanced <font size=-1>| <a href="javascript:void(0);" id="hide_button" onclick="toggle_visibility('advanced_div')" style="display:none">hide</a></font></h4>
<table class="dashboard" style="width:400px; margin-bottom:25px">
<tr>
......@@ -130,21 +130,21 @@
<tr>
<td colspan=2>
<b>Supports custom interface port</b> &nbsp;
<b>Supports custom interface port</b> &nbsp;
<input type="checkbox" name="container_supports_custom_interface_port" value="True" />
</td>
</tr>
<tr>
<td colspan=2>
<b>Supports interface auth</b> &nbsp;
<b>Supports interface auth</b> &nbsp;
<input type="checkbox" name="container_supports_interface_auth" value="True" />
</td>
</tr>
<tr>
<td colspan=2>
<b>Disable HTTP auth embedding</b> &nbsp;
<b>Disable HTTP auth embedding</b> &nbsp;
<input type="checkbox" name="container_disable_http_basicauth_embedding" value="True" />
</td>
</tr>
......@@ -158,7 +158,7 @@
</table>
</div>
<table style="width:400px; border:0; background:#ffffff; margin-top:20px">
<tr><td align="center">
<input type="submit" value="Add">
......@@ -166,17 +166,17 @@
</table>
<input type="hidden" name="new_container_from" value="registry">
</form>
{% else %}
<div style="font-size:1.1em; background:white; display:inline-block; padding:2px 15px 2px 15px"><a href="?new_container_from=registry">New container from registry</a></div>
<div style="font-size:1.1em; background:whitesmoke; display:inline-block; padding:2px 15px 2px 15px">New container from Git repository</div>
<div style="font-size:1.1em; background:whitesmoke; display:inline-block; padding:2px 15px 2px 15px">New container from Git repository</div>
<hr style="margin-top:0;">
<h4>Basics</h4>
<h4>Basics</h4>
<form action="#" method="POST">
{% csrf_token %}
......@@ -196,7 +196,7 @@
<textarea name="container_description" rows="3" cols="22"></textarea>
</td>
</tr>
<tr>
<td><b>Repository URL</b></td><td>
<input type="text" name="repository_url" size="23" required />
......@@ -214,24 +214,23 @@
</table>
<table style="width:400px; border:0; background:#ffffff; margin-top:20px">
<tr><td align="center">
<input type="submit" value="Add">
</td></tr>
</table>
<input type="hidden" name="new_container_from" value="repository">
</form>
</form>
{% endif %}
<br/>
<br/>
<br/>
<br/>
......@@ -240,21 +239,22 @@
<br/>
<br/>
<br/>
<br/>
{% else %}
Ok, software container added. Go to <a href="/software">software</a>.
{% endif %}
{% endif %}
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>
</div>
</div>
</div>
......@@ -270,7 +270,7 @@ function toggle_visibility(id) {
x.style.display = "block";
y.style.display = "none"
z.style.display= "inline"
} else {
x.style.display = "none";
y.style.display= "inline"
......@@ -283,3 +283,4 @@ function toggle_visibility(id) {
......@@ -13,10 +13,10 @@
<div style="color:green">
{{data.success}}
</div>
{% else %}
{% else %}
<br/>
{% endif %}
{% endif %}
{% if request.user.is_authenticated %}
Logged in as <b>{{ request.user.username }}</b> | <a href="/logout">Logout</a>
{% else %}
......@@ -41,11 +41,12 @@
<li><a href="/api/v1/base/login/">/api/v1/base/login/</a></li>
<li><a href="/api/v1/base/logout/">/api/v1/base/logout/</a></li>
</ul>
</div>
</body>
</html>
{% if data.computing %}
{% if data.computing %}
<div style="float:left; width:500px; margin:10px; margin-bottom:20px">
<table class="dashboard" style="width:100%">
......@@ -23,7 +23,7 @@
<td><b>Type</b></td>
<td>{{ data.computing.type }}</td>
</tr>
<tr>
<td><b>Access mode</b></td>
<td>{{ data.computing.access_mode }}</td>
......@@ -73,7 +73,7 @@
</table>
</div>
{% else %}
......@@ -95,10 +95,10 @@
{% if not computing_configured %}
<div style="width:298px; float:left; margin:0; margin-bottom:5px; padding:2px; background: gary; color:black; text-align:center; border-bottom: #e0e0e0 solid 1px;">
<i class="fa fa-info-circle"></i> Not configured <!-- style="color:#337ab7" -->
</div>
</div>
{% endif %}
{% if not computing_configured %}
<!-- <div style="padding:10px; -webkit-filter: blur(5px); -moz-filter: blur(5px); -o-filter: blur(5px); -ms-filter: blur(5px); filter: blur(5px);"> -->
......@@ -107,12 +107,12 @@
<div style="padding:10px;">
{% endif %}
{% if computing.description %}
{% if computing.description %}
<div class="description-box" title="{{ computing.description }}">
{{ computing.description }}
</div>
{% else %}
<br/>
<br/>
{% endif %}
<div class="image-version-box">
<b>Type:</b> {{ computing.type }} <font style="font-size:0.9em">({{ computing.arch }})</font>
......@@ -133,7 +133,7 @@
<!-- ><b>Access:</b> {{ computing.access_mode }}<br/> --
<!-- <b>Owner:</b> {% if computing.user %}{{ computing.user }}{% else %}Platform{% endif %}<br/> -->
<!-- <b>Engines:</b> {{ computing.container_engines }} -->
<!-- {% if 'docker' in computing.container_engines %}<img src="/static/img/docker-logo.svg" style="height:18px; width:18px; margin-bottom:2px" />{% endif %}
{% if 'singularity' in computing.container_engines %}<img src="/static/img/singularity-logo.svg" style="height:18px; width:18px; margin-bottom:2px" />{% endif %}-->
{% if container %}
......@@ -152,15 +152,15 @@
{% endif %}
</div>
{% endif %}
</div>
</div>
</div>
{% endif %}
{% endif %}
\ No newline at end of file