summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--dashboard/src/api/migrations/0004_snapshotconfig_snapshotrelation.py42
-rw-r--r--dashboard/src/api/migrations/0005_snapshotconfig_delta.py18
-rw-r--r--dashboard/src/api/models.py103
-rw-r--r--dashboard/src/dashboard/views.py2
-rw-r--r--dashboard/src/notifier/manager.py4
-rw-r--r--dashboard/src/static/css/detail_view.css33
-rw-r--r--dashboard/src/templates/account/booking_list.html63
-rw-r--r--dashboard/src/templates/account/configuration_list.html33
-rw-r--r--dashboard/src/templates/account/details.html8
-rw-r--r--dashboard/src/templates/account/image_list.html38
-rw-r--r--dashboard/src/templates/account/resource_list.html31
-rw-r--r--dashboard/src/templates/base.html5
-rw-r--r--dashboard/src/templates/dashboard/landing.html71
-rw-r--r--dashboard/src/templates/notifier/email_fulfilled.txt6
-rw-r--r--dashboard/src/templates/snapshot_workflow/steps/meta.html19
-rw-r--r--dashboard/src/templates/snapshot_workflow/steps/select_host.html65
-rw-r--r--dashboard/src/workflow/forms.py2
-rw-r--r--dashboard/src/workflow/models.py10
-rw-r--r--dashboard/src/workflow/snapshot_workflow.py9
19 files changed, 402 insertions, 160 deletions
diff --git a/dashboard/src/api/migrations/0004_snapshotconfig_snapshotrelation.py b/dashboard/src/api/migrations/0004_snapshotconfig_snapshotrelation.py
new file mode 100644
index 0000000..62bc7af
--- /dev/null
+++ b/dashboard/src/api/migrations/0004_snapshotconfig_snapshotrelation.py
@@ -0,0 +1,42 @@
+# Generated by Django 2.1 on 2019-01-17 15:54
+
+import api.models
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('resource_inventory', '0004_auto_20181017_1532'),
+ ('api', '0003_auto_20190102_1956'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='SnapshotConfig',
+ fields=[
+ ('taskconfig_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='api.TaskConfig')),
+ ('image', models.IntegerField(null=True)),
+ ('dashboard_id', models.IntegerField()),
+ ('host', models.ForeignKey(null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='resource_inventory.Host')),
+ ],
+ bases=('api.taskconfig',),
+ ),
+ migrations.CreateModel(
+ name='SnapshotRelation',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('status', models.IntegerField(default=0)),
+ ('task_id', models.CharField(default=api.models.get_task_uuid, max_length=37)),
+ ('lab_token', models.CharField(default='null', max_length=50)),
+ ('message', models.TextField(default='')),
+ ('config', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='api.SnapshotConfig')),
+ ('job', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.Job')),
+ ('snapshot', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='resource_inventory.Image')),
+ ],
+ options={
+ 'abstract': False,
+ },
+ ),
+ ]
diff --git a/dashboard/src/api/migrations/0005_snapshotconfig_delta.py b/dashboard/src/api/migrations/0005_snapshotconfig_delta.py
new file mode 100644
index 0000000..559af90
--- /dev/null
+++ b/dashboard/src/api/migrations/0005_snapshotconfig_delta.py
@@ -0,0 +1,18 @@
+# Generated by Django 2.1 on 2019-01-17 16:07
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('api', '0004_snapshotconfig_snapshotrelation'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='snapshotconfig',
+ name='delta',
+ field=models.TextField(default='{}'),
+ ),
+ ]
diff --git a/dashboard/src/api/models.py b/dashboard/src/api/models.py
index a1fedfe..e4016aa 100644
--- a/dashboard/src/api/models.py
+++ b/dashboard/src/api/models.py
@@ -214,6 +214,10 @@ class Job(models.Model):
if 'network' not in d:
d['network'] = {}
d['network'][relation.task_id] = relation.config.to_dict()
+ for relation in SnapshotRelation.objects.filter(job=self):
+ if 'snapshot' not in d:
+ d['snapshot'] = {}
+ d['snapshot'][relation.task_id] = relation.config.to_dict()
j['payload'] = d
@@ -221,7 +225,13 @@ class Job(models.Model):
def get_tasklist(self, status="all"):
tasklist = []
- clist = [HostHardwareRelation, AccessRelation, HostNetworkRelation, SoftwareRelation]
+ clist = [
+ HostHardwareRelation,
+ AccessRelation,
+ HostNetworkRelation,
+ SoftwareRelation,
+ SnapshotRelation
+ ]
if status == "all":
for cls in clist:
tasklist += list(cls.objects.filter(job=self))
@@ -261,6 +271,10 @@ class Job(models.Model):
if 'network' not in d:
d['network'] = {}
d['network'][relation.task_id] = relation.config.get_delta()
+ for relation in SnapshotRelation.objects.filter(job=self).filter(status=status):
+ if 'snapshot' not in d:
+ d['snapshot'] = {}
+ d['snapshot'][relation.task_id] = relation.config.get_delta()
j['payload'] = d
return j
@@ -534,6 +548,61 @@ class NetworkConfig(TaskConfig):
self.delta = json.dumps(d)
+class SnapshotConfig(TaskConfig):
+
+ host = models.ForeignKey(Host, null=True, on_delete=models.DO_NOTHING)
+ image = models.IntegerField(null=True)
+ dashboard_id = models.IntegerField()
+ delta = models.TextField(default="{}")
+
+ def to_dict(self):
+ d = {}
+ if self.host:
+ d['host'] = self.host.labid
+ if self.image:
+ d['image'] = self.image
+ d['dashboard_id'] = self.dashboard_id
+ return d
+
+ def to_json(self):
+ return json.dumps(self.to_dict())
+
+ def get_delta(self):
+ if not self.delta:
+ self.delta = self.to_json()
+ self.save()
+ d = json.loads(self.delta)
+ return d
+
+ def clear_delta(self):
+ self.delta = json.dumps(self.to_dict())
+ self.save()
+
+ def set_host(self, host):
+ self.host = host
+ d = json.loads(self.delta)
+ d['host'] = host.labid
+ self.delta = json.dumps(d)
+
+ def set_image(self, image):
+ self.image = image
+ d = json.loads(self.delta)
+ d['image'] = self.image
+ self.delta = json.dumps(d)
+
+ def clear_image(self):
+ self.image = None
+ d = json.loads(self.delta)
+ d.pop("image", None)
+ self.delta = json.dumps(d)
+
+ def set_dashboard_id(self, dash):
+ self.dashboard_id = dash
+ d = json.loads(self.delta)
+ d['dashboard_id'] = self.dashboard_id
+ self.delta = json.dumps(d)
+
+
def get_task(task_id):
for taskclass in [AccessRelation, SoftwareRelation, HostHardwareRelation, HostNetworkRelation]:
try:
@@ -617,9 +686,41 @@ class HostNetworkRelation(TaskRelation):
return super(self.__class__, self).delete(*args, **kwargs)
+class SnapshotRelation(TaskRelation):
+ snapshot = models.ForeignKey(Image, on_delete=models.CASCADE)
+ config = models.OneToOneField(SnapshotConfig, on_delete=models.CASCADE)
+
+ def type_str(self):
+ return "Snapshot Task"
+
+ def get_delta(self):
+ return self.config.to_dict()
+
+ def delete(self, *args, **kwargs):
+ self.config.delete()
+ return super(self.__class__, self).delete(*args, **kwargs)
+
+
class JobFactory(object):
@classmethod
+ def makeSnapshotTask(cls, image, booking, host):
+ relation = SnapshotRelation()
+ job = Job.objects.get(booking=booking)
+ config = SnapshotConfig.objects.create(dashboard_id=image.id)
+
+ relation.job = job
+ relation.config = config
+ relation.config.save()
+ relation.config = relation.config
+ relation.snapshot = image
+ relation.save()
+
+ config.clear_delta()
+ config.set_host(host)
+ config.save()
+
+ @classmethod
def makeCompleteJob(cls, booking):
hosts = Host.objects.filter(bundle=booking.resource)
job = None
diff --git a/dashboard/src/dashboard/views.py b/dashboard/src/dashboard/views.py
index 36c3253..c4a6685 100644
--- a/dashboard/src/dashboard/views.py
+++ b/dashboard/src/dashboard/views.py
@@ -78,7 +78,7 @@ def landing_view(request):
manager_detected = True
if request.method == 'GET':
- return render(request, 'dashboard/landing.html', {'manager': manager_detected, 'title': "Welcome!"})
+ return render(request, 'dashboard/landing.html', {'manager': manager_detected, 'title': "Welcome to the Lab as a Service Dashboard"})
if request.method == 'POST':
try:
diff --git a/dashboard/src/notifier/manager.py b/dashboard/src/notifier/manager.py
index 3361074..f03c2cc 100644
--- a/dashboard/src/notifier/manager.py
+++ b/dashboard/src/notifier/manager.py
@@ -75,7 +75,7 @@ class NotificationHandler(object):
if (not hasattr(task, "user")) or task.user == user:
user_tasklist.append(
{
- "title": task.type_str + " Message: ",
+ "title": task.type_str() + " Message: ",
"content": task.message
}
)
@@ -94,7 +94,7 @@ class NotificationHandler(object):
"Your Booking is Ready",
message,
os.environ.get("DEFAULT_FROM_EMAIL", "opnfv@pharos-dashboard"),
- user.userprofile.email_addr,
+ [user.userprofile.email_addr],
fail_silently=False
)
diff --git a/dashboard/src/static/css/detail_view.css b/dashboard/src/static/css/detail_view.css
index 89d0867..69a2643 100644
--- a/dashboard/src/static/css/detail_view.css
+++ b/dashboard/src/static/css/detail_view.css
@@ -1,14 +1,25 @@
-.detail_card {
- border: 2px;
- border-color: black;
- border-radius: 5px;
- margin: 5px;
- padding: 5px;
- box-shadow: 0px 0px 7px 0px rgba(0,0,0,0.75);
+.card_container {
+ display: grid;
+ grid-template-columns: 1fr 1fr 1fr 1fr;
+ grid-gap: 25px 25px;
+ justify-items: stretch;
+}
+
+.card_container ul > li {
+ padding: 7px !important;
+ font-size: 16px;
}
-.detail_btn_group {
- margin: 3px;
- padding: 3px;
- padding-bottom: 5px;
+.detail_card {
+ border: 2px;
+ border-color: black;
+ border-radius: 5px;
+ margin: 5px;
+ padding-left: 25px;
+ padding-right: 25px;
+ padding-bottom: 15px;
+ box-shadow: 0px 0px 7px 0px rgba(0,0,0,0.75);
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
}
diff --git a/dashboard/src/templates/account/booking_list.html b/dashboard/src/templates/account/booking_list.html
index c7c5e00..9c6f3db 100644
--- a/dashboard/src/templates/account/booking_list.html
+++ b/dashboard/src/templates/account/booking_list.html
@@ -1,52 +1,41 @@
{% extends "base.html" %}
{% block content %}
-<script>
-function edit_booking(pk){
- var csrf = $('input[name="csrfmiddlewaretoken"]').val();
- $.ajax({
- type: "POST",
- url: "/",
- data: { "target": pk, "create": 0, "csrfmiddlewaretoken": csrf},
- beforeSend: function(request) {
- request.setRequestHeader("X-CSFRToken", csrf);
- }
- }).done(function(){
- window.location.replace("/wf/");
- }).fail(function(){})
-}
-</script>
<h2>Bookings I Own</h2>
+ <div class="card_container">
{% for booking in bookings %}
<div class="detail_card">
- <ul>
- <li>id: {{booking.id}}</li>
- <li>lab: {{booking.resource.template.lab.lab_user.username}}</li>
- <li>resource: {{booking.resource.template.name}}</li>
- <li>start: {{booking.start}}</li>
- <li>end: {{booking.end}}</li>
- <li>purpose: {{booking.purpose}}</li>
- </ul>
- <div class="detail_btn_group">
- <button style="display:none" class="btn btn-primary" onclick="edit_booking({{booking.id}});">Edit</button>
- <button class="btn btn-primary" onclick="location.href='/booking/detail/{{booking.id}}/';">Details</button>
+ <div>
+ <h3>Booking {{booking.id}}</h3>
+ <ul class="list-group">
+ <li class="list-group-item">id: {{booking.id}}</li>
+ <li class="list-group-item">lab: {{booking.resource.template.lab.lab_user.username}}</li>
+ <li class="list-group-item">resource: {{booking.resource.template.name}}</li>
+ <li class="list-group-item">start: {{booking.start}}</li>
+ <li class="list-group-item">end: {{booking.end}}</li>
+ <li class="list-group-item">purpose: {{booking.purpose}}</li>
+ </ul>
</div>
+ <a class="btn btn-primary" href="/booking/detail/{{booking.id}}/">Details</a>
</div>
{% endfor %}
+ </div>
<h2>Bookings I Collaborate On</h2>
+ <div class="card_container">
{% for booking in collab_bookings %}
<div class="detail_card">
- <ul>
- <li>id: {{booking.id}}</li>
- <li>lab: {{booking.lab}}</li>
- <li>resource: {{booking.resource_name}}</li>
- <li>start: {{booking.start}}</li>
- <li>end: {{booking.end}}</li>
- <li>purpose: {{booking.purpose}}</li>
- </ul>
- <div class="detail_btn_group">
- <button style="display: none" class="btn btn-primary" disabled=true onclick="edit_booking({{booking.id}});">Edit</button>
- <button class="btn btn-primary" onclick="location.href='/booking/detail/{{booking.id}}/';">Details</button>
+ <div>
+ <h3>Booking {{booking.id}}</h3>
+ <ul class="list-group">
+ <li class="list-group-item">id: {{booking.id}}</li>
+ <li class="list-group-item">lab: {{booking.lab}}</li>
+ <li class="list-group-item">resource: {{booking.resource_name}}</li>
+ <li class="list-group-item">start: {{booking.start}}</li>
+ <li class="list-group-item">end: {{booking.end}}</li>
+ <li class="list-group-item">purpose: {{booking.purpose}}</li>
+ </ul>
</div>
+ <a class="btn btn-primary" href="/booking/detail/{{booking.id}}/">Details</a>
</div>
{% endfor %}
+ </div>
{% endblock %}
diff --git a/dashboard/src/templates/account/configuration_list.html b/dashboard/src/templates/account/configuration_list.html
index 9dcec07..14d0472 100644
--- a/dashboard/src/templates/account/configuration_list.html
+++ b/dashboard/src/templates/account/configuration_list.html
@@ -1,31 +1,18 @@
{% extends "base.html" %}
{% block content %}
-<script>
-function edit_configuration(pk){
- var csrf = $('input[name="csrfmiddlewaretoken"]').val();
- $.ajax({
- type: "POST",
- url: "/",
- data: { "target": pk, "create": 2, "csrfmiddlewaretoken": csrf},
- beforeSend: function(request) {
- request.setRequestHeader("X-CSFRToken", csrf);
- }
- }).done(function(){
- window.location.replace("/wf/");
- }).fail(function(){});
-}
-</script>
+ <div class="card_container">
{% for config in configurations %}
<div class="detail_card">
- <ul>
- <li>id: {{config.id}}</li>
- <li>name: {{config.name}}</li>
- <li>description: {{config.description}}</li>
- <li>resource: {{config.bundle}}</li>
- </ul>
- <div class="detail_btn_group">
- <button style="display: none" class="btn btn-primary" onclick="edit_configuration({{config.id}});">Edit</button>
+ <div>
+ <h3>Configuration {{config.id}}</h3>
+ <ul class="list-group">
+ <li class="list-group-item">id: {{config.id}}</li>
+ <li class="list-group-item">name: {{config.name}}</li>
+ <li class="list-group-item">description: {{config.description}}</li>
+ <li class="list-group-item">resource: {{config.bundle}}</li>
+ </ul>
</div>
</div>
{% endfor %}
+ </div>
{% endblock %}
diff --git a/dashboard/src/templates/account/details.html b/dashboard/src/templates/account/details.html
index acf3eb1..3092ad0 100644
--- a/dashboard/src/templates/account/details.html
+++ b/dashboard/src/templates/account/details.html
@@ -2,8 +2,8 @@
{% load staticfiles %}
{% block content %}
<h1>Account Details</h1>
-<a class="btn btn-primary" onclick="location.href = '{% url 'account:my-resources' %}'">My Resources</a>
-<a class="btn btn-primary" onclick="location.href = '{% url 'account:my-bookings' %}'">My Bookings</a>
-<a class="btn btn-primary" onclick="location.href = '{% url 'account:my-configurations' %}'">My Configurations</a>
-<a class="btn btn-primary" onclick="location.href = '{% url 'account:my-images' %}'">My Snapshots</a>
+<a class="btn btn-primary" href="{% url 'account:my-resources' %}">My Resources</a>
+<a class="btn btn-primary" href="{% url 'account:my-bookings' %}">My Bookings</a>
+<a class="btn btn-primary" href="{% url 'account:my-configurations' %}">My Configurations</a>
+<a class="btn btn-primary" href="{% url 'account:my-images' %}">My Snapshots</a>
{% endblock content %}
diff --git a/dashboard/src/templates/account/image_list.html b/dashboard/src/templates/account/image_list.html
index 72ea1f5..7566a9c 100644
--- a/dashboard/src/templates/account/image_list.html
+++ b/dashboard/src/templates/account/image_list.html
@@ -1,27 +1,37 @@
{% extends "base.html" %}
{% block content %}
<h2>Images I Own</h2>
+<div class="card_container">
{% for image in images %}
<div class="detail_card">
- <ul>
- <li>id: {{image.id}}</li>
- <li>lab: {{image.from_lab.name}}</li>
- <li>name: {{image.name}}</li>
- <li>description: {{image.description}}</li>
- <li>host profile: {{image.host_type.name}}</li>
- </ul>
+ <div>
+ <h3>Image {{image.id}}</h3>
+ <ul class="list-group">
+ <li class="list-group-item">id: {{image.id}}</li>
+ <li class="list-group-item">lab: {{image.from_lab.name}}</li>
+ <li class="list-group-item">name: {{image.name}}</li>
+ <li class="list-group-item">description: {{image.description}}</li>
+ <li class="list-group-item">host profile: {{image.host_type.name}}</li>
+ </ul>
+ </div>
</div>
{% endfor %}
+</div>
<h2>Public Images</h2>
+<div class="card_container">
{% for image in public_images %}
<div class="detail_card">
- <ul>
- <li>id: {{image.id}}</li>
- <li>lab: {{image.from_lab.name}}</li>
- <li>name: {{image.name}}</li>
- <li>description: {{image.description}}</li>
- <li>host profile: {{image.host_type.name}}</li>
- </ul>
+ <div>
+ <h3>Image {{image.id}}</h3>
+ <ul class="list-group">
+ <li class="list-group-item">id: {{image.id}}</li>
+ <li class="list-group-item">lab: {{image.from_lab.name}}</li>
+ <li class="list-group-item">name: {{image.name}}</li>
+ <li class="list-group-item">description: {{image.description}}</li>
+ <li class="list-group-item">host profile: {{image.host_type.name}}</li>
+ </ul>
+ </div>
</div>
{% endfor %}
+</div>
{% endblock %}
diff --git a/dashboard/src/templates/account/resource_list.html b/dashboard/src/templates/account/resource_list.html
index 7e4194b..cdacdd6 100644
--- a/dashboard/src/templates/account/resource_list.html
+++ b/dashboard/src/templates/account/resource_list.html
@@ -1,30 +1,17 @@
{% extends "base.html" %}
{% block content %}
-<script>
-function edit_resource(pk){
- var csrf = $('input[name="csrfmiddlewaretoken"]').val();
- $.ajax({
- type: "POST",
- url: "/",
- data: { "target": pk, "create": 1, "csrfmiddlewaretoken": csrf},
- beforeSend: function(request) {
- request.setRequestHeader("X-CSFRToken", csrf);
- }
- }).done(function(){
- window.location.replace("/wf/");
- }).fail(function(){});
-}
-</script>
+ <div class="card_container">
{% for resource in resources %}
<div class="detail_card">
- <ul>
- <li>id: {{resource.id}}</li>
- <li>name: {{resource.name}}</li>
- <li>description: {{resource.description}}</li>
- </ul>
- <div class="detail_btn_group">
- <button style="display: none" class="btn btn-primary" onclick="edit_resource({{resource.id}});">Edit</button>
+ <div>
+ <h3>Resource {{resource.id}}</h3>
+ <ul class="list-group">
+ <li class="list-group-item">id: {{resource.id}}</li>
+ <li class="list-group-item">name: {{resource.name}}</li>
+ <li class="list-group-item">description: {{resource.description}}</li>
+ </ul>
</div>
</div>
{% endfor %}
+ </div>
{% endblock %}
diff --git a/dashboard/src/templates/base.html b/dashboard/src/templates/base.html
index 067c3e6..f7fa7cd 100644
--- a/dashboard/src/templates/base.html
+++ b/dashboard/src/templates/base.html
@@ -129,8 +129,9 @@
</a>
{% csrf_token %}
<div id="create_drop" class="create_drop" style="display:none">
- <button class="btn drop_btn" onclick="cwf(0)">Create a Booking</button>
- <button class="btn drop_btn" onclick="cwf(1)">Create a Pod</button>
+ <button class="btn drop_btn" onclick="location.href='/booking/quick/'">Express Booking</a>
+ <button class="btn drop_btn" onclick="cwf(0)">Book a Pod</button>
+ <button class="btn drop_btn" onclick="cwf(1)">Design a Pod</button>
<button class="btn drop_btn" onclick="cwf(2)">Configure a Pod</button>
<button class="btn drop_btn" onclick="cwf(3)">Create a Snapshot</button>
</div>
diff --git a/dashboard/src/templates/dashboard/landing.html b/dashboard/src/templates/dashboard/landing.html
index 6bbb25b..fb75d5f 100644
--- a/dashboard/src/templates/dashboard/landing.html
+++ b/dashboard/src/templates/dashboard/landing.html
@@ -2,16 +2,14 @@
{% load staticfiles %}
{% block content %}
- <div class="">
+ <div class="" style="text-align: center;">
{% if not request.user.is_anonymous %}
{% if not request.user.userprofile.ssh_public_key %}
- <h4 style="text-align: center; background: #AA0000; color: #FFFFFF; padding: 10px; border-radius: 3px; height: 40px;">
+ <h4 style="display: inline; text-align: center; border: 3px solid red; padding: 10px; border-radius: 10000px; height: 40px;">
Warning: you need to upload an ssh key under <a href="/accounts/settings">account settings</a> if you wish to log into the servers you book
</h4>
{% endif %}
- <p style="text-align:center;">Welcome to the Pharos Dashboard! For more info on LaaS, <a href="https://wiki.opnfv.org/display/INF/Lab+as+a+Service+2.0">check here</a>. To get started, select one of the options below:</p>
{% else %}
- <p style="text-align:center;">Welcome to the Pharos Dashboard! For more info on LaaS, <a href="https://wiki.opnfv.org/display/INF/Lab+as+a+Service+2.0">check here</a>. To get started, please log in with your <a href="/accounts/login">Linux Foundation Jira account</a></p>
{% endif %}
</div>
{% csrf_token %}
@@ -36,20 +34,63 @@
display: grid;
grid-template-columns: 33% 34% 33%;
}
+
+ .landing_container {
+ display: grid;
+ grid-template-columns: 1fr 30px 1fr;
+ }
+ .grid_panel {
+ padding: 30px;
+ }
+ .btn-primary {
+ margin: 10px;
+ }
+ h2 {
+ border-bottom: 1px solid #cccccc;
+ }
+ h1 {
+ }
</style>
-{% if not request.user.is_anonymous %}
-<div class='wf_create_div'>
-<a class="wf_create btn btn-primary" style="color: #FFF;" href="/booking/quick/">Create a Quick Booking</a>
-<button class="wf_create btn btn-primary" onclick="cwf(0)">Create a Booking</button>
-<button class="wf_create btn btn-primary" onclick="cwf(1)">Create a Pod</button>
-<button class="wf_create btn btn-primary" onclick="cwf(2)">Configure a Pod</button>
-<button class="wf_create btn btn-primary" onclick="cwf(3)">Create a Snapshot</button>
-{% if manager == True %}
-<button class="wf_continue btn btn-primary" onclick="continue_wf()">Finish Unfinished Business</button>
-{% endif %}
-{% endif %}
+<div class="landing_container">
+ <div class="info_panel grid_panel">
+ <h2>About Us:</h2>
+ <p>The Lab as a Service (LaaS) project aims to help in the development and testing of LFN projects such as OPNFV by hosting hardware and providing access to the community. Currently, the only participating lab is the University of New Hampshire Interoperability Lab (UNH-IOL).</p>
+ <p>To get started, you can request access to a server at the right. PTL's have the ability to design and book a whole block of servers with customized layer2 networks (e.g. a Pharos Pod). Read more here: <a href="https://wiki.opnfv.org/display/INF/Lab+as+a+Service+2.0">LaaS Wiki</a></p>
+
+ {% if not request.user.is_anonymous %}
+ <h2 style="margin-top: 50px;">Returning Users:</h2>
+ <p>If you're a returning user, some of the following options may be of interest:</p>
+ <button class="wf_create btn btn-primary" onclick="cwf(3)">Snapshot a Host</button>
+ <a class="wf_create btn btn-primary" href="{% url 'account:my-bookings' %}">My Bookings</a>
+ {% if manager == True %}
+ <button class="wf_continue btn btn-primary" onclick="continue_wf()">Continue Unfinished Workflow</button>
+ {% endif %}
+ {% endif %}
+ </div>
+ <div class="">
+ </div>
+ <div class="actions_panel grid_panel">
+ <h2>Get Started:</h2>
+ {% if request.user.is_anonymous %}
+ <h4 style="text-align:center;">To get started, please log in with your <a href="/accounts/login">Linux Foundation Jira account</a></h4>
+ {% else %}
+ <p>To get started, book a server below:</p>
+ <a class="wf_create btn btn-primary" style="display: flex; flex-direction: column; justify-content: center; margin: 20px; height: 100pt; vertical-align: middle; text-align: center; color: #FFF;" href="/booking/quick/"><p style="font-size: xx-large">Book a Server</p></a>
+ <p>PTLs can use our advanced options to book multi-node pods. If you are a PTL, you may use the options below:</p>
+ <div class='wf_create_div'>
+
+ <button class="wf_create btn btn-primary" onclick="cwf(0)">Book a Pod</button>
+ <button class="wf_create btn btn-primary" onclick="cwf(1)">Design a Pod</button>
+ <button class="wf_create btn btn-primary" onclick="cwf(2)">Configure a Pod</button>
+ {% endif %}
+ </div>
+ </div>
</div>
+
+
+
+
<script type="text/javascript">
function cwf(type)
{
diff --git a/dashboard/src/templates/notifier/email_fulfilled.txt b/dashboard/src/templates/notifier/email_fulfilled.txt
index d473961..65593db 100644
--- a/dashboard/src/templates/notifier/email_fulfilled.txt
+++ b/dashboard/src/templates/notifier/email_fulfilled.txt
@@ -3,9 +3,9 @@
The booking you requested of the OPNFV Lab as a Service has finished deploying and is ready for you to use.
The lab that fulfilled your booking request has sent you the following messages:
- {% for message in messages %}
- {% message.title %}
- {% message.content %}
+ {% for email_message in messages %}
+ {{ email_message.title }}
+ {{ email_message.content }}
--------------------
{% endfor %}
diff --git a/dashboard/src/templates/snapshot_workflow/steps/meta.html b/dashboard/src/templates/snapshot_workflow/steps/meta.html
index 2e767cc..cc49691 100644
--- a/dashboard/src/templates/snapshot_workflow/steps/meta.html
+++ b/dashboard/src/templates/snapshot_workflow/steps/meta.html
@@ -4,16 +4,21 @@
{% load bootstrap3 %}
{% block content %}
-
-
<style>
+.meta_container {
+ padding: 50px;
+}
</style>
-
{% bootstrap_form_errors form type='non_fields' %}
-<form id="meta_form" action="/wf/workflow/" method="POST" class="form">
-{% csrf_token %}
-{{form}}
-</form>
+<div class="meta_container">
+ <form id="meta_form" action="/wf/workflow/" method="POST" class="form">
+ {% csrf_token %}
+ <div class="form-group">
+ {% bootstrap_field form.name %}
+ {% bootstrap_field form.description %}
+ </div>
+ </form>
+</div>
{% endblock content %}
{% block onleave %}
diff --git a/dashboard/src/templates/snapshot_workflow/steps/select_host.html b/dashboard/src/templates/snapshot_workflow/steps/select_host.html
index 16dd5d4..27a9238 100644
--- a/dashboard/src/templates/snapshot_workflow/steps/select_host.html
+++ b/dashboard/src/templates/snapshot_workflow/steps/select_host.html
@@ -5,43 +5,73 @@
{% block content %}
-
<style>
.booking {
- border-style: solid;
+ border-style: none;
border-color: black;
- border-width: 2px;
- display: inline-block;
- padding: 3px;
+ border: 2px;
+ border-radius: 5px;
+ margin: 20px;
+ padding-left: 25px;
+ padding-right: 25px;
+ padding-bottom: 25px;
+ box-shadow: 0px 0px 7px 0px rgba(0,0,0,0.75);
+ transition-property: box-shadow;
+ transition-duration: 0.1s;
+ float: left;
+ }
+ .booking:hover {
+ box-shadow: 0px 0px 10px 0px rgba(0,0,0,0.75);
+ transition-property: box-shadow;
+ transition-duration: 0.1s;
}
.host {
+ cursor: pointer;
border-style: solid;
border-color: black;
border-width: 1px;
- margin: 2px;
+ border-radius: 5px;
+ margin: 5px;
+ padding: 5px;
+ text-align: center;
+ box-shadow: 0px 0px 2px 0px rgba(0,0,0,0.75);
+ transition-property: box-shadow;
+ transition-duration: 0.1s;
+ }
+ .host:hover {
+ box-shadow: 0px 0px 4px 0px rgba(0,0,0,0.75);
+ transition-property: box-shadow;
+ transition-duration: 0.1s;
+ background-color: rgba(144,238,144,0.3);
+ }
+ .selected {
+ background-color: lightgreen !important;
+ }
+ .booking_container {
+ overflow: auto;
+ padding: 30px;
}
</style>
-
{% bootstrap_form_errors form type='non_fields' %}
<form id="host_select_form" action="/wf/workflow/" method="POST" class="form">
{% csrf_token %}
<input type="hidden" id="hidden_json_input", name="host"/>
</form>
-<div id="host_select_container">
+<div id="host_select_container" class="booking_container">
</div>
<script>
var selected_host = null;
-var initial = {{chosen|default:'null'}};
+var initial = {{chosen|safe|default:'null'}};
function select(booking_id, host_name){
var input = document.getElementById("hidden_json_input");
input.value = JSON.stringify({"booking": booking_id, "name": host_name});
// clear out and highlist host
if(selected_host){
- selected_host.style['background-color'] = "white";
+ selected_host.classList.remove("selected");
}
selected_host = document.getElementById("booking_" + booking_id + "_host_" + host_name);
- selected_host.style['background-color'] = "lightgrey";
+ selected_host.classList.add("selected");
}
function draw_bookings(){
@@ -53,17 +83,20 @@ function draw_bookings(){
var heading = document.createElement("H3");
heading.appendChild(document.createTextNode("Booking " + booking_id));
booking.appendChild(heading);
- var desc = "start: " + booking_hosts[booking_id].start +
- " end: " + booking_hosts[booking_id].end +
- " purpose: " + booking_hosts[booking_id].purpose;
- booking.appendChild(document.createTextNode(desc));
+ booking.appendChild(document.createTextNode("start: " + booking_hosts[booking_id].start));
+ booking.appendChild(document.createElement("BR"));
+ booking.appendChild(document.createTextNode("end: " + booking_hosts[booking_id].end));
+ booking.appendChild(document.createElement("BR"));
+ booking.appendChild(document.createTextNode("purpose: " + booking_hosts[booking_id].purpose));
+ booking.appendChild(document.createElement("BR"));
+ booking.appendChild(document.createTextNode("hosts:"));
booking.id = "booking_" + booking_id;
booking.className = "booking";
var hosts = booking_hosts[booking_id].hosts;
for(var i=0; i<hosts.length; i++){
var host = document.createElement("DIV");
host.id = "booking_" + booking_id + "_host_" + hosts[i].name;
- host.className = "host";
+ host.classList.add("host");
host.appendChild(document.createTextNode(hosts[i].name));
var hostname = hosts[i].name;
host.booking = booking_id;
diff --git a/dashboard/src/workflow/forms.py b/dashboard/src/workflow/forms.py
index b8c7f66..726e7dd 100644
--- a/dashboard/src/workflow/forms.py
+++ b/dashboard/src/workflow/forms.py
@@ -461,7 +461,7 @@ class SnapshotHostSelectForm(forms.Form):
class SnapshotMetaForm(forms.Form):
name = forms.CharField()
- description = forms.CharField()
+ description = forms.CharField(widget=forms.Textarea)
class ConfirmationForm(forms.Form):
diff --git a/dashboard/src/workflow/models.py b/dashboard/src/workflow/models.py
index 495ce07..4e79546 100644
--- a/dashboard/src/workflow/models.py
+++ b/dashboard/src/workflow/models.py
@@ -11,6 +11,7 @@
from django.shortcuts import render
from django.contrib import messages
from django.http import HttpResponse
+from django.utils import timezone
import yaml
import requests
@@ -385,6 +386,8 @@ class Repository():
if not booking_id:
return "SNAP, No booking ID provided"
booking = Booking.objects.get(pk=booking_id)
+ if booking.start > timezone.now() or booking.end < timezone.now():
+ return "Booking is not active"
name = self.el.get(self.SNAPSHOT_NAME)
if not name:
return "SNAP, no name provided"
@@ -400,6 +403,13 @@ class Repository():
image.owner = owner
image.host_type = host.profile
image.save()
+ try:
+ current_image = host.config.image
+ image.os = current_image.os
+ image.save()
+ except Exception:
+ pass
+ JobFactory.makeSnapshotTask(image, booking, host)
def make_generic_resource_bundle(self):
owner = self.el[self.SESSION_USER]
diff --git a/dashboard/src/workflow/snapshot_workflow.py b/dashboard/src/workflow/snapshot_workflow.py
index 4ddc397..0d53ed4 100644
--- a/dashboard/src/workflow/snapshot_workflow.py
+++ b/dashboard/src/workflow/snapshot_workflow.py
@@ -87,7 +87,14 @@ class Image_Meta_Step(WorkflowStep):
def get_context(self):
context = super(Image_Meta_Step, self).get_context()
- context['form'] = SnapshotMetaForm()
+ name = self.repo_get(self.repo.SNAPSHOT_NAME, False)
+ desc = self.repo_get(self.repo.SNAPSHOT_DESC, False)
+ form = None
+ if name and desc:
+ form = SnapshotMetaForm(initial={"name": name, "description": desc})
+ else:
+ form = SnapshotMetaForm()
+ context['form'] = form
return context
def post_render(self, request):