diff options
author | Michael Wood <michael.g.wood@intel.com> | 2014-11-11 16:30:22 +0000 |
---|---|---|
committer | Richard Purdie <richard.purdie@linuxfoundation.org> | 2014-11-21 11:49:24 +0000 |
commit | f26c3cd6f12fdec56463c274ee2a881be27b5e7b (patch) | |
tree | 96a6c62cdd61ce79da8851290f7b4bd37854b461 /bitbake | |
parent | 5b8a62dad7d429034c52290385da570c5ca1da34 (diff) | |
download | poky-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>
Diffstat (limited to 'bitbake')
-rw-r--r-- | bitbake/lib/toaster/toastergui/static/css/default.css | 4 | ||||
-rw-r--r-- | bitbake/lib/toaster/toastergui/static/js/base.js | 125 | ||||
-rw-r--r-- | bitbake/lib/toaster/toastergui/templates/base.html | 69 | ||||
-rw-r--r-- | bitbake/lib/toaster/toastergui/urls.py | 3 | ||||
-rwxr-xr-x | bitbake/lib/toaster/toastergui/views.py | 32 |
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 8e60fd8b56..6194c97a0f 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 0000000000..864130def9 --- /dev/null +++ b/bitbake/lib/toaster/toastergui/static/js/base.js | |||
@@ -0,0 +1,125 @@ | |||
1 | |||
2 | |||
3 | function 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 1b9edfd7b7..87746bfc8c 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 bae7103091..b60f7614af 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 9f214bb677..a0dcf8797a 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 | ||