aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/account/views.py4
-rw-r--r--src/api/migrations/0004_snapshotconfig_snapshotrelation.py42
-rw-r--r--src/api/migrations/0005_snapshotconfig_delta.py18
-rw-r--r--src/api/models.py103
-rw-r--r--src/booking/forms.py10
-rw-r--r--src/booking/quick_deployer.py1
-rw-r--r--src/booking/urls.py7
-rw-r--r--src/booking/views.py58
-rw-r--r--src/dashboard/views.py2
-rw-r--r--src/notifier/manager.py4
-rw-r--r--src/static/css/detail_view.css33
-rw-r--r--src/templates/account/booking_list.html63
-rw-r--r--src/templates/account/configuration_list.html33
-rw-r--r--src/templates/account/details.html8
-rw-r--r--src/templates/account/image_list.html38
-rw-r--r--src/templates/account/resource_list.html31
-rw-r--r--src/templates/base.html5
-rw-r--r--src/templates/booking/booking_detail.html146
-rw-r--r--src/templates/dashboard/landing.html71
-rw-r--r--src/templates/notifier/email_fulfilled.txt6
-rw-r--r--src/templates/snapshot_workflow/steps/meta.html19
-rw-r--r--src/templates/snapshot_workflow/steps/select_host.html65
-rw-r--r--src/workflow/forms.py2
-rw-r--r--src/workflow/models.py10
-rw-r--r--src/workflow/snapshot_workflow.py9
25 files changed, 578 insertions, 210 deletions
diff --git a/src/account/views.py b/src/account/views.py
index 09c5266..e880208 100644
--- a/src/account/views.py
+++ b/src/account/views.py
@@ -181,8 +181,8 @@ def account_booking_view(request):
if not request.user.is_authenticated:
return render(request, "dashboard/login.html", {'title': 'Authentication Required'})
template = "account/booking_list.html"
- bookings = list(Booking.objects.filter(owner=request.user))
- collab_bookings = list(request.user.collaborators.all())
+ bookings = list(Booking.objects.filter(owner=request.user).order_by("-start"))
+ collab_bookings = list(request.user.collaborators.all().order_by("-start"))
context = {"title": "My Bookings", "bookings": bookings, "collab_bookings": collab_bookings}
return render(request, template, context=context)
diff --git a/src/api/migrations/0004_snapshotconfig_snapshotrelation.py b/src/api/migrations/0004_snapshotconfig_snapshotrelation.py
new file mode 100644
index 0000000..62bc7af
--- /dev/null
+++ b/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/src/api/migrations/0005_snapshotconfig_delta.py b/src/api/migrations/0005_snapshotconfig_delta.py
new file mode 100644
index 0000000..559af90
--- /dev/null
+++ b/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/src/api/models.py b/src/api/models.py
index a1fedfe..e4016aa 100644
--- a/src/api/models.py
+++ b/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/src/booking/forms.py b/src/booking/forms.py
index cb76383..7ba5af0 100644
--- a/src/booking/forms.py
+++ b/src/booking/forms.py
@@ -1,5 +1,5 @@
##############################################################################
-# Copyright (c) 2018 Sawyer Bergeron, Parker Berberian, and others.
+# Copyright (c) 2018 Parker Berberian, Sawyer Bergeron, and others.
#
# All rights reserved. This program and the accompanying materials
# are made available under the terms of the Apache License, Version 2.0
@@ -28,7 +28,7 @@ class QuickBookingForm(forms.Form):
installer = forms.ModelChoiceField(queryset=Installer.objects.all(), required=False)
scenario = forms.ModelChoiceField(queryset=Scenario.objects.all(), required=False)
- def __init__(self, data=None, *args, user=None, **kwargs):
+ def __init__(self, data=None, user=None, *args, **kwargs):
chosen_users = []
if "default_user" in kwargs:
default_user = kwargs.pop("default_user")
@@ -104,3 +104,9 @@ class QuickBookingForm(forms.Form):
'edit': False
}
return attrs
+
+
+class HostReImageForm(forms.Form):
+
+ image_id = forms.IntegerField()
+ host_id = forms.IntegerField()
diff --git a/src/booking/quick_deployer.py b/src/booking/quick_deployer.py
index 9bc8c66..c431017 100644
--- a/src/booking/quick_deployer.py
+++ b/src/booking/quick_deployer.py
@@ -229,6 +229,7 @@ def create_from_form(form, request):
booking.end = timezone.now() + timedelta(days=int(length))
booking.resource = resource_bundle
booking.pdf = ResourceManager().makePDF(booking.resource)
+ booking.config_bundle = cbundle
booking.save()
print("users field:")
print(users_field)
diff --git a/src/booking/urls.py b/src/booking/urls.py
index c6504e0..310aaa7 100644
--- a/src/booking/urls.py
+++ b/src/booking/urls.py
@@ -33,7 +33,8 @@ from booking.views import (
BookingListView,
booking_stats_view,
booking_stats_json,
- quick_create
+ quick_create,
+ booking_modify_image
)
app_name = "booking"
@@ -42,12 +43,10 @@ urlpatterns = [
url(r'^detail/(?P<booking_id>[0-9]+)/$', booking_detail_view, name='detail'),
url(r'^(?P<booking_id>[0-9]+)/$', booking_detail_view, name='booking_detail'),
-
url(r'^delete/$', BookingDeleteView.as_view(), name='delete_prefix'),
url(r'^delete/(?P<booking_id>[0-9]+)/$', BookingDeleteView.as_view(), name='delete'),
-
url(r'^delete/(?P<booking_id>[0-9]+)/confirm/$', bookingDelete, name='delete_booking'),
-
+ url(r'^modify/(?P<booking_id>[0-9]+)/image/$', booking_modify_image, name='modify_booking_image'),
url(r'^list/$', BookingListView.as_view(), name='list'),
url(r'^stats/$', booking_stats_view, name='stats'),
url(r'^stats/json$', booking_stats_json, name='stats_json'),
diff --git a/src/booking/views.py b/src/booking/views.py
index bc1d2c9..3be9c7b 100644
--- a/src/booking/views.py
+++ b/src/booking/views.py
@@ -10,17 +10,20 @@
from django.contrib import messages
from django.shortcuts import get_object_or_404
-from django.http import JsonResponse
+from django.http import JsonResponse, HttpResponse
from django.utils import timezone
from django.views import View
from django.views.generic import TemplateView
from django.shortcuts import redirect, render
+from django.db.models import Q
-from account.models import Lab
+from resource_inventory.models import ResourceBundle, HostProfile, Image, Host
from resource_inventory.resource_manager import ResourceManager
-from resource_inventory.models import ResourceBundle
+from account.models import Lab
from booking.models import Booking
from booking.stats import StatisticsManager
+from booking.forms import HostReImageForm
+from api.models import HostHardwareRelation, JobStatus
from workflow.views import login
from booking.forms import QuickBookingForm
from booking.quick_deployer import create_from_form, drop_filter
@@ -125,6 +128,19 @@ class ResourceBookingsJSON(View):
return JsonResponse({'bookings': list(bookings)})
+def build_image_mapping(lab, user):
+ mapping = {}
+ for profile in HostProfile.objects.filter(labs=lab):
+ images = Image.objects.filter(
+ from_lab=lab,
+ host_type=profile
+ ).filter(
+ Q(public=True) | Q(owner=user)
+ )
+ mapping[profile.name] = [{"name": image.name, "value": image.id} for image in images]
+ return mapping
+
+
def booking_detail_view(request, booking_id):
user = None
if request.user.is_authenticated:
@@ -138,15 +154,39 @@ def booking_detail_view(request, booking_id):
if user not in allowed_users:
return render(request, "dashboard/login.html", {'title': 'This page is private'})
+ context = {
+ 'title': 'Booking Details',
+ 'booking': booking,
+ 'pdf': booking.pdf,
+ 'user_id': user.id,
+ 'image_mapping': build_image_mapping(booking.lab, user)
+ }
+
return render(
request,
"booking/booking_detail.html",
- {
- 'title': 'Booking Details',
- 'booking': booking,
- 'pdf': booking.pdf,
- 'user_id': user.id
- })
+ context
+ )
+
+
+def booking_modify_image(request, booking_id):
+ form = HostReImageForm(request.POST)
+ if form.is_valid():
+ booking = Booking.objects.get(id=booking_id)
+ if request.user != booking.owner:
+ return HttpResponse("unauthorized")
+ if timezone.now() > booking.end:
+ return HttpResponse("unauthorized")
+ new_image = Image.objects.get(id=form.cleaned_data['image_id'])
+ host = Host.objects.get(id=form.cleaned_data['host_id'])
+ relation = HostHardwareRelation.objects.get(host=host, job__booking=booking)
+ config = relation.config
+ config.set_image(new_image.lab_id)
+ config.save()
+ relation.status = JobStatus.NEW
+ relation.save()
+ return HttpResponse(new_image.name)
+ return HttpResponse("error")
def booking_stats_view(request):
diff --git a/src/dashboard/views.py b/src/dashboard/views.py
index 36c3253..c4a6685 100644
--- a/src/dashboard/views.py
+++ b/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/src/notifier/manager.py b/src/notifier/manager.py
index 3361074..f03c2cc 100644
--- a/src/notifier/manager.py
+++ b/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/src/static/css/detail_view.css b/src/static/css/detail_view.css
index 89d0867..69a2643 100644
--- a/src/static/css/detail_view.css
+++ b/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/src/templates/account/booking_list.html b/src/templates/account/booking_list.html
index c7c5e00..9c6f3db 100644
--- a/src/templates/account/booking_list.html
+++ b/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/src/templates/account/configuration_list.html b/src/templates/account/configuration_list.html
index 9dcec07..14d0472 100644
--- a/src/templates/account/configuration_list.html
+++ b/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/src/templates/account/details.html b/src/templates/account/details.html
index acf3eb1..3092ad0 100644
--- a/src/templates/account/details.html
+++ b/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/src/templates/account/image_list.html b/src/templates/account/image_list.html
index 72ea1f5..7566a9c 100644
--- a/src/templates/account/image_list.html
+++ b/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/src/templates/account/resource_list.html b/src/templates/account/resource_list.html
index 7e4194b..cdacdd6 100644
--- a/src/templates/account/resource_list.html
+++ b/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/src/templates/base.html b/src/templates/base.html
index 067c3e6..f7fa7cd 100644
--- a/src/templates/base.html
+++ b/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/src/templates/booking/booking_detail.html b/src/templates/booking/booking_detail.html
index d78aa85..44fca98 100644
--- a/src/templates/booking/booking_detail.html
+++ b/src/templates/booking/booking_detail.html
@@ -1,20 +1,29 @@
{% extends "base.html" %}
{% load staticfiles %}
+{% load bootstrap3 %}
{% block extrahead %}
{{block.super}}
<script src="https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js?lang=yaml"></script>
-<script src="https://unpkg.com/masonry-layout@4/dist/masonry.pkgd.min.js"></script>
{% endblock %}
{% block content %}
+
+<style>
+#modal_warning {
+ transition: max-height 0.5s ease-out;
+ overflow: hidden;
+}
+
+</style>
+
<div class="container-fluid">
<div class="row">
- <div class="col-lg-6">
+ <div class="col-lg-4">
<div class="panel panel-default">
<div class="panel-heading clearfix">
<h4 style="display: inline;">Overview</h4>
- <a data-toggle="collapse" data-target="#panel_overview" class="btn pull-right" style="line-height: 1;" >Expand</a>
+ <a data-toggle="collapse" data-target="#panel_overview" class="btn pull-right" style="line-height: 1;">Expand</a>
</div>
<div class="panel-body" id="panel_overview">
<table class="table">
@@ -50,9 +59,7 @@
</div>
</div>
<div class="row">
-
- <div class="col-lg-6">
-
+ <div class="col-lg-12">
<div class="panel panel-default">
<div class="panel-heading clearfix">
<h4 style="display: inline;">Pod</h4>
@@ -79,7 +86,15 @@
</tr>
<tr>
<td>Image:</td>
- <td>{{host.config.image}}</td>
+ <td id="host_image_{{host.id}}">
+ {{host.config.image}}
+ <button
+ style="margin-left:10px;"
+ class="btn btn-primary"
+ data-toggle="modal"
+ data-target="#imageModal"
+ onclick="set_image_dropdown('{{host.profile.name}}', {{host.id}});"
+ >Change/Reset</button></td>
</tr>
<tr>
<td>RAM:</td>
@@ -152,10 +167,7 @@
</table>
</td>
</tr>
-
-
</table>
-
</td>
{% endfor %}
</tr>
@@ -163,31 +175,15 @@
</div>
</div>
</div>
- <div class="col-lg-6">
-
- <div class="panel panel-default">
- <div class="panel-heading clearfix">
- <h4 style="display: inline;">PDF</h4>
- <a data-toggle="collapse" data-target="#pdf_panel" class="btn pull-right" style="line-height: 1;" >Expand</a>
- </div>
-
- <div class="panel-body" id="pdf_panel" style="padding: 0px;">
- <pre class="prettyprint lang-yaml" style="margin: 0px; padding: 0px; border: none;">
-{{pdf}}
- </pre>
- </div>
- </div>
- </div>
</div>
</div>
- <div class="col-lg-6">
+ <div class="col-lg-8">
<div class="panel panel-default">
<div class="panel-heading clearfix">
<h4 style="display: inline;">Deployment Progress</h4>
<p style="display: inline; margin-left: 10px;"> These are the different tasks that have to be completed before your deployment is ready</p>
<a data-toggle="collapse" data-target="#panel_tasks" class="btn pull-right" style="line-height: 1;" >Expand</a>
</div>
-
<div class="panel-body" id="panel_tasks">
<table class="table">
<style>
@@ -215,7 +211,6 @@
border-radius: 50%;
animation: fadeInOut 1s infinite alternate;
-
}
@keyframes fadeInOut {
from { opacity: 0;}
@@ -244,9 +239,7 @@
{% else %}
<div class="done"></div>
{% endif %}
- </td>
-
-
+ </td>
<td>
{% if task.status < 100 %}
PENDING
@@ -257,7 +250,6 @@
{% endif %}
</td>
<td>
-
{% if task.message %}
{% if task.type_str == "Access Task" and user_id != task.config.user.id %}
Message from Lab: <pre>--secret--</pre>
@@ -270,16 +262,104 @@
</td>
<td>
{{ task.type_str }}
-
</td>
</tr>
{% endfor %}
</table>
</div>
</div>
+ <div class="row">
+ <div class="col-lg-8">
+ <div class="panel panel-default">
+ <div class="panel-heading clearfix">
+ <h4 style="display: inline;">PDF</h4>
+ <a data-toggle="collapse" data-target="#pdf_panel" class="btn pull-right" style="line-height: 1;" >Expand</a>
+ </div>
+ <div class="panel-body" id="pdf_panel" style="padding: 0px;">
+ <pre class="prettyprint lang-yaml" style="margin: 0px; padding: 15px; border: none;">
+{{pdf}}
+ </pre>
+ </div>
+ </div>
+ </div>
+ </div>
</div>
</div>
</div>
+<div class="modal fade" id="imageModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
+ <div class="modal-dialog" style="width: 450px;" role="document">
+ <div class="modal-content">
+ <div class="modal-header">
+ <h4 class="modal-title" id="exampleModalLabel" style="display: inline; float: left;">Host Image</h4>
+ <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+ <span aria-hidden="true">&times;</span>
+ </button>
+ </div>
+ <div class="modal-body">
+ <form id="image_host_form">
+ {% csrf_token %}
+ <select class="form-control" style="width: 80%; margin-left: 10%" id="image_select" name="image_id">
+ </select>
+ <input id="host_id_input" type="hidden" name="host_id">
+ </input>
+ </form>
+ </div>
+ <div class="modal-footer">
+ <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
+ <button type="button" class="btn btn-primary" onclick="document.getElementById('modal_warning').style['max-height'] = '500px';">Reset Host</button>
+ </div>
+ <div id="modal_warning" class="modal-footer" style="max-height:0px;" >
+ <div style="text-align:center; margin: 5px">
+ <h3>Are You Sure?</h3>
+ <p>This will wipe the disk and reimage the host</p>
+ <button class="btn" onclick="document.getElementById('modal_warning').style['max-height'] = '0px';">Nevermind</button>
+ <button class="btn btn-danger" data-dismiss="modal" onclick="submit_image_form();">I'm Sure</button>
+ </div>
+ </div>
+ </div>
+</div>
+
+<script>
+ var image_mapping = {{image_mapping|safe}};
+ var current_host_id = 0;
+ function set_image_dropdown(profile_name, host_id) {
+ document.getElementById("host_id_input").value = host_id;
+ current_host_id = host_id;
+ var dropdown = document.getElementById("image_select");
+ var length = dropdown.length;
+ //clear dropdown
+ for(i=length-1; i>=0; i--){
+ dropdown.options.remove(i);
+ }
+ var images = image_mapping[profile_name];
+ var image_length = images.length;
+ for(i=0; i<image_length; i++){
+ var opt = document.createElement("OPTION");
+ opt.value = images[i].value;
+ opt.appendChild(document.createTextNode(images[i].name));
+ dropdown.options.add(opt);
+ }
+
+ document.getElementById("modal_warning").style['max-height'] = '0px';
+ }
+
+ function submit_image_form() {
+ var ajaxForm = $("#image_host_form");
+ var formData = ajaxForm.serialize();
+ req = new XMLHttpRequest();
+ req.open("POST", "/booking/modify/{{booking.id}}/image/", true);
+ req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
+ req.onerror = function() { alert("problem submitting form"); }
+ req.onreadystatechange = function() {
+ if(req.readyState === 4) {
+ node = document.getElementById("host_image_" + current_host_id);
+ text = document.createTextNode(req.responseText);
+ node.replaceChild(text, node.firstChild);
+ }
+ }
+ req.send(formData);
+ }
+</script>
{% endblock content %}
diff --git a/src/templates/dashboard/landing.html b/src/templates/dashboard/landing.html
index 6bbb25b..fb75d5f 100644
--- a/src/templates/dashboard/landing.html
+++ b/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/src/templates/notifier/email_fulfilled.txt b/src/templates/notifier/email_fulfilled.txt
index d473961..65593db 100644
--- a/src/templates/notifier/email_fulfilled.txt
+++ b/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/src/templates/snapshot_workflow/steps/meta.html b/src/templates/snapshot_workflow/steps/meta.html
index 2e767cc..cc49691 100644
--- a/src/templates/snapshot_workflow/steps/meta.html
+++ b/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/src/templates/snapshot_workflow/steps/select_host.html b/src/templates/snapshot_workflow/steps/select_host.html
index 16dd5d4..27a9238 100644
--- a/src/templates/snapshot_workflow/steps/select_host.html
+++ b/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/src/workflow/forms.py b/src/workflow/forms.py
index b8c7f66..726e7dd 100644
--- a/src/workflow/forms.py
+++ b/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/src/workflow/models.py b/src/workflow/models.py
index 495ce07..4e79546 100644
--- a/src/workflow/models.py
+++ b/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/src/workflow/snapshot_workflow.py b/src/workflow/snapshot_workflow.py
index 4ddc397..0d53ed4 100644
--- a/src/workflow/snapshot_workflow.py
+++ b/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):