aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/booking/forms.py8
-rw-r--r--src/booking/quick_deployer.py3
-rw-r--r--src/dashboard/admin_utils.py226
-rw-r--r--src/notifier/tasks.py2
-rw-r--r--src/resource_inventory/migrations/0018_manual_change_rconfig_default_name.py14
-rw-r--r--src/static/img/lfedge-logo.pngbin0 -> 6633 bytes
-rw-r--r--src/static/js/dashboard.js9
-rw-r--r--src/templates/akraino/base.html24
-rw-r--r--src/templates/akraino/dashboard/landing.html23
-rw-r--r--src/templates/base/base.html2
-rw-r--r--src/templates/base/booking/quick_deploy.html33
-rw-r--r--src/templates/lfedge/base.html45
-rw-r--r--src/templates/lfedge/booking/booking_table.html (renamed from src/templates/akraino/booking/booking_table.html)0
-rw-r--r--src/templates/lfedge/booking/quick_deploy.html (renamed from src/templates/akraino/booking/quick_deploy.html)2
-rw-r--r--src/templates/lfedge/dashboard/landing.html23
-rw-r--r--src/templates/lfedge/layout.html (renamed from src/templates/akraino/layout.html)2
16 files changed, 276 insertions, 140 deletions
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/dashboard/admin_utils.py b/src/dashboard/admin_utils.py
index 76db762..ad276d9 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,
@@ -42,12 +51,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 +150,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 +168,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 +185,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 +269,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 +289,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()
@@ -224,6 +307,12 @@ def force_release_booking(booking_id):
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()
@@ -236,46 +325,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()
@@ -306,6 +399,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")
@@ -352,16 +450,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
+ }
-def add_server(profile, uname, interfaces, lab_username="unh_iol", vendor="unknown", model="unknown"):
+ @lab_username: username of the lab to be added to
+
+ @vendor: vendor name of the host, such as "HPE" or "Gigabyte"
+
+ @model: specific model of the host, such as "DL380 Gen 9"
+
+ """
server = Server.objects.create(
bundle=None,
profile=profile,
@@ -369,9 +478,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():
@@ -388,12 +497,25 @@ 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):
@@ -422,7 +544,13 @@ def docs(function=None, fulltext=False):
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/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/static/img/lfedge-logo.png b/src/static/img/lfedge-logo.png
new file mode 100644
index 0000000..689f09a
--- /dev/null
+++ b/src/static/img/lfedge-logo.png
Binary files differ
diff --git a/src/static/js/dashboard.js b/src/static/js/dashboard.js
index 99f6717..b0d2b6b 100644
--- a/src/static/js/dashboard.js
+++ b/src/static/js/dashboard.js
@@ -406,21 +406,30 @@ 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();
}
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 %}