Cloud plugin includes Fuseco
[myslice.git] / portal / templates / slice-tab-cloud.html
1 {% extends "layout_wide.html" %}
2 {% load portal_filters %}
3
4 {% block head %}
5 <style>
6 .disabled {
7     z-index: 1000;
8     background-color: #999999;
9 opacity: 0.3;
10          pointer-events: none;
11 }
12 </style>
13 <script type="text/javascript">
14
15 var global_list = {};
16 var data = Array(); 
17 var ad_rspec = Object(); 
18 var request_rspec = Object();
19 var manifest_rspec = Object(); 
20 var deleted_nodes = {}; 
21 var added_nodes = {}; 
22 var len_platforms = {{len_platforms}};
23
24 var key_sliver = '';
25 var key_slivername = '';
26 var key_image = '';
27
28 /* render_flavor & render_image */
29 function render_option(obj){
30     var option = document.createElement("option");
31     if(obj instanceof Object){
32         obj_name = obj["@name"];
33     }else{
34         obj_name = obj;
35     }
36     option.text = obj_name;
37     option.value = obj_name;
38     return option;
39 }
40 function render_description(platform, obj, type, node_name){
41     if($('#'+platform+'_'+type).length==0){
42         $('#'+platform+'_add_'+node_name).append("<div id='"+platform+"_"+type+"'>");
43     }
44     if(obj instanceof Array){
45         obj_name = obj["@name"];
46     }else{
47         obj_name = obj;
48     }
49     var d = platform+'_'+type+'_'+obj_name;
50     d = d.replace(/ /g, '');
51     id = d.replace( /(:|\.|\[|\])/g, "\\$1" );
52     if($('#'+id).length==0){
53         $('#'+platform+'_'+type).append("<div id='"+d+"' class='alert-success col-md-5' style='margin-top:10px;display:none;border-style:solid;border-color:#f1f1f1;border-width:2px;'></div>");
54     }
55     if($('#'+id+' div').length==0){
56         if(obj instanceof Array){
57              jQuery.each(obj, function(key,val){
58                     if (key != 'openstack:image' && key != 'disk_image'){
59                         $('#'+id).append("<div>"+key.replace('@','')+": "+val+"</div>");
60                     }
61             });
62         }else{
63             // What do we do?
64         }
65     }
66 }
67 function toogle_div(platform, value, type, node_name){
68     $("#"+platform+"_add_"+node_name).show();
69     if ( $("#"+platform+"_selectImage_"+node_name).length ) {
70         // show the add button only if image is selected
71         if($("#"+platform+"_selectImage_"+node_name).val()!=null && $("#"+platform+"_selectImage").val()!=0 && value!=0){
72             $("#"+platform+"_add_button_"+node_name).show();
73         }else{
74             $("#"+platform+"_add_button_"+node_name).hide();
75         }
76     }else{
77         $("#"+platform+"_add_button_"+node_name).show();
78     }
79     $("[id^='"+platform+"_"+type+"_"+"']").hide();
80     d = platform+'_'+type+'_'+value;
81     d = d.replace(/ /g, '');
82     id = d.replace( /(:|\.|\[|\])/g, "\\$1" );
83     $('#'+id).show();
84 }
85 function render_cloud(platform, node){
86     console.log(node);
87     if ('@component_name' in node){
88         node_name = node['@component_name']
89     }else{
90         node_name = node['@component_id'].split('+').pop();
91     }
92     elm = document.getElementById(platform+'_select_'+node_name);
93     //newElement = document.createElement('p');
94     //elm.appendChild(newElement); 
95     global_list[platform][node_name]={};
96     if('openstack:sliver' in node){
97         key_sliver = 'openstack:sliver';
98         key_slivername = '@sliver_name';
99     }else if('sliver_type' in node){
100         key_sliver = 'sliver_type';
101         key_slivername = '@name';
102     }
103     if (key_sliver != ''){
104         selectFlavor = document.createElement('select');
105         selectFlavor.id = platform+"_selectFlavor_-_"+node_name;
106         selectFlavor.name = platform+"_selectFlavor_-_"+node_name;
107         selectFlavor.onchange = function(){
108             /* 1) Display corresponding Flavor div - hide others - reset selectImage value */
109             name = this.id.split("_-_").pop();
110             toogle_div(platform, this.value, 'flavor', name);
111             if ( $("#"+platform+"_selectImage_"+name).length ) {
112                 $("#"+platform+"_selectImage_"+name+" option[value=0]").prop('selected', true);
113                 /* 3) Disable Images, Enable only compatible ones in selectImage */
114                 $("[id^='"+platform+"_image_"+name+"']").hide();
115                 $("#"+platform+"_selectImage_"+name+" option").attr("disabled",true);
116                 $.each(global_list[platform][name][this.value], function (i,v){
117                     $("#"+platform+"_selectImage_"+name+" option[value='" + v + "']").attr("disabled",false);
118                 });
119                 $("#"+platform+"_selectImage_"+name).attr("disabled",false);
120             }
121         }
122         var option = document.createElement("option");
123         option.text = "-- select a flavor --";
124         option.value = 0;
125         selectFlavor.appendChild(option);
126         jQuery.each( node[key_sliver], function( i, sliver ) {
127                 flavor = get_flavor(sliver);
128                 f = render_option(flavor);
129                 selectFlavor.appendChild(f);
130                 /* 1) create hidden div to explain caracteristics of the flavor */
131                 render_description(platform, flavor, 'flavor', node_name);
132                 if(flavor instanceof Object){
133                     flavor_name = flavor['@name'];
134                 }else{
135                     flavor_name = flavor;
136                 }
137                 global_list[platform][node_name][flavor_name]=[];
138                 if(flavor instanceof Object){
139                     if ("openstack:image" in flavor){
140                         images = flavor['openstack:image'];
141                         key_image = 'openstack:image';
142                     }else if("disk_image" in flavor){
143                         images = flavor['disk_image'];
144                         key_image = 'disk_image';
145                     }else{
146                         key_image = false;
147                     }
148                 }else{
149                     key_image = false;
150                 }
151                 if(key_image){
152                     selectImage = document.createElement('select');
153                     selectImage.id = platform+"_selectImage_"+node_name;
154                     selectImage.name = platform+"_selectImage_"+node_name;
155                     selectImage.onchange = function(){
156                         /* 2) display corresponding Image div - hide others */
157                         toogle_div(platform, this.value, 'image', node_name);
158                     }
159                     var option = document.createElement("option");
160                     option.text = "-- select an image --";
161                     option.value = 0;
162                     selectImage.appendChild(option);
163                     if(images instanceof Array){
164                         jQuery.each( images, function( i, img ) {
165                                 image = render_option(img);
166                                 image.disabled = true;
167                                 selectImage.appendChild(image);
168                                 /* 2) create hidden div to explain caracteristics of the image */
169                                 render_description(platform, img, 'image', node_name);
170                                 global_list[platform][node_name][flavor_name].push(img['@name']);
171                         });
172                     }else{
173                         image = render_option(images);
174                         image.disabled = true;
175                         selectImage.appendChild(image);
176                         /* 2) create hidden div to explain caracteristics of the image */
177                         render_description(platform, images, 'image', node_name);
178                         global_list[platform][node_name][flavor_name].push(images['@name']);
179                     }
180                 }
181         });
182         elm.appendChild(selectFlavor); 
183         if(key_image){
184             elm.appendChild(selectImage); 
185         }
186     }
187     $("#"+platform+"_selectFlavor_-_"+node_name).css("width","100px");
188     $("#"+platform+"_selectFlavor_-_"+node_name).css("height","30px");
189     if(key_image){
190         $("#"+platform+"_selectImage_"+node_name).css("width","100px");
191         $("#"+platform+"_selectImage_"+node_name).css("height","30px");
192     }
193 }
194 function get_flavor(sliver){
195     if(typeof sliver === 'string' || sliver instanceof String){
196         return sliver
197     } else if('openstack:flavor' in sliver){
198         return sliver['openstack:flavor'];
199     }else{
200         return sliver;
201     }
202 }
203 function is_finished(len_platforms, pf_status){
204     if(len_platforms == pf_status){
205         return true;
206     }else{
207         return false;
208     }
209 }
210 function sliver_name_exists(sliver_name){
211     if(sliver_name in added_nodes){
212         return true;
213     }
214     /*
215        if (sliver_name in deleted_nodes){
216        return true;
217        }
218      */
219     return false;
220 }
221 function find_sliver_name(sliver_name, num){
222     for (j=Object.keys(added_nodes).length; j<Object.keys(added_nodes).length+num; i++){
223         vm_name = sliver_name+'_'+j;
224         if(!sliver_name_exists(vm_name)){
225             return vm_name
226         }
227     }
228 }
229
230 function send_add(platform, node_name){ 
231     $('#'+platform+'_pending_add').show();
232     sliver_name = $('#'+platform+'_sliver_name_'+node_name).val();
233     sliver_name = sliver_name.replace(' ','_');
234     flavor_name = $('#'+platform+'_selectFlavor_-_'+node_name).val();
235     image_name = $('#'+platform+'_selectImage_'+node_name).val();
236
237     num = $('#'+platform+'_number_'+node_name).val();
238     if (num > 1){
239         for (i = 0; i < num; i++){
240             // XXX Check if the name already exist in existing VMs and added_nodes
241             vm_name = sliver_name+"_"+i;
242             if (sliver_name_exists(vm_name)){
243                 vm_name = find_sliver_name(sliver_name, num);
244             }
245             node = get_node(vm_name, flavor_name, image_name, node_name);
246             add_to_request_rspec(node);
247             render_node(platform, node, 'pending_add');
248             added_nodes[vm_name]=node;
249         }
250     }else{
251         if (sliver_name_exists(sliver_name)){
252             sliver_name = find_sliver_name(sliver_name, num);
253         }
254         node = get_node(sliver_name, flavor_name, image_name, node_name);
255         add_to_request_rspec(node);
256         render_node(platform, node, 'pending_add');
257         added_nodes[sliver_name]=node;
258     }
259     toogle_div(platform, flavor_name, 'flavor', node_name);
260     toogle_div(platform, image_name, 'image', node_name);
261     flavor_name = $('#'+platform+'_selectFlavor_-_'+node_name).val(0);
262     image_name = $('#'+platform+'_selectImage_'+node_name).val(0);
263     $('#'+platform+'_add_'+node_name).hide();
264     $('#'+platform+'_add_button_'+node_name).hide();
265     $('#'+platform+'_div_pending').show();
266     console.log(request_rspec);
267     //jQuery('#'+platform+'_form_delete').submit();
268 }
269 function get_node(vm_name, flavor_name, image_name, node_name){
270     var node = {};
271     if(ad_rspec['rspec']['node'] instanceof Array) {
272         // Deep copy of the Array to avoid reference
273         var i=0;
274         jQuery.each(ad_rspec['rspec']['node'], function(x, n){
275             if(n['@component_name']==node_name){
276                 i = x;
277                 return;
278             }
279         });
280         node = jQuery.extend(true, {}, ad_rspec['rspec']['node'][i]);
281     }else{
282         // Deep copy of the Array to avoid reference
283         node = jQuery.extend(true, {}, ad_rspec['rspec']['node']);
284     }
285     if(node[key_sliver] instanceof Array) {
286         node[key_sliver] = node[key_sliver][0];
287     }
288     node[key_sliver][key_slivername] = vm_name;
289     node["@client_id"] = vm_name;
290     console.log(vm_name);
291
292     flavor = Array();
293
294     if('openstack:flavor' in node[key_sliver]){
295         flavor = {'@name':flavor_name,'openstack:image':{'@name':image_name}};
296         node[key_sliver]['openstack:flavor']=flavor;
297     }else{
298         if(typeof image_name === "undefined"){
299             flavor = {'@name':flavor_name};
300         }else{
301             flavor = {'@name':flavor_name,'disk_image':{'@name':image_name}};
302         }
303         node[key_sliver]=flavor;
304     }
305
306     if('openstack:security_group' in node[key_sliver]){
307         $.each(node[key_sliver]['openstack:security_group'], function(i, group){
308                 if(group['@name']=='default'){
309                 node[key_sliver]['openstack:security_group'] = group;
310                 return false;
311                 }
312                 });
313     }
314     return node;
315 }
316 function send_delete(platform, sliver_name){
317     $('#'+platform+'_pending_delete').show();
318     jQuery('#'+platform+'_vm').val(sliver_name);
319     jQuery('#'+platform+'_existing_'+sliver_name).hide();
320     node = remove_node_from_request_rspec(sliver_name);
321     deleted_nodes[sliver_name]=node;
322     render_node(platform, node, 'pending_delete');
323     $('#'+platform+'_div_pending').show();
324     console.log(request_rspec);
325 }
326 function add_to_request_rspec(node){
327     if(request_rspec['rspec']['node'] instanceof Array) {
328         request_rspec['rspec']['node'].push(node);
329     }else{
330         n = request_rspec['rspec']['node'];
331         request_rspec['rspec']['node'] = Array();
332         request_rspec['rspec']['node'].push(n);
333         request_rspec['rspec']['node'].push(node);
334     }
335 }
336 function remove_node_from_request_rspec(sliver_name){
337     var save_node = Array();
338     jQuery.each( request_rspec['rspec']['node'], function( i, node ) {
339             if(node[key_sliver][key_slivername]==sliver_name){
340             request_rspec['rspec']['node'].splice(i,1);
341             save_node = node;
342             return false;
343             }
344             });
345     return save_node;
346 }
347 function cancel_add(platform,sliver_name){
348     // remove the canceled node from the pending list
349     $('#'+platform+'_pending_add_'+sliver_name).remove();
350     // remove the canceled node from the request rspec
351     node = remove_node_from_request_rspec(sliver_name);
352     delete added_nodes[sliver_name];
353     // hide the pending div if there are no more nodes
354     if ($('#'+platform+'_pending_add div').length==0){
355         $('#'+platform+'_pending_add').hide();
356         if ($('#'+platform+'_pending_delete div').length==0){
357             $('#'+platform+'_div_pending').hide();
358             $('#'+platform+'_pending_delete').hide();
359         }
360     }
361 }
362 function cancel_delete(platform,sliver_name){
363     // display the canceled node back in existing nodes
364     $('#'+platform+'_existing_'+sliver_name).show();
365     // remove the canceled node from the pending list
366     $('#'+platform+'_pending_delete_'+sliver_name).remove();
367     // Cancel the deletion of an existing node -> push it back to the request rspec
368     request_rspec['rspec']['node'].push(deleted_nodes[sliver_name]);
369     delete deleted_nodes[sliver_name];
370     // hide the pending div if there are no more nodes
371     if ($('#'+platform+'_pending_delete div').length==0){
372         $('#'+platform+'_pending_delete').hide();
373         if ($('#'+platform+'_pending_add div').length==0){
374             $('#'+platform+'_div_pending').hide();
375             $('#'+platform+'_pending_add').hide();
376         }
377     }
378 }
379
380 function public_ip(platform,sliver_name,is_public){
381     // XXX Change ip status in request_rspec
382     if(is_public){
383         // external_ip="true"
384         node = remove_node_from_request_rspec(sliver_name);
385         node['external_ip']="true";
386         added_nodes[sliver_name]['external_ip']="true";
387         request_rspec['rspec']['node'].push(node);        
388     }else{
389         console.log(sliver_name+' NOT public ');
390         // external_ip="false"
391         node = remove_node_from_request_rspec(sliver_name);
392         node['external_ip']="false";
393         added_nodes[sliver_name]['external_ip']="false";
394         request_rspec['rspec']['node'].push(node);        
395     }
396 }
397
398 function render_node(platform, node, state){
399     if(key_sliver in node){
400         sliver = node[key_sliver];
401         if(sliver instanceof Array){
402             client_id = sliver['@name'];
403         }else{
404             client_id = node['@client_id'];
405         }
406         if ('@component_name' in node){
407             node_name = node['@component_name']
408         }else{
409             node_name = node['@component_id'].split('+').pop();
410         }
411
412         var d = platform+'_'+state+'_'+client_id;
413         d = d.replace(/ /g, '');
414         id = d.replace( /(:|\.|\[|\])/g, "\\$1" );
415         $("#"+platform+"_"+state).append("<div id='"+platform+'_'+state+'_'+client_id+"' class='row'></div>");
416         //$("#"+id).append("<input type='hidden' name='"+platform+"_"+client_id+"' value='"+client_id+"'>");
417         if(state=='pending_add'){
418             $("#"+id).append("<div class='col-md-1' style='width:40px;margin-left:0px;!important;'><input type='checkbox' id='publicip_"+platform+"_"+client_id+"' onclick=public_ip('"+platform+"','"+client_id+"',this.checked);></div>");
419         }else{
420             $("#"+id).append("<div class='col-md-1' style='margin-left:0px;!important;'>&nbsp;</div>");
421         }
422         $("#"+id).append("<div class='col-md-2' style='padding-left:0px;'>"+node_name+"</div>");
423         $("#"+id).append("<div class='col-md-2' style='padding-left:0px;'>"+client_id+"</div>");
424         if('openstack:flavor' in node[key_sliver]){
425             $("#"+id).append("<div class='col-md-2'>"+sliver['openstack:flavor']['@name']+"</div>");
426             $("#"+id).append("<div class='col-md-3'>"+sliver['openstack:flavor']['openstack:image']['@name']+"</div>");
427         }else{
428             $("#"+id).append("<div class='col-md-2'>"+sliver['@name']+"</div>");
429             if('disk_image' in sliver){
430                 $("#"+id).append("<div class='col-md-3'>"+sliver['disk_image']['@name']+"</div>");
431             }else{
432                 $("#"+id).append("<div class='col-md-3'>&nbsp;</div>");
433             }
434         }
435
436         if(state=='existing'){
437             $("#"+id).append("<div class='col-md-1'><input id='"+platform+"_delete_"+client_id+"' type='submit' form='"+platform+"_form_delete' value='Delete' onclick=send_delete('"+platform+"','"+client_id+"');></div>");
438         }else if(state=='pending_add'){
439             $("#"+id).append("<div class='col-md-1'><input id='"+platform+"_cancel_"+client_id+"' type='submit' form='"+platform+"_form_cancel' value='Cancel' onclick=cancel_add('"+platform+"','"+client_id+"');></div>");
440         }else if (state=='pending_delete'){
441             $("#"+id).append("<div class='col-md-1'><input id='"+platform+"_cancel_"+client_id+"' type='submit' form='"+platform+"_form_cancel' value='Cancel' onclick=cancel_delete('"+platform+"','"+client_id+"');></div>");
442         }else{
443             console.log("state: "+state+" not impemented");
444         }
445         /*
446            sliver['openstack:address']
447            sliver['openstack:flavor']
448          */
449     }
450 }
451
452 function allocate(platform, slicename){
453     $("#"+platform+"_main").addClass("disabled");
454     $("#"+platform+"_wait").show();
455     $("#"+platform+"_wait").spin();
456     console.log("allocate rspec = ")
457     console.log(request_rspec);
458     console.log("allocate json = ")
459     console.log(JSON.stringify(request_rspec));
460     $.post("/sfa/Allocate",{'hrn':slicename, 'type':'slice', 'platform':[platform], 'rspec':JSON.stringify(request_rspec)}, function( result ) {
461             console.log(result);
462             clear_data(platform);
463             load_data(platform,slicename);
464             $("#"+platform+"_main").removeClass("disabled");
465             $("#"+platform+"_wait").hide();
466             if('error' in result[platform]){
467                 mysliceAlert('Error: '+result[platform]['error_msg'],'danger', true);
468             }else if('code' in result[platform] && result[platform]['code']['am_code']==-1){
469                 mysliceAlert('Error: '+result[platform]['output'],'danger', true);
470             }else{
471                 mysliceAlert('Success: changes applied','success', true);
472             }
473     });
474 }
475 function clear_data(platform){
476     $('#'+platform+'_existing').children().remove();
477     $('#'+platform+'_pending_delete').children().remove();
478     $('#'+platform+'_pending_delete').hide();
479     $("[id^="+platform+"_selectFlavor]").remove();
480     $("[id^="+platform+"_selectImage]").remove();
481     $('#'+platform+'_pending_add').children().remove();
482     $('#'+platform+'_pending_add').hide();
483     $('#'+platform+'_div_pending').hide();
484 }
485 function load_data(platform, slicename){
486     var platform_status = Array();
487     var platform_empty = Array();
488
489     $.post("/sfa/Describe",{'hrn':slicename, 'type':'slice', 'platform':[platform]}, function( d ) {
490        console.log(data);
491        $("#"+platform+"_main").spin(false);
492        data = d;
493        if('parsed' in data[platform] && 'rspec' in data[platform]['parsed']){
494            manifest_rspec = data[platform]['parsed']['rspec'];
495            request_rspec = data[platform]['parsed'];
496            request_rspec['rspec']['@type']='request';
497            if('node' in manifest_rspec){
498                if(manifest_rspec['node'] instanceof Array) {
499                    jQuery.each( manifest_rspec['node'], function( i, node ) {
500                        render_node(platform, node, 'existing');
501                    });
502                }else{
503                    render_node(platform, manifest_rspec['node'], 'existing');
504                }
505                $('#'+platform+'_existing').show();
506            }
507        }else{
508            // Let's build a default request_rspec
509            request_rspec['rspec']=Object();
510            request_rspec['rspec']['@type']='request';
511            request_rspec['rspec']['node']=Array();
512        }
513     });
514     $.post("/sfa/ListResources",{'platform':[platform]}, function( d ) {
515             //$.extend(data,d);
516             console.log(data);
517             global_list[platform]={};
518             if('parsed' in d[platform] && 'rspec' in d[platform]['parsed']){
519             ad_rspec = d[platform]['parsed'];
520             if('node' in ad_rspec['rspec']){
521             if(ad_rspec['rspec']['node'] instanceof Array) {
522             jQuery.each( ad_rspec['rspec']['node'], function( i, node ) {
523                 render_cloud(platform,node);
524                 });
525             }else{
526             render_cloud(platform,ad_rspec['rspec']['node']);
527             }
528             }else{
529             platform_empty.push(platform);
530             }
531             }else{
532             platform_empty.push(platform);
533             }
534             platform_status.push(platform);
535             if(is_finished(len_platforms,platform_status.length)){
536                 $("#loading").hide();
537                 if(platform_empty.length == len_platforms){
538                     $("#warning_message").show();
539                 }
540             }
541     });
542 }
543 $(document).ready(function() {
544         {% for platform in platforms %}
545         {% if platform in cloud_platforms %}
546         $("#{{platform}}_main").spin();
547         load_data('{{platform}}', '{{slicename}}');
548         {% endif %}
549         {% endfor %}
550         });
551 </script>
552 {% endblock %}
553
554 {% block content %}
555 {{post_values}}
556 {% for platform in platforms %}
557     {% if platform in cloud_platforms %}
558
559     <div id="{{platform}}_wait" style="display:none;margin-top:35px;position:absolute;margin-left:50%;"></div>
560     <div id="{{platform}}_main" style="padding-left:20px;padding-top:20px;padding-right:20px;padding-bottom:20px;border-style:solid;border-width:1px;">
561         <h2>{{ platform }}</h2>
562         // display only if VMs already in slice
563         <h4>VMs in slice {{slicename}}</h4>
564         <div id="{{platform}}_existing" class="row alert alert-info" style="display:none;">
565         <input type="hidden" name="{{platform}}_vm" id="{{platform}}_vm">
566         <input type="hidden" name="action" id="action" value="delete">
567         <input type="hidden" name="platform" id="platform" value="{{platform}}">
568         </div> 
569         <div id="{{platform}}_div_nodes">
570         {% for key, value in result.items %} 
571           {% if key == platform %}
572             {% if value.parsed.rspec.node|get_type == 'list' %}
573                 {% for node in value.parsed.rspec.node %}
574                     {% for k,node_urn in node.items %} 
575                         {% if k == '@component_id' %}
576                         {% widget '_widget-cloud-node.html' %}
577                         {% endif %}
578                     {% endfor %}
579                 {% endfor %}
580             {% else %}
581                 {% for k,node_urn in value.parsed.rspec.node.items %}
582                     {% if k == '@component_id' %}
583                     {% widget '_widget-cloud-node.html' %}
584                     {% endif %}
585                 {% endfor %}
586             {% endif %}
587           {% endif %}
588         {% endfor %}
589         </div>
590         <br>
591         <div id="{{platform}}_div_pending" style="display:none;">
592             // display only pending changes
593             <h4>Pending changes</h4>
594             <form id="{{platform}}_form_reserve" method="post">
595             {% csrf_token %}
596             <div id="{{platform}}_pending_add" class="row alert alert-success" style="display:none;margin-bottom:3px !important;"></div> 
597             <div id="{{platform}}_pending_delete" class="row alert alert-danger" style="display:none;margin-bottom:3px !important;"></div> 
598             <br>
599             <input type="hidden" name="action" id="action" value="reserve">
600             <input type="hidden" name="platform" id="platform" value="{{platform}}">
601             <input type="submit" form="{{platform}}_form_reserve" value="Apply changes" onclick="allocate('{{platform}}','{{slicename}}');">
602             </form>
603         </div>
604     </div>
605     {% endif %}
606 {% endfor %}
607 {% endblock %}