diff options
21 files changed, 366 insertions, 161 deletions
diff --git a/src/account/models.py b/src/account/models.py index 40de4d8..2a7b017 100644 --- a/src/account/models.py +++ b/src/account/models.py @@ -237,7 +237,7 @@ class Lab(models.Model): def get_available_resources(self): # Cannot import model normally due to ciruclar import Server = apps.get_model('resource_inventory', 'Server') # TODO: Find way to import ResourceQuery - resources = [str(resource.profile) for resource in Server.objects.filter(lab=self, booked=False)] + resources = [str(resource.profile) for resource in Server.objects.filter(lab=self, working=True, booked=False)] return dict(Counter(resources)) def __str__(self): diff --git a/src/booking/forms.py b/src/booking/forms.py index 2a8784f..19c0c85 100644 --- a/src/booking/forms.py +++ b/src/booking/forms.py @@ -59,6 +59,14 @@ class QuickBookingForm(forms.Form): self.fields['filter_field'] = MultipleSelectFilterField(widget=MultipleSelectFilterWidget(**lab_data)) + help_text = 'Hostname can be set only for single-node bookings. For multi-node bookings set hostname through Design a POD.' + self.fields['hostname'].widget.attrs.update({ + 'class': 'has-popover', + 'data-content': help_text, + 'data-placement': 'top', + 'data-container': 'body' + }) + def build_user_list(self): """ Build list of UserProfiles. diff --git a/src/booking/quick_deployer.py b/src/booking/quick_deployer.py index 8b3af6c..7865ee4 100644 --- a/src/booking/quick_deployer.py +++ b/src/booking/quick_deployer.py @@ -111,7 +111,8 @@ def update_template(old_template, image, hostname, user): profile=old_config.profile, image=image_to_set, template=template, - is_head_node=old_config.is_head_node + is_head_node=old_config.is_head_node, + name=hostname if len(old_template.getConfigs()) == 1 else old_config.name ) for old_iface_config in old_config.interface_configs.all(): diff --git a/src/booking/views.py b/src/booking/views.py index c41a7d6..66cb594 100644 --- a/src/booking/views.py +++ b/src/booking/views.py @@ -63,9 +63,12 @@ def quick_create(request): return redirect(reverse('booking:booking_detail', kwargs={'booking_id': booking.id})) except Exception as e: messages.error(request, "Whoops, an error occurred: " + str(e)) + context.update(drop_filter(request.user)) return render(request, 'booking/quick_deploy.html', context) else: messages.error(request, "Looks like the form didn't validate. Check that you entered everything correctly") + context['status'] = 'false' + context.update(drop_filter(request.user)) return render(request, 'booking/quick_deploy.html', context) diff --git a/src/dashboard/admin_utils.py b/src/dashboard/admin_utils.py index b69981c..186a64f 100644 --- a/src/dashboard/admin_utils.py +++ b/src/dashboard/admin_utils.py @@ -1,3 +1,12 @@ +############################################################################## +# Copyright (c) 2021 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 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + from resource_inventory.models import ( ResourceTemplate, Image, @@ -17,6 +26,9 @@ from resource_inventory.models import ( ) import json +import sys +import inspect +import pydoc from django.contrib.auth.models import User @@ -42,12 +54,37 @@ from api.models import JobStatus def print_div(): - print("====================================================================") + """ + Utility function for printing dividers, does nothing directly useful as a utility + """ + print("=" * 68) def book_host(owner_username, host_labid, lab_username, hostname, image_id, template_name, length_days=21, collaborator_usernames=[], purpose="internal", project="LaaS"): """ creates a quick booking using the given host + + @owner_username is the simple username for the user who will own the resulting booking. + Do not set this to a lab username! + + @image_id is the django id of the image in question, NOT the labid of the image. + Query Image objects by their public status and compatible host types + + @host_labid is usually of the form `hpe3` or similar, is the labid of the Server (subtype of Resource) object + + @lab_username for iol is `unh_iol`, other labs will be documented here + + @hostname the hostname that the resulting host should have set + + @template_name the name of the (public, or user accessible) template to use for this booking + + @length_days how long the booking should be, no hard limit currently + + @collaborator_usernames a list of usernames for collaborators to the booking + + @purpose what this booking will be used for + + @project what project/group this booking is on behalf of or the owner represents """ lab = Lab.objects.get(lab_user__username=lab_username) host = Server.objects.filter(lab=lab).get(labid=host_labid) @@ -116,6 +153,16 @@ def book_host(owner_username, host_labid, lab_username, hostname, image_id, temp def mark_working(host_labid, lab_username, working=True): + """ + Mark a host working/not working so that it is either bookable or hidden in the dashboard. + + @host_labid is usually of the form `hpe3` or similar, is the labid of the Server (subtype of Resource) object + + @lab_username: param of the form `unh_iol` or similar + + @working: bool, whether by the end of execution the host should be considered working or not working + """ + lab = Lab.objects.get(lab_user__username=lab_username) server = Server.objects.filter(lab=lab).get(labid=host_labid) print("changing server working status from ", server.working, "to", working) @@ -124,6 +171,16 @@ def mark_working(host_labid, lab_username, working=True): def mark_booked(host_labid, lab_username, booked=True): + """ + Mark a host as booked/unbooked + + @host_labid is usually of the form `hpe3` or similar, is the labid of the Server (subtype of Resource) object + + @lab_username: param of the form `unh_iol` or similar + + @working: bool, whether by the end of execution the host should be considered booked or not booked + """ + lab = Lab.objects.get(lab_user__username=lab_username) server = Server.objects.filter(lab=lab).get(labid=host_labid) print("changing server booked status from ", server.booked, "to", booked) @@ -131,13 +188,26 @@ def mark_booked(host_labid, lab_username, booked=True): server.save() -# returns host filtered by lab and then unique id within lab def get_host(host_labid, lab_username): + """ + Returns host filtered by lab and then unique id within lab + + @host_labid is usually of the form `hpe3` or similar, is the labid of the Server (subtype of Resource) object + + @lab_username: param of the form `unh_iol` or similar + """ lab = Lab.objects.get(lab_user__username=lab_username) return Server.objects.filter(lab=lab).get(labid=host_labid) def get_info(host_labid, lab_username): + """ + Returns various information on the host queried by the given parameters + + @host_labid is usually of the form `hpe3` or similar, is the labid of the Server (subtype of Resource) object + + @lab_username: param of the form `unh_iol` or similar + """ info = {} host = get_host(host_labid, lab_username) info['host_labid'] = host_labid @@ -202,8 +272,16 @@ def detect_leaked_hosts(labid="unh_iol"): return filtered -def booking_for_host(host_labid: str, labid="unh_iol"): - server = Server.objects.get(lab__lab_user__username=labid, labid=host_labid) +def booking_for_host(host_labid: str, lab_username="unh_iol"): + """ + Returns the booking that this server is a part of, if any. + Fails with an exception if no such booking exists + + @host_labid is usually of the form `hpe3` or similar, is the labid of the Server (subtype of Resource) object + + @lab_username: param of the form `unh_iol` or similar + """ + server = Server.objects.get(lab__lab_user__username=lab_username, lab_username=host_labid) booking = server.bundle.booking_set.first() print_div() print(booking) @@ -214,7 +292,15 @@ def booking_for_host(host_labid: str, labid="unh_iol"): return booking -def force_release_booking(booking_id): +def force_release_booking(booking_id: int): + """ + Takes a booking id and forces the booking to end whether or not the tasks have + completed normally. + + Use with caution! Hosts may or may not be released depending on other underlying issues + + @booking_id: the id of the Booking object to be released + """ booking = Booking.objects.get(id=booking_id) job = booking.job tasks = job.get_tasklist() @@ -239,6 +325,12 @@ def free_leaked_public_vlans(safety_buffer_days=2): def get_network_metadata(booking_id: int): + """ + Takes a booking id and prints all (known) networks that are owned by it. + Returns an object of the form {<network name>: {"vlan_id": int, "netname": str <network name>, "public": bool <whether network is public/routable}} + + @booking_id: the id of the Booking object to be queried + """ booking = Booking.objects.get(id=booking_id) bundle = booking.resource pnets = PhysicalNetwork.objects.filter(bundle=bundle).all() @@ -251,46 +343,50 @@ def get_network_metadata(booking_id: int): def print_dict_pretty(a_dict): + """ + admin_utils internal function + """ + print(json.dumps(a_dict, sort_keys=True, indent=4)) -""" -schema: -{ - "name": str - "description": str - "labs": [ - str (lab username) - ] - "disks": { - <diskname> : { - capacity: int (GiB) - media_type: str ("SSD" or "HDD") - interface: str ("sata", "sas", "ssd", "nvme", "scsi", or "iscsi") +def add_profile(data): + """ + Used for adding a host profile to the dashboard + + schema (of dict passed as "data" param): + { + "name": str + "description": str + "labs": [ + str (lab username) + ] + "disks": { + <diskname> : { + capacity: int (GiB) + media_type: str ("SSD" or "HDD") + interface: str ("sata", "sas", "ssd", "nvme", "scsi", or "iscsi") + } } - } - interfaces: { - <intname>: { - "speed": int (mbit) - "nic_type": str ("onboard" or "pcie") - "order": int (compared to the other interfaces, indicates the "order" that the ports are laid out) + interfaces: { + <intname>: { + "speed": int (mbit) + "nic_type": str ("onboard" or "pcie") + "order": int (compared to the other interfaces, indicates the "order" that the ports are laid out) + } + } + cpus: { + cores: int (hardware threads count) + architecture: str (x86_64" or "aarch64") + cpus: int (number of sockets) + cflags: str + } + ram: { + amount: int (GiB) + channels: int } } - cpus: { - cores: int (hardware threads count) - architecture: str (x86_64" or "aarch64") - cpus: int (number of sockets) - cflags: str - } - ram: { - amount: int (GiB) - channels: int - } -} -""" - - -def add_profile(data): + """ base_profile = ResourceProfile.objects.create(name=data['name'], description=data['description']) base_profile.save() @@ -321,6 +417,11 @@ def add_profile(data): def make_default_template(resource_profile, image_id=None, template_name=None, connected_interface_names=None, interfaces_tagged=False, connected_interface_tagged=False, owner_username="root", lab_username="unh_iol", public=True, temporary=False, description=""): + """ + Do not call this function without reading the related source code, it may have unintended effects. + + Used for creating a default template from some host profile + """ if not resource_profile: raise Exception("No viable continuation from none resource_profile") @@ -367,16 +468,27 @@ def make_default_template(resource_profile, image_id=None, template_name=None, c connection.save() -""" -Note: interfaces should be dict from interface name (eg ens1f0) to dict of schema: - { - mac_address: <mac addr>, - bus_addr: <bus addr>, //this field is optional, "" is default - } -""" +def add_server(profile, name, interfaces, lab_username="unh_iol", vendor="unknown", model="unknown"): + """ + Used to enroll a new host of some profile + + @profile: the ResourceProfile in question (by reference to a model object) + + @name: the unique name of the server, currently indistinct from labid + @interfaces: interfaces should be dict from interface name (eg ens1f0) to dict of schema: + { + mac_address: <mac addr>, + bus_addr: <bus addr>, //this field is optional, "" is default + } + + @lab_username: username of the lab to be added to + + @vendor: vendor name of the host, such as "HPE" or "Gigabyte" -def add_server(profile, uname, interfaces, lab_username="unh_iol", vendor="unknown", model="unknown"): + @model: specific model of the host, such as "DL380 Gen 9" + + """ server = Server.objects.create( bundle=None, profile=profile, @@ -384,9 +496,9 @@ def add_server(profile, uname, interfaces, lab_username="unh_iol", vendor="unkno working=True, vendor=vendor, model=model, - labid=uname, + labid=name, lab=Lab.objects.get(lab_user__username=lab_username), - name=uname, + name=name, booked=False) for iface_prof in InterfaceProfile.objects.filter(host=profile).all(): @@ -403,6 +515,60 @@ def add_server(profile, uname, interfaces, lab_username="unh_iol", vendor="unkno def extend_booking(booking_id, days=0, hours=0, minutes=0, weeks=0): + """ + Extend a booking by n <days, hours, minutes, weeks> + + @booking_id: id of the booking + + @days/@hours/@minutes/@weeks: the cumulative amount of delta to add to the length of the booking + """ + booking = Booking.objects.get(id=booking_id) booking.end = booking.end + timedelta(days=days, hours=hours, minutes=minutes, weeks=weeks) booking.save() + + +def docs(function=None, fulltext=False): + """ + Print documentation for a given function in admin_utils. + Call without arguments for more information + """ + + fn = None + + if isinstance(function, str): + try: + fn = globals()[function] + except KeyError: + print("Couldn't find a function by the given name") + return + elif callable(function): + fn = function + else: + print("docs(function: callable | str, fulltext: bool) was called with a 'function' that was neither callable nor a string name of a function") + print("usage: docs('some_function_in_admin_utils', fulltext=True)") + print("The 'fulltext' argument is used to choose if you want the complete source of the function printed. If this argument is false then you will only see the pydoc rendered documentation for the function") + return + + if not fn: + print("couldn't find a function by that name") + + if not fulltext: + print("Pydoc documents the function as such:") + print(pydoc.render_doc(fn)) + else: + print("The full source of the function is this:") + print(inspect.getsource(fn)) + + +def admin_functions(): + """ + List functions available to call within admin_utils + """ + + return [name for name, func in inspect.getmembers(sys.modules[__name__]) if (inspect.isfunction(func) and func.__module__ == __name__)] + + +print("Hint: call `docs(<function name>)` or `admin_functions()` for help on using the admin utils") +print("docs(<function name>) displays documentation on a given function") +print("admin_functions() lists all functions available to call within this module") diff --git a/src/dashboard/tasks.py b/src/dashboard/tasks.py index fec8384..3f88449 100644 --- a/src/dashboard/tasks.py +++ b/src/dashboard/tasks.py @@ -83,6 +83,7 @@ def free_hosts(): job__complete=True, resource__isnull=False ) + for booking in bookings: ResourceManager.getInstance().releaseResourceBundle(booking.resource) diff --git a/src/laas_dashboard/settings.py b/src/laas_dashboard/settings.py index 8f43a3d..6b3ed09 100644 --- a/src/laas_dashboard/settings.py +++ b/src/laas_dashboard/settings.py @@ -240,6 +240,10 @@ CELERYBEAT_SCHEDULE = { 'query_vpn_users': { 'task': 'dashboard.tasks.query_vpn_users', 'schedule': timedelta(hours=1) + }, + 'dispatch_emails': { + 'task': 'notifier.tasks.dispatch_emails', + 'schedule': timedelta(minutes=10) } } diff --git a/src/notifier/tasks.py b/src/notifier/tasks.py index 389750a..64d7574 100644 --- a/src/notifier/tasks.py +++ b/src/notifier/tasks.py @@ -47,5 +47,5 @@ def dispatch_emails(): email.title, email.message, os.environ.get("DEFAULT_FROM_EMAIL", "opnfv@laas-dashboard"), - email.recipient, + [email.recipient], fail_silently=False) diff --git a/src/resource_inventory/migrations/0018_manual_change_rconfig_default_name.py b/src/resource_inventory/migrations/0018_manual_change_rconfig_default_name.py deleted file mode 100644 index b3459bf..0000000 --- a/src/resource_inventory/migrations/0018_manual_change_rconfig_default_name.py +++ /dev/null @@ -1,14 +0,0 @@ -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ('resource_inventory', '0016_auto_20201109_1947'), - ] - operations = [ - migrations.AlterField( - model_name='resourceconfiguration', - name='name', - field=models.CharField(default='opnfv-host') - ), - ] diff --git a/src/resource_inventory/resource_manager.py b/src/resource_inventory/resource_manager.py index f587a82..9406977 100644 --- a/src/resource_inventory/resource_manager.py +++ b/src/resource_inventory/resource_manager.py @@ -67,8 +67,7 @@ class ResourceManager: raise NotImplementedError("Resource Bundle Deletion Not Implemented") def releaseResourceBundle(self, resourceBundle): - for resource in resourceBundle.get_resources(): - resource.release() + resourceBundle.release() def get_vlans(self, resourceTemplate): networks = {} diff --git a/src/static/img/lfedge-logo.png b/src/static/img/lfedge-logo.png Binary files differnew file mode 100644 index 0000000..689f09a --- /dev/null +++ b/src/static/img/lfedge-logo.png diff --git a/src/static/js/dashboard.js b/src/static/js/dashboard.js index efc0542..dfbb157 100644 --- a/src/static/js/dashboard.js +++ b/src/static/js/dashboard.js @@ -225,21 +225,39 @@ class MultipleSelectFilterWidget { make_selection(initial_data){ if(!initial_data || jQuery.isEmptyObject(initial_data)) return; - for(let item_class in initial_data) { - const selected_items = initial_data[item_class]; - for( let node_id in selected_items ){ - const node = this.filter_items[node_id]; - const selection_data = selected_items[node_id] - if( selection_data.selected ) { - this.select(node); - this.markAndSweep(node); - this.updateResult(node); - } - if(node['multiple']){ - this.make_multiple_selection(node, selection_data); - } + + // Need to sort through labs first + let initial_lab = initial_data['lab']; + let initial_resources = initial_data['resource']; + + for( let node_id in initial_lab) { // This should only be length one + const node = this.filter_items[node_id]; + const selection_data = initial_lab[node_id]; + if( selection_data.selected ) { + this.select(node); + this.markAndSweep(node); + this.updateResult(node); } + if(node['multiple']){ + this.make_multiple_selection(node, selection_data); + } + this.currentLab = node; + this.available_resources = JSON.parse(node['available_resources']); } + + for( let node_id in initial_resources){ + const node = this.filter_items[node_id]; + const selection_data = initial_resources[node_id]; + if( selection_data.selected ) { + this.select(node); + this.markAndSweep(node); + this.updateResult(node); + } + if(node['multiple']){ + this.make_multiple_selection(node, selection_data); + } + } + this.updateAvailibility(); } make_multiple_selection(node, selection_data){ @@ -338,10 +356,13 @@ class MultipleSelectFilterWidget { this.available_resources = JSON.parse(node['available_resources']); this.updateAvailibility(); } else { - // a lab is already selected, clear already selected resources - if(confirm('Unselecting a lab will reset all selected resources, are you sure?')) + // a lab is already selected, clear already selected resources + if(confirm('Unselecting a lab will reset all selected resources, are you sure?')) { location.reload(); + return false; + } } + return true; } updateAvailibility() { @@ -357,7 +378,6 @@ class MultipleSelectFilterWidget { let quantityDescription; let quantityNode; - // console.log(this.available_resources); for(let resource in required_resources) { currCount = Math.floor(this.available_resources[resource] / required_resources[resource]); if(currCount < leastAvailable) @@ -388,32 +408,45 @@ class MultipleSelectFilterWidget { reserveResource(node){ const required_resources = JSON.parse(node['required_resources']); + let hostname = document.getElementById('id_hostname'); + let cnt = 0 + for(let resource in required_resources){ this.available_resources[resource] -= required_resources[resource]; + cnt += required_resources[resource]; } + if (cnt > 1) + hostname.readOnly = true; + this.updateAvailibility(); } releaseResource(node){ const required_resources = JSON.parse(node['required_resources']); + let hostname = document.getElementById('id_hostname'); for(let resource in required_resources){ this.available_resources[resource] += required_resources[resource]; } + hostname.readOnly = false; this.updateAvailibility(); } processClick(id){ + let lab_check; const node = this.filter_items[id]; if(!node['selectable']) return; // If they are selecting a lab, update accordingly - if (node['class'] == 'lab') - this.labCheck(node); + if (node['class'] == 'lab') { + lab_check = this.labCheck(node); + if (!lab_check) + return; + } // Can only select a resource if a lab is selected if (!this.currentLab) { diff --git a/src/templates/akraino/base.html b/src/templates/akraino/base.html deleted file mode 100644 index 1368476..0000000 --- a/src/templates/akraino/base.html +++ /dev/null @@ -1,24 +0,0 @@ -{% extends "base/base.html" %} -{% load staticfiles %} -{% block bgColor %} -<style> -.bgAkr { - background: #d9c2f2; -} -</style> -<nav class="navbar navbar-light bgAkr navbar-fixed-top border-bottom py-0 mb-0" role="navigation"> -{% endblock bgColor %} - -{% block logo %} -<div class="col-12 col-sm order-1 order-sm-2 text-center text-lg-left"> - <a href="https://www.lfedge.org/projects/akraino/" class="navbar-brand"> - <img src="{% static "img/akraino_logo.logo" %}"> - </a> - - <a class="navbar-brand d-none d-lg-inline" href={% url 'dashboard:index' %}> - Akraino Dashboard - </a> -</div> -{% endblock logo %} -{% block dropDown %} -{% endblock dropDown %} diff --git a/src/templates/akraino/dashboard/landing.html b/src/templates/akraino/dashboard/landing.html deleted file mode 100644 index 5533469..0000000 --- a/src/templates/akraino/dashboard/landing.html +++ /dev/null @@ -1,23 +0,0 @@ -{% extends "base/dashboard/landing.html" %} -{% block about_us %} - <p>The Shared Community Lab at the IOL aims to help development and testing of LFN projects by hosting hardware and providing access to the community.</p> - <p>To get started, you can request access to a server at the right.</p> -{% endblock about_us %} - -{% block btnGrp %} -<style> -.btnAkr { - color: #fff; - background-color: #39c0c0; -} -.btnAkr:hover{ - color: #fff; - background-color: #259a9a; -} -</style> -<p>To get started, book a pod below:</p> -<a class="btn btnAkr btn-lg d-flex flex-column justify-content-center align-content-center border text-white p-4" href="/booking/quick/">Book a Pod</a> -{% endblock btnGrp %} - -{% block returningUsers %} -{% endblock returningUsers %} diff --git a/src/templates/base/base.html b/src/templates/base/base.html index 3ecad1a..394ddec 100644 --- a/src/templates/base/base.html +++ b/src/templates/base/base.html @@ -36,6 +36,7 @@ <div class="col-6 col-sm-2 order-3 d-flex"> <ul class="nav mx-auto mr-sm-0"> <li class="dropdown ml-auto"> + {% block userDropDownText %} <a class="nav-link p-0 text-dark p-2" data-toggle="dropdown" href="#"> {% if request.user.username %} {{request.user.username}} @@ -44,6 +45,7 @@ {% endif %} <i class="fas fa-caret-down rotate"></i> </a> + {% endblock userDropDownText %} <div class="dropdown-menu dropdown-menu-right"> {% if LFID %} {% if user.is_authenticated %} diff --git a/src/templates/base/booking/quick_deploy.html b/src/templates/base/booking/quick_deploy.html index c954073..8c8b1df 100644 --- a/src/templates/base/booking/quick_deploy.html +++ b/src/templates/base/booking/quick_deploy.html @@ -18,7 +18,7 @@ </div> </div> <div class="row justify-content-center"> - <div class="col-12 col-lg-3 my-2"> + <div class="col-12 col-lg-4 my-2"> <div class="col border rounded py-2 h-100"> {% bootstrap_field form.purpose %} {% bootstrap_field form.project %} @@ -31,28 +31,19 @@ </div> </div> {% block collab %} - <div class="col-12 col-lg-3 my-2"> + <div class="col-12 col-lg-4 my-2"> <div class="col border rounded py-2 h-100"> <label>Collaborators</label> {{ form.users }} </div> </div> {% endblock collab %} - <div class="col-12 col-lg-3 my-2"> + <div class="col-12 col-lg-4 my-2"> <div class="col border rounded py-2 h-100"> {% bootstrap_field form.hostname %} {% bootstrap_field form.image %} </div> </div> - {% block opnfv %} - <div class="col-12 col-lg-3 my-2"> - <div class="col border rounded py-2 h-100"> - <strong>OPNFV: (Optional)</strong> - {% bootstrap_field form.installer %} - {% bootstrap_field form.scenario %} - </div> - </div> - {% endblock opnfv %} <div class="col-12 d-flex mt-2 justify-content-end"> <button id="quick_booking_confirm" onclick="submit_form();" type="button" class="btn btn-success">Confirm</button> </div> @@ -85,6 +76,10 @@ } } + $(document).ready(function() { + $('.has-popover').popover({'trigger':'hover'}); + }); + var sup_image_dict = {{image_filter | safe}}; var sup_installer_dict = {{installer_filter | safe}}; var sup_scenario_dict = {{scenario_filter | safe}}; @@ -108,25 +103,11 @@ } imageFilter(); - $('#id_installer').children().hide(); - $('#id_scenario').children().hide(); - Array.from(document.getElementsByClassName("grid-item-select-btn")).forEach(function (element) { element.addEventListener('click', imageFilter); }); - function installerHider() { - dropFilter("id_installer", sup_installer_dict, "id_image"); - scenarioHider(); - } - document.getElementById('id_image').addEventListener('change', installerHider); - - function scenarioHider() { - dropFilter("id_scenario", sup_scenario_dict, "id_installer"); - } - document.getElementById('id_installer').addEventListener('change', scenarioHider); - function dropFilter(target, target_filter, master) { var dropdown = document.getElementById(target); diff --git a/src/templates/lfedge/base.html b/src/templates/lfedge/base.html new file mode 100644 index 0000000..64c05a4 --- /dev/null +++ b/src/templates/lfedge/base.html @@ -0,0 +1,45 @@ +{% extends "base/base.html" %} +{% load staticfiles %} +{% block bgColor %} +<style> +.LFEdge { + background: #0049b0; + margin-left: -25px; +} + +.wtext { + font-size: 18px; + color: #FFFFFF; +} + +.wtext:hover { + color: #FFFFFF; + text-decoration: none; +} +</style> +<nav class="navbar navbar-light LFEdge navbar-fixed-top border-bottom py-0 mb-0" role="navigation"> +{% endblock bgColor %} + +{% block logo %} +<div class="barClamp col-12 col-sm order-1 order-sm-2 text-center text-lg-left"> + <a href="https://www.lfedge.org/" class="navbar-brand"> + <img src="{% static "img/lfedge-logo.png" %}"> + </a> + + <a class="wtext d-none d-lg-inline" href={% url 'dashboard:index' %}> + Dashboard + </a> +</div> +{% endblock logo %} +{% block dropDown %} +{% endblock dropDown %} +{% block userDropDownText %} +<a class="nav-link p-0 wtext p-2" data-toggle="dropdown" href="#"> + {% if request.user.username %} + {{request.user.username}} + {% else %} + <i class="fas fa-user"></i> + {% endif %} + <i class="fas fa-caret-down rotate"></i> +</a> +{% endblock userDropDownText %} diff --git a/src/templates/akraino/booking/booking_table.html b/src/templates/lfedge/booking/booking_table.html index 4afb4d2..4afb4d2 100644 --- a/src/templates/akraino/booking/booking_table.html +++ b/src/templates/lfedge/booking/booking_table.html diff --git a/src/templates/akraino/booking/quick_deploy.html b/src/templates/lfedge/booking/quick_deploy.html index c3e519f..dac3815 100644 --- a/src/templates/akraino/booking/quick_deploy.html +++ b/src/templates/lfedge/booking/quick_deploy.html @@ -6,7 +6,7 @@ Please select a host type you wish to book. Only available types are shown. More information can be found here: - <a href="https://wiki.akraino.org/display/AK/Shared+Community+Lab">Akraino Wiki</a>. + <a href="https://wiki.lfedge.org/display/LE/Shared+Community+Lab">LF Edge Wiki</a>. If something isn't working right, let us know <a href="mailto:{{contact_email}}"> here! </a> </p> {% endblock form-text %} diff --git a/src/templates/lfedge/dashboard/landing.html b/src/templates/lfedge/dashboard/landing.html new file mode 100644 index 0000000..9a776dc --- /dev/null +++ b/src/templates/lfedge/dashboard/landing.html @@ -0,0 +1,23 @@ +{% extends "base/dashboard/landing.html" %} +{% block about_us %} + <p>The Shared Community Lab at the IOL aims to help development and testing of LF Edge projects by hosting hardware and providing access to the community.</p> + <p>To get started, you can request access to a pod at the right.</p> +{% endblock about_us %} + +{% block btnGrp %} +<style> +.btnLFEdge { + color: #fff; + background-color: #0049b0; +} +.btnLFEdge:hover{ + color: #fff; + background-color: #001776; +} +</style> +<p>To get started, book a pod below:</p> +<a class="btn btnLFEdge btn-lg d-flex flex-column justify-content-center align-content-center border text-white p-4" href="/booking/quick/">Book a Pod</a> +{% endblock btnGrp %} + +{% block returningUsers %} +{% endblock returningUsers %} diff --git a/src/templates/akraino/layout.html b/src/templates/lfedge/layout.html index d30ddb6..217060c 100644 --- a/src/templates/akraino/layout.html +++ b/src/templates/lfedge/layout.html @@ -1,5 +1,5 @@ {% extends "base/layout.html" %} {% block head-title %} -<title>Akraino Dashboard</title> +<title>LF Edge Dashboard</title> {% endblock head-title %} |