improved execution framework

This commit is contained in:
dcore94 2022-05-05 12:19:06 +02:00
parent e8151b580f
commit bb6427afaa
5 changed files with 282 additions and 38 deletions

View File

@ -4,17 +4,16 @@ class CCPExecutionForm extends HTMLElement{
#rootdoc; #rootdoc;
#data; #data;
#method; #method;
#infrastructurecontroller; #executionmonitor;
#serviceurl = "https://nubis1.int.d4science.net:8080" #serviceurl = "https://nubis1.int.d4science.net:8080"
//#cdnurl = "https://nubis1.int.d4science.net:8080/ccp/executionformfragment.html" #cdnurl = "https://nubis1.int.d4science.net:8080/ccp/executionformfragment.html"
#cdnurl = "http://d4science-cdn-public:8984/resources/ccp/executionformfragment.html" //#cdnurl = "http://d4science-cdn-public:8984/resources/ccp/executionformfragment.html"
constructor(){ constructor(){
super() super()
this.#boot = document.querySelector("d4s-boot-2") this.#boot = document.querySelector("d4s-boot-2")
this.#rootdoc = this.attachShadow({ "mode" : "open"}) this.#rootdoc = this.attachShadow({ "mode" : "open"})
this.#infrastructurecontroller = document.querySelector("d4s-ccp-infrastructurelist")
this.fetchMarkup() this.fetchMarkup()
} }
@ -52,6 +51,16 @@ class CCPExecutionForm extends HTMLElement{
} }
).then(data=>{ ).then(data=>{
this.#data = data this.#data = data
const rts =
this.#data.links
.filter(l => l.rel === "compatibleWith")
.map(l=>l.href.replace("runtimes/",""))
.join(" ")
return this.#boot.secureFetch(this.#serviceurl + "/infrastructures/runtimes?runtimes=" + rts)
}).then(resp=>{
this.#data.executable = resp.status === 200
}).then(()=>{
this.showMethod() this.showMethod()
}).catch(err=>alert(err)) }).catch(err=>alert(err))
} }
@ -70,14 +79,49 @@ class CCPExecutionForm extends HTMLElement{
sendExecutionRequest(){ sendExecutionRequest(){
const url = this.#serviceurl + "/processes/" + this.#method + "/execution" const url = this.#serviceurl + "/processes/" + this.#method + "/execution"
const req = this.buildRequest()
this.#boot.secureFetch( this.#boot.secureFetch(
url, { method : "POST", body : JSON.stringify({}), headers : { "Content-Type" : "application/json"}} url, { method : "POST", body : JSON.stringify(req), headers : { "Content-Type" : "application/json"}}
).then(reply=>{ ).then(reply=>{
if(reply.status !== 200 && reply.status !== 201){ if(reply.status !== 200 && reply.status !== 201){
throw "Error while requesting resource" throw "Error while requesting resource"
} }
console.log("Execution reuqest sent") return reply.json()
}).catch(err => alert("Unable to call execute")) }).then(data=>{
if(data.status !== "accepted"){
throw "Execution has not been accepted by server"
}
this.#executionmonitor = document.querySelector("d4s-ccp-executionmonitor")
if(this.#executionmonitor){
this.#executionmonitor.addExecution( { self : data.links[0].href, events : [data], jobID : data.jobID, method : this.#data.title})
}
}).catch(err => alert("Unable to call execute: " + err))
}
buildRequest(){
let request = { inputs : {}, outputs : {}, response : "raw"}
//fill inputs
const inputs = this.getInputs()
inputs.forEach(i=>{
request.inputs[i.name] = i.value
})
//fill outputs
const outputs = this.getOutputs()
outputs.forEach(o=>{
if(o.enabled) request.outputs[o.name] = { transmissionMode : "value" };
})
return request
}
getInputs(){
return Array.prototype.slice.call(this.#rootdoc.querySelectorAll("d4s-ccp-input"))
}
getOutputs(){
return Array.prototype.slice.call(this.#rootdoc.querySelectorAll("d4s-ccp-output"))
} }
#empty_executionform_bss = { #empty_executionform_bss = {
@ -153,29 +197,13 @@ class CCPExecutionForm extends HTMLElement{
BSS.apply(this.#executionform_bss) BSS.apply(this.#executionform_bss)
} }
}, },
{
target: "div.ccp-runtimes select",
in : (e,d)=>d,
recurse : [
{
"in" : (e,d)=>{
const rts = d.links.filter(l=> l.rel === "compatibleWith").map(l=>l.href.replace("runtimes/",""))
return this.#infrastructurecontroller.getCompatibleRuntimes(rts)
},
target : "option",
apply : (e,d)=>{
e.value = d.runtime["descriptor-id"]
e.textContent = `${d.runtime.name} [${d.infrastructure.name}]`
}
}
]
},
{ {
target: "#execute_method_button", target: "#execute_method_button",
on_click : ev=>{ on_click : ev=>{
ev.preventDefault() ev.preventDefault()
ev.stopPropagation() ev.stopPropagation()
this.sendExecutionRequest() if(this.#data.executable) this.sendExecutionRequest();
else alert("This method has no compatible runtimes available")
return false; return false;
} }
}, },
@ -185,3 +213,35 @@ class CCPExecutionForm extends HTMLElement{
} }
window.customElements.define('d4s-ccp-executionform', CCPExecutionForm); window.customElements.define('d4s-ccp-executionform', CCPExecutionForm);
class CCPExecutionEnvironmentOption extends HTMLOptionElement{
#infrastructure;
#runtime;
constructor(runtime, infrastructure){
super(`${runtime.name} [${infrastructure.name}]`, runtime["descriptor-id"])
this.#runtime = runtime
this.#infrastructure = infrastructure
this.textContent = `${runtime.name} [${infrastructure.name}]`
this.value = this.runtimeId
}
get infrastructureName(){
return this.#infrastructure.name
}
get infrastructureId(){
return this.#infrastructure.id
}
get runtimeName(){
return this.#runtime.name
}
get runtimeId(){
return this.#runtime["descriptor-id"]
}
}
window.customElements.define('d4s-ccp-environment-option', CCPExecutionEnvironmentOption, {extends:'option'});

