class CCPExecutionHistory extends HTMLElement { #boot = null; #rootdoc = null; #serviceurl = null; #wsurl = null; #broadcasturl = null; #archive = null; #data = []; #pending = []; #filtered = []; #socket = null; #interval = null; #searchfield = null; // #fileupload = null; // #archiveupload = null; #ws = null; #execfolderid = null #archived = [] #messages = { "en" : { "search" : "Search", "refresh_help" : "Refresh contents of Execution Monitor", "paste_link" : "Paste your link here", "execution_monitor" : "Execution Monitor", "failed_count_help" : "Count of failed executions", "successful_count_help" : "Count of successful executions", "running_count_help" : "Count of running executions", "accepted_count_help" : "Count of accepted executions", "download_help" : "Click to download outputs to your local disk", "archive_execution_help" : "Archive whole execution to workspace folder", "archive_outputs_help" : "Archive only outputs to workspace folder", "re-submit_help" : "Re submit this exact execution", "re-submit" : "Re-submit", "delete_help" : "Delete this execution", "generate_code" : "Generate code for", "generate_code_help" : "Generate code that replicates this exact execution on a chosen programming language or runtime", "direct_link" : "Direct link", "import_file_help" : "Import from a previosusly exported file on your local disk", "import_link_help" : "Import execution from a link in your workspace", "direct_link_help" : "Navigate to the direct link for opening this exact execution in the exeuction form", "confirm_import_link" : "Please confirm importing of execution from link", "confirm_import_file" : "Please confirm importing of execution from file", "confirm_delete_execution" : "Please confirm deletion of this execution", "confirm_delete_archived_execution" : "Please confirm deletion of this archived execution from your workspace.", "confirm_archive_execution" : "Confirm archiving of this execution to workspace.", "confirm_archive_outputs" : "Confirm archiving of outputs to workspace.", "confirm_reexecution" : "Confirm resubmission of this execution", "live_executions" : "Live executions", "archived_executions" : "Archived executions", "restore_execution_help" : "Restore this archived execution back to CCP", "infrastructure_help" : "The infrastructure where this execution has been scheduled on", "runtime_help" : "The runtime where this execution has been scheduled on", "loading_archived_help" : "Loading archived executions from workspace. This can take a while.", "err_reexecute" : "Unable to re-submit the execution. Please check console for further info", "err_generate_code_for" : "Unable to generate code for " } } getLabel(key, localehint){ const locale = localehint ? localehint : navigator.language const actlocale = this.#messages[locale] ? locale : "en" const msg = this.#messages[actlocale][key] return msg == null || msg == undefined ? key : this.#messages[actlocale][key] } constructor(){ super() this.#boot = document.querySelector("d4s-boot-2") this.#rootdoc = this.attachShadow({ "mode" : "open"}) } connectedCallback(){ 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" this.#archive = this.getAttribute("allow-archive") && this.getAttribute("allow-archive").toLowerCase() === "true" const iss = document.querySelector("d4s-boot-2").loginToken.iss; const addresses = { "https://accounts.dev.d4science.org/auth/realms/d4science": "https://workspace-repository.dev.d4science.org/storagehub/workspace", "https://accounts.pre.d4science.org/auth/realms/d4science": "https://pre.d4science.org/workspace", "https://accounts.d4science.org/auth/realms/d4science": "https://api.d4science.org/workspace" }; this.#wsurl = addresses[iss] this.connectNewExecution() this.connectBroadcastWithSubject() this.render() this.refreshExecutions() if(this.#archive) this.refreshArchivedExecutions(); } render(){ this.#rootdoc.innerHTML = `
${this.getLabel("execution_monitor")}
${ this.#archive ? `
      ` : ` ` }
      ` this.#rootdoc.querySelector("button[name=refresh]").addEventListener("click", ev=>{ if(this.isShowingArchived()){ this.refreshArchivedExecutions() }else{ this.refreshExecutions() } }) this.#searchfield = this.#rootdoc.querySelector("input[name=search]") this.#searchfield.addEventListener("input", ev=>{ this.updateList() }) if(this.#archive){ const linklive = this.#rootdoc.querySelector("a.nav-link[name=live_executions]") const linkarch = this.#rootdoc.querySelector("a.nav-link[name=archived_executions]") const tablive = this.#rootdoc.querySelector("div.tab-pane#live_executions") const tabarch = this.#rootdoc.querySelector("div.tab-pane#archived_executions") this.#rootdoc.querySelector("#switch_executions").addEventListener("click", ev=>{ const tgt = ev.target if(tgt == linklive){ linklive.classList.add("active") linkarch.classList.remove("active") tablive.classList.add("show") tablive.classList.add("active") tabarch.classList.remove("show") tabarch.classList.remove("active") }else if(tgt == linkarch){ linklive.classList.remove("active") linkarch.classList.add("active") tabarch.classList.add("show") tabarch.classList.add("active") tablive.classList.remove("show") tablive.classList.remove("active") } ev.preventDefault(); ev.stopPropagation() }) } // 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){ // if(confirm(this.getLabel("confirm_import") + "?")){ // this.fromArchiveFolder(link) // } // } // }) } isShowingArchived(){ return this.#archive && this.#rootdoc.querySelector("#archived_executions").classList.contains("show") } 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) }) } this.#filtered.sort((a,b)=>(new Date(b.updated)) - (new Date(a.updated))) 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 }, {}) } refreshExecution(id){ this.#boot.secureFetch(`${this.#serviceurl}/executions?id=${id}`).then(reply =>{ if(reply.ok) return reply.json(); else throw `Unable to load execution ${id}. Check console.` }).then(data=>{ //this may occur for timing issues since workflow start is async const exec = data.length === 0 ? { id : id} : data[0] for(var i=0; i < this.#data.length; i++){ if(this.#data[i].id == exec.id){ this.#data[i] = exec break } } if(i === this.#data.length){ this.#data = [exec].concat(this.#data) } this.updateList() }).catch(err=>{ console.error(err)}) } deleteExecution(id){ this.#boot.secureFetch(`${this.#serviceurl}/executions/${id}`, { method: "DELETE"}).then(reply =>{ if(reply.ok) this.refreshExecutions(); else throw `Unable to delete execution ${id}. Check console.` }).catch(err=>{ console.error(err)}) } refreshExecutions(){ this.#boot.secureFetch(`${this.#serviceurl}/executions`).then(reply =>{ if(reply.ok) return reply.json(); else throw "Unable to load executions. Check console." }).then(data=>{ this.#data = data this.updateList() }).catch(err=>{ console.error(err)}) } connectNewExecution(){ document.addEventListener("newexecution", ev=>{ this.#pending.push(ev.detail) }) } connectBroadcastWithSubject(){ var interval = window.setInterval( ()=>{ if(this.#boot.subject){ window.clearInterval(interval) this.connectBroadcast() } }, 1000) } connectBroadcast(){ this.#socket = new WebSocket(`${this.#broadcasturl}/unified?subject=${this.#boot.subject}`); this.#socket.onmessage = event=>{ const data = JSON.parse(event.data) if(data[0] && data[0].source){ //has to be logs this.appendLogs(data) return } let exec = this.#data.filter(e=>e.id === data.jobID)[0] if(exec){ this.refreshExecution(exec.id) }else{ this.#pending = this.#pending.filter(pe=>{ if(pe === data.jobID){ this.refreshExecution(pe) return false }else{ return true } }) } } this.#interval = window.setInterval( ()=>{ if(this.#socket.readyState === 3){ this.#socket.close() window.clearInterval(this.#interval) this.connectBroadcast() }else{ this.#socket.send("ping") } }, 30000) } appendLogs(data){ if(!data.length) return; const exid = data[0]["attrs"]["execution"] const lt = this.#rootdoc.querySelector(`d4s-ccp-logterminal[index='${exid}']`) if(!lt){ console.error("No terminal found for adding logs of " + exid) }else{ lt.addLines(data) } } download(url, name) { this.#boot.secureFetch(url).then(reply => { if (!reply.ok) { throw "Unable to download. Check console." } 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)) } export(id, mime, filename){ this.#boot.secureFetch(`${this.#serviceurl}/executions/${id}`, { method: "GET", headers : { "Accept" : mime} }).then(reply =>{ if (!reply.ok) { throw "Unable to export " + 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)}) } reexecuteArchived(data, level){ this.#boot.secureFetch(`${this.#serviceurl}/executions/level/${level}`, { method: "POST", headers: {"Content-Type" : "application/json"}, body: JSON.stringify({ "request" : data.fullrequest, "method" : data.fullmethod, "infrastructure" : data.fullinfrastructure}) }) .then(reply =>{ if (!reply.ok) { throw this.getLabel("err_reexecute") } return reply.json() }).then(data=>{ this.refreshExecution(data.jobID) }).catch(err=>{ alert(err)}) } reexecute(id,level){ this.#boot.secureFetch(`${this.#serviceurl}/executions/${id}/level/${level}`, { method: "POST" }) .then(reply =>{ if (!reply.ok) { throw this.getLabel("err_reexecute") } return reply.json() }).then(data=>{ console.log(data) this.refreshExecution(data.jobID) }).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=>{ if (!reply.ok) { throw "Unable to import" }else return reply.text() }).then(data=>{ this.refreshExecutions() }).catch(err=>{ alert(err) }) } } generateCode(id, mime, filename){ this.#boot.secureFetch(`${this.#serviceurl}/executions/${id}/code`, { method: "GET", headers : { "Accept" : mime} }).then(reply =>{ if (!reply.ok) { throw this.getLabel("err_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)}) } toArchiveFolder(id){ this.#boot.secureFetch(`${this.#serviceurl}/executions/${id}/archive-to-folder`, { method: "POST" }) .then(reply =>{ if (!reply.ok) { throw "Unable to archive execution to folder" } }).catch(err=>{ alert(err)}) } toOutputsArchiveFolder(id){ this.#boot.secureFetch(`${this.#serviceurl}/executions/${id}/outputs/archive-to-folder`, { method: "POST" }) .then(reply =>{ if (!reply.ok) { throw "Unable to archive outputs to folder" } }).catch(err=>{ alert(err)}) } fromArchiveFolder(url){ if(url){ this.#boot.secureFetch(`${this.#serviceurl}/executions/archive?url=${url}`) .then(reply =>{ if (!reply.ok) { throw "Unable to fetch from archive folder" } return reply.text() }).then(data=>{ this.refreshExecutions() }).catch(err=>{ alert(err)}) } } async deleteArchiveFolder(id){ if(id){ await this.initWorkspace() await this.#ws.deleteItem(id) if(this.#archive) await this.refreshArchivedExecutions(); } } async initWorkspace(){ if(!this.#ws){ this.#ws = new D4SWorkspace(this.#wsurl, this.#boot) } if(!this.#execfolderid){ const rootws = await this.#ws.getWorkspace() const ccpfolder = await this.#ws.findByName(rootws.id, "CCP") if(ccpfolder.length === 1){ const execfolder = await this.#ws.findByName(ccpfolder[0].id, "executions") if(execfolder.length === 1){ this.#execfolderid = execfolder[0].id } } } } showLoading(el){ el.innerHTML = `
      ` } async refreshArchivedExecutions(){ this.showLoading(this.#rootdoc.querySelector("ul[name=ccp_archived_execution_list]")) await this.initWorkspace() if(this.#execfolderid){ const folders = await this.#ws.listFolder(this.#execfolderid) this.#archived = {} for(let i=0; i < folders.length; i++){ const methodfolder = folders[i] const executionfolders = await this.#ws.listFolder(methodfolder.id) for(let j=0; j < executionfolders.length; j++){ const metadatafolder = await this.#ws.findByName(executionfolders[j].id, "metadata") if(metadatafolder.length === 1){ //parse all metada content that is useful to build up an entry const metadata = await this.#ws.download(metadatafolder[0].id, null, true) const zip = new JSZip() const req = JSON.parse(await (zip.loadAsync(metadata).then(z=>z.file("metadata/request.json").async("string")))) const status = JSON.parse(await (zip.loadAsync(metadata).then(z=>z.file("metadata/jobStatus.json").async("string")))) const meth = JSON.parse(await (zip.loadAsync(metadata).then(z=>z.file("metadata/method.json").async("string")))) const infra = JSON.parse(await (zip.loadAsync(metadata).then(z=>z.file("metadata/infrastructure.json").async("string")))) const parser = new DOMParser() // Fasted way to get context is from prov-o. If it doesn't match the context in boot skip this execution const provo = parser.parseFromString(await zip.loadAsync(metadata).then(z=>z.file("metadata/prov-o.xml").async("string")), "text/xml") const context = provo.querySelector("entity[*|id='d4s:VRE']>value").textContent if(context !== this.#boot.context && context.replaceAll("/", "%2F") !== this.#boot.context) continue; //build entry from downloaded data const entry = { id : req.id, status : status.status, created : status.created, started : status.started, updated : status.updated, message : status.message, method : meth.title, methodversion : meth.version, infrastructure: infra.name, infrastructuretype : infra.type, ccpnote : req.inputs.ccpnote ? req.inputs.ccpnote : "", runtime : req.inputs.ccpimage, replicas : req.inputs.ccpreplicas ? req.inputs.ccpreplicas : 1, href : `items/${executionfolders[j].id}/download`, wsid : executionfolders[j].id, fullrequest : req, fullmethod : meth, fullinfrastructure : infra } if(this.#archived[entry.method]) this.#archived[entry.method].push(entry); else this.#archived[entry.method] = [entry] } } } } BSS.apply(this.#archived_execution_list_bss, this.#rootdoc) } #execution_list_bss = { template : "#EXECUTIOM_LIST_TEMPLATE", target : "ul[name=ccp_execution_list]", in : ()=>this, 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") if(confirm(this.getLabel("confirm_import_file") + "?")){ this.importExecutions(files) } } ev.target.classList.toggle("border-info") }, recurse:[ { target : "li.ccp-method-item", "in" : (e,d)=>Object.keys(this.#filtered), recurse: [ { target : "details[name=level1]", apply : (e,d)=>{ e.alt = e.title = d if(sessionStorage.getItem(d) === "open") e.open = "open"; else e.removeAttribute("open"); }, on_toggle : ev=>{ if(ev.target.open){ sessionStorage.setItem(ev.currentTarget.alt, 'open') }else{ sessionStorage.removeItem(ev.currentTarget.alt) } }, }, { target : "summary.ccp-method-item-header h5", apply : (e,d) => { e.textContent = d } }, { target : "summary.ccp-method-item-header span[name=accepted]", apply : (e,d) => { e.textContent = this.#filtered[d].filter(x=>x.status === 'accepted').length } }, { target : "summary.ccp-method-item-header span[name=failed]", apply : (e,d) => { e.textContent = this.#filtered[d].filter(x=>x.status === 'failed').length } }, { target : "summary.ccp-method-item-header span[name=successful]", apply : (e,d) => { e.textContent = this.#filtered[d].filter(x=>x.status === 'successful').length } }, { target : "summary.ccp-method-item-header span[name=running]", apply : (e,d) => { e.textContent = this.#filtered[d].filter(x=>x.status === 'running').length } }, { target : "li.ccp-execution-item", "in" : (e,d)=>this.#filtered[d], apply : (e,d)=>e.setAttribute("data-index", d.id), 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() }, on_click: ev=>{ if(ev.target.getAttribute("name") === "delete"){ if(window.confirm(this.getLabel("confirm_delete_execution"))){ 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") } if(ev.target.getAttribute("name") === "reexecute1"){ if(window.confirm(this.getLabel("confirm_reexecution"))){ const id = ev.currentTarget.getAttribute("data-index") this.reexecute(id, 1) } } if(ev.target.getAttribute("name") === "archive"){ if(confirm(this.getLabel("confirm_archive_execution"))){ const id = ev.currentTarget.getAttribute("data-index") this.toArchiveFolder(id) } } if(ev.target.getAttribute("name") === "archiveoutputs"){ if(confirm(this.getLabel("confirm_archive_outputs"))){ const id = ev.currentTarget.getAttribute("data-index") this.toOutputsArchiveFolder(id) } } }, recurse : [ { target : "details", apply : (e,d)=>{ e.alt = e.title = d.id if(sessionStorage.getItem(d.id) === "open") e.open = "open"; else e.removeAttribute("open"); }, on_toggle : ev=>{ if(ev.target.open){ sessionStorage.setItem(ev.currentTarget.alt, 'open') }else{ sessionStorage.removeItem(ev.currentTarget.alt) } } }, { target : "span[name=version]", apply : (e,d)=>{ if(d.ccpnote){ e.textContent = `${d.ccpnote} (${d.methodversion})` }else{ e.textContent = `${d.methodversion}` } } }, { 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"); else if (status === "failed") e.classList.add("badge-danger"); else e.classList.add("badge-secondary"); } } }, { 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()}` } } }, { 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 } } }, { target : "span[name=infrastructure]", apply : (e,d)=>{ e.textContent = d.infrastructure } }, { target : "span[name=runtime]", apply : (e,d)=>{ const rt = d.runtime ? d.runtime : "" 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 const t3 = !t2 && infratype.match(/galaxy/i) ? "galaxy" : t e.classList.add(t3) } }, { target : "button[name=codegen]", apply : (e,d)=>e.setAttribute("data-index", d.id), on_click: (ev)=>{ const id = ev.target.getAttribute("data-index") const langsel = ev.target.parentElement.querySelector("select[name=language-selector]") const lang = langsel.value const ext = langsel.selectedOptions[0].getAttribute("data-ext") this.generateCode(id, lang, `${id}.${ext}`) } }, { target : "a[name=direct_link_execution]", apply : (e,d)=>e.href = window.location.origin + window.location.pathname + "?execution=" + d.id }, { target : "div[name=logterminalcontainer]", apply : (e,d)=>{ e.innerHTML = `` } }, { 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 = `${d.path}` } } ] } ] } ] } ] } #archived_execution_list_bss = { template : "#ARCHIVED_EXECUTION_LIST_TEMPLATE", target : "ul[name=ccp_archived_execution_list]", in : ()=>this, recurse:[ { target : "li.ccp-method-item", "in" : (e,d)=>Object.keys(this.#archived), recurse: [ { target : "details[name=level1]", apply : (e,d)=>{ e.alt = e.title = d if(sessionStorage.getItem("arch_" + d) === "open") e.open = "open"; else e.removeAttribute("open"); }, on_toggle : ev=>{ if(ev.target.open){ sessionStorage.setItem("arch_" + ev.currentTarget.alt, 'open') }else{ sessionStorage.removeItem("arch_" + ev.currentTarget.alt) } }, }, { target : "summary.ccp-method-item-header h5", apply : (e,d) => { e.textContent = d } }, { target : "li.ccp-execution-item", "in" : (e,d)=>this.#archived[d], apply : (e,d)=>{ e.setAttribute("data-index", d.id) e.setAttribute("data-href", d.href) e.setAttribute("data-wsid", d.wsid) }, 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() }, on_click: ev=>{ if(ev.target.getAttribute("name") === "restore"){ if(window.confirm(this.getLabel("confirm_restore_execution"))){ const href = ev.currentTarget.getAttribute("data-href") this.fromArchiveFolder(href) } }else if(ev.target.getAttribute("name") === "delete"){ if(window.confirm(this.getLabel("confirm_delete_archived_execution"))){ const wsid = ev.currentTarget.getAttribute("data-wsid") this.deleteArchiveFolder(wsid) } }else if(ev.target.getAttribute("name") === "reexecute2"){ if(window.confirm(this.getLabel("confirm_reexecution"))){ this.reexecuteArchived(ev.currentTarget.bss_input.data, 1) } } }, recurse : [ { target : "span[name=version]", apply : (e,d)=>{ if(d.ccpnote){ e.textContent = `${d.ccpnote} (${d.methodversion})` }else{ e.textContent = `${d.methodversion}` } } }, { 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"); else if (status === "failed") e.classList.add("badge-danger"); else e.classList.add("badge-secondary"); } } }, { 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()}` } } }, { 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 } } }, { target : "span[name=infrastructure]", apply : (e,d)=>{ e.textContent = d.infrastructure } }, { target : "span[name=runtime]", apply : (e,d)=>{ const rt = d.runtime ? d.runtime : "" 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 const t3 = !t2 && infratype.match(/galaxy/i) ? "galaxy" : t e.classList.add(t3) } } ] } ] } ] } } window.customElements.define('d4s-ccp-executionhistory', CCPExecutionHistory);