added more widgets
This commit is contained in:
parent
14b9dc8299
commit
6aa0998f11
|
@ -2,7 +2,52 @@ class CCPInfrastructureList extends HTMLElement{
|
|||
|
||||
#boot;
|
||||
#socket;
|
||||
#data = {};
|
||||
#infrastructures;
|
||||
#runtimes;
|
||||
#rootdoc;
|
||||
|
||||
#style = `
|
||||
<link rel="stylesheet" href="https://cdn.dev.d4science.org/ccp/css/common.css"></link>
|
||||
<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">
|
||||
<style>
|
||||
.ccp_infrastructure_controller{
|
||||
|
||||
}
|
||||
.ccp_toolbar_button {
|
||||
padding: .3rem;
|
||||
line-height: 1rem;
|
||||
}
|
||||
.ccp_infrastructure_list{
|
||||
list-style:none;
|
||||
padding-left: 1rem;
|
||||
user-select: none;
|
||||
}
|
||||
.ccp_infrastructure_item{
|
||||
padding: .3rem 0 .3rem 0;
|
||||
}
|
||||
.ccp_infrastructure_list .lxd{
|
||||
background-color: #dd4814;
|
||||
}
|
||||
.ccp_infrastructure_list .docker{
|
||||
background-color: #2496ed;
|
||||
}
|
||||
.ccp_runtime_list{
|
||||
list-style:none;
|
||||
padding-left: 1rem;
|
||||
}
|
||||
.ccp_runtime_item{
|
||||
padding: .3rem 0 .3rem 0;
|
||||
}
|
||||
.ccp_instance_list{
|
||||
list-style:none;
|
||||
}
|
||||
.ccp_instance_item{
|
||||
padding: 0.3rem 0 0.3rem 1rem;
|
||||
font-style: italic;
|
||||
color: 888888;
|
||||
}
|
||||
</style>
|
||||
`;
|
||||
|
||||
#serviceurl = "https://nubis1.int.d4science.net:8080"
|
||||
#broadcasturl = "ws://nubis1.int.d4science.net:8989/ws/notification"
|
||||
|
@ -10,61 +55,181 @@ class CCPInfrastructureList extends HTMLElement{
|
|||
constructor(){
|
||||
super()
|
||||
this.#boot = document.querySelector("d4s-boot-2")
|
||||
this.#rootdoc = this.attachShadow({ "mode" : "open"})
|
||||
}
|
||||
|
||||
connectedCallback(){
|
||||
this.fetchInfrastructures()
|
||||
}
|
||||
|
||||
get data(){
|
||||
return this.#data
|
||||
this.connectBroadcast()
|
||||
this.fetchInfrastructures()
|
||||
}
|
||||
|
||||
fetchInfrastructures(){
|
||||
console.log("Calling fetch infrastructures")
|
||||
this.#boot.secureFetch(this.#serviceurl + "/infrastructures/cache").
|
||||
then(resp=>{
|
||||
return resp.json()
|
||||
}).then(data=>{
|
||||
this.#data = data
|
||||
/*this.updateList()*/
|
||||
}).catch(err=>{
|
||||
alert("Error while downloading methods: ", err)
|
||||
const prom1 = this.#boot.secureFetch(this.#serviceurl + "/infrastructures/cache").
|
||||
then(resp=>{
|
||||
if(resp.status !== 200) throw "Unable to fetch infrastructure cache";
|
||||
return resp.json()
|
||||
}).then(data=>{
|
||||
this.#infrastructures = data
|
||||
}).catch(err=>{
|
||||
alert(err)
|
||||
})
|
||||
|
||||
const prom2 = this.#boot.secureFetch(this.#serviceurl + "/runtimes").
|
||||
then(resp=>{
|
||||
if(resp.status !== 200) throw "Unable to fetch runtimes";
|
||||
return resp.json()
|
||||
}).then(data=>{
|
||||
this.#runtimes = data
|
||||
}).catch(err=>{
|
||||
alert(err)
|
||||
})
|
||||
|
||||
Promise.all([prom1, prom2]).then(results=>{
|
||||
this.showList()
|
||||
})
|
||||
}
|
||||
|
||||
refreshInfrastructures(){
|
||||
this.#boot.secureFetch(this.#serviceurl + "/infrastructures/cache", { method : "HEAD"})
|
||||
}
|
||||
|
||||
showList(){
|
||||
if(this.getAttribute("render") !== "true") {
|
||||
this.#rootdoc.innerHTML = ""
|
||||
return;
|
||||
}
|
||||
this.#rootdoc.innerHTML = `
|
||||
<div class="ccp_infrastructure_controller">
|
||||
${this.#style}
|
||||
${this.toolbar()}
|
||||
<ul class="ccp_infrastructure_list">
|
||||
${this.showInfrastructures()}
|
||||
</ul>
|
||||
</div>
|
||||
`
|
||||
this.#rootdoc.querySelector(".ccp_infrastructure_toolbar").addEventListener("click", ev=>{
|
||||
const evname = ev.target.getAttribute("name")
|
||||
switch(evname){
|
||||
case "refresh":
|
||||
this.refreshInfrastructures();
|
||||
break;
|
||||
}
|
||||
})
|
||||
|
||||
const forms = Array.prototype.slice.call(this.#rootdoc.querySelectorAll(".ccp_runtime_builder"))
|
||||
forms.forEach(f=>{
|
||||
f.addEventListener("submit", ev=>{
|
||||
ev.stopPropagation()
|
||||
ev.preventDefault()
|
||||
const sel = ev.target.querySelector("select")
|
||||
let url = ev.target.action + sel.value
|
||||
this.#boot.secureFetch(url, { method : ev.target.method})
|
||||
return false
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/* updateList(resp){
|
||||
this.connectBroadcast()
|
||||
this.#infrastructures.forEach(i=>{
|
||||
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)
|
||||
)
|
||||
})
|
||||
toolbar(){
|
||||
return `
|
||||
<div class="ccp_infrastructure_toolbar">
|
||||
<button name="refresh" class="btn btn-primary ccp_toolbar_button" title="Refresh">↺</button>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
*/
|
||||
|
||||
/* connectBroadcast(){
|
||||
showInfrastructures(){
|
||||
return this.#infrastructures.map(i => {
|
||||
return `
|
||||
<li class="ccp_infrastructure_item" title="${i.description}">
|
||||
<details>
|
||||
<summary>
|
||||
<span>${i.name}</span>
|
||||
${this.showAge(i)}
|
||||
${this.showType(i)}
|
||||
</summary>
|
||||
${this.showDeployableRuntimes(i)}
|
||||
<ul class="ccp_runtime_list">
|
||||
${this.showRuntimes(i)}
|
||||
</ul>
|
||||
</details>
|
||||
</li>
|
||||
`
|
||||
}).join("\n")
|
||||
}
|
||||
|
||||
showDeployableRuntimes(infra){
|
||||
const alreadydeployed = infra.runtimes.map(r=>r["descriptor-id"])
|
||||
const options = this.#runtimes.filter(r=>{
|
||||
return infra.type === r.type && alreadydeployed.indexOf(r.id) === -1
|
||||
}).map(r=>{
|
||||
return `<option value="${r.id}" title="${r.description}">${r.name}</option>`
|
||||
}).join("\n")
|
||||
return `
|
||||
<div style="padding-left:1rem">
|
||||
<form class="ccp_runtime_builder" action="${this.#serviceurl}/infrastructures/${infra.id}/runtime/" method="POST">
|
||||
<select name="runtime_selector" style="padding:.2rem;margin:5px;border:none">
|
||||
${options}
|
||||
</select>
|
||||
<button type="submit" class="btn btn-primary ccp_toolbar_button" title="Deploy runtime">+</button>
|
||||
</form>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
showRuntimes(infra){
|
||||
return infra.runtimes.map(r =>{
|
||||
return `
|
||||
<li class="ccp_runtime_item" title="${r.description}">
|
||||
<details>
|
||||
<summary>${r.name}</summary>
|
||||
<ul class="ccp_instance_list">
|
||||
${this.showInstances(r)}
|
||||
</ul>
|
||||
</details>
|
||||
</li>
|
||||
`
|
||||
}).join("\n")
|
||||
}
|
||||
|
||||
showInstances(runtime){
|
||||
return runtime.instances.map(i =>{
|
||||
return `
|
||||
<li class="ccp_instance_item">${i.name}</li>
|
||||
`
|
||||
}).join("\n")
|
||||
}
|
||||
|
||||
showAge(infra){
|
||||
const age = Math.floor(((new Date()) - (new Date(infra.date))) / 3600000)
|
||||
var cls = "badge-success"
|
||||
var agetext = "fresh"
|
||||
if(age > 24){
|
||||
cls = "badge-warning";
|
||||
agetext = "aged"
|
||||
} else if (age > 72){
|
||||
cls = "badge-secondary"
|
||||
agetext = "old"
|
||||
}
|
||||
return `<span class="badge ${cls}" name="age" title="${age} hours">${agetext}</span>`
|
||||
}
|
||||
|
||||
showType(infra){
|
||||
return `<span class="badge badge-secondary ${infra.type}" title="${infra.type}">${infra.type}</span>`
|
||||
}
|
||||
|
||||
connectBroadcast(){
|
||||
this.#socket = new WebSocket(this.#broadcasturl + "/infrastructures");
|
||||
this.#socket.onmessage = event=>{
|
||||
const data = JSON.parse(event.data)
|
||||
this.#data[data.infrastructure].status = data.status.toLowerCase()
|
||||
if(data.type.toLowerCase() === "update" && data.status.toLowerCase() === "ok"){
|
||||
this.#data[data.infrastructure].runtimes = data.data.runtimes
|
||||
}else{
|
||||
this.#data[data.infrastructure].runtimes = []
|
||||
}
|
||||
console.log("New Infrastructure status", this.#data)
|
||||
this.fetchInfrastructures()
|
||||
}
|
||||
window.setInterval( ()=>this.#socket.send("ping"), 30000)
|
||||
}
|
||||
*/
|
||||
|
||||
getCompatibleRuntimes(rts){
|
||||
const available = Object.keys(this.#data).reduce((acc, i) => {
|
||||
const compatiblerts = this.#data[i].runtimes.
|
||||
const available = Object.keys(this.#infrastructures).reduce((acc, i) => {
|
||||
const compatiblerts = this.#infrastructures[i].runtimes.
|
||||
filter(r=>rts.indexOf(r["descriptor-id"]) >= 0).
|
||||
map(cr=>{ return { runtime : cr, infrastructure : this.#data[i] } })
|
||||
map(cr=>{ return { runtime : cr, infrastructure : this.#infrastructures[i] } })
|
||||
return acc.concat(compatiblerts)
|
||||
}, [])
|
||||
return available
|
||||
|
|
|
@ -0,0 +1,158 @@
|
|||
class CCPInputWidgetEditorController extends HTMLElement{
|
||||
|
||||
#input = null
|
||||
#index = null
|
||||
#type = null
|
||||
|
||||
#delete_icon = `
|
||||
<svg style="width:24px;height:24px;pointer-events: none;" 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" />
|
||||
</svg>
|
||||
`
|
||||
|
||||
constructor(){
|
||||
super();
|
||||
|
||||
}
|
||||
|
||||
connectedCallback(){
|
||||
|
||||
}
|
||||
|
||||
get index(){
|
||||
return this.#index
|
||||
}
|
||||
|
||||
render(input, i){
|
||||
this.#index = i
|
||||
this.#input = input
|
||||
this.#type = input.schema.enum ? "enum" : "string"
|
||||
this.innerHTML = `
|
||||
<details>
|
||||
<summary class="mb-3">
|
||||
<input class="form-control" style="width:auto;display:inline" required="required" name="id" value="${this.#input.id}"/>
|
||||
<button data-index="${this.#index}" name="delete-input" title="Delete" class="btn btn-danger ccp-toolbar-button">
|
||||
${this.#delete_icon}
|
||||
</button>
|
||||
</summary>
|
||||
<div style="padding-left: 1rem;border-left: 1px solid gray;">
|
||||
<div class="row mb-3">
|
||||
<div class="col">
|
||||
<div class="form-field">
|
||||
<input name="title" class="form-control" placeholder="title" value="${this.#input.title}" required="required"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div class="form-field">
|
||||
<input name="description" class="form-control" placeholder="description" value="${this.#input.description}" required="required"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-3">
|
||||
<div class="form-field">
|
||||
<label>Type</label>
|
||||
<select name="type" class="form-control" placeholder="type" value="${this.#input.schema.type}">
|
||||
<option value="string">String</option>
|
||||
<option value="enum">Enum</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="form-field">
|
||||
<label>Min</label>
|
||||
<input value="${this.#input.minOccurs}" type="number" min="0" step="1" name="minOccurs" value="${this.#input.minOccurs}" required="required" class="form-control" placeholder="minOccurs"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="form-field">
|
||||
<label>Max</label>
|
||||
<input value="${this.#input.maxOccurs}" type="number" min="0" step="1" name="maxOccurs" value="${this.#input.maxOccurs}" required="required" class="form-control" placeholder="maxOccurs"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div name="string-input" class="${this.#type === 'enum' ? 'd-none' : ''} row mb-3">
|
||||
<div class="col-3">
|
||||
<div class="form-field">
|
||||
<label>Format</label>
|
||||
<select value="${this.#input.schema.format}" name="format" class="form-control" value="${this.#input.schema.format}">
|
||||
<option value="none">None</option>
|
||||
<option value="date">Date</option>
|
||||
<option value="time">Time</option>
|
||||
<option value="dateTime">Date time</option>
|
||||
<option value="code">Code</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<label>Mime</label>
|
||||
<div class="form-field">
|
||||
<input value="${this.#input.schema.contentMediaType}" name="contentMediaType" class="form-control" placeholder="mime"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div name="enum-input" class="${this.#type !== 'enum' ? 'd-none' : ''} mb-3">
|
||||
<div class="form-field">
|
||||
<input name="options" class="form-control" type="text" placeholder="option"/>
|
||||
<small class="form-text text-muted">Comma separated list of options</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div class="form-field">
|
||||
<input value="${this.#input.schema.default}" name="default" class="form-control" placeholder="default"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
`
|
||||
|
||||
this.addEventListener("input", ev=>{
|
||||
const val = ev.target.value
|
||||
const ename = ev.target.getAttribute("name")
|
||||
if(ename === "id"){
|
||||
this.#input.id = val
|
||||
}
|
||||
else if(ename === "title"){
|
||||
this.#input.title = val
|
||||
}
|
||||
else if(ename === "description"){
|
||||
this.#input.title = val
|
||||
}
|
||||
else if(ename === "minOccurs"){
|
||||
this.#input.minOccurs = val
|
||||
}
|
||||
else if(ename === "maxOccurs"){
|
||||
this.#input.maxOccurs = val
|
||||
}
|
||||
else if(ename === "format"){
|
||||
this.#input.schema.format = val
|
||||
}
|
||||
else if(ename === "contentMediaType"){
|
||||
this.#input.schema.contentMediaType = val
|
||||
}
|
||||
else if(ename === "options"){
|
||||
this.#input.schema.enum = val.split(",")
|
||||
}
|
||||
else if(ename === "default"){
|
||||
this.#input.schema.default = val
|
||||
}
|
||||
else if(ename === "type"){
|
||||
this.#type = ev.target.value
|
||||
if(this.#type === "enum"){
|
||||
this.querySelector("div[name=string-input]").classList.add("d-none")
|
||||
this.querySelector("div[name=enum-input]").classList.remove("d-none")
|
||||
this.#input.schema.enum = this.querySelector("input[name=options]").value.split(",")
|
||||
}else if(this.#type === "string"){
|
||||
this.querySelector("div[name=enum-input]").classList.add("d-none")
|
||||
this.querySelector("div[name=string-input]").classList.remove("d-none")
|
||||
delete this.#input.schema['enum']
|
||||
}
|
||||
}
|
||||
|
||||
console.log(this.#input)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
window.customElements.define('d4s-ccp-input-editor', CCPInputWidgetEditorController);
|
|
@ -0,0 +1,666 @@
|
|||
class CCPMethodEditorController extends HTMLElement{
|
||||
|
||||
#boot;
|
||||
#rootdoc;
|
||||
|
||||
#serviceurl = "https://nubis1.int.d4science.net:8080"
|
||||
#runtimes;
|
||||
#locked = false;
|
||||
|
||||
#tmp_inputs = []
|
||||
#tmp_outputs = []
|
||||
#current = null;
|
||||
#method_template = {
|
||||
id : "",
|
||||
title : "New Method",
|
||||
description : "New empty method",
|
||||
version : "1.0.0",
|
||||
jobControlOptions : "async-execute",
|
||||
inputs : {},
|
||||
outputs : {},
|
||||
additionalParameters : {
|
||||
parameters : [
|
||||
{
|
||||
name : "deploy-script",
|
||||
value : [
|
||||
`
|
||||
- hosts: localhost
|
||||
tasks:
|
||||
- name: hookup instance
|
||||
add_host:
|
||||
name: "{ instancename }"
|
||||
ansible_connection: lxd
|
||||
groupname: "targets"
|
||||
`
|
||||
]
|
||||
},
|
||||
{
|
||||
name : "execute-script",
|
||||
value : [
|
||||
`
|
||||
- hosts: localhost
|
||||
tasks:
|
||||
- name: hookup instance
|
||||
add_host:
|
||||
name: "{ instancename }"
|
||||
ansible_connection: lxd
|
||||
groupname: "targets"
|
||||
`
|
||||
]
|
||||
},
|
||||
{
|
||||
name : "fetch-output-script",
|
||||
value : [
|
||||
`
|
||||
- hosts: localhost
|
||||
tasks:
|
||||
- name: hookup instance
|
||||
add_host:
|
||||
name: "{ instancename }"
|
||||
ansible_connection: lxd
|
||||
groupname: "targets"
|
||||
`
|
||||
]
|
||||
},
|
||||
{
|
||||
name : "undeploy-script",
|
||||
value : [
|
||||
`
|
||||
- hosts: localhost
|
||||
tasks:
|
||||
- name: hookup instance
|
||||
add_host:
|
||||
name: "{ instancename }"
|
||||
ansible_connection: lxd
|
||||
groupname: "targets"
|
||||
`
|
||||
]
|
||||
},
|
||||
{
|
||||
name : "cancel-script",
|
||||
value : [
|
||||
`
|
||||
- hosts: localhost
|
||||
tasks:
|
||||
- name: hookup instance
|
||||
add_host:
|
||||
name: "{ instancename }"
|
||||
ansible_connection: lxd
|
||||
groupname: "targets"
|
||||
`
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
links : []
|
||||
}
|
||||
|
||||
#style = `
|
||||
<link rel="stylesheet" href="https://cdn.dev.d4science.org/ccp/css/common.css"></link>
|
||||
<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">
|
||||
<style>
|
||||
.ccp-method-editor {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.ccp-toolbar-button {
|
||||
font-weight: bold;
|
||||
padding:.3rem;
|
||||
line-height:.8rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ccp-toolbar-button svg {
|
||||
fill: white;
|
||||
width:24px;
|
||||
height:24px;
|
||||
}
|
||||
|
||||
.ccp-option {
|
||||
background-color:#eeffff;
|
||||
color:#0099CC;
|
||||
border:solid 1px #0099CC;
|
||||
}
|
||||
|
||||
.plexiglass{
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0,0,0,.3);
|
||||
z-index: 10;
|
||||
}
|
||||
</style>
|
||||
`
|
||||
#erase_icon = `
|
||||
<svg viewBox="0 0 24 24">
|
||||
<path d="M16.24,3.56L21.19,8.5C21.97,9.29 21.97,10.55 21.19,11.34L12,20.53C10.44,22.09 7.91,22.09 6.34,20.53L2.81,17C2.03,16.21 2.03,14.95 2.81,14.16L13.41,3.56C14.2,2.78 15.46,2.78 16.24,3.56M4.22,15.58L7.76,19.11C8.54,19.9 9.8,19.9 10.59,19.11L14.12,15.58L9.17,10.63L4.22,15.58Z" />
|
||||
`
|
||||
|
||||
#plus_icon = `
|
||||
<svg viewBox="0 0 24 24">
|
||||
<path d="M19,13H13V19H11V13H5V11H11V5H13V11H19V13Z" />
|
||||
</svg>
|
||||
`
|
||||
#disc_icon = `
|
||||
<svg viewBox="0 0 24 24">
|
||||
<path d="M15,9H5V5H15M12,19A3,3 0 0,1 9,16A3,3 0 0,1 12,13A3,3 0 0,1 15,16A3,3 0 0,1 12,19M17,3H5C3.89,3 3,3.9 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V7L17,3Z" />
|
||||
</svg>
|
||||
`
|
||||
#output_icon = `
|
||||
<svg viewBox="0 0 24 24">
|
||||
<path d="M23,12L19,8V11H10V13H19V16M1,18V6C1,4.89 1.9,4 3,4H15A2,2 0 0,1 17,6V9H15V6H3V18H15V15H17V18A2,2 0 0,1 15,20H3A2,2 0 0,1 1,18Z" />
|
||||
</svg>
|
||||
`
|
||||
|
||||
#ansible_icon = `
|
||||
<svg viewBox="0 0 24 24"><path fill="currentColor" d="M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10s10-4.5 10-10S17.5 2 12 2m4.1 15c-.19 0-.34-.1-.55-.27l-5.16-4.17l-1.73 4.34H7.17l4.37-10.51c.11-.28.35-.42.63-.42s.5.14.62.42l3.98 9.58c.04.11.07.22.07.29c-.01.42-.34.74-.74.74m-3.93-8.89l2.59 6.39l-3.91-3.08l1.32-3.31Z"></path>
|
||||
</svg>
|
||||
`
|
||||
|
||||
constructor(){
|
||||
super();
|
||||
this.#boot = document.querySelector("d4s-boot-2")
|
||||
this.#rootdoc = this.attachShadow({ "mode" : "open"})
|
||||
this.fetchRuntimes()
|
||||
}
|
||||
|
||||
connectedCallback(){
|
||||
|
||||
}
|
||||
|
||||
fetchRuntimes(){
|
||||
this.#boot.secureFetch(this.#serviceurl + "/runtimes").
|
||||
then(resp=>{
|
||||
if(resp.status !== 200) throw "Unable to fetch runtimes";
|
||||
return resp.json()
|
||||
}).then(data=>{
|
||||
this.#runtimes = data
|
||||
this.render()
|
||||
}).catch(err=>{
|
||||
alert(err)
|
||||
})
|
||||
}
|
||||
|
||||
saveCurrent(){
|
||||
if(this.#current != null){
|
||||
this.adoptTemporaries()
|
||||
console.log(this.#current)
|
||||
}
|
||||
}
|
||||
|
||||
cloneMethod(method){
|
||||
if(this.#locked) return;
|
||||
this.lockRender()
|
||||
this.#boot.secureFetch(this.#serviceurl + "/processes/" + method).then(
|
||||
(resp)=>{
|
||||
if(resp.status === 200){
|
||||
return resp.json()
|
||||
}else throw "Error retrieving process"
|
||||
}
|
||||
).then(data=>{
|
||||
this.#current = data
|
||||
this.#current.id = ""
|
||||
this.#tmp_inputs = Object.keys(this.#current.inputs).map(k=>this.#current.inputs[k])
|
||||
this.#tmp_outputs = Object.keys(this.#current.outputs).map(k=>this.#current.outputs[k])
|
||||
this.unlockRender()
|
||||
}).catch(err=>{
|
||||
this.unlockRender()
|
||||
})
|
||||
}
|
||||
|
||||
adoptTemporaries(){
|
||||
this.#current.inputs = {}
|
||||
this.#tmp_inputs.forEach(t=>{
|
||||
this.#current.inputs[t.id] = t
|
||||
})
|
||||
this.#current.outputs = {}
|
||||
this.#tmp_outputs.forEach(t=>{
|
||||
this.#current.outputs[t.id] = t
|
||||
})
|
||||
}
|
||||
|
||||
getInputAt(i){
|
||||
return this.#tmp_inputs[i]
|
||||
}
|
||||
|
||||
deleteTmpInputAt(i){
|
||||
this.#tmp_inputs.splice(i,1)
|
||||
}
|
||||
|
||||
deleteTmpOutputAt(i){
|
||||
this.#tmp_outputs.splice(i,1)
|
||||
}
|
||||
|
||||
lockRender(){
|
||||
this.#locked = true
|
||||
this.#rootdoc.querySelector(".plexiglass").classList.toggle("d-none")
|
||||
}
|
||||
|
||||
unlockRender(){
|
||||
this.#rootdoc.querySelector(".plexiglass").classList.toggle("d-none")
|
||||
this.render()
|
||||
this.#locked = false
|
||||
}
|
||||
|
||||
render(){
|
||||
if(this.#current == null){
|
||||
this.#current = JSON.parse(JSON.stringify(this.#method_template))
|
||||
this.#current.id = Math.random(10e6)|0
|
||||
}
|
||||
this.#rootdoc.innerHTML = `
|
||||
<div class="ccp-method-editor">
|
||||
<div class="d-none plexiglass">
|
||||
<svg class="spinner" viewBox="0 0 66 66">
|
||||
<circle class="path" fill="none" stroke-width="6" stroke-linecap="round" cx="33" cy="33" r="30"></circle>
|
||||
</svg>
|
||||
</div>
|
||||
${this.#style}
|
||||
<div class="ccp-method-editor-form">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div>
|
||||
<span class="mr-2">${this.#current.title}</span>
|
||||
${this.renderSaveButton()}
|
||||
${this.renderResetButton()}
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="mb-3 row">
|
||||
<div class="col">
|
||||
<label class="form-label">Title</label>
|
||||
<input name="title" class="form-control" type="text" required="required" value="${this.#current.title}"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<label class="form-label">Version</label>
|
||||
<input name="version" class="form-control" type="text" required="required" value="${this.#current.version}"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Description</label>
|
||||
<input name="description" class="form-control" type="text" required="required" value="${this.#current.description}"/>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Keywords</label>
|
||||
<input name="keyword-input" class="form-control" type="text"/>
|
||||
<div name="keyword-list" class="form-text">
|
||||
${this.renderKeywords()}
|
||||
</div>
|
||||
</div>
|
||||
<div name="runtimes" class="mb-3">
|
||||
<label class="form-label">Compatible Runtimes</label>
|
||||
<select name="runtime-input" class="form-control">
|
||||
${this.renderRuntimeOptions()}
|
||||
</select>
|
||||
<div name="runtime-list" class="form-text">
|
||||
${this.renderRuntimes()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<details class="card">
|
||||
<summary class="card-header">
|
||||
<span class="mr-2">Inputs</span>
|
||||
${this.renderPlusButton("add-input")}
|
||||
</summary>
|
||||
<div class="card-body" name="input-list">
|
||||
</div>
|
||||
</details>
|
||||
<details class="card">
|
||||
<summary class="card-header" name="output-buttons">
|
||||
<span class="mr-2">Outputs</span>
|
||||
${this.renderStandardOutputButtons()}
|
||||
${this.renderPlusButton("add-output")}
|
||||
</summary>
|
||||
<div class="card-body" name="output-list">
|
||||
<d4s-ccp-output-editor></d4s-ccp-output-editor>
|
||||
</div>
|
||||
</div>
|
||||
<details class="card" name="script-list">
|
||||
<summary class="card-header">
|
||||
<span>Scripts</span>
|
||||
<select name="script-selector" style="border:none; padding:.3rem">
|
||||
<option value="deploy-script">Deploy</option>
|
||||
<option value="execute-script">Execute</option>
|
||||
<option value="fetch-output-script">Fetch output</option>
|
||||
<option value="undeploy-script">Undeploy</option>
|
||||
<option value="cancel-script">Cancel</option>
|
||||
</select>
|
||||
${ this.renderAnsibleIcon() }
|
||||
</summary>
|
||||
<div class="card-body" name="input_container">
|
||||
${this.renderScripts()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
this.renderInputs()
|
||||
this.renderOutputs()
|
||||
|
||||
this.#rootdoc.addEventListener("drop", ev=>{
|
||||
if(ev.dataTransfer && ev.dataTransfer.getData('text/plain+ccpmethod')){
|
||||
const id = ev.dataTransfer.getData('text/plain+ccpmethod')
|
||||
ev.preventDefault()
|
||||
ev.stopPropagation()
|
||||
this.cloneMethod(id)
|
||||
}
|
||||
})
|
||||
|
||||
this.#rootdoc.addEventListener("dragover", ev=>{
|
||||
ev.preventDefault()
|
||||
})
|
||||
|
||||
this.#rootdoc.querySelector("button[name=reset]").addEventListener("click", ev=>{
|
||||
this.#current = this.#method_template
|
||||
this.#tmp_inputs = []
|
||||
this.#tmp_outputs = []
|
||||
this.render()
|
||||
ev.preventDefault()
|
||||
ev.stopPropagation()
|
||||
})
|
||||
|
||||
this.#rootdoc.querySelector("button[name=save]").addEventListener("click", ev=>{
|
||||
ev.preventDefault()
|
||||
ev.stopPropagation()
|
||||
this.saveCurrent()
|
||||
})
|
||||
|
||||
this.#rootdoc.querySelector("input[name=keyword-input]").addEventListener("keypress", ev=>{
|
||||
if(ev.key === "Enter" || ev.which === 13){
|
||||
ev.preventDefault()
|
||||
ev.stopPropagation()
|
||||
const val = ev.target.value
|
||||
ev.target.value = null
|
||||
if(!this.#current.keywords){
|
||||
this.#current.keywords = [val]
|
||||
}else{
|
||||
this.#current.keywords.push(val)
|
||||
}
|
||||
this.reRenderKeywords()
|
||||
}
|
||||
})
|
||||
|
||||
this.#rootdoc.querySelector("div[name=keyword-list]").addEventListener("click", ev=>{
|
||||
ev.preventDefault()
|
||||
ev.stopPropagation()
|
||||
if(ev.target.getAttribute('name') === "delete-keyword"){
|
||||
const index = ev.target.getAttribute("data-index")
|
||||
this.#current.keywords.splice(index, 1)
|
||||
this.reRenderKeywords()
|
||||
this.#rootdoc.querySelector("input[name=keyword-input]").focus()
|
||||
}
|
||||
})
|
||||
|
||||
this.#rootdoc.querySelector("div[name=runtimes]").addEventListener("change", ev=>{
|
||||
if(ev.target.getAttribute("name") === "runtime-input" && ev.target.value){
|
||||
ev.preventDefault()
|
||||
ev.stopPropagation()
|
||||
const id = ev.target.value
|
||||
const display = ev.target.options[ev.target.selectedIndex].text
|
||||
let link = {
|
||||
rel : "compatibleWith", title : display, href : "runtimes/" + id
|
||||
}
|
||||
if(!this.#current.links){
|
||||
this.#current.links = [link]
|
||||
}else{
|
||||
this.#current.links.push(link)
|
||||
}
|
||||
this.reRenderRuntimes(ev.currentTarget)
|
||||
}
|
||||
})
|
||||
|
||||
this.#rootdoc.querySelector("div[name=runtime-list]").addEventListener("click", ev=>{
|
||||
ev.preventDefault()
|
||||
ev.stopPropagation()
|
||||
if(ev.target.getAttribute('name') === "delete-runtime"){
|
||||
const index = ev.target.getAttribute("data-index")
|
||||
this.#current.links.splice(index, 1)
|
||||
this.reRenderRuntimes()
|
||||
}
|
||||
})
|
||||
|
||||
this.#rootdoc.querySelector("button[name=add-input]").addEventListener("click", ev=>{
|
||||
ev.preventDefault()
|
||||
ev.stopPropagation()
|
||||
this.#tmp_inputs.push(
|
||||
{
|
||||
id : "new_input",
|
||||
title : "New input",
|
||||
description : "A new input field",
|
||||
minOccurs : 1,
|
||||
maxOccurs : 1,
|
||||
schema : {
|
||||
type : "string",
|
||||
format : null,
|
||||
contentMediaType : null,
|
||||
default : ""
|
||||
}
|
||||
}
|
||||
)
|
||||
this.renderInputs()
|
||||
})
|
||||
|
||||
this.#rootdoc.querySelector("div[name=input-list]").addEventListener("click", ev=>{
|
||||
const evname = ev.target.getAttribute('name')
|
||||
if(evname === "delete-input"){
|
||||
ev.preventDefault()
|
||||
ev.stopPropagation()
|
||||
const index = Number(ev.target.getAttribute("data-index"))
|
||||
console.log("deleting input at index", index)
|
||||
this.deleteTmpInputAt(index)
|
||||
this.renderInputs()
|
||||
}
|
||||
})
|
||||
|
||||
this.#rootdoc.querySelector("button[name=add-output]").addEventListener("click", ev=>{
|
||||
ev.preventDefault()
|
||||
ev.stopPropagation()
|
||||
this.#tmp_outputs.push(
|
||||
{
|
||||
id : "new_output",
|
||||
title : "New ouput",
|
||||
description : "A new output field",
|
||||
minOccurs : 1,
|
||||
maxOccurs : 1,
|
||||
schema : {
|
||||
type : "string",
|
||||
contentMediaType : null
|
||||
}
|
||||
}
|
||||
)
|
||||
this.renderOutputs()
|
||||
})
|
||||
|
||||
this.#rootdoc.querySelector("button[name=add-stdout]").addEventListener("click", ev=>{
|
||||
ev.preventDefault()
|
||||
ev.stopPropagation()
|
||||
this.#tmp_outputs.push(
|
||||
{
|
||||
id : "stdout",
|
||||
title : "Standard output",
|
||||
description : "Standard output channel",
|
||||
minOccurs : 1,
|
||||
maxOccurs : 1,
|
||||
schema : {
|
||||
type : "string",
|
||||
contentMediaType : "text/plain"
|
||||
}
|
||||
}
|
||||
)
|
||||
this.renderOutputs()
|
||||
})
|
||||
|
||||
this.#rootdoc.querySelector("button[name=add-stderr]").addEventListener("click", ev=>{
|
||||
ev.preventDefault()
|
||||
ev.stopPropagation()
|
||||
this.#tmp_outputs.push(
|
||||
{
|
||||
id : "stderr",
|
||||
title : "Standard error",
|
||||
description : "Standard error channel",
|
||||
minOccurs : 1,
|
||||
maxOccurs : 1,
|
||||
schema : {
|
||||
type : "string",
|
||||
contentMediaType : "text/plain"
|
||||
}
|
||||
}
|
||||
)
|
||||
this.renderOutputs()
|
||||
})
|
||||
|
||||
this.#rootdoc.querySelector("div[name=output-list]").addEventListener("click", ev=>{
|
||||
const evname = ev.target.getAttribute('name')
|
||||
if(evname === "delete-output"){
|
||||
ev.preventDefault()
|
||||
ev.stopPropagation()
|
||||
const index = Number(ev.target.getAttribute("data-index"))
|
||||
console.log("deleting output at index", index)
|
||||
this.deleteTmpOutputAt(index)
|
||||
this.renderOutputs()
|
||||
}
|
||||
})
|
||||
|
||||
this.#rootdoc.querySelector("select[name=script-selector]").addEventListener("change", ev=>{
|
||||
ev.preventDefault()
|
||||
ev.stopPropagation()
|
||||
const scriptname = ev.target.value
|
||||
const areas = Array.prototype.slice.call(this.#rootdoc.querySelectorAll("textarea.script-area"))
|
||||
areas.forEach(a=>{
|
||||
if(a.getAttribute("name") === scriptname) a.classList.remove("d-none");
|
||||
else a.classList.add("d-none")
|
||||
})
|
||||
})
|
||||
|
||||
this.#rootdoc.querySelector("details[name=script-list]").addEventListener("input", ev=>{
|
||||
ev.preventDefault()
|
||||
ev.stopPropagation()
|
||||
const scriptname = ev.target.getAttribute("name")
|
||||
const script = this.#current.additionalParameters.parameters.filter(p=>p.name === scriptname)[0]
|
||||
if(script) script.value[0] = ev.target.value;
|
||||
})
|
||||
}
|
||||
|
||||
renderAnsibleIcon(){
|
||||
return `
|
||||
<div title="Ansible" style="width:2rem;height:2rem;display:inline-block;vertical-align:middle;position:absolute;right:0;margin-right:1rem;opacity:0.4;">
|
||||
${ this.#ansible_icon }
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
renderSaveButton(){
|
||||
return `
|
||||
<button title="Save" name="save" class="btn btn-primary ccp-toolbar-button">
|
||||
${this.#disc_icon}
|
||||
</button>
|
||||
`
|
||||
}
|
||||
|
||||
renderResetButton(){
|
||||
return `
|
||||
<button name="reset" title="Reset" class="btn btn-primary ccp-toolbar-button">
|
||||
<svg viewBox="0 0 24 24">
|
||||
${this.#erase_icon}
|
||||
</svg>
|
||||
</button>
|
||||
`
|
||||
}
|
||||
|
||||
renderPlusButton(name){
|
||||
return `
|
||||
<button name="${name}" title="Add" name="reset" class="btn btn-primary ccp-toolbar-button">
|
||||
${this.#plus_icon}
|
||||
</button>
|
||||
`
|
||||
}
|
||||
|
||||
renderStandardOutputButtons(){
|
||||
return `
|
||||
<button name="add-stdout" title="Add stdout" name="reset" class="btn btn-success ccp-toolbar-button">
|
||||
${this.#output_icon}
|
||||
</button>
|
||||
<button name="add-stderr" title="Add stderr" name="reset" class="btn btn-danger ccp-toolbar-button">
|
||||
${this.#output_icon}
|
||||
</button>
|
||||
`
|
||||
}
|
||||
|
||||
reRenderKeywords(){
|
||||
this.#rootdoc.querySelector("div[name=keyword-list]").innerHTML = this.renderKeywords()
|
||||
}
|
||||
|
||||
renderKeywords(){
|
||||
if(this.#current.keywords){
|
||||
return this.#current.keywords.map((k,i) => {
|
||||
return `
|
||||
<div class="ccp-option badge ccp-keyword">
|
||||
<span>${k}</span>
|
||||
<span class="btn text-danger ccp-toolbar-button" name="delete-keyword" data-index="${i}">x</span>
|
||||
</div>
|
||||
`
|
||||
}).join("\n")
|
||||
}else{
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
reRenderRuntimes(){
|
||||
this.#rootdoc.querySelector("select[name=runtime-input]").innerHTML = this.renderRuntimeOptions()
|
||||
this.#rootdoc.querySelector("div[name=runtime-list]").innerHTML = this.renderRuntimes()
|
||||
}
|
||||
|
||||
renderRuntimeOptions(){
|
||||
const selectedrts = this.#current.links.filter(l=>l.rel === "compatibleWith").map(r=>r.href.split("/")[1])
|
||||
const available = this.#runtimes.filter(r=>{ return selectedrts.indexOf(r.id) === -1 })
|
||||
return `
|
||||
<option></option>
|
||||
${
|
||||
available.map(rt=>{
|
||||
return `
|
||||
<option value="${rt.id}">${rt.name}</option>
|
||||
`
|
||||
}).join("\n")
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
renderRuntimes(){
|
||||
return this.#current.links.filter(l=>l.rel === "compatibleWith").map((l, i)=>{
|
||||
return `
|
||||
<div class="ccp-option badge ccp-keyword">
|
||||
<span>${l.title}</span>
|
||||
<span class="btn text-danger ccp-toolbar-button" name="delete-runtime" data-index="${i}">x</span>
|
||||
</div>
|
||||
`
|
||||
}).join("\n")
|
||||
}
|
||||
|
||||
renderInputs(){
|
||||
const parent = this.#rootdoc.querySelector("div[name=input-list]")
|
||||
parent.innerHTML = ""
|
||||
return this.#tmp_inputs.map((inp, i)=>{
|
||||
const c = document.createElement("d4s-ccp-input-editor");
|
||||
parent.appendChild(c)
|
||||
c.render(inp, i)
|
||||
})
|
||||
}
|
||||
|
||||
renderOutputs(){
|
||||
const parent = this.#rootdoc.querySelector("div[name=output-list]")
|
||||
parent.innerHTML = ""
|
||||
return this.#tmp_outputs.map((outp, i)=>{
|
||||
const c = document.createElement("d4s-ccp-output-editor");
|
||||
parent.appendChild(c)
|
||||
c.render(outp, i)
|
||||
})
|
||||
}
|
||||
|
||||
renderScripts(){
|
||||
return this.#current.additionalParameters.parameters.map(
|
||||
(script, i) => `<textarea rows="5" class="script-area form-control ${ i > 0 ? 'd-none' : ''}" name="${script.name}">${script.value[0]}</textarea>`
|
||||
).join("\n")
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define('d4s-ccp-methodeditor', CCPMethodEditorController);
|
|
@ -0,0 +1,82 @@
|
|||
class CCPOutputWidgetEditorController extends HTMLElement {
|
||||
|
||||
#output = null
|
||||
#index = null
|
||||
#type = null
|
||||
|
||||
#delete_icon = `
|
||||
<svg style="width:24px;height:24px;pointer-events: none;" 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" />
|
||||
</svg>
|
||||
`
|
||||
|
||||
constructor(){
|
||||
super()
|
||||
}
|
||||
|
||||
connectedCallback(controller){
|
||||
|
||||
}
|
||||
|
||||
get index(){
|
||||
return this.#index
|
||||
}
|
||||
|
||||
render(output, i){
|
||||
this.#index = i
|
||||
this.#output = output
|
||||
this.#type = output.schema.enum ? "enum" : "string"
|
||||
this.innerHTML = `
|
||||
<details>
|
||||
<summary class="mb-3">
|
||||
<input class="form-control" style="width:auto;display:inline" required="required" name="id" value="${this.#output.id}"/>
|
||||
<button data-index="${this.#index}" name="delete-output" title="Delete" class="btn btn-danger ccp-toolbar-button">
|
||||
${this.#delete_icon}
|
||||
</button>
|
||||
</summary>
|
||||
<div style="padding-left: 1rem;border-left: 1px solid gray;">
|
||||
<div class="row mb-3">
|
||||
<div class="col">
|
||||
<div class="form-field">
|
||||
<input name="title" class="form-control" placeholder="title" value="${this.#output.title}" required="required"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div class="form-field">
|
||||
<input name="description" class="form-control" placeholder="description" value="${this.#output.description}" required="required"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col">
|
||||
<div class="form-field">
|
||||
<input value="${this.#output.minOccurs}" type="number" min="0" step="1" name="minOccurs" class="form-control" placeholder="minOccurs"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="form-field">
|
||||
<input value="${this.#output.maxOccurs}" type="number" min="0" step="1" name="maxOccurs" class="form-control" placeholder="maxOccurs"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-3">
|
||||
<div class="form-field">
|
||||
<select name="type" class="form-control" placeholder="type">
|
||||
<option value="string">String</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="form-field">
|
||||
<input value="${this.#output.schema.contentMediaType}" name="contentMediaType" class="form-control" placeholder="mime"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
`
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define('d4s-ccp-output-editor', CCPOutputWidgetEditorController);
|
Loading…
Reference in New Issue