1%{-- 2 - Copyright 2016 SimplifyOps, Inc. (http://simplifyops.com) 3 - 4 - Licensed under the Apache License, Version 2.0 (the "License"); 5 - you may not use this file except in compliance with the License. 6 - You may obtain a copy of the License at 7 - 8 - http://www.apache.org/licenses/LICENSE-2.0 9 - 10 - Unless required by applicable law or agreed to in writing, software 11 - distributed under the License is distributed on an "AS IS" BASIS, 12 - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 - See the License for the specific language governing permissions and 14 - limitations under the License. 15 --}% 16 17<%@ page import="org.rundeck.core.auth.AuthConstants; grails.util.Environment; rundeck.Execution; rundeck.ScheduledExecution" %> 18 19<html> 20 <head> 21 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> 22 <meta name="tabpage" content="events"/> 23 <meta name="layout" content="base" /> 24 <meta name="skipPrototypeJs" content="base" /> 25 26 <title><g:appTitle/> - 27 %{-- <g:if test="${null==execution?.dateCompleted}"><g:message code="now.running" /> - </g:if>--}% 28 <g:if test="${scheduledExecution}"><g:enc>${scheduledExecution?.jobName}</g:enc> : </g:if> 29 <g:else><g:message code="execution.type.adhoc.title" /></g:else> <g:message code="execution.at.time.by.user" args="[g.relativeDateString(atDate:execution.dateStarted),execution.user]"/> 30 </title> 31 <g:set var="followmode" value="${params.mode in ['browse','tail','node']?params.mode:'tail'}"/> 32 <g:set var="authKeys" value="${[AuthConstants.ACTION_KILL, 33 AuthConstants.ACTION_READ, AuthConstants.ACTION_VIEW, AuthConstants.ACTION_CREATE, AuthConstants.ACTION_RUN]}"/> 34 <g:set var="authChecks" value="${[:]}"/> 35 <g:each in="${authKeys}" var="actionName"> 36 <g:if test="${execution.scheduledExecution}"> 37 <%-- set auth values --%> 38 %{ 39 authChecks[actionName]=auth.jobAllowedTest(job:execution.scheduledExecution,action: actionName) 40 }% 41 </g:if> 42 <g:else> 43 %{ 44 authChecks[actionName] = auth.adhocAllowedTest(action: actionName,project:execution.project) 45 }% 46 </g:else> 47 </g:each> 48 <g:set var="adhocRunAllowed" value="${auth.adhocAllowedTest(action: AuthConstants.ACTION_RUN,project:execution.project)}"/> 49 <g:set var="projAdminAuth" value="${auth.resourceAllowedTest(context: AuthConstants.CTX_APPLICATION, type: AuthConstants.TYPE_PROJECT, name: params.project, action: [AuthConstants.ACTION_ADMIN, AuthConstants.ACTION_APP_ADMIN])}"/> 50 <g:set var="deleteExecAuth" value="${auth.resourceAllowedTest(context: AuthConstants.CTX_APPLICATION, type: AuthConstants.TYPE_PROJECT, name: params.project, action: AuthConstants.ACTION_DELETE_EXECUTION) || projAdminAuth}"/> 51 52 <g:set var="defaultLastLines" value="${grailsApplication.config.rundeck.gui.execution.tail.lines.default}"/> 53 <g:set var="maxLastLines" value="${grailsApplication.config.rundeck.gui.execution.tail.lines.max}"/> 54 55 <asset:javascript src="execution/show.js"/> 56 57 <g:embedJSON id="execInfoJSON" data="${[jobId:scheduledExecution?.extid,execId:execution.id]}"/> 58 <g:embedJSON id="jobDetail" 59 data="${[id: scheduledExecution?.extid, name: scheduledExecution?.jobName, group: scheduledExecution?.groupPath, 60 project: params.project ?: request.project]}"/> 61 <g:embedJSON id="workflowDataJSON" data="${workflowTree}"/> 62 <g:embedJSON id="nodeStepPluginsJSON" data="${stepPluginDescriptions.node.collectEntries { [(it.key): [title: it.value.title]] }}"/> 63 <g:embedJSON id="wfStepPluginsJSON" data="${stepPluginDescriptions.workflow.collectEntries { [(it.key): [title: it.value.title]] }}"/> 64 <g:if test="${grails.util.Environment.current==grails.util.Environment.DEVELOPMENT}"> 65 <asset:javascript src="workflow.test.js"/> 66 <asset:javascript src="util/compactMapList.test.js"/> 67 </g:if> 68 <g:jsMessages codes="['execution.show.mode.Log.title','execution.page.show.tab.Nodes.title']"/> 69 70 <asset:stylesheet href="static/css/pages/project-dashboard.css"/> 71 <g:jsMessages code="jobslist.date.format.ko,select.all,select.none,delete.selected.executions,cancel.bulk.delete,cancel,close,all,bulk.delete,running"/> 72 <g:jsMessages code="search.ellipsis 73jobquery.title.titleFilter 74jobquery.title.jobFilter 75jobquery.title.jobIdFilter 76jobquery.title.userFilter 77jobquery.title.statFilter 78jobquery.title.filter 79jobquery.title.recentFilter 80jobquery.title.startbeforeFilter 81jobquery.title.startafterFilter 82jobquery.title.endbeforeFilter 83jobquery.title.endafterFilter 84saved.filters 85search 86"/> 87 <style type="text/css"> 88 #log{ 89 margin-bottom:20px; 90 } 91 .padded{ 92 padding: 10px; 93 } 94 .errmsg { 95 color: gray; 96 } 97 .executionshow.affix:before, 98 .executionshow.affix:after { 99 content: " "; 100 display: table; 101 } 102 .executionshow.affix:after { 103 clear: both; 104 } 105 .executionshow .runoutput { 106 display: none; 107 } 108 .executionshow.affix .runoutput { 109 display: block; 110 } 111 .executionshow.affix { 112 top: 0; 113 width: 80%; 114 z-index: 1; 115 margin-right: auto; 116 margin-left: auto; 117 padding-left: 15px; 118 padding-right: 15px; 119 padding-top: 20px; 120 padding-bottom: 10px; 121 border-bottom: 1px solid #eeeeee; 122 } 123 .executionshow.affix.panel-heading-affix { 124 background-color: #eeeeee; 125 width: auto; 126 margin: 0 15px; 127 padding: 8px 10px; 128 } 129 .affixed-shown { 130 display: none; 131 } 132 .affix .affixed-shown { 133 display: block; 134 margin-top: 0px; 135 margin-left: 15px; 136 } 137 .affix .affixed-shown.affixed-shown-inline { 138 display: inline; 139 } 140 </style> 141 <g:set var="projectName" value="${execution.project}"/> 142 <g:javascript> 143 var execInfo=loadJsonData('execInfoJSON'); 144 window._rundeck = Object.assign(window._rundeck || {}, { 145 data:{ 146 projectAdminAuth:${enc(js:projAdminAuth)}, 147 deleteExecAuth:${enc(js:deleteExecAuth)}, 148 jobslistDateFormatMoment:"${enc(js:g.message(code:'jobslist.date.format.ko'))}", 149 runningDateFormatMoment:"${enc(js:g.message(code:'jobslist.running.format.ko'))}", 150 activityUrl: appLinks.reportsEventsAjax, 151 nowrunningUrl: "${createLink(uri:"/api/${com.dtolabs.rundeck.app.api.ApiVersions.API_CURRENT_VERSION}/project/${projectName}/executions/running")}", 152 bulkDeleteUrl: appLinks.apiExecutionsBulkDelete, 153 activityPageHref:"${enc(js:createLink(controller:'reports',action:'index',params:[project:projectName]))}", 154 sinceUpdatedUrl:"${enc(js:g.createLink(controller:'reports',action: 'since.json', params: [project:projectName]))}", 155 filterListUrl:"${enc(js:g.createLink(controller:'reports',action: 'listFiltersAjax', params: [project:projectName]))}", 156 filterSaveUrl:"${enc(js:g.createLink(controller:'reports',action: 'saveFilterAjax', params: [project:projectName]))}", 157 filterDeleteUrl:"${enc(js:g.createLink(controller:'reports',action: 'deleteFilterAjax', params: [project:projectName]))}", 158 pagination:{ 159 max: ${enc(js:params.max?params.int('max',10):10)} 160 }, 161 query:{ 162 jobIdFilter:execInfo.jobId 163 }, 164 filterOpts: { 165 showFilter: false, 166 showRecentFilter: true, 167 showSavedFilter: false 168 }, 169 runningOpts: { 170 loadRunning:false, 171 allowAutoRefresh: false 172 } 173 } 174}) 175 </g:javascript> 176 <asset:javascript src="static/pages/project-activity.js" defer="defer"/> 177 178 <asset:stylesheet href="static/css/chunk-vendors.css"/> 179 <asset:stylesheet href="static/css/pages/execution-show.css"/> 180 <asset:javascript src="static/pages/execution-show.js" defer="defer"/> 181 </head> 182 <g:set var="isAdhoc" value="${!scheduledExecution && execution.workflow.commands.size() == 1}"/> 183 <body id="executionShowPage"> 184 185 186 <div class="content"> 187 <div id="layoutBody"> 188 <div class="container-fluid"> 189 190 <nav id="subtitlebar" class=" subtitlebar has-content execution-page"> 191 <div class="subtitle-head flex-container reverse flex-align-items-stretch" data-ko-bind="nodeflow"> 192 <div class="subtitle-head-item execution-head-info flex-item-1"> 193 <section class="flex-container reverse"> 194 <section class="flex-item-1 text-right"> 195 <div style="display:inline-block;vertical-align:bottom;margin-right:.3em;"> 196 <g:render template="/scheduledExecution/showExecutionLink" 197 model="[scheduledExecution: scheduledExecution, 198 linkCss : 'text-h4', 199 noimgs : true, 200 execution : execution, 201 hideExecStatus : true, 202 followparams : [mode: followmode, lastlines: params.lastlines] 203 ]"/> 204 </div> 205 206 <g:if test="${deleteExecAuth || authChecks[AuthConstants.ACTION_READ]}"> 207 <div class="btn-group" data-bind="visible: completed()"> 208 <button type="button" 209 class="btn btn-default btn-sm dropdown-toggle" 210 data-toggle="dropdown" 211 aria-expanded="false"> 212 <i class="glyphicon glyphicon-list"></i> 213 <span class="caret"></span> 214 </button> 215 <ul class="dropdown-menu dropdown-menu-right" role="menu"> 216 <g:if test="${eprev}"> 217 <li> 218 <g:link action="show" controller="execution" id="${eprev.id}" 219 params="[project: eprev.project]" 220 title="Previous Execution #${eprev.id}"> 221 <i class="glyphicon glyphicon-arrow-left"></i> 222 <g:message code="previous.execution"/> 223 </g:link> 224 </li> 225 </g:if> 226 227 <g:if test="${enext}"> 228 <li> 229 <g:link action="show" controller="execution" 230 title="Next Execution #${enext.id}" 231 params="[project: enext.project]" 232 id="${enext.id}"> 233 <i class="glyphicon glyphicon-arrow-right"></i> 234 <g:message code="next.execution"/> 235 </g:link> 236 </li> 237 </g:if> 238 <g:if test="${deleteExecAuth}"> 239 <li> 240 <a href="#execdelete" 241 data-toggle="modal"> 242 <i class="fas fa-trash"></i> 243 <g:message code="button.action.delete.this.execution"/> 244 </a> 245 </li> 246 </g:if> 247 248 <g:if test="${authChecks[AuthConstants.ACTION_READ]}"> 249 <li class="divider "> 250 251 </li> 252 <li> 253 <a type="button" href="#details_modal" data-toggle="modal"> 254 <g:icon name="info-sign"/> 255 <g:message code="definition"/> 256 </a> 257 </li> 258 259 </g:if> 260 </ul> 261 </div> 262 </g:if> 263 264 265 266 267 </section> 268 <section class="flex-item-2"> 269 270 <section class="section-space"> 271 272 %{-- end of ifScheduledExecutions --}% 273 <tmpl:wfstateSummaryLine/> 274 275 </section> 276 277 278 <g:if test="${execution.retryAttempt}"> 279 <section class="text-secondary section-space"> 280 <i class="glyphicon glyphicon-repeat"></i> 281 <g:message code="execution.retry.info.label" 282 args="${[execution.retryAttempt, execution.retry]}"/> 283 </section> 284 </g:if> 285 </section> 286 </section> 287 288 <section class="section-space execution-action-links " style="padding-top:.6em;"> 289 290 <g:if test="${null == execution.dateCompleted}"> 291 <span data-bind="if: canKillExec()"> 292 <span data-bind="visible: !completed() "> 293 <!-- ko if: !killRequested() || killStatusFailed() --> 294 <span class="btn btn-sm btn-danger pull-right" 295 data-bind="click: killExecAction"> 296 <g:message code="button.action.kill.job"/> 297 <i class="glyphicon glyphicon-remove"></i> 298 </span> 299 <!-- /ko --> 300 <!-- ko if: killRequested() --> 301 <!-- ko if: killStatusPending() --> 302 <g:img class="loading-spinner" file="spinner-gray.gif" width="16px" 303 height="16px"/> 304 <!-- /ko --> 305 <span class="loading" data-bind="text: killStatusText"></span> 306 <!-- /ko --> 307 <!-- ko if: killedbutNotSaved() --> 308 <span class="btn btn-danger btn-xs pull-right" 309 data-bind="click: markExecAction"> 310 <g:message code="button.action.incomplete.job" default="Mark as Incomplete"/> 311 <i class="glyphicon glyphicon-remove"></i> 312 </span> 313 <!-- /ko --> 314 </span> 315 </span> 316 </g:if> 317 <g:if test="${scheduledExecution}"> 318 <g:if test="${authChecks[AuthConstants.ACTION_RUN] && g.executionMode( 319 active: true, 320 project: execution.project 321 )}"> 322 %{--Run again link--}% 323 <g:link controller="scheduledExecution" 324 action="execute" 325 id="${scheduledExecution.extid}" 326 class=" pull-right" 327 params="${[retryExecId: execution.id, project: execution.project]}" 328 title="${g.message(code: 'execution.job.action.runAgain')}" 329 style="${wdgt.styleVisible( 330 if: null != execution.dateCompleted && 331 null == 332 execution.failedNodeList 333 )};" 334 data-bind="visible: completed() && !failed()"> 335 <g:message code="execution.action.runAgain"/> 336 <i class="fas fa-redo-alt"></i> 337 </g:link> 338 %{--Run again and retry failed links in a dropdown --}% 339 <div class="btn-group pull-right" 340 style="${wdgt.styleVisible( 341 if: null != execution.dateCompleted && 342 null != 343 execution.failedNodeList 344 )};" 345 data-bind="visible: failed()"> 346 <button class="btn btn-default btn-sm dropdown-toggle" 347 data-target="#" 348 data-toggle="dropdown"> 349 <g:message code="execution.action.runAgain.ellipsis"/> 350 <i class="caret"></i> 351 </button> 352 <ul class="dropdown-menu pull-left" role="menu"> 353 <li class="retrybuttons"> 354 <g:link controller="scheduledExecution" 355 action="execute" 356 id="${scheduledExecution.extid}" 357 params="${[retryExecId: execution.id, project: execution.project]}" 358 title="${g.message(code: 'execution.job.action.runAgain')}" 359 data-bind="visible: completed()"> 360 <b class="glyphicon glyphicon-play"></b> 361 362 363 <g:message code="execution.action.runAgain"/> 364 </g:link> 365 </li> 366 <li class="divider"> 367 368 </li> 369 <li class="retrybuttons"> 370 <g:link controller="scheduledExecution" action="execute" 371 id="${scheduledExecution.extid}" 372 params="${[retryFailedExecId: execution.id, project: execution.project]}" 373 title="${g.message(code: 'retry.job.failed.nodes')}"> 374 <b class="glyphicon glyphicon-play"></b> 375 <g:message code="retry.failed.nodes"/> 376 </g:link> 377 </li> 378 379 %{-- todo extra actions--}% 380 381 <g:ifMenuItems type="EXECUTION_RETRY" project="${params.project}" execution="${execution.id.toString()}"> 382 <li role="separator" class="divider"></li> 383 <g:forMenuItems type="EXECUTION_RETRY" var="item" project="${params.project}" execution="${execution.id.toString()}"> 384 <li> 385 <a href="${enc(attr:item.getExecutionHref(params.project, execution.id.toString()))}" 386 title="${enc(attr:g.message(code:item.titleCode,default:item.title))}"> 387 <span class="sidebar-mini"><i class="${enc(attr: item.iconCSS ?: 'fas fa-plug')}"></i></span> 388 <span class="sidebar-normal"> 389 <g:message code="${item.titleCode}" default="${item.title}"/> 390 </span> 391 </a> 392 </li> 393 </g:forMenuItems> 394 </g:ifMenuItems> 395 </ul> 396 </div> 397 398 </g:if> 399 400 </g:if> 401 <g:if test="${isAdhoc}"> 402 %{--run again links--}% 403 <g:if test="${adhocRunAllowed && g.executionMode( 404 active: true, 405 project: execution.project 406 )}"> 407 %{--run again only--}% 408 <g:link 409 controller="framework" 410 action="adhoc" 411 params="${[fromExecId: execution.id, project: execution.project]}" 412 title="${g.message(code: 'execution.action.runAgain')}" 413 class=" pull-right" 414 style="${wdgt.styleVisible( 415 if: null != execution.dateCompleted && 416 null == 417 execution.failedNodeList 418 )}" 419 data-bind="visible: completed() && !failed()"> 420 421 <g:message code="execution.action.runAgain"/> 422 <i class="fas fa-redo-alt"></i> 423 </g:link> 424 %{--run again and retry failed --}% 425 <div class="btn-group pull-right" 426 style="${wdgt.styleVisible( 427 if: null != execution.dateCompleted && 428 null != 429 execution.failedNodeList 430 )}" 431 data-bind="visible: failed()"> 432 <button class="btn btn-default btn-sm dropdown-toggle " 433 data-target="#" 434 data-toggle="dropdown"> 435 <g:message code="execution.action.runAgain.ellipsis"/> 436 <i class="caret"></i> 437 </button> 438 <ul class="dropdown-menu pull-right" role="menu"> 439 440 <li> 441 <g:link 442 controller="framework" 443 action="adhoc" 444 params="${[fromExecId: execution.id, project: execution.project]}" 445 title="${g.message(code: 'execution.action.runAgain')}"> 446 447 <b class="glyphicon glyphicon-play"></b> 448 <g:message code="execution.action.runAgain"/>… 449 </g:link> 450 </li> 451 <li class="divider "> 452 453 </li> 454 <li> 455 <g:link 456 controller="framework" 457 action="adhoc" 458 params="${[retryFailedExecId: execution.id, project: execution.project]}" 459 title="${g.message(code: 'retry.failed.nodes.description')}"> 460 461 <b class="glyphicon glyphicon-play"></b> 462 <g:message code="retry.failed.nodes"/>… 463 </g:link> 464 </li> 465 </ul> 466 </div> 467 </g:if> 468 </g:if> 469 470 </section> 471 472 </div> 473 474 <div class="subtitle-head-item execution-aux-info flex-item-1"> 475 <section> 476 <g:if test="${isAdhoc}"> 477 <div class="text-h5"> 478 <b class="exec-status icon " 479 data-bind="attr: { 'data-execstate': executionState, 'data-statusstring':executionStatusString }"> 480 </b> 481 <g:render template="wfItemView" model="[ 482 item: execution.workflow.commands[0], 483 icon: 'icon-small' 484 ]"/> 485 </div> 486 </g:if> 487 <g:if test="${scheduledExecution}"> 488 <g:render template="/scheduledExecution/showHead" 489 model="[scheduledExecution: scheduledExecution, 490 includeExecStatus : true, 491 jobDescriptionMode: 'expanded', 492 jobActionButtons : true, 493 linkCss : 'text-h4', 494 scmExportEnabled : scmExportEnabled, 495 scmExportStatus : scmExportStatus, 496 scmImportEnabled : scmImportEnabled, 497 scmImportStatus : scmImportStatus 498 ]"/> 499 500 <g:if test="${execution.argString}"> 501 <section class=" section-space exec-args-section argstring-scrollable"> 502 <span class="text-secondary"><g:message code="options.prompt"/></span class="text-secondary"> 503 <g:render template="/execution/execArgString" 504 model="[argString: execution.argString, inputFilesMap: inputFilesMap]"/> 505 </section> 506 </g:if> 507 508 509 510 </g:if> 511 512 </section> 513 514 </div> 515 </div> 516 517 <div class="" data-bind="if: !completed() " data-ko-bind="nodeflow"> 518 <g:if test="${scheduledExecution}"> 519 %{--progress bar--}% 520 <div> 521 <section 522 data-bind="if: !completed() && !queued() && jobAverageDuration()>0"> 523 <g:set var="progressBind" 524 value="${', css: { \'progress-bar-info\': jobPercentageFixed() < 105 , \'progress-bar-warning\': jobPercentageFixed() > 104 }'}"/> 525 <g:render template="/common/progressBar" 526 model="[completePercent : execution.dateCompleted ? 100 : 0, 527 progressClass : 'rd-progress-exec progress-embed progress-square', 528 progressBarClass: '', 529 containerId : 'progressContainer2', 530 innerContent : '', 531 showpercent : true, 532 height : 28, 533 progressId : 'progressBar', 534 bind : 'jobPercentageFixed()', 535 bindText : '(jobPercentageFixed() < 105 ? jobPercentageFixed() + \'%\' : \'+\' + jobOverrunDuration()) + \' of average \' + formatDurationHumanize(jobAverageDuration())', 536 progressBind : progressBind, 537 ]"/> 538 </section> 539 <section data-bind="if: queued()"> 540 <div class="progress progress-striped" style="height: 28px"> 541 <div class="progress-bar" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" 542 style="width: 100%;line-height: 28px"> 543 Queued 544 </div> 545 </div> 546 </section> 547 </div> 548 </g:if> 549 <g:if test="${isAdhoc}"> 550 551 %{--progress bar--}% 552 <div> 553 <section> 554 <g:render template="/common/progressBar" 555 model="[completePercent : 100, 556 indefinite : true, 557 progressClass : 'rd-progress-exec progress-embed progress-square', 558 progressBarClass: '', 559 containerId : 'progressContainer2', 560 innerContent : '', 561 showpercent : false, 562 height : 28, 563 progressId : 'progressBar', 564 565 ]"/> 566 </section> 567 568 </div> 569 </g:if> 570 </div> 571 </nav> 572 573 574 <div class="row"> 575 <div class="col-sm-12"> 576 <div class="card card-plain " data-ko-bind="nodeflow"> 577 <div class="btn-group " data-bind="if: views().length>2"> 578 <button class="btn btn-default btn-sm dropdown-toggle " 579 id="views_dropdown_button" 580 data-target="#" 581 data-toggle="dropdown"> 582 <span class="colon-after"><g:message code="view"/></span> 583 <span data-bind="text: activeTabData() && activeTabData().title"> 584 585 </span> 586 <i class="caret"></i> 587 </button> 588 <ul class="dropdown-menu pull-left" role="menu" data-bind="foreach: views"> 589 590 <li data-bind="attr: {id: 'tab_link_'+id }"> 591 <a href="#" 592 data-bind="click: function(){$root.activeTab(id)}, attr: {href: '#'+id }"> 593 <span data-bind="text: title"></span> 594 <!-- ko if: $root.activeTab()===id --> 595 <i class="fas fa-check" style="margin-left:1em"></i> 596 <!-- /ko --> 597 </a> 598 </li> 599 600 </ul> 601 602 </div> 603 <!-- ko foreach: viewButtons --> 604 <a href="#" 605 data-bind="click:function(){$root.activeTab(id)}, attr: {href: '#'+id, id: 'btn_view_'+id }, visible: $root.activeTab()!==id" 606 class="btn btn-sm"> 607 <span data-bind="text: title"></span> » 608 </a> 609 <!-- /ko --> 610 611 612 <span data-bind="visible: activeTab().startsWith('output')"> 613 614 615 <span data-bind="visible: completed()" class="execution-action-links pull-right"> 616 617 <span class="btn-group"> 618 <button type="button" class="btn btn-default btn-xs dropdown-toggle" 619 data-toggle="dropdown"> 620 <g:message code="execution.log" /> 621 <span class="caret"></span> 622 </button> 623 <ul class="dropdown-menu pull-right" role="menu"> 624 <li> 625 <g:link class="" 626 title="${message( 627 code: 'execution.show.log.text.button.description', 628 default: 'View text output' 629 )}" 630 controller="execution" 631 action="downloadOutput" 632 id="${execution.id}" 633 params="[ 634 view : 'inline', 635 formatted: false, 636 project : execution.project, 637 stripansi: true 638 ]" 639 target="_blank"> 640 641 <g:message code="execution.show.log.text.button.title"/> 642 </g:link> 643 </li> 644 <li> 645 646 <g:link class="" 647 title="${message( 648 code: 'execution.show.log.html.button.description', 649 default: 'View rendered output' 650 )}" 651 controller="execution" 652 action="renderOutput" 653 id="${execution.id}" 654 params="[ 655 project: execution.project, 656 ansicolor: 'on', 657 loglevels: 'on', 658 convertContent: 'on' 659 ]" 660 target="_blank"> 661 662 <g:message code="execution.show.log.html.button.title"/> 663 </g:link> 664 </li> 665 <li role="separator" class="divider"></li> 666 <li class="dropdown-header"> 667 <g:message code="execution.show.log.download.button.title"/> 668 </li> 669 <li> 670 <g:link class="_guess_tz_param" 671 data-tz-url-param="timeZone" 672 title="${message( 673 code: 'execution.show.log.download.button.description', 674 default: 'Download {0} bytes', 675 args: [filesize > 0 ? filesize : '?'] 676 )}" 677 controller="execution" 678 action="downloadOutput" 679 id="${execution.id}" 680 params="[project: execution.project]" 681 target="_blank"> 682 683 <b class="glyphicon glyphicon-download"></b> 684 <g:message code="formatted.text" /> 685 </g:link> 686 </li> 687 </ul> 688 </span> 689 690 </span> 691 692 <g:render template="/common/modal" 693 model="[modalid : 'view-options-modal', 694 titleCode : 'execution.page.view.options.title', 695 cancelCode: 'close']"> 696 <div class="container form-horizontal"> 697 698 <div class="form-group"> 699 <label class="col-sm-2 control-label" for="view-option-style-mode"> 700 Style 701 </label> 702 703 <div class="col-sm-10"> 704 705 <select data-bind="options: logoutput().options.styleModesAvailable, value:logoutput().options.styleMode" 706 class="form-control" 707 id="view-option-style-mode"> 708 709 </select> 710 711 </div> 712 </div> 713 714 715 <div class="form-group"> 716 717 <label class="col-sm-2 control-label"> 718 Text 719 </label> 720 <div class="col-sm-10"> 721 <div class="checkbox"> 722 <input type="checkbox" 723 data-bind="checked: logoutput().options.showAnsicolor" 724 id="view-option-ansi-color"/> 725 <label for="view-option-ansi-color"> 726 <g:message code="execution.show.mode.ansicolor.title" 727 default="Ansi Color"/> 728 </label> 729 </div> 730 </div> 731 732 <div class="col-sm-offset-2 col-sm-10"> 733 <div class="checkbox"> 734 <input type="checkbox" 735 data-bind="checked: logoutput().options.wrapLines" 736 id="view-option-wrap-lines"/> 737 <label for="view-option-wrap-lines"> 738 <g:message code="execution.show.mode.wrapmode.title" 739 default="Wrap Long Lines"/> 740 </label> 741 </div> 742 </div> 743 744 <div class="col-sm-offset-2 col-sm-10"> 745 <div class="checkbox"> 746 <input type="checkbox" 747 data-bind="checked: logoutput().options.followmodeNode" 748 id="view-option-node-view"/> 749 <label for="view-option-node-view"> 750 <g:message code="execution.show.mode.Compact.title" 751 default="Compact"/> 752 </label> 753 </div> 754 </div> 755 756 </div> 757 758 <div class="form-group"> 759 760 <label class="col-sm-2 control-label">Columns</label> 761 762 <div class="col-sm-10"> 763 764 <div> 765 766 <div class="checkbox-inline"> 767 <input type="checkbox" 768 value="true" 769 data-bind="checked: logoutput().options.showTime" 770 id="view-option-show-time"/> 771 <label for="view-option-show-time"> 772 <g:message code="execution.show.mode.column.time" /> 773 </label> 774 </div> 775 776 <div class="checkbox-inline"> 777 <input type="checkbox" 778 value="true" 779 data-bind="checked: logoutput().options.showNodeCol" 780 id="view-option-show-node"/> 781 <label for="view-option-show-node"> 782 <g:message code="execution.show.mode.column.node" /> 783 </label> 784 </div> 785 786 <div class="checkbox-inline"> 787 <input type="checkbox" 788 value="true" 789 data-bind="checked: logoutput().options.showStep" 790 id="view-option-show-step"/> 791 <label for="view-option-show-step"> 792 <g:message code="execution.show.mode.column.step" /> 793 </label> 794 </div> 795 </div> 796 797 </div> 798 </div> 799 800 801 <div class="form-group"> 802 <label class="col-sm-2 control-label"> 803 <g:message code="execution.show.mode.inset.label" /> 804 </label> 805 <div class="col-sm-10"> 806 <div class="checkbox"> 807 <input type="checkbox" 808 data-bind="checked: logoutput().options.showNodeInset" 809 id="view-option-node-inset"/> 810 <label for="view-option-node-inset"> 811 <g:message code="execution.show.mode.inset.node" /> 812 </label> 813 </div> 814 </div> 815 816 </div> 817 818 </div> 819 820 </g:render> 821 822 </span> 823 824 </div> 825 826 <div class="card exec-output " 827 data-ko-bind="nodeflow" 828 data-mode="normal" 829 data-bind="attr: {'data-mode': logoutput().options.styleMode }, css: {'exec-output-bg': activeTab()==='output' }"> 830 831 <div class="card-content " data-bind="css: {tight: activeTab().startsWith('output') }"> 832 <g:render template="/common/messages"/> 833 834 835 <div class="tab-content" id="exec-main-view"> 836 837 <!-- ko foreach: contentViews --> 838 <div class="tab-pane" data-bind="css: {active: $root.activeTab()===id}, attr: {id:id}"> 839 <span data-bind="attr: {id:id+'_content'}, html:content"></span> 840 </div> 841 <!-- /ko --> 842 <div class="tab-pane " id="nodes" data-bind="css: {active: activeTab()==='nodes'}"> 843 <div class="flowstate ansicolor ansicolor-on" id="nodeflowstate"> 844 <g:render template="wfstateNodeModelDisplay" bean="${workflowState}" 845 var="workflowState"/> 846 </div> 847 </div> 848 849 <div style="height: calc(100vh - 250px); display: none; contain: layout;" 850 id="output" 851 class="card-content-full-width" 852 data-bind="visible: activeTab() === 'output' || activeTab().startsWith('outputL')" 853 > 854 <div class="execution-log-viewer" data-execution-id="${execution.id}" data-theme="light" data-follow="true"></div> 855 </div> 856 857 </div> 858 </div> 859 860 <g:if test="${authChecks[AuthConstants.ACTION_READ]}"> 861 <g:render template="/common/modal" 862 model="[ 863 modalid : 'details_modal', 864 modalsize : 'modal-lg', 865 title : message(code: 'definition'), 866 cancelCode: 'close', 867 links : isAdhoc ? [ 868 [ 869 messageCode: 'execution.action.saveAsJob.ellipsis', 870 href : createLink( 871 controller: 'scheduledExecution', 872 action: 'createFromExecution', 873 params: [executionId: execution.id, project: execution.project] 874 ), 875 bind : 'visible: completed()', 876 css : 'btn-success' 877 ] 878 ] : [] 879 ]"> 880 881 <div> 882 <g:render template="execDetails" 883 model="[execdata: execution, showArgString: false, hideAdhoc: false, isScheduled:isScheduled]"/> 884 </div> 885 886 </g:render> 887 888 </g:if> 889 </div> 890 </div> 891 <g:if test="${scheduledExecution}"> 892 893 <g:set var="hasEventReadAuth" value="${auth.resourceAllowedTest( 894 project: scheduledExecution.project, 895 action: AuthConstants.ACTION_READ, 896 kind: AuthConstants.TYPE_EVENT 897 )}"/> 898 <div class="col-sm-12"> 899 900 <div class="card" id="activity_section"> 901 <div class="card-content"> 902 903 <div class="vue-tabs"> 904 <div class="nav-tabs-navigation"> 905 <div class="nav-tabs-wrapper"> 906 <ul class="nav nav-tabs activity_links"> 907 <li class="active"> 908 <a href="#stats" data-toggle="tab"><g:message code="job.view.stats.label" /></a> 909 </li> 910 <g:if test="${hasEventReadAuth}"> 911 <li> 912 <a href="#history" data-toggle="tab"><g:message code="page.section.Activity" /></a> 913 </li> 914 </g:if> 915 </ul> 916 </div> 917 </div> 918 <div class="tab-content"> 919 <div class="tab-pane active" id="stats"> 920 921 922 <section class="_jobstats_content section-space-bottom-lg container-fluid" id="_job_stats_main"> 923 <g:render template="/scheduledExecution/renderJobStats" 924 model="${[scheduledExecution: scheduledExecution]}"/> 925 </section> 926 927 928 <div id="_job_stats_extra_placeholder"></div> 929 </div> 930 <g:if test="${hasEventReadAuth}"> 931 <div class="tab-pane" id="history"> 932 933 <div data-ko-bind="history" class="_history_content vue-project-activity"> 934 935 <activity-list :event-bus="EventBus"></activity-list> 936 </div> 937 </div> 938 </g:if> 939 </div> 940 </div> 941 942 943 </div> 944 </div> 945 </div> 946 947 </g:if> 948 </div> 949 950 951 </div> 952 </div> 953 </div> 954 <g:render template="/menu/copyModal" 955 model="[projectNames: projectNames]"/> 956 957 %{--delete execution modal--}% 958 <g:if test="${deleteExecAuth}"> 959 <div class="modal" id="execdelete" tabindex="-1" role="dialog" aria-labelledby="deleteexectitle" aria-hidden="true"> 960 <div class="modal-dialog"> 961 <div class="modal-content"> 962 <div class="modal-header"> 963 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button> 964 <h4 class="modal-title" id="deleteexectitle"><g:message code="delete.execution.title" /></h4> 965 </div> 966 967 <div class="modal-body"> 968 <p class=" "><g:message code="really.delete.this.execution" /></p> 969 </div> 970 <div class="modal-footer"> 971 <g:form controller="execution" action="delete" method="post" useToken="true"> 972 <g:hiddenField name="id" value="${execution.id}"/> 973 <button type="submit" class="btn btn-default btn-xs " data-dismiss="modal"> 974 <g:message code="cancel" /> 975 </button> 976 <input type="submit" value="${g.message(code:'button.action.Delete')}" class="btn btn-danger btn-xs"/> 977 </g:form> 978 </div> 979 </div> 980 </div> 981 </div> 982 </g:if> 983 %{--/delete execution modal--}% 984 985 986 <script type="text/html" id="step-info-simple"> 987 %{--Display the lowest level step info: [icon] identity --}% 988 <i class="rdicon icon-small" data-bind="css: stepinfo.type"></i> 989 <span data-bind="text: stepinfo.stepident"></span> 990 </script> 991 <script type="text/html" id="step-info"> 992 %{--wrap step-info-simple in tooltip --}% 993 <span data-bind="attr: {title: stepinfo.stepctxPathFull}, bootstrapTooltip: stepinfo.stepctxPathFull" data-placement="top" data-container='body'> 994 <span data-bind="template: { name: 'step-info-simple', data:stepinfo, as: 'stepinfo' }"></span> 995 </span> 996 </script> 997 <script type="text/html" id="step-info-simple-link"> 998 %{--wrap step-info-simple in tooltip --}% 999 <span data-bind="if: stepinfo.hasLink()"> 1000 <a data-bind="urlPathParam: stepinfo.linkJobId(), attr: {title: 'Click to view Job: '+stepinfo.linkTitle() }" 1001 href="${createLink( 1002 controller: 'scheduledExecution', 1003 action: 'show', 1004 params: [project: execution.project, id: '<$>'] 1005 )}"> 1006 <span data-bind="template: { name: 'step-info-simple', data:stepinfo, as: 'stepinfo' }"></span> 1007 </a> 1008 </span> 1009 <span data-bind="if: !stepinfo.hasLink()"> 1010 <span data-bind="template: { name: 'step-info-simple', data:stepinfo, as: 'stepinfo' }"></span> 1011 </span> 1012 </script> 1013 <script type="text/html" id="step-info-path"> 1014 %{-- Display the full step path with icon and identity --}% 1015 <span data-bind="if: stepinfo.hasParent()"> 1016 <span data-bind="with: stepinfo.parentStepInfo()"> 1017 <span data-bind="template: { name: 'step-info-path', data:$data, as: 'stepinfo' }"></span> 1018 </span> 1019 <g:icon name="menu-right" css="text-strong"/> 1020 1021 </span> 1022 <span data-bind="template: { name: 'step-info-simple', data:stepinfo, as: 'stepinfo' }"></span> 1023 </script> 1024 <script type="text/html" id="step-info-path-links"> 1025 %{-- Display the full step path with icon and identity --}% 1026 <span data-bind="if: stepinfo.hasParent()"> 1027 <span data-bind="with: stepinfo.parentStepInfo()"> 1028 <span data-bind="template: { name: 'step-info-path-links', data:$data, as: 'stepinfo' }"></span> 1029 </span> 1030 <g:icon name="menu-right" css="text-strong"/> 1031 1032 </span> 1033 <span data-bind="template: { name: 'step-info-simple-link', data:stepinfo, as: 'stepinfo' }"></span> 1034 </script> 1035 <script type="text/html" id="step-info-parent-path"> 1036 %{-- Display the full step path with icon and identity --}% 1037 1038 <span data-bind="if: stepinfo.hasParent()"> 1039 <span data-bind="with: stepinfo.parentStepInfo()"> 1040 <span data-bind="template: { name: 'step-info-path', data:$data, as: 'stepinfo' }"></span> 1041 </span> 1042 <g:icon name="menu-right" css="text-strong"/> 1043 </span> 1044 </script> 1045 <script type="text/html" id="step-info-parent-path-links"> 1046 %{-- Display the full step path with icon and identity --}% 1047 1048 <span data-bind="if: stepinfo.hasParent()"> 1049 <span data-bind="with: stepinfo.parentStepInfo()"> 1050 <span data-bind="template: { name: 'step-info-path-links', data:$data, as: 'stepinfo' }"></span> 1051 </span> 1052 <g:icon name="menu-right" css="text-strong"/> 1053 </span> 1054 </script> 1055 1056 <script type="text/html" id="step-info-path-base"> 1057 %{-- Display the full step path with icon and identity --}% 1058 <span data-bind="template: { name: 'step-info-parent-path', data:stepinfo, as: 'stepinfo' }"></span> 1059 1060 <span data-bind="template: { name: 'step-info', data:stepinfo, as: 'stepinfo' }"></span> 1061 </script> 1062 1063 <script type="text/html" id="step-info-extended"> 1064 %{--Display the lowest level extended info: [icon] number. identity --}% 1065 <span data-bind="attr: {title: stepinfo.stepctxPathFull}, bootstrapTooltip: stepinfo.stepctxPathFull" data-placement="top" data-container='body'> 1066 <i class="rdicon icon-small" data-bind="css: stepinfo.type"></i> 1067 <span data-bind="text: stepinfo.stepdesc"></span> 1068 </span> 1069 </script> 1070 <g:jsonToken id="exec_cancel_token" url="${request.forwardURI}"/> 1071 <!--[if (gt IE 8)|!(IE)]><!--> <asset:javascript src="ace-bundle.js"/><!--<![endif]--> 1072 <script type="application/javascript"> 1073 var workflow=null; 1074 var followControl=null; 1075 var flowState=null; 1076 var nodeflowvm=null; 1077 var logoutput=null; 1078 function followOutput(){ 1079 nodeflowvm.logoutput().beginFollowingOutput('${enc(js: execution?.id)}'); 1080 } 1081 function followState(){ 1082 try{ 1083 flowState.beginFollowing(); 1084 }catch(e){ 1085 nodeflowvm.errorMessage('Could not load flow state: '+e); 1086 nodeflowvm.stateLoaded(false); 1087 } 1088 } 1089 1090 var activity; 1091 function init() { 1092 var execInfo=loadJsonData('execInfoJSON'); 1093 var workflowData=loadJsonData('workflowDataJSON'); 1094 RDWorkflow.nodeSteppluginDescriptions=loadJsonData('nodeStepPluginsJSON'); 1095 RDWorkflow.wfSteppluginDescriptions=loadJsonData('wfStepPluginsJSON'); 1096 workflow = new RDWorkflow(workflowData); 1097 1098 var multiworkflow=new MultiWorkflow(workflow,{ 1099 dynamicStepDescriptionDisabled:${enc(js:feature.isDisabled(name:'workflowDynamicStepSummaryGUI'))}, 1100 url:appLinks.scheduledExecutionWorkflowJson, 1101 id:execInfo.jobId||execInfo.execId,//id of job or execution 1102 workflow:workflowData 1103 }); 1104 followControl = new FollowControl('${execution?.id}','outputappendform',{ 1105 parentElement:'commandPerform', 1106 fileloadId:'fileload', 1107 fileloadPctId:'fileloadpercent', 1108 fileloadProgressId:'fileloadprogress', 1109 cmdOutputErrorId:'cmdoutputerror', 1110 outfileSizeId:'outfilesize', 1111 workflow:workflow, 1112 multiworkflow:multiworkflow, 1113 appLinks:appLinks, 1114 1115 extraParams:"<%="true" == params.disableMarkdown ? '&disableMarkdown=true' : ''%>&markdown=${enc(js:enc(url: params.markdown))}&ansicolor=${enc(js:enc(url: params.ansicolor))}&renderContent=${enc(js:enc(url: params.renderContent))}", 1116 lastlines: '${enc(js:params.int('lastlines') ?: defaultLastLines)}', 1117 maxLastLines:'${enc(js:params.int('maxlines') ?: maxLastLines)}', 1118 collapseCtx: {value:${enc(js:null == execution?.dateCompleted)},changed:false}, 1119 showFinalLine: {value:false,changed:false}, 1120 tailmode: ${enc(js:followmode == 'tail')}, 1121 browsemode: ${enc(js:followmode == 'browse')}, 1122 nodemode: ${enc(js:followmode == 'node')}, 1123 execData: {}, 1124 groupOutput:{value:${enc(js:followmode == 'browse')}}, 1125 updatepagetitle:${enc(js:null == execution?.dateCompleted)}, 1126 killjobauth:${enc(js: authChecks[AuthConstants.ACTION_KILL] ? true : false)}, 1127 <g:if test="${authChecks[AuthConstants.ACTION_KILL]}"> 1128 killjobhtml: '<span class="btn btn-danger btn-xs textbtn" onclick="followControl.docancel();">Kill <g:message code="domain.ScheduledExecution.title"/> <i class="glyphicon glyphicon-remove"></i></span>', 1129 </g:if> 1130 <g:if test="${!authChecks[AuthConstants.ACTION_KILL]}"> 1131 killjobhtml: "", 1132 </g:if> 1133 totalDuration : '${enc(js:scheduledExecution?.getTotalTimeStats()?: -1)}', 1134 totalCount: '${enc(js: scheduledExecution?.getExecCountStats() ?: -1)}', 1135 colStep:{value:${enc(js: !isAdhoc)} }, 1136 colNode:{value:false} 1137 }); 1138 nodeflowvm=new NodeFlowViewModel( 1139 workflow, 1140 "${enc(js:g.createLink(controller: 'execution', action: 'tailExecutionOutput', id: execution.id,params:[format:'json']))}", 1141 "${enc(js:g.createLink(controller: 'execution', action: 'ajaxExecNodeState', id: execution.id))}", 1142 multiworkflow, 1143 { 1144 followControl:followControl, 1145 executionId:'${enc(js: execution.id)}', 1146 logoutput: new LogOutput({ 1147 followControl:followControl, 1148 bindFollowControl:true, 1149 options:{ 1150 followmode:"${enc(js: followmode)}", 1151 showStep:${enc(js: !isAdhoc)}, 1152 showNodeCol:false, 1153 } 1154 } ), 1155 views: [ 1156 {id: 'nodes', title: message('execution.page.show.tab.Nodes.title'), showButton: true}, 1157 {id: 'output', title: message('execution.show.mode.Log.title'), showButton: true} 1158 ] 1159 } 1160 ); 1161 flowState = new FlowState('${enc(js: execution?.id)}','flowstate',{ 1162 workflow:workflow, 1163 loadUrl: "${enc(js:g.createLink(controller: 'execution', action: 'ajaxExecState', id: execution.id))}", 1164 outputUrl:"${g.enc(js:createLink(controller: 'execution', action: 'tailExecutionOutput', id: execution.id,params:[format:'json']))}", 1165 selectedOutputStatusId:'selectedoutputview', 1166 reloadInterval:1500, 1167 }); 1168 1169 nodeflowvm.followFlowState(flowState,true); 1170 1171 ko.mapping.fromJS({ 1172 completed:'${execution.dateCompleted != null}', 1173 startTime:'${enc(js:execution.dateStarted)}', 1174 endTime:'${enc(js:execution.dateCompleted)}', 1175 executionState:'${enc(js:execution.executionState)}', 1176 executionStatusString:'${enc(js:execution.status)}' 1177 },{},nodeflowvm); 1178 1179 nodeflowvm.selectedNodes.subscribe(function (newValue) { 1180 if (newValue) { 1181 flowState.loadUrlParams=jQuery.extend(flowState.loadUrlParamsBase,{nodes:newValue.join(",")}); 1182 }else{ 1183 flowState.loadUrlParams=flowState.loadUrlParamsBase; 1184 } 1185 }); 1186 1187 //knockout activeTab change listener to begin output or state listener 1188 nodeflowvm.activeTab.subscribe(function(val){ 1189 window.location.hash = "#" + val 1190 if (val === 'nodes') { 1191 followState(); 1192 } 1193 }); 1194 1195 let doupdate = true//!nodeflowvm.completed() 1196 let prefixed='' 1197 const updateTitle = function (prefix) { 1198 let title=document.title 1199 if(prefixed && title.startsWith(prefixed)){ 1200 title=title.substring(prefixed.length) 1201 } 1202 document.title = prefix + title; 1203 prefixed=prefix 1204 } 1205 1206 nodeflowvm.executionState.subscribe(function (val) { 1207 if (val === 'RUNNING' && !doupdate) { 1208 doupdate = true 1209 } else if (val === 'RUNNING' && doupdate) { 1210 doupdate = true 1211 1212 updateTitle('[RUNNING] ') 1213 } else if (null != val && val !== 'RUNNING' && doupdate) { 1214 var prefix = ( 1215 val === 'SUCCEEDED' ? 1216 '✅ [OK] ' : 1217 val === 'ABORTED' ? 1218 '✖︎ [KILLED] ' : 1219 val === 'TIMEDOUT' ? 1220 '⏱︎ [TIMEOUT] ' : 1221 val === 'FAILED' ? 1222 '⛔︎ [FAILED] ' : 1223 val === 'QUEUED' ? 1224 ' [QUEUED] ' : 1225 ('✴️ [' + (val) + '] ')// 1226 ); 1227 updateTitle(prefix) 1228 } 1229 }) 1230 1231 1232 1233 jQuery('.apply_ace').each(function () { 1234 _applyAce(this); 1235 }); 1236 1237 PageActionHandlers.registerHandler('copy_other_project',function(el){ 1238 jQuery('#jobid').val(el.data('jobId')); 1239 jQuery('#selectProject').modal(); 1240 jQuery.ajax({ 1241 dataType:'json', 1242 method: 'GET', 1243 url:_genUrl(appLinks.authProjectsToCreateAjax), 1244 success:function(data){ 1245 jQuery('#jobProject').empty(); 1246 for (let i in data.projectNames ) { 1247 jQuery('#jobProject').append( 1248 '<option value="' + data.projectNames[i] + '">' + data.projectNames[i] + '</option>' 1249 ); 1250 } 1251 } 1252 }); 1253 }); 1254 followState(); 1255 var outDetails = window.location.hash; 1256 if(outDetails.startsWith('#output')) { 1257 nodeflowvm.activeTab(outDetails.slice(1)) 1258 } else if (outDetails === '#nodes') { 1259 nodeflowvm.activeTab("nodes"); 1260 }else{ 1261 //default to nodes tab 1262 nodeflowvm.activeTab("nodes"); 1263 } 1264 initKoBind(null, {nodeflow: nodeflowvm}) 1265 } 1266 jQuery(init); 1267 </script> 1268 </body> 1269</html> 1270