diff options
4 files changed, 742 insertions, 199 deletions
diff --git a/bitbake/lib/toaster/toastergui/templates/managed_builds.html b/bitbake/lib/toaster/toastergui/templates/managed_builds.html new file mode 100644 index 0000000000..5944dc4747 --- /dev/null +++ b/bitbake/lib/toaster/toastergui/templates/managed_builds.html | |||
| @@ -0,0 +1,135 @@ | |||
| 1 | {% extends "base.html" %} | ||
| 2 | |||
| 3 | {% load static %} | ||
| 4 | {% load projecttags %} | ||
| 5 | {% load humanize %} | ||
| 6 | |||
| 7 | {% block pagecontent %} | ||
| 8 | <div class="row-fluid"> | ||
| 9 | |||
| 10 | {% include "managed_mrb_section.html" %} | ||
| 11 | |||
| 12 | |||
| 13 | {% if 1 %} | ||
| 14 | <div class="page-header top-air"> | ||
| 15 | <h1> | ||
| 16 | {% if request.GET.filter and objects.paginator.count > 0 or request.GET.search and objects.paginator.count > 0 %} | ||
| 17 | {{objects.paginator.count}} build{{objects.paginator.count|pluralize}} found | ||
| 18 | {%elif request.GET.filter and objects.paginator.count == 0 or request.GET.search and objects.paginator.count == 0 %} | ||
| 19 | No builds found | ||
| 20 | {%else%} | ||
| 21 | All builds | ||
| 22 | {%endif%} | ||
| 23 | </h1> | ||
| 24 | </div> | ||
| 25 | |||
| 26 | {% if objects.paginator.count == 0 %} | ||
| 27 | <div class="row-fluid"> | ||
| 28 | <div class="alert"> | ||
| 29 | <form class="no-results input-append" id="searchform"> | ||
| 30 | <input id="search" name="search" class="input-xxlarge" type="text" value="{{request.GET.search}}"/>{% if request.GET.search %}<a href="javascript:$('#search').val('');searchform.submit()" class="add-on btn" tabindex="-1"><i class="icon-remove"></i></a>{% endif %} | ||
| 31 | <button class="btn" type="submit" value="Search">Search</button> | ||
| 32 | <button class="btn btn-link" onclick="javascript:$('#search').val('');searchform.submit()">Show all builds</button> | ||
| 33 | </form> | ||
| 34 | </div> | ||
| 35 | </div> | ||
| 36 | |||
| 37 | |||
| 38 | {% else %} | ||
| 39 | {% include "basetable_top_buildprojects.html" %} | ||
| 40 | <!-- Table data rows; the order needs to match the order of "tablecols" definitions; and the <td class value needs to match the tablecols clclass value for show/hide buttons to work --> | ||
| 41 | {% for br in objects %}{% if br.build %} {% with build=br.build %} {# if we have a build, just display it #} | ||
| 42 | <tr class="data"> | ||
| 43 | <td class="outcome"><a href="{% url "builddashboard" build.id %}">{%if build.outcome == build.SUCCEEDED%}<i class="icon-ok-sign success"></i>{%elif build.outcome == build.FAILED%}<i class="icon-minus-sign error"></i>{%else%}{%endif%}</a></td> | ||
| 44 | <td class="target">{% for t in build.target_set.all %} <a href="{% url "builddashboard" build.id %}"> {{t.target}} </a> <br />{% endfor %}</td> | ||
| 45 | <td class="machine"><a href="{% url "builddashboard" build.id %}">{{build.machine}}</a></td> | ||
| 46 | <td class="started_on"><a href="{% url "builddashboard" build.id %}">{{build.started_on|date:"d/m/y H:i"}}</a></td> | ||
| 47 | <td class="completed_on"><a href="{% url "builddashboard" build.id %}">{{build.completed_on|date:"d/m/y H:i"}}</a></td> | ||
| 48 | <td class="failed_tasks error"> | ||
| 49 | {% query build.task_build outcome=4 order__gt=0 as exectask%} | ||
| 50 | {% if exectask.count == 1 %} | ||
| 51 | <a href="{% url "task" build.id exectask.0.id %}">{{exectask.0.recipe.name}}.{{exectask.0.task_name}}</a> | ||
| 52 | {% if MANAGED and build.project %} | ||
| 53 | <a href="{% url 'build_artifact' build.id "tasklogfile" exectask.0.id %}"> | ||
| 54 | <i class="icon-download-alt" title="" data-original-title="Download task log file"></i> | ||
| 55 | </a> | ||
| 56 | {% endif %} | ||
| 57 | {% elif exectask.count > 1%} | ||
| 58 | <a href="{% url "tasks" build.id %}?filter=outcome%3A4">{{exectask.count}} task{{exectask.count|pluralize}}</a> | ||
| 59 | {%endif%} | ||
| 60 | </td> | ||
| 61 | <td class="errors_no"> | ||
| 62 | {% if build.errors_no %} | ||
| 63 | <a class="errors_no error" href="{% url "builddashboard" build.id %}#errors">{{build.errors_no}} error{{build.errors_no|pluralize}}</a> | ||
| 64 | {% if MANAGED and build.project %} | ||
| 65 | <a href="{% url 'build_artifact' build.id "cookerlog" build.id %}"> | ||
| 66 | <i class="icon-download-alt" title="" data-original-title="Download build log"></i> | ||
| 67 | </a> | ||
| 68 | {% endif %} | ||
| 69 | {%endif%} | ||
| 70 | </td> | ||
| 71 | <td class="warnings_no">{% if build.warnings_no %}<a class="warnings_no warning" href="{% url "builddashboard" build.id %}#warnings">{{build.warnings_no}} warning{{build.warnings_no|pluralize}}</a>{%endif%}</td> | ||
| 72 | <td class="time"><a href="{% url "buildtime" build.id %}">{{build.timespent|sectohms}}</a></td> | ||
| 73 | {% if not MANAGED or not build.project %} | ||
| 74 | <td class="log">{{build.cooker_log_path}}</td> | ||
| 75 | {% endif %} | ||
| 76 | <td class="output"> | ||
| 77 | {% if build.outcome == build.SUCCEEDED %} | ||
| 78 | <a href="{%url "builddashboard" build.id%}#images">{{fstypes|get_dict_value:build.id}}</a> | ||
| 79 | {% endif %} | ||
| 80 | </td> | ||
| 81 | {% if MANAGED %} | ||
| 82 | <td class="project"> | ||
| 83 | {% if build.project %} | ||
| 84 | <a href="{% url 'project' build.project.id %}">{{build.project.name}}</a> | ||
| 85 | {% endif %} | ||
| 86 | </td> | ||
| 87 | {% endif %} | ||
| 88 | </tr> | ||
| 89 | |||
| 90 | |||
| 91 | {%endwith%} | ||
| 92 | {% else %} {# we don't have a build for this build request, mask the data with build request data #} | ||
| 93 | |||
| 94 | |||
| 95 | |||
| 96 | <tr class="data"> | ||
| 97 | <td class="outcome">{% if buildrequest.state == buildrequest.REQ_FAILED %}<i class="icon-minus-sign error"></i>{%else%}FIXME_build_request_state{%endif%}</td> | ||
| 98 | <td class="target"> | ||
| 99 | <span data-toggle="tooltip" {%if br.brtarget_set.all.count > 1%}title="Targets: {%for target in br.brtarget_set.all%}{{target.target}} {%endfor%}"{%endif%}>{{br.brtarget_set.all.0.target}} {%if br.brtarget_set.all.count > 1%}(+ {{br.brtarget_set.all.count|add:"-1"}}){%endif%} </span> | ||
| 100 | </td> | ||
| 101 | <td class="machine"> | ||
| 102 | {{br.machine}} | ||
| 103 | </td> | ||
| 104 | <td class="started_on"> | ||
| 105 | {{br.created|date:"d/m/y H:i"}} | ||
| 106 | </td> | ||
| 107 | <td class="completed_on"> | ||
| 108 | {{br.updated|date:"d/m/y H:i"}} | ||
| 109 | </td> | ||
| 110 | <td class="failed_tasks error"> | ||
| 111 | {{br.brerror_set.all.0.errmsg|whitespace_slice:":32"}} | ||
| 112 | </td> | ||
| 113 | <td class="errors_no"> | ||
| 114 | </td> | ||
| 115 | <td class="warnings_no"> | ||
| 116 | </td> | ||
| 117 | <td class="time"> | ||
| 118 | {{br.timespent.total_seconds|sectohms}} | ||
| 119 | </td> | ||
| 120 | <td class="output"> {# we have no output here #} | ||
| 121 | </td> | ||
| 122 | <td class="project"> | ||
| 123 | <a href="{% url 'project' br.project.id %}">{{br.project.name}}</a> | ||
| 124 | </td> | ||
| 125 | </tr> | ||
| 126 | {%endif%} | ||
| 127 | {% endfor %} | ||
| 128 | |||
| 129 | |||
| 130 | {% include "basetable_bottom.html" %} | ||
| 131 | {% endif %} {# objects.paginator.count #} | ||
| 132 | {% endif %} {# empty #} | ||
| 133 | </div><!-- end row-fluid--> | ||
| 134 | |||
| 135 | {% endblock %} | ||
diff --git a/bitbake/lib/toaster/toastergui/templates/managed_mrb_section.html b/bitbake/lib/toaster/toastergui/templates/managed_mrb_section.html new file mode 100644 index 0000000000..d4959a0b52 --- /dev/null +++ b/bitbake/lib/toaster/toastergui/templates/managed_mrb_section.html | |||
| @@ -0,0 +1,172 @@ | |||
| 1 | {% load static %} | ||
| 2 | {% load projecttags %} | ||
| 3 | {% load humanize %} | ||
| 4 | |||
| 5 | |||
| 6 | {%if mru.count > 0%} | ||
| 7 | |||
| 8 | <div class="page-header top-air"> | ||
| 9 | <h1> | ||
| 10 | Latest builds | ||
| 11 | </h1> | ||
| 12 | </div> | ||
| 13 | <div id="latest-builds"> | ||
| 14 | {% for buildrequest in mru %}{% with build=buildrequest.build %} | ||
| 15 | |||
| 16 | {% if build %} {# if we have a build, just display it #} | ||
| 17 | |||
| 18 | <div class="alert {%if build.outcome == build.SUCCEEDED%}alert-success{%elif build.outcome == build.FAILED%}alert-error{%else%}alert-info{%endif%} {% if MANAGED and build.project %}project-name{% endif %} "> | ||
| 19 | {% if MANAGED and build.project %} | ||
| 20 | <span class="label {%if build.outcome == build.SUCCEEDED%}label-success{%elif build.outcome == build.FAILED%}label-danger{%else%}label-info{%endif%}"> {{build.project.name}} </span> | ||
| 21 | {% endif %} | ||
| 22 | |||
| 23 | <div class="row-fluid"> | ||
| 24 | <div class="span3 lead"> | ||
| 25 | {%if build.outcome == build.SUCCEEDED or build.outcome == build.FAILED %} | ||
| 26 | <a href="{%url 'builddashboard' build.pk%}" class="{%if build.outcome == build.SUCCEEDED %}success{%else%}error{%endif%}"> | ||
| 27 | {% endif %} | ||
| 28 | <span data-toggle="tooltip" {%if build.target_set.all.count > 1%}title="Targets: {%for target in build.target_set.all%}{{target.target}} {%endfor%}"{%endif%}>{{build.target_set.all.0.target}} {%if build.target_set.all.count > 1%}(+ {{build.target_set.all.count|add:"-1"}}){%endif%} | ||
| 29 | </span> | ||
| 30 | {%if build.outcome == build.SUCCEEDED or build.outcome == build.FAILED %} | ||
| 31 | </a> | ||
| 32 | {% endif %} | ||
| 33 | </div> | ||
| 34 | <div class="span2 lead"> | ||
| 35 | {% if build.completed_on|format_build_date %} | ||
| 36 | {{ build.completed_on|date:'d/m/y H:i' }} | ||
| 37 | {% else %} | ||
| 38 | {{ build.completed_on|date:'H:i' }} | ||
| 39 | {% endif %} | ||
| 40 | </div> | ||
| 41 | {%if build.outcome == build.SUCCEEDED or build.outcome == build.FAILED %} | ||
| 42 | <div class="span2 lead"> | ||
| 43 | {% if build.errors_no %} | ||
| 44 | <i class="icon-minus-sign red"></i> <a href="{%url 'builddashboard' build.pk%}#errors" class="error">{{build.errors_no}} error{{build.errors_no|pluralize}}</a> | ||
| 45 | {% endif %} | ||
| 46 | </div> | ||
| 47 | <div class="span2 lead"> | ||
| 48 | {% if build.warnings_no %} | ||
| 49 | <i class="icon-warning-sign yellow"></i> <a href="{%url 'builddashboard' build.pk%}#warnings" class="warning">{{build.warnings_no}} warning{{build.warnings_no|pluralize}}</a> | ||
| 50 | {% endif %} | ||
| 51 | </div> | ||
| 52 | <div class="lead "> | ||
| 53 | <span class="lead{%if not MANAGED or not build.project%} pull-right{%endif%}"> | ||
| 54 | Build time: <a href="{% url 'buildtime' build.pk %}">{{ build.timespent|sectohms }}</a> | ||
| 55 | </span> | ||
| 56 | {% if MANAGED and build.project %} | ||
| 57 | <a class="btn {%if build.outcome == build.SUCCEEDED%}btn-success{%elif build.outcome == build.FAILED%}btn-danger{%else%}btn-info{%endif%} pull-right" onclick="scheduleBuild({% url 'xhr_projectbuild' build.project.id as bpi%}{{bpi|json}}, {{build.project.name|json}}, {{build.get_sorted_target_list|mapselect:'target'|json}})">Run again</a> | ||
| 58 | {% endif %} | ||
| 59 | </div> | ||
| 60 | {%endif%} | ||
| 61 | {%if build.outcome == build.IN_PROGRESS %} | ||
| 62 | <div class="span4"> | ||
| 63 | <div class="progress" style="margin-top:5px;" data-toggle="tooltip" title="{{build.completeper}}% of tasks complete"> | ||
| 64 | <div style="width: {{build.completeper}}%;" class="bar"></div> | ||
| 65 | </div> | ||
| 66 | </div> | ||
| 67 | <div class="lead pull-right">ETA: in {{build.eta|naturaltime}}</div> | ||
| 68 | {%endif%} | ||
| 69 | </div> | ||
| 70 | </div> | ||
| 71 | |||
| 72 | {% else %} {# we use the project's page recent build design #} | ||
| 73 | |||
| 74 | <div class="alert {% if buildrequest.state == buildrequest.REQ_FAILED %}alert-error{% else %}alert-info{% endif %}"> | ||
| 75 | <div class="row-fluid"> | ||
| 76 | |||
| 77 | |||
| 78 | {% if buildrequest.state == buildrequest.REQ_FAILED %} | ||
| 79 | <div class="lead span3"> | ||
| 80 | <span data-toggle="tooltip" {%if buildrequest.brtarget_set.all.count > 1%}title="Targets: {%for target in buildrequest.brtarget_set.all%}{{target.target}} {%endfor%}"{%endif%}>{{buildrequest.brtarget_set.all.0.target}} {%if buildrequest.brtarget_set.all.count > 1%}(+ {{buildrequest.brtarget_set.all.count|add:"-1"}}){%endif%} </span> | ||
| 81 | </div> | ||
| 82 | <div > | ||
| 83 | </div> | ||
| 84 | <div class="row-fluid"> | ||
| 85 | {% for e in buildrequest.brerror_set.all|slice:":3" %} | ||
| 86 | <div class="air well"> | ||
| 87 | <pre>{{e.errmsg|whitespace_slice:":150"}}</pre> | ||
| 88 | </div> | ||
| 89 | {% endfor %} | ||
| 90 | </div> | ||
| 91 | |||
| 92 | {% elif buildrequest.state == buildrequest.REQ_QUEUED %} | ||
| 93 | |||
| 94 | <div class="lead span5"> | ||
| 95 | |||
| 96 | <span data-toggle="tooltip" {%if buildrequest.brtarget_set.all.count > 1%}title="Targets: {%for target in buildrequest.brtarget_set.all%}{{target.target}} {%endfor%}"{%endif%}>{{buildrequest.brtarget_set.all.0.target}} {%if buildrequest.brtarget_set.all.count > 1%}(+ {{buildrequest.brtarget_set.all.count|add:"-1"}}){%endif%} </span> | ||
| 97 | </div> | ||
| 98 | <div class="span4 lead" >Build queued | ||
| 99 | <i title="This build will start as soon as a build server is available" class="icon-question-sign get-help get-help-blue heading-help" data-toggle="tooltip"></i> | ||
| 100 | </div> | ||
| 101 | |||
| 102 | {% elif buildrequest.state == buildrequest.REQ_CREATED %} | ||
| 103 | |||
| 104 | <div class="lead span3"> | ||
| 105 | <span data-toggle="tooltip" {%if buildrequest.brtarget_set.all.count > 1%}title="Targets: {%for target in buildrequest.brtarget_set.all%}{{target.target}} {%endfor%}"{%endif%}>{{buildrequest.brtarget_set.all.0.target}} {%if buildrequest.brtarget_set.all.count > 1%}(+ {{buildrequest.brtarget_set.all.count|add:"-1"}}){%endif%} </span> | ||
| 106 | </div> | ||
| 107 | <div class="span6" > | ||
| 108 | <span class="lead">Creating build</span> | ||
| 109 | </div> | ||
| 110 | |||
| 111 | {% elif buildrequest.state == buildrequest.REQ_INPROGRESS %} | ||
| 112 | |||
| 113 | <div class="lead span5"> | ||
| 114 | <span data-toggle="tooltip" {%if buildrequest.brtarget_set.all.count > 1%}title="Targets: {%for target in buildrequest.brtarget_set.all%}{{target.target}} {%endfor%}"{%endif%}>{{buildrequest.brtarget_set.all.0.target}} {%if buildrequest.brtarget_set.all.count > 1%}(+ {{buildrequest.brtarget_set.all.count|add:"-1"}}){%endif%} </span> | ||
| 115 | </div> | ||
| 116 | <div class="span4 lead"> | ||
| 117 | Checking out layers | ||
| 118 | </div> | ||
| 119 | {% else %} | ||
| 120 | |||
| 121 | <div>FIXME!</div> | ||
| 122 | |||
| 123 | {% endif %} | ||
| 124 | <div class="lead pull-right"> | ||
| 125 | </div> | ||
| 126 | </div> | ||
| 127 | </div> | ||
| 128 | |||
| 129 | |||
| 130 | |||
| 131 | {% endif %} {# this ends the build request most recent build section #} | ||
| 132 | |||
| 133 | {%endwith%}{% endfor %} | ||
| 134 | </div> | ||
| 135 | |||
| 136 | <script> | ||
| 137 | |||
| 138 | function _makeXHRBuildCall(url, data, onsuccess, onfail) { | ||
| 139 | $.ajax( { | ||
| 140 | type: "POST", | ||
| 141 | url: url, | ||
| 142 | data: data, | ||
| 143 | headers: { 'X-CSRFToken' : $.cookie('csrftoken')}, | ||
| 144 | success: function (_data) { | ||
| 145 | if (_data.error != "ok") { | ||
| 146 | alert(_data.error); | ||
| 147 | } else { | ||
| 148 | if (onsuccess != undefined) onsuccess(_data); | ||
| 149 | } | ||
| 150 | }, | ||
| 151 | error: function (_data) { | ||
| 152 | alert("Call failed"); | ||
| 153 | console.log(_data); | ||
| 154 | if (onfail) onfail(data); | ||
| 155 | } }); | ||
| 156 | } | ||
| 157 | |||
| 158 | |||
| 159 | function scheduleBuild(url, projectName, buildlist) { | ||
| 160 | console.log("scheduleBuild"); | ||
| 161 | _makeXHRBuildCall(url, {targets: buildlist.join(" ")}, function (_data) { | ||
| 162 | |||
| 163 | $('#latest-builds').prepend('<div class="alert alert-info" style="padding-top:0px">' + '<span class="label label-info" style="font-weight: normal; margin-bottom: 5px; margin-left:-15px; padding-top:5px;">'+projectName+'</span><div class="row-fluid">' + | ||
| 164 | '<div class="span4 lead">' + buildlist.join(" ") + | ||
| 165 | '</div><div class="span4 lead pull-right">Build queued. Your build will start shortly.</div></div></div>'); | ||
| 166 | }); | ||
| 167 | } | ||
| 168 | |||
| 169 | </script> | ||
| 170 | |||
| 171 | {%endif%} | ||
| 172 | |||
diff --git a/bitbake/lib/toaster/toastergui/templatetags/projecttags.py b/bitbake/lib/toaster/toastergui/templatetags/projecttags.py index f564edfe49..276c6eb098 100644 --- a/bitbake/lib/toaster/toastergui/templatetags/projecttags.py +++ b/bitbake/lib/toaster/toastergui/templatetags/projecttags.py | |||
| @@ -65,6 +65,25 @@ def query(qs, **kwargs): | |||
| 65 | """ | 65 | """ |
| 66 | return qs.filter(**kwargs) | 66 | return qs.filter(**kwargs) |
| 67 | 67 | ||
| 68 | |||
| 69 | @register.filter("whitespace_slice") | ||
| 70 | def whitespace_space_filter(value, arg): | ||
| 71 | try: | ||
| 72 | bits = [] | ||
| 73 | for x in arg.split(":"): | ||
| 74 | if len(x) == 0: | ||
| 75 | bits.append(None) | ||
| 76 | else: | ||
| 77 | # convert numeric value to the first whitespace after | ||
| 78 | first_whitespace = value.find(" ", int(x)) | ||
| 79 | if first_whitespace == -1: | ||
| 80 | bits.append(int(x)) | ||
| 81 | else: | ||
| 82 | bits.append(first_whitespace) | ||
| 83 | return value[slice(*bits)] | ||
| 84 | except (ValueError, TypeError): | ||
| 85 | raise | ||
| 86 | |||
| 68 | @register.filter | 87 | @register.filter |
| 69 | def divide(value, arg): | 88 | def divide(value, arg): |
| 70 | if int(arg) == 0: | 89 | if int(arg) == 0: |
diff --git a/bitbake/lib/toaster/toastergui/views.py b/bitbake/lib/toaster/toastergui/views.py index e414b66480..e8e4927b7e 100755 --- a/bitbake/lib/toaster/toastergui/views.py +++ b/bitbake/lib/toaster/toastergui/views.py | |||
| @@ -255,197 +255,6 @@ def _save_parameters_cookies(response, pagesize, orderby, request): | |||
| 255 | return response | 255 | return response |
| 256 | 256 | ||
| 257 | 257 | ||
| 258 | |||
| 259 | # shows the "all builds" page | ||
| 260 | def builds(request): | ||
| 261 | template = 'build.html' | ||
| 262 | # define here what parameters the view needs in the GET portion in order to | ||
| 263 | # be able to display something. 'count' and 'page' are mandatory for all views | ||
| 264 | # that use paginators. | ||
| 265 | (pagesize, orderby) = _get_parameters_values(request, 10, 'completed_on:-') | ||
| 266 | mandatory_parameters = { 'count': pagesize, 'page' : 1, 'orderby' : orderby } | ||
| 267 | retval = _verify_parameters( request.GET, mandatory_parameters ) | ||
| 268 | if retval: | ||
| 269 | return _redirect_parameters( 'all-builds', request.GET, mandatory_parameters) | ||
| 270 | |||
| 271 | # boilerplate code that takes a request for an object type and returns a queryset | ||
| 272 | # for that object type. copypasta for all needed table searches | ||
| 273 | (filter_string, search_term, ordering_string) = _search_tuple(request, Build) | ||
| 274 | queryset_all = Build.objects.exclude(outcome = Build.IN_PROGRESS) | ||
| 275 | queryset_with_search = _get_queryset(Build, queryset_all, None, search_term, ordering_string, '-completed_on') | ||
| 276 | queryset = _get_queryset(Build, queryset_all, filter_string, search_term, ordering_string, '-completed_on') | ||
| 277 | |||
| 278 | # retrieve the objects that will be displayed in the table; builds a paginator and gets a page range to display | ||
| 279 | build_info = _build_page_range(Paginator(queryset, pagesize), request.GET.get('page', 1)) | ||
| 280 | |||
| 281 | # build view-specific information; this is rendered specifically in the builds page, at the top of the page (i.e. Recent builds) | ||
| 282 | build_mru = Build.objects.order_by("-started_on")[:3] | ||
| 283 | |||
| 284 | # set up list of fstypes for each build | ||
| 285 | fstypes_map = {}; | ||
| 286 | for build in build_info: | ||
| 287 | targets = Target.objects.filter( build_id = build.id ) | ||
| 288 | comma = ""; | ||
| 289 | extensions = ""; | ||
| 290 | for t in targets: | ||
| 291 | if ( not t.is_image ): | ||
| 292 | continue | ||
| 293 | tif = Target_Image_File.objects.filter( target_id = t.id ) | ||
| 294 | for i in tif: | ||
| 295 | s=re.sub('.*tar.bz2', 'tar.bz2', i.file_name) | ||
| 296 | if s == i.file_name: | ||
| 297 | s=re.sub('.*\.', '', i.file_name) | ||
| 298 | if None == re.search(s,extensions): | ||
| 299 | extensions += comma + s | ||
| 300 | comma = ", " | ||
| 301 | fstypes_map[build.id]=extensions | ||
| 302 | |||
| 303 | # send the data to the template | ||
| 304 | context = { | ||
| 305 | # specific info for | ||
| 306 | 'mru' : build_mru, | ||
| 307 | # TODO: common objects for all table views, adapt as needed | ||
| 308 | 'objects' : build_info, | ||
| 309 | 'objectname' : "builds", | ||
| 310 | 'default_orderby' : 'completed_on:-', | ||
| 311 | 'fstypes' : fstypes_map, | ||
| 312 | 'search_term' : search_term, | ||
| 313 | 'total_count' : queryset_with_search.count(), | ||
| 314 | # Specifies the display of columns for the table, appearance in "Edit columns" box, toggling default show/hide, and specifying filters for columns | ||
| 315 | 'tablecols' : [ | ||
| 316 | {'name': 'Outcome', # column with a single filter | ||
| 317 | 'qhelp' : "The outcome tells you if a build successfully completed or failed", # the help button content | ||
| 318 | 'dclass' : "span2", # indication about column width; comes from the design | ||
| 319 | 'orderfield': _get_toggle_order(request, "outcome"), # adds ordering by the field value; default ascending unless clicked from ascending into descending | ||
| 320 | 'ordericon':_get_toggle_order_icon(request, "outcome"), | ||
| 321 | # filter field will set a filter on that column with the specs in the filter description | ||
| 322 | # the class field in the filter has no relation with clclass; the control different aspects of the UI | ||
| 323 | # still, it is recommended for the values to be identical for easy tracking in the generated HTML | ||
| 324 | 'filter' : {'class' : 'outcome', | ||
| 325 | 'label': 'Show:', | ||
| 326 | 'options' : [ | ||
| 327 | ('Successful builds', 'outcome:' + str(Build.SUCCEEDED), queryset_with_search.filter(outcome=str(Build.SUCCEEDED)).count()), # this is the field search expression | ||
| 328 | ('Failed builds', 'outcome:'+ str(Build.FAILED), queryset_with_search.filter(outcome=str(Build.FAILED)).count()), | ||
| 329 | ] | ||
| 330 | } | ||
| 331 | }, | ||
| 332 | {'name': 'Target', # default column, disabled box, with just the name in the list | ||
| 333 | 'qhelp': "This is the build target or build targets (i.e. one or more recipes or image recipes)", | ||
| 334 | 'orderfield': _get_toggle_order(request, "target__target"), | ||
| 335 | 'ordericon':_get_toggle_order_icon(request, "target__target"), | ||
| 336 | }, | ||
| 337 | {'name': 'Machine', | ||
| 338 | 'qhelp': "The machine is the hardware for which you are building a recipe or image recipe", | ||
| 339 | 'orderfield': _get_toggle_order(request, "machine"), | ||
| 340 | 'ordericon':_get_toggle_order_icon(request, "machine"), | ||
| 341 | 'dclass': 'span3' | ||
| 342 | }, # a slightly wider column | ||
| 343 | {'name': 'Started on', 'clclass': 'started_on', 'hidden' : 1, # this is an unchecked box, which hides the column | ||
| 344 | 'qhelp': "The date and time you started the build", | ||
| 345 | 'orderfield': _get_toggle_order(request, "started_on", True), | ||
| 346 | 'ordericon':_get_toggle_order_icon(request, "started_on"), | ||
| 347 | 'filter' : {'class' : 'started_on', | ||
| 348 | 'label': 'Show:', | ||
| 349 | 'options' : [ | ||
| 350 | ("Today's builds" , 'started_on__gte:'+timezone.now().strftime("%Y-%m-%d"), queryset_with_search.filter(started_on__gte=timezone.now()).count()), | ||
| 351 | ("Yesterday's builds", 'started_on__gte:'+(timezone.now()-timedelta(hours=24)).strftime("%Y-%m-%d"), queryset_with_search.filter(started_on__gte=(timezone.now()-timedelta(hours=24))).count()), | ||
| 352 | ("This week's builds", 'started_on__gte:'+(timezone.now()-timedelta(days=7)).strftime("%Y-%m-%d"), queryset_with_search.filter(started_on__gte=(timezone.now()-timedelta(days=7))).count()), | ||
| 353 | ] | ||
| 354 | } | ||
| 355 | }, | ||
| 356 | {'name': 'Completed on', | ||
| 357 | 'qhelp': "The date and time the build finished", | ||
| 358 | 'orderfield': _get_toggle_order(request, "completed_on", True), | ||
| 359 | 'ordericon':_get_toggle_order_icon(request, "completed_on"), | ||
| 360 | 'orderkey' : 'completed_on', | ||
| 361 | 'filter' : {'class' : 'completed_on', | ||
| 362 | 'label': 'Show:', | ||
| 363 | 'options' : [ | ||
| 364 | ("Today's builds", 'completed_on__gte:'+timezone.now().strftime("%Y-%m-%d"), queryset_with_search.filter(completed_on__gte=timezone.now()).count()), | ||
| 365 | ("Yesterday's builds", 'completed_on__gte:'+(timezone.now()-timedelta(hours=24)).strftime("%Y-%m-%d"), queryset_with_search.filter(completed_on__gte=(timezone.now()-timedelta(hours=24))).count()), | ||
| 366 | ("This week's builds", 'completed_on__gte:'+(timezone.now()-timedelta(days=7)).strftime("%Y-%m-%d"), queryset_with_search.filter(completed_on__gte=(timezone.now()-timedelta(days=7))).count()), | ||
| 367 | ] | ||
| 368 | } | ||
| 369 | }, | ||
| 370 | {'name': 'Failed tasks', 'clclass': 'failed_tasks', # specifing a clclass will enable the checkbox | ||
| 371 | 'qhelp': "How many tasks failed during the build", | ||
| 372 | 'filter' : {'class' : 'failed_tasks', | ||
| 373 | 'label': 'Show:', | ||
| 374 | 'options' : [ | ||
| 375 | ('Builds with failed tasks', 'task_build__outcome:4', queryset_with_search.filter(task_build__outcome=4).count()), | ||
| 376 | ('Builds without failed tasks', 'task_build__outcome:NOT4', queryset_with_search.filter(~Q(task_build__outcome=4)).count()), | ||
| 377 | ] | ||
| 378 | } | ||
| 379 | }, | ||
| 380 | {'name': 'Errors', 'clclass': 'errors_no', | ||
| 381 | 'qhelp': "How many errors were encountered during the build (if any)", | ||
| 382 | 'orderfield': _get_toggle_order(request, "errors_no", True), | ||
| 383 | 'ordericon':_get_toggle_order_icon(request, "errors_no"), | ||
| 384 | 'orderkey' : 'errors_no', | ||
| 385 | 'filter' : {'class' : 'errors_no', | ||
| 386 | 'label': 'Show:', | ||
| 387 | 'options' : [ | ||
| 388 | ('Builds with errors', 'errors_no__gte:1', queryset_with_search.filter(errors_no__gte=1).count()), | ||
| 389 | ('Builds without errors', 'errors_no:0', queryset_with_search.filter(errors_no=0).count()), | ||
| 390 | ] | ||
| 391 | } | ||
| 392 | }, | ||
| 393 | {'name': 'Warnings', 'clclass': 'warnings_no', | ||
| 394 | 'qhelp': "How many warnings were encountered during the build (if any)", | ||
| 395 | 'orderfield': _get_toggle_order(request, "warnings_no", True), | ||
| 396 | 'ordericon':_get_toggle_order_icon(request, "warnings_no"), | ||
| 397 | 'orderkey' : 'warnings_no', | ||
| 398 | 'filter' : {'class' : 'warnings_no', | ||
| 399 | 'label': 'Show:', | ||
| 400 | 'options' : [ | ||
| 401 | ('Builds with warnings','warnings_no__gte:1', queryset_with_search.filter(warnings_no__gte=1).count()), | ||
| 402 | ('Builds without warnings','warnings_no:0', queryset_with_search.filter(warnings_no=0).count()), | ||
| 403 | ] | ||
| 404 | } | ||
| 405 | }, | ||
| 406 | {'name': 'Time', 'clclass': 'time', 'hidden' : 1, | ||
| 407 | 'qhelp': "How long it took the build to finish", | ||
| 408 | 'orderfield': _get_toggle_order(request, "timespent", True), | ||
| 409 | 'ordericon':_get_toggle_order_icon(request, "timespent"), | ||
| 410 | 'orderkey' : 'timespent', | ||
| 411 | }, | ||
| 412 | {'name': 'Image files', 'clclass': 'output', | ||
| 413 | 'qhelp': "The root file system types produced by the build. You can find them in your <code>/build/tmp/deploy/images/</code> directory", | ||
| 414 | # TODO: compute image fstypes from Target_Image_File | ||
| 415 | }, | ||
| 416 | ] | ||
| 417 | } | ||
| 418 | |||
| 419 | if not toastermain.settings.MANAGED: | ||
| 420 | context['tablecols'].insert(-2, | ||
| 421 | {'name': 'Log1', | ||
| 422 | 'dclass': "span4", | ||
| 423 | 'qhelp': "Path to the build main log file", | ||
| 424 | 'clclass': 'log', 'hidden': 1, | ||
| 425 | 'orderfield': _get_toggle_order(request, "cooker_log_path"), | ||
| 426 | 'ordericon':_get_toggle_order_icon(request, "cooker_log_path"), | ||
| 427 | 'orderkey' : 'cooker_log_path', | ||
| 428 | } | ||
| 429 | ) | ||
| 430 | |||
| 431 | |||
| 432 | if toastermain.settings.MANAGED: | ||
| 433 | context['tablecols'].append( | ||
| 434 | {'name': 'Project', 'clclass': 'project', | ||
| 435 | 'filter': {'class': 'project', | ||
| 436 | 'label': 'Project:', | ||
| 437 | 'options': map(lambda x: (x.name,'',x.build_set.filter(outcome__lt=Build.IN_PROGRESS).count()), Project.objects.all()), | ||
| 438 | |||
| 439 | } | ||
| 440 | } | ||
| 441 | ) | ||
| 442 | |||
| 443 | |||
| 444 | response = render(request, template, context) | ||
| 445 | _save_parameters_cookies(response, pagesize, orderby, request) | ||
| 446 | return response | ||
| 447 | |||
| 448 | |||
| 449 | ## | 258 | ## |
| 450 | # build dashboard for a single build, coming in as argument | 259 | # build dashboard for a single build, coming in as argument |
| 451 | # Each build may contain multiple targets and each target | 260 | # Each build may contain multiple targets and each target |
| @@ -1895,6 +1704,221 @@ if toastermain.settings.MANAGED: | |||
| 1895 | del request.session['project_id'] | 1704 | del request.session['project_id'] |
| 1896 | return ret | 1705 | return ret |
| 1897 | 1706 | ||
| 1707 | |||
| 1708 | # shows the "all builds" page for managed mode; it displays build requests (at least started!) instead of actual builds | ||
| 1709 | def builds(request): | ||
| 1710 | template = 'managed_builds.html' | ||
| 1711 | # define here what parameters the view needs in the GET portion in order to | ||
| 1712 | # be able to display something. 'count' and 'page' are mandatory for all views | ||
| 1713 | # that use paginators. | ||
| 1714 | |||
| 1715 | # ATTN: we use here the ordering parameters for interactive mode; the translation for BuildRequest fields will happen below | ||
| 1716 | (pagesize, orderby) = _get_parameters_values(request, 10, 'completed_on:-') | ||
| 1717 | mandatory_parameters = { 'count': pagesize, 'page' : 1, 'orderby' : orderby } | ||
| 1718 | retval = _verify_parameters( request.GET, mandatory_parameters ) | ||
| 1719 | if retval: | ||
| 1720 | return _redirect_parameters( 'all-builds', request.GET, mandatory_parameters) | ||
| 1721 | |||
| 1722 | # translate interactive mode ordering to managed mode ordering | ||
| 1723 | ordering_params = orderby.split(":") | ||
| 1724 | if ordering_params[0] == "completed_on": | ||
| 1725 | ordering_params[0] = "updated" | ||
| 1726 | if ordering_params[0] == "started_on": | ||
| 1727 | ordering_params = "created" | ||
| 1728 | |||
| 1729 | request.GET = request.GET.copy() # get a mutable copy of the GET QueryDict | ||
| 1730 | request.GET['orderby'] = ":".join(ordering_params) | ||
| 1731 | |||
| 1732 | # boilerplate code that takes a request for an object type and returns a queryset | ||
| 1733 | # for that object type. copypasta for all needed table searches | ||
| 1734 | (filter_string, search_term, ordering_string) = _search_tuple(request, BuildRequest) | ||
| 1735 | # we don't display in-progress or deleted builds | ||
| 1736 | queryset_all = BuildRequest.objects.exclude(state__lte = BuildRequest.REQ_INPROGRESS).exclude(state=BuildRequest.REQ_DELETED) | ||
| 1737 | queryset_with_search = _get_queryset(BuildRequest, queryset_all, None, search_term, ordering_string, '-updated') | ||
| 1738 | queryset = _get_queryset(BuildRequest, queryset_all, filter_string, search_term, ordering_string, '-updated') | ||
| 1739 | |||
| 1740 | # retrieve the objects that will be displayed in the table; builds a paginator and gets a page range to display | ||
| 1741 | build_info = _build_page_range(Paginator(queryset, pagesize), request.GET.get('page', 1)) | ||
| 1742 | |||
| 1743 | # build view-specific information; this is rendered specifically in the builds page, at the top of the page (i.e. Recent builds) | ||
| 1744 | # most recent build is like projects' most recent builds, but across all projects | ||
| 1745 | build_mru = BuildRequest.objects.all() | ||
| 1746 | build_mru = list(build_mru.filter(Q(state__lt=BuildRequest.REQ_COMPLETED) or Q(state=BuildRequest.REQ_DELETED)).order_by("-pk")) + list(build_mru.filter(state__in=[BuildRequest.REQ_COMPLETED, BuildRequest.REQ_FAILED]).order_by("-pk")[:3]) | ||
| 1747 | |||
| 1748 | fstypes_map = {}; | ||
| 1749 | for build_request in build_info: | ||
| 1750 | # set display variables for build request | ||
| 1751 | build_request.machine = build_request.brvariable_set.get(name="MACHINE").value | ||
| 1752 | build_request.timespent = build_request.updated - build_request.created | ||
| 1753 | |||
| 1754 | # set up list of fstypes for each build | ||
| 1755 | if build_request.build is None: | ||
| 1756 | continue | ||
| 1757 | targets = Target.objects.filter( build_id = build_request.build.id ) | ||
| 1758 | comma = ""; | ||
| 1759 | extensions = ""; | ||
| 1760 | for t in targets: | ||
| 1761 | if ( not t.is_image ): | ||
| 1762 | continue | ||
| 1763 | tif = Target_Image_File.objects.filter( target_id = t.id ) | ||
| 1764 | for i in tif: | ||
| 1765 | s=re.sub('.*tar.bz2', 'tar.bz2', i.file_name) | ||
| 1766 | if s == i.file_name: | ||
| 1767 | s=re.sub('.*\.', '', i.file_name) | ||
| 1768 | if None == re.search(s,extensions): | ||
| 1769 | extensions += comma + s | ||
| 1770 | comma = ", " | ||
| 1771 | fstypes_map[build_request.build.id]=extensions | ||
| 1772 | |||
| 1773 | |||
| 1774 | # send the data to the template | ||
| 1775 | context = { | ||
| 1776 | # specific info for | ||
| 1777 | 'mru' : build_mru, | ||
| 1778 | # TODO: common objects for all table views, adapt as needed | ||
| 1779 | 'objects' : build_info, | ||
| 1780 | 'objectname' : "builds", | ||
| 1781 | 'default_orderby' : 'updated:-', | ||
| 1782 | 'fstypes' : fstypes_map, | ||
| 1783 | 'search_term' : search_term, | ||
| 1784 | 'total_count' : queryset_with_search.count(), | ||
| 1785 | # Specifies the display of columns for the table, appearance in "Edit columns" box, toggling default show/hide, and specifying filters for columns | ||
| 1786 | 'tablecols' : [ | ||
| 1787 | {'name': 'Outcome', # column with a single filter | ||
| 1788 | 'qhelp' : "The outcome tells you if a build successfully completed or failed", # the help button content | ||
| 1789 | 'dclass' : "span2", # indication about column width; comes from the design | ||
| 1790 | 'orderfield': _get_toggle_order(request, "state"), # adds ordering by the field value; default ascending unless clicked from ascending into descending | ||
| 1791 | 'ordericon':_get_toggle_order_icon(request, "state"), | ||
| 1792 | # filter field will set a filter on that column with the specs in the filter description | ||
| 1793 | # the class field in the filter has no relation with clclass; the control different aspects of the UI | ||
| 1794 | # still, it is recommended for the values to be identical for easy tracking in the generated HTML | ||
| 1795 | 'filter' : {'class' : 'outcome', | ||
| 1796 | 'label': 'Show:', | ||
| 1797 | 'options' : [ | ||
| 1798 | ('Successful builds', 'state:' + str(BuildRequest.REQ_COMPLETED), queryset_with_search.filter(state=str(BuildRequest.REQ_COMPLETED)).count()), # this is the field search expression | ||
| 1799 | ('Failed builds', 'state:'+ str(BuildRequest.REQ_FAILED), queryset_with_search.filter(state=str(BuildRequest.REQ_FAILED)).count()), | ||
| 1800 | ] | ||
| 1801 | } | ||
| 1802 | }, | ||
| 1803 | {'name': 'Target', # default column, disabled box, with just the name in the list | ||
| 1804 | 'qhelp': "This is the build target or build targets (i.e. one or more recipes or image recipes)", | ||
| 1805 | 'orderfield': _get_toggle_order(request, "target__target"), | ||
| 1806 | 'ordericon':_get_toggle_order_icon(request, "target__target"), | ||
| 1807 | }, | ||
| 1808 | {'name': 'Machine', | ||
| 1809 | 'qhelp': "The machine is the hardware for which you are building a recipe or image recipe", | ||
| 1810 | 'orderfield': _get_toggle_order(request, "machine"), | ||
| 1811 | 'ordericon':_get_toggle_order_icon(request, "machine"), | ||
| 1812 | 'dclass': 'span3' | ||
| 1813 | }, # a slightly wider column | ||
| 1814 | {'name': 'Started on', 'clclass': 'started_on', 'hidden' : 1, # this is an unchecked box, which hides the column | ||
| 1815 | 'qhelp': "The date and time you started the build", | ||
| 1816 | 'orderfield': _get_toggle_order(request, "created", True), | ||
| 1817 | 'ordericon':_get_toggle_order_icon(request, "created"), | ||
| 1818 | 'filter' : {'class' : 'created', | ||
| 1819 | 'label': 'Show:', | ||
| 1820 | 'options' : [ | ||
| 1821 | ("Today's builds" , 'created__gte:'+timezone.now().strftime("%Y-%m-%d"), queryset_with_search.filter(created__gte=timezone.now()).count()), | ||
| 1822 | ("Yesterday's builds", 'created__gte:'+(timezone.now()-timedelta(hours=24)).strftime("%Y-%m-%d"), queryset_with_search.filter(created__gte=(timezone.now()-timedelta(hours=24))).count()), | ||
| 1823 | ("This week's builds", 'created__gte:'+(timezone.now()-timedelta(days=7)).strftime("%Y-%m-%d"), queryset_with_search.filter(created__gte=(timezone.now()-timedelta(days=7))).count()), | ||
| 1824 | ] | ||
| 1825 | } | ||
| 1826 | }, | ||
| 1827 | {'name': 'Completed on', | ||
| 1828 | 'qhelp': "The date and time the build finished", | ||
| 1829 | 'orderfield': _get_toggle_order(request, "updated", True), | ||
| 1830 | 'ordericon':_get_toggle_order_icon(request, "updated"), | ||
| 1831 | 'orderkey' : 'updated', | ||
| 1832 | 'filter' : {'class' : 'updated', | ||
| 1833 | 'label': 'Show:', | ||
| 1834 | 'options' : [ | ||
| 1835 | ("Today's builds", 'updated__gte:'+timezone.now().strftime("%Y-%m-%d"), queryset_with_search.filter(updated__gte=timezone.now()).count()), | ||
| 1836 | ("Yesterday's builds", 'updated__gte:'+(timezone.now()-timedelta(hours=24)).strftime("%Y-%m-%d"), queryset_with_search.filter(updated__gte=(timezone.now()-timedelta(hours=24))).count()), | ||
| 1837 | ("This week's builds", 'updated__gte:'+(timezone.now()-timedelta(days=7)).strftime("%Y-%m-%d"), queryset_with_search.filter(updated__gte=(timezone.now()-timedelta(days=7))).count()), | ||
| 1838 | ] | ||
| 1839 | } | ||
| 1840 | }, | ||
| 1841 | {'name': 'Failed tasks', 'clclass': 'failed_tasks', # specifing a clclass will enable the checkbox | ||
| 1842 | 'qhelp': "How many tasks failed during the build", | ||
| 1843 | 'filter' : {'class' : 'failed_tasks', | ||
| 1844 | 'label': 'Show:', | ||
| 1845 | 'options' : [ | ||
| 1846 | ('BuildRequests with failed tasks', 'build__task_build__outcome:4', queryset_with_search.filter(build__task_build__outcome=4).count()), | ||
| 1847 | ('BuildRequests without failed tasks', 'build__task_build__outcome:NOT4', queryset_with_search.filter(~Q(build__task_build__outcome=4)).count()), | ||
| 1848 | ] | ||
| 1849 | } | ||
| 1850 | }, | ||
| 1851 | {'name': 'Errors', 'clclass': 'errors_no', | ||
| 1852 | 'qhelp': "How many errors were encountered during the build (if any)", | ||
| 1853 | 'orderfield': _get_toggle_order(request, "errors_no", True), | ||
| 1854 | 'ordericon':_get_toggle_order_icon(request, "errors_no"), | ||
| 1855 | 'orderkey' : 'errors_no', | ||
| 1856 | 'filter' : {'class' : 'errors_no', | ||
| 1857 | 'label': 'Show:', | ||
| 1858 | 'options' : [ | ||
| 1859 | ('BuildRequests with errors', 'errors_no__gte:1', queryset_with_search.filter(build__errors_no__gte=1).count()), | ||
| 1860 | ('BuildRequests without errors', 'errors_no:0', queryset_with_search.filter(build__errors_no=0).count()), | ||
| 1861 | ] | ||
| 1862 | } | ||
| 1863 | }, | ||
| 1864 | {'name': 'Warnings', 'clclass': 'warnings_no', | ||
| 1865 | 'qhelp': "How many warnings were encountered during the build (if any)", | ||
| 1866 | 'orderfield': _get_toggle_order(request, "build__warnings_no", True), | ||
| 1867 | 'ordericon':_get_toggle_order_icon(request, "build__warnings_no"), | ||
| 1868 | 'orderkey' : 'build__warnings_no', | ||
| 1869 | 'filter' : {'class' : 'build__warnings_no', | ||
| 1870 | 'label': 'Show:', | ||
| 1871 | 'options' : [ | ||
| 1872 | ('BuildRequests with warnings','build__warnings_no__gte:1', queryset_with_search.filter(build__warnings_no__gte=1).count()), | ||
| 1873 | ('BuildRequests without warnings','build__warnings_no:0', queryset_with_search.filter(build__warnings_no=0).count()), | ||
| 1874 | ] | ||
| 1875 | } | ||
| 1876 | }, | ||
| 1877 | {'name': 'Time', 'clclass': 'time', 'hidden' : 1, | ||
| 1878 | 'qhelp': "How long it took the build to finish", | ||
| 1879 | 'orderfield': _get_toggle_order(request, "timespent", True), | ||
| 1880 | 'ordericon':_get_toggle_order_icon(request, "timespent"), | ||
| 1881 | 'orderkey' : 'timespent', | ||
| 1882 | }, | ||
| 1883 | {'name': 'Image files', 'clclass': 'output', | ||
| 1884 | 'qhelp': "The root file system types produced by the build. You can find them in your <code>/build/tmp/deploy/images/</code> directory", | ||
| 1885 | # TODO: compute image fstypes from Target_Image_File | ||
| 1886 | }, | ||
| 1887 | ] | ||
| 1888 | } | ||
| 1889 | |||
| 1890 | if not toastermain.settings.MANAGED: | ||
| 1891 | context['tablecols'].insert(-2, | ||
| 1892 | {'name': 'Log1', | ||
| 1893 | 'dclass': "span4", | ||
| 1894 | 'qhelp': "Path to the build main log file", | ||
| 1895 | 'clclass': 'log', 'hidden': 1, | ||
| 1896 | 'orderfield': _get_toggle_order(request, "cooker_log_path"), | ||
| 1897 | 'ordericon':_get_toggle_order_icon(request, "cooker_log_path"), | ||
| 1898 | 'orderkey' : 'cooker_log_path', | ||
| 1899 | } | ||
| 1900 | ) | ||
| 1901 | |||
| 1902 | |||
| 1903 | if toastermain.settings.MANAGED: | ||
| 1904 | context['tablecols'].append( | ||
| 1905 | {'name': 'Project', 'clclass': 'project', | ||
| 1906 | 'filter': {'class': 'project', | ||
| 1907 | 'label': 'Project:', | ||
| 1908 | 'options': map(lambda x: (x.name,'',x.build_set.filter(outcome__lt=BuildRequest.REQ_INPROGRESS).count()), Project.objects.all()), | ||
| 1909 | |||
| 1910 | } | ||
| 1911 | } | ||
| 1912 | ) | ||
| 1913 | |||
| 1914 | |||
| 1915 | response = render(request, template, context) | ||
| 1916 | _save_parameters_cookies(response, pagesize, orderby, request) | ||
| 1917 | return response | ||
| 1918 | |||
| 1919 | |||
| 1920 | |||
| 1921 | |||
| 1898 | # new project | 1922 | # new project |
| 1899 | def newproject(request): | 1923 | def newproject(request): |
| 1900 | template = "newproject.html" | 1924 | template = "newproject.html" |
| @@ -2819,21 +2843,21 @@ if toastermain.settings.MANAGED: | |||
| 2819 | 'filter' : {'class' : 'failed_tasks', | 2843 | 'filter' : {'class' : 'failed_tasks', |
| 2820 | 'label': 'Show:', | 2844 | 'label': 'Show:', |
| 2821 | 'options' : [ | 2845 | 'options' : [ |
| 2822 | ('Builds with failed tasks', 'task_build__outcome:4', queryset_with_search.filter(task_build__outcome=4).count()), | 2846 | ('Builds with failed tasks', 'build__task_build__outcome:4', queryset_with_search.filter(build__task_build__outcome=4).count()), |
| 2823 | ('Builds without failed tasks', 'task_build__outcome:NOT4', queryset_with_search.filter(~Q(task_build__outcome=4)).count()), | 2847 | ('Builds without failed tasks', 'build__task_build__outcome:NOT4', queryset_with_search.filter(~Q(build__task_build__outcome=4)).count()), |
| 2824 | ] | 2848 | ] |
| 2825 | } | 2849 | } |
| 2826 | }, | 2850 | }, |
| 2827 | {'name': 'Errors', 'clclass': 'errors_no', | 2851 | {'name': 'Errors', 'clclass': 'errors_no', |
| 2828 | 'qhelp': "How many errors were encountered during the build (if any)", | 2852 | 'qhelp': "How many errors were encountered during the build (if any)", |
| 2829 | 'orderfield': _get_toggle_order(request, "errors_no", True), | 2853 | 'orderfield': _get_toggle_order(request, "build__errors_no", True), |
| 2830 | 'ordericon':_get_toggle_order_icon(request, "errors_no"), | 2854 | 'ordericon':_get_toggle_order_icon(request, "build__errors_no"), |
| 2831 | 'orderkey' : 'errors_no', | 2855 | 'orderkey' : 'build__errors_no', |
| 2832 | 'filter' : {'class' : 'errors_no', | 2856 | 'filter' : {'class' : 'build__errors_no', |
| 2833 | 'label': 'Show:', | 2857 | 'label': 'Show:', |
| 2834 | 'options' : [ | 2858 | 'options' : [ |
| 2835 | ('Builds with errors', 'errors_no__gte:1', queryset_with_search.filter(errors_no__gte=1).count()), | 2859 | ('Builds with errors', 'build__errors_no__gte:1', queryset_with_search.filter(build__errors_no__gte=1).count()), |
| 2836 | ('Builds without errors', 'errors_no:0', queryset_with_search.filter(errors_no=0).count()), | 2860 | ('Builds without errors', 'build__errors_no:0', queryset_with_search.filter(build__errors_no=0).count()), |
| 2837 | ] | 2861 | ] |
| 2838 | } | 2862 | } |
| 2839 | }, | 2863 | }, |
| @@ -3007,6 +3031,199 @@ else: | |||
| 3007 | "DEBUG" : toastermain.settings.DEBUG | 3031 | "DEBUG" : toastermain.settings.DEBUG |
| 3008 | } | 3032 | } |
| 3009 | 3033 | ||
| 3034 | |||
| 3035 | # shows the "all builds" page for interactive mode; this is the old code, simply moved | ||
| 3036 | def builds(request): | ||
| 3037 | template = 'build.html' | ||
| 3038 | # define here what parameters the view needs in the GET portion in order to | ||
| 3039 | # be able to display something. 'count' and 'page' are mandatory for all views | ||
| 3040 | # that use paginators. | ||
| 3041 | (pagesize, orderby) = _get_parameters_values(request, 10, 'completed_on:-') | ||
| 3042 | mandatory_parameters = { 'count': pagesize, 'page' : 1, 'orderby' : orderby } | ||
| 3043 | retval = _verify_parameters( request.GET, mandatory_parameters ) | ||
| 3044 | if retval: | ||
| 3045 | return _redirect_parameters( 'all-builds', request.GET, mandatory_parameters) | ||
| 3046 | |||
| 3047 | # boilerplate code that takes a request for an object type and returns a queryset | ||
| 3048 | # for that object type. copypasta for all needed table searches | ||
| 3049 | (filter_string, search_term, ordering_string) = _search_tuple(request, Build) | ||
| 3050 | queryset_all = Build.objects.exclude(outcome = Build.IN_PROGRESS) | ||
| 3051 | queryset_with_search = _get_queryset(Build, queryset_all, None, search_term, ordering_string, '-completed_on') | ||
| 3052 | queryset = _get_queryset(Build, queryset_all, filter_string, search_term, ordering_string, '-completed_on') | ||
| 3053 | |||
| 3054 | # retrieve the objects that will be displayed in the table; builds a paginator and gets a page range to display | ||
| 3055 | build_info = _build_page_range(Paginator(queryset, pagesize), request.GET.get('page', 1)) | ||
| 3056 | |||
| 3057 | # build view-specific information; this is rendered specifically in the builds page, at the top of the page (i.e. Recent builds) | ||
| 3058 | build_mru = Build.objects.order_by("-started_on")[:3] | ||
| 3059 | |||
| 3060 | # set up list of fstypes for each build | ||
| 3061 | fstypes_map = {}; | ||
| 3062 | for build in build_info: | ||
| 3063 | targets = Target.objects.filter( build_id = build.id ) | ||
| 3064 | comma = ""; | ||
| 3065 | extensions = ""; | ||
| 3066 | for t in targets: | ||
| 3067 | if ( not t.is_image ): | ||
| 3068 | continue | ||
| 3069 | tif = Target_Image_File.objects.filter( target_id = t.id ) | ||
| 3070 | for i in tif: | ||
| 3071 | s=re.sub('.*tar.bz2', 'tar.bz2', i.file_name) | ||
| 3072 | if s == i.file_name: | ||
| 3073 | s=re.sub('.*\.', '', i.file_name) | ||
| 3074 | if None == re.search(s,extensions): | ||
| 3075 | extensions += comma + s | ||
| 3076 | comma = ", " | ||
| 3077 | fstypes_map[build.id]=extensions | ||
| 3078 | |||
| 3079 | # send the data to the template | ||
| 3080 | context = { | ||
| 3081 | # specific info for | ||
| 3082 | 'mru' : build_mru, | ||
| 3083 | # TODO: common objects for all table views, adapt as needed | ||
| 3084 | 'objects' : build_info, | ||
| 3085 | 'objectname' : "builds", | ||
| 3086 | 'default_orderby' : 'completed_on:-', | ||
| 3087 | 'fstypes' : fstypes_map, | ||
| 3088 | 'search_term' : search_term, | ||
| 3089 | 'total_count' : queryset_with_search.count(), | ||
| 3090 | # Specifies the display of columns for the table, appearance in "Edit columns" box, toggling default show/hide, and specifying filters for columns | ||
| 3091 | 'tablecols' : [ | ||
| 3092 | {'name': 'Outcome', # column with a single filter | ||
| 3093 | 'qhelp' : "The outcome tells you if a build successfully completed or failed", # the help button content | ||
| 3094 | 'dclass' : "span2", # indication about column width; comes from the design | ||
| 3095 | 'orderfield': _get_toggle_order(request, "outcome"), # adds ordering by the field value; default ascending unless clicked from ascending into descending | ||
| 3096 | 'ordericon':_get_toggle_order_icon(request, "outcome"), | ||
| 3097 | # filter field will set a filter on that column with the specs in the filter description | ||
| 3098 | # the class field in the filter has no relation with clclass; the control different aspects of the UI | ||
| 3099 | # still, it is recommended for the values to be identical for easy tracking in the generated HTML | ||
| 3100 | 'filter' : {'class' : 'outcome', | ||
| 3101 | 'label': 'Show:', | ||
| 3102 | 'options' : [ | ||
| 3103 | ('Successful builds', 'outcome:' + str(Build.SUCCEEDED), queryset_with_search.filter(outcome=str(Build.SUCCEEDED)).count()), # this is the field search expression | ||
| 3104 | ('Failed builds', 'outcome:'+ str(Build.FAILED), queryset_with_search.filter(outcome=str(Build.FAILED)).count()), | ||
| 3105 | ] | ||
| 3106 | } | ||
| 3107 | }, | ||
| 3108 | {'name': 'Target', # default column, disabled box, with just the name in the list | ||
| 3109 | 'qhelp': "This is the build target or build targets (i.e. one or more recipes or image recipes)", | ||
| 3110 | 'orderfield': _get_toggle_order(request, "target__target"), | ||
| 3111 | 'ordericon':_get_toggle_order_icon(request, "target__target"), | ||
| 3112 | }, | ||
| 3113 | {'name': 'Machine', | ||
| 3114 | 'qhelp': "The machine is the hardware for which you are building a recipe or image recipe", | ||
| 3115 | 'orderfield': _get_toggle_order(request, "machine"), | ||
| 3116 | 'ordericon':_get_toggle_order_icon(request, "machine"), | ||
| 3117 | 'dclass': 'span3' | ||
| 3118 | }, # a slightly wider column | ||
| 3119 | {'name': 'Started on', 'clclass': 'started_on', 'hidden' : 1, # this is an unchecked box, which hides the column | ||
| 3120 | 'qhelp': "The date and time you started the build", | ||
| 3121 | 'orderfield': _get_toggle_order(request, "started_on", True), | ||
| 3122 | 'ordericon':_get_toggle_order_icon(request, "started_on"), | ||
| 3123 | 'filter' : {'class' : 'started_on', | ||
| 3124 | 'label': 'Show:', | ||
| 3125 | 'options' : [ | ||
| 3126 | ("Today's builds" , 'started_on__gte:'+timezone.now().strftime("%Y-%m-%d"), queryset_with_search.filter(started_on__gte=timezone.now()).count()), | ||
| 3127 | ("Yesterday's builds", 'started_on__gte:'+(timezone.now()-timedelta(hours=24)).strftime("%Y-%m-%d"), queryset_with_search.filter(started_on__gte=(timezone.now()-timedelta(hours=24))).count()), | ||
| 3128 | ("This week's builds", 'started_on__gte:'+(timezone.now()-timedelta(days=7)).strftime("%Y-%m-%d"), queryset_with_search.filter(started_on__gte=(timezone.now()-timedelta(days=7))).count()), | ||
| 3129 | ] | ||
| 3130 | } | ||
| 3131 | }, | ||
| 3132 | {'name': 'Completed on', | ||
| 3133 | 'qhelp': "The date and time the build finished", | ||
| 3134 | 'orderfield': _get_toggle_order(request, "completed_on", True), | ||
| 3135 | 'ordericon':_get_toggle_order_icon(request, "completed_on"), | ||
| 3136 | 'orderkey' : 'completed_on', | ||
| 3137 | 'filter' : {'class' : 'completed_on', | ||
| 3138 | 'label': 'Show:', | ||
| 3139 | 'options' : [ | ||
| 3140 | ("Today's builds", 'completed_on__gte:'+timezone.now().strftime("%Y-%m-%d"), queryset_with_search.filter(completed_on__gte=timezone.now()).count()), | ||
| 3141 | ("Yesterday's builds", 'completed_on__gte:'+(timezone.now()-timedelta(hours=24)).strftime("%Y-%m-%d"), queryset_with_search.filter(completed_on__gte=(timezone.now()-timedelta(hours=24))).count()), | ||
| 3142 | ("This week's builds", 'completed_on__gte:'+(timezone.now()-timedelta(days=7)).strftime("%Y-%m-%d"), queryset_with_search.filter(completed_on__gte=(timezone.now()-timedelta(days=7))).count()), | ||
| 3143 | ] | ||
| 3144 | } | ||
| 3145 | }, | ||
| 3146 | {'name': 'Failed tasks', 'clclass': 'failed_tasks', # specifing a clclass will enable the checkbox | ||
| 3147 | 'qhelp': "How many tasks failed during the build", | ||
| 3148 | 'filter' : {'class' : 'failed_tasks', | ||
| 3149 | 'label': 'Show:', | ||
| 3150 | 'options' : [ | ||
| 3151 | ('Builds with failed tasks', 'task_build__outcome:4', queryset_with_search.filter(task_build__outcome=4).count()), | ||
| 3152 | ('Builds without failed tasks', 'task_build__outcome:NOT4', queryset_with_search.filter(~Q(task_build__outcome=4)).count()), | ||
| 3153 | ] | ||
| 3154 | } | ||
| 3155 | }, | ||
| 3156 | {'name': 'Errors', 'clclass': 'errors_no', | ||
| 3157 | 'qhelp': "How many errors were encountered during the build (if any)", | ||
| 3158 | 'orderfield': _get_toggle_order(request, "errors_no", True), | ||
| 3159 | 'ordericon':_get_toggle_order_icon(request, "errors_no"), | ||
| 3160 | 'orderkey' : 'errors_no', | ||
| 3161 | 'filter' : {'class' : 'errors_no', | ||
| 3162 | 'label': 'Show:', | ||
| 3163 | 'options' : [ | ||
| 3164 | ('Builds with errors', 'errors_no__gte:1', queryset_with_search.filter(errors_no__gte=1).count()), | ||
| 3165 | ('Builds without errors', 'errors_no:0', queryset_with_search.filter(errors_no=0).count()), | ||
| 3166 | ] | ||
| 3167 | } | ||
| 3168 | }, | ||
| 3169 | {'name': 'Warnings', 'clclass': 'warnings_no', | ||
| 3170 | 'qhelp': "How many warnings were encountered during the build (if any)", | ||
| 3171 | 'orderfield': _get_toggle_order(request, "warnings_no", True), | ||
| 3172 | 'ordericon':_get_toggle_order_icon(request, "warnings_no"), | ||
| 3173 | 'orderkey' : 'warnings_no', | ||
| 3174 | 'filter' : {'class' : 'warnings_no', | ||
| 3175 | 'label': 'Show:', | ||
| 3176 | 'options' : [ | ||
| 3177 | ('Builds with warnings','warnings_no__gte:1', queryset_with_search.filter(warnings_no__gte=1).count()), | ||
| 3178 | ('Builds without warnings','warnings_no:0', queryset_with_search.filter(warnings_no=0).count()), | ||
| 3179 | ] | ||
| 3180 | } | ||
| 3181 | }, | ||
| 3182 | {'name': 'Time', 'clclass': 'time', 'hidden' : 1, | ||
| 3183 | 'qhelp': "How long it took the build to finish", | ||
| 3184 | 'orderfield': _get_toggle_order(request, "timespent", True), | ||
| 3185 | 'ordericon':_get_toggle_order_icon(request, "timespent"), | ||
| 3186 | 'orderkey' : 'timespent', | ||
| 3187 | }, | ||
| 3188 | {'name': 'Image files', 'clclass': 'output', | ||
| 3189 | 'qhelp': "The root file system types produced by the build. You can find them in your <code>/build/tmp/deploy/images/</code> directory", | ||
| 3190 | # TODO: compute image fstypes from Target_Image_File | ||
| 3191 | }, | ||
| 3192 | ] | ||
| 3193 | } | ||
| 3194 | |||
| 3195 | if not toastermain.settings.MANAGED: | ||
| 3196 | context['tablecols'].insert(-2, | ||
| 3197 | {'name': 'Log1', | ||
| 3198 | 'dclass': "span4", | ||
| 3199 | 'qhelp': "Path to the build main log file", | ||
| 3200 | 'clclass': 'log', 'hidden': 1, | ||
| 3201 | 'orderfield': _get_toggle_order(request, "cooker_log_path"), | ||
| 3202 | 'ordericon':_get_toggle_order_icon(request, "cooker_log_path"), | ||
| 3203 | 'orderkey' : 'cooker_log_path', | ||
| 3204 | } | ||
| 3205 | ) | ||
| 3206 | |||
| 3207 | |||
| 3208 | if toastermain.settings.MANAGED: | ||
| 3209 | context['tablecols'].append( | ||
| 3210 | {'name': 'Project', 'clclass': 'project', | ||
| 3211 | 'filter': {'class': 'project', | ||
| 3212 | 'label': 'Project:', | ||
| 3213 | 'options': map(lambda x: (x.name,'',x.build_set.filter(outcome__lt=Build.IN_PROGRESS).count()), Project.objects.all()), | ||
| 3214 | |||
| 3215 | } | ||
| 3216 | } | ||
| 3217 | ) | ||
| 3218 | |||
| 3219 | |||
| 3220 | response = render(request, template, context) | ||
| 3221 | _save_parameters_cookies(response, pagesize, orderby, request) | ||
| 3222 | return response | ||
| 3223 | |||
| 3224 | |||
| 3225 | |||
| 3226 | |||
| 3010 | def newproject(request): | 3227 | def newproject(request): |
| 3011 | raise Exception("page not available in interactive mode") | 3228 | raise Exception("page not available in interactive mode") |
| 3012 | 3229 | ||
