6 from django.views.generic import TemplateView, View
8 from pprint import pprint
10 from syndicate.models import *
11 from core.models import *
12 from hpc.models import ContentProvider
13 from operator import attrgetter
14 from django import template
15 from django.views.decorators.csrf import csrf_exempt
16 from django.http import HttpResponse, HttpResponseServerError
17 from django.core import urlresolvers
18 from django.contrib.gis.geoip import GeoIP
19 from ipware.ip import get_ip
20 from operator import itemgetter, attrgetter
24 BLESSED_SITES = ["Stanford", "Washington", "Princeton", "GeorgiaTech", "MaxPlanck"]
26 if os.path.exists("/home/smbaker/projects/vicci/cdn/bigquery"):
27 sys.path.append("/home/smbaker/projects/vicci/cdn/bigquery")
29 sys.path.append("/opt/planetstack/hpc_wizard")
31 from planetstack_analytics import DoPlanetStackAnalytics, PlanetStackAnalytics, RED_LOAD, BLUE_LOAD
33 class DashboardWelcomeView(TemplateView):
34 template_name = 'admin/dashboard/welcome.html'
36 def get(self, request, *args, **kwargs):
37 context = self.get_context_data(**kwargs)
38 context = getDashboardContext(request.user, context)
39 return self.render_to_response(context=context)
41 class DashboardDynamicView(TemplateView):
42 head_template = r"""{% extends "admin/dashboard/dashboard_base.html" %}
43 {% load admin_static %}
47 tail_template = r"{% endblock %}"
49 def get(self, request, name="root", *args, **kwargs):
50 context = self.get_context_data(**kwargs)
51 context = getDashboardContext(request.user, context)
54 return self.multiDashboardView(request, context)
56 return self.singleDashboardView(request, name, context)
58 def readDashboard(self, fn):
60 template= open("/opt/planetstack/templates/admin/dashboard/%s.html" % fn, "r").read()
62 # fix for tenant view - it writes html to a div called tabs-5
63 template = '<div id="tabs-5"></div>' + template
64 if (fn=="slice_interactions"):
65 # fix for slice_interactions - it gives its container div a 40px
66 # margin, and then positions it's header at -40px
67 template = '<div id="tabs-4">' + template + '</div>'
70 return "failed to open %s" % fn
72 def multiDashboardView(self, request, context):
73 head_template = self.head_template
74 tail_template = self.tail_template
78 <ul id="suit_form_tabs" class="nav nav-tabs nav-tabs-suit" data-tab-prefix="suit-tab">
81 dashboards = request.user.get_dashboards()
83 # customize is a special dashboard they always get
84 customize = DashboardView.objects.filter(name="Customize")
86 dashboards.append(customize[0])
88 for i,view in enumerate(dashboards):
89 body = body + '<li><a href="#dashtab-%d">%s</a></li>\n' % (i, view.name)
91 body = body + "</ul>\n"
93 for i,view in enumerate(dashboards):
95 body = body + '<div id="dashtab-%d">\n' % i
96 if url.startswith("template:"):
98 body = body + self.readDashboard(fn)
99 body = body + '</div>\n'
103 t = template.Template(head_template + body + self.tail_template)
106 response_kwargs.setdefault('content_type', self.content_type)
107 return self.response_class(
\r
113 def singleDashboardView(self, request, name, context):
114 head_template = self.head_template
115 tail_template = self.tail_template
117 t = template.Template(head_template + self.readDashboard(fn) + self.tail_template)
120 response_kwargs.setdefault('content_type', self.content_type)
121 return self.response_class(
\r
127 def getDashboardContext(user, context={}, tableFormat = False):
130 userSliceData = getSliceInfo(user)
132 context['userSliceInfo'] = userSliceTableFormatter(userSliceData)
134 context['userSliceInfo'] = userSliceData
135 context['cdnData'] = getCDNOperatorData(wait=False)
136 context['cdnContentProviders'] = getCDNContentProviderData()
138 (dashboards, unusedDashboards)= getDashboards(user)
139 unusedDashboards=[x for x in unusedDashboards if x!="Customize"]
140 context['dashboards'] = dashboards
141 context['unusedDashboards'] = unusedDashboards
145 def getDashboards(user):
146 #dashboards = sorted(list(user.dashboardViews.all()), key=attrgetter('order'))
147 dashboards = user.get_dashboards()
149 dashboard_names = [d.name for d in dashboards]
151 unused_dashboard_names = []
152 for dashboardView in DashboardView.objects.all():
153 if not dashboardView.name in dashboard_names:
154 unused_dashboard_names.append(dashboardView.name)
156 return (dashboard_names, unused_dashboard_names)
158 class TenantCreateSlice(View):
159 def post(self, request, *args, **kwargs):
160 sliceName = request.POST.get("sliceName", "0")
161 serviceClass = request.POST.get("serviceClass", "0")
162 imageName = request.POST.get("imageName", "0")
163 actionToDo = request.POST.get("actionToDo", "0")
164 #network = request.POST.get("network","0")
165 mountDataSets = request.POST.get("mountDataSets","0")
166 if (actionToDo == "add"):
167 serviceClass = ServiceClass.objects.get(name=serviceClass)
168 site = request.user.site
169 image = Image.objects.get(name=imageName)
170 newSlice = Slice(name=sliceName,serviceClass=serviceClass,site=site,imagePreference=image,mountDataSets=mountDataSets)
172 return HttpResponse("Slice created")
174 class TenantUpdateSlice(View):
175 def post(self, request, *args, **kwargs):
\r
176 sliceName = request.POST.get("sliceName", "0")
\r
177 serviceClass = request.POST.get("serviceClass", "0")
\r
178 imageName = request.POST.get("imageName", "0")
\r
179 actionToDo = request.POST.get("actionToDo", "0")
\r
180 #network = request.POST.get("network","0")
\r
181 dataSet = request.POST.get("dataSet","0")
\r
182 slice = Slice.objects.all()
\r
183 for entry in slice:
\r
184 serviceClass = ServiceClass.objects.get(name=serviceClass)
\r
185 if(entry.name==sliceName):
\r
186 if (actionToDo == "update"):
\r
187 setattr(entry,'serviceClass',serviceClass)
\r
188 setattr(entry,'imagePreference',imageName)
\r
189 #setattr(entry,'network',network)
\r
190 setattr(entry,'mountDataSets',dataSet)
\r
193 return HttpResponse("Slice updated")
\r
195 def getTenantSliceInfo(user, tableFormat = False):
196 tenantSliceDetails = {}
197 tenantSliceData = getTenantInfo(user)
198 tenantServiceClassData = getServiceClassInfo(user)
200 tenantSliceDetails['userSliceInfo'] = userSliceTableFormatter(tenantSliceData)
201 tenantSliceDetails['sliceServiceClass']=userSliceTableFormatter(tenantServiceClassData)
203 tenantSliceDetails['userSliceInfo'] = tenantSliceData
204 tenantSliceDetails['sliceServiceClass']=userSliceTableFormatter(tenantServiceClassData)
205 tenantSliceDetails['image']=userSliceTableFormatter(getImageInfo(user))
206 #tenantSliceDetails['network']=userSliceTableFormatter(getNetworkInfo(user))
207 tenantSliceDetails['deploymentSites']=userSliceTableFormatter(getDeploymentSites())
208 tenantSliceDetails['sites'] = userSliceTableFormatter(getTenantSitesInfo())
209 tenantSliceDetails['mountDataSets'] = userSliceTableFormatter(getMountDataSets())
210 tenantSliceDetails['publicKey'] = getPublicKey(user)
211 return tenantSliceDetails
213 def getTenantInfo(user):
214 slices =Slice.objects.all()
217 sliceName = Slice.objects.get(id=entry.id).name
218 slice = Slice.objects.get(name=Slice.objects.get(id=entry.id).name)
219 sliceServiceClass = entry.serviceClass.name
220 preferredImage = entry.imagePreference
221 sliceDataSet = entry.mountDataSets
222 #sliceNetwork = entry.network
228 for sliver in slice.slivers.all():
229 if sliver.node.site.name in BLESSED_SITES:
230 sliceSite[sliver.node.site.name] = sliceSite.get(sliver.node.site.name,0) + 1
231 sliceImage = sliver.image.name
232 sliceNode[str(sliver)] = sliver.node.name
233 numSliver = sum(sliceSite.values())
234 numSites = len(sliceSite)
235 userSliceInfo.append({'sliceName': sliceName,'sliceServiceClass': sliceServiceClass,'preferredImage':preferredImage,'numOfSites':numSites, 'sliceSite':sliceSite,'sliceImage':sliceImage,'numOfSlivers':numSliver,'sliceDataSet':sliceDataSet,'instanceNodePair':sliceNode})
238 def getTenantSitesInfo():
240 for entry in Site.objects.all():
241 if entry.name in BLESSED_SITES:
242 tenantSiteInfo.append({'siteName':entry.name})
243 return tenantSiteInfo
245 def userSliceTableFormatter(data):
252 def getPublicKey(user):
253 users=User.objects.all()
\r
255 if (str(key.email)==str(user)):
\r
256 sshKey = key.public_key
\r
259 def getServiceClassInfo(user):
260 serviceClassList = ServiceClass.objects.all()
262 for entry in serviceClassList:
263 sliceInfo.append({'serviceClass':entry.name})
266 def getImageInfo(user):
267 imageList = Image.objects.all()
268 #imageList = ['Fedora 16 LXC rev 1.3','Hadoop','MPI']
270 for imageEntry in imageList:
271 imageInfo.append({'Image':imageEntry.name})
272 #imageInfo.append({'Image':imageEntry})
275 def createPrivateVolume(user, sliceName):
276 caps = Volume.CAP_READ_DATA | Volume.CAP_WRITE_DATA | Volume.CAP_HOST_DATA
277 getattr(Volume.default_gateway_caps,"read data") | \
278 getattr(Volume.default_gateway_caps,"write data") | \
279 getattr(Volume.default_gateway_caps,"host files")
280 v = Volume(name="private_" + sliceName, owner_id=user, description="private volume for %s" % sliceName, blocksize=61440, private=True, archive=False, default_gateway_caps = caps)
283 SYNDICATE_REPLICATE_PORTNUM = 1025
287 inuse[SYNDICATE_REPLICATE_PORTNUM] = True
288 for vs in VolumeSlice.objects.all():
289 inuse[vs.peer_portnum]=True
290 inuse[vs.replicate_portnum]=True
291 for network in Network.objects.all():
292 network_ports = [x.strip() for x in network.ports.split(",")]
293 for network_port in network_ports:
295 inuse[int(network_port)] = True
297 # in case someone has put a malformed port number in the list
299 for i in range(1025, 65535):
300 if not inuse.get(i,False):
304 def mountVolume(sliceName, volumeName, readWrite):
305 slice = Slice.objects.get(name=sliceName)
306 volume = Volume.objects.get(name=volumeName)
307 # choose some unused port numbers
308 flags = Volume.CAP_READ_DATA
310 flags = flags | Volume.CAP_WRITE_DATA
311 vs = VolumeSlice(volume_id = volume, slice_id = slice, gateway_caps=flags, peer_portnum = get_free_port(), replicate_portnum = SYNDICATE_REPLICATE_PORTNUM)
314 def hasPrivateVolume(sliceName):
315 slice = Slice.objects.get(name=sliceName)
316 for vs in VolumeSlice.objects.filter(slice_id=slice):
317 if vs.volume_id.private:
321 def getMountDataSets():
323 for volume in Volume.objects.all():
\r
324 if not volume.private:
\r
325 dataSetInfo.append({'DataSet': volume.name})
\r
329 def getNetworkInfo(user):
330 #networkList = Network.objects.all()
331 networkList = ['Private Only','Private and Publicly Routable']
333 for networkEntry in networkList:
334 #networkInfo.append({'Network':networkEntry.name})
335 networkInfo.append({'Network':networkEntry})
338 def getDeploymentSites():
339 deploymentList = Deployment.objects.all()
341 for entry in deploymentList:
342 deploymentInfo.append({'DeploymentSite':entry.name})
343 return deploymentInfo
345 def getSliceInfo(user):
346 sliceList = Slice.objects.all()
347 slicePrivs = SlicePrivilege.objects.filter(user=user)
349 for entry in slicePrivs:
351 slicename = Slice.objects.get(id=entry.slice.id).name
352 slice = Slice.objects.get(name=Slice.objects.get(id=entry.slice.id).name)
353 sliverList=Sliver.objects.all()
355 for sliver in slice.slivers.all():
356 #sites_used['deploymentSites'] = sliver.node.deployment.name
357 # sites_used[sliver.image.name] = sliver.image.name
358 sites_used[sliver.node.site.name] = sliver.numberCores
359 sliceid = Slice.objects.get(id=entry.slice.id).id
361 sliverList = Sliver.objects.filter(slice=entry.slice.id)
364 if x.node.site not in siteList:
365 siteList[x.node.site] = 1
366 slivercount = len(sliverList)
367 sitecount = len(siteList)
369 traceback.print_exc()
373 userSliceInfo.append({'slicename': slicename, 'sliceid':sliceid,
374 'sitesUsed':sites_used,
375 'role': SliceRole.objects.get(id=entry.role.id).role,
376 'slivercount': slivercount,
377 'sitecount':sitecount})
381 def getCDNContentProviderData():
383 for dm_cp in ContentProvider.objects.all():
384 cp = {"name": dm_cp.name,
385 "account": dm_cp.account}
390 def getCDNOperatorData(randomizeData = False, wait=True):
391 HPC_SLICE_NAME = "HyperCache"
393 bq = PlanetStackAnalytics()
395 rows = bq.get_cached_query_results(bq.compose_cached_query(), wait)
397 # wait=False on the first time the Dashboard is opened. This means we might
398 # not have any rows yet. The dashboard code polls every 30 seconds, so it
399 # will eventually pick them up.
402 rows = bq.postprocess_results(rows, filter={"event": "hpc_heartbeat"}, maxi=["cpu"], count=["hostname"], computed=["bytes_sent/elapsed"], groupBy=["Time","site"], maxDeltaTime=80)
404 # dictionaryize the statistics rows by site name
407 stats_rows[row["site"]] = row
411 slice = Slice.objects.filter(name=HPC_SLICE_NAME)
413 slice_slivers = list(slice[0].slivers.all())
418 for site in Site.objects.all():
419 # compute number of slivers allocated in the data model
420 allocated_slivers = 0
421 for sliver in slice_slivers:
422 if sliver.node.site == site:
423 allocated_slivers = allocated_slivers + 1
425 stats_row = stats_rows.get(site.name,{})
427 max_cpu = stats_row.get("max_avg_cpu", stats_row.get("max_cpu",0))
428 cpu=float(max_cpu)/100.0
429 hotness = max(0.0, ((cpu*RED_LOAD) - BLUE_LOAD)/(RED_LOAD-BLUE_LOAD))
431 # format it to what that CDN Operations View is expecting
432 new_row = {"lat": float(site.location.longitude),
433 "long": float(site.location.longitude),
434 "lat": float(site.location.latitude),
436 "numNodes": int(site.nodes.count()),
437 "activeHPCSlivers": int(stats_row.get("count_hostname", 0)), # measured number of slivers, from bigquery statistics
438 "numHPCSlivers": allocated_slivers, # allocated number of slivers, from data model
439 "siteUrl": str(site.site_url),
440 "bandwidth": stats_row.get("sum_computed_bytes_sent_div_elapsed",0),
442 "hot": float(hotness)}
443 new_rows[str(site.name)] = new_row
445 # get rid of sites with 0 slivers that overlap other sites with >0 slivers
446 for (k,v) in new_rows.items():
448 if v["numHPCSlivers"]==0:
449 for v2 in new_rows.values():
450 if (v!=v2) and (v2["numHPCSlivers"]>=0):
451 d = haversine(v["lat"],v["long"],v2["lat"],v2["long"])
459 def getPageSummary(request):
460 slice = request.GET.get('slice', None)
461 site = request.GET.get('site', None)
462 node = request.GET.get('node', None)
464 class SimulatorView(View):
465 def get(self, request, **kwargs):
466 sim = json.loads(file("/tmp/simulator.json","r").read())
467 text = "<html><head></head><body>"
468 text += "Iteration: %d<br>" % sim["iteration"]
469 text += "Elapsed since report %d<br><br>" % sim["elapsed_since_report"]
470 text += "<table border=1>"
471 text += "<tr><th>site</th><th>trend</th><th>weight</th><th>bytes_sent</th><th>hot</th></tr>"
472 for site in sim["site_load"].values():
474 text += "<td>%s</td><td>%0.2f</td><td>%0.2f</td><td>%d</td><td>%0.2f</td>" % \
475 (site["name"], site["trend"], site["weight"], site["bytes_sent"], site["load_frac"])
478 text += "</body></html>"
479 return HttpResponse(text)
481 class DashboardUserSiteView(View):
482 def get(self, request, **kwargs):
483 return HttpResponse(json.dumps(getDashboardContext(request.user, tableFormat=True)), mimetype='application/javascript')
485 class TenantViewData(View):
486 def get(self, request, **kwargs):
487 return HttpResponse(json.dumps(getTenantSliceInfo(request.user, True)), mimetype='application/javascript')
489 def haversine(site_lat, site_lon, lat, lon):
491 if lat and lon and site_lat and site_lon:
492 site_lat = float(site_lat)
493 site_lon = float(site_lon)
497 a = math.sin( math.radians((lat - site_lat)/2.0) )**2 + math.cos( math.radians(lat) )*math.cos( math.radians(site_lat) )*(math.sin( math.radians((lon - site_lon)/2.0 ) )**2)
498 c = 2 * math.atan2( math.sqrt(a), math.sqrt(1 - a) )
503 def siteSortKey(site, slice=None, count=None, lat=None, lon=None):
504 # try to pick a site we're already using
505 has_slivers_here=False
507 for sliver in slice.slivers.all():
508 if sliver.node.site.name == site.name:
509 has_slivers_here=True
512 d = haversine(site.location.latitude, site.location.longitude, lat, lon)
514 return (-has_slivers_here, d)
516 def tenant_pick_sites(user, user_ip=None, slice=None, count=None):
517 """ Returns list of sites, sorted from most favorable to least favorable """
521 client_geo = GeoIP().city(user_ip)
523 lat=float(client_geo["latitude"])
524 lon=float(client_geo["longitude"])
526 print "exception in geo code"
527 traceback.print_exc()
529 sites = Site.objects.all()
530 sites = [x for x in sites if x.name in BLESSED_SITES]
531 sites = sorted(sites, key=functools.partial(siteSortKey, slice=slice, count=count, lat=lat, lon=lon))
535 def slice_increase_slivers(user, user_ip, siteList, slice, count, noAct=False):
538 # let's compute how many slivers are in use in each node of each site
539 for site in siteList:
540 site.nodeList = list(site.nodes.all())
541 for node in site.nodeList:
543 for sliver in node.slivers.all():
544 if sliver.slice.id == slice.id:
545 node.sliverCount = node.sliverCount + 1
547 # Allocate slivers to nodes
548 # for now, assume we want to allocate all slivers from the same site
549 nodes = siteList[0].nodeList
551 # Sort the node list by number of slivers per node, then pick the
552 # node with the least number of slivers.
553 nodes = sorted(nodes, key=attrgetter("sliverCount"))
556 print "adding sliver at node", node.name, "of site", node.site.name
559 sliver = Sliver(name=node.name,
562 image = Image.objects.all()[0],
563 creator = User.objects.get(email=user),
564 deploymentNetwork=node.deployment,
568 node.sliverCount = node.sliverCount + 1
572 sitesChanged[node.site.name] = sitesChanged.get(node.site.name,0) + 1
576 def slice_decrease_slivers(user, siteList, slice, count, noAct=False):
580 siteNames = [site.name for site in siteList]
584 for sliver in slice.slivers.all():
585 if(not siteNames) or (sliver.node.site.name in siteNames):
\r
587 sliverList[sliver.name]=node.name
589 for key in sliverList:
591 sliver = Sliver.objects.filter(name=key)[0]
\r
593 print "deleting sliver",sliverList[key],"at node",sliver.node.name
\r
595 sitesChanged[sliver.node.site.name] = sitesChanged.get(sliver.node.site.name,0) - 1
\r
599 class TenantDeleteSliceView(View):
600 def post(self,request):
\r
601 sliceName = request.POST.get("sliceName",None)
\r
602 slice = Slice.objects.get(name=sliceName)
\r
603 #print slice, slice.id
\r
604 sliceToDel=Slice(name=sliceName, id=slice.id)
\r
606 return HttpResponse("Slice deleted")
608 class TenantAddOrRemoveSliverView(View):
609 """ Add or remove slivers from a Slice
612 siteName - name of site. If not specified, PlanetStack will pick the
614 actionToDo - [add | rem]
615 count - number of slivers to add or remove
616 sliceName - name of slice
617 noAct - if set, no changes will be made to db, but result will still
618 show which sites would have been modified.
621 Dictionary of sites that were modified, and the count of nodes
622 that were added or removed at each site.
624 def post(self, request, *args, **kwargs):
625 siteName = request.POST.get("siteName", None)
626 actionToDo = request.POST.get("actionToDo", None)
627 count = int(request.POST.get("count","0"))
628 sliceName = request.POST.get("slice", None)
629 noAct = request.POST.get("noAct", False)
632 return HttpResponseServerError("No slice name given")
634 slice = Slice.objects.get(name=sliceName)
637 siteList = [Site.objects.get(name=siteName)]
641 if (actionToDo == "add"):
642 user_ip = request.GET.get("ip", get_ip(request))
643 if (siteList is None):
644 siteList = tenant_pick_sites(user, user_ip, slice, count)
646 sitesChanged = slice_increase_slivers(request.user, user_ip, siteList, slice, count, noAct)
647 elif (actionToDo == "rem"):
648 sitesChanged = slice_decrease_slivers(request.user, siteList, slice, count, noAct)
650 return HttpResponseServerError("Unknown actionToDo %s" % actionToDo)
652 return HttpResponse(json.dumps(sitesChanged), mimetype='application/javascript')
654 def get(self, request, *args, **kwargs):
655 request.POST = request.GET
656 return self.post(request, *args, **kwargs) # for testing REST in browser
657 #return HttpResponseServerError("GET is not supported")
659 class TenantPickSitesView(View):
660 """ primarily just for testing purposes """
661 def get(self, request, *args, **kwargs):
662 count = request.GET.get("count","0")
663 slice = request.GET.get("slice",None)
665 slice = Slice.objects.get(name=slice)
666 ip = request.GET.get("ip", get_ip(request))
667 sites = tenant_pick_sites(request.user, user_ip=ip, count=0, slice=slice)
668 sites = [x.name for x in sites]
669 return HttpResponse(json.dumps(sites), mimetype='application/javascript')
671 class DashboardSummaryAjaxView(View):
672 def get(self, request, **kwargs):
676 return float(sum(x))/len(x)
678 sites = getCDNOperatorData().values()
680 sites = [site for site in sites if site["numHPCSlivers"]>0]
682 total_slivers = sum( [site["numHPCSlivers"] for site in sites] )
683 total_bandwidth = sum( [site["bandwidth"] for site in sites] )
684 average_cpu = int(avg( [site["load"] for site in sites] ))
686 result= {"total_slivers": total_slivers,
687 "total_bandwidth": total_bandwidth,
688 "average_cpu": average_cpu}
690 return HttpResponse(json.dumps(result), mimetype='application/javascript')
692 class DashboardAddOrRemoveSliverView(View):
693 # TODO: deprecate this view in favor of using TenantAddOrRemoveSliverView
695 def post(self, request, *args, **kwargs):
696 siteName = request.POST.get("site", None)
697 actionToDo = request.POST.get("actionToDo", "0")
699 siteList = [Site.objects.get(name=siteName)]
700 slice = Slice.objects.get(name="HyperCache")
702 if (actionToDo == "add"):
703 user_ip = request.GET.get("ip", get_ip(request))
704 slice_increase_slivers(request.user, user_ip, siteList, slice, 1)
705 elif (actionToDo == "rem"):
706 slice_decrease_slivers(request.user, siteList, slice, 1)
709 print 'Ask for site: ' + siteName + ' to ' + actionToDo + ' another HPC Sliver'
710 return HttpResponse('This is POST request ')
712 class DashboardAjaxView(View):
713 def get(self, request, **kwargs):
714 return HttpResponse(json.dumps(getCDNOperatorData(True)), mimetype='application/javascript')
716 class DashboardAnalyticsAjaxView(View):
717 def get(self, request, name="hello_world", **kwargs):
718 if (name == "hpcSummary"):
719 return HttpResponse(json.dumps(hpc_wizard.get_hpc_wizard().get_summary_for_view()), mimetype='application/javascript')
720 elif (name == "hpcUserSite"):
721 return HttpResponse(json.dumps(getDashboardContext(request.user, tableFormat=True)), mimetype='application/javascript')
722 elif (name == "hpcMap"):
723 return HttpResponse(json.dumps(getCDNOperatorData(True)), mimetype='application/javascript')
724 elif (name == "bigquery"):
725 (mimetype, data) = DoPlanetStackAnalytics(request)
726 return HttpResponse(data, mimetype=mimetype)
728 return HttpResponse(json.dumps("Unknown"), mimetype='application/javascript')
730 class DashboardCustomize(View):
731 def post(self, request, *args, **kwargs):
\r
732 dashboards = request.POST.get("dashboards", None)
\r
736 dashboards = [x.strip() for x in dashboards.split(",")]
\r
737 dashboards = [DashboardView.objects.get(name=x) for x in dashboards]
\r
739 request.user.dashboardViews.all().delete()
\r
741 for i,dashboard in enumerate(dashboards):
\r
742 udbv = UserDashboardView(user=request.user, dashboardView=dashboard, order=i)
\r
745 return HttpResponse("updated")
\r