dynamic home view with customization
diff --git a/planetstack/core/admin.py b/planetstack/core/admin.py
index 3b87dc8..f054e3f 100644
--- a/planetstack/core/admin.py
+++ b/planetstack/core/admin.py
@@ -874,6 +874,12 @@
# field does not have access to the initial value
return self.initial["password"]
+class UserDashboardViewInline(PlStackTabularInline):
+ model = UserDashboardView
+ extra = 0
+ suit_classes = 'suit-tab suit-tab-dashboards'
+ fields = ['user', 'dashboardView', 'order']
+
class UserAdmin(UserAdmin):
class Meta:
app_label = "core"
@@ -888,7 +894,7 @@
list_display = ('email', 'firstname', 'lastname', 'site', 'last_login')
#list_display = ('email', 'username','firstname', 'lastname', 'is_admin', 'last_login')
list_filter = ('site',)
- inlines = [SlicePrivilegeInline,SitePrivilegeInline,DeploymentPrivilegeInline]
+ inlines = [SlicePrivilegeInline,SitePrivilegeInline,DeploymentPrivilegeInline,UserDashboardViewInline]
fieldListLoginDetails = ['email','site','password','is_readonly','is_amin','public_key']
fieldListContactInfo = ['firstname','lastname','phone','timezone']
@@ -896,6 +902,7 @@
fieldsets = (
('Login Details', {'fields': ['email', 'site','password', 'is_readonly', 'is_admin', 'public_key'], 'classes':['suit-tab suit-tab-general']}),
('Contact Information', {'fields': ('firstname','lastname','phone', 'timezone'), 'classes':['suit-tab suit-tab-contact']}),
+ #('Dashboard Views', {'fields': ('dashboards',), 'classes':['suit-tab suit-tab-dashboards']}),
#('Important dates', {'fields': ('last_login',)}),
)
add_fieldsets = (
@@ -911,7 +918,12 @@
user_readonly_fields = fieldListLoginDetails
user_readonly_inlines = [SlicePrivilegeROInline,SitePrivilegeROInline,DeploymentPrivilegeROInline]
- suit_form_tabs =(('general','Login Details'),('contact','Contact Information'),('sliceprivileges','Slice Privileges'),('siteprivileges','Site Privileges'),('deploymentprivileges','Deployment Privileges'))
+ suit_form_tabs =(('general','Login Details'),
+ ('contact','Contact Information'),
+ ('sliceprivileges','Slice Privileges'),
+ ('siteprivileges','Site Privileges'),
+ ('deploymentprivileges','Deployment Privileges'),
+ ('dashboards','Dashboard Views'))
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == 'site':
@@ -956,7 +968,13 @@
def queryset(self, request):
return User.select_by_user(request.user)
+class DashboardViewAdmin(PlanetStackBaseAdmin):
+ fieldsets = [('Dashboard View Details',
+ {'fields': ['name', 'url'],
+ 'classes': ['suit-tab suit-tab-general']})
+ ]
+ suit_form_tabs =(('general','Dashboard View Details'),)
class ServiceResourceROInline(ReadOnlyTabularInline):
model = ServiceResource
@@ -1378,4 +1396,5 @@
#admin.site.register(SitePrivilege, SitePrivilegeAdmin)
admin.site.register(Sliver, SliverAdmin)
admin.site.register(Image, ImageAdmin)
+ admin.site.register(DashboardView, DashboardViewAdmin)
diff --git a/planetstack/core/models/__init__.py b/planetstack/core/models/__init__.py
index bf0015a..5dda2ed 100644
--- a/planetstack/core/models/__init__.py
+++ b/planetstack/core/models/__init__.py
@@ -8,7 +8,8 @@
from .role import Role
#from .deployment import Deployment
from .site import Site,Deployment, DeploymentRole, DeploymentPrivilege, SiteDeployments
-from .user import User, UserDeployments
+from .dashboard import DashboardView
+from .user import User, UserDeployments, UserDashboardView
from .serviceclass import ServiceClass
from .slice import Slice, SliceDeployments
from .site import SitePrivilege, SiteDeployments
@@ -29,3 +30,4 @@
from .reservation import Reservation
from .network import Network, NetworkParameterType, NetworkParameter, NetworkSliver, NetworkTemplate, Router, NetworkSlice, NetworkDeployments
from .billing import Account, Invoice, Charge, UsableObject, Payment
+
diff --git a/planetstack/core/models/dashboard.py b/planetstack/core/models/dashboard.py
new file mode 100644
index 0000000..aa79f84
--- /dev/null
+++ b/planetstack/core/models/dashboard.py
@@ -0,0 +1,11 @@
+import os
+from django.db import models
+from core.models import PlCoreBase
+from django.contrib.contenttypes import generic
+
+class DashboardView(PlCoreBase):
+ name = models.CharField(max_length=200, unique=True, help_text="Name of the View")
+ url = models.CharField(max_length=1024, help_text="URL of Dashboard")
+
+ def __unicode__(self): return u'%s' % (self.name)
+
diff --git a/planetstack/core/models/user.py b/planetstack/core/models/user.py
index c4e06e0..6e7eef6 100644
--- a/planetstack/core/models/user.py
+++ b/planetstack/core/models/user.py
@@ -3,10 +3,11 @@
from collections import defaultdict
from django.db import models
from django.db.models import F, Q
-from core.models import PlCoreBase,Site
+from core.models import PlCoreBase,Site, DashboardView
from core.models.deployment import Deployment
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager
from timezones.fields import TimeZoneField
+from operator import itemgetter, attrgetter
# Create your models here.
class UserManager(BaseUserManager):
@@ -77,6 +78,8 @@
timezone = TimeZoneField()
+ dashboards = models.ManyToManyField('DashboardView', through='UserDashboardView', blank=True)
+
objects = UserManager()
USERNAME_FIELD = 'email'
@@ -113,6 +116,20 @@
def is_superuser(self):
return False
+ def get_dashboards(self):
+ DEFAULT_DASHBOARDS=["Tenant"]
+
+ dashboards = sorted(list(self.dashboardViews.all()), key=attrgetter('order'))
+ dashboards = [x.dashboardView for x in dashboards]
+
+ if not dashboards:
+ for dashboardName in DEFAULT_DASHBOARDS:
+ dbv = DashboardView.objects.filter(name=dashboardName)
+ if dbv:
+ dashboards.append(dbv[0])
+
+ return dashboards
+
# def get_roles(self):
# from core.models.site import SitePrivilege
# from core.models.slice import SliceMembership
@@ -163,4 +180,9 @@
else:
users = Users.select_by_user(user)
qs = Usereployments.objects.filter(user__in=slices)
- return qs
+ return qs
+
+class UserDashboardView(PlCoreBase):
+ user = models.ForeignKey(User, related_name="dashboardViews")
+ dashboardView = models.ForeignKey(DashboardView, related_name="dashboardViews")
+ order = models.IntegerField(default=0)
diff --git a/planetstack/core/plus/sites.py b/planetstack/core/plus/sites.py
index b496481..66c5d00 100644
--- a/planetstack/core/plus/sites.py
+++ b/planetstack/core/plus/sites.py
@@ -12,17 +12,19 @@
def get_urls(self):
"""Add our dashboard view to the admin urlconf. Deleted the default index."""
from django.conf.urls import patterns, url
- from views import DashboardView, DashboardWelcomeView, DashboardAjaxView, SimulatorView, DashboardSummaryAjaxView, DashboardAddOrRemoveSliverView, DashboardUserSiteView, DashboardAnalyticsAjaxView, TenantViewData,TenantCreateSlice, TenantAddOrRemoveSliverView, TenantPickSitesView, TenantDeleteSliceView,TenantUpdateSlice
+ from views import DashboardCustomize, DashboardDynamicView, DashboardWelcomeView, DashboardAjaxView, SimulatorView, DashboardSummaryAjaxView, DashboardAddOrRemoveSliverView, DashboardUserSiteView, DashboardAnalyticsAjaxView, TenantViewData,TenantCreateSlice, TenantAddOrRemoveSliverView, TenantPickSitesView, TenantDeleteSliceView,TenantUpdateSlice
urls = super(AdminMixin, self).get_urls()
del urls[0]
custom_url = patterns('',
- url(r'^$', self.admin_view(DashboardWelcomeView.as_view()),
+ url(r'^$', self.admin_view(DashboardDynamicView.as_view()),
name="index"),
url(r'^test/', self.admin_view(DashboardUserSiteView.as_view()),
name="test"),
- url(r'^dashboard/(?P<name>\w+)/$', self.admin_view(DashboardView.as_view()),
+ url(r'^dashboard/(?P<name>\w+)/$', self.admin_view(DashboardDynamicView.as_view()),
name="dashboard"),
+ url(r'^customize/$', self.admin_view(DashboardCustomize.as_view()),
+ name="customize"),
url(r'^hpcdashuserslices/', self.admin_view(DashboardUserSiteView.as_view()),
name="hpcdashuserslices"),
url(r'^hpcdashboard/', self.admin_view(DashboardAjaxView.as_view()), # DEPRECATED
diff --git a/planetstack/core/plus/views.py b/planetstack/core/plus/views.py
index 451ee6d..142911b 100644
--- a/planetstack/core/plus/views.py
+++ b/planetstack/core/plus/views.py
@@ -17,6 +17,7 @@
from django.core import urlresolvers
from django.contrib.gis.geoip import GeoIP
from ipware.ip import get_ip
+from operator import itemgetter, attrgetter
import traceback
import socket
@@ -34,15 +35,10 @@
def get(self, request, *args, **kwargs):
context = self.get_context_data(**kwargs)
- userDetails = getUserSliceInfo(request.user)
- #context['site'] = userDetails['site']
-
- context['userSliceInfo'] = userDetails['userSliceInfo']
- context['cdnData'] = userDetails['cdnData']
- context['cdnContentProviders'] = userDetails['cdnContentProviders']
+ context = getDashboardContext(request.user, context)
return self.render_to_response(context=context)
-class DashboardView(TemplateView):
+class DashboardDynamicView(TemplateView):
head_template = r"""{% extends "admin/dashboard/dashboard_base.html" %}
{% load admin_static %}
{% block content %}
@@ -50,25 +46,56 @@
tail_template = r"{% endblock %}"
- def get(self, request, name="hpc_historical", *args, **kwargs):
+ def get(self, request, name="root", *args, **kwargs):
context = self.get_context_data(**kwargs)
+ context = getDashboardContext(request.user, context)
+ if name=="root":
+ return self.multiDashboardView(request, context)
+ else:
+ return self.singleDashboardView(request, name, context)
+
+ def readDashboard(self, fn):
+ try:
+ template= open("/opt/planetstack/templates/admin/dashboard/%s.html" % fn, "r").read()
+ if (fn=="tenant"):
+ template = '<div id="tabs-5"></div>' + template
+ return template
+ except:
+ return "failed to open %s" % fn
+
+ def multiDashboardView(self, request, context):
head_template = self.head_template
tail_template = self.tail_template
- if (name=="tenant"):
- # quick fix for tenant view
- head_template = head_template + '<div id="tabs-5"></div>'
+ body = """
+ <div id="hometabs" >
+ <ul id="suit_form_tabs" class="nav nav-tabs nav-tabs-suit" data-tab-prefix="suit-tab">
+ """
+ dashboards = request.user.get_dashboards()
- t = template.Template(head_template + open("/opt/planetstack/templates/admin/dashboard/%s.html" % name, "r").read() + self.tail_template)
+ # customize is a special dashboard they always get
+ customize = DashboardView.objects.filter(name="Customize")
+ if customize:
+ dashboards.append(customize[0])
- userDetails = getUserSliceInfo(request.user)
- #context['site'] = userDetails['site']
+ for i,view in enumerate(dashboards):
+ body = body + '<li><a href="#dashtab-%d">%s</a></li>\n' % (i, view.name)
- context['userSliceInfo'] = userDetails['userSliceInfo']
- context['cdnData'] = userDetails['cdnData']
- context['cdnContentProviders'] = userDetails['cdnContentProviders']
+ body = body + "</ul>\n"
+
+ for i,view in enumerate(dashboards):
+ url = view.url
+ body = body + '<div id="dashtab-%d">\n' % i
+ if url.startswith("template:"):
+ fn = url[9:]
+ body = body + self.readDashboard(fn)
+ body = body + '</div>\n'
+
+ body=body+"</div>\n"
+
+ t = template.Template(head_template + body + self.tail_template)
response_kwargs = {}
response_kwargs.setdefault('content_type', self.content_type)
@@ -78,18 +105,50 @@
context = context,
**response_kwargs)
-def getUserSliceInfo(user, tableFormat = False):
- userDetails = {}
+ def singleDashboardView(self, request, name, context):
+ head_template = self.head_template
+ tail_template = self.tail_template
+
+ t = template.Template(head_template + self.readDashboard(fn) + self.tail_template)
+
+ response_kwargs = {}
+ response_kwargs.setdefault('content_type', self.content_type)
+ return self.response_class(
+ request = request,
+ template = t,
+ context = context,
+ **response_kwargs)
+
+def getDashboardContext(user, context={}, tableFormat = False):
+ context = {}
userSliceData = getSliceInfo(user)
if (tableFormat):
-# pprint("******* GET USER SLICE INFO")
- userDetails['userSliceInfo'] = userSliceTableFormatter(userSliceData)
+ context['userSliceInfo'] = userSliceTableFormatter(userSliceData)
else:
- userDetails['userSliceInfo'] = userSliceData
- userDetails['cdnData'] = getCDNOperatorData(wait=False)
- userDetails['cdnContentProviders'] = getCDNContentProviderData()
- return userDetails
+ context['userSliceInfo'] = userSliceData
+ context['cdnData'] = getCDNOperatorData(wait=False)
+ context['cdnContentProviders'] = getCDNContentProviderData()
+
+ (dashboards, unusedDashboards)= getDashboards(user)
+ unusedDashboards=[x for x in unusedDashboards if x!="Customize"]
+ context['dashboards'] = dashboards
+ context['unusedDashboards'] = unusedDashboards
+
+ return context
+
+def getDashboards(user):
+ #dashboards = sorted(list(user.dashboardViews.all()), key=attrgetter('order'))
+ dashboards = user.get_dashboards()
+
+ dashboard_names = [d.name for d in dashboards]
+
+ unused_dashboard_names = []
+ for dashboardView in DashboardView.objects.all():
+ if not dashboardView.name in dashboard_names:
+ unused_dashboard_names.append(dashboardView.name)
+
+ return (dashboard_names, unused_dashboard_names)
class TenantCreateSlice(View):
def post(self, request, *args, **kwargs):
@@ -416,7 +475,7 @@
class DashboardUserSiteView(View):
def get(self, request, **kwargs):
- return HttpResponse(json.dumps(getUserSliceInfo(request.user, True)), mimetype='application/javascript')
+ return HttpResponse(json.dumps(getDashboardContext(request.user, tableFormat=True)), mimetype='application/javascript')
class TenantViewData(View):
def get(self, request, **kwargs):
@@ -654,7 +713,7 @@
if (name == "hpcSummary"):
return HttpResponse(json.dumps(hpc_wizard.get_hpc_wizard().get_summary_for_view()), mimetype='application/javascript')
elif (name == "hpcUserSite"):
- return HttpResponse(json.dumps(getUserSliceInfo(request.user, True)), mimetype='application/javascript')
+ return HttpResponse(json.dumps(getDashboardContext(request.user, tableFormat=True)), mimetype='application/javascript')
elif (name == "hpcMap"):
return HttpResponse(json.dumps(getCDNOperatorData(True)), mimetype='application/javascript')
elif (name == "bigquery"):
@@ -663,3 +722,21 @@
else:
return HttpResponse(json.dumps("Unknown"), mimetype='application/javascript')
+class DashboardCustomize(View):
+ def post(self, request, *args, **kwargs):
+ dashboards = request.POST.get("dashboards", None)
+ if not dashboards:
+ return HttpResponse("no data")
+
+ dashboards = [x.strip() for x in dashboards.split(",")]
+
+ dashboards = [DashboardView.objects.get(name=x) for x in dashboards]
+
+ request.user.dashboardViews.all().delete()
+
+ for i,dashboard in enumerate(dashboards):
+ udbv = UserDashboardView(user=request.user, dashboardView=dashboard, order=i)
+ udbv.save()
+
+ return HttpResponse("updated")
+
diff --git a/planetstack/core/static/planetstack.css b/planetstack/core/static/planetstack.css
index 39102be..234261c 100644
--- a/planetstack/core/static/planetstack.css
+++ b/planetstack/core/static/planetstack.css
@@ -1141,4 +1141,12 @@
#private-vol{
margin-right: 15% !important;
-}
\ No newline at end of file
+}
+
+.customize_row {
+ display: table;
+}
+.customize_column {
+ display: table-cell;
+ padding: 10px;
+}
diff --git a/planetstack/templates/admin/dashboard/customize.html b/planetstack/templates/admin/dashboard/customize.html
new file mode 100644
index 0000000..3f1d2c6
--- /dev/null
+++ b/planetstack/templates/admin/dashboard/customize.html
@@ -0,0 +1,85 @@
+<form>
+ <div class="customize_row">
+ <div class="customize_column">
+ <div>Available Dashboard Views</div>
+ <select name="selectfrom" id="select-from" multiple size="5">
+ {% for cp in unusedDashboards %}
+ <option value="{{ cp }}">{{ cp }}</option>
+ {% endfor %}
+ </select>
+ </div>
+ <div class="customize_column">
+ <br>
+ <div class="btn btn-success" id="btn-add">Add »</div><br><br>
+ <div class="btn btn-success" id="btn-remove">« Remove</div>
+ </div>
+ <div class="customize_column">
+ <div>Selected Dashboard Views</div>
+ <select name="selectto" id="select-to" multiple size="5">
+ {% for cp in dashboards %}
+ <option value="{{ cp }}">{{ cp }}</option>
+ {% endfor %}
+ </select>
+ <br>
+ <div class="btn btn-high btn-info" id="btn-save">Save</div>
+ </div>
+ <div class="customize_column">
+ <br>
+ <div class="btn btn-success" id="btn-up">Up</div><br><br>
+ <div class="btn btn-success" id="btn-down">Down</div>
+ </div>
+ </div>
+</form>
+
+<script>
+$(document).ready(function() {
+ $('#btn-add').click(function(){
+ $('#select-from option:selected').each( function() {
+ $('#select-to').append("<option value='"+$(this).val()+"'>"+$(this).text()+"</option>");
+ $(this).remove();
+ });
+ });
+ $('#btn-remove').click(function(){
+ $('#select-to option:selected').each( function() {
+ $('#select-from').append("<option value='"+$(this).val()+"'>"+$(this).text()+"</option>");
+ $(this).remove();
+ });
+ });
+ $('#btn-up').bind('click', function() {
+ $('#select-to option:selected').each( function() {
+ var newPos = $('#select-to option').index(this) - 1;
+ if (newPos > -1) {
+ $('#select-to option').eq(newPos).before("<option value='"+$(this).val()+"' selected='selected'>"+$(this).text()+"</option>");
+ $(this).remove();
+ }
+ });
+ });
+ $('#btn-down').bind('click', function() {
+ var countOptions = $('#select-to option').size();
+ $('#select-to option:selected').each( function() {
+ var newPos = $('#select-to option').index(this) + 1;
+ if (newPos < countOptions) {
+ $('#select-to option').eq(newPos).after("<option value='"+$(this).val()+"' selected='selected'>"+$(this).text()+"</option>");
+ $(this).remove();
+ }
+ });
+ });
+ $('#btn-save').bind('click', function() {
+ var items=[];
+ $("#select-to option").each(function() { items.push($(this).val()); });
+ $.ajax({
+ url: '/customize/',
+ dataType: 'json',
+ data: {
+ dashboards: items.join(","),
+ csrfmiddlewaretoken: "{{ csrf_token }}" // < here
+ },
+ type: 'POST',
+ complete: function () {
+ location.reload();
+ }
+ });
+ });
+});
+</script>
+