1<!-- Copyright 2020 Google Inc. 2Licensed under the Apache License, Version 2.0 (the "License"); 3you may not use this file except in compliance with the License. 4You may obtain a copy of the License at 5 http://www.apache.org/licenses/LICENSE-2.0 6Unless required by applicable law or agreed to in writing, software 7distributed under the License is distributed on an "AS IS" BASIS, 8WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9See the License for the specific language governing permissions and 10limitations under the License. --> 11 12<link rel="import" href="../../bower_components/polymer/polymer-element.html"> 13<link rel="import" href="../../bower_components/app-layout/app-drawer-layout/app-drawer-layout.html"> 14<link rel="import" href="../../bower_components/app-layout/app-drawer/app-drawer.html"> 15<link rel="import" href="../../bower_components/app-layout/app-scroll-effects/app-scroll-effects.html"> 16<link rel="import" href="../../bower_components/app-layout/app-header/app-header.html"> 17<link rel="import" href="../../bower_components/app-layout/app-header-layout/app-header-layout.html"> 18<link rel="import" href="../../bower_components/app-layout/app-toolbar/app-toolbar.html"> 19<link rel="import" href="../../bower_components/paper-item/paper-item.html"> 20<link rel="import" href="../../bower_components/paper-item/paper-item-body.html"> 21<link rel="import" href="../../bower_components/paper-button/paper-button.html"> 22<link rel="import" href="../../bower_components/paper-card/paper-card.html"> 23<link rel="import" href="../../bower_components/paper-tabs/paper-tabs.html"> 24<link rel="import" href="../../bower_components/paper-icon-button/paper-icon-button.html"> 25<link rel="import" href="../../bower_components/iron-icons/iron-icons.html"> 26<link rel="import" href="../../bower_components/iron-ajax/iron-ajax.html"> 27<link rel="import" href="../../bower_components/iron-flex-layout/iron-flex-layout-classes.html"> 28<link rel="import" href="../../bower_components/polymer/lib/elements/dom-if.html"> 29<link rel="import" href="../../bower_components/polymer/lib/elements/dom-repeat.html"> 30<link rel="import" href="../../bower_components/app-route/app-location.html"> 31<link rel="import" href="../../bower_components/app-route/app-route.html"> 32 33<dom-module id="build-status"> 34 <template> 35 <app-location route="{{route}}" use-hash-as-path></app-location> 36 <app-route route="{{route}}" 37 pattern=":project_name" 38 data="{{routeData}}"> 39 </app-route> 40 <style is="custom-style" include="iron-flex iron-flex-alignment"> 41 <style> 42 .paper-item-link { 43 color: inherit; 44 text-decoration: none; 45 } 46 47 a { 48 text-decoration: none; 49 } 50 51 app-header { 52 background-color: #2ba4ad; 53 color: #fff; 54 } 55 56 paper-button { 57 font-weight: normal; 58 font-size: 14px; 59 -webkit-font-smoothing: antialiased; 60 } 61 62 paper-button.green:hover { 63 background-color: var(--paper-green-400); 64 } 65 66 paper-button.green { 67 background-color: var(--paper-green-500); 68 color: white; 69 } 70 71 paper-card { 72 margin: 0.5em; 73 } 74 75 paper-item { 76 cursor: pointer; 77 } 78 79 paper-tabs { 80 -webkit-font-smoothing: antialiased; 81 width: 100%; 82 margin-bottom: 1px; 83 height: 40px; 84 } 85 86 :host { 87 display: block; 88 } 89 90 .icon-error { 91 color: #e83030; 92 margin-right: 0.2em; 93 } 94 95 .icon-success { 96 color: var(--paper-green-500); 97 margin-right: 0.2em; 98 } 99 100 .icon-waiting { 101 color: var(--paper-yellow-500); 102 margin-right: 0.2em; 103 } 104 105 .projects { 106 min-width: 10em; 107 } 108 109 .log { 110 width: 80%; 111 display: inline; 112 } 113 114 .buildHistory { 115 margin: 20px 0; 116 } 117 118 pre { 119 white-space: pre-wrap; 120 } 121 </style> 122 <app-header reveals> 123 <app-toolbar> 124 <div main-title>OSS-Fuzz build status</div> 125 <div><small>(Updated every 30 minutes)</small></div> 126 </app-toolbar> 127 </app-header> 128 <div class="layout horizontal"> 129 <paper-card class="projects"> 130 <div class="card-tabs"> 131 <paper-tabs selected="fuzzing" id="build_type" attr-for-selected="type" on-click="onChanged"> 132 <paper-tab type="fuzzing">Fuzzing Builds</paper-tab> 133 <paper-tab type="coverage">Coverage Builds</paper-tab> 134 </paper-tabs> 135 </div> 136 <div class="card-content"> 137 <template is="dom-repeat" items="[[status.projects]]" as="project"> 138 <paper-item on-tap="onTap"> 139 <paper-item-body two-line> 140 <div> 141 <template is="dom-if" if="[[!isSuccessful(project)]]"> 142 <iron-icon class="icon-error" icon="icons:error"></iron-icon> 143 </template> 144 <template is="dom-if" if="[[!project.history.length]]"> 145 <iron-icon class="icon-waiting" icon="icons:error"></iron-icon> 146 </template> 147 [[project.name]] 148 </div> 149 <template is="dom-if" if="[[project.history.length]]"> 150 <div secondary title$="Last built [[toLocalDate(project.finish_time)]]"> 151 Last built [[toLocalDate(project.history.0.finish_time)]] 152 </div> 153 </template> 154 <template is="dom-if" if="[[!project.history.length]]"> 155 <div secondary title$="Not built yet"> 156 Not built yet 157 </div> 158 </template> 159 </paper-item-body> 160 </paper-item> 161 </template> 162 </div> 163 </paper-card> 164 <paper-card class="log"> 165 <div class="card-content"> 166 <template is="dom-if" if="[[!project]]"> 167 Select a project to see logs. 168 </template> 169 <template is="dom-if" if="[[build_history.length]]"> 170 Last Successful build: 171 <template is="dom-if" if="[[last_successful_build]]"> 172 <paper-button raised on-click="onLastBuildSuccessful" class="green"> 173 [[getLocalDate(last_successful_build.finish_time)]] 174 </paper-button> 175 </template> 176 <template is="dom-if" if="[[!last_successful_build]]"> 177 None yet. 178 </template> 179 <div class="buildHistory"> 180 Build History: <br> 181 <template is="dom-repeat" items="[[build_history]]" as="history"> 182 <paper-button raised on-click="onBuildHistory"> 183 <template is="dom-if" if="[[history.success]]"> 184 <iron-icon class="icon-success" icon="icons:done"></iron-icon> 185 </template> 186 <template is="dom-if" if="[[!history.success]]"> 187 <iron-icon class="icon-error" icon="icons:error"></iron-icon> 188 </template> 189 [[getLocalDate(history.finish_time)]] 190 </paper-button> 191 </template> 192 </div> 193 <template is="dom-if" if=[[!finish_time]]> 194 <pre>Select a build to see logs.</pre> 195 </template> 196 <template is="dom-if" if="[[finish_time]]"> 197 <a href="/log-[[build_id]].txt" target="_blank" tabindex="-1"> 198 <paper-button raised> 199 Open in new tab 200 <iron-icon icon="icons:link"></iron-iron> 201 </paper-button> 202 </a> 203 <pre>Finish Time : [[finish_time]]</pre> 204 </template> 205 </template> 206 <template is="dom-if" if="[[loading_log]]"> 207 Loading... 208 </template> 209 <pre>[[log]]</pre> 210 </div> 211 </paper-card> 212 </div> 213 <iron-ajax id="status_fuzzing" auto handle-as="json" url="/status.json" on-response="onResponseForFuzzing"></iron-ajax> 214 <iron-ajax id="status_coverage" auto handle-as="json" url="/status-coverage.json" on-response="onResponseForCoverage"></iron-ajax> 215 <iron-ajax id="logxhr" handle-as="text" on-response="onLogResponse"></iron-ajax> 216 </template> 217 218 <script> 219 /** @polymerElement */ 220 class BuildStatus extends Polymer.Element { 221 static get is() { 222 return "build-status"; 223 } 224 static get properties() { 225 return { 226 log: { 227 type: String, 228 value: '', 229 }, 230 loading_log: { 231 type: Boolean, 232 value: false, 233 }, 234 finish_time: { 235 type: String, 236 value: '', 237 } 238 } 239 } 240 static get observers() { 241 return ["_routeChanged(route.*)"] 242 } 243 244 _routeChanged() { 245 if(!this.routeData.project_name) 246 this.project = ""; 247 if (!this.status || !this.routeData.project_name) { 248 // If our status json is loaded and there is a project_name specified 249 // in the URL, we can proceed to load that project's log. 250 return 251 } 252 this.project = this.getProjectByName(this.routeData.project_name); 253 this.build_history = this.project.history; 254 if (this.project['last_successful_build']){ 255 this.last_successful_build = this.project.last_successful_build; 256 } else{ 257 this.last_successful_build = ""; 258 } 259 this.log = ""; 260 this.finish_time = ""; 261 } 262 263 getProjectByName(project_name) { 264 return this.status.projects.find( 265 p => p.name === project_name 266 ); 267 } 268 269 onResponseForFuzzing(e) { 270 this.status_fuzzing = e.detail.response; 271 if (!this.status) { 272 // Show status of the fuzzing builds by default. 273 this.status = this.status_fuzzing; 274 // Manually invoke a _routeChanged call, in order to load the log for 275 // someone going directly to a project's URL. 276 this._routeChanged(); 277 } 278 } 279 280 onResponseForCoverage(e) { 281 this.status_coverage = e.detail.response; 282 } 283 284 onLogResponse(e) { 285 this.log = e.detail.response; 286 this.loading_log = false; 287 } 288 289 onTap(e) { 290 // Change the route, this should auto-magically update the url in the 291 // browser and invoke the _routeChanged method. 292 this.set("route.path", e.model.project.name); 293 } 294 295 onChanged(e) { 296 if (this.$.build_type.selected == "coverage") { 297 this.status = this.status_coverage; 298 } else { 299 this.status = this.status_fuzzing; 300 } 301 } 302 303 requestLog() { 304 var ajax = this.$.logxhr; 305 ajax.url = "/log-" + this.build_id + ".txt"; 306 ajax.generateRequest(); 307 this.loading_log = true; 308 this.log = ""; 309 this.finish_time = this.toLocalDate(this.finish_time); 310 } 311 312 onLastBuildSuccessful(e) { 313 this.build_id = this.last_successful_build.build_id 314 this.finish_time = this.last_successful_build.finish_time 315 this.requestLog() 316 } 317 318 onBuildHistory(e) { 319 this.build_id = e.model.history.build_id 320 this.finish_time = e.model.history.finish_time 321 this.requestLog() 322 } 323 324 showLog(log) { 325 return log !== ""; 326 } 327 328 showTabs() { 329 return this.tab_count !== 0; 330 } 331 332 toLocalDate(str) { 333 let date = new Date(str); 334 let ds = date.toString(); 335 let timezone = ds.substring(ds.indexOf("(")); 336 return date.toLocaleString() + " " + timezone; 337 } 338 339 pad(n) { 340 return n<10 ? '0'+n : n; 341 } 342 343 getLocalDate(str) { 344 let date = new Date(str); 345 return date.toLocaleString() 346 } 347 348 isSuccessful(project) { 349 return (!project.history.length || project.history[0].success) 350 } 351 } 352 353 window.customElements.define(BuildStatus.is, BuildStatus) 354 </script> 355</dom-module> 356