You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airavata.apache.org by sm...@apache.org on 2020/05/01 03:07:15 UTC
[airavata-custos-portal] 04/20: Added navigation buttons to the
application
This is an automated email from the ASF dual-hosted git repository.
smarru pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/airavata-custos-portal.git
commit ac2a89410fd6d3b2b81567a5ade60c800b2bd6fe
Author: Shivam Rastogi <sh...@yahoo.com>
AuthorDate: Sun Mar 22 21:10:07 2020 -0400
Added navigation buttons to the application
---
.../custos_admin_portal/app_config.py | 101 +++++++++++++++++++++
.../custos_admin_portal/apps/admin/apps.py | 19 +++-
.../custos_admin_portal/apps/admin/urls.py | 2 +-
.../custos_admin_portal/apps/admin/views.py | 3 +-
.../custos_admin_portal/apps/workspace/apps.py | 28 +++++-
.../custos_admin_portal/apps/workspace/urls.py | 2 +-
.../custos_admin_portal/apps/workspace/views.py | 6 +-
.../custos_admin_portal/context_processors.py | 87 ++++++++++++++++++
.../custos_admin_portal/settings.py | 3 +-
.../static/common/dist/webpack-stats.json | 2 +-
.../static/common/package-lock.json | 7 +-
.../static/common/vue.config.js | 1 +
.../custos_admin_portal/templates/base.html | 68 +++++++++++++-
13 files changed, 310 insertions(+), 19 deletions(-)
diff --git a/custos_admin_portal/custos_admin_portal/app_config.py b/custos_admin_portal/custos_admin_portal/app_config.py
new file mode 100644
index 0000000..b99cf99
--- /dev/null
+++ b/custos_admin_portal/custos_admin_portal/app_config.py
@@ -0,0 +1,101 @@
+
+import logging
+from abc import ABC, abstractmethod
+from importlib import import_module
+
+from django.apps import AppConfig
+
+logger = logging.getLogger(__name__)
+
+
+class CustosAppConfig(AppConfig, ABC):
+ """Custom AppConfig for Django Airavata apps."""
+
+ @property
+ def url_app_name(self):
+ """Return the urls application namespace."""
+ return get_url_app_name(self)
+
+ @property
+ @abstractmethod
+ def app_order(self):
+ """Return positive int order of app in listings, lowest sorts first."""
+ pass
+
+ @property
+ @abstractmethod
+ def url_home(self):
+ """Named route of home page for this application."""
+ pass
+
+ @property
+ @abstractmethod
+ def fa_icon_class(self):
+ """Font Awesome icon class name."""
+ pass
+
+ @property
+ @abstractmethod
+ def app_description(self):
+ """Some user friendly text to briefly describe the application."""
+ pass
+
+
+def enhance_custom_app_config(app):
+ """As necessary add default values for properties to custom AppConfigs."""
+ app.url_app_name = get_url_app_name(app)
+ app.url_home = get_url_home(app)
+ app.fa_icon_class = get_fa_icon_class(app)
+ app.app_description = get_app_description(app)
+ return app
+
+
+def get_url_app_name(app_config):
+ """Return the urls namespace for the given AppConfig instance."""
+ urls = get_app_urls(app_config)
+ return getattr(urls, 'app_name', None)
+
+
+def get_url_home(app_config):
+ """Get named URL of home page of app."""
+ if hasattr(app_config, 'url_home'):
+ return app_config.url_home
+ else:
+ return get_default_url_home(app_config)
+
+
+def get_default_url_home(app_config):
+ """Return first url pattern as a default."""
+ urls = get_app_urls(app_config)
+ app_name = get_url_app_name(app_config)
+ logger.warning("Custom Django app {} has no URL namespace "
+ "defined".format(app_config.label))
+ first_named_url = None
+ for urlpattern in urls.urlpatterns:
+ if hasattr(urlpattern, 'name'):
+ first_named_url = urlpattern.name
+ break
+ if not first_named_url:
+ raise Exception("{} has no named urls, "
+ "can't figure out default home URL")
+ if app_name:
+ return app_name + ":" + first_named_url
+ else:
+ return first_named_url
+
+
+def get_fa_icon_class(app_config):
+ """Return Font Awesome icon class to use for app."""
+ if hasattr(app_config, "fa_icon_class"):
+ return app_config.fa_icon_class
+ else:
+ return 'fa-circle'
+
+
+def get_app_description(app_config):
+ """Return brief description of app."""
+ return getattr(app_config, 'app_description', None)
+
+
+def get_app_urls(app_config):
+ return import_module(".urls", app_config.name)
diff --git a/custos_admin_portal/custos_admin_portal/apps/admin/apps.py b/custos_admin_portal/custos_admin_portal/apps/admin/apps.py
index 6b3d0d6..2385180 100644
--- a/custos_admin_portal/custos_admin_portal/apps/admin/apps.py
+++ b/custos_admin_portal/custos_admin_portal/apps/admin/apps.py
@@ -1,6 +1,21 @@
-from django.apps import AppConfig
+from custos_admin_portal.app_config import CustosAppConfig
-class AdminConfig(AppConfig):
+class AdminConfig(CustosAppConfig):
name = 'custos_admin_portal.apps.admin'
label = 'custos_admin_portal_admin'
+ verbose_name = 'Admin'
+ app_order = 100
+ url_home = 'custos_admin_portal_admin:list_requests'
+ fa_icon_class = 'fa-cog'
+ app_description = """
+ Configure and share resources with other users.
+ """
+ nav = [
+ {
+ 'label': 'Application Catalog',
+ 'icon': 'fa fa-list',
+ 'url': 'custos_admin_portal_admin:list_requests',
+ 'active_prefixes': ['applications', 'list-requests']
+ }
+ ]
\ No newline at end of file
diff --git a/custos_admin_portal/custos_admin_portal/apps/admin/urls.py b/custos_admin_portal/custos_admin_portal/apps/admin/urls.py
index dd0828d..dc77d80 100644
--- a/custos_admin_portal/custos_admin_portal/apps/admin/urls.py
+++ b/custos_admin_portal/custos_admin_portal/apps/admin/urls.py
@@ -5,7 +5,7 @@ from . import views
app_name = 'custos_admin_portal_admin'
urlpatterns = [
- url(r'^list-requests', views.list_new_tenant_requests, name='list_request'),
+ url(r'^list-requests', views.list_new_tenant_requests, name='list_requests'),
url(r'^request/(?P<tenant_request_id>[^/]+)/$', views.view_tenant_request, name="view_tenant_request"),
url(r'^edit-tenant-request/(?P<tenant_request_id>[^/]+)/$', views.edit_tenant_request, name="edit_tenant_request")
]
diff --git a/custos_admin_portal/custos_admin_portal/apps/admin/views.py b/custos_admin_portal/custos_admin_portal/apps/admin/views.py
index 4b4428f..6be7603 100644
--- a/custos_admin_portal/custos_admin_portal/apps/admin/views.py
+++ b/custos_admin_portal/custos_admin_portal/apps/admin/views.py
@@ -2,7 +2,8 @@ from django.shortcuts import render
def list_new_tenant_requests(request):
- print("New tenant requests list is called")
+ request.active_nav_item = 'list-requests'
+
# TODO fetch all the tenant requests from airavata here.
return render(request, 'workspace/list_requests.html', {
'bundle_name': 'admin-list-requests',
diff --git a/custos_admin_portal/custos_admin_portal/apps/workspace/apps.py b/custos_admin_portal/custos_admin_portal/apps/workspace/apps.py
index 7be3c80..4cf73f2 100644
--- a/custos_admin_portal/custos_admin_portal/apps/workspace/apps.py
+++ b/custos_admin_portal/custos_admin_portal/apps/workspace/apps.py
@@ -1,5 +1,27 @@
-from django.apps import AppConfig
+from custos_admin_portal.app_config import CustosAppConfig
-class WorkspaceConfig(AppConfig):
- name = 'workspace'
+class WorkspaceConfig(CustosAppConfig):
+ name = 'custos_admin_portal.apps.workspace'
+ label = 'custos_admin_portal_workspace'
+ verbose_name = 'Workspace'
+ app_order = 0
+ url_home = 'custos_admin_portal_workspace:request_new_tenant'
+ fa_icon_class = 'fa-flask'
+ app_description = """
+ Launch applications and manage your experiments and projects.
+ """
+ nav = [
+ {
+ 'label': 'Create new tenant request',
+ 'icon': 'fa fa-plus-square',
+ 'url': 'custos_admin_portal_workspace:request_new_tenant',
+ 'active_prefixes': ['applications', 'request-new-tenant']
+ },
+ {
+ 'label': 'List of all existing tenant requests',
+ 'icon': 'fa fa-list',
+ 'url': 'custos_admin_portal_workspace:list_requests',
+ 'active_prefixes': ['applications', 'list-requests']
+ }
+ ]
\ No newline at end of file
diff --git a/custos_admin_portal/custos_admin_portal/apps/workspace/urls.py b/custos_admin_portal/custos_admin_portal/apps/workspace/urls.py
index f41aec0..29c5c34 100644
--- a/custos_admin_portal/custos_admin_portal/apps/workspace/urls.py
+++ b/custos_admin_portal/custos_admin_portal/apps/workspace/urls.py
@@ -5,6 +5,6 @@ from . import views
app_name = 'custos_admin_portal_workspace'
urlpatterns = [
url(r'^request-new-tenant', views.request_new_tenant, name='request_new_tenant'),
- url(r'^list-requests', views.list_new_tenant_requests, name='list_request'),
+ url(r'^list-requests', views.list_new_tenant_requests, name='list_requests'),
url(r'request/(?P<tenant_request_id>[^/]+)/$', views.view_tenant_request, name="view_tenant_request")
]
diff --git a/custos_admin_portal/custos_admin_portal/apps/workspace/views.py b/custos_admin_portal/custos_admin_portal/apps/workspace/views.py
index 9c4d624..8a89243 100644
--- a/custos_admin_portal/custos_admin_portal/apps/workspace/views.py
+++ b/custos_admin_portal/custos_admin_portal/apps/workspace/views.py
@@ -2,7 +2,7 @@ from django.shortcuts import render
def request_new_tenant(request):
- print("request new tenant is called")
+ request.active_nav_item = 'request-new-tenant'
return render(request, 'workspace/request_new_tenant.html', {
'bundle_name': 'request-new-tenant',
@@ -10,7 +10,7 @@ def request_new_tenant(request):
def list_new_tenant_requests(request):
- print("New tenant requests list is called")
+ request.active_nav_item = 'list-requests'
return render(request, 'workspace/list_requests.html', {
'bundle_name': 'list-requests',
@@ -18,7 +18,7 @@ def list_new_tenant_requests(request):
def view_tenant_request(request, tenant_request_id):
- print("Tenant request Id: {}".format(tenant_request_id)),
+
return render(request, 'workspace/view_tenant_request.html', {
'bundle_name': 'view-request',
'tenant_request_id': tenant_request_id
diff --git a/custos_admin_portal/custos_admin_portal/context_processors.py b/custos_admin_portal/custos_admin_portal/context_processors.py
new file mode 100644
index 0000000..824ec95
--- /dev/null
+++ b/custos_admin_portal/custos_admin_portal/context_processors.py
@@ -0,0 +1,87 @@
+import copy
+import logging
+import re
+
+from django.apps import apps
+from django.conf import settings
+
+from custos_admin_portal.app_config import CustosAppConfig
+
+logger = logging.getLogger(__name__)
+
+
+def airavata_app_registry(request):
+ """Put airavata django apps into the context."""
+ print([app for app in apps.app_configs])
+ print([app for app in apps.get_app_configs()])
+
+ airavata_apps = [app for app in apps.get_app_configs()
+ if isinstance(app, CustosAppConfig)]
+ print("Custos apps", airavata_apps)
+ # Sort by app_order then by verbose_name (case-insensitive)
+ airavata_apps.sort(
+ key=lambda app: "{:09}-{}".format(app.app_order,
+ app.verbose_name.lower()))
+ current_app = _get_current_app(request, airavata_apps)
+
+ return {
+ 'airavata_apps': airavata_apps,
+ 'current_airavata_app': current_app,
+ 'airavata_app_nav': (_get_app_nav(request, current_app)
+ if current_app else None)
+ }
+
+
+def custom_app_registry(request):
+ """Put custom Django apps into the context."""
+ custom_apps = settings.CUSTOM_DJANGO_APPS.copy()
+ custom_apps.sort(key=lambda app: app.verbose_name.lower())
+ current_custom_app = _get_current_app(request, custom_apps)
+ return {
+ # 'custom_apps': list(map(_app_to_dict, custom_apps)),
+ 'custom_apps': custom_apps,
+ 'current_custom_app': current_custom_app,
+ 'custom_app_nav': (_get_app_nav(request, current_custom_app)
+ if current_custom_app else None)
+ }
+
+
+def _get_current_app(request, apps):
+ current_app = [
+ app for app in apps
+ if request.resolver_match and
+ app.url_app_name == request.resolver_match.app_name]
+ return current_app[0] if len(current_app) > 0 else None
+
+
+def _get_app_nav(request, current_app):
+ if hasattr(current_app, 'nav'):
+ nav = copy.copy(current_app.nav)
+ # convert "/djangoapp/path/in/app" to "path/in/app"
+ app_path = "/".join(request.path.split("/")[2:])
+ print(app_path)
+ for nav_item in nav:
+ if 'active_prefixes' in nav_item:
+ if re.match("|".join(nav_item['active_prefixes']), app_path):
+ nav_item['active'] = True
+ else:
+ nav_item['active'] = False
+ else:
+ # 'active_prefixes' is optional, and if not specified, assume
+ # current item is active
+ nav_item['active'] = True
+ else:
+ # Default to the home view in the app
+ nav = [
+ {
+ 'label': current_app.verbose_name,
+ 'icon': 'fa ' + current_app.fa_icon_class,
+ 'url': current_app.url_home
+ }
+ ]
+ return nav
+
+
+def resolver_match(request):
+ """Put resolver_match (ResolverMatch instance) into the context."""
+ return {'resolver_match': request.resolver_match}
diff --git a/custos_admin_portal/custos_admin_portal/settings.py b/custos_admin_portal/custos_admin_portal/settings.py
index 8247c61..d248ab9 100644
--- a/custos_admin_portal/custos_admin_portal/settings.py
+++ b/custos_admin_portal/custos_admin_portal/settings.py
@@ -38,7 +38,7 @@ INSTALLED_APPS = [
'webpack_loader',
'custos_admin_portal.apps.auth.apps.AuthConfig',
- 'custos_admin_portal.apps.workspace',
+ 'custos_admin_portal.apps.workspace.apps.WorkspaceConfig',
'custos_admin_portal.apps.admin.apps.AdminConfig'
]
@@ -65,6 +65,7 @@ TEMPLATES = [
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
+ 'custos_admin_portal.context_processors.airavata_app_registry',
],
},
},
diff --git a/custos_admin_portal/custos_admin_portal/static/common/dist/webpack-stats.json b/custos_admin_portal/custos_admin_portal/static/common/dist/webpack-stats.json
index 4e733dd..111ba4b 100644
--- a/custos_admin_portal/custos_admin_portal/static/common/dist/webpack-stats.json
+++ b/custos_admin_portal/custos_admin_portal/static/common/dist/webpack-stats.json
@@ -1 +1 @@
-{"status":"done","publicPath":"http://localhost:9000/static/common/dist/","chunks":{"admin-edit-request":[{"name":"admin-edit-request.js","publicPath":"http://localhost:9000/static/common/dist/admin-edit-request.js","path":"/home/shivam/Programming/custos_UI/custos_admin_portal/custos_admin_portal/static/common/dist/admin-edit-request.js"},{"name":"admin-edit-request.e3e36cbf5bfd0814904f.hot-update.js","publicPath":"http://localhost:9000/static/common/dist/admin-edit-request.e3e36cbf5bfd [...]
\ No newline at end of file
+{"status":"done","publicPath":"http://localhost:9000/static/common/dist/","chunks":{"admin-edit-request":[{"name":"admin-edit-request.js","publicPath":"http://localhost:9000/static/common/dist/admin-edit-request.js","path":"/home/shivam/Programming/custos_UI/custos_admin_portal/custos_admin_portal/static/common/dist/admin-edit-request.js"},{"name":"admin-edit-request.js.map","publicPath":"http://localhost:9000/static/common/dist/admin-edit-request.js.map","path":"/home/shivam/Programming [...]
\ No newline at end of file
diff --git a/custos_admin_portal/custos_admin_portal/static/common/package-lock.json b/custos_admin_portal/custos_admin_portal/static/common/package-lock.json
index 81b4457..fc49d8d 100755
--- a/custos_admin_portal/custos_admin_portal/static/common/package-lock.json
+++ b/custos_admin_portal/custos_admin_portal/static/common/package-lock.json
@@ -1,5 +1,5 @@
{
- "name": "django-airavata-common-ui",
+ "name": "custos-admin-portal-ui",
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
@@ -11968,6 +11968,11 @@
"integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==",
"dev": true
},
+ "vuelidate": {
+ "version": "0.7.5",
+ "resolved": "https://registry.npmjs.org/vuelidate/-/vuelidate-0.7.5.tgz",
+ "integrity": "sha512-GAAG8QAFVp7BFeQlNaThpTbimq3+HypBPNwdkCkHZZeVaD5zmXXfhp357dcUJXHXTZjSln0PvP6wiwLZXkFTwg=="
+ },
"watchpack": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.0.tgz",
diff --git a/custos_admin_portal/custos_admin_portal/static/common/vue.config.js b/custos_admin_portal/custos_admin_portal/static/common/vue.config.js
index 6edcf61..bbe120e 100755
--- a/custos_admin_portal/custos_admin_portal/static/common/vue.config.js
+++ b/custos_admin_portal/custos_admin_portal/static/common/vue.config.js
@@ -39,6 +39,7 @@ module.exports = {
* See also: https://bitbucket.org/calidae/dejavu/src/d63d10b0030a951c3cafa6b574dad25b3bef3fe9/%7B%7Bcookiecutter.project_slug%7D%7D/frontend/vue.config.js?at=master&fileviewer=file-view-default#vue.config.js-27
*/
splitChunks: {
+ chunks: 'all',
cacheGroups: {
vendors: {
name: 'chunk-vendors',
diff --git a/custos_admin_portal/custos_admin_portal/templates/base.html b/custos_admin_portal/custos_admin_portal/templates/base.html
index 9a4a419..07e2425 100644
--- a/custos_admin_portal/custos_admin_portal/templates/base.html
+++ b/custos_admin_portal/custos_admin_portal/templates/base.html
@@ -149,6 +149,69 @@
{% endblock %}
<div class=c-header__title><a href="/">{% block title %}Custos Portal{% endblock %}</a>
</div>
+ {% if not user.is_authenticated %}
+ <div class=c-header__controls>
+ <div class="btn-group">
+ <div class=dropdown>
+ <a href=# class="dropdown-toggle text-dark" id=appDropdownMenuButton data-toggle=dropdown aria-haspopup=true
+ aria-expanded=false>
+ {% if current_airavata_app %}
+ <i class="fa {{ current_airavata_app.fa_icon_class }} mr-2"></i>
+ {{ current_airavata_app.verbose_name }}
+ {% elif current_custom_app %}
+ <i class="fa {{ current_custom_app.fa_icon_class }} mr-2"></i>
+ {{ current_custom_app.verbose_name }}
+ {% else %}
+ Menu
+ {% endif %}
+ </a>
+ <div class=dropdown-menu aria-labelledby=appDropdownMenuButton>
+ {% for app in airavata_apps %}
+ {% if app == current_airavata_app %}
+ <a class="dropdown-item active" href="{% url app.url_home %}">
+ <i class="fa {{ app.fa_icon_class }} mr-2"></i>{{ app.verbose_name }}
+ </a>
+ {% else %}
+ <a class="dropdown-item" href="{% url app.url_home %}">
+ <i class="fa {{ app.fa_icon_class }} mr-2"></i>{{ app.verbose_name }}
+ </a>
+ {% endif %}
+ {% endfor %}
+ {% if custom_apps|length > 0 %}
+ <div class="dropdown-divider"></div>
+ {% for app in custom_apps %}
+ {% if current_custom_app and app.label == current_custom_app.label %}
+ <a class="dropdown-item active" href="{% url app.url_home %}">
+ <i class="fa {{ app.fa_icon_class }} mr-2"></i>{{ app.verbose_name }}
+ </a>
+ {% else %}
+ <a class="dropdown-item" href="{% url app.url_home %}">
+ <i class="fa {{ app.fa_icon_class }} mr-2"></i>{{ app.verbose_name }}
+ </a>
+ {% endif %}
+ {% endfor %}
+ {% endif %}
+ </div>
+ </div>
+ </div>
+ <div class="btn-group ml-3">
+ <div class=dropdown>
+ <a href=# class="dropdown-toggle text-dark" id=dropdownMenuButton data-toggle=dropdown aria-haspopup=true
+ aria-expanded=false>
+ <i class="fa fa-user mr-2"></i>
+ {{ request.session.USERINFO.given_name }}
+ {{ request.session.USERINFO.family_name }}
+ </a>
+ <div class=dropdown-menu aria-labelledby=dropdownMenuButton>
+ <a class=dropdown-item href=#>User settings</a>
+ <a class=dropdown-item href="#">
+ Logout <i class="fa fa-sign-out-alt"></i>
+ </a>
+ </div>
+ </div>
+ </div>
+ </div>
+ {% endif %}
</header>
<div class=stage>
@@ -167,11 +230,6 @@
<i class="{{ nav_item.icon }}"></i> <span class=sr-only>{{ nav_item.label }}</span>
</a>
{% endfor %}
- {% for nav_item in custom_app_nav %}
- <a href="{% url nav_item.url %}" class="c-nav__item {% if nav_item.active %}is-active{% endif %}" data-toggle=tooltip data-placement=right title="{{ nav_item.label }}">
- <i class="{{ nav_item.icon }}"></i> <span class=sr-only>{{ nav_item.label }}</span>
- </a>
- {% endfor %}
{% endblock %}
</nav>
{% comment %}