2023-01-26 15:03:47 +01:00
class CCPExecutionHistory extends HTMLElement {
2023-02-01 16:07:15 +01:00
2023-01-26 15:03:47 +01:00
# boot = null ;
# rootdoc = null ;
# serviceurl = null ;
# broadcasturl = null ;
2023-04-21 15:05:56 +02:00
# archive = null ;
2023-02-01 16:07:15 +01:00
# data = [ ] ;
2023-05-18 12:11:46 +02:00
# pending = [ ] ;
2023-02-01 16:07:15 +01:00
# filtered = [ ] ;
2023-06-23 12:04:53 +02:00
# socket = null ;
2023-03-13 17:36:15 +01:00
# interval = null ;
2023-02-01 16:07:15 +01:00
# searchfield = null ;
2023-04-21 15:05:56 +02:00
# fileupload = null ;
# archiveupload = null ;
2023-01-26 15:03:47 +01:00
constructor ( ) {
super ( )
this . # boot = document . querySelector ( "d4s-boot-2" )
this . # rootdoc = this . attachShadow ( { "mode" : "open" } )
this . # serviceurl = this . getAttribute ( "serviceurl" )
this . # broadcasturl = this . getAttribute ( "broadcasturl" )
if ( ! this . # broadcasturl ) {
this . # broadcasturl = this . # serviceurl . replace ( /^http/ , "ws" )
}
this . # broadcasturl = this . # broadcasturl + "/ws/notification"
2023-04-21 15:05:56 +02:00
this . # archive = this . getAttribute ( "archive" )
2023-01-26 15:03:47 +01:00
this . connectNewExecution ( )
}
connectedCallback ( ) {
2023-06-26 20:11:40 +02:00
this . connectBroadcastWithSubject ( )
2023-02-01 16:07:15 +01:00
this . render ( )
2023-01-26 15:03:47 +01:00
this . refreshExecutions ( )
}
render ( ) {
2023-02-01 16:07:15 +01:00
this . # rootdoc . innerHTML = `
2023-03-28 11:43:38 +02:00
< link rel = "stylesheet" href = "https://cdn.dev.d4science.org/ccp/css/common.css" > < / l i n k >
< link rel = "stylesheet" href = "https://cdn.jsdelivr.net/npm/bootstrap@4.0.0/dist/css/bootstrap.min.css" integrity = "sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin = "anonymous" >
2023-01-26 15:03:47 +01:00
< style >
. lxd {
background - color : # dd4814 ;
color : white ;
}
. docker {
color : white ;
background - color : # 2496 ed ;
}
2023-02-07 17:25:07 +01:00
. ccp - execution - list {
min - height : 3 rem ;
}
2023-03-29 16:19:35 +02:00
. ccp - execution - list . lxd {
background - color : # dd4814 ;
}
. ccp - execution - list . docker {
background - color : # 2496 ed ;
}
2023-01-26 15:03:47 +01:00
< / s t y l e >
< template id = "EXECUTIOM_LIST_TEMPLATE" >
2023-02-07 17:25:07 +01:00
< ul name = "ccp_execution_list" class = "ccp-execution-list list-group border border-2" >
2023-02-01 16:07:15 +01:00
< li class = "ccp-method-item list-group-item list-group-item-dark" >
< details name = "level1" >
2023-05-24 09:34:53 +02:00
< summary class = "ccp-method-item-header noselect d-flex flex-wrap justify-content-between" >
2023-05-24 17:25:27 +02:00
< h5 class = "text-primary d-inline" > < / h 5 >
2023-05-24 09:34:53 +02:00
< div >
< span name = "failed" title = "Failed executions" class = "badge badge-danger float-right" > Z < / s p a n >
< span name = "successful" title = "Successful executions" class = "badge badge-success float-right mr-1" > Y < / s p a n >
< span name = "running" title = "Running executions" class = "badge badge-primary float-right mr-1" > Y < / s p a n >
< span name = "accepted" title = "Accepted executions" class = "badge badge-secondary float-right mr-1" > X < / s p a n >
< / d i v >
2023-01-26 15:03:47 +01:00
< / s u m m a r y >
2023-02-01 16:07:15 +01:00
< ul class = "ccp-execution-list list-group" style = "list-style:none" >
2023-02-01 18:33:24 +01:00
< li class = "ccp-execution-item list-group-item-secondary my-2 p-2" draggable = "true" >
2023-02-01 16:07:15 +01:00
< details >
< summary >
< span name = "version" class = "badge badge-primary" > < / s p a n >
< span name = "status" class = "ml-1 badge" > < / s p a n >
2023-02-07 17:25:07 +01:00
< div class = "d-flex float-right" style = "gap: 3px 5px; max-width: 40%; min-width:60px; flex-wrap:wrap;" >
2023-04-21 15:05:56 +02:00
$ { this . # archive ? `
2024-06-04 16:53:01 +02:00
< button data - index = "0" name = "archive" title = "Archive whole execution to workspace folder" class = "btn btn-primary ccp-toolbar-button ccp-toolbar-button-small" >
2023-04-21 15:05:56 +02:00
< svg viewBox = "0 96 960 960" > < path d = "M140 796h680V516H140v280Zm540.118-90Q701 706 715.5 691.382q14.5-14.617 14.5-35.5Q730 635 715.382 620.5q-14.617-14.5-35.5-14.5Q659 606 644.5 620.618q-14.5 14.617-14.5 35.5Q630 677 644.618 691.5q14.617 14.5 35.5 14.5ZM880 456h-85L695 356H265L165 456H80l142-142q8-8 19.278-13 11.278-5 23.722-5h430q12.444 0 23.722 5T738 314l142 142ZM140 856q-24.75 0-42.375-17.625T80 796V456h800v340q0 24.75-17.625 42.375T820 856H140Z" / > < / s v g >
< / b u t t o n > `
: ` `
}
2024-06-04 16:53:01 +02:00
<!-- button data - index = "0" name = "provo" title = "Export to Prov-o document" class = "btn btn-primary ccp-toolbar-button ccp-toolbar-button-small" >
2023-02-07 17:25:07 +01:00
< svg viewBox = "0 0 24 24" > < path d = "M0 0h24v24H0V0z" fill = "none" / > < path d = "M13 3H6v18h4v-6h3c3.31 0 6-2.69 6-6s-2.69-6-6-6zm.2 8H10V7h3.2c1.1 0 2 .9 2 2s-.9 2-2 2z" / > < / s v g >
2024-06-04 16:53:01 +02:00
< / b u t t o n - - >
2023-06-09 17:54:41 +02:00
<!-- button data - index = "0" name = "zip" title = "Download as zip archive" class = "btn btn-primary ccp-toolbar-button ccp-toolbar-button-small" >
< svg viewBox = "0 0 48 48" > < path d = "M7 40q-1.15 0-2.075-.925Q4 38.15 4 37V11q0-1.15.925-2.075Q5.85 8 7 8h14l3 3h17q1.15 0 2.075.925Q44 12.85 44 14v23q0 1.15-.925 2.075Q42.15 40 41 40Zm25-3h9V14h-9v4.6h4.6v4.6H32v4.6h4.6v4.6H32ZM7 37h20.4v-4.6H32v-4.6h-4.6v-4.6H32v-4.6h-4.6V14h-4.65l-3-3H7v26Zm0-23v-3 26-23Z" / > < / s v g >
< / b u t t o n - - >
2024-06-04 16:53:01 +02:00
< button data - index = "0" name = "archiveoutputs" title = "Archive only outputs to workspace folder" class = "btn btn-primary ccp-toolbar-button ccp-toolbar-button-small" >
2023-06-09 16:55:28 +02:00
< svg viewBox = "0 0 48 48" > < path d = "M7 40q-1.15 0-2.075-.925Q4 38.15 4 37V11q0-1.15.925-2.075Q5.85 8 7 8h14l3 3h17q1.15 0 2.075.925Q44 12.85 44 14v23q0 1.15-.925 2.075Q42.15 40 41 40Zm25-3h9V14h-9v4.6h4.6v4.6H32v4.6h4.6v4.6H32ZM7 37h20.4v-4.6H32v-4.6h-4.6v-4.6H32v-4.6h-4.6V14h-4.65l-3-3H7v26Zm0-23v-3 26-23Z" / > < / s v g >
2023-02-07 17:25:07 +01:00
< / b u t t o n >
2023-05-30 18:37:57 +02:00
< button data - index = "0" name = "reexecute1" title = "Re-submit this execution" class = "btn btn-info ccp-toolbar-button ccp-toolbar-button-small" >
Re - submit
2023-02-07 17:25:07 +01:00
< / b u t t o n >
< button data - index = "0" name = "delete" title = "Delete" class = "btn btn-danger ccp-toolbar-button ccp-toolbar-button-small" >
< svg viewBox = "0 0 24 24" >
< path d = "M19,4H15.5L14.5,3H9.5L8.5,4H5V6H19M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19Z" > < / p a t h >
< / s v g >
< / b u t t o n >
< / d i v >
2023-04-05 12:50:27 +02:00
< p name = "createstart" class = "font-weight-light font-italic" style = "margin-top:revert" >
< span name = "created" > < / s p a n > .
< span name = "started" class = "ml-1" > < / s p a n >
< / p >
2023-02-01 16:07:15 +01:00
< p name = "message" class = "font-weight-light font-italic" style = "margin-top:revert" >
< span name = "updated" > < / s p a n > :
< span name = "message" class = "ml-1" > < / s p a n >
< / p >
< / s u m m a r y >
2023-05-24 09:41:51 +02:00
< div class = "d-flex flex-wrap" style = "gap:3px; overflow:hidden" >
< span style = "text-overflow:ellipsis" class = "badge badge-light text-info border border-info" name = "infrastructure" alt = "Infrastructure" title = "Infrastructure" > < / s p a n >
2023-03-29 16:59:19 +02:00
< span class = "badge" name = "runtime" alt = "Runtime" title = "Runtime" > < / s p a n >
2023-03-29 16:19:35 +02:00
< / d i v >
2023-06-23 16:40:02 +02:00
< div name = "logterminalcontainer" style = "margin:5px 0 5px 0" >
2023-06-23 14:44:08 +02:00
< / d i v >
2023-02-01 16:07:15 +01:00
< ul >
< li > < / l i >
< / u l >
2024-02-02 18:44:01 +01:00
< div class = "d-flex flex-column align-items-end" style = "gap: 3px;" >
2024-02-02 19:06:01 +01:00
< div class = "d-flex align-items-center" style = "gap:5px" >
2024-02-02 19:07:30 +01:00
< label style = "text-wrap:nowrap" > Generate code for : < / l a b e l >
2024-02-02 18:33:42 +01:00
< select name = "language-selector" class = "form-control" >
< option value = "text/python" data - ext = "py" title = "Generate plain Python3" > Python 3 < / o p t i o n >
< option value = "text/plain+r" data - ext = "r" title = "Generate plain R" > R < / o p t i o n >
< option value = "application/vnd.jupyter+python" data - ext = "ipynb" title = "Generate Jupyter notebook with Python 3 cells" > Jupyter Python3 < / o p t i o n >
< option value = "text/julia" data - ext = "jl" title = "Generate Julia 1.9.0 code (HTTP 1.10.0, JSON3 1.13.2)" > Julia 1.9 . 0 < / o p t i o n >
2024-04-10 12:21:14 +02:00
< option value = "application/x-sh+curl" data - ext = "sh" title = "Generate Bash script (curl)" > Bash ( curl ) < / o p t i o n >
< option value = "application/x-sh+wget" data - ext = "sh" title = "Generate Bash script (wget)" > Bash ( wget ) < / o p t i o n >
2024-02-02 18:33:42 +01:00
< / s e l e c t >
< button data - index = "0" name = "codegen" title = "Generate code" class = "btn btn-primary ccp-toolbar-button ccp-toolbar-button-small" >
< svg viewBox = "0 96 960 960" >
< path d = "m384 721 43-43-101-102 101-101-43-43-144 144.5L384 721Zm192 0 145-145-144-144-43 43 101 101-102 102 43 43ZM180 936q-24.75 0-42.375-17.625T120 876V276q0-24.75 17.625-42.375T180 216h205q5-35 32-57.5t63-22.5q36 0 63 22.5t32 57.5h205q24.75 0 42.375 17.625T840 276v600q0 24.75-17.625 42.375T780 936H180Zm0-60h600V276H180v600Zm300-617q14 0 24.5-10.5T515 224q0-14-10.5-24.5T480 189q-14 0-24.5 10.5T445 224q0 14 10.5 24.5T480 259ZM180 876V276v600Z" / >
< / s v g >
< / b u t t o n >
< / d i v >
< div class = "d-flex align-items-middle" style = "gap:5px" >
< div class = "d-flex" >
2024-04-09 13:16:51 +02:00
< a class = "text-truncate" name = "direct_link_execution" href = "${window.location.href}" > Direct link < / a >
2024-02-02 18:33:42 +01:00
< / d i v >
< / d i v >
2023-04-12 15:50:18 +02:00
< / d i v >
2023-02-01 16:07:15 +01:00
< / d e t a i l s >
< / l i >
2023-01-26 15:03:47 +01:00
< / u l >
< / d e t a i l s >
< / l i >
2023-02-01 16:07:15 +01:00
< / u l >
2023-01-26 15:03:47 +01:00
< / t e m p l a t e >
< link rel = "stylesheet" href = "https://cdn.jsdelivr.net/npm/bootstrap@4.0.0/dist/css/bootstrap.min.css" integrity = "sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin = "anonymous" >
2023-05-23 20:03:31 +02:00
< div class = "card" >
2023-02-01 16:07:15 +01:00
< div class = "card-header" >
2023-05-23 20:01:50 +02:00
< div class = "ccp-toolbar-header d-flex flex-wrap justify-content-between" >
2023-02-01 16:07:15 +01:00
< div >
2023-05-23 20:01:50 +02:00
< span name = "header" > Execution Monitor < / s p a n >
2023-02-01 16:07:15 +01:00
< / d i v >
2023-05-23 20:01:50 +02:00
< div class = "d-flex flex-wrap" style = "gap:2px" >
2023-02-01 16:07:15 +01:00
< button name = "refresh" class = "btn btn-primary ccp-toolbar-button" title = "Refresh" >
< svg viewBox = "0 0 48 48" > < path d = "M24 40q-6.65 0-11.325-4.675Q8 30.65 8 24q0-6.65 4.675-11.325Q17.35 8 24 8q4.25 0 7.45 1.725T37 14.45V8h3v12.7H27.3v-3h8.4q-1.9-3-4.85-4.85Q27.9 11 24 11q-5.45 0-9.225 3.775Q11 18.55 11 24q0 5.45 3.775 9.225Q18.55 37 24 37q4.15 0 7.6-2.375 3.45-2.375 4.8-6.275h3.1q-1.45 5.25-5.75 8.45Q29.45 40 24 40Z" / > < / s v g >
2023-04-21 15:05:56 +02:00
< / b u t t o n >
< label name = "fileupload" class = "btn btn-primary ccp-toolbar-button m-0" title = "Upload from file" >
< svg viewBox = "0 96 960 960" > < path d = "M452 854h60V653l82 82 42-42-156-152-154 154 42 42 84-84v201ZM220 976q-24 0-42-18t-18-42V236q0-24 18-42t42-18h361l219 219v521q0 24-18 42t-42 18H220Zm331-554V236H220v680h520V422H551ZM220 236v186-186 680-680Z" / > < / s v g >
< input type = "file" class = "d-none" multiple = "multiple" / >
< / l a b e l >
2023-05-23 20:01:50 +02:00
< div class = "d-flex" style = "gap:2px" >
2023-04-21 15:05:56 +02:00
< input type = "text" class = "form-control" placeholder = "Paste link here" / >
< button name = "archive" class = "btn btn-primary ccp-toolbar-button m-0" title = "Upload from link" >
2024-02-02 18:44:01 +01:00
< svg viewBox = "0 96 960 960" >
< path d = "M450 776H280q-83 0-141.5-58.5T80 576q0-83 58.5-141.5T280 376h170v60H280q-58.333 0-99.167 40.765-40.833 40.764-40.833 99Q140 634 180.833 675q40.834 41 99.167 41h170v60ZM324 606v-60h310v60H324Zm556-30h-60q0-58-40.833-99-40.834-41-99.167-41H510v-60h170q83 0 141.5 58.5T880 576ZM699 896V776H579v-60h120V596h60v120h120v60H759v120h-60Z" / >
< / s v g >
2023-04-21 15:05:56 +02:00
< / b u t t o n >
< / d i v >
2023-02-01 16:07:15 +01:00
< / d i v >
< / d i v >
< / d i v >
< div class = "card-body" >
< div class = "mb-3" >
2023-04-21 15:05:56 +02:00
< input accept = "application/zip" type = "text" name = "search" class = "form-control" placeholder = "Search" / >
2023-02-01 16:07:15 +01:00
< / d i v >
< div >
2023-02-07 17:25:07 +01:00
< ul name = "ccp_execution_list" > < / u l >
2023-02-01 16:07:15 +01:00
< / d i v >
< / d i v >
< / d i v >
2023-01-26 15:03:47 +01:00
`
2023-02-01 16:07:15 +01:00
this . # rootdoc . querySelector ( "button[name=refresh]" ) . addEventListener ( "click" , ev => {
this . refreshExecutions ( )
} )
this . # searchfield = this . # rootdoc . querySelector ( "input[name=search]" )
this . # searchfield . addEventListener ( "input" , ev => {
this . updateList ( )
} )
2023-04-21 15:05:56 +02:00
this . # fileupload = this . # rootdoc . querySelector ( "label[name=fileupload] > input[type=file]" )
this . # fileupload . addEventListener ( "change" , ev => {
const filelist = ev . target . files ;
if ( filelist . length > 0 ) {
const files = Array . prototype . slice . call ( filelist )
this . importExecutions ( files )
}
} )
this . # archiveupload = this . # rootdoc . querySelector ( "button[name=archive]" )
this . # archiveupload . addEventListener ( "click" , ev => {
const link = ev . target . parentElement . querySelector ( "input" ) . value
if ( link ) {
2023-05-23 19:55:04 +02:00
if ( confirm ( "Please confirm importing of execution from link?" ) ) {
2024-06-04 16:56:01 +02:00
this . fromArchiveFolder ( link )
2023-04-21 15:41:21 +02:00
}
2023-04-21 15:05:56 +02:00
}
} )
2023-02-01 16:07:15 +01:00
}
updateList ( ) {
const filter = this . # searchfield . value
if ( filter === "" || filter == null || filter == undefined ) {
this . # filtered = this . # data
} else {
const f = filter . toLowerCase ( )
this . # filtered = this . # data . filter ( d => {
return false ||
( d . status . toLowerCase ( ) . indexOf ( f ) !== - 1 ) ||
( d . method . indexOf ( f ) !== - 1 )
} )
}
2023-03-29 17:20:31 +02:00
this . # filtered . sort ( ( a , b ) => ( new Date ( b . updated ) ) - ( new Date ( a . updated ) ) )
2023-02-01 16:07:15 +01:00
this . groupBy ( )
BSS . apply ( this . # execution _list _bss , this . # rootdoc )
}
groupBy ( ) {
this . # filtered = this . # filtered . reduce ( ( catalog , exec ) => {
const category = exec . method
catalog [ category ] = catalog [ category ] ? ? [ ]
catalog [ category ] . push ( exec )
return catalog
} , { } )
2023-01-26 15:03:47 +01:00
}
refreshExecution ( id ) {
this . # boot . secureFetch ( ` ${ this . # serviceurl } /executions?id= ${ id } ` ) . then ( reply => {
2023-05-18 10:09:47 +02:00
if ( reply . ok ) return reply . json ( ) ;
else throw ` Unable to load execution ${ id } . Check console. `
2023-01-26 15:03:47 +01:00
} ) . then ( data => {
2023-02-02 20:03:06 +01:00
//this may occur for timing issues since workflow start is async
const exec = data . length === 0 ? { id : id } : data [ 0 ]
2023-02-01 16:07:15 +01:00
for ( var i = 0 ; i < this . # data . length ; i ++ ) {
if ( this . # data [ i ] . id == exec . id ) {
this . # data [ i ] = exec
2023-01-26 15:03:47 +01:00
break
}
2023-02-02 20:03:06 +01:00
}
if ( i === this . # data . length ) {
this . # data = [ exec ] . concat ( this . # data )
2023-01-26 15:03:47 +01:00
}
2023-02-01 16:07:15 +01:00
this . updateList ( )
2023-01-26 15:03:47 +01:00
} ) . catch ( err => { console . error ( err ) } )
}
deleteExecution ( id ) {
this . # boot . secureFetch ( ` ${ this . # serviceurl } /executions/ ${ id } ` , { method : "DELETE" } ) . then ( reply => {
2023-05-18 10:09:47 +02:00
if ( reply . ok ) this . refreshExecutions ( ) ;
else throw ` Unable to delete execution ${ id } . Check console. `
2023-01-26 15:03:47 +01:00
} ) . catch ( err => { console . error ( err ) } )
}
refreshExecutions ( ) {
this . # boot . secureFetch ( ` ${ this . # serviceurl } /executions ` ) . then ( reply => {
2023-05-18 10:09:47 +02:00
if ( reply . ok ) return reply . json ( ) ;
else throw "Unable to load executions. Check console."
2023-01-26 15:03:47 +01:00
} ) . then ( data => {
2023-02-01 16:07:15 +01:00
this . # data = data
this . updateList ( )
2023-01-26 15:03:47 +01:00
} ) . catch ( err => { console . error ( err ) } )
}
connectNewExecution ( ) {
2023-05-05 18:14:08 +02:00
document . addEventListener ( "newexecution" , ev => {
2023-05-18 12:22:21 +02:00
this . # pending . push ( ev . detail )
2023-01-26 15:03:47 +01:00
} )
}
2023-06-26 20:11:40 +02:00
connectBroadcastWithSubject ( ) {
var interval = window . setInterval ( ( ) => {
if ( this . # boot . subject ) {
window . clearInterval ( interval )
this . connectBroadcast ( )
}
} , 1000 )
}
2023-01-26 15:03:47 +01:00
connectBroadcast ( ) {
2023-06-26 19:39:52 +02:00
this . # socket = new WebSocket ( ` ${ this . # broadcasturl } /unified?subject= ${ this . # boot . subject } ` ) ;
2023-01-26 15:03:47 +01:00
this . # socket . onmessage = event => {
const data = JSON . parse ( event . data )
2023-06-23 15:03:04 +02:00
2023-06-23 15:35:28 +02:00
if ( data [ 0 ] && data [ 0 ] . source ) {
2023-06-23 15:03:04 +02:00
//has to be logs
this . appendLogs ( data )
2023-06-23 11:55:34 +02:00
return
}
2023-06-23 15:03:04 +02:00
2023-02-01 16:07:15 +01:00
let exec = this . # data . filter ( e => e . id === data . jobID ) [ 0 ]
2023-01-26 15:03:47 +01:00
if ( exec ) {
this . refreshExecution ( exec . id )
2023-05-18 12:16:34 +02:00
} else {
2023-05-18 12:27:23 +02:00
this . # pending = this . # pending . filter ( pe => {
2023-05-18 12:23:49 +02:00
if ( pe === data . jobID ) {
2023-05-18 12:16:34 +02:00
this . refreshExecution ( pe )
return false
} else {
return true
}
} )
2023-01-26 15:03:47 +01:00
}
2023-03-13 17:36:15 +01:00
}
this . # interval = window . setInterval ( ( ) => {
if ( this . # socket . readyState === 3 ) {
this . # socket . close ( )
window . clearInterval ( this . # interval )
this . connectBroadcast ( )
} else {
this . # socket . send ( "ping" )
}
} , 30000 )
2023-01-26 15:03:47 +01:00
}
2023-06-23 15:03:04 +02:00
appendLogs ( data ) {
if ( ! data . length ) return ;
const exid = data [ 0 ] [ "attrs" ] [ "execution" ]
2023-06-23 16:40:02 +02:00
const lt = this . # rootdoc . querySelector ( ` d4s-ccp-logterminal[index=' ${ exid } '] ` )
2023-06-23 15:03:04 +02:00
if ( ! lt ) {
console . error ( "No terminal found for adding logs of " + exid )
} else {
lt . addLines ( data )
}
}
2023-01-26 15:03:47 +01:00
download ( url , name ) {
this . # boot . secureFetch ( url ) . then ( reply => {
2023-05-18 10:09:47 +02:00
if ( ! reply . ok ) {
throw "Unable to download. Check console."
2023-01-26 15:03:47 +01:00
}
return reply . blob ( )
} ) . then ( blob => {
const objectURL = URL . createObjectURL ( blob )
var tmplnk = document . createElement ( "a" )
tmplnk . download = name
tmplnk . href = objectURL
document . body . appendChild ( tmplnk )
tmplnk . click ( )
document . body . removeChild ( tmplnk )
} ) . catch ( err => console . error ( err ) )
}
2023-02-01 16:07:15 +01:00
export ( id , mime , filename ) {
2023-01-31 13:13:43 +01:00
this . # boot . secureFetch ( ` ${ this . # serviceurl } /executions/ ${ id } ` ,
2023-02-01 16:07:15 +01:00
{ method : "GET" , headers : { "Accept" : mime } } ) . then ( reply => {
2023-05-18 10:09:47 +02:00
if ( ! reply . ok ) {
2023-02-01 16:07:15 +01:00
throw "Unable to export " + mime
2023-01-31 13:13:43 +01:00
}
return reply . blob ( )
} ) . then ( blob => {
const objectURL = URL . createObjectURL ( blob )
var tmplnk = document . createElement ( "a" )
2023-02-01 16:07:15 +01:00
tmplnk . download = filename
2023-01-31 13:13:43 +01:00
tmplnk . href = objectURL
document . body . appendChild ( tmplnk )
tmplnk . click ( )
document . body . removeChild ( tmplnk )
2023-02-07 17:25:07 +01:00
} ) . catch ( err => { alert ( err ) } )
2023-01-31 13:13:43 +01:00
}
2023-02-02 12:42:03 +01:00
reexecute ( id , level ) {
this . # boot . secureFetch ( ` ${ this . # serviceurl } /executions/ ${ id } /level/ ${ level } ` , { method : "POST" } )
. then ( reply => {
2023-05-18 10:09:47 +02:00
if ( ! reply . ok ) {
throw "Unable to re-execute. Check console."
2023-02-02 12:42:03 +01:00
}
2023-02-02 20:03:06 +01:00
return reply . json ( )
} ) . then ( data => {
console . log ( data )
this . refreshExecution ( data . jobID )
2023-02-07 17:25:07 +01:00
} ) . catch ( err => { alert ( err ) } )
}
importExecutions ( files ) {
if ( files && files . length ) {
let formdata = new FormData ( ) ;
files . reduce ( ( formdata , f ) => {
formdata . append ( "files[]" , f )
return formdata
} , formdata )
this . # boot . secureFetch ( ` ${ this . # serviceurl } /executions ` , { body : formdata , method : "POST" } )
. then ( reply => {
2023-05-18 10:09:47 +02:00
if ( ! reply . ok ) {
2023-02-07 17:25:07 +01:00
throw "Unable to import"
} else return reply . text ( )
} ) . then ( data => {
this . refreshExecutions ( )
} ) . catch ( err => { alert ( err ) } )
}
2023-02-02 12:42:03 +01:00
}
2023-04-12 16:03:20 +02:00
generateCode ( id , mime , filename ) {
2023-04-12 15:50:18 +02:00
this . # boot . secureFetch ( ` ${ this . # serviceurl } /executions/ ${ id } /code ` ,
{ method : "GET" , headers : { "Accept" : mime } } ) . then ( reply => {
2023-05-18 10:09:47 +02:00
if ( ! reply . ok ) {
2023-04-12 15:50:18 +02:00
throw "Unable to generate code for " + mime
}
return reply . blob ( )
} ) . then ( blob => {
const objectURL = URL . createObjectURL ( blob )
var tmplnk = document . createElement ( "a" )
tmplnk . download = filename
tmplnk . href = objectURL
document . body . appendChild ( tmplnk )
tmplnk . click ( )
document . body . removeChild ( tmplnk )
} ) . catch ( err => { alert ( err ) } )
}
2024-06-04 16:56:01 +02:00
toArchiveFolder ( id ) {
2024-06-04 16:53:01 +02:00
this . # boot . secureFetch ( ` ${ this . # serviceurl } /executions/ ${ id } /archive-to-folder ` , { method : "POST" } )
2023-04-21 15:05:56 +02:00
. then ( reply => {
2023-05-18 10:09:47 +02:00
if ( ! reply . ok ) {
2024-06-04 16:53:01 +02:00
throw "Unable to archive execution to folder"
2023-04-21 15:05:56 +02:00
}
} ) . catch ( err => { alert ( err ) } )
}
2023-06-09 17:54:41 +02:00
2024-06-04 16:56:01 +02:00
toOutputsArchiveFolder ( id ) {
2024-06-04 16:53:01 +02:00
this . # boot . secureFetch ( ` ${ this . # serviceurl } /executions/ ${ id } /outputs/archive-to-folder ` , { method : "POST" } )
2023-06-09 17:54:41 +02:00
. then ( reply => {
if ( ! reply . ok ) {
2024-06-04 16:53:01 +02:00
throw "Unable to archive outputs to folder"
2023-06-09 17:54:41 +02:00
}
} ) . catch ( err => { alert ( err ) } )
}
2023-04-21 15:05:56 +02:00
2024-06-04 16:56:01 +02:00
fromArchiveFolder ( url ) {
2023-04-21 15:05:56 +02:00
if ( url ) {
this . # boot . secureFetch ( ` ${ this . # serviceurl } /executions/archive?url= ${ url } ` )
. then ( reply => {
2023-05-18 10:09:47 +02:00
if ( ! reply . ok ) {
2024-06-04 16:53:01 +02:00
throw "Unable to fetch from archive folder"
2023-04-21 15:05:56 +02:00
}
return reply . text ( )
} ) . then ( data => {
this . refreshExecutions ( )
} ) . catch ( err => { alert ( err ) } )
}
}
2023-02-01 16:07:15 +01:00
# execution _list _bss = {
2023-01-26 15:03:47 +01:00
template : "#EXECUTIOM_LIST_TEMPLATE" ,
2023-02-01 16:07:15 +01:00
target : "ul[name=ccp_execution_list]" ,
in : ( ) => this ,
2023-02-07 17:25:07 +01:00
on _dragover : ( ev ) => {
ev . preventDefault ( )
} ,
on _dragenter : ( ev ) => {
ev . target . classList . toggle ( "border-info" )
} ,
on _dragleave : ( ev ) => {
ev . target . classList . toggle ( "border-info" )
} ,
on _drop : ( ev ) => {
if ( ev . dataTransfer && ev . dataTransfer . files && ev . dataTransfer . files . length ) {
const files = Array . prototype . slice . call ( ev . dataTransfer . files )
const zips = files . filter ( f => f . type === "application/zip" )
2023-05-23 19:55:04 +02:00
if ( confirm ( "Please confirm import of execution files?" ) ) {
2023-02-07 17:25:07 +01:00
this . importExecutions ( files )
}
}
ev . target . classList . toggle ( "border-info" )
} ,
2023-01-26 15:03:47 +01:00
recurse : [
{
2023-02-01 16:07:15 +01:00
target : "li.ccp-method-item" ,
"in" : ( e , d ) => Object . keys ( this . # filtered ) ,
recurse : [
2023-01-26 15:03:47 +01:00
{
2023-02-01 16:07:15 +01:00
target : "details[name=level1]" ,
2023-01-26 15:03:47 +01:00
apply : ( e , d ) => {
2023-02-01 16:07:15 +01:00
e . alt = e . title = d
if ( sessionStorage . getItem ( d ) === "open" ) e . open = "open" ;
2023-01-26 15:03:47 +01:00
else e . removeAttribute ( "open" ) ;
} ,
on _toggle : ev => {
if ( ev . target . open ) {
sessionStorage . setItem ( ev . currentTarget . alt , 'open' )
} else {
sessionStorage . removeItem ( ev . currentTarget . alt )
}
2023-02-01 16:07:15 +01:00
} ,
2023-01-26 15:03:47 +01:00
} ,
{
2023-02-01 16:07:15 +01:00
target : "summary.ccp-method-item-header h5" ,
apply : ( e , d ) => { e . textContent = d }
2023-01-26 15:03:47 +01:00
} ,
{
2023-02-01 16:07:15 +01:00
target : "summary.ccp-method-item-header span[name=accepted]" ,
apply : ( e , d ) => { e . textContent = this . # filtered [ d ] . filter ( x => x . status === 'accepted' ) . length }
2023-01-26 15:03:47 +01:00
} ,
{
2023-02-01 16:07:15 +01:00
target : "summary.ccp-method-item-header span[name=failed]" ,
apply : ( e , d ) => { e . textContent = this . # filtered [ d ] . filter ( x => x . status === 'failed' ) . length }
2023-01-26 15:03:47 +01:00
} ,
{
2023-02-01 16:07:15 +01:00
target : "summary.ccp-method-item-header span[name=successful]" ,
apply : ( e , d ) => { e . textContent = this . # filtered [ d ] . filter ( x => x . status === 'successful' ) . length }
2023-01-26 15:03:47 +01:00
} ,
{
2023-02-01 16:07:15 +01:00
target : "summary.ccp-method-item-header span[name=running]" ,
apply : ( e , d ) => { e . textContent = this . # filtered [ d ] . filter ( x => x . status === 'running' ) . length }
2023-01-26 15:03:47 +01:00
} ,
{
2023-02-01 16:07:15 +01:00
target : "li.ccp-execution-item" ,
"in" : ( e , d ) => this . # filtered [ d ] ,
apply : ( e , d ) => e . setAttribute ( "data-index" , d . id ) ,
2023-02-01 18:33:24 +01:00
on _dragstart : ev => {
ev . dataTransfer . effectAllowed = 'move'
ev . dataTransfer . setData ( 'text/html' , ev . currentTarget . innerHTML )
ev . dataTransfer . setData ( 'text/plain+ccpexecution' , ev . currentTarget . getAttribute ( "data-index" ) )
ev . dataTransfer . setData ( 'application/json+ccpexecution' , JSON . stringify ( ev . currentTarget . bss _input . data ) )
} ,
on _dragend : ev => {
ev . preventDefault ( )
} ,
2023-02-01 16:07:15 +01:00
on _click : ev => {
if ( ev . target . getAttribute ( "name" ) === "delete" ) {
2023-05-23 19:55:04 +02:00
if ( window . confirm ( "Please confirm deletion of this execution?" ) ) {
2023-02-01 16:07:15 +01:00
const id = ev . currentTarget . getAttribute ( "data-index" )
this . deleteExecution ( id )
}
}
if ( ev . target . getAttribute ( "name" ) === "zip" ) {
const id = ev . currentTarget . getAttribute ( "data-index" )
this . export ( id , "application/zip" , id + ".zip" )
}
if ( ev . target . getAttribute ( "name" ) === "provo" ) {
const id = ev . currentTarget . getAttribute ( "data-index" )
this . export ( id , "application/prov-o+xml" , id + ".xml" )
}
2023-02-02 12:42:03 +01:00
if ( ev . target . getAttribute ( "name" ) === "reexecute1" ) {
2023-05-24 10:18:16 +02:00
if ( window . confirm ( "Please confirm re-execution?" ) ) {
const id = ev . currentTarget . getAttribute ( "data-index" )
this . reexecute ( id , 1 )
}
2023-02-02 12:42:03 +01:00
}
2023-04-21 15:05:56 +02:00
if ( ev . target . getAttribute ( "name" ) === "archive" ) {
2024-06-04 16:53:01 +02:00
if ( confirm ( " Please confirm archiving of execution to workspace folder?" ) ) {
2023-04-21 15:41:21 +02:00
const id = ev . currentTarget . getAttribute ( "data-index" )
2024-06-04 16:53:01 +02:00
this . toArchiveFolder ( id )
2023-04-21 15:41:21 +02:00
}
2023-02-02 12:42:03 +01:00
}
2024-06-04 16:53:01 +02:00
if ( ev . target . getAttribute ( "name" ) === "archiveoutputs" ) {
if ( confirm ( " Please confirm archiving of execution outputs to workspace folder?" ) ) {
2023-06-09 17:54:41 +02:00
const id = ev . currentTarget . getAttribute ( "data-index" )
2024-06-04 16:53:01 +02:00
this . toOutputArchiveFolder ( id )
2023-06-09 17:54:41 +02:00
}
}
2023-02-01 16:07:15 +01:00
} ,
2023-01-26 15:03:47 +01:00
recurse : [
{
2023-02-01 16:07:15 +01:00
target : "details" ,
apply : ( e , d ) => {
e . alt = e . title = d . id
if ( sessionStorage . getItem ( d . id ) === "open" ) e . open = "open" ;
else e . removeAttribute ( "open" ) ;
2023-01-26 15:03:47 +01:00
} ,
2023-02-01 16:07:15 +01:00
on _toggle : ev => {
if ( ev . target . open ) {
sessionStorage . setItem ( ev . currentTarget . alt , 'open' )
} else {
sessionStorage . removeItem ( ev . currentTarget . alt )
}
}
} ,
{
target : "span[name=version]" ,
2023-01-26 15:03:47 +01:00
apply : ( e , d ) => {
2023-05-24 17:25:27 +02:00
if ( d . ccpnote ) {
e . textContent = ` ${ d . ccpnote } ( ${ d . methodversion } ) `
} else {
e . textContent = ` ${ d . methodversion } `
}
2023-01-26 15:03:47 +01:00
}
2023-02-01 16:07:15 +01:00
} ,
{
target : "span[name=status]" ,
apply : ( e , d ) => {
if ( d . status ) {
const status = d . status
e . textContent = status
if ( status === "running" ) e . classList . add ( "badge-primary" ) ;
else if ( status === "successful" ) e . classList . add ( "badge-success" ) ;
2023-02-06 13:26:01 +01:00
else if ( status === "failed" ) e . classList . add ( "badge-danger" ) ;
2023-02-01 16:07:15 +01:00
else e . classList . add ( "badge-secondary" ) ;
}
}
} ,
2023-04-05 12:50:27 +02:00
{
target : "span[name=created]" ,
apply : ( e , d ) => {
if ( d . created ) {
const dt = new Date ( d . created )
e . textContent = ` Accepted ${ dt . toLocaleDateString ( ) } @ ${ dt . toLocaleTimeString ( ) } `
}
}
} ,
{
target : "span[name=started]" ,
apply : ( e , d ) => {
if ( d . started ) {
const dt = new Date ( d . started )
e . textContent = ` Started ${ dt . toLocaleDateString ( ) } @ ${ dt . toLocaleTimeString ( ) } `
}
}
} ,
2023-02-01 16:07:15 +01:00
{
target : "span[name=updated]" ,
apply : ( e , d ) => {
const dt = new Date ( d . updated )
e . textContent = ` Last update ${ dt . toLocaleDateString ( ) } @ ${ dt . toLocaleTimeString ( ) } `
}
} ,
{
target : "span[name=message]" ,
apply : ( e , d ) => {
if ( d . message ) {
e . textContent = d . message
}
}
} ,
2023-03-29 16:19:35 +02:00
{
target : "span[name=infrastructure]" ,
apply : ( e , d ) => {
e . textContent = d . infrastructure
2023-03-29 16:59:19 +02:00
}
} ,
{
target : "span[name=runtime]" ,
apply : ( e , d ) => {
2023-05-05 18:29:40 +02:00
const rt = d . runtime ? d . runtime : ""
2023-05-05 18:34:24 +02:00
const infratype = d . infrastructuretype ? d . infrastructuretype : ""
e . textContent = rt + ( d . replicas && d . replicas !== "1" ? ' x ' + d . replicas : '' )
const t = infratype . match ( /docker/i ) ? "docker" : null
const t2 = ! t && infratype . match ( /lxd/i ) ? "lxd" : t
2023-03-29 16:59:19 +02:00
e . classList . add ( t2 )
2023-03-29 16:20:30 +02:00
}
2023-03-29 16:19:35 +02:00
} ,
2023-04-12 15:50:18 +02:00
{
target : "button[name=codegen]" ,
apply : ( e , d ) => e . setAttribute ( "data-index" , d . id ) ,
on _click : ( ev ) => {
const id = ev . target . getAttribute ( "data-index" )
2023-04-12 16:03:20 +02:00
const langsel = ev . target . parentElement . querySelector ( "select[name=language-selector]" )
const lang = langsel . value
2023-04-12 16:05:40 +02:00
const ext = langsel . selectedOptions [ 0 ] . getAttribute ( "data-ext" )
2023-04-12 16:03:20 +02:00
this . generateCode ( id , lang , ` ${ id } . ${ ext } ` )
2023-04-12 15:50:18 +02:00
}
} ,
2024-02-02 18:33:42 +01:00
{
target : "a[name=direct_link_execution]" ,
2024-04-09 13:22:14 +02:00
apply : ( e , d ) => e . href = window . location . origin + window . location . pathname + "?execution=" + d . id
2024-02-02 18:33:42 +01:00
} ,
2023-06-23 15:35:28 +02:00
{
2023-06-23 16:40:02 +02:00
target : "div[name=logterminalcontainer]" ,
2023-06-23 15:35:28 +02:00
apply : ( e , d ) => {
2023-06-23 16:40:02 +02:00
e . innerHTML = ` <d4s-ccp-logterminal index=" ${ d . id } " maxstoredlines="100" maxlines="10"></d4s-ccp-logterminal> `
2023-06-23 15:35:28 +02:00
}
} ,
2023-02-01 16:07:15 +01:00
{
target : "ul" ,
recurse : [
{
target : "li" ,
"in" : ( e , d ) => {
return d . resources . map ( l => {
return { href : this . # serviceurl + "/executions/" + d . id + "/" + l . path , path : l . path }
} )
} ,
on _click : ev => {
const href = ev . currentTarget . bss _input . data . href
const name = ev . currentTarget . bss _input . data . path
this . download ( href , name )
} ,
apply : ( e , d ) => {
e . innerHTML = ` <a href=" ${ d . href } " onclick="event.preventDefault()"> ${ d . path } </a> `
}
}
]
2023-01-26 15:03:47 +01:00
}
]
}
]
}
]
}
}
2023-06-23 16:40:02 +02:00
window . customElements . define ( 'd4s-ccp-executionhistory' , CCPExecutionHistory ) ;