diff options
| -rw-r--r-- | bitbake/lib/toaster/orm/models.py | 49 | ||||
| -rw-r--r-- | bitbake/lib/toaster/toastergui/static/css/default.css | 60 | ||||
| -rw-r--r-- | bitbake/lib/toaster/toastergui/static/js/projectapp.js | 531 | ||||
| -rw-r--r-- | bitbake/lib/toaster/toastergui/templates/base.html | 12 | ||||
| -rw-r--r-- | bitbake/lib/toaster/toastergui/templates/baseprojectpage.html | 8 | ||||
| -rw-r--r-- | bitbake/lib/toaster/toastergui/templates/build.html | 7 | ||||
| -rw-r--r-- | bitbake/lib/toaster/toastergui/templates/layers.html | 8 | ||||
| -rw-r--r-- | bitbake/lib/toaster/toastergui/templates/newproject.html | 7 | ||||
| -rw-r--r-- | bitbake/lib/toaster/toastergui/templates/project.html | 652 | ||||
| -rw-r--r-- | bitbake/lib/toaster/toastergui/urls.py | 2 | ||||
| -rwxr-xr-x | bitbake/lib/toaster/toastergui/views.py | 237 |
11 files changed, 1144 insertions, 429 deletions
diff --git a/bitbake/lib/toaster/orm/models.py b/bitbake/lib/toaster/orm/models.py index 1b3bb22e79..1521717482 100644 --- a/bitbake/lib/toaster/orm/models.py +++ b/bitbake/lib/toaster/orm/models.py | |||
| @@ -76,21 +76,25 @@ class Project(models.Model): | |||
| 76 | def schedule_build(self): | 76 | def schedule_build(self): |
| 77 | from bldcontrol.models import BuildRequest, BRTarget, BRLayer, BRVariable, BRBitbake | 77 | from bldcontrol.models import BuildRequest, BRTarget, BRLayer, BRVariable, BRBitbake |
| 78 | br = BuildRequest.objects.create(project = self) | 78 | br = BuildRequest.objects.create(project = self) |
| 79 | try: | ||
| 79 | 80 | ||
| 80 | BRBitbake.objects.create(req = br, | 81 | BRBitbake.objects.create(req = br, |
| 81 | giturl = self.bitbake_version.giturl, | 82 | giturl = self.bitbake_version.giturl, |
| 82 | commit = self.bitbake_version.branch, | 83 | commit = self.bitbake_version.branch, |
| 83 | dirpath = self.bitbake_version.dirpath) | 84 | dirpath = self.bitbake_version.dirpath) |
| 84 | 85 | ||
| 85 | for l in self.projectlayer_set.all(): | 86 | for l in self.projectlayer_set.all(): |
| 86 | BRLayer.objects.create(req = br, name = l.layercommit.layer.name, giturl = l.layercommit.layer.vcs_url, commit = l.layercommit.commit, dirpath = l.layercommit.dirpath) | 87 | BRLayer.objects.create(req = br, name = l.layercommit.layer.name, giturl = l.layercommit.layer.vcs_url, commit = l.layercommit.commit, dirpath = l.layercommit.dirpath) |
| 87 | for t in self.projecttarget_set.all(): | 88 | for t in self.projecttarget_set.all(): |
| 88 | BRTarget.objects.create(req = br, target = t.target, task = t.task) | 89 | BRTarget.objects.create(req = br, target = t.target, task = t.task) |
| 89 | for v in self.projectvariable_set.all(): | 90 | for v in self.projectvariable_set.all(): |
| 90 | BRVariable.objects.create(req = br, name = v.name, value = v.value) | 91 | BRVariable.objects.create(req = br, name = v.name, value = v.value) |
| 91 | 92 | ||
| 92 | br.state = BuildRequest.REQ_QUEUED | 93 | br.state = BuildRequest.REQ_QUEUED |
| 93 | br.save() | 94 | br.save() |
| 95 | except Exception as e: | ||
| 96 | br.delete() | ||
| 97 | raise e | ||
| 94 | return br | 98 | return br |
| 95 | 99 | ||
| 96 | class Build(models.Model): | 100 | class Build(models.Model): |
| @@ -131,7 +135,7 @@ class Build(models.Model): | |||
| 131 | 135 | ||
| 132 | def eta(self): | 136 | def eta(self): |
| 133 | from django.utils import timezone | 137 | from django.utils import timezone |
| 134 | eta = 0 | 138 | eta = timezone.now() |
| 135 | completeper = self.completeper() | 139 | completeper = self.completeper() |
| 136 | if self.completeper() > 0: | 140 | if self.completeper() > 0: |
| 137 | eta = timezone.now() + ((timezone.now() - self.started_on)*(100-completeper)/completeper) | 141 | eta = timezone.now() + ((timezone.now() - self.started_on)*(100-completeper)/completeper) |
| @@ -534,12 +538,16 @@ class LayerIndexLayerSource(LayerSource): | |||
| 534 | def _get_json_response(apiurl = self.apiurl): | 538 | def _get_json_response(apiurl = self.apiurl): |
| 535 | import httplib, urlparse, json | 539 | import httplib, urlparse, json |
| 536 | parsedurl = urlparse.urlparse(apiurl) | 540 | parsedurl = urlparse.urlparse(apiurl) |
| 537 | (host, port) = parsedurl.netloc.split(":") | 541 | try: |
| 542 | (host, port) = parsedurl.netloc.split(":") | ||
| 543 | except ValueError: | ||
| 544 | host = parsedurl.netloc | ||
| 545 | port = None | ||
| 546 | |||
| 538 | if port is None: | 547 | if port is None: |
| 539 | port = 80 | 548 | port = 80 |
| 540 | else: | 549 | else: |
| 541 | port = int(port) | 550 | port = int(port) |
| 542 | #print "-- connect to: http://%s:%s%s?%s" % (host, port, parsedurl.path, parsedurl.query) | ||
| 543 | conn = httplib.HTTPConnection(host, port) | 551 | conn = httplib.HTTPConnection(host, port) |
| 544 | conn.request("GET", parsedurl.path + "?" + parsedurl.query) | 552 | conn.request("GET", parsedurl.path + "?" + parsedurl.query) |
| 545 | r = conn.getresponse() | 553 | r = conn.getresponse() |
| @@ -550,8 +558,9 @@ class LayerIndexLayerSource(LayerSource): | |||
| 550 | # verify we can get the basic api | 558 | # verify we can get the basic api |
| 551 | try: | 559 | try: |
| 552 | apilinks = _get_json_response() | 560 | apilinks = _get_json_response() |
| 553 | except: | 561 | except Exception as e: |
| 554 | print "EE: could not connect to %s, skipping update" % self.apiurl | 562 | import traceback |
| 563 | print "EE: could not connect to %s, skipping update: %s\n%s" % (self.apiurl, e, traceback.format_exc(e)) | ||
| 555 | return | 564 | return |
| 556 | 565 | ||
| 557 | # update branches; only those that we already have names listed in the database | 566 | # update branches; only those that we already have names listed in the database |
| @@ -582,7 +591,7 @@ class LayerIndexLayerSource(LayerSource): | |||
| 582 | 591 | ||
| 583 | # update layerbranches/layer_versions | 592 | # update layerbranches/layer_versions |
| 584 | layerbranches_info = _get_json_response(apilinks['layerBranches'] | 593 | layerbranches_info = _get_json_response(apilinks['layerBranches'] |
| 585 | + "?filter=branch:%s" % "OR".join(map(lambda x: str(x.up_id), Branch.objects.filter(layer_source = self))) | 594 | + "?filter=branch:%s" % "OR".join(map(lambda x: str(x.up_id), [i for i in Branch.objects.filter(layer_source = self) if i.up_id is not None] )) |
| 586 | ) | 595 | ) |
| 587 | for lbi in layerbranches_info: | 596 | for lbi in layerbranches_info: |
| 588 | lv, created = Layer_Version.objects.get_or_create(layer_source = self, | 597 | lv, created = Layer_Version.objects.get_or_create(layer_source = self, |
diff --git a/bitbake/lib/toaster/toastergui/static/css/default.css b/bitbake/lib/toaster/toastergui/static/css/default.css index 8e0df591df..8780c4f23d 100644 --- a/bitbake/lib/toaster/toastergui/static/css/default.css +++ b/bitbake/lib/toaster/toastergui/static/css/default.css | |||
| @@ -8,10 +8,14 @@ | |||
| 8 | 8 | ||
| 9 | /* Styles for the help information */ | 9 | /* Styles for the help information */ |
| 10 | .get-help { color: #CCCCCC; } | 10 | .get-help { color: #CCCCCC; } |
| 11 | .get-help:hover { color: #999999; cursor: pointer; } | 11 | .get-help:hover, .icon-plus-sign:hover { color: #999999; cursor: pointer; } |
| 12 | .get-help-blue { color: #3A87AD; } | 12 | .get-help-blue { color: #3A87AD; } |
| 13 | .get-help-blue:hover { color: #005580; cursor: pointer; } | 13 | .get-help-blue:hover { color: #005580; cursor: pointer; } |
| 14 | .manual { margin-top: 11px; } | 14 | .get-help-yellow { color: #C09853; } |
| 15 | .get-help-yellow:hover { color: #B38942; cursor: pointer; } | ||
| 16 | .get-help-red { color: #B94A48; font-size: 16px; padding-left: 2px; } | ||
| 17 | .get-help-red:hover { color: #943A38; cursor: pointer; } | ||
| 18 | .manual { margin: 11px 15px;} | ||
| 15 | .heading-help { font-size: 14px; } | 19 | .heading-help { font-size: 14px; } |
| 16 | 20 | ||
| 17 | /* Styles for the external link */ | 21 | /* Styles for the external link */ |
| @@ -44,6 +48,7 @@ dd p { line-height: 20px; } | |||
| 44 | 48 | ||
| 45 | /* Some extra space before headings when needed */ | 49 | /* Some extra space before headings when needed */ |
| 46 | .details { margin-top: 30px; } | 50 | .details { margin-top: 30px; } |
| 51 | .air { margin-top: 30px; } | ||
| 47 | 52 | ||
| 48 | /* Required classes for the highlight behaviour in tables */ | 53 | /* Required classes for the highlight behaviour in tables */ |
| 49 | .highlight { -webkit-animation: target-fade 10s 1; -moz-animation: target-fade 10s 1; animation: target-fade 10s 1; } | 54 | .highlight { -webkit-animation: target-fade 10s 1; -moz-animation: target-fade 10s 1; animation: target-fade 10s 1; } |
| @@ -96,6 +101,10 @@ th > a, th > span { font-weight: normal; } | |||
| 96 | .content-directory a:hover { color: #005580; text-decoration: underline; } | 101 | .content-directory a:hover { color: #005580; text-decoration: underline; } |
| 97 | .symlink { color: #CCCCCC; } | 102 | .symlink { color: #CCCCCC; } |
| 98 | 103 | ||
| 104 | /* Styles for the navbar actions */ | ||
| 105 | .btn-group + .btn-group { margin-right: 10px; } | ||
| 106 | .navbar-inner > .btn-group { margin-top: 6px; } | ||
| 107 | |||
| 99 | /* Other styles */ | 108 | /* Other styles */ |
| 100 | .dropdown-menu { padding: 10px; } | 109 | .dropdown-menu { padding: 10px; } |
| 101 | select { width: auto; } | 110 | select { width: auto; } |
| @@ -104,6 +113,7 @@ select { width: auto; } | |||
| 104 | .progress { margin-bottom: 0px; } | 113 | .progress { margin-bottom: 0px; } |
| 105 | .lead .badge { font-size: 18px; font-weight: normal; border-radius: 15px; padding: 9px; } | 114 | .lead .badge { font-size: 18px; font-weight: normal; border-radius: 15px; padding: 9px; } |
| 106 | .well > .lead, .alert .lead { margin-bottom: 0px; } | 115 | .well > .lead, .alert .lead { margin-bottom: 0px; } |
| 116 | .well-transparent { background-color: transparent; } | ||
| 107 | .no-results { margin: 10px 0; } | 117 | .no-results { margin: 10px 0; } |
| 108 | .task-name { margin-left: 7px; } | 118 | .task-name { margin-left: 7px; } |
| 109 | .icon-hand-right {color: #CCCCCC; } | 119 | .icon-hand-right {color: #CCCCCC; } |
| @@ -119,9 +129,14 @@ select { width: auto; } | |||
| 119 | /* Configuration styles */ | 129 | /* Configuration styles */ |
| 120 | .icon-trash { color: #B94A48; font-size: 16px; padding-left: 2px; } | 130 | .icon-trash { color: #B94A48; font-size: 16px; padding-left: 2px; } |
| 121 | .icon-trash:hover { color: #943A38; text-decoration: none; cursor: pointer; } | 131 | .icon-trash:hover { color: #943A38; text-decoration: none; cursor: pointer; } |
| 122 | .icon-pencil, .icon-download-alt { font-size: 16px; color: #0088CC; padding-left: 2px; } | 132 | .icon-pencil, .icon-download-alt, .icon-refresh, .icon-star-empty, .icon-star, .icon-tasks { font-size: 16px; color: #0088CC; padding-left: 2px; } |
| 123 | .icon-pencil:hover, .icon-download-alt:hover { color: #005580; text-decoration: none; cursor: pointer; } | 133 | .icon-pencil:hover, .icon-download-alt:hover, .icon-refresh:hover, .icon-star-empty:hover, .icon-star:hover, .icon-tasks:hover { color: #005580; text-decoration: none; cursor: pointer; } |
| 124 | .configuration-list li { line-height: 35px; font-size: 21px; font-weight: 200; } | 134 | .icon-share { padding-left: 2px; } |
| 135 | .alert-success .icon-refresh, .alert-success .icon-tasks { color: #468847; } | ||
| 136 | .alert-success .icon-refresh:hover, .alert-success .icon-tasks:hover { color: #347132; } | ||
| 137 | .alert-error .icon-refresh, .alert-error .icon-tasks { color: #b94a48; } | ||
| 138 | .alert-error .icon-refresh:hover, .alert-error .icon-tasks:hover { color: #943A38; } | ||
| 139 | .configuration-list li, .configuration-list label { line-height: 35px; font-size: 21px; font-weight: 200; margin-bottom: 0px;} | ||
| 125 | .configuration-list { font-size: 16px; margin-bottom: 1.5em; } | 140 | .configuration-list { font-size: 16px; margin-bottom: 1.5em; } |
| 126 | .configuration-list i { font-size: 16px; } | 141 | .configuration-list i { font-size: 16px; } |
| 127 | /*.configuration-layers { height: 135px; overflow: scroll; }*/ | 142 | /*.configuration-layers { height: 135px; overflow: scroll; }*/ |
| @@ -132,15 +147,46 @@ select { width: auto; } | |||
| 132 | .configuration-alert p { margin-bottom: 0px; } | 147 | .configuration-alert p { margin-bottom: 0px; } |
| 133 | fieldset { padding-left: 19px; } | 148 | fieldset { padding-left: 19px; } |
| 134 | .project-form { margin-top: 10px; } | 149 | .project-form { margin-top: 10px; } |
| 135 | .add-layers .btn-block + .btn-block { margin-top: 0px; } | 150 | .add-layers .btn-block + .btn-block, .build .btn-block + .btn-block { margin-top: 0px; } |
| 136 | input.huge { font-size: 17.5px; padding: 11px 19px; } | 151 | input.huge { font-size: 17.5px; padding: 11px 19px; } |
| 137 | .build-form { margin-bottom: 0px; padding-left: 20px; } | 152 | .build-form { margin-bottom: 0px; } |
| 153 | .build-form .input-append { margin-bottom: 0px; } | ||
| 154 | .build-form .btn-large { padding: 11px 35px; } | ||
| 155 | .build-form p { font-size:17.5px ;margin:12px 0 0 10px;} | ||
| 156 | .btn-primary .icon-question-sign, .btn-danger .icon-question-sign { color: #fff; } | ||
| 157 | .btn-primary .icon-question-sign:hover, .btn-danger .icon-question-sign:hover { color: #999; } | ||
| 138 | a code { color: #0088CC; } | 158 | a code { color: #0088CC; } |
| 139 | a code:hover { color: #005580; } | 159 | a code:hover { color: #005580; } |
| 140 | .localconf { font-size: 17.5px; margin-top: 40px; } | 160 | .localconf { font-size: 17.5px; margin-top: 40px; } |
| 141 | .localconf code { font-size: 17.5px; } | 161 | .localconf code { font-size: 17.5px; } |
| 142 | #add-layer-dependencies { margin-top: 5px; } | 162 | #add-layer-dependencies { margin-top: 5px; } |
| 163 | .link-action { font-size: 17.5px; margin-top: 40px; } | ||
| 164 | .link-action code { font-size: 17.5px; } | ||
| 143 | .artifact { width: 9em; } | 165 | .artifact { width: 9em; } |
| 144 | .control-group { margin-bottom: 0px; } | 166 | .control-group { margin-bottom: 0px; } |
| 145 | #project-details form { margin: 0px; } | 167 | #project-details form { margin: 0px; } |
| 146 | dd form { margin: 10px 0 0 0; } | 168 | dd form { margin: 10px 0 0 0; } |
| 169 | dd form { margin-bottom: 0px; } | ||
| 170 | dl textarea { resize: vertical; } | ||
| 171 | .navbar-fixed-top { z-index: 1; } | ||
| 172 | .popover { z-index: 2; } | ||
| 173 | .btn-danger .icon-trash { color: #fff; } | ||
| 174 | .bbappends { list-style-type: none; margin-left: 0; } | ||
| 175 | .bbappends li { line-height: 25px; } | ||
| 176 | .configuration-list input[type="checkbox"] { margin-top:13px;margin-right:10px; } | ||
| 177 | .alert input[type="checkbox"] { margin-top: 0px; margin-right: 3px; } | ||
| 178 | .alert ol { padding: 10px 0px 0px 20px; } | ||
| 179 | .alert ol > li { line-height: 35px; } | ||
| 180 | .dl-vertical form { margin-top: 10px; } | ||
| 181 | .scrolling { border: 1px solid #dddddd; height: 154px; overflow: auto; padding: 8px; width: 27.5%; margin-bottom: 10px; } | ||
| 182 | .lead .help-block { font-size: 14px; line-height: 20px; font-weight: normal; } | ||
| 183 | .button-place .btn { margin: 0 0 20px 0; } | ||
| 184 | .tooltip-inner { max-width: 250px; } | ||
| 185 | dd > span { line-height: 20px; } | ||
| 186 | .new-build { padding: 20px; } | ||
| 187 | .new-build li { line-height: 30px; } | ||
| 188 | .new-build h6 { margin: 10px 0 0 0; color: #5a5a5a; } | ||
| 189 | .new-build h3 { margin: 0; color: #5a5a5a; } | ||
| 190 | .new-build form { margin: 5px 0 0; } | ||
| 191 | .new-build .input-append { margin-bottom: 0; } | ||
| 192 | #build-selected { margin-top: 15px; } | ||
diff --git a/bitbake/lib/toaster/toastergui/static/js/projectapp.js b/bitbake/lib/toaster/toastergui/static/js/projectapp.js new file mode 100644 index 0000000000..e674d8ffd1 --- /dev/null +++ b/bitbake/lib/toaster/toastergui/static/js/projectapp.js | |||
| @@ -0,0 +1,531 @@ | |||
| 1 | // vim: set tabstop=4 expandtab ai: | ||
| 2 | // BitBake Toaster Implementation | ||
| 3 | // | ||
| 4 | // Copyright (C) 2013 Intel Corporation | ||
| 5 | // | ||
| 6 | // This program is free software; you can redistribute it and/or modify | ||
| 7 | // it under the terms of the GNU General Public License version 2 as | ||
| 8 | // published by the Free Software Foundation. | ||
| 9 | // | ||
| 10 | // This program is distributed in the hope that it will be useful, | ||
| 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 13 | // GNU General Public License for more details. | ||
| 14 | // | ||
| 15 | // You should have received a copy of the GNU General Public License along | ||
| 16 | // with this program; if not, write to the Free Software Foundation, Inc., | ||
| 17 | // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | ||
| 18 | |||
| 19 | angular_formpost = function($httpProvider) { | ||
| 20 | // Use x-www-form-urlencoded Content-Type | ||
| 21 | // By Ezekiel Victor, http://victorblog.com/2012/12/20/make-angularjs-http-service-behave-like-jquery-ajax/, no license, with attribution | ||
| 22 | $httpProvider.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=utf-8'; | ||
| 23 | |||
| 24 | /** | ||
| 25 | * The workhorse; converts an object to x-www-form-urlencoded serialization. | ||
| 26 | * @param {Object} obj | ||
| 27 | * @return {String} | ||
| 28 | */ | ||
| 29 | var param = function(obj) { | ||
| 30 | var query = '', name, value, fullSubName, subName, subValue, innerObj, i; | ||
| 31 | |||
| 32 | for(name in obj) { | ||
| 33 | value = obj[name]; | ||
| 34 | |||
| 35 | if(value instanceof Array) { | ||
| 36 | for(i=0; i<value.length; ++i) { | ||
| 37 | subValue = value[i]; | ||
| 38 | fullSubName = name + '[' + i + ']'; | ||
| 39 | innerObj = {}; | ||
| 40 | innerObj[fullSubName] = subValue; | ||
| 41 | query += param(innerObj) + '&'; | ||
| 42 | } | ||
| 43 | } | ||
| 44 | else if(value instanceof Object) { | ||
| 45 | for(subName in value) { | ||
| 46 | subValue = value[subName]; | ||
| 47 | fullSubName = name + '[' + subName + ']'; | ||
| 48 | innerObj = {}; | ||
| 49 | innerObj[fullSubName] = subValue; | ||
| 50 | query += param(innerObj) + '&'; | ||
| 51 | } | ||
| 52 | } | ||
| 53 | else if(value !== undefined && value !== null) | ||
| 54 | query += encodeURIComponent(name) + '=' + encodeURIComponent(value) + '&'; | ||
| 55 | } | ||
| 56 | |||
| 57 | return query.length ? query.substr(0, query.length - 1) : query; | ||
| 58 | }; | ||
| 59 | |||
| 60 | // Override $http service's default transformRequest | ||
| 61 | $httpProvider.defaults.transformRequest = [function(data) { | ||
| 62 | return angular.isObject(data) && String(data) !== '[object File]' ? param(data) : data; | ||
| 63 | }]; | ||
| 64 | } | ||
| 65 | |||
| 66 | |||
| 67 | /** | ||
| 68 | * Helper to execute callback on elements from array differences; useful for incremental UI updating. | ||
| 69 | * @param {Array} oldArray | ||
| 70 | * @param {Array} newArray | ||
| 71 | * @param {function} compareElements | ||
| 72 | * @param {function} onAdded | ||
| 73 | * @param {function} onDeleted | ||
| 74 | * | ||
| 75 | * no return | ||
| 76 | */ | ||
| 77 | function _diffArrays(oldArray, newArray, compareElements, onAdded, onDeleted ) { | ||
| 78 | if (onDeleted !== undefined) { | ||
| 79 | oldArray.filter(function (e) { var found = 0; newArray.map(function (f) { if (compareElements(e, f)) {found = 1};}); return !found;}).map(onDeleted); | ||
| 80 | } | ||
| 81 | if (onAdded !== undefined) { | ||
| 82 | newArray.filter(function (e) { var found = 0; oldArray.map(function (f) { if (compareElements(e, f)) {found = 1};}); return !found;}).map(onAdded); | ||
| 83 | } | ||
| 84 | } | ||
| 85 | |||
| 86 | |||
| 87 | var projectApp = angular.module('project', ['ui.bootstrap', 'ngCookies'], angular_formpost); | ||
| 88 | |||
| 89 | // modify the template tag markers to prevent conflicts with Django | ||
| 90 | projectApp.config(function($interpolateProvider) { | ||
| 91 | $interpolateProvider.startSymbol("{["); | ||
| 92 | $interpolateProvider.endSymbol("]}"); | ||
| 93 | }); | ||
| 94 | |||
| 95 | // main controller for the project page | ||
| 96 | projectApp.controller('prjCtrl', function($scope, $modal, $http, $interval, $location, $cookies, $q, $sce) { | ||
| 97 | |||
| 98 | $scope.getSuggestions = function(type, currentValue) { | ||
| 99 | var deffered = $q.defer(); | ||
| 100 | |||
| 101 | $http({method:"GET", url: $scope.urls.xhr_datatypeahead, params : { type: type, value: currentValue}}) | ||
| 102 | .success(function (_data) { | ||
| 103 | if (_data.error != "ok") { | ||
| 104 | alert(_data.error); | ||
| 105 | deffered.reject(_data.error); | ||
| 106 | } | ||
| 107 | deffered.resolve(_data.list); | ||
| 108 | }); | ||
| 109 | |||
| 110 | return deffered.promise; | ||
| 111 | } | ||
| 112 | |||
| 113 | var inXHRcall = false; | ||
| 114 | |||
| 115 | // default handling of XHR calls that handles errors and updates commonly-used pages | ||
| 116 | $scope._makeXHRCall = function(callparams) { | ||
| 117 | if (inXHRcall) { | ||
| 118 | if (callparams.data === undefined) { | ||
| 119 | // we simply skip the data refresh calls | ||
| 120 | console.log("race on XHR, aborted"); | ||
| 121 | return; | ||
| 122 | } else { | ||
| 123 | // we return a promise that we'll solve by reissuing the command later | ||
| 124 | var delayed = $q.defer(); | ||
| 125 | console.log("race on XHR, delayed"); | ||
| 126 | $interval(function () {$scope._makeXHRCall(callparams).then(function (d) { delayed.resolve(d); });}, 100, 1); | ||
| 127 | |||
| 128 | return delayed.promise; | ||
| 129 | } | ||
| 130 | |||
| 131 | } | ||
| 132 | var deffered = $q.defer(); | ||
| 133 | |||
| 134 | if (undefined === callparams.headers) { callparams.headers = {} }; | ||
| 135 | callparams.headers['X-CSRFToken'] = $cookies.csrftoken; | ||
| 136 | |||
| 137 | $http(callparams).success(function(_data, _status, _headers, _config) { | ||
| 138 | if (_data.error != "ok") { | ||
| 139 | alert("Failed XHR request (" + _status + "): " + _data.error); | ||
| 140 | console.error("Failed XHR request: ", _data, _status, _headers, _config); | ||
| 141 | deffered.reject(_data.error); | ||
| 142 | } | ||
| 143 | else { | ||
| 144 | // TODO: update screen data if we have fields here | ||
| 145 | |||
| 146 | if (_data.builds !== undefined) { | ||
| 147 | |||
| 148 | var oldbuilds = $scope.builds; | ||
| 149 | $scope.builds = _data.builds; | ||
| 150 | |||
| 151 | // identify canceled builds here, so we can display them. | ||
| 152 | _diffArrays(oldbuilds, $scope.builds, | ||
| 153 | function (e,f) { return e.status == f.status && e.id == f.id }, // compare | ||
| 154 | undefined, // added | ||
| 155 | function (e) { // deleted | ||
| 156 | if (e.status == "deleted") return; | ||
| 157 | e.status = "deleted"; | ||
| 158 | for (var i = 0; i < $scope.builds.length; i++) { | ||
| 159 | if ($scope.builds[i].status == "queued" && $scope.builds[i].id > e.id) | ||
| 160 | continue; | ||
| 161 | $scope.builds.splice(i, 0, e); | ||
| 162 | break; | ||
| 163 | } | ||
| 164 | }); | ||
| 165 | |||
| 166 | } | ||
| 167 | if (_data.layers !== undefined) { | ||
| 168 | var oldlayers = $scope.layers; | ||
| 169 | $scope.layers = _data.layers; | ||
| 170 | |||
| 171 | // show added/deleted layer notifications | ||
| 172 | var addedLayers = []; | ||
| 173 | var deletedLayers = []; | ||
| 174 | _diffArrays( oldlayers, $scope.layers, function (e, f) { return e.id == f.id }, | ||
| 175 | function (e) { console.log("new layer", e);addedLayers.push(e); }, | ||
| 176 | function (e) { console.log("del layer", e);deletedLayers.push(e); }); | ||
| 177 | |||
| 178 | if (addedLayers.length > 0) { | ||
| 179 | $scope.displayAlert($scope.zone2alerts, "You have added <b>"+addedLayers.length+"</b> layer" + ((addedLayers.length>1)?"s: ":": ") + addedLayers.map(function (e) { return "<a href=\""+e.layerdetailurl+"\">"+e.name+"</a>" }).join(", "), "alert-info"); | ||
| 180 | } | ||
| 181 | if (deletedLayers.length > 0) { | ||
| 182 | $scope.displayAlert($scope.zone2alerts, "You have deleted <b>"+deletedLayers.length+"</b> layer" + ((deletedLayers.length>1)?"s: ":": ") + deletedLayers.map(function (e) { return "<a href=\""+e.layerdetailurl+"\">"+e.name+"</a>" }).join(", "), "alert-info"); | ||
| 183 | } | ||
| 184 | |||
| 185 | } | ||
| 186 | if (_data.targets !== undefined) { | ||
| 187 | $scope.targets = _data.targets; | ||
| 188 | } | ||
| 189 | if (_data.machine !== undefined) { | ||
| 190 | $scope.machine = _data.machine; | ||
| 191 | } | ||
| 192 | if (_data.user !== undefined) { | ||
| 193 | $scope.user = _data.user; | ||
| 194 | } | ||
| 195 | |||
| 196 | if (_data.prj !== undefined) { | ||
| 197 | $scope.project = _data.prj; | ||
| 198 | |||
| 199 | // update breadcrumb, outside the controller | ||
| 200 | $('#project_name').text($scope.project.name); | ||
| 201 | } | ||
| 202 | |||
| 203 | $scope.validateData(); | ||
| 204 | inXHRcall = false; | ||
| 205 | deffered.resolve(_data); | ||
| 206 | } | ||
| 207 | }).error(function(_data, _status, _headers, _config) { | ||
| 208 | alert("Failed HTTP XHR request (" + _status + ")" + _data); | ||
| 209 | console.error("Failed HTTP XHR request: ", _data, _status, _headers, _config); | ||
| 210 | inXHRcall = false; | ||
| 211 | deffered.reject(_data.error); | ||
| 212 | }); | ||
| 213 | |||
| 214 | return deffered.promise; | ||
| 215 | } | ||
| 216 | |||
| 217 | $scope.layeralert = undefined; | ||
| 218 | // shows user alerts on invalid project data | ||
| 219 | $scope.validateData = function () { | ||
| 220 | if ($scope.layers.length == 0) { | ||
| 221 | $scope.layeralert = $scope.displayAlert($scope.zone1alerts, "You need to add some layers to this project. <a href=\""+$scope.urls.layers+"\">View all layers available in Toaster</a> or <a href=\""+$scope.urls.importlayer+"\">import a layer</a>"); | ||
| 222 | } else { | ||
| 223 | if ($scope.layeralert != undefined) { | ||
| 224 | $scope.layeralert.close(); | ||
| 225 | $scope.layeralert = undefined; | ||
| 226 | } | ||
| 227 | } | ||
| 228 | } | ||
| 229 | |||
| 230 | $scope.targetExistingBuild = function(targets) { | ||
| 231 | var oldTargetName = $scope.targetName; | ||
| 232 | $scope.targetName = targets.map(function(v,i,a){return v.target}).join(' '); | ||
| 233 | $scope.targetNamedBuild(); | ||
| 234 | $scope.targetName = oldTargetName; | ||
| 235 | } | ||
| 236 | |||
| 237 | $scope.targetNamedBuild = function(target) { | ||
| 238 | if ($scope.targetName === undefined){ | ||
| 239 | alert("No target defined, please type in a target name"); | ||
| 240 | return; | ||
| 241 | } | ||
| 242 | |||
| 243 | $scope.sanitizeTargetName(); | ||
| 244 | |||
| 245 | $scope._makeXHRCall({ | ||
| 246 | method: "POST", url: $scope.urls.xhr_build, | ||
| 247 | data : { | ||
| 248 | targets: $scope.targetName | ||
| 249 | } | ||
| 250 | }).then(function (data) { | ||
| 251 | console.log("received ", data); | ||
| 252 | $scope.targetName = undefined; | ||
| 253 | }); | ||
| 254 | } | ||
| 255 | |||
| 256 | $scope.sanitizeTargetName = function() { | ||
| 257 | if (undefined === $scope.targetName) return; | ||
| 258 | $scope.targetName = $scope.targetName.replace(/\[.*\]/, '').trim(); | ||
| 259 | } | ||
| 260 | |||
| 261 | $scope.buildCancel = function(id) { | ||
| 262 | $scope._makeXHRCall({ | ||
| 263 | method: "POST", url: $scope.urls.xhr_build, | ||
| 264 | data: { | ||
| 265 | buildCancel: id, | ||
| 266 | } | ||
| 267 | }); | ||
| 268 | } | ||
| 269 | |||
| 270 | $scope.onLayerSelect = function (item, model, label) { | ||
| 271 | $scope.layerAddId = item.id; | ||
| 272 | } | ||
| 273 | |||
| 274 | $scope.layerAdd = function() { | ||
| 275 | |||
| 276 | $http({method:"GET", url: $scope.urls.xhr_datatypeahead, params : { type: "layerdeps", value: $scope.layerAddId }}) | ||
| 277 | .success(function (_data) { | ||
| 278 | if (_data.error != "ok") { | ||
| 279 | alert(_data.error); | ||
| 280 | } else { | ||
| 281 | if (_data.list.length > 0) { | ||
| 282 | // activate modal | ||
| 283 | var modalInstance = $modal.open({ | ||
| 284 | templateUrl: 'dependencies_modal', | ||
| 285 | controller: function ($scope, $modalInstance, items, layerAddName) { | ||
| 286 | $scope.items = items; | ||
| 287 | $scope.layerAddName = layerAddName; | ||
| 288 | $scope.selectedItems = (function () { s = {}; for (var i = 0; i < items.length; i++) { s[items[i].id] = true; };return s; })(); | ||
| 289 | |||
| 290 | $scope.ok = function() { | ||
| 291 | console.log("scope selected is ", $scope.selectedItems); | ||
| 292 | $modalInstance.close(Object.keys($scope.selectedItems).filter(function (e) { return $scope.selectedItems[e];})); | ||
| 293 | }; | ||
| 294 | |||
| 295 | $scope.cancel = function() { | ||
| 296 | $modalInstance.dismiss('cancel'); | ||
| 297 | }; | ||
| 298 | |||
| 299 | $scope.update = function() { | ||
| 300 | console.log("updated ", $scope.selectedItems); | ||
| 301 | }; | ||
| 302 | }, | ||
| 303 | resolve: { | ||
| 304 | items: function () { | ||
| 305 | return _data.list; | ||
| 306 | }, | ||
| 307 | layerAddName: function () { | ||
| 308 | return $scope.layerAddName; | ||
| 309 | }, | ||
| 310 | } | ||
| 311 | }); | ||
| 312 | |||
| 313 | modalInstance.result.then(function (selectedArray) { | ||
| 314 | selectedArray.push($scope.layerAddId); | ||
| 315 | console.log("selected", selectedArray); | ||
| 316 | |||
| 317 | $scope._makeXHRCall({ | ||
| 318 | method: "POST", url: $scope.urls.xhr_edit, | ||
| 319 | data: { | ||
| 320 | layerAdd: selectedArray.join(","), | ||
| 321 | } | ||
| 322 | }).then(function () { | ||
| 323 | $scope.layerAddName = undefined; | ||
| 324 | }); | ||
| 325 | }); | ||
| 326 | } | ||
| 327 | else { | ||
| 328 | $scope._makeXHRCall({ | ||
| 329 | method: "POST", url: $scope.urls.xhr_edit, | ||
| 330 | data: { | ||
| 331 | layerAdd: $scope.layerAddId, | ||
| 332 | } | ||
| 333 | }).then(function () { | ||
| 334 | $scope.layerAddName = undefined; | ||
| 335 | }); | ||
| 336 | } | ||
| 337 | } | ||
| 338 | }); | ||
| 339 | } | ||
| 340 | |||
| 341 | $scope.layerDel = function(id) { | ||
| 342 | $scope._makeXHRCall({ | ||
| 343 | method: "POST", url: $scope.urls.xhr_edit, | ||
| 344 | data: { | ||
| 345 | layerDel: id, | ||
| 346 | } | ||
| 347 | }); | ||
| 348 | } | ||
| 349 | |||
| 350 | |||
| 351 | $scope.test = function(elementid) { | ||
| 352 | $http({method:"GET", url: $scope.urls.xhr_datatypeahead, params : { type: "versionlayers", value: $scope.projectVersion }}). | ||
| 353 | success(function (_data) { | ||
| 354 | if (_data.error != "ok") { | ||
| 355 | alert (_data.error); | ||
| 356 | } | ||
| 357 | else { | ||
| 358 | if (_data.list.length > 0) { | ||
| 359 | // activate modal | ||
| 360 | var modalInstance = $modal.open({ | ||
| 361 | templateUrl: 'change_version_modal', | ||
| 362 | controller: function ($scope, $modalInstance, items, releaseName) { | ||
| 363 | $scope.items = items; | ||
| 364 | $scope.releaseName = releaseName; | ||
| 365 | |||
| 366 | $scope.ok = function() { | ||
| 367 | $modalInstance.close(); | ||
| 368 | }; | ||
| 369 | |||
| 370 | $scope.cancel = function() { | ||
| 371 | $modalInstance.dismiss('cancel'); | ||
| 372 | }; | ||
| 373 | |||
| 374 | }, | ||
| 375 | resolve: { | ||
| 376 | items: function () { | ||
| 377 | return _data.list; | ||
| 378 | }, | ||
| 379 | releaseName: function () { | ||
| 380 | return $scope.releases.filter(function (e) { if (e.id == $scope.projectVersion) return e;})[0].name; | ||
| 381 | }, | ||
| 382 | } | ||
| 383 | }); | ||
| 384 | |||
| 385 | modalInstance.result.then(function () { $scope.edit(elementid)}); | ||
| 386 | } else { | ||
| 387 | $scope.edit(elementid); | ||
| 388 | } | ||
| 389 | } | ||
| 390 | }); | ||
| 391 | } | ||
| 392 | |||
| 393 | $scope.edit = function(elementid) { | ||
| 394 | var data = {}; | ||
| 395 | console.log("edit with ", elementid); | ||
| 396 | var alertText = undefined; | ||
| 397 | var alertZone = undefined; | ||
| 398 | switch(elementid) { | ||
| 399 | case '#select-machine': | ||
| 400 | alertText = "You have changed the machine to: <b>" + $scope.machineName + "</b>"; | ||
| 401 | alertZone = $scope.zone2alerts; | ||
| 402 | data['machineName'] = $scope.machineName; | ||
| 403 | break; | ||
| 404 | case '#change-project-name': | ||
| 405 | data['projectName'] = $scope.projectName; | ||
| 406 | alertText = "You have changed the project name to: <b>" + $scope.projectName + "</b>"; | ||
| 407 | alertZone = $scope.zone3alerts; | ||
| 408 | break; | ||
| 409 | case '#change-project-version': | ||
| 410 | data['projectVersion'] = $scope.projectVersion; | ||
| 411 | alertText = "You have changed the release to: "; | ||
| 412 | alertZone = $scope.zone3alerts; | ||
| 413 | break; | ||
| 414 | default: | ||
| 415 | throw "FIXME: implement conversion for element " + elementid; | ||
| 416 | } | ||
| 417 | |||
| 418 | console.log("calling edit with ", data); | ||
| 419 | $scope._makeXHRCall({ | ||
| 420 | method: "POST", url: $scope.urls.xhr_edit, data: data, | ||
| 421 | }).then( function () { | ||
| 422 | $scope.toggle(elementid); | ||
| 423 | if (data['projectVersion'] != undefined) { | ||
| 424 | alertText += "<b>" + $scope.release.name + "</b>"; | ||
| 425 | } | ||
| 426 | $scope.displayAlert(alertZone, alertText, "alert-info"); | ||
| 427 | }); | ||
| 428 | } | ||
| 429 | |||
| 430 | |||
| 431 | $scope.executeCommands = function() { | ||
| 432 | cmd = $location.path(); | ||
| 433 | |||
| 434 | function _cmdExecuteWithParam(param, f) { | ||
| 435 | if (cmd.indexOf(param)==0) { | ||
| 436 | if (cmd.indexOf("=") > -1) { | ||
| 437 | var parameter = cmd.split("=", 2)[1]; | ||
| 438 | if (parameter != undefined && parameter.length > 0) { | ||
| 439 | f(parameter); | ||
| 440 | } | ||
| 441 | } else { | ||
| 442 | f(); | ||
| 443 | }; | ||
| 444 | } | ||
| 445 | } | ||
| 446 | |||
| 447 | _cmdExecuteWithParam("/newproject", function () { | ||
| 448 | $scope.displayAlert($scope.zone1alerts, | ||
| 449 | "Your project <strong>" + $scope.project.name + | ||
| 450 | "</strong> has been created. You can now <a href=\""+ $scope.urls.layers + | ||
| 451 | "\">add layers</a> and <a href=\""+ $scope.urls.targets + | ||
| 452 | "\">select targets</a> you want to build.", "alert-success"); | ||
| 453 | }); | ||
| 454 | |||
| 455 | _cmdExecuteWithParam("/targetbuild=", function (targets) { | ||
| 456 | var oldTargetName = $scope.targetName; | ||
| 457 | $scope.targetName = targets.split(",").join(" "); | ||
| 458 | $scope.targetNamedBuild(); | ||
| 459 | $scope.targetName = oldTargetName; | ||
| 460 | }); | ||
| 461 | |||
| 462 | _cmdExecuteWithParam("/machineselect=", function (machine) { | ||
| 463 | $scope.machineName = machine; | ||
| 464 | $scope.toggle('#select-machine'); | ||
| 465 | }); | ||
| 466 | |||
| 467 | |||
| 468 | _cmdExecuteWithParam("/layeradd=", function (layer) { | ||
| 469 | angular.forEach(layer.split(","), function (l) { | ||
| 470 | $scope.layerAddId = l; | ||
| 471 | $scope.layerAdd(); | ||
| 472 | }); | ||
| 473 | }); | ||
| 474 | } | ||
| 475 | |||
| 476 | $scope.displayAlert = function(zone, text, type) { | ||
| 477 | if (zone.maxid === undefined) { zone.maxid = 0; } | ||
| 478 | var crtid = zone.maxid ++; | ||
| 479 | angular.forEach(zone, function (o) { o.close() }); | ||
| 480 | o = { | ||
| 481 | id: crtid, text: $sce.trustAsHtml(text), type: type, | ||
| 482 | close: function() { | ||
| 483 | zone.splice((function(id){ for (var i = 0; i < zone.length; i++) if (id == zone[i].id) { return i}; return undefined;})(crtid), 1); | ||
| 484 | }, | ||
| 485 | } | ||
| 486 | zone.push(o); | ||
| 487 | return o; | ||
| 488 | } | ||
| 489 | |||
| 490 | $scope.toggle = function(id) { | ||
| 491 | $scope.projectName = $scope.project.name; | ||
| 492 | $scope.projectVersion = $scope.project.release.id; | ||
| 493 | $scope.machineName = $scope.machine.name; | ||
| 494 | |||
| 495 | angular.element(id).toggle(); | ||
| 496 | angular.element(id+"-opposite").toggle(); | ||
| 497 | } | ||
| 498 | |||
| 499 | $scope.selectedMostBuildTargets = function () { | ||
| 500 | keys = Object.keys($scope.mostBuiltTargets); | ||
| 501 | keys = keys.filter(function (e) { if ($scope.mostBuiltTargets[e]) return e }); | ||
| 502 | return keys.length == 0; | ||
| 503 | |||
| 504 | } | ||
| 505 | |||
| 506 | // init code | ||
| 507 | // | ||
| 508 | $scope.init = function() { | ||
| 509 | $scope.pollHandle = $interval(function () { $scope._makeXHRCall({method: "POST", url: $scope.urls.xhr_edit, data: undefined});}, 4000, 0); | ||
| 510 | } | ||
| 511 | |||
| 512 | $scope.init(); | ||
| 513 | }); | ||
| 514 | |||
| 515 | |||
| 516 | /** | ||
| 517 | TESTING CODE | ||
| 518 | */ | ||
| 519 | |||
| 520 | function test_diff_arrays() { | ||
| 521 | _diffArrays([1,2,3], [2,3,4], function(e,f) { return e==f; }, function(e) {console.log("added", e)}, function(e) {console.log("deleted", e);}) | ||
| 522 | } | ||
| 523 | |||
| 524 | var s = undefined; | ||
| 525 | |||
| 526 | function test_set_alert(text) { | ||
| 527 | s = angular.element("div#main").scope(); | ||
| 528 | s.displayAlert(s.zone3alerts, text); | ||
| 529 | console.log(s.zone3alerts); | ||
| 530 | s.$digest(); | ||
| 531 | } | ||
diff --git a/bitbake/lib/toaster/toastergui/templates/base.html b/bitbake/lib/toaster/toastergui/templates/base.html index 9ef249aab3..d414bfbbde 100644 --- a/bitbake/lib/toaster/toastergui/templates/base.html +++ b/bitbake/lib/toaster/toastergui/templates/base.html | |||
| @@ -61,18 +61,6 @@ function reload_params(params) { | |||
| 61 | {%if MANAGED %} | 61 | {%if MANAGED %} |
| 62 | <div class="btn-group pull-right"> | 62 | <div class="btn-group pull-right"> |
| 63 | <a class="btn" href="{% url 'newproject' %}">New project</a> | 63 | <a class="btn" href="{% url 'newproject' %}">New project</a> |
| 64 | <button class="btn dropdown-toggle" data-toggle="dropdown"> | ||
| 65 | <i class="icon-caret-down"></i> | ||
| 66 | </button> | ||
| 67 | <ul class="dropdown-menu"> | ||
| 68 | {% for prj in projects %} | ||
| 69 | <li><a href="{% url 'project' prj.id %}">{{prj.name}}</a></li> | ||
| 70 | {% endfor %} | ||
| 71 | <li><hr/></li> | ||
| 72 | <li><a href="#">Clone project</a></li> | ||
| 73 | <li><a href="#">Export project</a></li> | ||
| 74 | <li><a href="#">Import project</a></li> | ||
| 75 | </ul> | ||
| 76 | </div> | 64 | </div> |
| 77 | {%endif%} | 65 | {%endif%} |
| 78 | <a class="pull-right manual" target="_blank" href="http://www.yoctoproject.org/documentation/toaster-manual"> | 66 | <a class="pull-right manual" target="_blank" href="http://www.yoctoproject.org/documentation/toaster-manual"> |
diff --git a/bitbake/lib/toaster/toastergui/templates/baseprojectpage.html b/bitbake/lib/toaster/toastergui/templates/baseprojectpage.html index 54edaaf27c..95a9f470ba 100644 --- a/bitbake/lib/toaster/toastergui/templates/baseprojectpage.html +++ b/bitbake/lib/toaster/toastergui/templates/baseprojectpage.html | |||
| @@ -12,7 +12,7 @@ | |||
| 12 | {% block parentbreadcrumb %} | 12 | {% block parentbreadcrumb %} |
| 13 | {% if project %} | 13 | {% if project %} |
| 14 | <li> | 14 | <li> |
| 15 | <a href="{%url 'project' project.id %}">{{project.name}} | 15 | <a href="{%url 'project' project.id %}"><span id="project_name">{{project.name}}</span> |
| 16 | </a> | 16 | </a> |
| 17 | </li> | 17 | </li> |
| 18 | {% endif %} | 18 | {% endif %} |
| @@ -28,11 +28,11 @@ | |||
| 28 | </script> | 28 | </script> |
| 29 | </div> | 29 | </div> |
| 30 | 30 | ||
| 31 | <div class="row-fluid"> | 31 | <div> |
| 32 | 32 | ||
| 33 | <!-- Begin right container --> | 33 | <!-- Begin main page container --> |
| 34 | {% block projectinfomain %}{% endblock %} | 34 | {% block projectinfomain %}{% endblock %} |
| 35 | <!-- End right container --> | 35 | <!-- End main container --> |
| 36 | 36 | ||
| 37 | 37 | ||
| 38 | </div> | 38 | </div> |
diff --git a/bitbake/lib/toaster/toastergui/templates/build.html b/bitbake/lib/toaster/toastergui/templates/build.html index faabd22f8b..bef1f15399 100644 --- a/bitbake/lib/toaster/toastergui/templates/build.html +++ b/bitbake/lib/toaster/toastergui/templates/build.html | |||
| @@ -127,6 +127,13 @@ | |||
| 127 | <a href="{%url "builddashboard" build.id%}#images">{{fstypes|get_dict_value:build.id}}</a> | 127 | <a href="{%url "builddashboard" build.id%}#images">{{fstypes|get_dict_value:build.id}}</a> |
| 128 | {% endif %} | 128 | {% endif %} |
| 129 | </td> | 129 | </td> |
| 130 | {% if MANAGED %} | ||
| 131 | <td class="project"> | ||
| 132 | {% if build.project %} | ||
| 133 | <a href="{% url 'project' build.project.id %}">{{build.project.name}}</a> | ||
| 134 | {% endif %} | ||
| 135 | </td> | ||
| 136 | {% endif %} | ||
| 130 | </tr> | 137 | </tr> |
| 131 | 138 | ||
| 132 | {% endfor %} | 139 | {% endfor %} |
diff --git a/bitbake/lib/toaster/toastergui/templates/layers.html b/bitbake/lib/toaster/toastergui/templates/layers.html index 281b72aec5..b32a7ed2e2 100644 --- a/bitbake/lib/toaster/toastergui/templates/layers.html +++ b/bitbake/lib/toaster/toastergui/templates/layers.html | |||
| @@ -85,14 +85,6 @@ | |||
| 85 | </div> | 85 | </div> |
| 86 | </div> | 86 | </div> |
| 87 | 87 | ||
| 88 | <script src="assets/js/jquery-1.9.1.min.js" type='text/javascript'></script> | ||
| 89 | <script src="assets/js/jquery.tablesorter.min.js" type='text/javascript'></script> | ||
| 90 | <script src="assets/js/jquery-ui-1.10.3.custom.min.js"></script> | ||
| 91 | <script src="assets/js/bootstrap.min.js" type='text/javascript'></script> | ||
| 92 | <script src="assets/js/prettify.js" type='text/javascript'></script> | ||
| 93 | <script src="assets/js/jit.js" type='text/javascript'></script> | ||
| 94 | <script src="assets/js/main.js" type='text/javascript'></script> | ||
| 95 | |||
| 96 | <script> | 88 | <script> |
| 97 | $(document).ready(function() { | 89 | $(document).ready(function() { |
| 98 | 90 | ||
diff --git a/bitbake/lib/toaster/toastergui/templates/newproject.html b/bitbake/lib/toaster/toastergui/templates/newproject.html index 12c4e9f07f..43c4e28bee 100644 --- a/bitbake/lib/toaster/toastergui/templates/newproject.html +++ b/bitbake/lib/toaster/toastergui/templates/newproject.html | |||
| @@ -17,13 +17,6 @@ | |||
| 17 | <label>Project name <span class="muted">(required)</span></label> | 17 | <label>Project name <span class="muted">(required)</span></label> |
| 18 | <input type="text" class="input-xlarge" required name="projectname" value="{{projectname}}"> | 18 | <input type="text" class="input-xlarge" required name="projectname" value="{{projectname}}"> |
| 19 | <label class="project-form"> | 19 | <label class="project-form"> |
| 20 | Project owner | ||
| 21 | <i class="icon-question-sign get-help" title="The go-to person for this project"></i> | ||
| 22 | </label> | ||
| 23 | <input type="text" name="username" value="{{username}}"> | ||
| 24 | <label class="project-form">Owner's email</label> | ||
| 25 | <input type="email" class="input-large" name="email" value="{{email}}"> | ||
| 26 | <label class="project-form"> | ||
| 27 | Yocto Project version | 20 | Yocto Project version |
| 28 | <i class="icon-question-sign get-help" title="This sets the branch for the Yocto Project core layers (meta, meta-yocto and meta-yocto-bsp), and for the layers you use from the OpenEmbedded Metadata Index"></i> | 21 | <i class="icon-question-sign get-help" title="This sets the branch for the Yocto Project core layers (meta, meta-yocto and meta-yocto-bsp), and for the layers you use from the OpenEmbedded Metadata Index"></i> |
| 29 | </label> | 22 | </label> |
diff --git a/bitbake/lib/toaster/toastergui/templates/project.html b/bitbake/lib/toaster/toastergui/templates/project.html index c3a470c54a..9399b312ca 100644 --- a/bitbake/lib/toaster/toastergui/templates/project.html +++ b/bitbake/lib/toaster/toastergui/templates/project.html | |||
| @@ -1,366 +1,354 @@ | |||
| 1 | {% extends "base.html" %} | 1 | {% extends "baseprojectpage.html" %} |
| 2 | <!-- | ||
| 3 | vim: expandtab tabstop=2 | ||
| 4 | --> | ||
| 2 | {% load projecttags %} | 5 | {% load projecttags %} |
| 3 | {% load humanize %} | 6 | {% load humanize %} |
| 4 | {% block pagecontent %} | 7 | {% load static %} |
| 5 | |||
| 6 | <script> | ||
| 7 | |||
| 8 | var buildrequests = []; | ||
| 9 | |||
| 10 | function targetInPage(targetname) { | ||
| 11 | return targetname in $("ul#target-list > li > a").map(function (i, x) {return x.text}); | ||
| 12 | } | ||
| 13 | |||
| 14 | function setEventHandlers() { | ||
| 15 | $("i#del-target-icon").unbind().click(function (evt) { | ||
| 16 | console.log("del target", evt.target.attributes["x-data"].value); | ||
| 17 | postEditAjaxRequest({"targetDel": evt.target.attributes["x-data"].value}); | ||
| 18 | }); | ||
| 19 | $("button#add-target-button").unbind().click( function (evt) { | ||
| 20 | if ( $("input#target")[0].value.length == 0) { | ||
| 21 | alert("cannot add empty target"); | ||
| 22 | return; | ||
| 23 | } | ||
| 24 | postEditAjaxRequest({"targetAdd" : $("input#target")[0].value}); | ||
| 25 | }); | ||
| 26 | } | ||
| 27 | |||
| 28 | function onEditPageUpdate(data) { | ||
| 29 | // update targets | ||
| 30 | var i; var orightml = ""; | ||
| 31 | |||
| 32 | $("span#target-count").html(data.targets.length); | ||
| 33 | for (i = 0; i < data.targets.length; i++) { | ||
| 34 | if (! targetInPage(data.targets[i].target)) { | ||
| 35 | orightml += '<li><a href="#">'+data.targets[i].target; | ||
| 36 | if (data.targets[i].task != "" && data.targets[i].task !== null) { | ||
| 37 | orightml += " ("+data.targets[i].task+")"; | ||
| 38 | } | ||
| 39 | orightml += '</a><i title="" data-original-title="" class="icon-trash" id="del-target-icon" x-data="'+data.targets[i].pk+'"></i></li>'; | ||
| 40 | } | ||
| 41 | } | ||
| 42 | |||
| 43 | $("ul#target-list").html(orightml); | ||
| 44 | |||
| 45 | // update recent builds | ||
| 46 | |||
| 47 | setEventHandlers(); | ||
| 48 | } | ||
| 49 | |||
| 50 | function onEditAjaxSuccess(data, textstatus) { | ||
| 51 | console.log("XHR returned:", data, "(" + textstatus + ")"); | ||
| 52 | if (data.error != "ok") { | ||
| 53 | alert("error on request:\n" + data.error); | ||
| 54 | return; | ||
| 55 | } | ||
| 56 | onEditPageUpdate(data); | ||
| 57 | } | ||
| 58 | |||
| 59 | function onEditAjaxError(jqXHR, textstatus, error) { | ||
| 60 | alert("XHR errored:\n" + error + "\n(" + textstatus + ")"); | ||
| 61 | } | ||
| 62 | |||
| 63 | function postEditAjaxRequest(reqdata) { | ||
| 64 | var ajax = $.ajax({ | ||
| 65 | type:"POST", | ||
| 66 | data: $.param(reqdata), | ||
| 67 | url:"{% url 'xhr_projectedit' project.id%}", | ||
| 68 | headers: { 'X-CSRFToken': $.cookie("csrftoken")}, | ||
| 69 | success: onEditAjaxSuccess, | ||
| 70 | error: onEditAjaxError, | ||
| 71 | }) | ||
| 72 | } | ||
| 73 | |||
| 74 | |||
| 75 | |||
| 76 | |||
| 77 | $(document).ready(function () { | ||
| 78 | setEventHandlers(); | ||
| 79 | |||
| 80 | /* Provide XHR calls for the "build" buttons.*/ | ||
| 81 | $("button#build-all-button").click( function (evt) { | ||
| 82 | var ajax = $.ajax({ | ||
| 83 | type:"POST", | ||
| 84 | url:"{% url 'xhr_projectbuild' project.id %}", | ||
| 85 | headers: { 'X-CSRFToken': $.cookie("csrftoken")}, | ||
| 86 | success: function (data, textstatus) { | ||
| 87 | if (data.error != "ok") { | ||
| 88 | alert("XHR fail: " + data.error ); | ||
| 89 | } | ||
| 90 | }, | ||
| 91 | error: function (jqXHR, textstatus, error) { alert("XHR errored:" + error + "(" + textstatus + ")"); }, | ||
| 92 | }) | ||
| 93 | }); | ||
| 94 | }); | ||
| 95 | |||
| 96 | |||
| 97 | </script> | ||
| 98 | 8 | ||
| 99 | 9 | ||
| 100 | <div class="page-header"> | 10 | {% block projectinfomain %} |
| 101 | <h1> | 11 | <script src="{% static "js/angular.min.js" %}"></script> |
| 102 | {{project.name}} | 12 | <script src="{% static "js/angular-cookies.min.js" %}"></script> |
| 103 | {% if project.build_set.all.count == 0 %} | 13 | <script src="{% static "js/ui-bootstrap-tpls-0.11.0.js" %}"></script> |
| 104 | <small>No builds yet</small> | ||
| 105 | {% else %} | ||
| 106 | <small><a href="#">{{project.build_set.all.count}} builds</a></small> | ||
| 107 | {% endif %} | ||
| 108 | </h1> | ||
| 109 | </div> | ||
| 110 | 14 | ||
| 111 | 15 | ||
| 112 | <div class="well"> | 16 | <div id="main" role="main" ng-app="project" ng-controller="prjCtrl" class="top-padded"> |
| 113 | <form class="build-form"> | ||
| 114 | <div class="input-append input-prepend controls"> | ||
| 115 | <input type="text" class="huge span7" placeholder="Type the target(s) you want to build" autocomplete="off" data-minLength="1" data-autocomplete="off" | ||
| 116 | data-provide="typeahead" data-source='["core-image-base [meta | daisy]", | ||
| 117 | "core-image-clutter [meta | daisy]", | ||
| 118 | "core-image-directfb [meta | daisy]", | ||
| 119 | "core-image-myimage [meta-imported-layer | 3e1dbabbf3…]", | ||
| 120 | "core-image-anotherimage [meta-imported-layer | master]", | ||
| 121 | "core-image-full-cmdline [meta | daisy]", | ||
| 122 | "core-image-lsb [meta | daisy]", | ||
| 123 | "core-image-lsb-dev [meta | daisy]", | ||
| 124 | "core-image-lsb-sdk [meta| daisy]", | ||
| 125 | "core-image-minimal [meta| daisy]" | ||
| 126 | ]'> | ||
| 127 | <a href="#" id="build-button" class="btn btn-large btn-primary" disabled> | ||
| 128 | Build | ||
| 129 | <i class="icon-question-sign get-help heading-help" style="margin-left: 5px;" title="Type the name of one or more targets you want to build, separated by a space. You can also specify a task by appending a semicolon and a task name to a target name, like so: <code>core-image-minimal:do_build</code>"></i> | ||
| 130 | </a> | ||
| 131 | </div> | ||
| 132 | <p> | ||
| 133 | <a href="all-targets.html" style="padding-right: 5px;"> | ||
| 134 | View all targets | ||
| 135 | </a> | ||
| 136 | | | ||
| 137 | <a href="{% url 'projectbuilds' project.id%}" style="padding-left:5px;"> | ||
| 138 | View all project builds ({{project.build_set.count}}) | ||
| 139 | </a> | ||
| 140 | </form> | ||
| 141 | </div> | ||
| 142 | 17 | ||
| 143 | 18 | ||
| 19 | <!-- project name --> | ||
| 20 | <div class="page-header"> | ||
| 21 | <h1>{[project.name]}</h1> | ||
| 22 | </div> | ||
| 144 | 23 | ||
| 24 | <!-- alerts section 1--> | ||
| 25 | <div ng-repeat="a in zone1alerts"> | ||
| 26 | <div class="alert alert-dismissible lead" role="alert" ng-class="a.type"><button type="button" class="close" data-dismiss="alert"><span aria-hidden="true">×</span></button> | ||
| 27 | <span ng-bind-html="a.text"></span> | ||
| 28 | </div> | ||
| 29 | </div> | ||
| 145 | 30 | ||
| 31 | <!-- custom templates for ng --> | ||
| 146 | 32 | ||
| 147 | {% if builds|length > 0 or buildrequests|length > 0 %} | 33 | <script type="text/ng-template" id="suggestion_details"> |
| 148 | <h2 class="air">Recent Builds</h2> | 34 | <a> {[match.model.name]} {[match.model.detail]} </a> |
| 35 | </script> | ||
| 149 | 36 | ||
| 150 | <div id="scheduled-builds"> | 37 | <!-- modal dialogs --> |
| 151 | {% for br in buildrequests %} | 38 | <script type="text/ng-template" id="dependencies_modal"> |
| 152 | <div class="alert {% if br.0.state == br.0.REQ_FAILED%}alert-error{%else%}alert-info{%endif%}" id="build-request"> | 39 | <div class="modal-header"> |
| 153 | <div class="row-fluid"> | 40 | <button type="button" class="close" data-dismiss="modal" aria-hidden="true">x</button> |
| 154 | <div class="lead span4"> | 41 | <h3><span ng-bind="layerAddName"></span> dependencies</h3> |
| 155 | <span> | 42 | </div> |
| 156 | {{br.0.brtarget_set.all.0.target}} {%if br.brtarget_set.all.count > 1%}(+ {{br.brtarget_set.all.count|add:"-1"}}){%endif%} {{br.1.machine.value}} (Created {{br.0.created}}) | 43 | <div class="modal-body"> |
| 157 | </span> | 44 | <p><strong>{[layerAddName]}</strong> depends on some layers that are not added to your project. Select the ones you want to add:</p> |
| 45 | <ul class="unstyled"> | ||
| 46 | <li ng-repeat="ld in items"> | ||
| 47 | <label class="checkbox"> | ||
| 48 | <input type="checkbox" ng-model="selectedItems[ld.id]"> {[ld.name]} | ||
| 49 | </label> | ||
| 50 | </li> | ||
| 51 | </ul> | ||
| 52 | </div> | ||
| 53 | <div class="modal-footer"> | ||
| 54 | <button class="btn btn-primary" ng-click="ok()">Add layers</button> | ||
| 55 | <button class="btn" ng-click="cancel()">Cancel</button> | ||
| 56 | </div> | ||
| 57 | </form> | ||
| 58 | </script> | ||
| 59 | |||
| 60 | |||
| 61 | <script type="text/ng-template" id="change_version_modal"> | ||
| 62 | <div class="modal-header"> | ||
| 63 | <button type="button" class="close" data-dismiss="modal" aria-hidden="true">x</button> | ||
| 64 | <h3>Changing release to {[releaseName]}</h3> | ||
| 65 | </div> | ||
| 66 | <div class="modal-body"> | ||
| 67 | <p>The following project layers do not exist for {[releaseName]}:</p> | ||
| 68 | <ul> | ||
| 69 | <li ng-repeat="i in items"><span class="layer-info" data-toggle="tooltip" tooltip="{[i.detail]}">{[i.name]}</span></li> | ||
| 70 | </ul> | ||
| 71 | <p>If you change the release to {[releaseName]}, the above layers will be deleted from your project layers.</p> | ||
| 72 | </div> | ||
| 73 | <div class="modal-footer"> | ||
| 74 | <button class="btn btn-primary" ng-click="ok()">Change release and delete layers</button> | ||
| 75 | <button class="btn" ng-click="cancel()">Cancel</button> | ||
| 76 | </div> | ||
| 77 | </script> | ||
| 78 | |||
| 79 | <!-- build form --> | ||
| 80 | <div class="well"> | ||
| 81 | <form class="build-form" ng-submit="targetNamedBuild()"> | ||
| 82 | <div class="input-append input-prepend controls"> | ||
| 83 | <input type="text" class="huge span7 " placeholder="Type the target(s) you want to build" autocomplete="off" ng-model="targetName" typeahead="e.name for e in getSuggestions('targets', $viewValue)|filter:$viewValue" typeahead-template-url="suggestion_details" ng-disabled="!layers.length"/> | ||
| 84 | <button type="submit" id="build-button" class="btn btn-large btn-primary" ng-disabled="!targetName.length"> | ||
| 85 | Build | ||
| 86 | <i class="icon-question-sign get-help heading-help" style="margin-left: 5px;" data-toggle="tooltip" title="Type the name of one or more targets you want to build, separated by a space. You can also specify a task by appending a semicolon and a task name to a target name, like so: <code>core-image-minimal:do_build</code>"></i> | ||
| 87 | </button> | ||
| 88 | </div> | ||
| 89 | <p> | ||
| 90 | <a href="{% url 'targets' %}" style="padding-right: 5px;"> | ||
| 91 | View all targets | ||
| 92 | </a> | ||
| 93 | {% if completedbuilds.count %} | ||
| 94 | | <a href="{% url 'projectbuilds' project.id %}">View all project builds ({{completedbuilds.count}})</a> | ||
| 95 | {% endif %} | ||
| 96 | </p> | ||
| 97 | </form> | ||
| 98 | </div> | ||
| 99 | |||
| 100 | <h2 class="air" ng-if="builds.length">Latest builds</h2> | ||
| 101 | |||
| 102 | <div class="alert" ng-repeat="b in builds" ng-class="{'queued':'alert-info', 'deleted':'alert-info', 'in progress': 'alert-info', 'In Progress':'alert-info', 'Succeeded':'alert-success', 'failed':'alert-error', 'Failed':'alert-error'}[b.status]"> | ||
| 103 | <div class="row-fluid"> | ||
| 104 | <switch ng-switch="b.status"> | ||
| 105 | <case ng-switch-when="failed"> | ||
| 106 | <div class="lead span3"> <span ng-repeat="t in b.targets">{[t.target]} </span> </div> | ||
| 107 | <div class="row-fluid"> | ||
| 108 | <div class="air well" ng-repeat="e in b.errors"> | ||
| 109 | {[e.type]}: <pre>{[e.msg]}</pre> | ||
| 110 | </div> | ||
| 111 | </div> | ||
| 112 | </case> | ||
| 113 | <case ng-switch-when="queued"> | ||
| 114 | <div class="lead span5"> <span ng-repeat="t in b.targets">{[t.target]} </span> </div> | ||
| 115 | <div class="span4 lead" >Build queued | ||
| 116 | <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> | ||
| 117 | </div> | ||
| 118 | <button class="btn pull-right btn-info" ng-click="buildCancel(b.id)">Cancel</button> | ||
| 119 | </case> | ||
| 120 | <case ng-switch-when="created"> | ||
| 121 | <div class="lead span3"> <span ng-repeat="t in b.targets">{[t.target]} </span> </div> | ||
| 122 | <div class="span6" > | ||
| 123 | <span class="lead">Creating build</span> | ||
| 158 | </div> | 124 | </div> |
| 159 | <div class="span2"> | 125 | <button class="btn pull-right btn-info" ng-click="buildCancel(b.id)">Cancel</button> |
| 160 | {{br.0.get_state_display}} | 126 | </case> |
| 127 | <case ng-switch-when="deleted"> | ||
| 128 | <div class="lead span3"> <span ng-repeat="t in b.targets">{[t.target]} </span> </div> | ||
| 129 | <div class="span6" id="{[b.id]}-deleted" > | ||
| 130 | <span class="lead">Build deleted</span> | ||
| 161 | </div> | 131 | </div> |
| 162 | <div class="span8"> | 132 | <button class="btn pull-right btn-info" ng-click="builds.splice(builds.indexOf(b), 1)">Close</button> |
| 163 | {% if br.state == br.REQ_FAILED%} | 133 | </case> |
| 164 | {% for bre in br.0.brerror_set.all %} {{bre.errmsg}} ({{bre.errtype}}) <br/><hr/><code>{{bre.traceback}}</code>{%endfor%} | 134 | <case ng-switch-when="in progress"> |
| 165 | {%endif%} | 135 | <div class="lead span3"> <span ng-repeat="t in b.targets">{[t.target]} </span> </div> |
| 136 | <div class="span4" > | ||
| 166 | </div> | 137 | </div> |
| 138 | <div class="lead pull-right">Build starting shortly</div> | ||
| 139 | </case> | ||
| 140 | <case ng-switch-when="In Progress"> | ||
| 141 | <div class="span4" > | ||
| 142 | <div class="progress" style="margin-top:5px;" data-toggle="tooltip" tooltip="{[b.completeper]}% of tasks complete"> | ||
| 143 | <div style="width: {[b.completeper]}%;" class="bar"></div> | ||
| 144 | </div> | ||
| 145 | </div> | ||
| 146 | <div class="lead pull-right">ETA: at {[b.eta]}</div> | ||
| 147 | </case> | ||
| 148 | <case ng-switch-default=""> | ||
| 149 | <div class="lead span3"><a href="{[b.build_page_url]}"><span ng-repeat="t in b.targets">{[t.target]} </span> </div></a> | ||
| 150 | <div class="span2 lead"> | ||
| 151 | {[b.completed_on|date:'dd/MM/yy HH:mm']} | ||
| 152 | </div> | ||
| 153 | <div class="span2"><span>{[b.errors.len]}</span></div> | ||
| 154 | <div class="span2"><span>{[b.warnings.len]}</span></div> | ||
| 155 | <div> <span class="lead">Build time: {[b.build_time|date:"HH:mm"]}</span> | ||
| 156 | <button class="btn pull-right" ng-class="{'Succeeded': 'btn-success', 'Failed': 'btn-danger'}[b.status]" | ||
| 157 | ng-click="targetExistingBuild(b.targets)">Run again</button> | ||
| 167 | 158 | ||
| 168 | </div> | 159 | </div> |
| 169 | </div> | 160 | </case> |
| 161 | </switch> | ||
| 162 | <div class="lead pull-right"> | ||
| 163 | </div> | ||
| 164 | </div> | ||
| 165 | </div> | ||
| 170 | 166 | ||
| 171 | {% endfor %} | 167 | <h2 class="air">Project configuration</h2> |
| 172 | 168 | ||
| 169 | <!-- alerts section 2 --> | ||
| 170 | <div ng-repeat="a in zone2alerts"> | ||
| 171 | <div class="alert alert-dismissible lead" role="alert" ng-class="a.type"><button type="button" class="close" data-dismiss="alert"><span aria-hidden="true">×</span></button> | ||
| 172 | <span ng-bind-html="a.text"></span> | ||
| 173 | </div> | ||
| 174 | </div> | ||
| 175 | |||
| 176 | <div class="row-fluid"> | ||
| 177 | |||
| 178 | <!-- project layers --> | ||
| 179 | <div id="layer-container" class="well well-transparent span4"> | ||
| 180 | <h3> | ||
| 181 | Project layers <span class="muted counter">({[layers.length]})</span> | ||
| 182 | <i class="icon-question-sign get-help heading-help" title="OpenEmbedded organises metadata into modules called 'layers'. Layers allow you to isolate different types of customizations from each other. <a href='http://www.yoctoproject.org/docs/current/dev-manual/dev-manual.html#understanding-and-creating-layers' target='_blank'>More on layers</a>"></i> | ||
| 183 | </h3> | ||
| 184 | <div class="alert" ng-if="!layers.length"> | ||
| 185 | <b>You need to add some layers </b> | ||
| 186 | <p> | ||
| 187 | You can: | ||
| 188 | <ul> | ||
| 189 | <li> <a href="{% url 'layers'%}">View all layers available in Toaster</a> | ||
| 190 | <li> <a href="{% url 'importlayer' %}">Import a layer</a> | ||
| 191 | <li> <a href="https://www.yoctoproject.org/docs/1.6.1/dev-manual/dev-manual.html#understanding-and-creating-layers" target="_blank">Read about layers in the manual</a> | ||
| 192 | </ul> | ||
| 193 | Or type a layer name below. | ||
| 194 | </p> | ||
| 195 | </div> | ||
| 196 | <form class="input-append" ng-submit="layerAdd()"> | ||
| 197 | <input type="text" class="input-xlarge" id="layer" autocomplete="off" placeholder="Type a layer name" data-minLength="1" ng-model="layerAddName" typeahead="e.name for e in getSuggestions('layers', $viewValue)|filter:$viewValue" typeahead-template-url="suggestion_details" typeahead-on-select="onLayerSelect($item, $model, $label)" typeahead-editable="false" ng-class="{ 'has-error': layerAddName.$invalid }" /> | ||
| 198 | <input type="submit" id="add-layer" class="btn" value="Add" ng-disabled="!layerAddName.length"/> | ||
| 199 | {% csrf_token %} | ||
| 200 | </form> | ||
| 201 | <p><a href="{% url 'layers' %}">View all layers</a> | <a href="{% url 'importlayer' %}">Import layer</a></p> | ||
| 202 | <ul class="unstyled configuration-list"> | ||
| 203 | <li ng-repeat="l in layers"> | ||
| 204 | <a href="{[l.layerdetailurl]}" target="_#" class="layer-info" data-toggle="tooltip" tooltip="{[l.branch.layersource]} | {[l.branch.name]}">{[l.name]} </a> | ||
| 205 | <i class="icon-trash" ng-click="layerDel(l.id)" tooltip="Delete"></i> | ||
| 206 | </li> | ||
| 207 | </ul> | ||
| 173 | </div> | 208 | </div> |
| 174 | 209 | ||
| 175 | 210 | ||
| 211 | <!-- project targets --> | ||
| 212 | <div id="target-container" class="well well-transparent span4"> | ||
| 213 | <h3> | ||
| 214 | Targets | ||
| 215 | <i class="icon-question-sign get-help heading-help" title="What you build, often a recipe producing a root file system file (an image). Something like <code>core-image-minimal</code> or <code>core-image-sato</code>"></i> | ||
| 216 | </h3> | ||
| 217 | <form ng-submit="targetNamedBuild()" class="input-append"> | ||
| 218 | <input type="text" class="input-xlarge" placeholder="Type the target(s) you want to build" autocomplete="off" data-minLength="1" ng-model="targetName" typeahead="e.name for e in getSuggestions('targets', $viewValue)|filter:$viewValue" typeahead-template-url="suggestion_details" ng-disabled="!layers.length"> | ||
| 219 | <button type="submit" id="build-button" class="btn btn-primary" ng-disabled="!targetName.length"> | ||
| 220 | Build </button> | ||
| 221 | {% csrf_token %} | ||
| 222 | </form> | ||
| 223 | <p><a href="{% url 'targets' %}">View all targets</a></p> | ||
| 224 | <div ng-if="frequenttargets.length"> | ||
| 225 | <h4> | ||
| 226 | Most built targets | ||
| 227 | </h4> | ||
| 228 | <ul class="unstyled configuration-list"> | ||
| 229 | <li ng-repeat="t in frequenttargets"> | ||
| 230 | <label class="checkbox"> | ||
| 231 | <input type="checkbox" ng-model="mostBuiltTargets[t]">{[t]} | ||
| 232 | </label> | ||
| 233 | </li> | ||
| 234 | </ul> | ||
| 235 | <button class="btn btn-large btn-primary" ng-disabled="selectedMostBuildTargets()">Build selected targets</button> | ||
| 236 | </div> | ||
| 237 | </div> | ||
| 176 | 238 | ||
| 177 | <!-- Lifted from build.html --> | 239 | <!-- project configuration --> |
| 178 | {% for build in builds %} | 240 | <div id="machine-distro" class="well well-transparent span4"> |
| 179 | <div class="alert {%if build.outcome == build.SUCCEEDED%}alert-success{%elif build.outcome == build.FAILED%}alert-error{%else%}alert-info{%endif%}"> | 241 | <h3> |
| 180 | <div class="row-fluid"> | 242 | Project machine |
| 181 | <div class="lead span5"> | 243 | <i class="icon-question-sign get-help heading-help" title="The machine is the hardware for which you want to build. You can only set one machine per project"></i> |
| 182 | {%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%} | 244 | </h3> |
| 183 | {%if build.outcome == build.SUCCEEDED or build.outcome == build.FAILED %} | 245 | <p class="lead" id="select-machine-opposite"> |
| 184 | <a href="{%url 'builddashboard' build.pk%}" class="{%if build.outcome == build.SUCCEEDED %}success{%else%}error{%endif%}"> | 246 | {[machine.name]}<i id="change-machine" class="icon-pencil" ng-click="toggle('#select-machine')" tooltip="Change"></i> |
| 185 | {% endif %} | 247 | </p> |
| 186 | <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%} {{build.machine}} ({{build.completed_on|naturaltime}})</span> | 248 | <div id="select-machine" style="display: none"> |
| 187 | {%if build.outcome == build.SUCCEEDED or build.outcome == build.FAILED %} | 249 | <div class="alert alert-info"> |
| 188 | </a> | 250 | <strong>Machine changes have a big impact on build outcome.</strong> |
| 189 | {% endif %} | 251 | You cannot really compare the builds for the new machine with the previous ones. |
| 190 | </div> | ||
| 191 | {%if build.outcome == build.SUCCEEDED or build.outcome == build.FAILED %} | ||
| 192 | <div class="span2 lead"> | ||
| 193 | {% if build.errors_no %} | ||
| 194 | <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> | ||
| 195 | {% endif %} | ||
| 196 | </div> | ||
| 197 | <div class="span2 lead"> | ||
| 198 | {% if build.warnings_no %} | ||
| 199 | <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> | ||
| 200 | {% endif %} | ||
| 201 | </div > | ||
| 202 | <div class="lead pull-right"> | ||
| 203 | Build time: <a href="{% url 'buildtime' build.pk %}">{{ build.timespent|sectohms }}</a> | ||
| 204 | </div> | ||
| 205 | {%endif%}{%if build.outcome == build.IN_PROGRESS %} | ||
| 206 | <div class="span4"> | ||
| 207 | <div class="progress" style="margin-top:5px;" data-toggle="tooltip" title="{{build.completeper}}% of tasks complete"> | ||
| 208 | <div style="width: {{build.completeper}}%;" class="bar"></div> | ||
| 209 | </div> | ||
| 210 | </div> | ||
| 211 | <div class="lead pull-right">ETA: in {{build.eta|naturaltime}}</div> | ||
| 212 | {%endif%} | ||
| 213 | </div> | 252 | </div> |
| 253 | <form ng-submit="edit('#select-machine')" class="input-append"> | ||
| 254 | <input type="text" id="machine" autocomplete="off" ng-model="machineName" typeahead="m.name for m in getSuggestions('machines', $viewValue)"/> | ||
| 255 | <input type="submit" id="apply-change-machine" class="btn" type="button" ng-disabled="machineName == machine.name || machineName.length == 0" value="Save"></input> | ||
| 256 | <input type="reset" id="cancel-machine" class="btn btn-link" ng-click="toggle('#select-machine')" value="Cancel"></input> | ||
| 257 | {% csrf_token %} | ||
| 258 | </form> | ||
| 259 | <p><a href="{% url 'machines' %}" class="link">View all machines</a></p> | ||
| 260 | </div> | ||
| 261 | <p class="link-action"> | ||
| 262 | <a href="{% url 'projectconf' project.id %}" class="link">Edit configuration variables</a> | ||
| 263 | <i data-original-title="You can set other project configuration options here. Each option, like everything else in the build system, is a variable - value pair" class="icon-question-sign get-help heading-help" title=""></i> | ||
| 264 | </p> | ||
| 214 | </div> | 265 | </div> |
| 215 | {% endfor %} | 266 | </div> |
| 216 | <!-- end of lift--> | ||
| 217 | {%endif%} | ||
| 218 | 267 | ||
| 219 | <h2 class="air">Project configuration</h2> | ||
| 220 | 268 | ||
| 221 | <div class="row-fluid"> | 269 | <h2>Project details</h2> |
| 222 | 270 | ||
| 223 | <div id="layer-container" class="well well-transparent span4"> | 271 | <!-- alerts section 3 --> |
| 224 | <h3> | 272 | <div ng-repeat="a in zone3alerts"> |
| 225 | Add layers | 273 | <div class="alert alert-dismissible lead" role="alert" ng-class="a.type"><button type="button" class="close" data-dismiss="alert"><span aria-hidden="true">×</span></button> |
| 226 | <i data-original-title="OpenEmbedded organises metadata into modules called 'layers'. Layers allow you to isolate different types of customizations from each other. <a href='http://www.yoctoproject.org/docs/1.6.1/dev-manual/dev-manual.html#understanding-and-creating-layers' target='_blank'>More on layers</a>" class="icon-question-sign get-help heading-help" title=""></i> | 274 | <span ng-bind-html="a.text"></span> |
| 227 | </h3> | 275 | </div> |
| 228 | <form style="margin-top:20px;"> | 276 | </div> |
| 229 | <div class="input-append"> | 277 | |
| 230 | <input class="input-xlarge" id="layer" autocomplete="off" placeholder="Type a layer name" data-provide="typeahead" data-source="" data-minlength="1" data-autocomplete="off" type="text"> | 278 | |
| 231 | <button id="add-layer" class="btn" disabled="">Add</button> | 279 | <div id="project-details" class="well well-transparent"> |
| 232 | </div> | 280 | <h3>Project name</h3> |
| 233 | <div id="import-alert" class="alert alert-info" style="display:none;"> | 281 | <p class="lead" id="change-project-name-opposite"> |
| 234 | Toaster does not know about this layer. Please <a href="#">import it</a> | 282 | <span >{[project.name]}</span> |
| 235 | </div> | 283 | <i class="icon-pencil" ng-click="toggle('#change-project-name')" tooltip="Change"></i> |
| 236 | <div id="dependency-alert" class="alert alert-info" style="display:none;"> | 284 | </p> |
| 237 | <p><strong>meta-tizen</strong> depends on the layers below. Check the ones you want to add: </p> | 285 | <div id="change-project-name" style="display:none;"> |
| 238 | <ul class="unstyled"> | 286 | <form ng-submit="edit('#change-project-name')" class="input-append"> |
| 239 | {% for f in layer_dependency %} | 287 | <input type="text" class="input-xlarge" id="type-project-name" ng-model="projectName"> |
| 240 | <li> | 288 | <input type="submit" class="btn" value="Save" ng-disabled="project.name == projectName"/> |
| 241 | <label class="checkbox"> | 289 | <input type="reset" class="btn btn-link" value="Cancel" ng-click="toggle('#change-project-name')"> |
| 242 | <input checked="checked" type="checkbox"> | 290 | </form> |
| 243 | meta-ruby | 291 | </div> |
| 244 | </label> | ||
| 245 | </li> | ||
| 246 | {% endfor %} | ||
| 247 | </ul> | ||
| 248 | <button id="add-layer-dependencies" class="btn btn-info add-layer">Add layers</button> | ||
| 249 | </div> | ||
| 250 | |||
| 251 | <p><a href="{% url 'importlayer' %}">Import your layer</a> | <a href="{% url 'layers'%}">View all layers</a></p> | ||
| 252 | </form> | ||
| 253 | |||
| 254 | <h4 class="air"> | ||
| 255 | Added layers | ||
| 256 | <span class="muted counter">{{project.projectlayer_set.count}}</span> | ||
| 257 | <i data-original-title="Your added layers will be listed in this same order in your <code>bblayers.conf</code> file" class="icon-question-sign get-help heading-help" title=""></i> | ||
| 258 | </h4> | ||
| 259 | <ul class="unstyled configuration-list"> | ||
| 260 | {% for pl in project.projectlayer_set.all %} | ||
| 261 | <li> | ||
| 262 | <a href="#">{{pl.layercommit.layer.name}} (<span class="layer-version">{{pl.layercommit.layer.layer_index_url}}</span>)</a> | ||
| 263 | {% if pl.optional %} | ||
| 264 | <i title="" data-original-title="" class="icon-trash" id="del-layer-icon" x-data="{{pl.pk}}"></i> | ||
| 265 | {% endif %} | ||
| 266 | </li> | ||
| 267 | {% endfor %} | ||
| 268 | </ul> | ||
| 269 | </div> | ||
| 270 | 292 | ||
| 271 | <div id="target-container" class="well well-transparent span4"> | ||
| 272 | <h3> | ||
| 273 | Add targets | ||
| 274 | <i data-original-title="A target is what you want to build, usually an image recipe that produces a root file system" class="icon-question-sign get-help heading-help" title=""></i> | ||
| 275 | </h3> | ||
| 276 | <form style="margin-top:20px;"> | ||
| 277 | <div class="input-append"> | ||
| 278 | <input id="target" class="input-xlarge" autocomplete="off" placeholder="Type a target name" data-provide="typeahead" data-source="" data-minlength="1" data-autocomplete="off" type="text"> | ||
| 279 | <button id="add-target-button" class="btn" type="button">Add</button> | ||
| 280 | </div> | ||
| 281 | |||
| 282 | <p><a href="{% url 'targets' %}" class="link">View all targets</a></p> | ||
| 283 | </form> | ||
| 284 | <h4 class="air"> | ||
| 285 | Added targets | ||
| 286 | <span id="target-count" class="muted counter">{{project.projecttarget_set.count}}</span> | ||
| 287 | </h4> | ||
| 288 | <ul class="unstyled configuration-list" id="target-list"> | ||
| 289 | {% for target in project.projecttarget_set.all %} | ||
| 290 | {% if target %} | ||
| 291 | <li> | ||
| 292 | <a href="#">{{target.target}}{% if target.task%} (target.task){%endif%}</a> | ||
| 293 | {% if target.notprovided %} | ||
| 294 | <i title="" data-original-title="" id="msg1" class="icon-exclamation-sign get-help-yellow" data-title="<strong>Target may not be provided</strong>" data-content="From the layer information it currently has, Toaster thinks this target is not provided by any of your added layers. If a target is not provided by one of your added layers, the build will fail.<h5>What Toaster suggests</h5><p>The <a href='#'>meta-abc</a> and <a href='#'>meta-efg</a> layers provide core-image-notprovided. You could add one of them to your project.</p><button class='btn btn-block'>Add meta-abc</button><button class='btn btn-block'>Add meta-efg</button><button id='dismiss1' class='btn btn-block btn-info'>Stop showing this message</button>"></i> | ||
| 295 | {% elif target.notknown %} | ||
| 296 | <i title="" data-original-title="" id="msg2" class="icon-exclamation-sign get-help-yellow" data-title="<strong>Target may not be provided</strong>" data-content="From the layer information it currently has, Toaster thinks this target is not provided by any of your added layers. If a target is not provided by one of your added layers, the build will fail.<h5>What Toaster suggests</h5><p>Review your added layers to make sure one of them provides core-image-unknown. Clicking on a layer name will give you all the information Toaster has about the layer. </p> <button class='btn btn-block btn-info'>Stop showing this message</button>"></i> | ||
| 297 | {% endif %} | ||
| 298 | <i title="" data-original-title="" class="icon-trash" id="del-target-icon" x-data="{{target.pk}}"></i> | ||
| 299 | </li> | ||
| 300 | {% endif %} | ||
| 301 | {% endfor %} | ||
| 302 | |||
| 303 | |||
| 304 | </ul> | ||
| 305 | </div> | ||
| 306 | 293 | ||
| 307 | <div class="well well-transparent span4"> | 294 | <h3> |
| 308 | 295 | Release | |
| 309 | <h3> | 296 | <i class="icon-question-sign get-help heading-help" title="The version of the build system you want to use"></i> |
| 310 | Project machine | 297 | </h3> |
| 311 | <i class="icon-question-sign get-help heading-help" title="The machine is the hardware for which you want to build. You can only set one machine per project"></i> | 298 | <p class="lead" id="change-project-version-opposite"> |
| 312 | </h3> | 299 | <span id="project-version">{[project.release.name]}</span> |
| 313 | <p class="lead" id="selected-machine"> {{machine}} | 300 | <i id="change-version" class="icon-pencil" ng-click="toggle('#change-project-version')" tooltip="Change"></i> |
| 314 | <i id="change-machine" class="icon-pencil"></i> | 301 | </p> |
| 315 | </p> | 302 | <div class="div-inline" id="change-project-version" style="display:none;"> |
| 316 | <form id="select-machine"> | 303 | <form ng-submit="test('#change-project-version')" class="input-append"> |
| 317 | <div class="alert alert-info"> | 304 | <select id="select-version" ng-model="projectVersion"> |
| 318 | <strong>Machine changes have a big impact on build outcome.</strong> | 305 | <option ng-repeat="r in releases" value="{[r.id]}" ng-selected="r.id == project.release.id">{[r.name]}</option> |
| 319 | You cannot really compare the builds for the new machine with the previous ones. | 306 | </select> |
| 320 | </div> | 307 | <input type="submit" class="btn" style="margin-left:5px;" value="Save" ng-disabled="project.release.id == projectVersion"/> |
| 321 | <div class="input-append"> | 308 | <input type="reset" class="btn btn-link" value="Cancel" ng-click="toggle('#change-project-version')" ng-disabled="project.release.id == projectVersion"/> |
| 322 | <input type="text" id="machine" autocomplete="off" value="qemux86" data-provide="typeahead" | 309 | |
| 323 | data-minLength="1" | 310 | </form> |
| 324 | data-autocomplete="off" | 311 | </div> |
| 325 | data-source='[ | 312 | </div> |
| 326 | ]'> | ||
| 327 | <button id="apply-change-machine" class="btn" type="button">Save</button> | ||
| 328 | <a href="#" id="cancel-machine" class="btn btn-link">Cancel</a> | ||
| 329 | </div> | ||
| 330 | <p><a href="{% url 'machines' %}" class="link">View all machines</a></p> | ||
| 331 | </form> | ||
| 332 | <p class="link-action"> | ||
| 333 | <a href="{% url 'projectconf' project.id %}" class="link">Edit configuration variables</a> | ||
| 334 | <i class="icon-question-sign get-help heading-help" title="You can set other project configuration options here. Each option, like everything else in the build system, is a variable - value pair"></i> | ||
| 335 | </p> | ||
| 336 | 313 | ||
| 337 | </div> | 314 | <!-- end main --> |
| 315 | </div> | ||
| 338 | 316 | ||
| 339 | 317 | ||
| 340 | </div> | 318 | <!-- load application logic !--> |
| 319 | <script src="{% static "js/projectapp.js" %}"></script> | ||
| 320 | |||
| 321 | <!-- dump initial data for use in the angular app --> | ||
| 322 | <script> | ||
| 323 | angular.element(document).ready(function() { | ||
| 324 | scope = angular.element("#main").scope(); | ||
| 325 | scope.urls = {}; | ||
| 326 | scope.urls.xhr_build = "{% url 'xhr_projectbuild' project.id %}"; | ||
| 327 | scope.urls.xhr_edit = "{% url 'xhr_projectedit' project.id %}"; | ||
| 328 | scope.urls.xhr_datatypeahead = "{% url 'xhr_datatypeahead' %}"; | ||
| 329 | scope.urls.layers = "{% url 'layers' %}"; | ||
| 330 | scope.urls.targets = "{% url 'targets' %}"; | ||
| 331 | scope.urls.importlayer = "{% url 'importlayer'%}" | ||
| 332 | scope.project = {{prj|safe}}; | ||
| 333 | scope.builds = {{builds|safe}}; | ||
| 334 | scope.layers = {{layers|safe}}; | ||
| 335 | scope.targets = {{targets|safe}}; | ||
| 336 | scope.frequenttargets = {{freqtargets|safe}}; | ||
| 337 | scope.machine = {{machine|safe}}; | ||
| 338 | scope.releases = {{releases|safe}}; | ||
| 339 | |||
| 340 | scope.zone1alerts = []; | ||
| 341 | scope.zone2alerts = []; | ||
| 342 | scope.zone3alerts = []; | ||
| 343 | |||
| 344 | scope.mostBuiltTargets = {}; | ||
| 345 | |||
| 346 | scope.executeCommands(); | ||
| 347 | scope.validateData(); | ||
| 348 | |||
| 349 | scope.$digest(); | ||
| 350 | |||
| 351 | }); | ||
| 352 | </script> | ||
| 341 | 353 | ||
| 342 | <h2>Project details</h2> | ||
| 343 | |||
| 344 | <div class="well well-transparent"> | ||
| 345 | <h3>Project name</h3> | ||
| 346 | <p class="lead"> | ||
| 347 | {{project.name}} | ||
| 348 | <i title="" data-original-title="" class="icon-pencil"></i> | ||
| 349 | </p> | ||
| 350 | <h3>Project owner</h3> | ||
| 351 | <p class="lead"> | ||
| 352 | {{puser.username}} | ||
| 353 | <i title="" data-original-title="" class="icon-pencil"></i> | ||
| 354 | </p> | ||
| 355 | <h3>Owner's email</h3> | ||
| 356 | <p class="lead"> | ||
| 357 | {{puser.email}} | ||
| 358 | <i title="" data-original-title="" class="icon-pencil"></i> | ||
| 359 | </p> | ||
| 360 | <h3>Yocto Project version</h3> | ||
| 361 | <p class="lead"> | ||
| 362 | {{project.release.name}} - {{project.release.description}} | ||
| 363 | <i title="" data-original-title="" class="icon-pencil"></i> | ||
| 364 | </p> | ||
| 365 | </div> | ||
| 366 | {% endblock %} | 354 | {% endblock %} |
diff --git a/bitbake/lib/toaster/toastergui/urls.py b/bitbake/lib/toaster/toastergui/urls.py index a9c05922c2..e642e32036 100644 --- a/bitbake/lib/toaster/toastergui/urls.py +++ b/bitbake/lib/toaster/toastergui/urls.py | |||
| @@ -81,6 +81,8 @@ urlpatterns = patterns('toastergui.views', | |||
| 81 | url(r'^xhr_projectbuild/(?P<pid>\d+)/$', 'xhr_projectbuild', name='xhr_projectbuild'), | 81 | url(r'^xhr_projectbuild/(?P<pid>\d+)/$', 'xhr_projectbuild', name='xhr_projectbuild'), |
| 82 | url(r'^xhr_projectedit/(?P<pid>\d+)/$', 'xhr_projectedit', name='xhr_projectedit'), | 82 | url(r'^xhr_projectedit/(?P<pid>\d+)/$', 'xhr_projectedit', name='xhr_projectedit'), |
| 83 | 83 | ||
| 84 | url(r'^xhr_datatypeahead/$', 'xhr_datatypeahead', name='xhr_datatypeahead'), | ||
| 85 | |||
| 84 | 86 | ||
| 85 | # default redirection | 87 | # default redirection |
| 86 | url(r'^$', RedirectView.as_view( url= 'builds/')), | 88 | url(r'^$', RedirectView.as_view( url= 'builds/')), |
diff --git a/bitbake/lib/toaster/toastergui/views.py b/bitbake/lib/toaster/toastergui/views.py index 3fde3c9a62..5fe4a9d869 100755 --- a/bitbake/lib/toaster/toastergui/views.py +++ b/bitbake/lib/toaster/toastergui/views.py | |||
| @@ -387,6 +387,17 @@ def builds(request): | |||
| 387 | ] | 387 | ] |
| 388 | } | 388 | } |
| 389 | 389 | ||
| 390 | if toastermain.settings.MANAGED: | ||
| 391 | context['tablecols'].append( | ||
| 392 | {'name': 'Project', 'clclass': 'project', | ||
| 393 | 'filter': {'class': 'project', | ||
| 394 | 'label': 'Project:', | ||
| 395 | 'options': map(lambda x: (x.name,'',x.build_set.filter(outcome__lt=Build.IN_PROGRESS).count()), Project.objects.all()), | ||
| 396 | |||
| 397 | } | ||
| 398 | }) | ||
| 399 | |||
| 400 | |||
| 390 | response = render(request, template, context) | 401 | response = render(request, template, context) |
| 391 | _save_parameters_cookies(response, pagesize, orderby, request) | 402 | _save_parameters_cookies(response, pagesize, orderby, request) |
| 392 | return response | 403 | return response |
| @@ -1799,7 +1810,7 @@ if toastermain.settings.MANAGED: | |||
| 1799 | from django.contrib.auth.decorators import login_required | 1810 | from django.contrib.auth.decorators import login_required |
| 1800 | 1811 | ||
| 1801 | from orm.models import Project, ProjectLayer, ProjectTarget, ProjectVariable | 1812 | from orm.models import Project, ProjectLayer, ProjectTarget, ProjectVariable |
| 1802 | from orm.models import Branch, LayerSource, ToasterSetting, Release, Machine | 1813 | from orm.models import Branch, LayerSource, ToasterSetting, Release, Machine, LayerVersionDependency |
| 1803 | from bldcontrol.models import BuildRequest | 1814 | from bldcontrol.models import BuildRequest |
| 1804 | 1815 | ||
| 1805 | import traceback | 1816 | import traceback |
| @@ -1831,7 +1842,7 @@ if toastermain.settings.MANAGED: | |||
| 1831 | # render new project page | 1842 | # render new project page |
| 1832 | return render(request, template, context) | 1843 | return render(request, template, context) |
| 1833 | elif request.method == "POST": | 1844 | elif request.method == "POST": |
| 1834 | mandatory_fields = ['projectname', 'email', 'username', 'projectversion'] | 1845 | mandatory_fields = ['projectname', 'projectversion'] |
| 1835 | try: | 1846 | try: |
| 1836 | # make sure we have values for all mandatory_fields | 1847 | # make sure we have values for all mandatory_fields |
| 1837 | if reduce( lambda x, y: x or y, map(lambda x: len(request.POST.get(x, '')) == 0, mandatory_fields)): | 1848 | if reduce( lambda x, y: x or y, map(lambda x: len(request.POST.get(x, '')) == 0, mandatory_fields)): |
| @@ -1840,9 +1851,9 @@ if toastermain.settings.MANAGED: | |||
| 1840 | ", ".join([x for x in mandatory_fields if len(request.POST.get(x, '')) == 0 ])) | 1851 | ", ".join([x for x in mandatory_fields if len(request.POST.get(x, '')) == 0 ])) |
| 1841 | 1852 | ||
| 1842 | if not request.user.is_authenticated(): | 1853 | if not request.user.is_authenticated(): |
| 1843 | user = authenticate(username = request.POST['username'], password = 'nopass') | 1854 | user = authenticate(username = request.POST.get('username', '_anonuser'), password = 'nopass') |
| 1844 | if user is None: | 1855 | if user is None: |
| 1845 | user = User.objects.create_user(username = request.POST['username'], email = request.POST['email'], password = "nopass") | 1856 | user = User.objects.create_user(username = request.POST.get('username', '_anonuser'), email = request.POST.get('email', ''), password = "nopass") |
| 1846 | 1857 | ||
| 1847 | user = authenticate(username = user.username, password = 'nopass') | 1858 | user = authenticate(username = user.username, password = 'nopass') |
| 1848 | login(request, user) | 1859 | login(request, user) |
| @@ -1852,7 +1863,7 @@ if toastermain.settings.MANAGED: | |||
| 1852 | release = Release.objects.get(pk = request.POST['projectversion'])) | 1863 | release = Release.objects.get(pk = request.POST['projectversion'])) |
| 1853 | prj.user_id = request.user.pk | 1864 | prj.user_id = request.user.pk |
| 1854 | prj.save() | 1865 | prj.save() |
| 1855 | return redirect(reverse(project, args = (prj.pk,))) | 1866 | return redirect(reverse(project, args = (prj.pk,)) + "#/newproject") |
| 1856 | 1867 | ||
| 1857 | except (IntegrityError, BadParameterException) as e: | 1868 | except (IntegrityError, BadParameterException) as e: |
| 1858 | # fill in page with previously submitted values | 1869 | # fill in page with previously submitted values |
| @@ -1865,6 +1876,40 @@ if toastermain.settings.MANAGED: | |||
| 1865 | 1876 | ||
| 1866 | raise Exception("Invalid HTTP method for this page") | 1877 | raise Exception("Invalid HTTP method for this page") |
| 1867 | 1878 | ||
| 1879 | |||
| 1880 | def _project_recent_build_list(prj): | ||
| 1881 | # build requests not yet started | ||
| 1882 | return (map(lambda x: { | ||
| 1883 | "id": x.pk, | ||
| 1884 | "targets" : map(lambda y: {"target": y.target }, x.brtarget_set.all()), | ||
| 1885 | "status": x.get_state_display(), | ||
| 1886 | }, prj.buildrequest_set.filter(state__lt = BuildRequest.REQ_INPROGRESS).order_by("-pk")) + | ||
| 1887 | # build requests started, but with no build yet | ||
| 1888 | map(lambda x: { | ||
| 1889 | "id": x.pk, | ||
| 1890 | "targets" : map(lambda y: {"target": y.target }, x.brtarget_set.all()), | ||
| 1891 | "status": x.get_state_display(), | ||
| 1892 | }, prj.buildrequest_set.filter(state = BuildRequest.REQ_INPROGRESS, build = None).order_by("-pk")) + | ||
| 1893 | # build requests that failed | ||
| 1894 | map(lambda x: { | ||
| 1895 | "id": x.pk, | ||
| 1896 | "targets" : map(lambda y: {"target": y.target }, x.brtarget_set.all()), | ||
| 1897 | "status": x.get_state_display(), | ||
| 1898 | "errors": map(lambda y: {"type": y.errtype, "msg": y.errmsg, "tb": y.traceback}, x.brerror_set.all()), | ||
| 1899 | }, prj.buildrequest_set.filter(state = BuildRequest.REQ_FAILED).order_by("-pk")) + | ||
| 1900 | # and already made builds | ||
| 1901 | map(lambda x: { | ||
| 1902 | "id": x.pk, | ||
| 1903 | "targets": map(lambda y: {"target": y.target }, x.target_set.all()), | ||
| 1904 | "status": x.get_outcome_display(), | ||
| 1905 | "completed_on" : x.completed_on.strftime('%s')+"000", | ||
| 1906 | "build_time" : (x.completed_on - x.started_on).total_seconds(), | ||
| 1907 | "build_page_url" : reverse('builddashboard', args=(x.pk,)), | ||
| 1908 | "completeper": x.completeper(), | ||
| 1909 | "eta": x.eta().ctime(), | ||
| 1910 | }, prj.build_set.all())) | ||
| 1911 | |||
| 1912 | |||
| 1868 | # Shows the edit project page | 1913 | # Shows the edit project page |
| 1869 | def project(request, pid): | 1914 | def project(request, pid): |
| 1870 | template = "project.html" | 1915 | template = "project.html" |
| @@ -1881,27 +1926,40 @@ if toastermain.settings.MANAGED: | |||
| 1881 | # we use implicit knowledge of the current user's project to filter layer information, e.g. | 1926 | # we use implicit knowledge of the current user's project to filter layer information, e.g. |
| 1882 | request.session['project_id'] = prj.id | 1927 | request.session['project_id'] = prj.id |
| 1883 | 1928 | ||
| 1929 | from collections import Counter | ||
| 1930 | freqtargets = [] | ||
| 1931 | try: | ||
| 1932 | freqtargets += map(lambda x: x.target, reduce(lambda x, y: x + y, map(lambda x: list(x.target_set.all()), Build.objects.filter(project = prj, outcome__lt = Build.IN_PROGRESS)))) | ||
| 1933 | freqtargets += map(lambda x: x.target, reduce(lambda x, y: x + y, map(lambda x: list(x.brtarget_set.all()), BuildRequest.objects.filter(project = prj, state__lte = BuildRequest.REQ_QUEUED)))) | ||
| 1934 | except TypeError: | ||
| 1935 | pass | ||
| 1936 | freqtargets = Counter(freqtargets) | ||
| 1937 | freqtargets = sorted(freqtargets, key = lambda x: freqtargets[x]) | ||
| 1938 | |||
| 1884 | context = { | 1939 | context = { |
| 1885 | "project" : prj, | 1940 | "project" : prj, |
| 1941 | "completedbuilds": Build.objects.filter(project = prj).exclude(outcome = Build.IN_PROGRESS), | ||
| 1942 | "prj" : json.dumps({"name": prj.name, "release": { "id": prj.release.pk, "name": prj.release.name}}), | ||
| 1886 | #"buildrequests" : prj.buildrequest_set.filter(state=BuildRequest.REQ_QUEUED), | 1943 | #"buildrequests" : prj.buildrequest_set.filter(state=BuildRequest.REQ_QUEUED), |
| 1887 | "buildrequests" : map(lambda x: (x, {"machine" : x.brvariable_set.filter(name="MACHINE")[0]}), prj.buildrequest_set.filter(state__lt = BuildRequest.REQ_INPROGRESS).order_by("-pk")), | 1944 | "builds" : json.dumps(_project_recent_build_list(prj)), |
| 1888 | "builds" : prj.build_set.all(), | 1945 | "layers" : json.dumps(map(lambda x: {"id": x.layercommit.pk, "name" : x.layercommit.layer.name, "url": x.layercommit.layer.layer_index_url, "layerdetailurl": reverse("layerdetails", args=(x.layercommit.layer.pk,)), "branch" : { "name" : x.layercommit.up_branch.name, "layersource" : x.layercommit.up_branch.layer_source.name}}, prj.projectlayer_set.all())), |
| 1889 | "puser": puser, | 1946 | "targets" : json.dumps(map(lambda x: {"target" : x.target, "task" : x.task, "pk": x.pk}, prj.projecttarget_set.all())), |
| 1947 | "freqtargets": json.dumps(freqtargets), | ||
| 1948 | "releases": json.dumps(map(lambda x: {"id": x.pk, "name": x.name}, Release.objects.all())), | ||
| 1890 | } | 1949 | } |
| 1891 | try: | 1950 | try: |
| 1892 | context["machine"] = prj.projectvariable_set.get(name="MACHINE").value | 1951 | context["machine"] = json.dumps({"name": prj.projectvariable_set.get(name="MACHINE").value}) |
| 1893 | except ProjectVariable.DoesNotExist: | 1952 | except ProjectVariable.DoesNotExist: |
| 1894 | context["machine"] = "-- not set yet" | 1953 | context["machine"] = json.dumps(None) |
| 1895 | |||
| 1896 | try: | 1954 | try: |
| 1897 | context["distro"] = prj.projectvariable_set.get(name="DISTRO").value | 1955 | context["distro"] = prj.projectvariable_set.get(name="DISTRO").value |
| 1898 | except ProjectVariable.DoesNotExist: | 1956 | except ProjectVariable.DoesNotExist: |
| 1899 | context["distro"] = "-- not set yet" | 1957 | context["distro"] = "-- not set yet" |
| 1900 | 1958 | ||
| 1901 | 1959 | response = render(request, template, context) | |
| 1902 | return render(request, template, context) | 1960 | response['Cache-Control'] = "no-cache, must-revalidate, no-store" |
| 1903 | 1961 | response['Pragma'] = "no-cache" | |
| 1904 | import json | 1962 | return response |
| 1905 | 1963 | ||
| 1906 | def xhr_projectbuild(request, pid): | 1964 | def xhr_projectbuild(request, pid): |
| 1907 | try: | 1965 | try: |
| @@ -1909,57 +1967,155 @@ if toastermain.settings.MANAGED: | |||
| 1909 | raise BadParameterException("invalid method") | 1967 | raise BadParameterException("invalid method") |
| 1910 | prj = Project.objects.get(id = pid) | 1968 | prj = Project.objects.get(id = pid) |
| 1911 | 1969 | ||
| 1912 | if prj.projecttarget_set.count() == 0: | ||
| 1913 | raise BadParameterException("no targets selected") | ||
| 1914 | 1970 | ||
| 1915 | br = prj.schedule_build() | 1971 | if 'buildCancel' in request.POST: |
| 1916 | return HttpResponse(json.dumps({"error":"ok", | 1972 | for i in request.POST['buildCancel'].strip().split(" "): |
| 1917 | "brtarget" : map(lambda x: x.target, br.brtarget_set.all()), | 1973 | br = BuildRequest.objects.select_for_update().get(project = prj, pk = i, state__lte = BuildRequest.REQ_QUEUED) |
| 1918 | "machine" : br.brvariable_set.get(name="MACHINE").value, | 1974 | print "selected for delete", br.pk |
| 1975 | br.delete() | ||
| 1976 | print "selected for delete", br.pk | ||
| 1919 | 1977 | ||
| 1920 | }), content_type = "application/json") | 1978 | if 'targets' in request.POST: |
| 1921 | except Exception as e: | 1979 | ProjectTarget.objects.filter(project = prj).delete() |
| 1922 | return HttpResponse(json.dumps({"error":str(e) + "\n" + traceback.format_exc()}), content_type = "application/json") | 1980 | for t in request.POST['targets'].strip().split(" "): |
| 1923 | |||
| 1924 | def xhr_projectedit(request, pid): | ||
| 1925 | try: | ||
| 1926 | prj = Project.objects.get(id = pid) | ||
| 1927 | # add targets | ||
| 1928 | if 'targetAdd' in request.POST: | ||
| 1929 | for t in request.POST['targetAdd'].strip().split(" "): | ||
| 1930 | if ":" in t: | 1981 | if ":" in t: |
| 1931 | target, task = t.split(":") | 1982 | target, task = t.split(":") |
| 1932 | else: | 1983 | else: |
| 1933 | target = t | 1984 | target = t |
| 1934 | task = "" | 1985 | task = "" |
| 1986 | ProjectTarget.objects.create(project = prj, target = target, task = task) | ||
| 1935 | 1987 | ||
| 1936 | pt, created = ProjectTarget.objects.get_or_create(project = prj, target = target, task = task) | 1988 | br = prj.schedule_build() |
| 1937 | # remove targets | ||
| 1938 | if 'targetDel' in request.POST: | ||
| 1939 | for t in request.POST['targetDel'].strip().split(" "): | ||
| 1940 | pt = ProjectTarget.objects.get(pk = int(t)).delete() | ||
| 1941 | 1989 | ||
| 1990 | return HttpResponse(json.dumps({"error":"ok", | ||
| 1991 | "builds" : _project_recent_build_list(prj), | ||
| 1992 | }), content_type = "application/json") | ||
| 1993 | except Exception as e: | ||
| 1994 | return HttpResponse(json.dumps({"error":str(e) + "\n" + traceback.format_exc()}), content_type = "application/json") | ||
| 1995 | |||
| 1996 | def xhr_projectedit(request, pid): | ||
| 1997 | try: | ||
| 1998 | prj = Project.objects.get(id = pid) | ||
| 1942 | # add layers | 1999 | # add layers |
| 2000 | if 'layerAdd' in request.POST: | ||
| 2001 | for lc in Layer_Version.objects.filter(pk__in=request.POST['layerAdd'].split(",")): | ||
| 2002 | ProjectLayer.objects.get_or_create(project = prj, layercommit = lc) | ||
| 1943 | 2003 | ||
| 1944 | # remove layers | 2004 | # remove layers |
| 2005 | if 'layerDel' in request.POST: | ||
| 2006 | for t in request.POST['layerDel'].strip().split(" "): | ||
| 2007 | pt = ProjectLayer.objects.get(project = prj, layercommit_id = int(t)).delete() | ||
| 2008 | |||
| 2009 | if 'projectName' in request.POST: | ||
| 2010 | prj.name = request.POST['projectName'] | ||
| 2011 | prj.save(); | ||
| 2012 | |||
| 2013 | |||
| 2014 | if 'projectVersion' in request.POST: | ||
| 2015 | prj.release = Release.objects.get(pk = request.POST['projectVersion']) | ||
| 2016 | prj.save() | ||
| 2017 | # we need to change the layers | ||
| 2018 | for i in prj.projectlayer_set.all(): | ||
| 2019 | # find and add a similarly-named layer from the same layer source on the new branch | ||
| 2020 | lv = Layer_Version.objects.filter(layer_source = i.layercommit.layer_source, layer__name = i.layercommit.layer.name, up_branch__in = Branch.objects.filter(name = prj.release.branch)) | ||
| 2021 | if lv.count() == 1: | ||
| 2022 | ProjectLayer.objects.get_or_create(project = prj, layercommit = lv[0]) | ||
| 2023 | # get rid of the old entry | ||
| 2024 | i.delete() | ||
| 1945 | 2025 | ||
| 1946 | # return all project settings | 2026 | # return all project settings |
| 1947 | return HttpResponse(json.dumps( { | 2027 | return HttpResponse(json.dumps( { |
| 1948 | "error": "ok", | 2028 | "error": "ok", |
| 1949 | "layers": map(lambda x: (x.layercommit.layer.name, x.layercommit.layer.layer_index_url), prj.projectlayer_set.all()), | 2029 | "layers" : map(lambda x: {"id": x.layercommit.pk, "name" : x.layercommit.layer.name, "url": x.layercommit.layer.layer_index_url, "layerdetailurl": reverse("layerdetails", args=(x.layercommit.layer.pk,)), "branch" : { "name" : x.layercommit.up_branch.name, "layersource" : x.layercommit.up_branch.layer_source.name}}, prj.projectlayer_set.all()), |
| 1950 | "targets" : map(lambda x: {"target" : x.target, "task" : x.task, "pk": x.pk}, prj.projecttarget_set.all()), | 2030 | "builds" : _project_recent_build_list(prj), |
| 1951 | "variables": map(lambda x: (x.name, x.value), prj.projectvariable_set.all()), | 2031 | "variables": map(lambda x: (x.name, x.value), prj.projectvariable_set.all()), |
| 2032 | "prj": {"name": prj.name, "release": { "id": prj.release.pk, "name": prj.release.name}}, | ||
| 1952 | }), content_type = "application/json") | 2033 | }), content_type = "application/json") |
| 1953 | 2034 | ||
| 1954 | except Exception as e: | 2035 | except Exception as e: |
| 1955 | return HttpResponse(json.dumps({"error":str(e) + "\n" + traceback.format_exc()}), content_type = "application/json") | 2036 | return HttpResponse(json.dumps({"error":str(e) + "\n" + traceback.format_exc()}), content_type = "application/json") |
| 1956 | 2037 | ||
| 2038 | |||
| 2039 | from django.views.decorators.csrf import csrf_exempt | ||
| 2040 | @csrf_exempt | ||
| 2041 | def xhr_datatypeahead(request): | ||
| 2042 | try: | ||
| 2043 | if request.GET['type'] == "layers": | ||
| 2044 | queryset_all = Layer_Version.objects.all() | ||
| 2045 | if 'project_id' in request.session: | ||
| 2046 | prj = Project.objects.get(pk = request.session['project_id']) | ||
| 2047 | queryset_all = queryset_all.filter(up_branch__in = Branch.objects.filter(name = prj.release.name)).exclude(pk__in = map(lambda x: x.layercommit_id, prj.projectlayer_set.all())) | ||
| 2048 | queryset_all = queryset_all.filter(layer__name__icontains=request.GET.get('value','')) | ||
| 2049 | return HttpResponse(json.dumps( { "error":"ok", | ||
| 2050 | "list" : map( lambda x: {"id": x.pk, "name": x.layer.name, "detail": "(" + x.layer.layer_source.name + (")" if x.up_branch == None else " | "+x.up_branch.name+")")}, | ||
| 2051 | queryset_all[:8]) | ||
| 2052 | }), content_type = "application/json") | ||
| 2053 | |||
| 2054 | if request.GET['type'] == "layerdeps": | ||
| 2055 | queryset_all = LayerVersionDependency.objects.filter(layer_version_id = request.GET['value']) | ||
| 2056 | |||
| 2057 | if 'project_id' in request.session: | ||
| 2058 | prj = Project.objects.get(pk = request.session['project_id']) | ||
| 2059 | queryset_all = queryset_all.exclude(depends_on__in = map(lambda x: x.layercommit, prj.projectlayer_set.all())) | ||
| 2060 | |||
| 2061 | queryset_all.order_by("-up_id"); | ||
| 2062 | |||
| 2063 | return HttpResponse(json.dumps( { "error":"ok", | ||
| 2064 | "list" : map( | ||
| 2065 | lambda x: {"id": x.pk, "name": x.layer.name, "detail": "(" + x.layer.layer_source.name + (")" if x.up_branch == None else " | "+x.up_branch.name+")")}, | ||
| 2066 | map(lambda x: x.depends_on, queryset_all)) | ||
| 2067 | }), content_type = "application/json") | ||
| 2068 | |||
| 2069 | if request.GET['type'] == "versionlayers": | ||
| 2070 | if not 'project_id' in request.session: | ||
| 2071 | raise Exception("This call cannot makes no sense outside a project context") | ||
| 2072 | |||
| 2073 | retval = [] | ||
| 2074 | prj = Project.objects.get(pk = request.session['project_id']) | ||
| 2075 | for i in prj.projectlayer_set.all(): | ||
| 2076 | lv = Layer_Version.objects.filter(layer_source = i.layercommit.layer_source, layer__name = i.layercommit.layer.name, up_branch__in = Branch.objects.filter(name = Release.objects.get(pk=request.GET['value']).branch)) | ||
| 2077 | if lv.count() != 1: | ||
| 2078 | retval.append(i) | ||
| 2079 | |||
| 2080 | return HttpResponse(json.dumps( {"error":"ok", | ||
| 2081 | "list": map( | ||
| 2082 | lambda x: {"id": x.layercommit.pk, "name": x.layercommit.layer.name, "detail": "(" + x.layercommit.layer.layer_source.name + (")" if x.layercommit.up_branch == None else " | "+x.layercommit.up_branch.name+")")}, | ||
| 2083 | retval) }), content_type = "application/json") | ||
| 2084 | |||
| 2085 | |||
| 2086 | if request.GET['type'] == "targets": | ||
| 2087 | queryset_all = Recipe.objects.all() | ||
| 2088 | if 'project_id' in request.session: | ||
| 2089 | queryset_all = queryset_all.filter(layer_version__layer__in = map(lambda x: x.layercommit.layer, ProjectLayer.objects.filter(project_id=request.session['project_id']))) | ||
| 2090 | return HttpResponse(json.dumps({ "error":"ok", | ||
| 2091 | "list" : map ( lambda x: {"id": x.pk, "name": x.name, "detail":"[" + x.layer_version.layer.name+ (" | " + x.layer_version.up_branch.name + "]" if x.layer_version.up_branch is not None else "]")}, | ||
| 2092 | queryset_all.filter(name__istartswith=request.GET.get('value',''))[:8]), | ||
| 2093 | |||
| 2094 | }), content_type = "application/json") | ||
| 2095 | |||
| 2096 | if request.GET['type'] == "machines": | ||
| 2097 | queryset_all = Machine.objects.all() | ||
| 2098 | if 'project_id' in request.session: | ||
| 2099 | queryset_all = queryset_all.filter(layer_version__layer__in = map(lambda x: x.layercommit.layer, ProjectLayer.objects.filter(project_id=request.session['project_id']))) | ||
| 2100 | return HttpResponse(json.dumps({ "error":"ok", | ||
| 2101 | "list" : map ( lambda x: {"id": x.pk, "name": x.name, "detail":"[" + x.layer_version.layer.name+ (" | " + x.layer_version.up_branch.name + "]" if x.layer_version.up_branch is not None else "]")}, | ||
| 2102 | queryset_all.filter(name__istartswith=request.GET.get('value',''))[:8]), | ||
| 2103 | |||
| 2104 | }), content_type = "application/json") | ||
| 2105 | |||
| 2106 | raise Exception("Unknown request! " + request.GET.get('type', "No parameter supplied")) | ||
| 2107 | except Exception as e: | ||
| 2108 | return HttpResponse(json.dumps({"error":str(e) + "\n" + traceback.format_exc()}), content_type = "application/json") | ||
| 2109 | |||
| 2110 | |||
| 2111 | |||
| 1957 | def importlayer(request): | 2112 | def importlayer(request): |
| 1958 | template = "importlayer.html" | 2113 | template = "importlayer.html" |
| 1959 | context = { | 2114 | context = { |
| 1960 | } | 2115 | } |
| 1961 | return render(request, template, context) | 2116 | return render(request, template, context) |
| 1962 | 2117 | ||
| 2118 | |||
| 1963 | def layers(request): | 2119 | def layers(request): |
| 1964 | template = "layers.html" | 2120 | template = "layers.html" |
| 1965 | # define here what parameters the view needs in the GET portion in order to | 2121 | # define here what parameters the view needs in the GET portion in order to |
| @@ -2220,7 +2376,7 @@ if toastermain.settings.MANAGED: | |||
| 2220 | # boilerplate code that takes a request for an object type and returns a queryset | 2376 | # boilerplate code that takes a request for an object type and returns a queryset |
| 2221 | # for that object type. copypasta for all needed table searches | 2377 | # for that object type. copypasta for all needed table searches |
| 2222 | (filter_string, search_term, ordering_string) = _search_tuple(request, Build) | 2378 | (filter_string, search_term, ordering_string) = _search_tuple(request, Build) |
| 2223 | queryset_all = Build.objects.all.exclude(outcome = Build.IN_PROGRESS) | 2379 | queryset_all = Build.objects.all().exclude(outcome = Build.IN_PROGRESS) |
| 2224 | queryset_with_search = _get_queryset(Build, queryset_all, None, search_term, ordering_string, '-completed_on') | 2380 | queryset_with_search = _get_queryset(Build, queryset_all, None, search_term, ordering_string, '-completed_on') |
| 2225 | queryset = _get_queryset(Build, queryset_all, filter_string, search_term, ordering_string, '-completed_on') | 2381 | queryset = _get_queryset(Build, queryset_all, filter_string, search_term, ordering_string, '-completed_on') |
| 2226 | 2382 | ||
| @@ -2388,6 +2544,9 @@ else: | |||
| 2388 | def xhr_projectedit(request, pid): | 2544 | def xhr_projectedit(request, pid): |
| 2389 | raise Exception("page not available in interactive mode") | 2545 | raise Exception("page not available in interactive mode") |
| 2390 | 2546 | ||
| 2547 | def xhr_datatypeahead(request): | ||
| 2548 | raise Exception("page not available in interactive mode") | ||
| 2549 | |||
| 2391 | def importlayer(request): | 2550 | def importlayer(request): |
| 2392 | raise Exception("page not available in interactive mode") | 2551 | raise Exception("page not available in interactive mode") |
| 2393 | 2552 | ||
