From 574ab14b3b3daf0f407dcc2a73a54ad5f87af372 Mon Sep 17 00:00:00 2001 From: Ciro Scognamiglio Date: Mon, 24 Feb 2014 18:51:33 +0100 Subject: [PATCH] rest module, home view shows the list of slices --- myslice/urls.py | 11 ++ portal/static/css/onelab.css | 25 ++++ portal/templates/onelab/onelab_home-view.html | 17 ++- rest/__init__.py | 110 ++++++++++++++++++ rest/get.py | 63 ++++++++++ rest/platform.py | 42 +++++++ rest/urls.py | 8 ++ ui/static/img/icon_authority_color_small.png | Bin 0 -> 6649 bytes ui/static/img/loading.gif | Bin 0 -> 1279 bytes 9 files changed, 274 insertions(+), 2 deletions(-) create mode 100644 rest/__init__.py create mode 100644 rest/get.py create mode 100644 rest/platform.py create mode 100644 rest/urls.py create mode 100644 ui/static/img/icon_authority_color_small.png create mode 100644 ui/static/img/loading.gif diff --git a/myslice/urls.py b/myslice/urls.py index 5e1547bd..1eac188f 100644 --- a/myslice/urls.py +++ b/myslice/urls.py @@ -55,6 +55,17 @@ urls = [ # the manifold proxy (r'^manifold/proxy/(?P\w+)/?$', 'manifold.manifoldproxy.proxy'), # + # + # RESTful interface + (r'^rest/(?P[^/]+)/(?P[^/]+)?/?$', 'rest.dispatch'), + (r'^datatable/(?P[^/]+)/(?P[^/]+)?/?$', 'rest.dispatch'), + # + # + #(r'^view/?', include('view.urls')), + #(r'^list/slices', 'view.list.slices'), + #(r'^list/(?P[^/]+)', 'view.list.default'), + # + # # Portal url(r'^portal/', include('portal.urls')), ] diff --git a/portal/static/css/onelab.css b/portal/static/css/onelab.css index e2f62164..dc8932ce 100644 --- a/portal/static/css/onelab.css +++ b/portal/static/css/onelab.css @@ -177,6 +177,20 @@ div#home-dashboard div#manager { display:none; } +div#home-dashboard div#home-slice-list { + margin:25px 0; + padding:0 25px; + text-align:left; +} +div#home-dashboard div#home-slice-list ul { + list-style: none; + padding:0; + margin:0; +} +div#home-dashboard div#home-slice-list li { + +} + .login-submit { vertical-align:middle; padding:0; @@ -227,6 +241,17 @@ div.well { } +/* INSTITUTION */ +div#institution { + color:black; + margin:25px auto; +} +.form-hint { + font-size:11pt; + font-style:italic; + color:gray; +} + /* SLICE REQUEST */ div#slice-request { color:black; diff --git a/portal/templates/onelab/onelab_home-view.html b/portal/templates/onelab/onelab_home-view.html index 80e040cc..6777eb33 100644 --- a/portal/templates/onelab/onelab_home-view.html +++ b/portal/templates/onelab/onelab_home-view.html @@ -36,6 +36,7 @@ {% if person %} +
Loading Slices
{% else %} {% endif %} @@ -93,10 +94,22 @@ }); $('button#ticketbtn').click(function() { window.location="/portal/contact/"; - }) - ;$('button#slicerequestbtn').click(function() { + }); + $('button#slicerequestbtn').click(function() { window.location="/portal/slice_request/"; }); + + {% if person %} + $.getJSON("rest/user", function( data ) { + var items = []; + $.each( data[0].slices, function( key, val ) { + items.push( "
  • " + val + "
  • " ); + }); + + $("div#home-slice-list").html($( "
      ", { html: items.join( "" ) })); + + }); + {% endif %} }); {% endblock unfold_main %} diff --git a/rest/__init__.py b/rest/__init__.py new file mode 100644 index 00000000..892e4ca5 --- /dev/null +++ b/rest/__init__.py @@ -0,0 +1,110 @@ +from manifold.core.query import Query + +from django.views.generic.base import TemplateView + +from unfold.loginrequired import LoginRequiredView +from django.http import HttpResponse + +from manifold.core.query import Query, AnalyzedQuery +from manifold.manifoldapi import execute_query + +from string import join +import json + + +def dispatch(request, object_type, object_name): + + switch = { + 'platform' : platform, + 'slice' : slice, + 'user' : user + } + + # platform is local + if (object_type == 'platform') : + object_type = 'local:platform' + object_properties = ['platform', 'platform_longname', 'platform_url', 'platform_description','gateway_type']; + else : + query = Query.get('local:object').filter_by('table', '==', object_type).select('column.name') + results = execute_query(request, query) + if results : + object_properties = [] + for r in results[0]['column'] : + object_properties.append(r['name']) + else : + return error() + + return switch.get(object_type, error)(request, object_type, object_name, object_properties) + +# if request.method == 'GET': +# return switch.get(request, object_type, object_name, object_properties) +# elif request.method == 'POST': +# return post(request, object_type, object_name) + +def platform(): + return HttpResponse(json.dumps({'user' : 'error message'}), content_type="application/json") + +def slice(): + return HttpResponse(json.dumps({'user' : 'error message'}), content_type="application/json") + +def user(request, object_type, object_name, object_properties): + query = Query().get('user').filter_by('user_hrn', '==', '$user_hrn').select(object_properties) + #.select('slice.slice_hrn') + + return send(request, execute_query(request, query), object_properties) + +def send(request, response, object_properties): + if request.path.split('/')[1] == 'rest' : + response_data = response + else : + response_data = {} + response_data['columns'] = object_properties + response_data['labels'] = object_properties + #response_data['labels'] = [ 'Platform', 'Name', 'Url', 'Description','Gateway Type' ] + response_data['data'] = [] + response_data['total'] = len(response) + for r in response : + d = [] + for p in object_properties : + d.append(r[p]) + print d + + response_data['data'].append(d) + #response_data['data'].append([ r['platform'], r['platform_longname'], r['platform_url'], r['platform_description'], r['gateway_type'] ]) + + return HttpResponse(json.dumps(response_data), content_type="application/json") + + + + + +def get(request, object_type, object_name, object_properties): + + query = Query().get(object_type).select(object_properties).filter_by('user_hrn', '=', '$user_hrn') + if (object_name) : + query = query.filter_by(object_type + '_hrn', '=', object_name) + + response = execute_query(request, query) + + response_data = {} + response_data['columns'] = object_properties + response_data['labels'] = object_properties + #response_data['labels'] = [ 'Platform', 'Name', 'Url', 'Description','Gateway Type' ] + response_data['data'] = [] + response_data['total'] = len(response) + for r in response : + d = [] + for p in object_properties : + d.append(r[p]) + print d + + response_data['data'].append(d) + #response_data['data'].append([ r['platform'], r['platform_longname'], r['platform_url'], r['platform_description'], r['gateway_type'] ]) + + return HttpResponse(json.dumps(response_data), content_type="application/json") + +def post(request, object_type, object_name): + pass + +def error(): + return HttpResponse(json.dumps({'error' : 'error message'}), content_type="application/json") \ No newline at end of file diff --git a/rest/get.py b/rest/get.py new file mode 100644 index 00000000..c72b4e4d --- /dev/null +++ b/rest/get.py @@ -0,0 +1,63 @@ +from manifold.core.query import Query, AnalyzedQuery + +from django.views.generic.base import TemplateView + +from unfold.loginrequired import LoginRequiredView +from django.http import HttpResponse + +from manifold.core.query import Query, AnalyzedQuery +from manifold.manifoldapi import execute_query + +import json + +def platform(request, platform_name): + + platform_query = Query().get('local:platform').filter_by('disabled', '==', '0').select('platform', 'platform_longname', 'platform_url', 'platform_description','gateway_type') + response = execute_query(request,platform_query) + + response_data = {} + response_data['columns'] = [ 'platform', 'platform_longname', 'platform_url', 'platform_description','gateway_type' ] + response_data['labels'] = [ 'Platform', 'Name', 'Url', 'Description','Gateway Type' ] + response_data['data'] = [] + for r in response : + response_data['data'].append([ r['platform'], r['platform_longname'], r['platform_url'], r['platform_description'], r['gateway_type'] ]) + + return HttpResponse(json.dumps(response_data), content_type="application/json") + +def slice(request, slice_name): + + platform_query = Query().get('local:platform').filter_by('disabled', '==', '0').select('platform', 'platform_longname', 'platform_url', 'platform_description','gateway_type') + response = execute_query(request,platform_query) + + + + response_data = {} + response_data['columns'] = [ 'platform', 'platform_longname', 'platform_url', 'platform_description','gateway_type' ] + response_data['labels'] = [ 'Platform', 'Name', 'Url', 'Description','Gateway Type' ] + response_data['data'] = [] + for r in response : + response_data['data'].append([ r['platform'], r['platform_longname'], r['platform_url'], r['platform_description'], r['gateway_type'] ]) + + return HttpResponse(json.dumps(response_data), content_type="application/json") + +# slicename = 'ple.upmc.myslicedemo' +# main_query = Query.get('slice').filter_by('slice_hrn', '=', slicename) +# main_query.select( +# 'slice_hrn', +# 'resource.hrn', 'resource.urn', +# 'resource.hostname', 'resource.type', +# 'resource.network_hrn', +# 'lease.urn', +# 'user.user_hrn', +# #'application.measurement_point.counter' +# ) +# +# res = execute_query(self.request,main_query) +# +# print res +# +# return render(request, self.template_name, {"resources": res[0]['resource']}) + + +# def get (self, request, name='default'): +# return HttpResponse() \ No newline at end of file diff --git a/rest/platform.py b/rest/platform.py new file mode 100644 index 00000000..87792a6d --- /dev/null +++ b/rest/platform.py @@ -0,0 +1,42 @@ +from manifold.core.query import Query, AnalyzedQuery + +from django.views.generic.base import TemplateView + +from unfold.loginrequired import LoginRequiredView +from django.http import HttpResponse + +from manifold.core.query import Query, AnalyzedQuery +from manifold.manifoldapi import execute_query + +import json + + +def dispatch(request, platform_name): + return get(request, platform_name) + +def get(request, platform_name): + + platform_query = Query().get('local:platform').filter_by('disabled', '==', '0').select('platform', 'platform_longname', 'platform_url', 'platform_description','gateway_type') + response = execute_query(request,platform_query) + + q = Query.get('slice').select('slice_hrn') + result = execute_query(request,q) + print result +# # +# query = Query.get('local:object').select('table') +# results = execute_query(request, query) +# print results +# + query = Query.get('local:object').filter_by('table', '==', 'slice').select('column.name') + results = execute_query(request, query) + print results + + response_data = {} + response_data['columns'] = [ 'platform', 'platform_longname', 'platform_url', 'platform_description','gateway_type' ] + response_data['labels'] = [ 'Platform', 'Name', 'Url', 'Description','Gateway Type' ] + response_data['data'] = [] + #response_data['resources'] = { 'total' : len(r) } + for r in response : + response_data['data'].append([ r['platform'], r['platform_longname'], r['platform_url'], r['platform_description'], r['gateway_type'] ]) + + return HttpResponse(json.dumps(response_data), content_type="application/json") \ No newline at end of file diff --git a/rest/urls.py b/rest/urls.py new file mode 100644 index 00000000..55d364d2 --- /dev/null +++ b/rest/urls.py @@ -0,0 +1,8 @@ + +from django.conf.urls import patterns, include, url +from django.conf import settings + + +urlpatterns = patterns('', + url(r'^rest/get/slice/(?P[^/]+)/?$', 'rest.get.slice') +) diff --git a/ui/static/img/icon_authority_color_small.png b/ui/static/img/icon_authority_color_small.png new file mode 100644 index 0000000000000000000000000000000000000000..b38ac5f796359c143e545f0541d5f2f4c374d826 GIT binary patch literal 6649 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 D=g0|T literal 0 HcmV?d00001 diff --git a/ui/static/img/loading.gif b/ui/static/img/loading.gif new file mode 100644 index 0000000000000000000000000000000000000000..e3e22a3e9013ca14b5517ecde3be3f4f61fb41c5 GIT binary patch literal 1279 zcma*m|1aZr90%}EwP>l&eSED}>)NU-+N#f`5f^^o^LeA6qSM_STXvW2q&DsZRo^1A zql`}-y;Ak-SX_ifh(sb0UAq0Y_+hU<;PrgGUa!2; ziL`_}j{=|oXbXZ|F4yJd<;BIt`T6GARL(b3Vt!NLCi{@&i6)9KvZ-QC&Q z+1}pX+}zyQ*jQg*Ut3#qI2^01tINyFi;Ig33k!2|bF;IvGcz;O)6)|Z6XWCKcDsFW zaImkhue-auy}jLJGLa-{Fc|9U>S}6gs;a7VI$c3QL4JOIZf>qxtr0yo}F(I#~OFgAg03jV(sqUVS@AIF` zcZz1ED;Q1qFg{Ek+Ewu0C$CX2v}rgTiZErX@(-io?NI6GN#QgOx4}=%k&#j++*+&h z<+XFFI#JKBlvHn(w;+s&i1tzT3t=pXfd$44gPWsx6bqje111xE2N0xc!HVAsQm0&J zDsh_{`J_Xe*^vzgh6?qe2~3h<`ZA%-6ERA3L&4!ceu0*0QaqTC$7;v3xgRnJspv4> z#)~t^{R5k-)ch7+b(;x9@(Hn26)Ap-vT&Bn3Pe4)gl&^a01Sf>(4?Pwjz+&wU2781l87|jElg+&R@fxyaBv!oonUNAq~yt1ZLAo{i?&|v0dR$|*bRC{6TRH1 zf+%XKnDC;vDW$SnG#sZgEI`tY#}#9?0z5Elc*IWCziPqdyz2=?Zh}HXI<(9RO~6Os z!^z1b{{7cOib}e1a)pso9aO`6_e<0dDTz+U&(v@U7e;fQe5<>iP{-l$8cRH3(y#nk zE0D(!euzpQ$Hr(Ln3ul=$hvR_4Y%p>kPz^y4V#>zk6eENog0w((7gkz1{O<%7G)VC zmTi~DNc$&OGIFZ_8W6r)i)LVnPNk6+o){H0!zyy>(mf@vpRd_W#yIgMyQ^Da!VLcZLpLbzM$99Jc?Q01XYkmfcOz%0&fEG z_rm