diff options
| -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 | ||
