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,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'});

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{
#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] } })

View File

@ -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 = {

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);