View File

@ -0,0 +1,132 @@
class CCPExecutionMonitor extends HTMLElement {
#boot = null;
#rootdoc = null;
#broadcasturl = "ws://nubis1.int.d4science.net:8989/ws/notification";
#executions = [];
#socket = null;
constructor(){
super()
this.#boot = document.querySelector("d4s-boot-2")
this.#rootdoc = this.attachShadow({ "mode" : "open"})
this.connectBroadcast()
}
connectedCallback(){
this.#rootdoc.innerHTML = this.render()
}
render(){
return `
<template id="EXECUTIOM_LIST_TEMPLATE">
<ol name="ccp_execution_list" class="ccp-execution-list" class="ccp-execution-list">
<li class="ccp-execution-item">
<details open>
<summary>
<h5 style="display:inline"></h5>
<span name="status" class="badge"></span>
</summary>
<p name="message" class="status-message font-weight-light font-italic" style="margin-top:revert"></span>
<ul class="list-group">
<li class="list-group-item"></li>
</ul>
</details>
</li>
</ol>
</template>
<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">
<h3>Execution monitor</h3>
<ol name="ccp_execution_list" style="display:none"></ol>
`
}
addExecution(execution){
this.#executions.push(execution)
this.refreshExecution(execution)
}
refreshExecution(execution){
this.#boot.secureFetch(execution.self).then(reply =>{
if(reply.status === 200) return reply.json();
else throw "Unable to load job links" + reply.text
}).then(data=>{
execution.links = data
BSS.apply(this.execution_list_bss, this.#rootdoc)
}).catch(err=>{ console.error(err)})
}
connectBroadcast(){
this.#socket = new WebSocket(this.#broadcasturl + "/executions");
this.#socket.onmessage = event=>{
const data = JSON.parse(event.data)
let exec = this.#executions.filter(e=>e.jobID === data.jobID)[0]
if(exec){
exec.events.push(data)
if(!exec.self) exec.self = data.links[0].href;
this.refreshExecution(exec)
}
}
window.setInterval( ()=>this.#socket.send("ping"), 30000)
}
execution_list_bss = {
template : "#EXECUTIOM_LIST_TEMPLATE",
target : "ol[name=ccp_execution_list]",
in : ()=>{ return {executions : this.#executions} },
recurse:[
{
target : "li",
in : (e,d)=>d.executions,
apply: (e,d)=>{
if(d.events && d.events.length > 0){
e.alt = e.title = (new Date(d.events[d.events.length - 1].updated)).toLocaleString()
}
},
recurse : [
{
target : "h5",
apply : (e,d)=>{
e.textContent = d.method
}
},
{
target : "span[name=status]",
apply : (e,d)=>{
if(d.events && d.events.length > 0){
const status = d.events[d.events.length - 1].status
e.textContent = status
if (status === "running") e.classList.add("badge-primary");
else if (status === "successful") e.classList.add("badge-success");
else if (status === "failure") e.classList.add("badge-danger");
else e.classList.add("badge-secondary");
}
}
},
{
target : ".status-message",
apply : (e,d)=>{
if(d.events && d.events.length > 0){
e.textContent = d.events[d.events.length - 1].message
}
}
},
{
target : "ul",
recurse : [
{
target : "li",
"in" : (e,d)=>{return d.links.map(l=>{ return { href : d.self + "/" + l.path, path : l.path} })},
apply : (e,d)=>{
console.log("applying", e, d)
e.innerHTML = `<a href="${d.href}">${d.path}</a>`
}
}
]
}
]
}
]
}
}
window.customElements.define('d4s-ccp-executionmonitor', CCPExecutionMonitor);

View File

@ -1,7 +1,6 @@
class CCPInfrastructureList extends HTMLElement{ class CCPInfrastructureList extends HTMLElement{
#boot; #boot;
#infrastructures;
#socket; #socket;
#data = {}; #data = {};
@ -23,29 +22,29 @@ class CCPInfrastructureList extends HTMLElement{
fetchInfrastructures(){ fetchInfrastructures(){
console.log("Calling fetch infrastructures") console.log("Calling fetch infrastructures")
this.#boot.secureFetch(this.#serviceurl + "/infrastructures"). this.#boot.secureFetch(this.#serviceurl + "/infrastructures/cache").
then(resp=>{ then(resp=>{
console.log("Received resp for infrastructures ", resp.status)
return resp.json() return resp.json()
}).then(data=>{ }).then(data=>{
this.#infrastructures = data this.#data = data
this.updateList() /*this.updateList()*/
}).catch(err=>{ }).catch(err=>{
alert("Error while downloading methods: ", err) alert("Error while downloading methods: ", err)
}) })
} }
updateList(resp){ /* updateList(resp){
this.connectBroadcast() this.connectBroadcast()
this.#infrastructures.forEach(i=>{ this.#infrastructures.forEach(i=>{
this.#data[i.id] = { status : "unknown", type : i.type, name : i.name, runtimes : [] } this.#data[i.id] = { id : i.id, status : "unknown", type : i.type, name : i.name, runtimes : [] }
this.#boot.secureFetch(this.#serviceurl + `/infrastructures/${i.id}/status`).then( this.#boot.secureFetch(this.#serviceurl + `/infrastructures/${i.id}/status`).then(
()=>console.log("Requested async status update for infrastructure ", i.id) ()=>console.log("Requested async status update for infrastructure ", i.id)
) )
}) })
} }
*/
connectBroadcast(){ /* connectBroadcast(){
this.#socket = new WebSocket(this.#broadcasturl + "/infrastructures"); this.#socket = new WebSocket(this.#broadcasturl + "/infrastructures");
this.#socket.onmessage = event=>{ this.#socket.onmessage = event=>{
const data = JSON.parse(event.data) const data = JSON.parse(event.data)
@ -59,11 +58,10 @@ class CCPInfrastructureList extends HTMLElement{
} }
window.setInterval( ()=>this.#socket.send("ping"), 30000) window.setInterval( ()=>this.#socket.send("ping"), 30000)
} }
*/
getCompatibleRuntimes(rts){ getCompatibleRuntimes(rts){
console.log("Request for ", rts)
const available = Object.keys(this.#data).reduce((acc, i) => { const available = Object.keys(this.#data).reduce((acc, i) => {
console.log(this.#data[i].runtimes)
const compatiblerts = this.#data[i].runtimes. const compatiblerts = this.#data[i].runtimes.
filter(r=>rts.indexOf(r["descriptor-id"]) >= 0). filter(r=>rts.indexOf(r["descriptor-id"]) >= 0).
map(cr=>{ return { runtime : cr, infrastructure : this.#data[i] } }) map(cr=>{ return { runtime : cr, infrastructure : this.#data[i] } })

View File

@ -2,7 +2,6 @@ class CCPInputWidgetController extends HTMLElement {
#input = null; #input = null;
#renderer = null; #renderer = null;
#rootdoc = null;
constructor(){ constructor(){
super() super()
@ -19,6 +18,14 @@ class CCPInputWidgetController extends HTMLElement {
render(){ render(){
return this.#renderer.render() return this.#renderer.render()
} }
get name(){
return this.#renderer.name
}
get value(){
return this.#renderer.getValue(this)
}
} }
window.customElements.define('d4s-ccp-input', CCPInputWidgetController); window.customElements.define('d4s-ccp-input', CCPInputWidgetController);
@ -89,6 +96,10 @@ class SimpleInputRenderer extends Renderer{
super(input) super(input)
} }
getValue(parent){
return parent.querySelector("input").value
}
render(){ render(){
let required = this.required ? 'required="required"' : "" let required = this.required ? 'required="required"' : ""
let readonly = this.readOnly ? 'readonly="readOnly"' : "" let readonly = this.readOnly ? 'readonly="readOnly"' : ""
@ -113,6 +124,10 @@ class DateTimeInputRenderer extends Renderer{
super(input) super(input)
} }
getValue(parent){
return parent.querySelector("input").value
}
render(){ render(){
let required = this.required ? 'required="required"' : "" let required = this.required ? 'required="required"' : ""
let readonly = this.schema.readOnly ? 'readonly="readOnly"' : "" let readonly = this.schema.readOnly ? 'readonly="readOnly"' : ""
@ -138,6 +153,10 @@ class EnumInputRenderer extends Renderer{
super(input) super(input)
} }
getValue(parent){
return parent.querySelector("select").value
}
render(){ render(){
let options = this.schema.enum.map(e => { let options = this.schema.enum.map(e => {
return e === this.schema.default ? return e === this.schema.default ?
@ -170,6 +189,10 @@ class CodeInputRenderer extends Renderer{
super(input) super(input)
} }
getValue(parent){
return parent.querySelector("textarea").textContent
}
connectedCallback(controller){ connectedCallback(controller){
/*const ta = controller.querySelector("textarea") /*const ta = controller.querySelector("textarea")
const opts = { const opts = {

View File

@ -0,0 +1,31 @@
class CCPOutputWidgetController extends HTMLElement {
#output = null;
constructor(){
super()
this.#output = JSON.parse(this.getAttribute("output"))
}
connectedCallback(){
this.innerHTML = this.render()
}
get name(){
return this.#output.id
}
get enabled(){
return this.querySelector("input").checked
}
render(){
return `
<div class="col form-check">
<input class="form-check-input" type="checkbox" name="this.#output.id" alt="${this.#output.description}" title="${this.#output.description}" checked="checked"/>
<label class="form-check-label">${this.#output.title}</label>
</div>
`
}
}
window.customElements.define('d4s-ccp-output', CCPOutputWidgetController);