diff --git a/ccp/js/executionformcontroller.js b/ccp/js/executionformcontroller.js index bf7c109..5dc17fe 100644 --- a/ccp/js/executionformcontroller.js +++ b/ccp/js/executionformcontroller.js @@ -4,22 +4,21 @@ class CCPExecutionForm extends HTMLElement{ #rootdoc; #data; #method; - #infrastructurecontroller; + #executionmonitor; #serviceurl = "https://nubis1.int.d4science.net:8080" - //#cdnurl = "https://nubis1.int.d4science.net:8080/ccp/executionformfragment.html" - #cdnurl = "http://d4science-cdn-public:8984/resources/ccp/executionformfragment.html" + #cdnurl = "https://nubis1.int.d4science.net:8080/ccp/executionformfragment.html" + //#cdnurl = "http://d4science-cdn-public:8984/resources/ccp/executionformfragment.html" constructor(){ super() this.#boot = document.querySelector("d4s-boot-2") this.#rootdoc = this.attachShadow({ "mode" : "open"}) - this.#infrastructurecontroller = document.querySelector("d4s-ccp-infrastructurelist") this.fetchMarkup() } static get observedAttributes() { - return ["method"]; + return ["method"]; } attributeChangedCallback(name, oldValue, newValue) { @@ -52,6 +51,16 @@ class CCPExecutionForm extends HTMLElement{ } ).then(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() }).catch(err=>alert(err)) } @@ -70,14 +79,49 @@ class CCPExecutionForm extends HTMLElement{ sendExecutionRequest(){ const url = this.#serviceurl + "/processes/" + this.#method + "/execution" + const req = this.buildRequest() 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=>{ if(reply.status !== 200 && reply.status !== 201){ throw "Error while requesting resource" } - console.log("Execution reuqest sent") - }).catch(err => alert("Unable to call execute")) + return reply.json() + }).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 = { @@ -153,29 +197,13 @@ class CCPExecutionForm extends HTMLElement{ 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", on_click : ev=>{ ev.preventDefault() ev.stopPropagation() - this.sendExecutionRequest() + if(this.#data.executable) this.sendExecutionRequest(); + else alert("This method has no compatible runtimes available") return false; } }, @@ -185,3 +213,35 @@ class CCPExecutionForm extends HTMLElement{ } 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'}); diff --git a/ccp/js/executionmonitorcontroller.js b/ccp/js/executionmonitorcontroller.js new file mode 100644 index 0000000..2f34711 --- /dev/null +++ b/ccp/js/executionmonitorcontroller.js @@ -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 ` + + +

Execution monitor

+
    + ` + } + + 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 = `${d.path}` + } + } + ] + } + ] + } + ] + } +} +window.customElements.define('d4s-ccp-executionmonitor', CCPExecutionMonitor); \ No newline at end of file diff --git a/ccp/js/infrastructurelistcontroller.js b/ccp/js/infrastructurelistcontroller.js index fc001c9..601c231 100644 --- a/ccp/js/infrastructurelistcontroller.js +++ b/ccp/js/infrastructurelistcontroller.js @@ -1,7 +1,6 @@ class CCPInfrastructureList extends HTMLElement{ #boot; - #infrastructures; #socket; #data = {}; @@ -23,29 +22,29 @@ class CCPInfrastructureList extends HTMLElement{ fetchInfrastructures(){ console.log("Calling fetch infrastructures") - this.#boot.secureFetch(this.#serviceurl + "/infrastructures"). + this.#boot.secureFetch(this.#serviceurl + "/infrastructures/cache"). then(resp=>{ - console.log("Received resp for infrastructures ", resp.status) return resp.json() }).then(data=>{ - this.#infrastructures = data - this.updateList() + this.#data = data + /*this.updateList()*/ }).catch(err=>{ alert("Error while downloading methods: ", err) }) } - updateList(resp){ +/* updateList(resp){ this.connectBroadcast() 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( ()=>console.log("Requested async status update for infrastructure ", i.id) ) }) } + */ - connectBroadcast(){ +/* connectBroadcast(){ this.#socket = new WebSocket(this.#broadcasturl + "/infrastructures"); this.#socket.onmessage = event=>{ const data = JSON.parse(event.data) @@ -59,11 +58,10 @@ class CCPInfrastructureList extends HTMLElement{ } window.setInterval( ()=>this.#socket.send("ping"), 30000) } + */ getCompatibleRuntimes(rts){ - console.log("Request for ", rts) const available = Object.keys(this.#data).reduce((acc, i) => { - console.log(this.#data[i].runtimes) const compatiblerts = this.#data[i].runtimes. filter(r=>rts.indexOf(r["descriptor-id"]) >= 0). map(cr=>{ return { runtime : cr, infrastructure : this.#data[i] } }) diff --git a/ccp/js/inputwidgetcontroller.js b/ccp/js/inputwidgetcontroller.js index 3eae1ed..d91d9e8 100644 --- a/ccp/js/inputwidgetcontroller.js +++ b/ccp/js/inputwidgetcontroller.js @@ -2,7 +2,6 @@ class CCPInputWidgetController extends HTMLElement { #input = null; #renderer = null; - #rootdoc = null; constructor(){ super() @@ -19,6 +18,14 @@ class CCPInputWidgetController extends HTMLElement { 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); @@ -88,7 +95,11 @@ class SimpleInputRenderer extends Renderer{ constructor(input){ super(input) } - + + getValue(parent){ + return parent.querySelector("input").value + } + render(){ let required = this.required ? 'required="required"' : "" let readonly = this.readOnly ? 'readonly="readOnly"' : "" @@ -113,6 +124,10 @@ class DateTimeInputRenderer extends Renderer{ super(input) } + getValue(parent){ + return parent.querySelector("input").value + } + render(){ let required = this.required ? 'required="required"' : "" let readonly = this.schema.readOnly ? 'readonly="readOnly"' : "" @@ -138,6 +153,10 @@ class EnumInputRenderer extends Renderer{ super(input) } + getValue(parent){ + return parent.querySelector("select").value + } + render(){ let options = this.schema.enum.map(e => { return e === this.schema.default ? @@ -170,6 +189,10 @@ class CodeInputRenderer extends Renderer{ super(input) } + getValue(parent){ + return parent.querySelector("textarea").textContent + } + connectedCallback(controller){ /*const ta = controller.querySelector("textarea") const opts = { diff --git a/ccp/js/outputwidgetcontroller.js b/ccp/js/outputwidgetcontroller.js new file mode 100644 index 0000000..ab904ea --- /dev/null +++ b/ccp/js/outputwidgetcontroller.js @@ -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 ` +
    + + +
    + ` + } +} +window.customElements.define('d4s-ccp-output', CCPOutputWidgetController); \ No newline at end of file