diff options
-rw-r--r-- | src/api/migrations/0022_merge_20211102_2136.py | 14 | ||||
-rw-r--r-- | src/api/models.py | 6 | ||||
-rw-r--r-- | src/api/views.py | 17 | ||||
-rw-r--r-- | src/booking/quick_deployer.py | 2 | ||||
-rw-r--r-- | src/dashboard/admin_utils.py | 29 | ||||
-rw-r--r-- | src/resource_inventory/migrations/0023_cloudinitfile_generated.py | 18 | ||||
-rw-r--r-- | src/resource_inventory/models.py | 1 | ||||
-rw-r--r-- | src/static/js/dashboard.js | 7 | ||||
-rw-r--r-- | src/templates/base/notifier/end_booking.html | 148 | ||||
-rw-r--r-- | src/templates/base/notifier/expiring_booking.html | 149 | ||||
-rw-r--r-- | src/templates/base/notifier/new_booking.html | 145 |
11 files changed, 450 insertions, 86 deletions
diff --git a/src/api/migrations/0022_merge_20211102_2136.py b/src/api/migrations/0022_merge_20211102_2136.py new file mode 100644 index 0000000..bb27ae4 --- /dev/null +++ b/src/api/migrations/0022_merge_20211102_2136.py @@ -0,0 +1,14 @@ +# Generated by Django 2.2 on 2021-11-02 21:36 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0019_auto_20210907_1448'), + ('api', '0021_auto_20210405_1943'), + ] + + operations = [ + ] diff --git a/src/api/models.py b/src/api/models.py index 5928ea9..93168f5 100644 --- a/src/api/models.py +++ b/src/api/models.py @@ -20,6 +20,7 @@ from django.utils import timezone import json import uuid import yaml +import re from booking.models import Booking from resource_inventory.models import ( @@ -362,7 +363,8 @@ class GeneratedCloudConfig(models.Model): def _normalize_username(self, username: str) -> str: # TODO: make usernames posix compliant - return username + s = re.sub(r'\W+', '', username) + return s def _get_ssh_string(self, username: str) -> str: user = User.objects.get(username=username) @@ -502,7 +504,7 @@ class GeneratedCloudConfig(models.Model): return main_dict def serialize(self) -> str: - return yaml.dump(self._to_dict()) + return yaml.dump(self._to_dict(), width=float("inf")) class APILog(models.Model): diff --git a/src/api/views.py b/src/api/views.py index a10b3ec..1516374 100644 --- a/src/api/views.py +++ b/src/api/views.py @@ -295,7 +295,7 @@ def resource_ci_userdata(request, lab_name="", job_id="", resource_id="", file_i "datasource_list": ["None"], } - return HttpResponse(yaml.dump(cloud_dict), status=200) + return HttpResponse(yaml.dump(cloud_dict, width=float("inf")), status=200) @csrf_exempt @@ -310,9 +310,9 @@ def resource_ci_userdata_directory(request, lab_name="", job_id="", resource_id= files = resource.config.cloud_init_files files = [{"id": file.id, "priority": file.priority} for file in files.order_by("priority").all()] - d = { - 'merge_failures': [] - } + d = {} + + merge_failures = [] merger = Merger( [ @@ -325,7 +325,7 @@ def resource_ci_userdata_directory(request, lab_name="", job_id="", resource_id= for f in resource.config.cloud_init_files.order_by("priority").all(): try: - other_dict = yaml.load(f.text) + other_dict = yaml.safe_load(f.text) if not (type(d) is dict): raise Exception("CI file was valid yaml but was not a dict") @@ -335,9 +335,12 @@ def resource_ci_userdata_directory(request, lab_name="", job_id="", resource_id= print("Failed to merge file in, as it had invalid content:", f.id) print("File text was:") print(f.text) - d['merge_failures'].append({f.id: str(e)}) + merge_failures.append({f.id: str(e)}) + + if len(merge_failures) > 0: + d['merge_failures'] = merge_failures - file = CloudInitFile.create(text=yaml.dump(d), priority=0) + file = CloudInitFile.create(text=yaml.dump(d, width=float("inf")), priority=0) return HttpResponse(json.dumps([{"id": file.id, "priority": file.priority}]), status=200) diff --git a/src/booking/quick_deployer.py b/src/booking/quick_deployer.py index 31865be..4b85d76 100644 --- a/src/booking/quick_deployer.py +++ b/src/booking/quick_deployer.py @@ -82,6 +82,8 @@ def update_template(old_template, image, hostname, user, global_cloud_config=Non description=old_template.description, public=False, temporary=True, + private_vlan_pool=old_template.private_vlan_pool, + public_vlan_pool=old_template.public_vlan_pool, copy_of=old_template ) diff --git a/src/dashboard/admin_utils.py b/src/dashboard/admin_utils.py index b105e96..045caeb 100644 --- a/src/dashboard/admin_utils.py +++ b/src/dashboard/admin_utils.py @@ -22,7 +22,8 @@ from resource_inventory.models import ( DiskProfile, CpuProfile, RamProfile, - Interface + Interface, + CloudInitFile, ) import json @@ -50,7 +51,7 @@ from booking.models import Booking from notifier.manager import NotificationHandler from api.models import JobFactory -from api.models import JobStatus +from api.models import JobStatus, Job, GeneratedCloudConfig def print_div(): @@ -528,6 +529,30 @@ def extend_booking(booking_id, days=0, hours=0, minutes=0, weeks=0): booking.save() +def regenerate_cloud_configs(booking_id): + b = Booking.objects.get(id=booking_id) + for res in b.resource.get_resources(): + res.config.cloud_init_files.set(res.config.cloud_init_files.filter(generated=False)) # careful! + res.config.save() + cif = GeneratedCloudConfig.objects.create(resource_id=res.labid, booking=b, rconfig=res.config) + cif.save() + cif = CloudInitFile.create(priority=0, text=cif.serialize()) + cif.save() + res.config.cloud_init_files.add(cif) + res.config.save() + + +def set_job_new(job_id): + j = Job.objects.get(id=job_id) + b = j.booking + regenerate_cloud_configs(b.id) + for task in j.get_tasklist(): + task.status = JobStatus.NEW + task.save() + j.status = JobStatus.NEW + j.save() + + def docs(function=None, fulltext=False): """ Print documentation for a given function in admin_utils. diff --git a/src/resource_inventory/migrations/0023_cloudinitfile_generated.py b/src/resource_inventory/migrations/0023_cloudinitfile_generated.py new file mode 100644 index 0000000..b309753 --- /dev/null +++ b/src/resource_inventory/migrations/0023_cloudinitfile_generated.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2 on 2021-12-17 18:54 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('resource_inventory', '0022_auto_20210925_2028'), + ] + + operations = [ + migrations.AddField( + model_name='cloudinitfile', + name='generated', + field=models.BooleanField(default=False), + ), + ] diff --git a/src/resource_inventory/models.py b/src/resource_inventory/models.py index aefd5ce..5d87430 100644 --- a/src/resource_inventory/models.py +++ b/src/resource_inventory/models.py @@ -157,6 +157,7 @@ class CloudInitFile(models.Model): # higher priority is applied later, so "on top" of existing files priority = models.IntegerField() + generated = models.BooleanField(default=False) @classmethod def merge_strategy(cls): diff --git a/src/static/js/dashboard.js b/src/static/js/dashboard.js index 85a337b..e3978e3 100644 --- a/src/static/js/dashboard.js +++ b/src/static/js/dashboard.js @@ -418,9 +418,12 @@ class MultipleSelectFilterWidget { cnt += required_resources[resource]; } - if (cnt > 1 && hostname && image) { + if (cnt > 1 && hostname) { hostname.readOnly = true; - image.disabled = true; + // we only disable hostname modification because there is no sane case where you want all hosts to have the same hostname + // image is still allowed to be set across all hosts, but is filtered to the set of images that are commonly applicable still + // if no images exist that would apply to all hosts in a pod, then the user is restricted to not setting an image + // and the default image for each host is used } this.updateAvailibility(); diff --git a/src/templates/base/notifier/end_booking.html b/src/templates/base/notifier/end_booking.html index ebad027..22fbd58 100644 --- a/src/templates/base/notifier/end_booking.html +++ b/src/templates/base/notifier/end_booking.html @@ -1,36 +1,134 @@ <html> + <link rel="preconnect" href="https://fonts.googleapis.com"> + <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> + <link href="https://fonts.googleapis.com/css2?family=Source+Sans+Pro&display=swap" rel="stylesheet"> + + <style> + h2{ + font-family: 'Source Sans Pro'; + } + p{ + font-family: Montserrat; + } + li{ + font-family: Montserrat; + } + a{ + color: #f8f9fa; + text-decoration: none; + } + button{ + background-color:#a3c1db; + color: #343a40; + border: 0px; + border-radius: 4mm; + height: 25px; + width: 310px; + text-align: center; + margin: 15px; + text-decoration: none; + font-family: Montserrat; + font-size: 16; + } + button:focus{ + border: 0px; + } + .textFormatting{ + text-align: center; + color:#343a40; + margin: auto; + + } + .content{ + background-color: #f8f9fa; + position: center; + } + table{ + margin-left: auto; + margin-right: auto; + border: 1px solid #343a40; + border-collapse: collapse; + } + tr{ + border-bottom: 1px solid #343a40; + } + td{ + color:#343a40; + padding: 3px; + font-family: Montserrat + } + .row1{ + background-color: #7598b6; + } + .row2{ + background-color: #d7e2f0; + } + .row3{ + background-color: #d2e5f3; + } + </style> + <body> - <div id="message_content_wrapper"> + <div id="message_content_wrapper" class="textFormatting content"> {% if owner %} - <h3>Your booking has expired</h3> + <h2>Your Booking Has Expired.</h2> <p>Your booking has ended and the machines have been cleaned up.</p> <p>Thank you for working on {{booking.lab.project}}, and feel free to book more machines if you need them.</p> {% else %} - <h3>A booking that you collaborated on has expired</h3> - <p>The booking owned by {{booking.owner.username}} that you worked on has ended</p> + <h2>A Booking That You Collaborated on Has Expired.</h2> + <p>The booking owned by {{booking.owner.username}} that you worked on has ended.</p> <p>Thank you for contributing to {{booking.lab.project}}.</p> {% endif %} - <p>Booking information:</p> - <ul> - <li>owner: {{booking.owner.username}}</li> - <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> - <li>collaborators: - <ul> - {% for user in booking.collaborators.all %} - <li>{{user.username}}</li> - {% empty %} - <li>No collaborators</li> - {% endfor %} - </ul> - </li> - </ul> - - <p>You can find more detailed information <a href=/booking/detail/{{booking.id}}/>Here</a></p> + <br> + <table> + <tr class="row1"> + <td style="text-align: center; font-size: larger;" colspan="2">Booking Information:</td> + </tr> + <tr class="row2"> + <td>Owner:</td> + <td>{{booking.owner.username}}</td> + </tr> + <tr class="row3"> + <td>id:</td> + <td>{{booking.id}}</td> + </tr> + <tr class="row2"> + <td>lab:</td> + <td>{{booking.resource.template.lab.lab_user.username}}</td> + </tr> + <tr class="row3"> + <td>resource:</td> + <td>{{booking.resource.template.name}}</td> + </tr> + <tr class="row2"> + <td>start:</td> + <td>{{booking.start}}</td> + </tr> + <tr class="row3"> + <td>end:</td> + <td>{{booking.end}}</td> + </tr> + <tr class="row2"> + <td>purpose:</td> + <td>{{booking.purpose}}</td> + </tr> + <tr class="row3"> + <td>collaborator{% booking.collaborators.all.count|pluralize:"s" %}:</td> + <td></td> + </tr> + {% for user in booking.collaborators.all %} + <tr class="{% cycle 'row2' 'row3' %}"> + <td></td> + <td>{{user.username}}</td> + </tr> + {% empty %} + <tr class="row2"> + <td></td> + <td>No collaborators</td> + </tr> + {% endfor %} + </table> + <button type="button" onclick="window.location.href='/booking/detail/{{booking.id}}'">You can find more detailed information here</button> </div> </body> </html> diff --git a/src/templates/base/notifier/expiring_booking.html b/src/templates/base/notifier/expiring_booking.html index 89042fe..7247081 100644 --- a/src/templates/base/notifier/expiring_booking.html +++ b/src/templates/base/notifier/expiring_booking.html @@ -1,35 +1,134 @@ <html> + <link rel="preconnect" href="https://fonts.googleapis.com"> + <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> + <link href="https://fonts.googleapis.com/css2?family=Source+Sans+Pro&display=swap" rel="stylesheet"> + + <style> + h2{ + font-family: 'Source Sans Pro'; + } + p{ + font-family: Montserrat; + } + li{ + font-family: Montserrat; + } + a{ + color: #f8f9fa; + text-decoration: none; + } + button{ + background-color:#a3c1db; + color: #343a40; + border: 0px; + border-radius: 4mm; + height: 25px; + width: 310px; + text-align: center; + margin: 15px; + text-decoration: none; + font-family: Montserrat; + font-size: 16; + } + button:focus{ + border: 0px; + } + .textFormatting{ + text-align: center; + color:#343a40; + margin: auto; + + } + .content{ + background-color: #f8f9fa; + position: center; + } + table{ + margin-left: auto; + margin-right: auto; + border: 1px solid #343a40; + border-collapse: collapse; + } + tr{ + border-bottom: 1px solid #343a40; + } + td{ + color:#343a40; + padding: 3px; + font-family: Montserrat + } + .row1{ + background-color: #7598b6; + } + .row2{ + background-color: #d7e2f0; + } + .row3{ + background-color: #d2e5f3; + } + </style> + + <body> - <div id="message_content_wrapper"> + <div id="message_content_wrapper" class="textFormatting content"> {% if owner %} - <h3>Your booking is about to expire</h3> + <h2>Your Booking Is About to Expire.</h2> <p>Your booking will expire within 48 hours ({{booking.end}}).</p> {% else %} - <h3>A booking that you collaborate on is about to expire</h3> - <p>The booking owned by {{booking.owner.username}} that you work on is about to expire</p> + <h2>A Booking That You Collaborate on Is About to Expire.</h2> + <p>The booking owned by {{booking.owner.username}} that you work on is about to expire.</p> {% endif %} <p>Please take the time to backup all data or extend the booking if needed.</p> - <p>Booking information:</p> - <ul> - <li>owner: {{booking.owner.username}}</li> - <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> - <li>collaborators: - <ul> - {% for user in booking.collaborators.all %} - <li>{{user.username}}</li> - {% empty %} - <li>No collaborators</li> - {% endfor %} - </ul> - </li> - </ul> - - <p>You can find more detailed information <a href=/booking/detail/{{booking.id}}/>Here</a></p> + <br> + <table> + <tr class="row1"> + <td style="text-align: center; font-size: larger;" colspan="2">Booking Information:</td> + </tr> + <tr class="row2"> + <td>Owner:</td> + <td>{{booking.owner.username}}</td> + </tr> + <tr class="row3"> + <td>id:</td> + <td>{{booking.id}}</td> + </tr> + <tr class="row2"> + <td>lab:</td> + <td>{{booking.resource.template.lab.lab_user.username}}</td> + </tr> + <tr class="row3"> + <td>resource:</td> + <td>{{booking.resource.template.name}}</td> + </tr> + <tr class="row2"> + <td>start:</td> + <td>{{booking.start}}</td> + </tr> + <tr class="row3"> + <td>end:</td> + <td>{{booking.end}}</td> + </tr> + <tr class="row2"> + <td>purpose:</td> + <td>{{booking.purpose}}</td> + </tr> + <tr class="row3"> + <td>collaborator{% booking.collaborators.all.count|pluralize:"s" %}:</td> + <td></td> + </tr> + {% for user in booking.collaborators.all %} + <tr class="{% cycle 'row2' 'row3' %}"> + <td></td> + <td>{{user.username}}</td> + </tr> + {% empty %} + <tr class="row2"> + <td></td> + <td>No collaborators</td> + </tr> + {% endfor %} + </table> + <button type="button" onclick="window.location.href='/booking/detail/{{booking.id}}'">You can find more detailed information here</button> </div> </body> </html> diff --git a/src/templates/base/notifier/new_booking.html b/src/templates/base/notifier/new_booking.html index 7580694..d886f40 100644 --- a/src/templates/base/notifier/new_booking.html +++ b/src/templates/base/notifier/new_booking.html @@ -1,34 +1,133 @@ <html> + <link rel="preconnect" href="https://fonts.googleapis.com"> + <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> + <link href="https://fonts.googleapis.com/css2?family=Source+Sans+Pro&display=swap" rel="stylesheet"> + + <style> + h2{ + font-family: 'Source Sans Pro'; + } + p{ + font-family: Montserrat; + } + li{ + font-family: Montserrat; + } + a{ + color: #f8f9fa; + text-decoration: none; + } + button{ + background-color:#a3c1db; + color: #343a40; + border: 0px; + border-radius: 4mm; + height: 25px; + width: 310px; + text-align: center; + margin: 15px; + text-decoration: none; + font-family: Montserrat; + font-size: 16; + } + button:focus{ + border: 0px; + } + .textFormatting{ + text-align: center; + color:#343a40; + margin: auto; + + } + .content{ + background-color: #f8f9fa; + position: center; + } + table{ + margin-left: auto; + margin-right: auto; + border: 1px solid #343a40; + border-collapse: collapse; + } + tr{ + border-bottom: 1px solid #343a40; + } + td{ + color:#343a40; + padding: 3px; + font-family: Montserrat + } + .row1{ + background-color: #7598b6; + font-weight: bolder; + } + .row2{ + background-color: #d7e2f0; + } + .row3{ + background-color: #d2e5f3; + } + </style> <body> - <div id="message_content_wrapper"> + <div id="message_content_wrapper" class="textFormatting content"> {% if owner %} - <h3>You have created a new booking</h3> + <h2>You Have Created a New Booking.</h2> <p>We have recieved your booking request and will start working on it right away.</p> {% else %} - <h3>You have been added as a collaborator to a booking</h3> + <h2>You Have Been Added as a Collaborator to a Booking.</h2> <p>{{booking.owner.username}} has given you access to their booking.</p> {% endif %} - <p>Booking information:</p> - <ul> - <li>owner: {{booking.owner.username}}</li> - <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> - <li>collaborators: - <ul> - {% for user in booking.collaborators.all %} - <li>{{user.username}}</li> - {% empty %} - <li>No collaborators</li> - {% endfor %} - </ul> - </li> - </ul> + <br> - <p>You can find more detailed information <a href=/booking/detail/{{booking.id}}/>Here</a></p> + <table> + <tr class="row1"> + <td style="text-align: center; font-size: larger;" colspan="2">Booking Information:</td> + </tr> + <tr class="row2"> + <td>Owner:</td> + <td>{{booking.owner.username}}</td> + </tr> + <tr class="row3"> + <td>id:</td> + <td>{{booking.id}}</td> + </tr> + <tr class="row2"> + <td>lab:</td> + <td>{{booking.resource.template.lab.lab_user.username}}</td> + </tr> + <tr class="row3"> + <td>resource:</td> + <td>{{booking.resource.template.name}}</td> + </tr> + <tr class="row2"> + <td>start:</td> + <td>{{booking.start}}</td> + </tr> + <tr class="row3"> + <td>end:</td> + <td>{{booking.end}}</td> + </tr> + <tr class="row2"> + <td>purpose:</td> + <td>{{booking.purpose}}</td> + </tr> + <tr class="row3"> + <td>collaborators:</td> + <td></td> + </tr> + {% for user in booking.collaborators.all %} + <tr class="{% cycle 'row2' 'row3' %}"> + <td></td> + <td>{{user.username}}</td> + </tr> + {% empty %} + <tr class="row2"> + <td></td> + <td>No collaborators</td> + </tr> + {% endfor %} + </table> + <button type="button" onclick="window.location.href='/booking/detail/{{booking.id}}'">You can find more detailed information here</button> </div> </body> </html> |