summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMichael Wood <michael.g.wood@intel.com>2014-11-11 16:30:22 (GMT)
committerRichard Purdie <richard.purdie@linuxfoundation.org>2014-11-21 11:49:24 (GMT)
commitf26c3cd6f12fdec56463c274ee2a881be27b5e7b (patch)
tree96a6c62cdd61ce79da8851290f7b4bd37854b461
parent5b8a62dad7d429034c52290385da570c5ca1da34 (diff)
downloadpoky-f26c3cd6f12fdec56463c274ee2a881be27b5e7b.tar.gz
bitbake: toaster: Add New Build Button feature
This adds a quick access dropdown menu feature for running builds on a selected project. [YOCTO #6677] (Bitbake rev: e92769b43b00764082a7cb2207e314b40510ef62) Signed-off-by: Michael Wood <michael.g.wood@intel.com> Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com> Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
-rw-r--r--bitbake/lib/toaster/toastergui/static/css/default.css4
-rw-r--r--bitbake/lib/toaster/toastergui/static/js/base.js125
-rw-r--r--bitbake/lib/toaster/toastergui/templates/base.html69
-rw-r--r--bitbake/lib/toaster/toastergui/urls.py3
-rwxr-xr-xbitbake/lib/toaster/toastergui/views.py32
5 files changed, 227 insertions, 6 deletions
diff --git a/bitbake/lib/toaster/toastergui/static/css/default.css b/bitbake/lib/toaster/toastergui/static/css/default.css
index 8e60fd8..6194c97 100644
--- a/bitbake/lib/toaster/toastergui/static/css/default.css
+++ b/bitbake/lib/toaster/toastergui/static/css/default.css
@@ -131,6 +131,10 @@ select { width: auto; }
131/* make tables Chrome-happy (me, not so much) */ 131/* make tables Chrome-happy (me, not so much) */
132#otable { table-layout: fixed; word-wrap: break-word; } 132#otable { table-layout: fixed; word-wrap: break-word; }
133 133
134/* styles for the new build button */
135.new-build .btn-primary { padding: 4px 30px; }
136#view-all-projects { display: block; }
137
134/* Configuration styles */ 138/* Configuration styles */
135.icon-trash { color: #B94A48; font-size: 16px; padding-left: 2px; } 139.icon-trash { color: #B94A48; font-size: 16px; padding-left: 2px; }
136.icon-trash:hover { color: #943A38; text-decoration: none; cursor: pointer; } 140.icon-trash:hover { color: #943A38; text-decoration: none; cursor: pointer; }
diff --git a/bitbake/lib/toaster/toastergui/static/js/base.js b/bitbake/lib/toaster/toastergui/static/js/base.js
new file mode 100644
index 0000000..864130d
--- /dev/null
+++ b/bitbake/lib/toaster/toastergui/static/js/base.js
@@ -0,0 +1,125 @@
1
2
3function basePageInit (ctx) {
4
5 var newBuildButton = $("#new-build-button");
6 /* Hide the button if we're on the project,newproject or importlyaer page */
7 if (ctx.currentUrl.search('newproject|project/\\d/$|importlayer/$') > 0){
8 newBuildButton.hide();
9 return;
10 }
11
12
13 newBuildButton.show().removeAttr("disabled");
14
15 _checkProjectBuildable()
16 _setupNewBuildButton();
17
18
19 function _checkProjectBuildable(){
20 libtoaster.getProjectInfo(ctx.projectInfoUrl, ctx.projectId,
21 function(data){
22 if (data.machine.name == undefined || data.layers.length == 0) {
23 /* we can't build anything with out a machine and some layers */
24 $("#new-build-button #targets-form").hide();
25 $("#new-build-button .alert").show();
26 } else {
27 $("#new-build-button #targets-form").show();
28 $("#new-build-button .alert").hide();
29 }
30 }, null);
31 }
32
33 function _setupNewBuildButton() {
34 /* Setup New build button */
35 var newBuildProjectInput = $("#new-build-button #project-name-input");
36 var newBuildTargetBuildBtn = $("#new-build-button #build-button");
37 var newBuildTargetInput = $("#new-build-button #build-target-input");
38 var newBuildProjectSaveBtn = $("#new-build-button #save-project-button");
39 var selectedTarget;
40 var selectedProject;
41
42 /* If we don't have a current project then present the set project
43 * form.
44 */
45 if (ctx.projectId == undefined) {
46 $('#change-project-form').show();
47 $('#project .icon-pencil').hide();
48 }
49
50 libtoaster.makeTypeahead(newBuildTargetInput, ctx.xhrDataTypeaheadUrl, { type : "targets", project_id: ctx.projectId }, function(item){
51 /* successfully selected a target */
52 selectedTarget = item;
53 });
54
55
56 libtoaster.makeTypeahead(newBuildProjectInput, ctx.xhrDataTypeaheadUrl, { type : "projects" }, function(item){
57 /* successfully selected a project */
58 newBuildProjectSaveBtn.removeAttr("disabled");
59 selectedProject = item;
60 });
61
62 /* Any typing in the input apart from enter key is going to invalidate
63 * the value that has been set by selecting a suggestion from the typeahead
64 */
65 newBuildProjectInput.keyup(function(event) {
66 if (event.keyCode == 13)
67 return;
68 newBuildProjectSaveBtn.attr("disabled", "disabled");
69 });
70
71 newBuildTargetInput.keyup(function() {
72 if ($(this).val().length == 0)
73 newBuildTargetBuildBtn.attr("disabled", "disabled");
74 else
75 newBuildTargetBuildBtn.removeAttr("disabled");
76 });
77
78 newBuildTargetBuildBtn.click(function() {
79 if (!newBuildTargetInput.val())
80 return;
81
82 /* fire and forget */
83 libtoaster.startABuild(ctx.projectBuildUrl, ctx.projectId, selectedTarget.name, null, null);
84 window.location.replace(ctx.projectPageUrl+ctx.projectId);
85 });
86
87 newBuildProjectSaveBtn.click(function() {
88 ctx.projectId = selectedProject.id
89 /* Update the typeahead project_id paramater */
90 _checkProjectBuildable();
91 newBuildTargetInput.data('typeahead').options.xhrParams.project_id = ctx.projectId;
92 newBuildTargetInput.val("");
93
94 $("#new-build-button #project a").text(selectedProject.name).attr('href', ctx.projectPageUrl+ctx.projectId);
95 $("#new-build-button .alert a").attr('href', ctx.projectPageUrl+ctx.projectId);
96
97
98 $("#change-project-form").slideUp({ 'complete' : function() {
99 $("#new-build-button #project").show();
100 }});
101 });
102
103 $('#new-build-button #project .icon-pencil').click(function() {
104 newBuildProjectSaveBtn.attr("disabled", "disabled");
105 newBuildProjectInput.val($("#new-build-button #project a").text());
106 $(this).parent().hide();
107 $("#change-project-form").slideDown();
108 });
109
110 $("#new-build-button #cancel-change-project").click(function() {
111 $("#change-project-form").hide(function(){
112 $('#new-build-button #project').show();
113 });
114
115 newBuildProjectInput.val("");
116 newBuildProjectSaveBtn.attr("disabled", "disabled");
117 });
118
119 /* Keep the dropdown open even unless we click outside the dropdown area */
120 $(".new-build").click (function(event) {
121 event.stopPropagation();
122 });
123 };
124
125}
diff --git a/bitbake/lib/toaster/toastergui/templates/base.html b/bitbake/lib/toaster/toastergui/templates/base.html
index 1b9edfd..87746bf 100644
--- a/bitbake/lib/toaster/toastergui/templates/base.html
+++ b/bitbake/lib/toaster/toastergui/templates/base.html
@@ -8,6 +8,7 @@
8<link rel="stylesheet" href="{% static 'css/font-awesome.min.css' %}" type='text/css'> 8<link rel="stylesheet" href="{% static 'css/font-awesome.min.css' %}" type='text/css'>
9<link rel="stylesheet" href="{% static 'css/prettify.css' %}" type='text/css'> 9<link rel="stylesheet" href="{% static 'css/prettify.css' %}" type='text/css'>
10<link rel="stylesheet" href="{% static 'css/default.css' %}" type='text/css'> 10<link rel="stylesheet" href="{% static 'css/default.css' %}" type='text/css'>
11<link rel="stylesheet" href="assets/css/jquery-ui-1.10.3.custom.min.css" type='text/css'>
11<meta name="viewport" content="width=device-width, initial-scale=1.0" /> 12<meta name="viewport" content="width=device-width, initial-scale=1.0" />
12<meta http-equiv="Content-Type" content="text/html;charset=UTF-8"> 13<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
13<script src="{% static 'js/jquery-2.0.3.min.js' %}"> 14<script src="{% static 'js/jquery-2.0.3.min.js' %}">
@@ -20,7 +21,25 @@
20</script> 21</script>
21<script src="{% static 'js/libtoaster.js' %}"> 22<script src="{% static 'js/libtoaster.js' %}">
22</script> 23</script>
24<script src="{% static 'js/base.js' %}"></script>
25{%if MANAGED %}
26<script>
27 $(document).ready(function () {
28 /* Vars needed for base.js */
29 var ctx = {};
30 ctx.xhrDataTypeaheadUrl = "{% url 'xhr_datatypeahead' %}";
31 ctx.projectBuildUrl = "{% url 'xhr_build' %}";
32 ctx.projectPageUrl = "{% url 'project' %}";
33 ctx.projectInfoUrl = "{% url 'xhr_projectinfo' %}";
34 {% if project %}
35 ctx.projectId = {{project.id}};
36 {% endif %}
37 ctx.currentUrl = "{{request.path|escapejs}}";
38
39 basePageInit(ctx);
40 });
23</script> 41</script>
42{% endif %}
24<script> 43<script>
25 44
26</script> 45</script>
@@ -34,15 +53,55 @@
34 <div class="navbar-inner"> 53 <div class="navbar-inner">
35 <a class="brand logo" href="#"><img src="{% static 'img/logo.png' %}" class="" alt="Yocto logo project"/></a> 54 <a class="brand logo" href="#"><img src="{% static 'img/logo.png' %}" class="" alt="Yocto logo project"/></a>
36 <a class="brand" href="/">Toaster</a> 55 <a class="brand" href="/">Toaster</a>
37 {%if MANAGED %}
38 <div class="btn-group pull-right">
39 <a class="btn" href="{% url 'newproject' %}">New project</a>
40 </div>
41 {%endif%}
42 <a class="pull-right manual" target="_blank" href="http://www.yoctoproject.org/documentation/toaster-manual"> 56 <a class="pull-right manual" target="_blank" href="http://www.yoctoproject.org/documentation/toaster-manual">
43 <i class="icon-book"></i> 57 <i class="icon-book"></i>
44 Toaster manual 58 Toaster manual
45 </a> 59 </a>
60 {%if MANAGED %}
61 <div class="btn-group pull-right">
62 <a class="btn" href="{% url 'newproject' %}">New project</a>
63 </div>
64 <!-- New build popover -->
65 <div class="btn-group pull-right" id="new-build-button">
66 <button class="btn dropdown-toggle" data-toggle="dropdown" href="#">
67 New build
68 <i class="icon-caret-down"></i>
69 </button>
70 <ul class="dropdown-menu new-build multi-select">
71 <li>
72 <h3>New build</h3>
73 <h6>Project:</h6>
74 <span id="project">
75 <a class="lead" href="{% if project.id %}{% url 'project' project.id %}{% endif %}">{{project.name}}</a>
76 <i class="icon-pencil"></i>
77 </span>
78 <form id="change-project-form" style="display:none;">
79 <div class="input-append">
80 <input type="text" class="input-medium" id="project-name-input" placeholder="Type a project name" autocomplete="off" data-minLength="1" data-autocomplete="off" data-provide="typeahead">
81 <button id="save-project-button" class="btn" type="button">Save</button>
82 <a href="#" id="cancel-change-project" class="btn btn-link">Cancel</a>
83 </div>
84 <a id="view-all-projects" href="{% url 'all-projects' %}">View all projects</a>
85 </form>
86 </li>
87 <div class="alert" style="display:none">
88 This project's configuration is incomplete,<br/>so you cannot run builds.<br/>
89 <a href="{% if project.id %}{% url 'project' project.id %}{% endif %}">View project configuration</a>
90 </div>
91 <li id="targets-form">
92 <h6>Target(s):</h6>
93 <form>
94 <input type="text" class="input-xlarge" id="build-target-input" placeholder="Type a target name" autocomplete="off" data-minLength="1" data-autocomplete="off" data-provide="typeahead" >
95 <div>
96 <a class="btn btn-primary" id="build-button" disabled="disabled" data-project-id="{{project.id}}">Build</a>
97 </div>
98 </form>
99 </li>
100 </ul>
101 </div>
102
103 {%endif%}
104
46 </div> 105 </div>
47</div> 106</div>
48 107
diff --git a/bitbake/lib/toaster/toastergui/urls.py b/bitbake/lib/toaster/toastergui/urls.py
index bae7103..b60f761 100644
--- a/bitbake/lib/toaster/toastergui/urls.py
+++ b/bitbake/lib/toaster/toastergui/urls.py
@@ -80,10 +80,13 @@ urlpatterns = patterns('toastergui.views',
80 url(r'^machines/$', 'machines', name='machines'), 80 url(r'^machines/$', 'machines', name='machines'),
81 81
82 url(r'^projects/$', 'projects', name='all-projects'), 82 url(r'^projects/$', 'projects', name='all-projects'),
83
84 url(r'^project/$', 'project', name='project'),
83 url(r'^project/(?P<pid>\d+)/$', 'project', name='project'), 85 url(r'^project/(?P<pid>\d+)/$', 'project', name='project'),
84 url(r'^project/(?P<pid>\d+)/configuration$', 'projectconf', name='projectconf'), 86 url(r'^project/(?P<pid>\d+)/configuration$', 'projectconf', name='projectconf'),
85 url(r'^project/(?P<pid>\d+)/builds$', 'projectbuilds', name='projectbuilds'), 87 url(r'^project/(?P<pid>\d+)/builds$', 'projectbuilds', name='projectbuilds'),
86 88
89 url(r'^xhr_build/$', 'xhr_build', name='xhr_build'),
87 url(r'^xhr_projectbuild/(?P<pid>\d+)/$', 'xhr_projectbuild', name='xhr_projectbuild'), 90 url(r'^xhr_projectbuild/(?P<pid>\d+)/$', 'xhr_projectbuild', name='xhr_projectbuild'),
88 url(r'^xhr_projectinfo/$', 'xhr_projectinfo', name='xhr_projectinfo'), 91 url(r'^xhr_projectinfo/$', 'xhr_projectinfo', name='xhr_projectinfo'),
89 url(r'^xhr_projectedit/(?P<pid>\d+)/$', 'xhr_projectedit', name='xhr_projectedit'), 92 url(r'^xhr_projectedit/(?P<pid>\d+)/$', 'xhr_projectedit', name='xhr_projectedit'),
diff --git a/bitbake/lib/toaster/toastergui/views.py b/bitbake/lib/toaster/toastergui/views.py
index 9f214bb..a0dcf87 100755
--- a/bitbake/lib/toaster/toastergui/views.py
+++ b/bitbake/lib/toaster/toastergui/views.py
@@ -2015,10 +2015,20 @@ if toastermain.settings.MANAGED:
2015 response['Pragma'] = "no-cache" 2015 response['Pragma'] = "no-cache"
2016 return response 2016 return response
2017 2017
2018 # This is a wrapper for xhr_projectbuild which allows for a project id
2019 # which only becomes known client side.
2020 def xhr_build(request):
2021 if request.POST.has_key("project_id"):
2022 pid = request.POST['project_id']
2023 return xhr_projectbuild(request, pid)
2024 else:
2025 raise BadParameterException("invalid project id")
2026
2018 def xhr_projectbuild(request, pid): 2027 def xhr_projectbuild(request, pid):
2019 try: 2028 try:
2020 if request.method != "POST": 2029 if request.method != "POST":
2021 raise BadParameterException("invalid method") 2030 raise BadParameterException("invalid method")
2031 request.session['project_id'] = pid
2022 prj = Project.objects.get(id = pid) 2032 prj = Project.objects.get(id = pid)
2023 2033
2024 2034
@@ -2057,6 +2067,8 @@ if toastermain.settings.MANAGED:
2057 except Exception as e: 2067 except Exception as e:
2058 return HttpResponse(jsonfilter({"error":str(e) + "\n" + traceback.format_exc()}), content_type = "application/json") 2068 return HttpResponse(jsonfilter({"error":str(e) + "\n" + traceback.format_exc()}), content_type = "application/json")
2059 2069
2070 # This is a wraper for xhr_projectedit which allows for a project id
2071 # which only becomes known client side
2060 def xhr_projectinfo(request): 2072 def xhr_projectinfo(request):
2061 if request.POST.has_key("project_id") == False: 2073 if request.POST.has_key("project_id") == False:
2062 raise BadParameterException("invalid project id") 2074 raise BadParameterException("invalid project id")
@@ -2121,8 +2133,12 @@ if toastermain.settings.MANAGED:
2121 def xhr_datatypeahead(request): 2133 def xhr_datatypeahead(request):
2122 try: 2134 try:
2123 prj = None 2135 prj = None
2124 if 'project_id' in request.session: 2136 if request.GET.has_key('project_id'):
2137 prj = Project.objects.get(pk = request.GET['project_id'])
2138 elif 'project_id' in request.session:
2125 prj = Project.objects.get(pk = request.session['project_id']) 2139 prj = Project.objects.get(pk = request.session['project_id'])
2140 else:
2141 raise Exception("No valid project selected")
2126 2142
2127 # returns layers for current project release that are not in the project set 2143 # returns layers for current project release that are not in the project set
2128 if request.GET['type'] == "layers": 2144 if request.GET['type'] == "layers":
@@ -2188,6 +2204,14 @@ if toastermain.settings.MANAGED:
2188 2204
2189 }), content_type = "application/json") 2205 }), content_type = "application/json")
2190 2206
2207 if request.GET['type'] == "projects":
2208 queryset_all = Project.objects.all()
2209 ret = { "error": "ok",
2210 "list": map (lambda x: {"id":x.pk, "name": x.name},
2211 queryset_all.filter(name__icontains=request.GET.get('value',''))[:8])}
2212
2213 return HttpResponse(jsonfilter(ret), content_type = "application/json")
2214
2191 raise Exception("Unknown request! " + request.GET.get('type', "No parameter supplied")) 2215 raise Exception("Unknown request! " + request.GET.get('type', "No parameter supplied"))
2192 except Exception as e: 2216 except Exception as e:
2193 return HttpResponse(jsonfilter({"error":str(e) + "\n" + traceback.format_exc()}), content_type = "application/json") 2217 return HttpResponse(jsonfilter({"error":str(e) + "\n" + traceback.format_exc()}), content_type = "application/json")
@@ -2773,6 +2797,12 @@ else:
2773 def xhr_projectbuild(request, pid): 2797 def xhr_projectbuild(request, pid):
2774 raise Exception("page not available in interactive mode") 2798 raise Exception("page not available in interactive mode")
2775 2799
2800 def xhr_build(request, pid):
2801 raise Exception("page not available in interactive mode")
2802
2803 def xhr_projectinfo(request, pid):
2804 raise Exception("page not available in interactive mode")
2805
2776 def xhr_projectedit(request, pid): 2806 def xhr_projectedit(request, pid):
2777 raise Exception("page not available in interactive mode") 2807 raise Exception("page not available in interactive mode")
2778 2808