Compare commits
108 Commits
Author | SHA1 | Date |
---|---|---|
dcore94 | 7f36ebe941 | |
dcore94 | a522a7a1cc | |
dcore94 | b5758dadc4 | |
dcore94 | 3b6899ef1f | |
dcore94 | 224206adf7 | |
dcore94 | 74c2819de8 | |
dcore94 | 7fae770186 | |
dcore94 | 9af993ac6c | |
dcore94 | 6752659166 | |
dcore94 | 8748836d9f | |
dcore94 | f574b323ce | |
dcore94 | 85e5c9b8e7 | |
dcore94 | 94d1357ba2 | |
dcore94 | a4769a6281 | |
dcore94 | ef6ab169df | |
dcore94 | c95b62b1d4 | |
dcore94 | 09114defad | |
dcore94 | 423cfc7bed | |
dcore94 | fd858f811a | |
dcore94 | 992e085a76 | |
dcore94 | 59f76372b7 | |
dcore94 | 9426adf4eb | |
dcore94 | d7b062828b | |
dcore94 | 794443bbdd | |
dcore94 | a013536dfb | |
dcore94 | c7021a06b6 | |
dcore94 | 43154c2e2b | |
dcore94 | 593429ca51 | |
dcore94 | 592cafbb3e | |
dcore94 | 01c4d8cf07 | |
dcore94 | be39c47217 | |
dcore94 | b982194b73 | |
dcore94 | ac10c3ba34 | |
dcore94 | 8407f59013 | |
dcore94 | 3e1dd1bf6d | |
dcore94 | 0eb335cc17 | |
dcore94 | 9ffdfa9a0b | |
dcore94 | ae38f41ac7 | |
dcore94 | ec93d76998 | |
dcore94 | 5cad7c3d9a | |
dcore94 | c8116426b9 | |
dcore94 | 5694938485 | |
dcore94 | 2e55fa7510 | |
dcore94 | 16c8a6068b | |
dcore94 | 47f83901c1 | |
dcore94 | 6fc96bfa3c | |
dcore94 | b254262f58 | |
dcore94 | f7cd0932b5 | |
dcore94 | e3efc70bad | |
dcore94 | 96030670c2 | |
dcore94 | e8c71ecb55 | |
dcore94 | 4173ee650c | |
dcore94 | cc752abe40 | |
dcore94 | 78a3009573 | |
dcore94 | cc792a3e62 | |
dcore94 | 2f8f9483b8 | |
dcore94 | f4faebc9f1 | |
dcore94 | 687be71314 | |
dcore94 | 1078e7abbb | |
dcore94 | 297b2f6ffe | |
dcore94 | 05ef785cf0 | |
dcore94 | 79ec13cdc5 | |
dcore94 | ff613b70c2 | |
dcore94 | f8902077ec | |
dcore94 | d275b087b8 | |
dcore94 | 4533b43773 | |
dcore94 | 6cb7fb7cae | |
dcore94 | bcc02f680c | |
dcore94 | d4cae32fe2 | |
dcore94 | 3f02f0a1a1 | |
dcore94 | c8b31cecc7 | |
dcore94 | d4f3ed98f5 | |
dcore94 | a6517301d5 | |
dcore94 | 0aee111148 | |
dcore94 | 556168a239 | |
dcore94 | f69ee9b3e6 | |
dcore94 | 4f3a892eeb | |
dcore94 | a087e4904f | |
dcore94 | cfea15251a | |
dcore94 | 0ce705bb3f | |
dcore94 | 19d7be6ea0 | |
dcore94 | ab07310252 | |
dcore94 | a4bbbc0ccf | |
dcore94 | 1381140c49 | |
dcore94 | a9ec721892 | |
dcore94 | 125f5492c5 | |
dcore94 | ccd75c7e6e | |
dcore94 | 45ab5dccca | |
dcore94 | f57057e307 | |
dcore94 | 8c73d0c8c5 | |
dcore94 | 901abe5869 | |
dcore94 | 7f76178458 | |
dcore94 | 5476851d1f | |
dcore94 | d374f62ae0 | |
dcore94 | bfea923d4b | |
dcore94 | 801e349e80 | |
dcore94 | 4818464409 | |
dcore94 | 07382c94a7 | |
dcore94 | 28d609a011 | |
dcore94 | 00d53aed50 | |
dcore94 | 1d6fe0beb8 | |
dcore94 | b891562705 | |
dcore94 | 1d52dc9fb8 | |
dcore94 | b7bd25fa7a | |
dcore94 | 528db74d03 | |
dcore94 | 63af988a9d | |
dcore94 | 71c6d94674 | |
dcore94 | 2897f65a60 |
|
@ -21,6 +21,7 @@ window.customElements.define('d4s-boot-2', class extends HTMLElement {
|
|||
#queue = []
|
||||
#interval = null
|
||||
#config = null
|
||||
#uma = false
|
||||
#rpt = null
|
||||
|
||||
constructor() {
|
||||
|
@ -63,8 +64,8 @@ window.customElements.define('d4s-boot-2', class extends HTMLElement {
|
|||
console.log("Keycloak initialized and user authenticated")
|
||||
//console.log("Token exp: " + this.expirationDate(this.#keycloak.tokenParsed.exp))
|
||||
|
||||
//if an audience is provided then perform also authorization
|
||||
if (this.#audience) {
|
||||
//if an audience is provided and UMA flow requested then perform also authorization
|
||||
if (this.#audience && this.#uma) {
|
||||
return this.loadConfig()
|
||||
} else {
|
||||
Promise.resolve()
|
||||
|
@ -101,20 +102,24 @@ window.customElements.define('d4s-boot-2', class extends HTMLElement {
|
|||
clientId: this.#clientId
|
||||
})
|
||||
|
||||
return this.#keycloak.init({onLoad: 'login-required', checkLoginIframe: false })
|
||||
const properties = {onLoad: 'login-required', checkLoginIframe: false}
|
||||
if(this.#audience && !this.#uma){
|
||||
properties["scope"] = `d4s-context:${this.#audience}`
|
||||
}
|
||||
return this.#keycloak.init(properties)
|
||||
}
|
||||
|
||||
startStateChecker() {
|
||||
this.#interval = window.setInterval(() => {
|
||||
if (this.#locked) {
|
||||
console.log("Still locked. Currently has " + this.#queue.length + " pending requests.")
|
||||
//console.log("Still locked. Currently has " + this.#queue.length + " pending requests.")
|
||||
} else if (!this.authenticated) {
|
||||
window.alert("Not authorized!")
|
||||
} else {
|
||||
if (this.#queue.length > 0) {
|
||||
this.#keycloak.updateToken(30).then(() => {
|
||||
if (this.#audience) {
|
||||
console.log("Checking entitlement for audience", this.#audience)
|
||||
if (this.#uma && this.#audience) {
|
||||
//console.log("Checking entitlement for audience", this.#audience)
|
||||
const audience = encodeURIComponent(this.#audience)
|
||||
return this.entitlement(audience)
|
||||
} else {
|
||||
|
@ -122,16 +127,16 @@ window.customElements.define('d4s-boot-2', class extends HTMLElement {
|
|||
}
|
||||
|
||||
}).then(token => {
|
||||
console.log("Authorized. Token exp: " + this.expirationDate(this.parseJwt(token).exp))
|
||||
//console.log("Authorized. Token exp: " + this.expirationDate(this.parseJwt(token).exp))
|
||||
//transform all queued requests to fetches
|
||||
console.log("All pending requests to promises")
|
||||
//console.log("All pending requests to promises")
|
||||
let promises = this.#queue.map(r => {
|
||||
r.request.headers["Authorization"] = "Bearer " + token
|
||||
return r.resolve( fetch(r.url, r.request) )
|
||||
})
|
||||
//clear queue
|
||||
this.#queue = []
|
||||
console.log("Resolving all fetches")
|
||||
//console.log("Resolving all fetches")
|
||||
return Promise.all(promises)
|
||||
|
||||
}).catch(err => console.error("Unable to make calls: " + err)) // Sometimes throws: Unable to make calls: TypeError: Cannot read properties of undefined (reading 'split')
|
||||
|
@ -156,18 +161,19 @@ window.customElements.define('d4s-boot-2', class extends HTMLElement {
|
|||
return d
|
||||
}
|
||||
|
||||
checkContext() {
|
||||
const parseJwt = this.parseJwt
|
||||
const expDt = this.expirationDate
|
||||
const audience = encodeURIComponent(this.#audience)
|
||||
this.entitlement(audience).then(function (rpt) {
|
||||
// onGrant callback function.
|
||||
// If authorization was successful you'll receive an RPT
|
||||
// with the necessary permissions to access the resource server
|
||||
//console.log(rpt)
|
||||
//console.log("rpt expires: " + expDt(parseJwt(rpt).exp))
|
||||
})
|
||||
}
|
||||
// TODO: Candidate for removal
|
||||
// checkContext() {
|
||||
// const parseJwt = this.parseJwt
|
||||
// const expDt = this.expirationDate
|
||||
// const audience = encodeURIComponent(this.#audience)
|
||||
// this.entitlement(audience).then(function (rpt) {
|
||||
// // onGrant callback function.
|
||||
// // If authorization was successful you'll receive an RPT
|
||||
// // with the necessary permissions to access the resource server
|
||||
// //console.log(rpt)
|
||||
// //console.log("rpt expires: " + expDt(parseJwt(rpt).exp))
|
||||
// })
|
||||
// }
|
||||
|
||||
secureFetch(url, request) {
|
||||
const p = new Promise((resolve, reject) => {
|
||||
|
@ -177,7 +183,7 @@ window.customElements.define('d4s-boot-2', class extends HTMLElement {
|
|||
} else {
|
||||
req.headers = { "Authorization" : null}
|
||||
}
|
||||
console.log("Queued request to url ", url)
|
||||
//console.log("Queued request to url ", url)
|
||||
this.#queue.push({ url : url, request : req, resolve : resolve, reject : reject})
|
||||
})
|
||||
return p
|
||||
|
@ -202,7 +208,7 @@ window.customElements.define('d4s-boot-2', class extends HTMLElement {
|
|||
.then(response => response.json())
|
||||
.then(json => {
|
||||
this.#config = json
|
||||
console.log("Keycloak uma2 configuration loaded")
|
||||
//console.log("Keycloak uma2 configuration loaded")
|
||||
resolve(true)
|
||||
})
|
||||
.catch(err => reject("Failed to fetch uma2-configuration from server: " + err))
|
||||
|
@ -291,7 +297,7 @@ window.customElements.define('d4s-boot-2', class extends HTMLElement {
|
|||
}
|
||||
|
||||
static get observedAttributes() {
|
||||
return ["url", "realm", "gateway", "redirect-url", "context"];
|
||||
return ["url", "realm", "gateway", "redirect-url", "context", "uma"];
|
||||
}
|
||||
|
||||
attributeChangedCallback(name, oldValue, newValue) {
|
||||
|
@ -312,10 +318,17 @@ window.customElements.define('d4s-boot-2', class extends HTMLElement {
|
|||
case "context":
|
||||
this.#audience = newValue
|
||||
break
|
||||
case "uma":
|
||||
this.#uma = newValue === "true" ? true : false
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get uma(){
|
||||
return this.#uma
|
||||
}
|
||||
|
||||
get authenticated(){
|
||||
return this.#authenticated
|
||||
}
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<script src="https://cdn.cloud-dev.d4science.org/boot/d4s-boot.js"></script>
|
||||
<script src="js/methodlistcontroller.js"></script>
|
||||
<script src="js/methodeditorcontroller.js"></script>
|
||||
<script src="js/inputwidgeteditorcontroller.js"></script>
|
||||
<script src="js/outputwidgeteditorcontroller.js"></script>
|
||||
<script src="js/executionformcontroller.js"></script>
|
||||
<script src="js/executionhistorycontroller.js"></script>
|
||||
<script src="js/logterminalcontroller.js"></script>
|
||||
<script src="js/inputwidgetcontroller.js"></script>
|
||||
<script src="js/outputwidgetcontroller.js"></script>
|
||||
<link href="css/common.css" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
|
||||
<script src="https://cdn.cloud-dev.d4science.org/common/js/bss-min-1.2.6.js"></script>
|
||||
<script src="../storage/d4s-storage.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/ol@v9.2.4/dist/ol.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/jszip@3.10.1/dist/jszip.min.js"></script>
|
||||
</head>
|
||||
|
||||
<body class="m-4">
|
||||
<d4s-boot-2 clientid="https://next.dev.d4science.org" context="%2Fgcube%2Fdevsec%2FCCP" gateway="next.dev.d4science.org" redirect-url="http://localhost:8080/ccp/index.html" url="https://accounts.dev.d4science.org/auth"></d4s-boot-2>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<d4s-ccp-methodlist serviceurl="https://ccp.cloud-dev.d4science.org" allow-edit="true" allow-execute="true" archive="true"></d4s-ccp-methodlist>
|
||||
</div>
|
||||
<div class="col">
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||
<d4s-ccp-methodeditor serviceurl="https://ccp.cloud-dev.d4science.org"></d4s-ccp-methodeditor>
|
||||
</div>
|
||||
<div class="col">
|
||||
<d4s-ccp-executionform serviceurl="https://ccp.cloud-dev.d4science.org"></d4s-ccp-executionform>
|
||||
</div>
|
||||
<div class="col">
|
||||
<d4s-ccp-executionhistory allow-archive="true" serviceurl="https://ccp.cloud-dev.d4science.org"></d4s-ccp-executionhistory>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -4,25 +4,71 @@ class CCPExecutionForm extends HTMLElement{
|
|||
#rootdoc;
|
||||
#data;
|
||||
#method;
|
||||
#executionmonitor;
|
||||
|
||||
#serviceurl;
|
||||
#messages = {
|
||||
"en": {
|
||||
"confirm_form_overwrite": "Please confirm the overwrite of the previous execution form.",
|
||||
"Inputs": "Inputs",
|
||||
"Options": "Options",
|
||||
"Outputs": "Outputs",
|
||||
"Execute": "Execute",
|
||||
"execute_help": "Submit an execution request",
|
||||
"generate_code": "Generate code for",
|
||||
"generate_code_help": "Generate example code for a selectable programming language or runtime",
|
||||
"direct_link": "Direct link",
|
||||
"direct_link_help": "Navigate to the link for directly reopening this method in the execution form",
|
||||
"automatic_archive_option": "Select what you like to archive automatically when the execution is completed",
|
||||
"automatic_archive_provenance": "Automatically archive whole execution provenance",
|
||||
"automatic_archive_provenance_help": "Automatically archive the whole execution provenance to the workspace",
|
||||
"automatic_archive_outputs": "Automatically archive uncompressed outputs only",
|
||||
"automatic_archive_outputs_help": "Automatically archive only the outputs to the workspace",
|
||||
"automatic_archive_none": "Do not archive anything automatically",
|
||||
"automatic_archive_none_help": "Do not archive anything automatically",
|
||||
"execution_accepted": "Execution request accepted with id: ",
|
||||
"drop_method_invitation": "Drop a method here to request an execution",
|
||||
"err_execution_not_accepted": "Error. Execution has not been accepted by server",
|
||||
"err_code_generation": "Error while generating code",
|
||||
"err_load_method": "Error while loading method",
|
||||
"err_no_runtimes": "This method has no compatible runtimes available",
|
||||
"err_execute": "Error while sending execution request",
|
||||
"container_based_warning_confirm" : "The compatible architectures appear to be container based but no ccpimage input has been specified. This could cause errors that can be unpredictable on some infrastructures. Do you want to proceed anyway?"
|
||||
}
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.#boot = document.querySelector("d4s-boot-2")
|
||||
this.#serviceurl = this.getAttribute("serviceurl")
|
||||
this.#rootdoc = this.attachShadow({ "mode": "open" })
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.#serviceurl = this.getAttribute("serviceurl")
|
||||
this.connectNewExecutionRequest()
|
||||
this.render()
|
||||
|
||||
const params = new URLSearchParams(window.location.search)
|
||||
if (params.get('execution')) {
|
||||
const execution = { id: params.get('execution') }
|
||||
this.prepareFromExecution(execution)
|
||||
} else if (params.get('method')) {
|
||||
this.#method = params.get('method')
|
||||
this.loadMethod()
|
||||
} else {
|
||||
this.showMethod()
|
||||
}
|
||||
}
|
||||
|
||||
static get observedAttributes() {
|
||||
return ["method"];
|
||||
}
|
||||
|
||||
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]
|
||||
}
|
||||
|
||||
attributeChangedCallback(name, oldValue, newValue) {
|
||||
//if((oldValue != newValue) && (name === "method")){
|
||||
if (name === "method") {
|
||||
|
@ -33,7 +79,12 @@ class CCPExecutionForm extends HTMLElement{
|
|||
|
||||
connectNewExecutionRequest() {
|
||||
document.addEventListener("newexecutionrequest", ev => {
|
||||
if(window.confirm("Please confirm overwrite of execution form?")){
|
||||
if (this.#data) {
|
||||
if (window.confirm(this.getLabel("confirm_form_overwrite"))) {
|
||||
this.setAttribute("method", ev.detail)
|
||||
this.parentElement.scrollIntoViewIfNeeded()
|
||||
}
|
||||
} else {
|
||||
this.setAttribute("method", ev.detail)
|
||||
this.parentElement.scrollIntoViewIfNeeded()
|
||||
}
|
||||
|
@ -43,8 +94,10 @@ class CCPExecutionForm extends HTMLElement{
|
|||
render() {
|
||||
this.#rootdoc.innerHTML = `
|
||||
<div>
|
||||
<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">
|
||||
<link rel="canonical" href="https://getbootstrap.com/docs/5.0/components/modal/">
|
||||
<link rel="stylesheet" href="https://cdn.cloud.d4science.org/ccp/css/common.css"></link>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons"/>
|
||||
<style>
|
||||
.ccp-execution-form{
|
||||
position: relative;
|
||||
|
@ -53,12 +106,12 @@ class CCPExecutionForm extends HTMLElement{
|
|||
<template id="EXECUTION_FORM_TEMPLATE">
|
||||
<div class="ccp-execution-form" name="execution_form">
|
||||
<h5 class="ccp-method-title"></h5>
|
||||
<p class="description font-italic font-weight-lighter"></p>
|
||||
<div class="description font-italic font-weight-lighter"></div>
|
||||
<div class="plexiglass d-none"></div>
|
||||
<form name="execution_form" class="d-flex flex-column gap-3" style="gap:5px">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5>Inputs</h5>
|
||||
<h5>${this.getLabel("Inputs")}</h5>
|
||||
</div>
|
||||
<div class="card-body ccp-inputs">
|
||||
<div>
|
||||
|
@ -68,34 +121,59 @@ class CCPExecutionForm extends HTMLElement{
|
|||
</div>
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5>Outputs</h5>
|
||||
<h5>${this.getLabel("Options")}</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="form-row ccp-outputs">
|
||||
<div class="p-1">
|
||||
<label class="small text-muted p-0">${this.getLabel("automatic_archive_option")}.</label>
|
||||
<select name="archive-selector" class="form-select p-2" style="padding:2px">
|
||||
<option value="execution" title="${this.getLabel("automatic_archive_provenance_help")}">${this.getLabel("automatic_archive_provenance")}</option>
|
||||
<option value="outputs" title="${this.getLabel("automatic_archive_outputs_help")}">${this.getLabel("automatic_archive_outputs")}</option>
|
||||
<option value="none" title="${this.getLabel("automatic_archive_none_help")}">${this.getLabel("automatic_archive_none")}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5>${this.getLabel("Outputs")}</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row ccp-outputs">
|
||||
<div class="col form-group"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<button id="execute_method_button" class="btn btn-info">Execute</button>
|
||||
<button title="${this.getLabel("execute_help")}" id="execute_method_button" class="btn btn-info">${this.getLabel("Execute")}</button>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<div class="">
|
||||
<label>Generate code for:</label>
|
||||
<div class="mb-3" title="${this.getLabel("generate_code_help")}">
|
||||
<label>${this.getLabel("generate_code")}</label>
|
||||
<div class="d-flex">
|
||||
<select name="language-selector" class="form-control" style="padding:2px">
|
||||
<option value="text/python" data-ext="py" title="Generate plain Python3">Python 3</option>
|
||||
<option value="text/plain+r" data-ext="r" title="Generate plain R">R</option>
|
||||
<option value="application/vnd.jupyter+python" data-ext="ipynb" title="Generate Jupyter notebook with Python 3 cells">Jupyter Python3</option>
|
||||
<option value="text/julia" data-ext="jl" title="Generate Julia 1.9.0 code (HTTP 1.10.0, JSON3 1.13.2)">Julia 1.9.0</option>
|
||||
<option value="application/x-sh+curl" data-ext="sh" title="Generate Bash script (curl)">Bash (curl)</option>
|
||||
<option value="application/x-sh+wget" data-ext="sh" title="Generate Bash script (wget)">Bash (wget)</option>
|
||||
<option value="application/json+galaxy" data-ext="json" title="Generate JSON request for Galaxy">Galaxy CCP request (preview)</option>
|
||||
<option value="application/xml+galaxy" data-ext="xml" title="Generate installable Galaxy tool">Galaxy tool (preview)</option>
|
||||
</select>
|
||||
<button name="codegen" title="Generate code" class="btn btn-primary ccp-toolbar-button ccp-toolbar-button-small">
|
||||
<button name="codegen" class="btn btn-primary ccp-toolbar-button ccp-toolbar-button-small">
|
||||
<svg viewBox="0 96 960 960">
|
||||
<path d="M320 814 80 574l242-242 43 43-199 199 197 197-43 43Zm318 2-43-43 199-199-197-197 43-43 240 240-242 242Z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="d-flex">
|
||||
<a title="${this.getLabel("direct_link_help")}" name="direct_link_method" class="text-truncate" href="${window.location.href}">${this.getLabel("direct_link")}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
@ -103,7 +181,7 @@ class CCPExecutionForm extends HTMLElement{
|
|||
</template>
|
||||
<template id="EXECUTION_FORM_EMPTY_TEMPLATE">
|
||||
<div name="execution_form">
|
||||
<i style="padding:3rem">Drop a method here!</i>
|
||||
<i style="padding:3rem">${this.getLabel("drop_method_invitation")}!</i>
|
||||
</div>
|
||||
</template>
|
||||
<div name="execution_form"></div>
|
||||
|
@ -137,7 +215,7 @@ class CCPExecutionForm extends HTMLElement{
|
|||
(resp) => {
|
||||
if (resp.status === 200) {
|
||||
return resp.json()
|
||||
}else throw "Error retrieving process"
|
||||
} else throw this.getLabel("err_load_method")
|
||||
}
|
||||
).then(data => {
|
||||
this.#data = data
|
||||
|
@ -167,22 +245,35 @@ class CCPExecutionForm extends HTMLElement{
|
|||
this.lockRender()
|
||||
const url = this.#serviceurl + "/processes/" + this.#method + "/execution"
|
||||
const req = this.buildRequest()
|
||||
|
||||
// Perform preliminary check on the possibility of infra being container based and ccpimage missing
|
||||
if(!req.inputs["ccpimage"]){
|
||||
const reg = /docker|lxd/i
|
||||
const containerbased =
|
||||
this.#data.links.filter(l=>l.rel === "compatibleWith").
|
||||
reduce((acc, l)=>{ return acc && (l.title.match(reg) || l.href.match(reg) ? true : false)}, true)
|
||||
if(containerbased && !window.confirm(this.getLabel("container_based_warning_confirm"))){
|
||||
this.unlockRender()
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.#boot.secureFetch(
|
||||
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"
|
||||
throw this.getLabel("err_execute")
|
||||
}
|
||||
return reply.json()
|
||||
}).then(data => {
|
||||
if (data.status !== "accepted") {
|
||||
throw "Execution has not been accepted by server"
|
||||
throw this.getLabel("err_execution_not_accepted")
|
||||
}
|
||||
const event = new CustomEvent('newexecution', { detail: data.jobID });
|
||||
document.dispatchEvent(event)
|
||||
this.writeToPlexi("Execution request accepted with id: " + data.jobID)
|
||||
this.writeToPlexi(this.getLabel("execution_accepted") + " " + data.jobID)
|
||||
window.setTimeout(() => this.unlockRender(), 3000)
|
||||
}).catch(err => {alert("Unable to call execute: " + err); this.unlockRender()})
|
||||
}).catch(err => { alert(this.getLabel("err_execute") + ": " + err); this.unlockRender() })
|
||||
}
|
||||
|
||||
generateCode(mime, filename) {
|
||||
|
@ -191,7 +282,7 @@ class CCPExecutionForm extends HTMLElement{
|
|||
this.#boot.secureFetch(
|
||||
url, { method: "POST", body: JSON.stringify(req), headers: { "Content-Type": "application/json", "Accept": mime } }
|
||||
).then(reply => {
|
||||
if(reply.status !== 200) throw "Error while requesting code:";
|
||||
if (reply.status !== 200) throw this.getLabel("err_code_generation");
|
||||
return reply.blob()
|
||||
}).then(blob => {
|
||||
const objectURL = URL.createObjectURL(blob)
|
||||
|
@ -219,6 +310,17 @@ class CCPExecutionForm extends HTMLElement{
|
|||
if (o.enabled) request.outputs[o.name] = { transmissionMode: "value" };
|
||||
})
|
||||
|
||||
const archiveoption = this.#rootdoc.querySelector("select[name='archive-selector']").value
|
||||
switch (archiveoption) {
|
||||
case 'execution':
|
||||
request.subscribers = [{ successUri: "http://registry:8080/executions/archive-to-folder" }]
|
||||
break;
|
||||
case 'outputs':
|
||||
request.subscribers = [{ successUri: "http://registry:8080/executions/outputs/archive-to-folder" }]
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
return request
|
||||
}
|
||||
|
||||
|
@ -230,7 +332,7 @@ class CCPExecutionForm extends HTMLElement{
|
|||
return Array.prototype.slice.call(this.#rootdoc.querySelectorAll("d4s-ccp-output"))
|
||||
}
|
||||
|
||||
initValues(inputs){
|
||||
initInputValues(inputs) {
|
||||
Object.keys(inputs).forEach(k => {
|
||||
const w = this.#rootdoc.querySelector(`d4s-ccp-input[name=${k}]`)
|
||||
if (w) {
|
||||
|
@ -239,13 +341,45 @@ class CCPExecutionForm extends HTMLElement{
|
|||
})
|
||||
}
|
||||
|
||||
initOptionValues(request) {
|
||||
const archiveselector = this.#rootdoc.querySelector("select[name='archive-selector']")
|
||||
if (request.subscribers) {
|
||||
if (request.subscribers.filter(s => s.successUri === "http://registry:8080/executions/archive-to-folder").length === 1) {
|
||||
archiveselector.value = "execution"
|
||||
} else if (request.subscribers.filter(s => s.successUri === "http://registry:8080/executions/outputs/archive-to-folder").length === 1) {
|
||||
archiveselector.value = "outputs"
|
||||
} else {
|
||||
archiveselector.value = "none"
|
||||
}
|
||||
} else {
|
||||
archiveselector.value = "none"
|
||||
}
|
||||
}
|
||||
|
||||
prepareFromExecution(exec) {
|
||||
//if exec carries already full information then it comes from archive. Use this info instead of fetching.
|
||||
if (exec.fullrequest && exec.fullmethod && exec.fullinfrastructure) {
|
||||
this.#data = exec.fullmethod
|
||||
this.#method = this.#data.id
|
||||
this.#boot.secureFetch(this.#serviceurl + "/infrastructures/" + exec.fullinfrastructure.id)
|
||||
.then(resp => {
|
||||
if (resp.ok) {
|
||||
this.#data.executable = true
|
||||
this.showMethod()
|
||||
this.initInputValues(exec.fullrequest.inputs)
|
||||
this.initOptionValues(exec.fullrequest)
|
||||
} else throw ("Unable to find infrastructure")
|
||||
}).catch(err => alert(err))
|
||||
return
|
||||
}
|
||||
|
||||
//fetch method and request and validate infra
|
||||
let f1 =
|
||||
this.#boot.secureFetch(this.#serviceurl + `/executions/${exec.id}/metadata/method.json`)
|
||||
.then(resp => {
|
||||
if (resp.status === 200) {
|
||||
return resp.json()
|
||||
}else throw "Error retrieving process"
|
||||
} else throw this.getLabel("err_load_method")
|
||||
}
|
||||
).then(data => data)
|
||||
|
||||
|
@ -254,7 +388,7 @@ class CCPExecutionForm extends HTMLElement{
|
|||
.then(resp => {
|
||||
if (resp.status === 200) {
|
||||
return resp.json()
|
||||
}else throw "Error retrieving process"
|
||||
} else throw this.getLabel("err_load_method")
|
||||
}
|
||||
).then(data => data)
|
||||
|
||||
|
@ -270,7 +404,8 @@ class CCPExecutionForm extends HTMLElement{
|
|||
this.#data.executable = resp.status === 200
|
||||
}).then(() => {
|
||||
this.showMethod()
|
||||
this.initValues(requestdata.inputs)
|
||||
this.initInputValues(requestdata.inputs)
|
||||
this.initOptionValues(requestdata)
|
||||
}).catch(err => alert(err))
|
||||
}
|
||||
|
||||
|
@ -329,8 +464,24 @@ class CCPExecutionForm extends HTMLElement{
|
|||
`
|
||||
},
|
||||
{
|
||||
target: "p.description",
|
||||
apply : (e,d)=>e.textContent = d.description
|
||||
target: "div.description",
|
||||
apply: (e, d) => {
|
||||
const maxchars = 200
|
||||
const maxlines = 4
|
||||
const s = d.description
|
||||
const lines = s.split("\n")
|
||||
if (lines.length <= maxlines && s.length <= maxchars) {
|
||||
e.innerHTML = s.replaceAll("\n", "<br/>")
|
||||
} else if (lines.length > maxlines) {
|
||||
const lines1 = lines.slice(0, maxlines -1)
|
||||
const index = Math.min(maxchars, lines1.join("<br/>").length)
|
||||
const sf = s.replaceAll("\n", "<br/>")
|
||||
e.innerHTML = `<details><summary>${sf.substring(0, index)}...</summary>...${sf.substring(index)}</details>`
|
||||
} else {
|
||||
const sf = s.replaceAll("\n", "<br/>")
|
||||
e.innerHTML = `<details><summary>${sf.substring(0, maxchars)}...</summary>...${sf.substring(maxchars)}</details>`
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
target: "div.ccp-inputs",
|
||||
|
@ -340,7 +491,7 @@ class CCPExecutionForm extends HTMLElement{
|
|||
"in": (e, d) => { return Object.values(d.inputs) },
|
||||
target: "div",
|
||||
apply: (e, d) => {
|
||||
e.innerHTML = `<d4s-ccp-input name="${d.id}" input='${JSON.stringify(d)}'></d4s-ccp-input>`
|
||||
e.innerHTML = `<d4s-ccp-input name="${d.id}" input='${btoa(JSON.stringify(d))}'></d4s-ccp-input>`
|
||||
}
|
||||
}
|
||||
]
|
||||
|
@ -353,7 +504,7 @@ class CCPExecutionForm extends HTMLElement{
|
|||
"in": (e, d) => { return Object.values(d.outputs) },
|
||||
target: "div",
|
||||
apply: (e, d) => {
|
||||
e.innerHTML = `<d4s-ccp-output name="${d.id}" output='${JSON.stringify(d)}'></d4s-ccp-output>`
|
||||
e.innerHTML = `<d4s-ccp-output name="${d.id}" output='${btoa(JSON.stringify(d))}'></d4s-ccp-output>`
|
||||
}
|
||||
}
|
||||
]
|
||||
|
@ -370,7 +521,7 @@ class CCPExecutionForm extends HTMLElement{
|
|||
ev.preventDefault()
|
||||
ev.stopPropagation()
|
||||
if (this.#data.executable) this.sendExecutionRequest();
|
||||
else alert("This method has no compatible runtimes available")
|
||||
else alert(this.getLabel("err_no_runtimes"))
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
@ -386,6 +537,10 @@ class CCPExecutionForm extends HTMLElement{
|
|||
return false
|
||||
}
|
||||
},
|
||||
{
|
||||
target: "a[name=direct_link_method]",
|
||||
apply: (e, d) => e.href = window.location.origin + window.location.pathname + "?method=" + d.id
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ class CCPExecutionHistory extends HTMLElement {
|
|||
#boot = null;
|
||||
#rootdoc = null;
|
||||
#serviceurl = null;
|
||||
#wsurl = null;
|
||||
#broadcasturl = null;
|
||||
#archive = null;
|
||||
#data = [];
|
||||
|
@ -11,34 +12,97 @@ class CCPExecutionHistory extends HTMLElement {
|
|||
#socket = null;
|
||||
#interval = null;
|
||||
#searchfield = null;
|
||||
#fileupload = null;
|
||||
#archiveupload = 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("archive")
|
||||
this.connectNewExecution()
|
||||
}
|
||||
this.#archive = this.getAttribute("allow-archive") && this.getAttribute("allow-archive").toLowerCase() === "true"
|
||||
|
||||
connectedCallback(){
|
||||
const iss = document.querySelector("d4s-boot-2").getAttribute("url");
|
||||
const addresses = {
|
||||
"https://accounts.dev.d4science.org/auth": "https://workspace-repository.dev.d4science.org/storagehub/workspace",
|
||||
"https://accounts.pre.d4science.org/auth": "https://pre.d4science.org/workspace",
|
||||
"https://accounts.d4science.org/auth": "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 = `
|
||||
<link rel="stylesheet" href="https://cdn.dev.d4science.org/ccp/css/common.css"></link>
|
||||
<link rel="stylesheet" href="https://cdn.cloud.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>
|
||||
.galaxy{
|
||||
background-color: #2c3143;
|
||||
color: yellow;
|
||||
}
|
||||
.lxd{
|
||||
background-color: #dd4814;
|
||||
color: white;
|
||||
|
@ -64,10 +128,10 @@ class CCPExecutionHistory extends HTMLElement {
|
|||
<summary class="ccp-method-item-header noselect d-flex flex-wrap justify-content-between">
|
||||
<h5 class="text-primary d-inline"></h5>
|
||||
<div>
|
||||
<span name="failed" title="Failed executions" class="badge badge-danger float-right">Z</span>
|
||||
<span name="successful" title="Successful executions"class="badge badge-success float-right mr-1">Y</span>
|
||||
<span name="running" title="Running executions"class="badge badge-primary float-right mr-1">Y</span>
|
||||
<span name="accepted" title="Accepted executions" class="badge badge-secondary float-right mr-1">X</span>
|
||||
<span name="failed" title="${this.getLabel("failed_count_help")}" class="badge badge-danger float-right">Z</span>
|
||||
<span name="successful" title="${this.getLabel("successful_count_help")}"class="badge badge-success float-right mr-1">Y</span>
|
||||
<span name="running" title="${this.getLabel("running_count_help")}"class="badge badge-primary float-right mr-1">Y</span>
|
||||
<span name="accepted" title="${this.getLabel("accepted_count_help")}" class="badge badge-secondary float-right mr-1">X</span>
|
||||
</div>
|
||||
</summary>
|
||||
<ul class="ccp-execution-list list-group" style="list-style:none">
|
||||
|
@ -78,24 +142,24 @@ class CCPExecutionHistory extends HTMLElement {
|
|||
<span name="status" class="ml-1 badge"></span>
|
||||
<div class="d-flex float-right" style="gap: 3px 5px; max-width: 40%; min-width:60px; flex-wrap:wrap;">
|
||||
${ this.#archive ? `
|
||||
<button data-index="0" name="archive" title="Archive to workspace" class="btn btn-primary ccp-toolbar-button ccp-toolbar-button-small">
|
||||
<button data-index="0" name="archive" title="${this.getLabel("archive_execution_help")}" class="btn btn-primary ccp-toolbar-button ccp-toolbar-button-small">
|
||||
<svg viewBox="0 96 960 960"><path d="M140 796h680V516H140v280Zm540.118-90Q701 706 715.5 691.382q14.5-14.617 14.5-35.5Q730 635 715.382 620.5q-14.617-14.5-35.5-14.5Q659 606 644.5 620.618q-14.5 14.617-14.5 35.5Q630 677 644.618 691.5q14.617 14.5 35.5 14.5ZM880 456h-85L695 356H265L165 456H80l142-142q8-8 19.278-13 11.278-5 23.722-5h430q12.444 0 23.722 5T738 314l142 142ZM140 856q-24.75 0-42.375-17.625T80 796V456h800v340q0 24.75-17.625 42.375T820 856H140Z"/></svg>
|
||||
</button>`
|
||||
: ``
|
||||
}
|
||||
<button data-index="0" name="provo" title="Export to Prov-o document" class="btn btn-primary ccp-toolbar-button ccp-toolbar-button-small">
|
||||
<!--button data-index="0" name="provo" title="Export to Prov-o document" class="btn btn-primary ccp-toolbar-button ccp-toolbar-button-small">
|
||||
<svg viewBox="0 0 24 24"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M13 3H6v18h4v-6h3c3.31 0 6-2.69 6-6s-2.69-6-6-6zm.2 8H10V7h3.2c1.1 0 2 .9 2 2s-.9 2-2 2z"/></svg>
|
||||
</button>
|
||||
</button-->
|
||||
<!-- button data-index="0" name="zip" title="Download as zip archive" class="btn btn-primary ccp-toolbar-button ccp-toolbar-button-small">
|
||||
<svg viewBox="0 0 48 48"><path d="M7 40q-1.15 0-2.075-.925Q4 38.15 4 37V11q0-1.15.925-2.075Q5.85 8 7 8h14l3 3h17q1.15 0 2.075.925Q44 12.85 44 14v23q0 1.15-.925 2.075Q42.15 40 41 40Zm25-3h9V14h-9v4.6h4.6v4.6H32v4.6h4.6v4.6H32ZM7 37h20.4v-4.6H32v-4.6h-4.6v-4.6H32v-4.6h-4.6V14h-4.65l-3-3H7v26Zm0-23v-3 26-23Z"/></svg>
|
||||
</button-->
|
||||
<button data-index="0" name="archivefolder" title="Archive outputs to workspace" class="btn btn-primary ccp-toolbar-button ccp-toolbar-button-small">
|
||||
<button data-index="0" name="archiveoutputs" title="${this.getLabel("archive_outputs_help")}" class="btn btn-primary ccp-toolbar-button ccp-toolbar-button-small">
|
||||
<svg viewBox="0 0 48 48"><path d="M7 40q-1.15 0-2.075-.925Q4 38.15 4 37V11q0-1.15.925-2.075Q5.85 8 7 8h14l3 3h17q1.15 0 2.075.925Q44 12.85 44 14v23q0 1.15-.925 2.075Q42.15 40 41 40Zm25-3h9V14h-9v4.6h4.6v4.6H32v4.6h4.6v4.6H32ZM7 37h20.4v-4.6H32v-4.6h-4.6v-4.6H32v-4.6h-4.6V14h-4.65l-3-3H7v26Zm0-23v-3 26-23Z"/></svg>
|
||||
</button>
|
||||
<button data-index="0" name="reexecute1" title="Re-submit this execution" class="btn btn-info ccp-toolbar-button ccp-toolbar-button-small">
|
||||
Re-submit
|
||||
<button data-index="0" name="reexecute1" title="${this.getLabel("re-submit_help")}" class="btn btn-info ccp-toolbar-button ccp-toolbar-button-small">
|
||||
${this.getLabel("re-submit")}
|
||||
</button>
|
||||
<button data-index="0" name="delete" title="Delete" class="btn btn-danger ccp-toolbar-button ccp-toolbar-button-small">
|
||||
<button data-index="0" name="delete" title="${this.getLabel("delete_help")}" class="btn btn-danger ccp-toolbar-button ccp-toolbar-button-small">
|
||||
<svg 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"></path>
|
||||
</svg>
|
||||
|
@ -111,27 +175,37 @@ class CCPExecutionHistory extends HTMLElement {
|
|||
</p>
|
||||
</summary>
|
||||
<div class="d-flex flex-wrap" style="gap:3px; overflow:hidden">
|
||||
<span style="text-overflow:ellipsis" class="badge badge-light text-info border border-info" name="infrastructure" alt="Infrastructure" title="Infrastructure"></span>
|
||||
<span class="badge" name="runtime" alt="Runtime" title="Runtime"></span>
|
||||
<span style="text-overflow:ellipsis" class="badge badge-light text-info border border-info" name="infrastructure" alt="${this.getLabel("infrastructure_help")}" title="${this.getLabel("infrastructure_help")}"></span>
|
||||
<span class="badge" name="runtime" alt="${this.getLabel("runtime_help")}" title="${this.getLabel("runtime_help")}"></span>
|
||||
</div>
|
||||
<div name="logterminalcontainer" style="margin:5px 0 5px 0">
|
||||
</div>
|
||||
<ul>
|
||||
<li></li>
|
||||
</ul>
|
||||
<div class="d-flex justify-content-end" style="gap: 3px;">
|
||||
<label>Generate code for:</label>
|
||||
<select name="language-selector" class="form-control" style="max-width:10rem;height:inherit;padding:2px">
|
||||
<div class="d-flex flex-column align-items-end" style="gap: 3px;">
|
||||
<div class="d-flex align-items-center" style="gap:5px" title="${this.getLabel("generate_code_help")}">
|
||||
<label style="text-wrap:nowrap">${this.getLabel("generate_code")}</label>
|
||||
<select name="language-selector" class="form-control">
|
||||
<option value="text/python" data-ext="py" title="Generate plain Python3">Python 3</option>
|
||||
<option value="text/plain+r" data-ext="r" title="Generate plain R">R</option>
|
||||
<option value="application/vnd.jupyter+python" data-ext="ipynb" title="Generate Jupyter notebook with Python 3 cells">Jupyter Python3</option>
|
||||
<option value="text/julia" data-ext="jl" title="Generate Julia 1.9.0 code (HTTP 1.10.0, JSON3 1.13.2)">Julia 1.9.0</option>
|
||||
<option value="application/x-sh+curl" data-ext="sh" title="Generate Bash script (curl)">Bash (curl)</option>
|
||||
<option value="application/x-sh+wget" data-ext="sh" title="Generate Bash script (wget)">Bash (wget)</option>
|
||||
</select>
|
||||
<button data-index="0" name="codegen" title="Generate code" class="btn btn-primary ccp-toolbar-button ccp-toolbar-button-small">
|
||||
<button data-index="0" name="codegen" title="${this.getLabel("generate_code_help")}" class="btn btn-primary ccp-toolbar-button ccp-toolbar-button-small">
|
||||
<svg viewBox="0 96 960 960">
|
||||
<path d="m384 721 43-43-101-102 101-101-43-43-144 144.5L384 721Zm192 0 145-145-144-144-43 43 101 101-102 102 43 43ZM180 936q-24.75 0-42.375-17.625T120 876V276q0-24.75 17.625-42.375T180 216h205q5-35 32-57.5t63-22.5q36 0 63 22.5t32 57.5h205q24.75 0 42.375 17.625T840 276v600q0 24.75-17.625 42.375T780 936H180Zm0-60h600V276H180v600Zm300-617q14 0 24.5-10.5T515 224q0-14-10.5-24.5T480 189q-14 0-24.5 10.5T445 224q0 14 10.5 24.5T480 259ZM180 876V276v600Z"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="d-flex align-items-middle" style="gap:5px">
|
||||
<div class="d-flex">
|
||||
<a title="${this.getLabel("direct_link_help")}" class="text-truncate" name="direct_link_execution" href="${window.location.href}">${this.getLabel("direct_link")}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
</li>
|
||||
</ul>
|
||||
|
@ -139,28 +213,73 @@ class CCPExecutionHistory extends HTMLElement {
|
|||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
<template id="ARCHIVED_EXECUTION_LIST_TEMPLATE">
|
||||
<ul name="ccp_archived_execution_list" class="ccp-execution-list list-group border border-2">
|
||||
<li class="ccp-method-item ccp-archived-execution-method-item list-group-item list-group-item-light">
|
||||
<details name="level1">
|
||||
<summary class="ccp-method-item-header noselect d-flex flex-wrap justify-content-between">
|
||||
<h5 class="text-primary d-inline"></h5>
|
||||
</summary>
|
||||
<ul class="ccp-execution-list list-group" style="list-style:none">
|
||||
<li class="ccp-execution-item ccp-archived-execution-item list-group-item-secondary my-2 p-2" draggable="true">
|
||||
<span name="version" class="badge badge-primary"></span>
|
||||
<span name="status" class="ml-1 badge"></span>
|
||||
<div class="d-flex float-right" style="gap: 3px 5px;flex-wrap:wrap;">
|
||||
<button data-index="0" name="reexecute2" title="${this.getLabel("re-submit_help")}" class="btn btn-info ccp-toolbar-button ccp-toolbar-button-small">
|
||||
${this.getLabel("re-submit")}
|
||||
</button>
|
||||
<button name="restore" title="${this.getLabel("restore_execution_help")}" class="btn btn-primary ccp-toolbar-button ccp-toolbar-button-small">
|
||||
<svg viewBox="0 -960 960 960" width="24px" fill="#5f6368">
|
||||
<path d="M480-250q78 0 134-56t56-134q0-78-56-134t-134-56q-38 0-71 14t-59 38v-62h-60v170h170v-60h-72q17-18 41-29t51-11q54 0 92 38t38 92q0 54-38 92t-92 38q-44 0-77-25.5T356-400h-62q14 65 65.5 107.5T480-250ZM240-80q-33 0-56.5-23.5T160-160v-640q0-33 23.5-56.5T240-880h320l240 240v480q0 33-23.5 56.5T720-80H240Zm0-80h480v-446L526-800H240v640Zm0 0v-640 640Z"/>
|
||||
</svg>
|
||||
</button>
|
||||
<button name="delete" title="${this.getLabel("delete_help")}" class="btn btn-danger ccp-toolbar-button ccp-toolbar-button-small">
|
||||
<svg 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"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<p name="createstart" class="font-weight-light font-italic" style="margin-top:revert">
|
||||
<span name="created"></span>.
|
||||
<span name="started" class="ml-1"></span>
|
||||
</p>
|
||||
<p name="message" class="font-weight-light font-italic" style="margin-top:revert">
|
||||
<span name="updated"></span>:
|
||||
<span name="message" class="ml-1"></span>
|
||||
</p>
|
||||
<div class="d-flex flex-wrap" style="gap:3px; overflow:hidden">
|
||||
<span style="text-overflow:ellipsis" class="badge badge-light text-info border border-info" name="infrastructure" alt="${this.getLabel("infrastructure_help")}" title="${this.getLabel("infrastructure_help")}"></span>
|
||||
<span class="badge" name="runtime" alt="${this.getLabel("runtime_help")}" title="${this.getLabel("runtime_help")}"></span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</details>
|
||||
</li>
|
||||
</ul>
|
||||
</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">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div class="ccp-toolbar-header d-flex flex-wrap justify-content-between">
|
||||
<div>
|
||||
<span name="header">Execution Monitor</span>
|
||||
<span name="header">${this.getLabel("execution_monitor")}</span>
|
||||
</div>
|
||||
<div class="d-flex flex-wrap" style="gap:2px">
|
||||
<button name="refresh" class="btn btn-primary ccp-toolbar-button" title="Refresh">
|
||||
<button name="refresh" class="btn btn-primary ccp-toolbar-button" title="${this.getLabel("refresh_help")}">
|
||||
<svg viewBox="0 0 48 48"><path d="M24 40q-6.65 0-11.325-4.675Q8 30.65 8 24q0-6.65 4.675-11.325Q17.35 8 24 8q4.25 0 7.45 1.725T37 14.45V8h3v12.7H27.3v-3h8.4q-1.9-3-4.85-4.85Q27.9 11 24 11q-5.45 0-9.225 3.775Q11 18.55 11 24q0 5.45 3.775 9.225Q18.55 37 24 37q4.15 0 7.6-2.375 3.45-2.375 4.8-6.275h3.1q-1.45 5.25-5.75 8.45Q29.45 40 24 40Z"/></svg>
|
||||
</button>
|
||||
<label name="fileupload" class="btn btn-primary ccp-toolbar-button m-0" title="Upload from file">
|
||||
<!--label name="fileupload" class="btn btn-primary ccp-toolbar-button m-0" title="${this.getLabel("import_file_help")}">
|
||||
<svg viewBox="0 96 960 960"><path d="M452 854h60V653l82 82 42-42-156-152-154 154 42 42 84-84v201ZM220 976q-24 0-42-18t-18-42V236q0-24 18-42t42-18h361l219 219v521q0 24-18 42t-42 18H220Zm331-554V236H220v680h520V422H551ZM220 236v186-186 680-680Z"/></svg>
|
||||
<input type="file" class="d-none" multiple="multiple"/>
|
||||
</label>
|
||||
<div class="d-flex" style="gap:2px">
|
||||
<input type="text" class="form-control" placeholder="Paste link here"/>
|
||||
<button name="archive" class="btn btn-primary ccp-toolbar-button m-0" title="Upload from link">
|
||||
<svg viewBox="0 96 960 960"><path d="M450 776H280q-83 0-141.5-58.5T80 576q0-83 58.5-141.5T280 376h170v60H280q-58.333 0-99.167 40.765-40.833 40.764-40.833 99Q140 634 180.833 675q40.834 41 99.167 41h170v60ZM324 606v-60h310v60H324Zm556-30h-60q0-58-40.833-99-40.834-41-99.167-41H510v-60h170q83 0 141.5 58.5T880 576ZM699 896V776H579v-60h120V596h60v120h120v60H759v120h-60Z"/></svg>
|
||||
|
||||
<input type="text" class="form-control" placeholder="${this.getLabel("paste_link")}"/>
|
||||
<button name="archive" class="btn btn-primary ccp-toolbar-button m-0" title="${this.getLabel("import_link_help")}">
|
||||
<svg viewBox="0 96 960 960">
|
||||
<path d="M450 776H280q-83 0-141.5-58.5T80 576q0-83 58.5-141.5T280 376h170v60H280q-58.333 0-99.167 40.765-40.833 40.764-40.833 99Q140 634 180.833 675q40.834 41 99.167 41h170v60ZM324 606v-60h310v60H324Zm556-30h-60q0-58-40.833-99-40.834-41-99.167-41H510v-60h170q83 0 141.5 58.5T880 576ZM699 896V776H579v-60h120V596h60v120h120v60H759v120h-60Z"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -168,14 +287,38 @@ class CCPExecutionHistory extends HTMLElement {
|
|||
<div class="mb-3">
|
||||
<input accept="application/zip" type="text" name="search" class="form-control" placeholder="Search"/>
|
||||
</div>
|
||||
<div>
|
||||
${
|
||||
this.#archive ? `
|
||||
<ul id="switch_executions" class="nav nav-tabs">
|
||||
<li class="nav-item">
|
||||
<a name="live_executions" class="nav-link active" href="#">${this.getLabel("live_executions")}</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a name="archived_executions" class="nav-link" href="#">${this.getLabel("archived_executions")}</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane fade show active" id="live_executions">
|
||||
<ul name="ccp_execution_list"></ul>
|
||||
</div>
|
||||
<div class="tab-pane fade" id="archived_executions">
|
||||
<ul name="ccp_archived_execution_list"></ul>
|
||||
</div>
|
||||
</div>
|
||||
` : `
|
||||
<ul name="ccp_execution_list"></ul>
|
||||
`
|
||||
}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
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]")
|
||||
|
@ -183,24 +326,55 @@ class CCPExecutionHistory extends HTMLElement {
|
|||
this.updateList()
|
||||
})
|
||||
|
||||
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)
|
||||
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.#archiveupload = this.#rootdoc.querySelector("button[name=archive]")
|
||||
this.#archiveupload.addEventListener("click", ev=>{
|
||||
const link = ev.target.parentElement.querySelector("input").value
|
||||
if(link){
|
||||
if(confirm("Please confirm importing of execution from link?")){
|
||||
this.fromArchive(link)
|
||||
// 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(){
|
||||
|
@ -365,11 +539,27 @@ class CCPExecutionHistory extends HTMLElement {
|
|||
}).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 "Unable to re-execute. Check console."
|
||||
throw this.getLabel("err_reexecute")
|
||||
}
|
||||
return reply.json()
|
||||
}).then(data=>{
|
||||
|
@ -400,7 +590,7 @@ class CCPExecutionHistory extends HTMLElement {
|
|||
this.#boot.secureFetch(`${this.#serviceurl}/executions/${id}/code`,
|
||||
{ method: "GET", headers : { "Accept" : mime} }).then(reply =>{
|
||||
if (!reply.ok) {
|
||||
throw "Unable to generate code for " + mime
|
||||
throw this.getLabel("err_generate_code_for") + mime
|
||||
}
|
||||
return reply.blob()
|
||||
}).then(blob => {
|
||||
|
@ -414,30 +604,30 @@ class CCPExecutionHistory extends HTMLElement {
|
|||
}).catch(err=>{ alert(err)})
|
||||
}
|
||||
|
||||
toArchive(id){
|
||||
this.#boot.secureFetch(`${this.#serviceurl}/executions/${id}/archive`, { method: "POST" })
|
||||
.then(reply =>{
|
||||
if (!reply.ok) {
|
||||
throw "Unable to archive"
|
||||
}
|
||||
}).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"
|
||||
throw "Unable to archive execution to folder"
|
||||
}
|
||||
}).catch(err=>{ alert(err)})
|
||||
}
|
||||
|
||||
fromArchive(url){
|
||||
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"
|
||||
throw "Unable to fetch from archive folder"
|
||||
}
|
||||
return reply.text()
|
||||
}).then(data=>{
|
||||
|
@ -446,6 +636,104 @@ class CCPExecutionHistory extends HTMLElement {
|
|||
}
|
||||
}
|
||||
|
||||
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 = `
|
||||
<div class="d-flex justify-content-center" title="${this.getLabel("loading_archived_help")}">
|
||||
<style>
|
||||
.spinning {
|
||||
animation:spin 4s linear infinite;
|
||||
}
|
||||
@keyframes spin {
|
||||
100% {
|
||||
-webkit-transform: rotate(-360deg);
|
||||
transform:rotate(-360deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<svg class="spinning" style="width:10%" viewBox="0 -960 960 960" width="24px" fill="#5f6368"><path d="M160-160v-80h110l-16-14q-52-46-73-105t-21-119q0-111 66.5-197.5T400-790v84q-72 26-116 88.5T240-478q0 45 17 87.5t53 78.5l10 10v-98h80v240H160Zm400-10v-84q72-26 116-88.5T720-482q0-45-17-87.5T650-648l-10-10v98h-80v-240h240v80H690l16 14q49 49 71.5 106.5T800-482q0 111-66.5 197.5T560-170Z"/></svg>
|
||||
<div>
|
||||
`
|
||||
}
|
||||
|
||||
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]",
|
||||
|
@ -463,7 +751,7 @@ class CCPExecutionHistory extends HTMLElement {
|
|||
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("Please confirm import of execution files?")){
|
||||
if(confirm(this.getLabel("confirm_import_file") + "?")){
|
||||
this.importExecutions(files)
|
||||
}
|
||||
}
|
||||
|
@ -524,7 +812,7 @@ class CCPExecutionHistory extends HTMLElement {
|
|||
},
|
||||
on_click: ev=>{
|
||||
if(ev.target.getAttribute("name") === "delete"){
|
||||
if(window.confirm("Please confirm deletion of this execution?")){
|
||||
if(window.confirm(this.getLabel("confirm_delete_execution"))){
|
||||
const id = ev.currentTarget.getAttribute("data-index")
|
||||
this.deleteExecution(id)
|
||||
}
|
||||
|
@ -538,23 +826,23 @@ class CCPExecutionHistory extends HTMLElement {
|
|||
this.export(id, "application/prov-o+xml", id + ".xml")
|
||||
}
|
||||
if(ev.target.getAttribute("name") === "reexecute1"){
|
||||
if(window.confirm("Please confirm re-execution?")){
|
||||
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(" Please confirm archiving of execution to workspace?")){
|
||||
const id = ev.currentTarget.getAttribute("data-index")
|
||||
this.toArchive(id)
|
||||
}
|
||||
}
|
||||
if(ev.target.getAttribute("name") === "archivefolder"){
|
||||
if(confirm(" Please confirm archiving of execution outputs to workspace?")){
|
||||
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 : [
|
||||
{
|
||||
|
@ -642,7 +930,8 @@ class CCPExecutionHistory extends HTMLElement {
|
|||
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
|
||||
e.classList.add(t2)
|
||||
const t3 = !t2 && infratype.match(/galaxy/i) ? "galaxy" : t
|
||||
e.classList.add(t3)
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -656,6 +945,10 @@ class CCPExecutionHistory extends HTMLElement {
|
|||
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)=>{
|
||||
|
@ -689,5 +982,150 @@ class CCPExecutionHistory extends HTMLElement {
|
|||
}
|
||||
]
|
||||
}
|
||||
|
||||
#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);
|
||||
|
|
|
@ -7,7 +7,7 @@ class CCPInfrastructureList extends HTMLElement{
|
|||
#rootdoc;
|
||||
|
||||
#style = `
|
||||
<link rel="stylesheet" href="https://cdn.dev.d4science.org/ccp/css/common.css"></link>
|
||||
<link rel="stylesheet" href="https://cdn.cloud.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"></link>
|
||||
<style>
|
||||
.ccp-infrastructure-list{
|
||||
|
|
|
@ -7,42 +7,67 @@ class CCPInputWidgetController extends HTMLElement {
|
|||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.#data = JSON.parse(this.getAttribute("input"))
|
||||
this.#data = JSON.parse(atob(this.getAttribute("input")))
|
||||
|
||||
if (this.isChecklist()) {
|
||||
|
||||
const opts = this.#data.schema.enum.join(",")
|
||||
this.innerHTML += `<d4s-ccp-input-checklist readonly="${this.#data.schema.readOnly}" options="${opts}" default="${this.#data.schema.default||''}" maxOccurs="${this.#data.maxOccurs||1}" minOccurs="${this.#data.minOccurs||1}" name="${this.#data.id}" description="${this.#data.description}" title="${this.#data.title}"></d4s-ccp-input-checklist>`
|
||||
this.innerHTML += `<d4s-ccp-input-checklist readonly="${this.#data.schema.readOnly}" options="${opts}" default="${this.#data.schema.default}" maxOccurs="${this.#data.maxOccurs}" minOccurs="${this.#data.minOccurs}" name="${this.#data.id}" description="${btoa(this.#data.description)}" title="${this.#data.title}"></d4s-ccp-input-checklist>`
|
||||
this.querySelector("d4s-ccp-input-checklist").default = this.#data.schema.default
|
||||
|
||||
} else if (this.isEnum()) {
|
||||
|
||||
const opts = this.#data.schema.enum.join(",")
|
||||
this.innerHTML += `<d4s-ccp-input-enum readonly="${this.#data.schema.readOnly}" options="${opts}" default="${this.#data.schema.default||''}" maxOccurs="${this.#data.maxOccurs||1}" minOccurs="${this.#data.minOccurs||1}" name="${this.#data.id}" description="${this.#data.description}" title="${this.#data.title}"></d4s-ccp-input-enum>`
|
||||
this.innerHTML += `<d4s-ccp-input-enum readonly="${this.#data.schema.readOnly}" options="${opts}" default="${this.#data.schema.default}" maxOccurs="${this.#data.maxOccurs}" minOccurs="${this.#data.minOccurs}" name="${this.#data.id}" description="${btoa(this.#data.description)}" title="${this.#data.title}"></d4s-ccp-input-enum>`
|
||||
this.querySelector("d4s-ccp-input-enum").default = this.#data.schema.default
|
||||
|
||||
} else if (this.isCode()) {
|
||||
this.innerHTML += `<d4s-ccp-input-textarea readonly="${this.#data.schema.readOnly}" default="${this.#data.schema.default||''}" maxOccurs="${this.#data.maxOccurs||1}" minOccurs="${this.#data.minOccurs||1}" name="${this.#data.id}" description="${this.#data.description}" title="${this.#data.title}"></d4s-ccp-input-textarea>`
|
||||
|
||||
this.innerHTML += `<d4s-ccp-input-textarea readonly="${this.#data.schema.readOnly}" default="${this.#data.schema.default}" maxOccurs="${this.#data.maxOccurs}" minOccurs="${this.#data.minOccurs}" name="${this.#data.id}" description="${btoa(this.#data.description)}" title="${this.#data.title}"></d4s-ccp-input-textarea>`
|
||||
this.querySelector("d4s-ccp-input-textarea").default = this.#data.schema.default
|
||||
|
||||
} else if (this.isDateTime()) {
|
||||
|
||||
const t = this.#data.schema.format.toLowerCase() === "datetime" ?
|
||||
"datetime-local" : this.#data.schema.format.toLowerCase()
|
||||
this.innerHTML += `<d4s-ccp-input-simple type="${t}" readonly="${this.#data.schema.readOnly}" default="${this.#data.schema.default||''}" maxOccurs="${this.#data.maxOccurs||1}" minOccurs="${this.#data.minOccurs||1}" name="${this.#data.id}" description="${this.#data.description}" title="${this.#data.title}"></d4s-ccp-input-simple>`
|
||||
this.innerHTML += `<d4s-ccp-input-simple type="${t}" readonly="${this.#data.schema.readOnly}" default="${this.#data.schema.default}" maxOccurs="${this.#data.maxOccurs}" minOccurs="${this.#data.minOccurs}" name="${this.#data.id}" description="${btoa(this.#data.description)}" title="${this.#data.title}"></d4s-ccp-input-simple>`
|
||||
this.querySelector("d4s-ccp-input-simple").default = this.#data.schema.default
|
||||
|
||||
} else if (this.isFile()) {
|
||||
this.innerHTML += `<d4s-ccp-input-file readonly="${this.#data.schema.readOnly}" default="${this.#data.schema.default||''}" maxOccurs="${this.#data.maxOccurs||1}" minOccurs="${this.#data.minOccurs||1}" name="${this.#data.id}" description="${this.#data.description}" title="${this.#data.title}"></d4s-ccp-input-file>`
|
||||
|
||||
this.innerHTML += `<d4s-ccp-input-file readonly="${this.#data.schema.readOnly}" default="${this.#data.schema.default}" maxOccurs="${this.#data.maxOccurs}" minOccurs="${this.#data.minOccurs}" name="${this.#data.id}" description="${btoa(this.#data.description)}" title="${this.#data.title}"></d4s-ccp-input-file>`
|
||||
this.querySelector("d4s-ccp-input-file").default = this.#data.schema.default
|
||||
|
||||
} else if (this.isRemoteFile()) {
|
||||
this.innerHTML += `<d4s-ccp-input-remotefile readonly="${this.#data.schema.readOnly}" default="${this.#data.schema.default||''}" maxOccurs="${this.#data.maxOccurs||1}" minOccurs="${this.#data.minOccurs||1}" name="${this.#data.id}" description="${this.#data.description}" title="${this.#data.title}"></d4s-ccp-input-remotefile>`
|
||||
|
||||
this.innerHTML += `<d4s-ccp-input-remotefile readonly="${this.#data.schema.readOnly}" default="${this.#data.schema.default}" maxOccurs="${this.#data.maxOccurs}" minOccurs="${this.#data.minOccurs}" name="${this.#data.id}" description="${btoa(this.#data.description)}" title="${this.#data.title}"></d4s-ccp-input-remotefile>`
|
||||
this.querySelector("d4s-ccp-input-remotefile").default = this.#data.schema.default
|
||||
|
||||
} else if (this.isGeo()) {
|
||||
|
||||
this.innerHTML += `<d4s-ccp-input-geo readonly="${this.#data.schema.readOnly}" default="${this.#data.schema.default}" maxOccurs="${this.#data.maxOccurs}" minOccurs="${this.#data.minOccurs}" name="${this.#data.id}" description="${btoa(this.#data.description)}" title="${this.#data.title}"></d4s-ccp-input-remotefile>`
|
||||
this.querySelector("d4s-ccp-input-geo").default = this.#data.schema.default
|
||||
|
||||
} else if (this.isSecret()) {
|
||||
this.innerHTML += `<d4s-ccp-input-secret readonly="${this.#data.schema.readOnly}" default="${this.#data.schema.default||''}" maxOccurs="${this.#data.maxOccurs||1}" minOccurs="${this.#data.minOccurs||1}" name="${this.#data.id}" description="${this.#data.description}" title="${this.#data.title}"></d4s-ccp-input-secret>`
|
||||
|
||||
this.innerHTML += `<d4s-ccp-input-secret readonly="${this.#data.schema.readOnly}" default="${this.#data.schema.default}" maxOccurs="${this.#data.maxOccurs}" minOccurs="${this.#data.minOccurs }" name="${this.#data.id}" description="${btoa(this.#data.description)}" title="${this.#data.title}"></d4s-ccp-input-secret>`
|
||||
this.querySelector("d4s-ccp-input-secret").default = this.#data.schema.default
|
||||
|
||||
} else if (this.isBoolean()) {
|
||||
this.innerHTML += `<d4s-ccp-input-boolean readonly="${this.#data.schema.readOnly}" default="${this.#data.schema.default||''}" maxOccurs="${this.#data.maxOccurs||1}" minOccurs="${this.#data.minOccurs||1}" name="${this.#data.id}" description="${this.#data.description}" title="${this.#data.title}"></d4s-ccp-input-boolean>`
|
||||
|
||||
this.innerHTML += `<d4s-ccp-input-boolean readonly="${this.#data.schema.readOnly}" default="${this.#data.schema.default}" maxOccurs="${this.#data.maxOccurs}" minOccurs="${this.#data.minOccurs}" name="${this.#data.id}" description="${btoa(this.#data.description)}" title="${this.#data.title}"></d4s-ccp-input-boolean>`
|
||||
this.querySelector("d4s-ccp-input-boolean").default = this.#data.schema.default
|
||||
|
||||
} else if (this.isNumber()) {
|
||||
this.innerHTML += `<d4s-ccp-input-simple type="number" readonly="${this.#data.schema.readOnly}" default="${this.#data.schema.default||''}" maxOccurs="${this.#data.maxOccurs||1}" minOccurs="${this.#data.minOccurs||1}" name="${this.#data.id}" description="${this.#data.description}" title="${this.#data.title}"></d4s-ccp-input-simple>`
|
||||
|
||||
this.innerHTML += `<d4s-ccp-input-simple type="number" readonly="${this.#data.schema.readOnly}" default="${this.#data.schema.default}" maxOccurs="${this.#data.maxOccurs}" minOccurs="${this.#data.minOccurs}" name="${this.#data.id}" description="${btoa(this.#data.description)}" title="${this.#data.title}"></d4s-ccp-input-simple>`
|
||||
this.querySelector("d4s-ccp-input-simple").default = this.#data.schema.default
|
||||
|
||||
} else {
|
||||
this.innerHTML += `<d4s-ccp-input-simple readonly="${this.#data.schema.readOnly}" default="${this.#data.schema.default||''}" maxOccurs="${this.#data.maxOccurs||1}" minOccurs="${this.#data.minOccurs||1}" name="${this.#data.id}" description="${this.#data.description}" title="${this.#data.title}"></d4s-ccp-input-simple>`
|
||||
|
||||
this.innerHTML += `<d4s-ccp-input-simple readonly="${this.#data.schema.readOnly}" default="${this.#data.schema.default}" maxOccurs="${this.#data.maxOccurs}" minOccurs="${this.#data.minOccurs}" name="${this.#data.id}" description="${btoa(this.#data.description)}" title="${this.#data.title}"></d4s-ccp-input-simple>`
|
||||
this.querySelector("d4s-ccp-input-simple").default = this.#data.schema.default
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -110,6 +135,13 @@ class CCPInputWidgetController extends HTMLElement {
|
|||
(this.#data.schema.format.toLowerCase() === "remotefile")
|
||||
}
|
||||
|
||||
isGeo() {
|
||||
return (this.#data.schema.type === "string") &&
|
||||
("format" in this.#data.schema) &&
|
||||
(this.#data.schema.format != null) &&
|
||||
(this.#data.schema.format.toLowerCase() === "geo")
|
||||
}
|
||||
|
||||
isDateTime() {
|
||||
return (this.#data.schema.type === "string") &&
|
||||
("format" in this.#data.schema) &&
|
||||
|
@ -142,9 +174,10 @@ class CCPBaseInputWidgetController extends HTMLElement{
|
|||
this.#name = this.getAttribute("name")
|
||||
this.#title = this.getAttribute("title")
|
||||
this.#description = this.getAttribute("description")
|
||||
this.#default = this.getAttribute("default")
|
||||
//this.#default = this.getAttribute("default")
|
||||
this.#minOccurs = Number(this.getAttribute("minoccurs") ? this.getAttribute("minoccurs") : 1)
|
||||
this.#maxOccurs = Number(this.getAttribute("maxoccurs") ? this.getAttribute("maxoccurs") : 1)
|
||||
this.#maxOccurs = Math.max(this.#minOccurs, this.#maxOccurs)
|
||||
this.#readonly = this.getAttribute("readonly") === "true"
|
||||
|
||||
// coalesce all basic input types
|
||||
|
@ -153,7 +186,22 @@ class CCPBaseInputWidgetController extends HTMLElement{
|
|||
// Handle enum case
|
||||
this.#options = (this.getAttribute("options") ? this.getAttribute("options").split(",") : null)
|
||||
|
||||
this.value = Array(Math.max(this.#minOccurs,1)).fill(this.#default)
|
||||
//this.value = Array(Math.max(this.#minOccurs, 1)).fill(this.#default)
|
||||
}
|
||||
|
||||
set default(def){
|
||||
this.#default = def
|
||||
const defarr = Array.isArray(def) ? def : [def]
|
||||
const min = Math.max(this.#minOccurs, 1)
|
||||
var v = []
|
||||
for(let j=0; j < this.#maxOccurs; j++){
|
||||
if(j < defarr.length){
|
||||
v.push(defarr[j])
|
||||
}else if(j < min){
|
||||
v.push(defarr[defarr.length - 1])
|
||||
}
|
||||
}
|
||||
this.value = v
|
||||
}
|
||||
|
||||
setValue(v) {
|
||||
|
@ -218,10 +266,6 @@ class CCPBaseInputWidgetController extends HTMLElement{
|
|||
return this.#options
|
||||
}
|
||||
|
||||
set default(v){
|
||||
this.#default = v
|
||||
}
|
||||
|
||||
get type() {
|
||||
return this.#type
|
||||
}
|
||||
|
@ -239,20 +283,21 @@ class CCPBaseInputWidgetController extends HTMLElement{
|
|||
|
||||
render() {
|
||||
this.rootdoc.innerHTML = `
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<div name="root" class="my-3 ccp-input-widget form-field row">
|
||||
<label class="form-label">
|
||||
<div class="d-flex justify-content-between">
|
||||
<div>
|
||||
${this.required ? `<span title="Required" class="p-1 text-danger">*</span>` : ``}
|
||||
${this.title}
|
||||
<span class="badge text-primary" title="${this.#description}" alt="${this.#description}">?</span>
|
||||
</div>
|
||||
<div name="tools" class="d-flex" style="gap:2px">
|
||||
<div name="plusminus">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<small class="text-muted">${atob(this.#description)}</small>
|
||||
</div>
|
||||
<div name="content">
|
||||
</div>
|
||||
</label>
|
||||
|
@ -265,13 +310,15 @@ class CCPBaseInputWidgetController extends HTMLElement{
|
|||
this.#rootdoc.querySelector("div[name=root]").addEventListener("click", ev => {
|
||||
const src = ev.target.getAttribute("name")
|
||||
if (src === "plus") {
|
||||
this.#value.push(this.#default)
|
||||
this.#value.push(this.#value[this.#value.length - 1])
|
||||
this.renderPlusMinus()
|
||||
this.renderContent()
|
||||
ev.preventDefault()
|
||||
} else if (src === "minus") {
|
||||
this.#value.pop()
|
||||
this.renderPlusMinus()
|
||||
this.renderContent()
|
||||
ev.preventDefault()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -323,14 +370,19 @@ class CCPEnumInputWidgetController extends CCPBaseInputWidgetController{
|
|||
}
|
||||
|
||||
content() {
|
||||
const opts = this.options.map(o=>`<option value="${o}">${o}</option>`).join("\n")
|
||||
if (this.value.length <= 1) {
|
||||
const opts = this.options ?
|
||||
this.options.map(o => `<option value="${o}" ${this.value.length && o === this.value[0] ? 'selected' : ''}>${o}</option>`).join("\n") :
|
||||
''
|
||||
return `<select data-index="0" value="${this.value.length ? this.value[0] : ''}" class="my-2 form-control" placeholder="${this.title}" name="${this.name}" ${this.readonly ? 'disabled' : ''} ${this.required ? 'required' : ''}>${opts}</select>`
|
||||
}
|
||||
var out =
|
||||
this.value.map((c,i)=>{
|
||||
this.value.map((v, i) => {
|
||||
const opts = this.options ?
|
||||
this.options.map(o => `<option value="${o}" ${o === v ? 'selected' : ''}>${o}</option>`).join("\n") :
|
||||
''
|
||||
return `
|
||||
<select data-index="${i}" value="${this.value.length ? this.value[0] : ''}" class="my-2 form-control" placeholder="${this.title}" name="${this.name}" ${this.readonly ? 'disabled' : ''} ${this.required ? 'required' : ''}>${opts}</select>
|
||||
<select data-index="${i}" value="${v}" class="my-2 form-control" placeholder="${this.title}" name="${this.name}" ${this.readonly ? 'disabled' : ''} ${this.required ? 'required' : ''}>${opts}</select>
|
||||
`
|
||||
}).join("\n")
|
||||
return out
|
||||
|
@ -357,11 +409,16 @@ class CCPChecklistInputWidgetController extends CCPBaseInputWidgetController{
|
|||
}
|
||||
|
||||
buildOpts(index, selections) {
|
||||
return this.options.map(o=>`
|
||||
return this.options ? this.options.map(o => `
|
||||
<div class="form-check form-switch form-check-inline">
|
||||
<label>${o}</label>
|
||||
<input data-index="${index}" class="form-check-input" type="checkbox" name="${this.name}" value="${o}" ${this.readonly ? 'readonly' : ''} ${selections.indexOf(o) > -1 ? 'checked' : ''}/></div>
|
||||
`).join("\n")
|
||||
`).join("\n") :
|
||||
`
|
||||
<div class="form-check form-switch form-check-inline">
|
||||
<span class="muted text-danger">No options supplied</span>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
content() {
|
||||
|
@ -394,11 +451,10 @@ class CCPBooleanInputWidgetController extends CCPBaseInputWidgetController{
|
|||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.value.forEach((v,i)=>{ this.value[i] = v === "" ? false : v})
|
||||
this.rootdoc.addEventListener("input", ev => {
|
||||
if (ev.target.getAttribute("name") === this.name) {
|
||||
const index = Number(ev.target.getAttribute("data-index"))
|
||||
this.value[index] = ev.target.checked
|
||||
this.value[index] = ev.target.checked ? "true" : "false"
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -407,7 +463,7 @@ class CCPBooleanInputWidgetController extends CCPBaseInputWidgetController{
|
|||
if (this.value.length <= 1) {
|
||||
return `
|
||||
<div class="my-2 form-check form-switch form-check-inline">
|
||||
<input data-index="0" value="${this.value.length ? this.value[0] : false}" class="my-2 form-check-input" type="checkbox" name="${this.name}" ${this.readonly ? 'readonly' : ''} ${this.value[0] == "true" ? 'checked' : ''} value="${ this.value[0]}"/>
|
||||
<input data-index="0" value="${this.value.length ? this.value[0] : false}" class="my-2 form-check-input" type="checkbox" name="${this.name}" ${this.readonly ? 'readonly' : ''} ${this.value[0] === "true" || this.value[0] === true ? 'checked' : ''} value="${this.value[0]}"/>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
@ -415,7 +471,7 @@ class CCPBooleanInputWidgetController extends CCPBaseInputWidgetController{
|
|||
this.value.map((c, i) => {
|
||||
return `
|
||||
<div class="my-2 form-check form-switch form-check-inline">
|
||||
<input data-index="${i}" value="${c}" class="my-2 form-check-input" type="checkbox" name="${this.name}" ${this.readonly ? 'readonly' : ''} ${c == "true" ? 'checked' : ''} value="${this.value[i]}"/>
|
||||
<input data-index="${i}" value="${c}" class="my-2 form-check-input" type="checkbox" name="${this.name}" ${this.readonly ? 'readonly' : ''} ${c === "true" || c === true ? 'checked' : ''}/>
|
||||
</div>
|
||||
`
|
||||
}).join("\n")
|
||||
|
@ -480,7 +536,10 @@ class CCPFileInputWidgetController extends CCPBaseInputWidgetController{
|
|||
encoded += '='.repeat(4 - (encoded.length % 4));
|
||||
}
|
||||
this.value[index] = encoded
|
||||
this.querySelector("small[name=preview]").textContent = encoded.substr(0,5) + "..." + encoded.substr(encoded.length-5)
|
||||
this.querySelector("small[name=preview]").textContent =
|
||||
encoded.length > 15 ? encoded.substring(0, 5) + "..." + encoded.substring(encoded.length - 5) : encoded
|
||||
const newev = new Event("input", { bubbles : true})
|
||||
this.dispatchEvent(newev)
|
||||
})
|
||||
reader.readAsDataURL(file)
|
||||
}
|
||||
|
@ -490,16 +549,18 @@ class CCPFileInputWidgetController extends CCPBaseInputWidgetController{
|
|||
content() {
|
||||
if (this.value.length <= 1) {
|
||||
const v = this.value.length ? this.value[0] : ''
|
||||
const preview = typeof(v) === "string" ? v.substring(0, 5) + "..." + v.substring(v.length - 5) : ""
|
||||
return `
|
||||
<input type="file" data-index="0" class="my-2 form-control" placeholder="${this.title}" name="${this.name}" ${this.readonly ? 'readonly' : ''} ${this.required && !v ? 'required' : ''} value="${v}"/>
|
||||
<small name ="preview" class="form-text text-muted">${v.substr(0,5) + "..." + v.substr(v.length-5)}</small>
|
||||
<small name ="preview" class="form-text text-muted">${preview}</small>
|
||||
`
|
||||
}
|
||||
var out =
|
||||
this.value.map((c, i) => {
|
||||
const preview = typeof(v) === "string" ? c.substring(0, 5) + "..." + c.substring(v.length - 5) : ""
|
||||
return `
|
||||
<input type="file" data-index="${i}" class="my-2 form-control" placeholder="${this.title}" name="${this.name}" ${this.readonly ? 'readonly' : ''} ${this.required ? 'required' : ''} value="${c}"/>
|
||||
<small name ="preview" class="form-text text-muted">${c.substr(0,5) + "..." + c.substr(s.length-5)}</small>
|
||||
<small name ="preview" class="form-text text-muted">${preview}</small>
|
||||
`
|
||||
}).join("\n")
|
||||
return out
|
||||
|
@ -509,44 +570,126 @@ window.customElements.define('d4s-ccp-input-file', CCPFileInputWidgetController)
|
|||
|
||||
class CCPRemoteFileInputWidgetController extends CCPBaseInputWidgetController {
|
||||
|
||||
#iss = null;
|
||||
#addresses = {
|
||||
#publicorprotected_dialog = null;
|
||||
#target = null
|
||||
#publiclink = null;
|
||||
#protectedlink = null;
|
||||
#index = null;
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
}
|
||||
|
||||
async setPublicLink(l){
|
||||
const b = document.querySelector("d4s-boot-2")
|
||||
const link = await b.secureFetch(l)
|
||||
if(link.ok){
|
||||
const result = await link.json()
|
||||
this.#target.value = this.value[this.#index] = result
|
||||
this.#publicorprotected_dialog.style.display = "none"
|
||||
this.#publicorprotected_dialog.classList.remove("show")
|
||||
this.#publiclink = this.#protectedlink = this.#target = this.#index = null
|
||||
const newev = new Event("input", { bubbles : true})
|
||||
this.dispatchEvent(newev)
|
||||
}else{
|
||||
alert("Unable to get public link for item")
|
||||
this.#target.value = this.value[this.#index] = ""
|
||||
}
|
||||
}
|
||||
|
||||
addToolContent() {
|
||||
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"
|
||||
};
|
||||
|
||||
constructor(){
|
||||
super()
|
||||
this.#iss = document.querySelector("d4s-boot-2").loginToken.iss
|
||||
}
|
||||
|
||||
get baseurl(){
|
||||
return this.#addresses[this.#iss]
|
||||
}
|
||||
|
||||
connectedCallback(){
|
||||
this.rootdoc.addEventListener("input", ev=>{
|
||||
if(ev.target.getAttribute("name") === this.name){
|
||||
const index = Number(ev.target.getAttribute("data-index"))
|
||||
this.value[index] = ev.target.value
|
||||
}
|
||||
})
|
||||
this.rootdoc.querySelector("div[name=tools]").innerHTML += `
|
||||
<svg name="trigger" style="width:24;height:24;fill:#007bff; cursor:pointer" viewBox="0 -960 960 960">
|
||||
<path d="M160-160q-33 0-56.5-23.5T80-240v-480q0-33 23.5-56.5T160-800h240l80 80h320q33 0 56.5 23.5T880-640H447l-80-80H160v480l96-320h684L837-217q-8 26-29.5 41.5T760-160H160Zm84-80h516l72-240H316l-72 240Zm0 0 72-240-72 240Zm-84-400v-80 80Z"/>
|
||||
</svg>
|
||||
<div name="ws" class="d-none position-absolute shadow border border-primary bg-light p-2" style="right:0;z-index:1000; line-height:1.5rem;overflow:hidden;padding:5px;">
|
||||
<div class="mb-1" style="border-bottom: solid 1px gray;">
|
||||
<div class="d-flex justify-content-between m-0">
|
||||
<h5 class="text-secondary m-0">Access your workspace</h5>
|
||||
<span class="material-icons btn text-danger p-0" style="font-weight:bold" name="closebtn">close</span>
|
||||
</div>
|
||||
<small class="text-muted m-0">Select an item or drag and drop it to a proper input</small>
|
||||
</div>
|
||||
<div style="min-width:500px; max-width:640px;overflow:auto;etxt-wrap:nowrap;text-overflow: ellipsis;min-height:5rem; max-height:10rem;">
|
||||
<d4s-storage-tree
|
||||
class="d-none position-absolute shadow border border-primary bg-light"
|
||||
style="left: 100%; margin: 0 0 0 -.5rem;min-width:350px; max-width:500px;z-index:1000; line-height:1.5rem;overflow-x:hidden;text-wrap:nowrap;text-overflow: ellipsis;min-height:5rem; max-height:10rem; padding:5px;"
|
||||
base-url="${this.baseurl}"
|
||||
base-url="${addresses[iss]}"
|
||||
file-download-enabled="true"
|
||||
show-files="true"
|
||||
allow-drag="true"/>
|
||||
</div>
|
||||
<div class="d-flex justify-content-end mt-1 pt-1" style="border-top: solid 1px gray;">
|
||||
<span class="btn btn-primary" name="selectbtn">SELECT</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal fade" style="background-color:rgba(0,0,0,0.3)" name="publicorprotected" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content shadow-lg border-primary">
|
||||
<div class="modal-body">
|
||||
Choose whether you want to use the public link or the protected link (requires authentication and authorization).
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<span name="public" class="btn btn-info">Public link</span>
|
||||
<span name="protected" class="btn btn-primary">Protected link</span>
|
||||
<span name="cancel" class="btn btn-danger">Cancel</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
const ws = this.rootdoc.querySelector("d4s-storage-tree")
|
||||
this.#publicorprotected_dialog = this.rootdoc.querySelector("div.modal[name=publicorprotected]")
|
||||
this.#publicorprotected_dialog.addEventListener("click", ev => {
|
||||
ev.stopPropagation()
|
||||
ev.preventDefault()
|
||||
const name = ev.target.getAttribute("name")
|
||||
if(this.#index != null && this.#target != null){
|
||||
if(name === "public") {
|
||||
this.setPublicLink(this.#publiclink);
|
||||
}
|
||||
else if(name === "protected"){
|
||||
this.#target.value = this.value[this.#index] = this.#protectedlink;
|
||||
this.#publicorprotected_dialog.style.display = "none"
|
||||
this.#publicorprotected_dialog.classList.remove("show")
|
||||
this.#publiclink = this.#protectedlink = this.#target = this.#index = null
|
||||
const newev = new Event("input", { bubbles : true})
|
||||
this.dispatchEvent(newev)
|
||||
}else{
|
||||
this.#publicorprotected_dialog.style.display = "none"
|
||||
this.#publicorprotected_dialog.classList.remove("show")
|
||||
this.#publiclink = this.#protectedlink = this.#target = this.#index = null
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const ws = this.rootdoc.querySelector("div[name=ws]")
|
||||
const st = ws.querySelector("d4s-storage-tree")
|
||||
this.addEventListener("click", ev=>{
|
||||
ev.stopPropagation()
|
||||
ev.preventDefault()
|
||||
if (ev.target.getAttribute("name") == "selectbtn") {
|
||||
this.#publicorprotected_dialog.style.display = "block"
|
||||
this.#publicorprotected_dialog.classList.add("show")
|
||||
this.#target = this.querySelector("input")
|
||||
this.#index = 0
|
||||
this.#publiclink = st.d4sWorkspace.getPublicLink(st.currentId)
|
||||
this.#protectedlink = st.d4sWorkspace.getDownloadLink(st.currentId)
|
||||
}
|
||||
})
|
||||
this.rootdoc.querySelector("svg[name=trigger]").addEventListener("click", ev => {
|
||||
ws.classList.toggle("d-none")
|
||||
ev.preventDefault()
|
||||
ev.stopPropagation()
|
||||
})
|
||||
|
||||
this.rootdoc.querySelector("span[name=closebtn]").addEventListener("click", ev => {
|
||||
ws.classList.add("d-none")
|
||||
ev.preventDefault()
|
||||
ev.stopPropagation()
|
||||
})
|
||||
|
||||
this.addEventListener("dragover", ev => {
|
||||
|
@ -556,9 +699,18 @@ class CCPRemoteFileInputWidgetController extends CCPBaseInputWidgetController{
|
|||
this.addEventListener("drop", ev => {
|
||||
ev.stopPropagation()
|
||||
if (ev.target.getAttribute("name") == this.name && ev.target.getAttribute("data-index") != null) {
|
||||
const index = Number(ev.target.getAttribute("data-index"))
|
||||
ev.target.value = ev.dataTransfer.getData("text/plain+downloadlink")
|
||||
this.value[index] = ev.target.value
|
||||
this.#publicorprotected_dialog.style.display = "block"
|
||||
this.#publicorprotected_dialog.classList.add("show")
|
||||
this.#target = ev.target
|
||||
this.#index = Number(ev.target.getAttribute("data-index"))
|
||||
this.#publiclink = ev.dataTransfer.getData("text/plain+publiclink")
|
||||
this.#protectedlink = ev.dataTransfer.getData("text/plain+downloadlink")
|
||||
}
|
||||
})
|
||||
|
||||
this.addEventListener("input", ev => {
|
||||
if (ev.target.getAttribute("name") == this.name && ev.target.getAttribute("data-index") != null) {
|
||||
this.value[Number(ev.target.getAttribute("data-index"))] = ev.target.value
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -568,6 +720,7 @@ class CCPRemoteFileInputWidgetController extends CCPBaseInputWidgetController{
|
|||
}
|
||||
|
||||
content() {
|
||||
if(!this.readonly && !this.querySelector("div[name=ws]")) this.addToolContent();
|
||||
if (this.value.length <= 1) {
|
||||
return `
|
||||
<input data-index="0" value="${this.value.length ? this.value[0] : ''}" class="my-2 form-control" placeholder="${this.title}" type="${this.type}" name="${this.name}" ${this.readonly ? 'readonly' : ''} ${this.required ? 'required' : ''}/>`
|
||||
|
@ -629,3 +782,225 @@ class CCPSecretInputWidgetController extends CCPBaseInputWidgetController{
|
|||
}
|
||||
}
|
||||
window.customElements.define('d4s-ccp-input-secret', CCPSecretInputWidgetController);
|
||||
|
||||
class CCPGeoInputWidgetController extends CCPBaseInputWidgetController {
|
||||
|
||||
#format_dialog = null;
|
||||
#target = null
|
||||
#draw = null;
|
||||
#raster = null
|
||||
#source = null
|
||||
#map = null;
|
||||
#vector = null
|
||||
#index = null;
|
||||
#widget = null
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
}
|
||||
|
||||
|
||||
addToolContent() {
|
||||
|
||||
this.rootdoc.querySelector("div[name=tools]").innerHTML += `
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||
<svg name="trigger" style="width:24;height:24;fill:#007bff; cursor:pointer" viewBox="0 -960 960 960">
|
||||
<path d="M480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm-40-82v-78q-33 0-56.5-23.5T360-320v-40L168-552q-3 18-5.5 36t-2.5 36q0 121 79.5 212T440-162Zm276-102q20-22 36-47.5t26.5-53q10.5-27.5 16-56.5t5.5-59q0-98-54.5-179T600-776v16q0 33-23.5 56.5T520-680h-80v80q0 17-11.5 28.5T400-560h-80v80h240q17 0 28.5 11.5T600-440v120h40q26 0 47 15.5t29 40.5Z"/>
|
||||
</svg>
|
||||
<div name="map" class="d-none position-absolute shadow border border-primary bg-light p-2" style="right:0;z-index:1000;width:100%;line-height:1.5rem;overflow:hidden;padding:5px;">
|
||||
<div name="maptoolbar" class="mb-1" style="border-bottom: solid 1px gray;">
|
||||
<div class="d-flex justify-content-between m-0">
|
||||
<div>
|
||||
<span title="Draw a rectangular box" name="Box" class="btn text-primary p-0 material-icons">crop_16_9</span>
|
||||
<span title="Draw a circle" name="Circle" class="btn text-primary p-0 material-icons">radio_button_unchecked</span>
|
||||
<span title="Select a Point" name="Point" class="btn text-primary p-0 material-icons">my_location</span>
|
||||
<span title="Draw a Polygon" name="Polygon" class="btn text-primary p-0 material-icons" style="color:transparent">pentagon</span>
|
||||
<span title="Delete all" name="deletebtn" class="btn text-danger p-0 material-icons">delete</span>
|
||||
</div>
|
||||
<div>
|
||||
<span title="Set geometry" class="btn btn-primary" style="font-weight:bold" name="selectbtn">Export</span>
|
||||
<span title="Close" class="material-icons btn text-danger p-0" style="font-weight:bold" name="closebtn">close</span>
|
||||
</div>
|
||||
</div>
|
||||
<small class="text-muted m-0">Draw and export geometry</small>
|
||||
</div>
|
||||
<div style="width:100%;height:500px;position:relative;">
|
||||
<style>
|
||||
div[name=internalmap]{
|
||||
width: 100%;
|
||||
height:100%;
|
||||
position: relative;
|
||||
}
|
||||
</style>
|
||||
<div name="internalmap"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal fade" style="background-color:rgba(0,0,0,0.3)" name="format" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content shadow-lg border-primary">
|
||||
<div class="modal-body">
|
||||
Choose export format.
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<span title="Export WKT" name="wkt" class="btn btn-info">WKT</span>
|
||||
<span title="Export to GML" name="gml" class="btn btn-primary">GML</span>
|
||||
<span title="Export to GeoJSON" name="geojson" class="btn btn-secondary">GeoJSON</span>
|
||||
<span title="Cancel" name="cancel" class="btn btn-danger">Cancel</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
|
||||
// Init map
|
||||
this.init()
|
||||
|
||||
this.rootdoc.querySelector("div[name=maptoolbar]").addEventListener("click", ev=>{
|
||||
const type = ev.target.getAttribute("name")
|
||||
if(type === "closebtn"){
|
||||
this.#widget.classList.add("d-none")
|
||||
ev.preventDefault()
|
||||
ev.stopPropagation()
|
||||
}if(type === "selectbtn"){
|
||||
this.#widget.classList.add("d-none")
|
||||
this.#format_dialog.style.display = "block"
|
||||
this.#format_dialog.classList.add("show")
|
||||
this.#index = this.value.length - 1
|
||||
const alltargets = this.#target = this.rootdoc.querySelectorAll("textarea")
|
||||
for(let i=0; i < alltargets.length;i++){
|
||||
if(!alltargets.item(i).value[i]){
|
||||
this.#index = i
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.#target = this.rootdoc.querySelectorAll("textarea").item(this.#index)
|
||||
ev.preventDefault()
|
||||
ev.stopPropagation()
|
||||
}else if(["Circle", "Point", "Polygon", "Box"].indexOf(type) !== -1){
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
this.undoAllDrawings()
|
||||
if(this.#draw != null && this.#draw.type === type) return;
|
||||
if(this.#draw != null) this.#map.removeInteraction(this.#draw);
|
||||
this.#draw = new ol.interaction.Draw({
|
||||
source: this.#source,
|
||||
type : type === "Box" ? "Circle" : type,
|
||||
geometryFunction : type === "Box" ? ol.interaction.Draw.createBox() : null
|
||||
})
|
||||
this.#map.addInteraction(this.#draw)
|
||||
}else if(type === "deletebtn"){
|
||||
this.undoAllDrawings()
|
||||
ev.preventDefault()
|
||||
ev.stopPropagation()
|
||||
}
|
||||
})
|
||||
|
||||
document.addEventListener("keydown", ev=>{
|
||||
if (ev.ctrlKey && ev.key === 'z') {
|
||||
this.undoDrawing()
|
||||
}else if(ev.key === "Escape"){
|
||||
this.stopDrawing()
|
||||
}
|
||||
})
|
||||
|
||||
this.#format_dialog = this.rootdoc.querySelector("div.modal[name=format]")
|
||||
this.#format_dialog.addEventListener("click", ev => {
|
||||
ev.stopPropagation()
|
||||
ev.preventDefault()
|
||||
const features = this.#source.getFeatures()
|
||||
if( features.length && this.#index != null && this.#target != null){
|
||||
const name = ev.target.getAttribute("name")
|
||||
var format
|
||||
if(name === "cancel"){
|
||||
this.#format_dialog.style.display = "none"
|
||||
this.#format_dialog.classList.remove("show")
|
||||
return
|
||||
}
|
||||
if(name === "wkt") {
|
||||
format = new ol.format.WKT()
|
||||
}else if(name === "geojson"){
|
||||
format = new ol.format.GeoJSON()
|
||||
}else if(name === "gml"){
|
||||
format = new ol.format.GML()
|
||||
}
|
||||
const result = features.map(f=>{
|
||||
const geom = f.getGeometry().getType() === "Circle" ? ol.geom.Polygon.fromCircle(f.getGeometry(), 50) : f.getGeometry()
|
||||
const tgeom = geom.transform(this.#map.getView().getProjection(), new ol.proj.Projection({code: "EPSG:4326"}))
|
||||
return format.writeGeometry(tgeom)
|
||||
}).join("\n")
|
||||
|
||||
this.#target.value = this.value[this.#index] = result
|
||||
const newev = new Event("input", { bubbles : true})
|
||||
this.dispatchEvent(newev)
|
||||
}
|
||||
|
||||
this.#format_dialog.style.display = "none"
|
||||
this.#format_dialog.classList.remove("show")
|
||||
})
|
||||
|
||||
this.rootdoc.querySelector("svg[name=trigger]").addEventListener("click", ev => {
|
||||
this.#widget.classList.toggle("d-none")
|
||||
ev.preventDefault()
|
||||
ev.stopPropagation()
|
||||
})
|
||||
|
||||
this.rootdoc.addEventListener("input", ev=>{
|
||||
if(ev.target.getAttribute("name") === this.name && ev.target.getAttribute("data-index")){
|
||||
const index = Number(ev.target.getAttribute("data-index"))
|
||||
this.value[index] = ev.target.value
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
init(){
|
||||
this.#widget = this.rootdoc.querySelector("div[name=map]")
|
||||
this.#draw = null
|
||||
this.#raster = new ol.layer.Tile({source: new ol.source.OSM()});
|
||||
this.#source = new ol.source.Vector({wrapX: false});
|
||||
this.#vector = new ol.layer.Vector({source: this.#source});
|
||||
|
||||
this.#map = new ol.Map({
|
||||
layers: [this.#raster, this.#vector],
|
||||
target: this.rootdoc.querySelector("div[name=internalmap]"),
|
||||
view: new ol.View({center: [0, 0], zoom: 4 }),
|
||||
controls :[]
|
||||
})
|
||||
}
|
||||
|
||||
undoAllDrawings(){
|
||||
this.#source.clear()
|
||||
}
|
||||
|
||||
undoDrawing(){
|
||||
const features = this.#source.getFeatures()
|
||||
if(features.length){
|
||||
this.#source.removeFeature(features[features.length-1])
|
||||
}
|
||||
}
|
||||
|
||||
stopDrawing(){
|
||||
if(this.#draw){
|
||||
this.#draw.abortDrawing()
|
||||
this.#map.removeInteraction(this.#draw);
|
||||
this.#draw = null
|
||||
}
|
||||
}
|
||||
|
||||
content() {
|
||||
if(!this.readonly && !this.querySelector("div[name=map]")) this.addToolContent();
|
||||
if (this.value.length <= 1) {
|
||||
return `
|
||||
<textarea name="${this.name}" data-index="0" class="ccp-input my-2 form-control" placeholder="${this.title}" type="${this.type}" name="${this.name}" rows="5" ${this.readonly ? 'readonly' : ''} ${this.required ? 'required' : ''}>${this.value.length ? this.value[0] : ''}</textarea>
|
||||
`
|
||||
}
|
||||
var out =
|
||||
this.value.map((c, i) => {
|
||||
return `
|
||||
<textarea name="${this.name}" data-index="${i}" class="ccp-input my-2 form-control" placeholder="${this.title}" type="${this.type}" name="${this.name}" rows="5" ${this.readonly ? 'readonly' : ''} ${this.required ? 'required' : ''}>${c}</textarea>
|
||||
`
|
||||
}).join("\n")
|
||||
return out
|
||||
}
|
||||
}
|
||||
window.customElements.define('d4s-ccp-input-geo', CCPGeoInputWidgetController);
|
||||
|
|
|
@ -3,6 +3,37 @@ class CCPInputWidgetEditorController extends HTMLElement{
|
|||
#input = null
|
||||
#index = null
|
||||
#type = null
|
||||
#messages = {
|
||||
"en": {
|
||||
"input_id_help": "The Id of the input. This has to be unique accross all the inputs the method. This is used as variable expansion in scripts.",
|
||||
"input_delete_help": "Delete this input",
|
||||
"title": "Title",
|
||||
"title_help": "Title of the input. This is how the input will appear in forms.",
|
||||
"description": "Description",
|
||||
"description_help": "A description for this input",
|
||||
"min_occurs": "Min. count",
|
||||
"max_occurs": "Max. count",
|
||||
"min_occurs_help": "Minimum cardinality of this input",
|
||||
"max_occurs_help": "Maximum cardinality of this input",
|
||||
"type": "Type",
|
||||
"type_help": "Set the type of the input. Either String or Enumeration",
|
||||
"mime": "Mime",
|
||||
"mime_help": "Set MIME type of expected input",
|
||||
"format": "Format",
|
||||
"format_help": "Set specific format to tune the widget that will be used in forms",
|
||||
"readonly": "Read only",
|
||||
"readonly_help": "If enabled this input will not be editable in forms",
|
||||
"string": "String",
|
||||
"enum": "Enumerated",
|
||||
"options": "Options",
|
||||
"options_help": "A comma separated list of options for this enumerated input",
|
||||
"options_ext_help": "Comma separated list of options",
|
||||
"choice": "Single choice",
|
||||
"multichoice": "Multiple choices",
|
||||
"default": "Default value",
|
||||
"default_help": "The default value applied for this input when nothing is set explicitly"
|
||||
}
|
||||
}
|
||||
|
||||
#delete_icon = `
|
||||
<svg style="width:24px;height:24px;pointer-events: none;" viewBox="0 0 24 24">
|
||||
|
@ -19,21 +50,15 @@ class CCPInputWidgetEditorController extends HTMLElement{
|
|||
|
||||
}
|
||||
|
||||
get index(){
|
||||
return this.#index
|
||||
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]
|
||||
}
|
||||
|
||||
computeDefaultInputType(){
|
||||
if(this.#input.schema.format === "secret"){
|
||||
return "password"
|
||||
}else if(this.#input.schema.format === "date"){
|
||||
return "date"
|
||||
}else if(this.#input.schema.format === "time"){
|
||||
return "time"
|
||||
}else if(this.#input.schema.format === "dateTime"){
|
||||
return "datetime-local"
|
||||
}
|
||||
return "text"
|
||||
get index() {
|
||||
return this.#index
|
||||
}
|
||||
|
||||
isSelectedFormat(fmt) {
|
||||
|
@ -41,19 +66,11 @@ class CCPInputWidgetEditorController extends HTMLElement{
|
|||
}
|
||||
|
||||
renderDefaultByType() {
|
||||
if(this.#input.schema.format === "code"){
|
||||
return `
|
||||
<textarea rows="5" name="default" class="form-control" placeholder="default">${this.#input.schema.default}</textarea>
|
||||
`
|
||||
} else {
|
||||
return `
|
||||
<input type="${this.computeDefaultInputType()}" value="${this.#input.schema.default}" name="default" class="form-control" placeholder="default"/>
|
||||
`
|
||||
}
|
||||
return `<d4s-ccp-input name="default" input="${btoa(JSON.stringify(this.#input))}"></d4s-ccp-input>`
|
||||
}
|
||||
|
||||
renderDeleteButton() {
|
||||
return `<button data-index="${this.#index}" name="delete-input" title="Delete" class="btn btn-danger ccp-toolbar-button">
|
||||
return `<button data-index="${this.#index}" name="delete-input" title="${this.getLabel("input_delete_help")}" class="btn btn-danger ccp-toolbar-button">
|
||||
${this.#delete_icon}
|
||||
</button>`
|
||||
}
|
||||
|
@ -62,49 +79,57 @@ class CCPInputWidgetEditorController extends HTMLElement{
|
|||
this.#index = i
|
||||
this.#input = input
|
||||
this.#type = input.schema.enum ? "enum" : "string"
|
||||
const minOccurs = input.minOccurs = Number(input.minOccurs) ? Number(input.minOccurs) : 0
|
||||
const maxOccurs = input.maxOccurs = Number(input.maxOccurs) ? Number(input.maxOccurs) : 0
|
||||
this.innerHTML = `
|
||||
<details ${reopen ? 'open' : ''}>
|
||||
<summary class="mb-3">
|
||||
<input class="form-control" style="width:auto;display:inline" required="required" name="id" value="${input.id}" title="Id of input" ${ input.id === 'ccpimage' ? 'readonly' : ''}/>
|
||||
<style>
|
||||
details:not([open]) p[name=static_default] {
|
||||
display: block !important;
|
||||
}
|
||||
</style>
|
||||
<summary class="mb-3" style="position:relative">
|
||||
<input class="form-control" style="width:auto;display:inline" required="required" name="id" value="${input.id}" title="${this.getLabel("input_id_help")}" ${input.id === 'ccpimage' ? 'readonly' : ''}/>
|
||||
${input.id !== 'ccpimage' ? this.renderDeleteButton() : ''}
|
||||
<p title="${input.schema.format === "secret" ? "*****" : input.schema.default}" name="static_default" class="m-1 px-2 d-none small text-truncate text-muted font-italic" style="user-select:none;max-width:30rem">${input.schema.format === "secret" ? "*****" : input.schema.default}</p>
|
||||
</summary>
|
||||
<div style="padding-left: 1rem;border-left: 1px solid gray;">
|
||||
<div class="row mb-3">
|
||||
<div class="col">
|
||||
<div class="form-field" title="Title">
|
||||
<input name="title" class="form-control" placeholder="title" value="${input.title}" required="required" ${ input.id === 'ccpimage' ? 'readonly' : ''}/>
|
||||
<div class="form-field" title="${this.getLabel("title_help")}">
|
||||
<input name="title" class="form-control" placeholder="${this.getLabel("title")}" value="${input.title}" required="required" ${input.id === 'ccpimage' ? 'readonly' : ''}/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div class="form-field" title="description">
|
||||
<input name="description" class="form-control" placeholder="description" value="${input.description}" required="required" ${ input.id === 'ccpimage' ? 'readonly' : ''}/>
|
||||
<div class="form-field" title="${this.getLabel("description_help")}">
|
||||
<textarea rows="2" name="description" class="form-control" placeholder="${this.getLabel("description")}" required="required" ${input.id === 'ccpimage' ? 'readonly' : ''}>${input.description}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-3">
|
||||
<div class="form-field" title="Type">
|
||||
<label>Type</label>
|
||||
<select name="type" class="form-control" placeholder="type" value="${this.#type}" ${ input.id === 'ccpimage' ? 'readonly' : ''}>
|
||||
<option value="string" ${this.#type === "string" ? "selected" : ""}>String</option>
|
||||
<option value="enum" ${this.#type === "enum" ? "selected" : ""}>Enum</option>
|
||||
<div class="form-field" title="${this.getLabel("type_help")}">
|
||||
<label>${this.getLabel("type")}</label>
|
||||
<select name="type" class="form-control" placeholder="${this.getLabel("type")}" value="${this.#type}">
|
||||
<option value="string" ${this.#type === "string" ? "selected" : ""}>${this.getLabel("string")}</option>
|
||||
${input.id === 'ccpimage' ? '' : `<option value="enum" ${this.#type === "enum" ? "selected" : ""}>${this.getLabel("enum")}</option>`}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="form-field" title="Minimum occurrences">
|
||||
<label>Min occurs</label>
|
||||
<input value="${input.minOccurs}" type="number" min="0" step="1" name="minOccurs" value="${input.minOccurs}" required="required" class="form-control" placeholder="minOccurs" ${ input.id === 'ccpimage' ? 'readonly' : ''}/>
|
||||
<div class="form-field" title="${this.getLabel("min_occurs_help")}">
|
||||
<label>${this.getLabel("min_occurs")}</label>
|
||||
<input value="${minOccurs}" type="number" min="0" step="1" name="minOccurs" value="${minOccurs}" required="required" class="form-control" placeholder="${this.getLabel("min_occurs")}" ${input.id === 'ccpimage' ? 'readonly' : ''}/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="form-field" title="Maximum occurrences">
|
||||
<label>Max occurs</label>
|
||||
<input value="${input.maxOccurs}" type="number" min="0" step="1" name="maxOccurs" value="${input.maxOccurs}" required="required" class="form-control" placeholder="maxOccurs" ${ input.id === 'ccpimage' ? 'readonly' : ''}/>
|
||||
<div class="form-field" title="${this.getLabel("max_occurs_help")}">
|
||||
<label>${this.getLabel("max_occurs")}</label>
|
||||
<input value="${maxOccurs}" type="number" min="0" step="1" name="maxOccurs" value="${maxOccurs}" required="required" class="form-control" placeholder="${this.getLabel("max_occurs")}" ${input.id === 'ccpimage' ? 'readonly' : ''}/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col" title="Read only">
|
||||
<label>Read only</label>
|
||||
<div class="col" title="${this.getLabel("readonly_help")}">
|
||||
<label>${this.getLabel("readonly")}</label>
|
||||
<div class="form-field">
|
||||
<input type="checkbox" ${input.schema.readOnly ? 'checked' : ''} name="readonly" class="form-check-input">
|
||||
</div>
|
||||
|
@ -112,46 +137,48 @@ class CCPInputWidgetEditorController extends HTMLElement{
|
|||
</div>
|
||||
<div name="string-input" class="${this.#type === 'enum' ? 'd-none' : ''} row mb-3">
|
||||
<div class="col-3">
|
||||
<div class="form-field" title="Format">
|
||||
<label>Format</label>
|
||||
<div class="form-field" title="${this.getLabel("format_help")}">
|
||||
<label>${this.getLabel("format")}</label>
|
||||
<select value="${input.schema.format}" name="format" class="form-control" ${input.id === 'ccpimage' ? 'readonly' : ''}>
|
||||
<option value="none" ${this.isSelectedFormat('none') ? "selected" : ""}>None</option>
|
||||
<option value="date" ${this.isSelectedFormat('date') ? "selected" : ""}>Date</option>
|
||||
<option value="time" ${this.isSelectedFormat('time') ? "selected" : ""}>Time</option>
|
||||
<option value="dateTime" ${this.isSelectedFormat('dateTime') ? "selected" : ""}>Date time</option>
|
||||
<option value="number" ${this.isSelectedFormat('number') ? "selected" : ""}>Number</option>
|
||||
<option value="number" ${this.isSelectedFormat('number') ? "selected" : ""}>Integer Number</option>
|
||||
<option value="boolean" ${this.isSelectedFormat('boolean') ? "selected" : ""}>True/False</option>
|
||||
<option value="code" ${this.isSelectedFormat('code') ? "selected" : ""}>Code</option>
|
||||
<option value="file" ${this.isSelectedFormat('file') ? "selected" : ""}>File</option>
|
||||
<option value="remotefile" ${this.isSelectedFormat('remotefile') ? "selected" : ""}>Remote file</option>
|
||||
<option value="remotefile" ${this.isSelectedFormat('remotefile') ? "selected" : ""}>Workspace file</option>
|
||||
<option value="geo" ${this.isSelectedFormat('geo') ? "selected" : ""}>Geography</option>
|
||||
<option value="secret" ${this.isSelectedFormat('secret') ? "selected" : ""}>Secret</option>
|
||||
<option value="url" ${this.isSelectedFormat('url') ? "selected" : ""}>Url</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col" title="Mime type">
|
||||
<label>Mime</label>
|
||||
<div class="col" title="${this.getLabel("mime_help")}">
|
||||
<label>${this.getLabel("mime")}</label>
|
||||
<div class="form-field">
|
||||
<input value="${input.schema.contentMediaType}" name="contentMediaType" class="form-control" placeholder="mime" ${input.id === 'ccpimage' ? 'readonly' : ''}/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div name="enum-input" class="${this.#type !== 'enum' ? 'd-none' : ''} mb-3">
|
||||
<div class="form-field" title="options">
|
||||
<input name="options" class="form-control" type="text" placeholder="option" value="${input.schema.enum ? input.schema.enum.join(',') : ''}"/>
|
||||
<small class="form-text text-muted">Comma separated list of options</small>
|
||||
<div class="form-field" title="${this.getLabel("options_help")}">
|
||||
<input name="options" class="form-control" type="text" placeholder="${this.getLabel("options")}" value="${input.schema.enum ? input.schema.enum.join(',') : ''}"/>
|
||||
<small class="form-text text-muted">${this.getLabel("options_ext_help")}</small>
|
||||
</div>
|
||||
<div class="form-field" title="Format">
|
||||
<label>Format</label>
|
||||
<div class="form-field" title="${this.getLabel("format_help")}">
|
||||
<label>${this.getLabel("format")}</label>
|
||||
<select value="${input.schema.format}" name="format" class="form-control">
|
||||
<option value="select" ${this.isSelectedFormat('select') ? "selected" : ""}>Choice</option>
|
||||
<option value="checklist" ${this.isSelectedFormat('checklist') ? "selected" : ""}>Multi Choice</option>
|
||||
<option value="select" ${this.isSelectedFormat('select') ? "selected" : ""}>${this.getLabel("choice")}</option>
|
||||
<option value="checklist" ${this.isSelectedFormat('checklist') ? "selected" : ""}>${this.getLabel("multi_choice")}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div name="input-default" class="mb-3">
|
||||
<div class="form-field" title="Default value">
|
||||
${this.renderDefaultByType()}
|
||||
<label>${this.getLabel("default")}</label>
|
||||
<div class="form-field border border-info px-2 py-1" style="background-color:#0dcaf022" title="${this.getLabel("default_help")}">
|
||||
<div name="default-container">${this.renderDefaultByType()}</div>
|
||||
<span style="user-select:none;position:relative;top:-1.6rem;float:right;cursor:pointer" name="password_toggle" class="${this.isSelectedFormat('secret') ? 'inline' : 'd-none'}">👁</span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -167,68 +194,147 @@ class CCPInputWidgetEditorController extends HTMLElement{
|
|||
}
|
||||
})
|
||||
|
||||
const defaultinp = this.querySelector("div[name='input-default']")
|
||||
defaultinp.addEventListener("input", ev => {
|
||||
const inp = this.querySelector("d4s-ccp-input")
|
||||
this.#input.schema.default = inp.value
|
||||
const p = this.querySelector("p[name=static_default]")
|
||||
p.innerHTML = p.title = inp.value
|
||||
})
|
||||
|
||||
defaultinp.addEventListener("change", ev => {
|
||||
const inp = this.querySelector("d4s-ccp-input")
|
||||
this.#input.schema.default = inp.value
|
||||
const p = this.querySelector("p[name=static_default]")
|
||||
p.innerHTML = p.title = inp.value
|
||||
})
|
||||
|
||||
defaultinp.addEventListener("click", ev => {
|
||||
const src = ev.target.getAttribute("name")
|
||||
if (src === "plus" || src === "minus") {
|
||||
const inp = this.querySelector("d4s-ccp-input")
|
||||
this.#input.schema.default = inp.value
|
||||
const p = this.querySelector("p[name=static_default]")
|
||||
p.innerHTML = p.title = inp.value
|
||||
}
|
||||
})
|
||||
|
||||
this.addEventListener("input", ev => {
|
||||
const val = ev.target.value
|
||||
const ename = ev.target.getAttribute("name")
|
||||
const display = this.querySelector("div[name='default-container']")
|
||||
if (ename === "id") {
|
||||
this.#input.id = val
|
||||
}
|
||||
else if (ename === "title") {
|
||||
this.#input.title = val
|
||||
display.innerHTML = this.renderDefaultByType()
|
||||
}
|
||||
else if (ename === "description") {
|
||||
this.#input.description = val
|
||||
display.innerHTML = this.renderDefaultByType()
|
||||
}
|
||||
else if (ename === "minOccurs") {
|
||||
this.#input.minOccurs = val
|
||||
this.#input.minOccurs = Number(val) ? Number(val) : 0
|
||||
display.innerHTML = this.renderDefaultByType()
|
||||
}
|
||||
else if (ename === "maxOccurs") {
|
||||
this.#input.maxOccurs = val
|
||||
this.#input.maxOccurs = Number(val) ? Number(val) : 0
|
||||
display.innerHTML = this.renderDefaultByType()
|
||||
}
|
||||
else if (ename === "format") {
|
||||
this.#input.schema.default = this.formatConversion(this.#input.schema.format, this.#input.schema.default, val)
|
||||
this.#input.schema.format = val
|
||||
//this.render(this.#input, this.#index, true)
|
||||
/*this.querySelector("div[name=input-default] span[name=password_toggle]").classList.add("d-none")
|
||||
this.querySelector("div[name=input-default] input[name=default]").type = ""
|
||||
if(this.#input.schema.format === "secret"){
|
||||
this.querySelector("div[name=input-default] input[name=default]").type = "password"
|
||||
this.querySelector("div[name=input-default] span[name=password_toggle]").classList.remove("d-none")
|
||||
}else if(this.#input.schema.format === "date"){
|
||||
this.querySelector("div[name=input-default] input[name=default]").type = "date"
|
||||
}else if(this.#input.schema.format === "time"){
|
||||
this.querySelector("div[name=input-default] input[name=default]").type = "time"
|
||||
}else if(this.#input.schema.format === "dateTime"){
|
||||
this.querySelector("div[name=input-default] input[name=default]").type = "dateTime"
|
||||
}else if(this.#input.schema.format === "file"){
|
||||
this.querySelector("div[name=input-default] input[name=default]").type = "file"
|
||||
}*/
|
||||
display.innerHTML = this.renderDefaultByType()
|
||||
}
|
||||
else if (ename === "contentMediaType") {
|
||||
this.#input.schema.contentMediaType = val
|
||||
display.innerHTML = this.renderDefaultByType()
|
||||
}
|
||||
else if (ename === "options") {
|
||||
this.#input.schema.enum = val.split(",")
|
||||
if (this.#input.schema.enum.indexOf(this.#input.schema.default) === -1) {
|
||||
this.#input.schema.default = this.#input.schema.enum[0]
|
||||
}
|
||||
else if(ename === "default"){
|
||||
this.#input.schema.default = val
|
||||
display.innerHTML = this.renderDefaultByType()
|
||||
}
|
||||
else if (ename === "readonly") {
|
||||
this.#input.schema.readOnly = ev.target.checked
|
||||
display.innerHTML = this.renderDefaultByType()
|
||||
}
|
||||
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.querySelector("div[name=enum-input] select[name=format]").value = "select"
|
||||
this.#input.schema.format = "select"
|
||||
this.#input.schema.enum = this.querySelector("input[name=options]").value.split(",")
|
||||
if (this.#input.schema.enum.indexOf(this.#input.schema.default) === -1) {
|
||||
this.#input.schema.default = this.#input.schema.enum[0]
|
||||
}
|
||||
} 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")
|
||||
this.#input.schema.format = "none"
|
||||
delete this.#input.schema['enum']
|
||||
}
|
||||
display.innerHTML = this.renderDefaultByType()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
boolean2number(prev){
|
||||
if(Array.isArray(prev)){
|
||||
return prev.map(v=>(v === "true" || v === true ? "1" : "0"))
|
||||
} else return prev === "true" || prev === true ? "1" : "0"
|
||||
}
|
||||
|
||||
none2number(prev){
|
||||
if(Array.isArray(prev)){
|
||||
return prev.map(v=>(isNaN(Number(v)) ? "0" : v))
|
||||
} else return isNaN(Number(prev)) ? "0" : prev
|
||||
}
|
||||
|
||||
number2boolean(prev){
|
||||
if(Array.isArray(prev)){
|
||||
return prev.map(v=>(Number(v) !== 0 ? "true" : "false"))
|
||||
} else return Number(prev) !== 0 ? "true" : "false"
|
||||
}
|
||||
|
||||
none2boolean(prev){
|
||||
if(Array.isArray(prev)){
|
||||
return prev.map(v=>(["true", "false"].indexOf(v.toLowerCase()) !== -1 ? v.toLowerCase() : "false"))
|
||||
} else return ["true", "false"].indexOf(prev.toLowerCase()) !== -1 ? prev.toLowerCase() : "false"
|
||||
}
|
||||
|
||||
any2boolean(prev){
|
||||
if(Array.isArray(prev)){
|
||||
return prev.map(v=>String(!!v))
|
||||
} else return String(!!v);
|
||||
}
|
||||
|
||||
any2empty(prev){
|
||||
if(Array.isArray(prev)){
|
||||
return prev.map(v=>"")
|
||||
} else return "";
|
||||
}
|
||||
|
||||
formatConversion(prevformat, prevvalue, format){
|
||||
if(format === "none"){
|
||||
return prevvalue;
|
||||
}
|
||||
if(format === "number"){
|
||||
if(prevformat === "boolean") return this.boolean2number(prevvalue);
|
||||
if(!prevformat || prevformat === "none") return this.none2number(prevvalue);
|
||||
return "0";
|
||||
}
|
||||
if(format === "boolean"){
|
||||
if(prevformat === "number") return this.number2boolean(prevvalue)
|
||||
if(!prevformat || prevformat === "none") return this.none2boolean(prevvalue)
|
||||
return this.any2boolean(prevvalue)
|
||||
}
|
||||
return this.any2empty(prevvalue)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -42,13 +42,55 @@ class CCPMethodEditorController extends HTMLElement{
|
|||
links: []
|
||||
}
|
||||
|
||||
#scripts = `
|
||||
<link rel="canonical" href="https://getbootstrap.com/docs/5.0/components/modal/">
|
||||
`
|
||||
#messages = {
|
||||
"en" : {
|
||||
"clone" : "Clone",
|
||||
"clone_help" : "All setting will be cloned but no relation to cloned method will be kept",
|
||||
"derive" : "Derive",
|
||||
"derive_help" : "All settings will be cloned and a relation to the originating method will be kept",
|
||||
"edit" : "Edit",
|
||||
"edit_help" : "Edit this method",
|
||||
"edit_clone_derive_help" : "Choose whether you want to clone this method or edit it",
|
||||
"save_help" : "Save this method definition",
|
||||
"reset_help" : "Reset all fields",
|
||||
"delete_help" : "Permanently delete this method",
|
||||
"title" : "Title",
|
||||
"title_help" : "The title for the method",
|
||||
"version" : "Version",
|
||||
"version_help" : "The version of the method",
|
||||
"description" : "Description",
|
||||
"description_help" : "The description of this method",
|
||||
"keywords" : "Keywords",
|
||||
"keywords_help" : "Keywords that can be used to search for the method. Write and add by hitting enter.",
|
||||
"categories" : "Categories",
|
||||
"categories_help" : "Categories to classify the method. Select one from the list or type a new one. Add by pressing enter.",
|
||||
"compatible_infrastructures" : "Compatible infrastructures",
|
||||
"compatible_infrastructures_help" : "Select the infrastructures that are able to execute your method",
|
||||
"inputs" : "Inputs",
|
||||
"outputs" : "Outputs",
|
||||
"scripts" : "Scripts",
|
||||
"scripts_help" : "Deploy and execute scripts configure the deployment and execution life cycle of the method on the selected infrastructures. Use {{input_id}} notation to expand the inputs with their default values.",
|
||||
"add-input_help" : "Add an input to this method",
|
||||
"add-output_help" : "Add an output to this method",
|
||||
"add_ccpnote_help" : "Add ccpnote annotation for this method. This helps in identifying executions.",
|
||||
"add_stdout_help" : "Add standard outout",
|
||||
"add_stderr_help" : "Add standard error",
|
||||
"preview" : "Preview",
|
||||
"err_fetch_infrastructures" : "Unable to fetch insfrastructures",
|
||||
"err_save_method" : "Unable to save method",
|
||||
"err_load_method" : "Unable to load method",
|
||||
"err_delete_method" : "Unable to delete method",
|
||||
"confirm_reset" : "All unsaved data will be lost. Proceed?",
|
||||
"confirm_save" : "Confirm updating of method",
|
||||
"confirm_delete" : "Confirm deletion of method",
|
||||
"execute-script" : "Execute script",
|
||||
"deploy-script" : "Deploy script",
|
||||
}
|
||||
}
|
||||
|
||||
#style = `
|
||||
<link rel="stylesheet" href="https://cdn.dev.d4science.org/ccp/css/common.css"></link>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="https://cdn.cloud.d4science.org/ccp/css/common.css"></link>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
|
||||
|
||||
<style>
|
||||
.ccp-method-editor {
|
||||
|
@ -149,20 +191,26 @@ class CCPMethodEditorController extends HTMLElement{
|
|||
</svg>
|
||||
`
|
||||
|
||||
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.initMethod()
|
||||
this.fetchInfrastructures()
|
||||
this.connectNewEditRequest()
|
||||
}
|
||||
|
||||
connectedCallback(){
|
||||
|
||||
}
|
||||
|
||||
connectNewEditRequest() {
|
||||
document.addEventListener("neweditrequest", ev => {
|
||||
this.cloneOrEditMethod(ev.detail)
|
||||
|
@ -176,7 +224,7 @@ class CCPMethodEditorController extends HTMLElement{
|
|||
fetchInfrastructures() {
|
||||
this.#boot.secureFetch(this.#serviceurl + "/infrastructures").
|
||||
then(resp => {
|
||||
if(resp.status !== 200) throw "Unable to fetch infrastructures";
|
||||
if (resp.status !== 200) throw this.getLabel("err_fetch_infrastructures");
|
||||
return resp.json()
|
||||
}).then(data => {
|
||||
this.#infrastructures = data
|
||||
|
@ -190,7 +238,7 @@ class CCPMethodEditorController extends HTMLElement{
|
|||
if (this.#locked) return;
|
||||
if (this.#current != null) {
|
||||
this.adoptTemporaries()
|
||||
const text = `Please confirm ${this.#isupdate ? "updating" : "creation"} of ${this.#current.title} version ${this.#current.version}`
|
||||
const text = this.getLabel("confirm_save") + ` ${this.#current.title} v. ${this.#current.version}`
|
||||
if (window.confirm(text)) {
|
||||
this.lockRender()
|
||||
const url = this.#serviceurl + "/methods"
|
||||
|
@ -203,7 +251,7 @@ class CCPMethodEditorController extends HTMLElement{
|
|||
(resp) => {
|
||||
if (resp.ok) {
|
||||
return resp.text()
|
||||
}else throw "Error saving process: " + resp.status
|
||||
} else throw this.getLabel("err_save_method") + resp.status
|
||||
}).then(data => {
|
||||
if (!this.#isupdate) {
|
||||
this.#current = JSON.parse(data)
|
||||
|
@ -223,7 +271,7 @@ class CCPMethodEditorController extends HTMLElement{
|
|||
deleteMethod() {
|
||||
if (this.#locked) return;
|
||||
if (this.#current != null) {
|
||||
const text = `Please confirm deletion of ${this.#current.title} version ${this.#current.version}`
|
||||
const text = this.getLabel("confirm_delete") + ` ${this.#current.title} v. ${this.#current.version}`
|
||||
if (window.confirm(text)) {
|
||||
this.lockRender()
|
||||
const url = this.#serviceurl + "/methods/" + this.#current.id
|
||||
|
@ -234,7 +282,7 @@ class CCPMethodEditorController extends HTMLElement{
|
|||
(resp) => {
|
||||
if (resp.status === 404 || resp.status === 204) {
|
||||
return null
|
||||
}else throw "Error deleting method: " + resp.status
|
||||
} else throw this.getLabel("err_delete_method") + ": " + resp.status
|
||||
}).then(data => {
|
||||
this.#isupdate = false
|
||||
this.initMethod()
|
||||
|
@ -263,7 +311,7 @@ class CCPMethodEditorController extends HTMLElement{
|
|||
format: "url",
|
||||
contentMediaType: "text/plain",
|
||||
default: "",
|
||||
readonly : true,
|
||||
readOnly: true,
|
||||
}
|
||||
}
|
||||
]
|
||||
|
@ -278,7 +326,7 @@ class CCPMethodEditorController extends HTMLElement{
|
|||
cloneOrEditMethod(method) {
|
||||
const subject = this.#boot.subject
|
||||
const matchingauthors = method.metadata ? method.metadata.filter(md => md.role === "author" && md.href.endsWith("/" + subject)).length : 0
|
||||
if(matchingauthors === 0) this.cloneMethod(method.id);
|
||||
if (matchingauthors === 0) this.cloneMethod(method.id, true);
|
||||
else {
|
||||
this.#dragging_method = method.id
|
||||
this.#cloneornew_dialog.style.display = "block"
|
||||
|
@ -286,14 +334,15 @@ class CCPMethodEditorController extends HTMLElement{
|
|||
}
|
||||
}
|
||||
|
||||
cloneMethod(method){
|
||||
cloneMethod(method, derivation) {
|
||||
if (this.#locked) return;
|
||||
this.lockRender()
|
||||
this.#boot.secureFetch(this.#serviceurl + "/methods/" + method + "/clone").then(
|
||||
const derive = derivation ? "?derive=true" : ""
|
||||
this.#boot.secureFetch(this.#serviceurl + "/methods/" + method + "/clone" + derive).then(
|
||||
(resp) => {
|
||||
if (resp.status === 200) {
|
||||
return resp.json()
|
||||
}else throw "Error retrieving process: " + resp.status
|
||||
} else throw this.getLabel("err_load_method") + ": " + resp.status
|
||||
}
|
||||
).then(data => {
|
||||
this.#current = data
|
||||
|
@ -313,7 +362,7 @@ class CCPMethodEditorController extends HTMLElement{
|
|||
(resp) => {
|
||||
if (resp.status === 200) {
|
||||
return resp.json()
|
||||
}else throw "Error retrieving process: " + resp.status
|
||||
} else throw this.getLabel("err_load_method") + ": " + resp.status
|
||||
}
|
||||
).then(data => {
|
||||
this.#current = data
|
||||
|
@ -368,11 +417,12 @@ class CCPMethodEditorController extends HTMLElement{
|
|||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content shadow-lg border-primary">
|
||||
<div class="modal-body">
|
||||
Choose whether you want to clone this method or edit it.
|
||||
${this.getLabel("edit_clone_derive_help")}
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button name="clone" type="button" class="btn btn-info">Clone</button>
|
||||
<button name="edit" type="button" class="btn btn-primary">Edit</button>
|
||||
<button name="clone" title="${this.getLabel("clone_help")}" type="button" class="btn btn-info">${this.getLabel("clone")}</button>
|
||||
<button name="edit" title="${this.getLabel("edit_help")}" type="button" class="btn btn-primary">${this.getLabel("edit")}</button>
|
||||
<button name="derive" title="${this.getLabel("derive_help")}" type="button" class="btn btn-secondary">${this.getLabel("derive")}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -406,29 +456,30 @@ class CCPMethodEditorController extends HTMLElement{
|
|||
${this.renderContexts()}
|
||||
</div-->
|
||||
<div class="mb-3 row">
|
||||
<div class="col">
|
||||
<label class="form-label">Title</label>
|
||||
<div class="col" title="${this.getLabel("title_help")}">
|
||||
<label class="form-label">${this.getLabel("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>
|
||||
<div class="col" title="${this.getLabel("version_help")}">
|
||||
<label class="form-label">${this.getLabel("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 class="mb-3" title="${this.getLabel("description_help")}">
|
||||
<label class="form-label">${this.getLabel("description")}</label>
|
||||
<!--input name="description" class="form-control" type="text" required="required" value="${this.#current.description}"/-->
|
||||
<textarea name="description" class="form-control" required="required" rows="5">${this.#current.description}</textarea>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col mb-3">
|
||||
<label class="form-label">Keywords</label>
|
||||
<div class="col mb-3" title="${this.getLabel("keywords_help")}">
|
||||
<label class="form-label">${this.getLabel("keywords")}</label>
|
||||
<input name="keyword-input" class="form-control" type="text"/>
|
||||
<div name="keyword-list" class="form-text">
|
||||
${this.renderKeywords()}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col mb-3">
|
||||
<label class="form-label">Categories</label>
|
||||
<div class="col mb-3" title="${this.getLabel("categories_help")}">
|
||||
<label class="form-label">${this.getLabel("categories")}</label>
|
||||
<input list="categoryhints" name="category-input" class="form-control" type="text"/>
|
||||
${this.renderCategoryHints()}
|
||||
<div name="category-list" class="form-text">
|
||||
|
@ -436,8 +487,8 @@ class CCPMethodEditorController extends HTMLElement{
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div name="infrastructures" class="mb-3">
|
||||
<label class="form-label">Compatible Infrastrucures</label>
|
||||
<div name="infrastructures" class="mb-3" title="${this.getLabel("compatible_infrastructures_help")}">
|
||||
<label class="form-label">${this.getLabel("compatible_infrastructures")}</label>
|
||||
<select name="infrastructure-input" class="form-control">
|
||||
${this.renderInfrastructureOptions()}
|
||||
</select>
|
||||
|
@ -451,7 +502,7 @@ class CCPMethodEditorController extends HTMLElement{
|
|||
<summary class="card-header">
|
||||
<div class="ccp-toolbar-header">
|
||||
<div>
|
||||
<span class="mr-2">Inputs</span>
|
||||
<span class="mr-2">${this.getLabel("inputs")}</span>
|
||||
</div>
|
||||
<div class="ccp-toolbar-right">
|
||||
${this.renderStandardInputButtons()}
|
||||
|
@ -466,7 +517,7 @@ class CCPMethodEditorController extends HTMLElement{
|
|||
<summary class="card-header" name="output-buttons">
|
||||
<div class="ccp-toolbar-header">
|
||||
<div>
|
||||
<span class="mr-2">Outputs</span>
|
||||
<span class="mr-2">${this.getLabel("outputs")}</span>
|
||||
</div>
|
||||
<div class="ccp-toolbar-right">
|
||||
${this.renderStandardOutputButtons()}
|
||||
|
@ -478,25 +529,11 @@ class CCPMethodEditorController extends HTMLElement{
|
|||
<d4s-ccp-output-editor></d4s-ccp-output-editor>
|
||||
</div>
|
||||
</div>
|
||||
<details class="card" name="script-list">
|
||||
<summary class="card-header">
|
||||
<div class="ccp-toolbar-header">
|
||||
<div>
|
||||
<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="undeploy-script">Undeploy</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</summary>
|
||||
<div class="card-body" name="input_container">
|
||||
<div name="scripts_container">
|
||||
${this.renderScripts()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
this.renderInputs()
|
||||
this.renderOutputs()
|
||||
|
@ -510,6 +547,13 @@ class CCPMethodEditorController extends HTMLElement{
|
|||
this.#dragging_method = null
|
||||
})
|
||||
|
||||
this.#rootdoc.querySelector("#cloneornew button[name=derive]").addEventListener("click", ev => {
|
||||
this.#cloneornew_dialog.classList.remove("show")
|
||||
this.#cloneornew_dialog.style.display = "none"
|
||||
this.cloneMethod(this.#dragging_method, true)
|
||||
this.#dragging_method = null
|
||||
})
|
||||
|
||||
this.#rootdoc.querySelector("#cloneornew button[name=edit]").addEventListener("click", ev => {
|
||||
this.#cloneornew_dialog.classList.remove("show")
|
||||
this.#cloneornew_dialog.style.display = "none"
|
||||
|
@ -526,7 +570,7 @@ class CCPMethodEditorController extends HTMLElement{
|
|||
this.#current.version = ev.currentTarget.value
|
||||
})
|
||||
|
||||
this.#rootdoc.querySelector("input[name=description]").addEventListener("input", ev=>{
|
||||
this.#rootdoc.querySelector("textarea[name=description]").addEventListener("input", ev => {
|
||||
this.#current.description = ev.currentTarget.value
|
||||
})
|
||||
|
||||
|
@ -545,7 +589,7 @@ class CCPMethodEditorController extends HTMLElement{
|
|||
})
|
||||
|
||||
this.#rootdoc.querySelector("button[name=reset]").addEventListener("click", ev => {
|
||||
if(window.confirm("All unsaved data will be lost. Proceed?")){
|
||||
if (window.confirm(this.getLabel("confirm_reset"))) {
|
||||
this.resetMethod()
|
||||
}
|
||||
ev.preventDefault()
|
||||
|
@ -663,6 +707,7 @@ class CCPMethodEditorController extends HTMLElement{
|
|||
}
|
||||
)
|
||||
this.renderInputs()
|
||||
this.reRenderScripts()
|
||||
})
|
||||
|
||||
this.#rootdoc.querySelector("button[name=add-ccpannotation]").addEventListener("click", ev => {
|
||||
|
@ -684,6 +729,7 @@ class CCPMethodEditorController extends HTMLElement{
|
|||
}
|
||||
)
|
||||
this.renderInputs()
|
||||
this.reRenderScripts()
|
||||
})
|
||||
|
||||
this.#rootdoc.querySelector("div[name=input-list]").addEventListener("click", ev => {
|
||||
|
@ -695,6 +741,23 @@ class CCPMethodEditorController extends HTMLElement{
|
|||
console.log("deleting input at index", index)
|
||||
this.deleteTmpInputAt(index)
|
||||
this.renderInputs()
|
||||
this.reRenderScripts()
|
||||
}
|
||||
})
|
||||
|
||||
//when any input changes update scripts so that possibly variable get re-expanded
|
||||
this.#rootdoc.querySelector("div[name=input-list]").addEventListener("input", ev => {
|
||||
this.reRenderScripts()
|
||||
})
|
||||
|
||||
this.#rootdoc.querySelector("div[name=input-list]").addEventListener("change", ev => {
|
||||
this.reRenderScripts()
|
||||
})
|
||||
|
||||
this.#rootdoc.querySelector("div[name=input-list]").addEventListener("click", ev => {
|
||||
const src = ev.target.getAttribute("name")
|
||||
if (src === "plus" || src === "minus" || src === "maxOccurs" || src === "minOccurs") {
|
||||
this.reRenderScripts()
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -788,23 +851,13 @@ class CCPMethodEditorController extends HTMLElement{
|
|||
}
|
||||
})
|
||||
|
||||
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=>{
|
||||
this.#rootdoc.querySelector("div[name=scripts_container]").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]
|
||||
const script = this.#current.additionalParameters.parameters.find(p => p.name === scriptname)
|
||||
if (script) script.value = ev.target.value.split(/\r?\n/);
|
||||
this.reRenderScripts()
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -841,7 +894,7 @@ class CCPMethodEditorController extends HTMLElement{
|
|||
|
||||
renderSaveButton() {
|
||||
return `
|
||||
<button title="Save" name="save" class="btn btn-primary ccp-toolbar-button">
|
||||
<button title="${this.getLabel("save_help")}" name="save" class="btn btn-primary ccp-toolbar-button">
|
||||
${this.#disc_icon}
|
||||
</button>
|
||||
`
|
||||
|
@ -849,7 +902,7 @@ class CCPMethodEditorController extends HTMLElement{
|
|||
|
||||
renderDeleteButton() {
|
||||
return `
|
||||
<button title="Delete" name="delete" class="btn btn-danger ccp-toolbar-button">
|
||||
<button title="${this.getLabel("delete_help")}" name="delete" class="btn btn-danger ccp-toolbar-button">
|
||||
${this.#delete_icon}
|
||||
</button>
|
||||
`
|
||||
|
@ -857,7 +910,7 @@ class CCPMethodEditorController extends HTMLElement{
|
|||
|
||||
renderResetButton() {
|
||||
return `
|
||||
<button name="reset" title="Reset" class="btn btn-primary ccp-toolbar-button">
|
||||
<button name="reset" title="${this.getLabel("reset_help")}" class="btn btn-primary ccp-toolbar-button">
|
||||
<svg viewBox="0 0 24 24">
|
||||
${this.#erase_icon}
|
||||
</svg>
|
||||
|
@ -867,7 +920,7 @@ class CCPMethodEditorController extends HTMLElement{
|
|||
|
||||
renderPlusButton(name) {
|
||||
return `
|
||||
<button name="${name}" title="Add" name="reset" class="btn btn-primary ccp-toolbar-button">
|
||||
<button name="${name}" title="${this.getLabel(name + "_help")}" class="btn btn-primary ccp-toolbar-button">
|
||||
${this.#plus_icon}
|
||||
</button>
|
||||
`
|
||||
|
@ -875,7 +928,7 @@ class CCPMethodEditorController extends HTMLElement{
|
|||
|
||||
renderStandardInputButtons() {
|
||||
return `
|
||||
<button name="add-ccpannotation" title="Add annotation input" name="reset" class="btn btn-info ccp-toolbar-button">
|
||||
<button name="add-ccpannotation" title="${this.getLabel("add_ccpnote_help")}" class="btn btn-info ccp-toolbar-button">
|
||||
${this.#annotation_input_icon}
|
||||
</button>
|
||||
`
|
||||
|
@ -883,10 +936,10 @@ class CCPMethodEditorController extends HTMLElement{
|
|||
|
||||
renderStandardOutputButtons() {
|
||||
return `
|
||||
<button name="add-stdout" title="Add stdout" class="btn btn-success ccp-toolbar-button">
|
||||
<button name="add-stdout" title="${this.getLabel("add_stdout_help")}" class="btn btn-success ccp-toolbar-button">
|
||||
${this.#output_icon}
|
||||
</button>
|
||||
<button name="add-stderr" title="Add stderr" class="btn btn-danger ccp-toolbar-button">
|
||||
<button name="add-stderr" title="${this.getLabel("add_stderr_help")}" class="btn btn-danger ccp-toolbar-button">
|
||||
${this.#output_icon}
|
||||
</button>
|
||||
`
|
||||
|
@ -940,8 +993,7 @@ class CCPMethodEditorController extends HTMLElement{
|
|||
const available = this.#infrastructures.filter(i => { return selectedinfras.indexOf(i.id) === -1 })
|
||||
return `
|
||||
<option></option>
|
||||
${
|
||||
available.map(infra=>{
|
||||
${available.map(infra => {
|
||||
return `
|
||||
<option value="${infra.id}" title="${infra.description}">${infra.name}</option>
|
||||
`
|
||||
|
@ -981,16 +1033,65 @@ class CCPMethodEditorController extends HTMLElement{
|
|||
})
|
||||
}
|
||||
|
||||
renderScripts(){
|
||||
const val = 'deploy-script'
|
||||
return this.#current.additionalParameters.parameters.map(
|
||||
(script, i) => {
|
||||
let code = script.value.length ? script.value.join("\r\n") : ""
|
||||
codeToPreview(code) {
|
||||
return this.#tmp_inputs.reduce((acc, i) => {
|
||||
const r = new RegExp(`{{\\s*${i.id}\\s*}}`, 'g');
|
||||
var def = i.schema.default
|
||||
if(i.schema.format === "secret"){
|
||||
def = Array.isArray(def) ? def : [def]
|
||||
def = def.map(v=>"***")
|
||||
}
|
||||
if(i.schema.format === "file"){
|
||||
def = Array.isArray(def) ? def : [def]
|
||||
def = def.map(v=>v.length > 15 ? v.substring(0,5) + "..." + v.substring(v.length-5) : v)
|
||||
}
|
||||
return acc.replaceAll(r, def)
|
||||
}, code)
|
||||
}
|
||||
|
||||
reRenderScripts(scriptname) {
|
||||
const container = this.#rootdoc.querySelector("div[name=scripts_container]")
|
||||
const scripts = ["deploy-script", "execute-script"]
|
||||
scripts.forEach(sname=>{
|
||||
const script = this.#current.additionalParameters.parameters.find(s => s.name === sname)
|
||||
const code = script.value && script.value.length ? script.value.join("\r\n") : ""
|
||||
const preview = container.querySelector(`details[name=${sname}] textarea[name=preview]`)
|
||||
preview.value = this.codeToPreview(code)
|
||||
})
|
||||
}
|
||||
|
||||
renderScriptContent(scriptname) {
|
||||
const script = this.#current.additionalParameters.parameters.find(s => s.name === scriptname)
|
||||
if (!script) return '';
|
||||
const code = script.value && script.value.length ? script.value.join("\r\n") : ""
|
||||
return `
|
||||
<textarea rows="5" class="script-area form-control ${script.name === val ? '' : 'd-none'}" name="${script.name}">${code}</textarea>
|
||||
<div class="script-area" name="${script.name}">
|
||||
<textarea name="${script.name}" rows="5" class="form-control">${code}</textarea>
|
||||
<span name="preview" class="d-inline-block fst-italic text-muted position-absolute m-0 end-0 px-4 py-2 pe-none">Preview</span>
|
||||
<textarea rows="5" name="preview" class="script-area form-control my-1 text-muted border-0" readonly>${this.codeToPreview(code)}</textarea>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
).join("\n")
|
||||
|
||||
renderScripts(){
|
||||
const scripts = ["deploy-script", "execute-script"]
|
||||
const html = scripts.map(sname=>{
|
||||
return `
|
||||
<details class="card" name="${sname}" title="${this.getLabel("scripts_help")}">
|
||||
<summary class="card-header">
|
||||
<div class="ccp-toolbar-header">
|
||||
<div>
|
||||
<span>${this.getLabel(sname)}</span>
|
||||
</div>
|
||||
</div>
|
||||
</summary>
|
||||
<div class="card-body">
|
||||
${this.renderScriptContent(sname)}
|
||||
</div>
|
||||
</details>
|
||||
`
|
||||
}).join("")
|
||||
return html
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,29 +4,47 @@ class CCPMethodList extends HTMLElement{
|
|||
#rootdoc;
|
||||
#data = null;
|
||||
#filtered;
|
||||
#dragged = null;
|
||||
#searchfield = null;
|
||||
#allowedit = false;
|
||||
#allowexecute = false;
|
||||
#archive = false;
|
||||
#fileupload = null;
|
||||
#archiveupload = null;
|
||||
|
||||
//#fileupload = null;
|
||||
//#archiveupload = null;
|
||||
#serviceurl;
|
||||
#messages = {
|
||||
"en" : {
|
||||
"Methods" : "Methods",
|
||||
"refresh_help" : "Refresh the list of available methods",
|
||||
"Search" : "Search",
|
||||
"count_executable_help" : "Number of executable methods",
|
||||
"count_not_executable_help" : "Number of non executable methods",
|
||||
"export_category_help" : "Export all the methods in this category as JSON files",
|
||||
"export_method_help" : "Export this method as JSON file",
|
||||
"archive_method_help" : "Export this method as JSON file to your workspace",
|
||||
"shared_help" : "This method is shared in this community",
|
||||
"share_help" : "You can share this method in this community",
|
||||
"execute_help" : "Open method's form to set inputs and execute",
|
||||
"edit_help" : "Open method's form for cloning, deriving or editing"
|
||||
}
|
||||
}
|
||||
|
||||
constructor(){
|
||||
super()
|
||||
this.#boot = document.querySelector("d4s-boot-2")
|
||||
this.#serviceurl = this.getAttribute("serviceurl")
|
||||
this.#rootdoc = this.attachShadow({ "mode" : "open"})
|
||||
this.#archive = this.getAttribute("archive")
|
||||
this.render()
|
||||
this.fetchProcesses()
|
||||
}
|
||||
|
||||
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]
|
||||
}
|
||||
|
||||
render(){
|
||||
this.#rootdoc.innerHTML = `
|
||||
<link rel="stylesheet" href="https://cdn.dev.d4science.org/ccp/css/common.css"></link>
|
||||
<link rel="stylesheet" href="https://cdn.cloud.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-process-category-list{
|
||||
|
@ -50,9 +68,9 @@ class CCPMethodList extends HTMLElement{
|
|||
<summary>
|
||||
<h5 class="d-inline mr-2 text-primary"></h5>
|
||||
<div class="float-right d-flex" style="gap:2px">
|
||||
<span name="count_notexecutables" title="Number of non executable methods" class="badge border border-danger text-danger"></span>
|
||||
<span name="count_executables" title="Number of executable methods" class="badge border border-success text-success"></span>
|
||||
<button name="export_category" title="Export whole category" class="btn btn-primary ccp-toolbar-button ccp-toolbar-button-small">
|
||||
<span name="count_notexecutables" title="${this.getLabel('count_not_executable_help')}" class="badge border border-danger text-danger"></span>
|
||||
<span name="count_executables" title="${this.getLabel('count_executable_help')}" class="badge border border-success text-success"></span>
|
||||
<button name="export_category" title="${this.getLabel('export_category_help')}" class="btn btn-primary ccp-toolbar-button ccp-toolbar-button-small">
|
||||
<svg viewBox="0 0 24 24"><g><rect fill="none" height="24" width="24"/></g><g><path d="M18,15v3H6v-3H4v3c0,1.1,0.9,2,2,2h12c1.1,0,2-0.9,2-2v-3H18z M17,11l-1.41-1.41L13,12.17V4h-2v8.17L8.41,9.59L7,11l5,5 L17,11z"/></g></svg>
|
||||
</button>
|
||||
</div>
|
||||
|
@ -64,31 +82,31 @@ class CCPMethodList extends HTMLElement{
|
|||
<span name="version" class="badge badge-primary"></span>
|
||||
<span name="author" class="badge badge-warning"></span>
|
||||
<div class="float-right d-flex" style="gap:3px">
|
||||
<button name="executable" title="Execute" class="btn btn-success ccp-toolbar-button ccp-toolbar-button-small">
|
||||
<button name="executable" title="${this.getLabel("execute_help")}" class="btn btn-success ccp-toolbar-button ccp-toolbar-button-small">
|
||||
<svg viewBox="0 0 48 48" style="fill:white; stroke:white">
|
||||
<path d="M10,10 v28 L38,24 Z"/>
|
||||
</svg>
|
||||
</button>
|
||||
<button name="export_method" title="Export this version" class="btn btn-primary ccp-toolbar-button ccp-toolbar-button-small">
|
||||
<button name="export_method" title="${this.getLabel("export_method_help")}" class="btn btn-primary ccp-toolbar-button ccp-toolbar-button-small">
|
||||
<svg viewBox="0 0 24 24"><g><rect fill="none"/></g><g><path d="M18,15v3H6v-3H4v3c0,1.1,0.9,2,2,2h12c1.1,0,2-0.9,2-2v-3H18z M17,11l-1.41-1.41L13,12.17V4h-2v8.17L8.41,9.59L7,11l5,5 L17,11z"/></g></svg>
|
||||
</button>
|
||||
<button name="edit" title="Edit" class="btn btn-primary ccp-toolbar-button ccp-toolbar-button-small">
|
||||
<button name="edit" title="${this.getLabel("edit_help")}" class="btn btn-primary ccp-toolbar-button ccp-toolbar-button-small">
|
||||
<svg viewBox="0 0 48 48">
|
||||
<path d="M9 39h2.2l22.15-22.15-2.2-2.2L9 36.8Zm30.7-24.3-6.4-6.4 2.1-2.1q.85-.85 2.1-.85t2.1.85l2.2 2.2q.85.85.85 2.1t-.85 2.1Zm-2.1 2.1L12.4 42H6v-6.4l25.2-25.2Zm-5.35-1.05-1.1-1.1 2.2 2.2Z"/>
|
||||
</svg>
|
||||
</button>
|
||||
${ this.#archive ? `
|
||||
<button data-index="0" name="archive" title="Archive to workspace" class="btn btn-primary ccp-toolbar-button ccp-toolbar-button-small">
|
||||
<button data-index="0" name="archive" title="${this.getLabel("archive_help")}" class="btn btn-primary ccp-toolbar-button ccp-toolbar-button-small">
|
||||
<svg viewBox="0 96 960 960"><path d="M140 796h680V516H140v280Zm540.118-90Q701 706 715.5 691.382q14.5-14.617 14.5-35.5Q730 635 715.382 620.5q-14.617-14.5-35.5-14.5Q659 606 644.5 620.618q-14.5 14.617-14.5 35.5Q630 677 644.618 691.5q14.617 14.5 35.5 14.5ZM880 456h-85L695 356H265L165 456H80l142-142q8-8 19.278-13 11.278-5 23.722-5h430q12.444 0 23.722 5T738 314l142 142ZM140 856q-24.75 0-42.375-17.625T80 796V456h800v340q0 24.75-17.625 42.375T820 856H140Z"/></svg>
|
||||
</button>`
|
||||
: ``
|
||||
}
|
||||
<button data-index="0" name="publish" title="Share with vlab" class="btn btn-warning ccp-toolbar-button ccp-toolbar-button-small">
|
||||
<button data-index="0" name="publish" title="${this.getLabel("share_help")}" class="btn btn-warning ccp-toolbar-button ccp-toolbar-button-small">
|
||||
<svg style="fill:black" viewBox="0 -960 960 960"><path d="M727-80q-47.5 0-80.75-33.346Q613-146.693 613-194.331q0-6.669 1.5-16.312T619-228L316-404q-15 17-37 27.5T234-366q-47.5 0-80.75-33.25T120-480q0-47.5 33.25-80.75T234-594q23 0 44 9t38 26l303-174q-3-7.071-4.5-15.911Q613-757.75 613-766q0-47.5 33.25-80.75T727-880q47.5 0 80.75 33.25T841-766q0 47.5-33.25 80.75T727-652q-23.354 0-44.677-7.5T646-684L343-516q2 8 3.5 18.5t1.5 17.741q0 7.242-1.5 15Q345-457 343-449l303 172q15-14 35-22.5t46-8.5q47.5 0 80.75 33.25T841-194q0 47.5-33.25 80.75T727-80Zm.035-632Q750-712 765.5-727.535q15.5-15.535 15.5-38.5T765.465-804.5q-15.535-15.5-38.5-15.5T688.5-804.465q-15.5 15.535-15.5 38.5t15.535 38.465q15.535 15.5 38.5 15.5Zm-493 286Q257-426 272.5-441.535q15.5-15.535 15.5-38.5T272.465-518.5q-15.535-15.5-38.5-15.5T195.5-518.465q-15.5 15.535-15.5 38.5t15.535 38.465q15.535 15.5 38.5 15.5Zm493 286Q750-140 765.5-155.535q15.5-15.535 15.5-38.5T765.465-232.5q-15.535-15.5-38.5-15.5T688.5-232.465q-15.5 15.535-15.5 38.5t15.535 38.465q15.535 15.5 38.5 15.5ZM727-766ZM234-480Zm493 286Z"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<p class="m-0 p-0 small" name="description"></p>
|
||||
<div class="m-0 p-0 small" name="description"></div>
|
||||
<div>
|
||||
<span name="keyword" class="badge badge-pill badge-light border border-dark mr-1" style="opacity:.6"></span>
|
||||
</div>
|
||||
|
@ -105,13 +123,13 @@ class CCPMethodList extends HTMLElement{
|
|||
<div class="card-header">
|
||||
<div class="ccp-toolbar-header d-flex flex-wrap justify-content-between">
|
||||
<div>
|
||||
<span name="header">Methods</span>
|
||||
<span name="header">${this.getLabel("Methods")}</span>
|
||||
</div>
|
||||
<div class="d-flex flex-wrap" style="gap:2px">
|
||||
<button name="refresh" class="btn btn-primary ccp-toolbar-button" title="Refresh">
|
||||
<button name="refresh" class="btn btn-primary ccp-toolbar-button" title="${this.getLabel('refresh_help')}">
|
||||
<svg viewBox="0 0 48 48"><path d="M24 40q-6.65 0-11.325-4.675Q8 30.65 8 24q0-6.65 4.675-11.325Q17.35 8 24 8q4.25 0 7.45 1.725T37 14.45V8h3v12.7H27.3v-3h8.4q-1.9-3-4.85-4.85Q27.9 11 24 11q-5.45 0-9.225 3.775Q11 18.55 11 24q0 5.45 3.775 9.225Q18.55 37 24 37q4.15 0 7.6-2.375 3.45-2.375 4.8-6.275h3.1q-1.45 5.25-5.75 8.45Q29.45 40 24 40Z"/></svg>
|
||||
</button>
|
||||
<label name="fileupload" class="btn btn-primary ccp-toolbar-button m-0" title="Upload from file">
|
||||
<!--label name="fileupload" class="btn btn-primary ccp-toolbar-button m-0" title="Upload from file">
|
||||
<svg viewBox="0 96 960 960"><path d="M452 854h60V653l82 82 42-42-156-152-154 154 42 42 84-84v201ZM220 976q-24 0-42-18t-18-42V236q0-24 18-42t42-18h361l219 219v521q0 24-18 42t-42 18H220Zm331-554V236H220v680h520V422H551ZM220 236v186-186 680-680Z"/></svg>
|
||||
<input type="file" class="d-none" multiple="multiple"/>
|
||||
</label>
|
||||
|
@ -120,13 +138,13 @@ class CCPMethodList extends HTMLElement{
|
|||
<button name="archive" class="btn btn-primary ccp-toolbar-button m-0" title="Upload from link">
|
||||
<svg viewBox="0 96 960 960"><path d="M450 776H280q-83 0-141.5-58.5T80 576q0-83 58.5-141.5T280 376h170v60H280q-58.333 0-99.167 40.765-40.833 40.764-40.833 99Q140 634 180.833 675q40.834 41 99.167 41h170v60ZM324 606v-60h310v60H324Zm556-30h-60q0-58-40.833-99-40.834-41-99.167-41H510v-60h170q83 0 141.5 58.5T880 576ZM699 896V776H579v-60h120V596h60v120h120v60H759v120h-60Z"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="mb-3">
|
||||
<input type="text" name="search" class="form-control" placeholder="Search"/>
|
||||
<input type="text" name="search" class="form-control" placeholder="${this.getLabel("Search")}"/>
|
||||
</div>
|
||||
<div>
|
||||
<ul name="process_category_list"></ul>
|
||||
|
@ -144,29 +162,32 @@ class CCPMethodList extends HTMLElement{
|
|||
this.updateList()
|
||||
})
|
||||
|
||||
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.importMethods(files)
|
||||
}
|
||||
})
|
||||
// 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.importMethods(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("Please confirm importing of method from link?")){
|
||||
this.fromArchive(link)
|
||||
}
|
||||
}
|
||||
})
|
||||
// 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("Please confirm importing of method from link?")){
|
||||
// this.fromArchive(link)
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
}
|
||||
|
||||
connectedCallback(){
|
||||
this.#allowedit = this.getAttribute("allow-edit") === "true"
|
||||
this.#allowexecute = this.getAttribute("allow-execute") === "true"
|
||||
this.#serviceurl = this.getAttribute("serviceurl")
|
||||
this.render()
|
||||
this.fetchProcesses()
|
||||
}
|
||||
|
||||
fetchProcesses(){
|
||||
|
@ -220,9 +241,9 @@ class CCPMethodList extends HTMLElement{
|
|||
const f = filter.toLowerCase()
|
||||
this.#filtered = this.#data.filter(d=>{
|
||||
return false ||
|
||||
(d.title.toLowerCase().indexOf(f) !== -1)||
|
||||
(d.description.indexOf(f) !== -1) ||
|
||||
(d.keywords.map(k=>k.toLowerCase()).filter(i=>i.indexOf(f) !== -1)).length
|
||||
(d.title && d.title.toLowerCase().indexOf(f) !== -1)||
|
||||
(d.description && d.description.indexOf(f) !== -1) ||
|
||||
(Array.isArray(d.keywords) && d.keywords.map(k=>k.toLowerCase()).filter(i=>i.indexOf(f) !== -1)).length
|
||||
})
|
||||
}
|
||||
this.groupBy()
|
||||
|
@ -457,6 +478,7 @@ class CCPMethodList extends HTMLElement{
|
|||
span.classList.add("badge")
|
||||
span.classList.add("badge-warning")
|
||||
span.textContent = "shared"
|
||||
span.alt = span.title = this.getLabel("shared_help")
|
||||
e.replaceWith(span)
|
||||
}else if(!this.isAuthor(d)){
|
||||
e.parentElement.removeChild(e)
|
||||
|
@ -479,9 +501,25 @@ class CCPMethodList extends HTMLElement{
|
|||
apply : (e,d)=>{ e.alt = e.title = e.textContent = d.title }
|
||||
},
|
||||
{
|
||||
target : "p[name=description]",
|
||||
apply : (e,d)=>{ e.textContent = d.description }
|
||||
},
|
||||
target : "div[name=description]",
|
||||
apply : (e,d)=>{
|
||||
const maxchars = 100
|
||||
const maxlines = 2
|
||||
const s = d.description
|
||||
const lines = s.split("\n")
|
||||
if (lines.length <= maxlines && s.length <= maxchars) {
|
||||
e.innerHTML = s.replaceAll("\n", "<br/>")
|
||||
} else if (lines.length > maxlines) {
|
||||
const lines1 = lines.slice(0, maxlines -1)
|
||||
const index = Math.min(maxchars, lines1.join("<br/>").length)
|
||||
const sf = s.replaceAll("\n", "<br/>")
|
||||
e.innerHTML = `<details><summary>${sf.substring(0, index)}...</summary>...${sf.substring(index)}</details>`
|
||||
} else {
|
||||
const sf = s.replaceAll("\n", "<br/>")
|
||||
e.innerHTML = `<details><summary>${sf.substring(0, maxchars)}...</summary>...${sf.substring(maxchars)}</details>`
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
@ -4,7 +4,7 @@ class CCPOutputWidgetController extends HTMLElement {
|
|||
|
||||
constructor(){
|
||||
super()
|
||||
this.#output = JSON.parse(this.getAttribute("output"))
|
||||
this.#output = JSON.parse(atob(this.getAttribute("output")))
|
||||
}
|
||||
|
||||
connectedCallback(){
|
||||
|
@ -22,7 +22,7 @@ class CCPOutputWidgetController extends HTMLElement {
|
|||
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"/>
|
||||
<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>
|
||||
`
|
||||
|
|
|
@ -3,13 +3,32 @@ class CCPOutputWidgetEditorController extends HTMLElement {
|
|||
#output = null
|
||||
#index = null
|
||||
#type = null
|
||||
|
||||
#messages = {
|
||||
"en" : {
|
||||
"output_id_help" : "The Id of the output. This has to be unique accross all the outputs the method",
|
||||
"output_delete_help" : "Delete this output",
|
||||
"output_title_help" : "Title of the output. This is how the output will appear in forms.",
|
||||
"output_description_help" : "A description for this output",
|
||||
"min_occurs_help" : "Minimum cardinality",
|
||||
"max_occurs_help" : "Msximum cardinality",
|
||||
"mimetype_help" : "Set MIME type of expected output",
|
||||
"file_path" : "Path to file",
|
||||
"file_path_help" : "Define the path to the file holding the output. This documents the location of the output in the zip archive returned by the execution. Paths in the execution runtime instead are infrastructure dependant."
|
||||
}
|
||||
}
|
||||
#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>
|
||||
`
|
||||
|
||||
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()
|
||||
}
|
||||
|
@ -29,38 +48,38 @@ class CCPOutputWidgetEditorController extends HTMLElement {
|
|||
this.innerHTML = `
|
||||
<details>
|
||||
<summary class="mb-3">
|
||||
<input class="form-control" style="width:auto;display:inline" required="required" name="id" value="${this.#output.id}" title="Id of output"/>
|
||||
<button data-index="${this.#index}" name="delete-output" title="Delete" class="btn btn-danger ccp-toolbar-button">
|
||||
<input class="form-control" style="width:auto;display:inline" required="required" name="id" value="${this.#output.id}" title="${this.getLabel('output_id_help')}"/>
|
||||
<button data-index="${this.#index}" name="delete-output" title="${this.getLabel('output_delete_help')}" 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" title="Title">
|
||||
<div class="form-field" title="${this.getLabel('output_title_help')}">
|
||||
<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" title="Description">
|
||||
<input name="description" class="form-control" placeholder="description" value="${this.#output.description}" required="required"/>
|
||||
<div class="form-field" title="${this.getLabel('output_description_help')}">
|
||||
<textarea rows="1" name="description" class="form-control" placeholder="description" required="required">${this.#output.description}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="row mb-3 d-none">
|
||||
<div class="col">
|
||||
<div class="form-field" title="Mininum cardinality">
|
||||
<div class="form-field" title="${this.getLabel("min_occurs_help")}">
|
||||
<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" title="Maximum cardinality">
|
||||
<div class="form-field" title="${this.getLabel("max_occurs_help")}">
|
||||
<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="col d-none">
|
||||
<div class="form-field" title="Type">
|
||||
<select name="type" class="form-control" placeholder="type">
|
||||
<option value="string">String</option>
|
||||
|
@ -68,7 +87,7 @@ class CCPOutputWidgetEditorController extends HTMLElement {
|
|||
</div>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<div class="form-field" title="Mime type">
|
||||
<div class="form-field" title="${this.getLabel("mimetype_help")}">
|
||||
<input value="${this.#output.schema.contentMediaType}" name="contentMediaType" class="form-control" placeholder="mime"/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -110,8 +129,9 @@ class CCPOutputWidgetEditorController extends HTMLElement {
|
|||
if(this.#output.metadata && this.#output.metadata.length > 0){
|
||||
return `
|
||||
<div class="col">
|
||||
<div class="form-field" title="File path">
|
||||
<input value="${this.#output.metadata[0].href}" name="href" class="form-control" placeholder="file path"/>
|
||||
<div class="form-field" title="${this.getLabel("file_path_help")}">
|
||||
|
||||
<input value="${this.#output.metadata[0].href}" name="href" class="form-control" placeholder="${this.getLabel("file_path")}"/>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
|
|
|
@ -217,6 +217,10 @@
|
|||
} else {
|
||||
kc.enableLogging = false;
|
||||
}
|
||||
|
||||
if (typeof initOptions.scope === 'string') {
|
||||
kc.scope = initOptions.scope;
|
||||
}
|
||||
}
|
||||
|
||||
if (!kc.responseMode) {
|
||||
|
@ -438,15 +442,13 @@
|
|||
baseUrl = kc.endpoints.authorize();
|
||||
}
|
||||
|
||||
var scope;
|
||||
if (options && options.scope) {
|
||||
if (options.scope.indexOf("openid") != -1) {
|
||||
scope = options.scope;
|
||||
} else {
|
||||
scope = "openid " + options.scope;
|
||||
}
|
||||
} else {
|
||||
var scope = options && options.scope || kc.scope;
|
||||
if (!scope) {
|
||||
// if scope is not set, default to "openid"
|
||||
scope = "openid";
|
||||
} else if (scope.indexOf("openid") === -1) {
|
||||
// if openid scope is missing, prefix the given scopes with it
|
||||
scope = "openid " + scope;
|
||||
}
|
||||
|
||||
var url = baseUrl
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<script src="js/rolescontroller.js"></script>
|
||||
<script src="../common/js/keycloak.js" type="text/javascript"></script>
|
||||
<script src="../boot/d4s-boot.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<d4s-boot-2 context="%2Fgcube%2Fdevsec%2FCCP"
|
||||
gateway="next.dev.d4science.org"
|
||||
redirect-url="http://localhost:8080"
|
||||
url="https://accounts.dev.d4science.org/auth">
|
||||
</d4s-boot-2>
|
||||
<d4s-extapp-role-manager groupid="8131621c-f896-4d41-81fd-3f6294bf1376" appid="076bfe7d-12a2-41d1-a720-05911f0ae527"></d4s-extapp-role-manager>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,246 @@
|
|||
class ExtAppRoleController extends HTMLElement{
|
||||
|
||||
#boot;
|
||||
#roles;
|
||||
#users;
|
||||
#serviceurl;
|
||||
#appid;
|
||||
#groupid;
|
||||
#loading = false;
|
||||
|
||||
#header;
|
||||
#section;
|
||||
|
||||
#userfilter = null;
|
||||
#rolefilter = null;
|
||||
|
||||
constructor(){
|
||||
super()
|
||||
this.attachShadow({ mode: "open" });
|
||||
}
|
||||
|
||||
connectedCallback(){
|
||||
this.#appid = this.getAttribute("appid")
|
||||
this.#groupid = this.getAttribute("groupid")
|
||||
this.#boot = document.querySelector("d4s-boot-2")
|
||||
this.#serviceurl = this.#boot.url
|
||||
if(!this.#loading){
|
||||
this.#loading = true
|
||||
this.loadData()
|
||||
}
|
||||
this.renderStructure()
|
||||
}
|
||||
|
||||
renderStructure(){
|
||||
this.shadowRoot.innerHTML = `
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous"/>
|
||||
<style>
|
||||
.dyn-opacity{
|
||||
opacity: .8;
|
||||
transition: opacity .5s;
|
||||
}
|
||||
.dyn-opacity:hover{
|
||||
opacity: 1;
|
||||
}
|
||||
</style>
|
||||
<header></header>
|
||||
<section></section>
|
||||
`
|
||||
this.#header = this.shadowRoot.querySelector("header")
|
||||
this.#section = this.shadowRoot.querySelector("section")
|
||||
}
|
||||
|
||||
renderHeader(){
|
||||
this.#header.innerHTML = `
|
||||
<div class="row g-0 mb-3" style="gap: 1rem;align-items:baseline;">
|
||||
<input class="col-3 form-control" id="search_user_filter" placeholder="Search by name" style="min-width: 10rem;max-width: 100rem;width: 30rem">
|
||||
<div class="role-filter col-9 d-flex" style="gap:1rem;">
|
||||
${
|
||||
this.#roles.map(r=>{
|
||||
return r.name === 'uma_protection' ? '' : `
|
||||
<div class="form-check form-switch">
|
||||
<label class="form-check-label">
|
||||
<input class="form-check-input" type="checkbox" name="${r.id}">
|
||||
<span>${r.name}</span>
|
||||
</label>
|
||||
</div>
|
||||
`
|
||||
}).join("")
|
||||
}
|
||||
</div>
|
||||
</div>`
|
||||
this.attachHeaderEvents()
|
||||
}
|
||||
|
||||
renderUsers(){
|
||||
var users = this.#rolefilter && this.#rolefilter.length ?
|
||||
this.#users.filter(u=>{
|
||||
const assigned = u.mappings.map(m=>m.id)
|
||||
return this.#rolefilter.reduce((a,f)=>{
|
||||
return a && (assigned.indexOf(f) !== -1)
|
||||
}, true)
|
||||
}) : this.#users
|
||||
users = this.#userfilter ?
|
||||
this.#users.filter(u=>{
|
||||
return u.username.toLowerCase().startsWith(this.#userfilter) ||
|
||||
u.lastName.toLowerCase().startsWith(this.#userfilter) ||
|
||||
u.firstName.toLowerCase().startsWith(this.#userfilter) ||
|
||||
u.email.toLowerCase().startsWith(this.#userfilter)}) : users
|
||||
const html = `
|
||||
<ul name="users" style="list-style:none" class="d-flex flex-row flex-wrap gap-2 p-0">
|
||||
${users.map( u=>{
|
||||
return `
|
||||
<li name="user" data-userid="${u.id}" style="user-select:none">
|
||||
<div class="card" style="width:20rem;height:10rem">
|
||||
<div class="card-header">
|
||||
<span name="name" class="fw-bold">${u.firstName} ${u.lastName}</span>
|
||||
</div>
|
||||
<div class="card-body d-flex" style="overflow-y:auto;padding:.5rem">
|
||||
<ul name="roles" style="list-style:none;min-width:70%;overflow-y:auto;align-items: flex-start" class="d-flex flex-row flex-wrap gap-1 pt-1 pb-1 ps-0 me-2">
|
||||
${this.renderMappings(u)}
|
||||
</ul>
|
||||
</divExtAppRoleController>
|
||||
</div>
|
||||
</li>`
|
||||
}).join('')}
|
||||
</li>
|
||||
</ul>
|
||||
`
|
||||
this.#section.innerHTML = html
|
||||
this.attachSectionEvents()
|
||||
}
|
||||
|
||||
renderMappings(u){
|
||||
const html = `
|
||||
<ul name="roles" style="list-style:none;min-width:70%;overflow-y:auto" class="d-flex flex-row flex-wrap gap-1 pt-1 pb-1 ps-0 me-2">
|
||||
${this.#roles ? this.#roles.map(r=>{
|
||||
const assigned = this.isAssigned(u, r)
|
||||
return r.name !== 'uma_protection' ? `
|
||||
<li data-userid="${u.id}" data-roleid="${r.id}" name="role" class="dyn-opacity btn m-0 p-0 ${assigned ? 'active' : ''}">
|
||||
<span style="pointer-events: none" name="name" class="badge bg-${assigned ? 'success' : 'secondary'}">${r.name}</span>
|
||||
</li>` : ''
|
||||
}).join('') : ''}
|
||||
</ul>
|
||||
`
|
||||
return html
|
||||
}
|
||||
|
||||
isAssigned(u, r){
|
||||
return u.mappings && u.mappings.filter(m=>m.id === r.id).length > 0
|
||||
}
|
||||
|
||||
attachSectionEvents(){
|
||||
this.#section.querySelector("ul[name=users]").addEventListener("click", ev=>{
|
||||
const tgt = ev.target
|
||||
if(tgt.getAttribute("name") === "role"){
|
||||
const uid = tgt.getAttribute("data-userid")
|
||||
const rid = tgt.getAttribute("data-roleid")
|
||||
if(tgt.classList.contains("active")){
|
||||
this.toggleMapping(uid, rid)
|
||||
}else{
|
||||
this.toggleMapping(uid, rid, true)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
attachHeaderEvents(){
|
||||
this.#header.querySelector("#search_user_filter").addEventListener("input", ev=>{
|
||||
this.#userfilter = ev.target.value.toLowerCase()
|
||||
this.renderUsers()
|
||||
})
|
||||
|
||||
this.#header.querySelector("div.role-filter").addEventListener("change", ev=>{
|
||||
this.#rolefilter = Array.prototype.slice.call(ev.currentTarget.querySelectorAll("input:checked")).map(r=>r.name)
|
||||
this.renderUsers()
|
||||
})
|
||||
}
|
||||
|
||||
loadData(){
|
||||
Promise.all([
|
||||
this.loadEligibleUsers(), this.loadRoles()
|
||||
]).then(()=>{
|
||||
//console.log("Done. Ui should be complete now")
|
||||
}).catch(err=>alert(err))
|
||||
}
|
||||
|
||||
loadEligibleUsers(){
|
||||
const url = this.#serviceurl + `/admin/realms/d4science/groups/${this.#groupid}/members?first=0&max=-1&briefRepresentation=true`
|
||||
return this.#boot.secureFetch(url).then(resp=>{
|
||||
if(resp.ok){
|
||||
return resp.json()
|
||||
}else if(resp.status === 403){
|
||||
throw "Fetching users: You are not allowed to manage roles for this application."
|
||||
}else{
|
||||
throw "Fetching users: Unspecified error"
|
||||
}
|
||||
}).then(json=>{
|
||||
this.#users = json
|
||||
const prom = this.loadMappings()
|
||||
this.renderUsers()
|
||||
return prom
|
||||
})
|
||||
}
|
||||
|
||||
loadRoles(){
|
||||
const url = this.#serviceurl + `/admin/realms/d4science/clients/${this.#appid}/roles`
|
||||
return this.#boot.secureFetch(url).then(resp=>{
|
||||
if(resp.ok){
|
||||
return resp.json()
|
||||
}else if(resp.status === 403){
|
||||
throw "Fetching roles: You are not allowed to manage roles for this application."
|
||||
}else{
|
||||
throw "Fetching roles: Unspecified error"
|
||||
}
|
||||
}).then(json=>{
|
||||
this.#roles = json
|
||||
this.renderHeader()
|
||||
})
|
||||
}
|
||||
|
||||
loadMappings(){
|
||||
return Promise.all(
|
||||
this.#users.map(u => {
|
||||
return this.loadMapping(u)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
loadMapping(u){
|
||||
const url = this.#serviceurl + `/admin/realms/d4science/users/${u.id}/role-mappings/clients/${this.#appid}`
|
||||
return this.#boot.secureFetch(url).then(resp=>{
|
||||
if(resp.ok){
|
||||
return resp.json()
|
||||
}else if(resp.status === 403){
|
||||
throw "Fetching role mappings: You are not allowed to manage roles for this application."
|
||||
}else{
|
||||
throw "Fetching role mappings: Unspecified error"
|
||||
}
|
||||
}).then(json=>{
|
||||
u.mappings = json
|
||||
this.renderUsers()
|
||||
})
|
||||
}
|
||||
|
||||
toggleMapping(uid, rid, add){
|
||||
const url = this.#serviceurl + `/admin/realms/d4science/users/${uid}/role-mappings/clients/${this.#appid}`
|
||||
const role = this.#roles.filter(r=>r.id === rid)
|
||||
if(role.length !== 1){
|
||||
alert("Something went wrong with looking up the role")
|
||||
return
|
||||
}
|
||||
this.#boot.secureFetch(url, { method : add ? "POST" : "DELETE", body : JSON.stringify(role), headers : { "Content-Type" : "application/json"}}).then(resp=>{
|
||||
if(resp.ok){
|
||||
return resp.text()
|
||||
}else if(resp.status === 403){
|
||||
throw "Assigning or removing role: You are not allowed to manage roles for this application."
|
||||
}else{
|
||||
throw "Assigning or removing role: Unspecified error"
|
||||
}
|
||||
}).then(()=>{
|
||||
return this.loadMapping(this.#users.filter(u=>u.id === uid)[0])
|
||||
}).catch(err=>alert(err))
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define('d4s-extapp-role-manager', ExtAppRoleController);
|
|
@ -12,10 +12,13 @@
|
|||
<h3>CDN sandbox</h3>
|
||||
<p>Select the section you want to enter</p>
|
||||
</header>
|
||||
<div class="d-flex">
|
||||
<div class="d-flex flex-column">
|
||||
<div class="d-inline-block">
|
||||
<a href="storage/index.html">D4S Workspace</a> (OIDC protected)
|
||||
</div>
|
||||
<div class="d-inline-block">
|
||||
<a href="ccp/index.html">CCP</a> (OIDC protected)
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
|
|
|
@ -16,10 +16,7 @@ class D4SStorageHtmlElement extends HTMLElement {
|
|||
}
|
||||
|
||||
connectedCallback() {
|
||||
let linkElem = document.createElement('link');
|
||||
linkElem.setAttribute('rel', 'stylesheet');
|
||||
linkElem.setAttribute('href', 'https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css');
|
||||
this.shadowRoot.appendChild(linkElem);
|
||||
|
||||
}
|
||||
|
||||
get srcBaseURL() {
|
||||
|
@ -98,7 +95,7 @@ class D4SStorageToolbar extends D4SStorageHtmlElement {
|
|||
|
||||
class D4SStorageTree extends D4SStorageHtmlElement {
|
||||
|
||||
static tree_event_name = "d4s-toolbar";
|
||||
static tree_event_name = "d4s-tree";
|
||||
static folder_data_event_name = "d4s-folder-data";
|
||||
|
||||
static dataid_attr = 'data-id';
|
||||
|
@ -226,8 +223,10 @@ class D4SStorageTree extends D4SStorageHtmlElement {
|
|||
|
||||
const div = document.createElement('div')
|
||||
div.innerHTML = /*css*/`
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
|
||||
<style>
|
||||
.selected > span {
|
||||
.selected > .li-title {
|
||||
background-color: ${this.#selectedbgcolor};
|
||||
}
|
||||
ul.root {
|
||||
|
@ -238,6 +237,12 @@ class D4SStorageTree extends D4SStorageHtmlElement {
|
|||
margin-bottom: 0;
|
||||
padding-left: 0.6em;
|
||||
}
|
||||
li{
|
||||
color: #444444;
|
||||
}
|
||||
li:hover{
|
||||
color: #555555;
|
||||
}
|
||||
li {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
@ -378,7 +383,7 @@ class D4SStorageTree extends D4SStorageHtmlElement {
|
|||
this.dispatchEvent(
|
||||
new CustomEvent(
|
||||
D4SStorageTree.tree_event_name,
|
||||
{detail: {id: id}}
|
||||
{detail: {id: id, bubbles: true}}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -417,15 +422,43 @@ class D4SStorageTree extends D4SStorageHtmlElement {
|
|||
data
|
||||
.filter(item => this.showFiles || item['@class'].includes('FolderItem'))
|
||||
.forEach(item => {
|
||||
ul.appendChild(this.createListItem(item.name, item.id, item['@class'], parentId));
|
||||
ul.appendChild(this.createListItem(item, parentId));
|
||||
})
|
||||
} else {
|
||||
ul.appendChild(this.createListItem(data.displayName ? data.displayName : data.name, data.id, data['@class'], parentId));
|
||||
ul.appendChild(this.createListItem(data, parentId));
|
||||
}
|
||||
parentElement.appendChild(ul);
|
||||
}
|
||||
|
||||
createListItem(label, id, type, parentId) {
|
||||
getIconByMIME(mime){
|
||||
if(mime.startsWith("image")) return "image";
|
||||
if(mime.startsWith("application/pdf")) return "picture_as_pdf";
|
||||
if(mime.startsWith("application/zip")) return "archive";
|
||||
if(mime.startsWith("application/xml")) return "code";
|
||||
if(mime.match(/tar|compressed|gzip|gz|tgz/)) return 'archive';
|
||||
return null;
|
||||
}
|
||||
|
||||
getIconByExtension(name){
|
||||
const tks = name.split(".")
|
||||
if(tks.length === 0) return null;
|
||||
const ext = tks[tks.length - 1]
|
||||
if(ext.match(/java$|py$|^r$|^c$|ccp|lua|jl|^sh$|json|xml|xsl|xslt|md$|xq$|xqm$/i)) return "code";
|
||||
if(ext.match(/js$/i)) return "javascript";
|
||||
if(ext.match(/html$|css$|php$/i)) return ext;
|
||||
if(ext.match(/kml$/i)) return "place";
|
||||
if(ext.match(/ics$/i)) return "event";
|
||||
if(ext.match(/csv$|xls$|ods$/i)) return "assessment";
|
||||
if(ext.match(/txt$|odt$|rtf$|doc$|docx$/i)) return "description";
|
||||
if(ext.match(/ppt$|odp$/i)) return "slideshow";
|
||||
return null;
|
||||
}
|
||||
|
||||
createListItem(item, parentId) {
|
||||
const label = item.displayName ? item.displayName : item.title
|
||||
const id = item.id
|
||||
const type = item["@class"]
|
||||
const mime = item.content && item.content.mimeType ? item.content.mimeType : null
|
||||
const li = document.createElement('li');
|
||||
li.setAttribute(D4SStorageTree.dataname_attr, label);
|
||||
li.setAttribute(D4SStorageTree.dataid_attr, id);
|
||||
|
@ -434,14 +467,30 @@ class D4SStorageTree extends D4SStorageHtmlElement {
|
|||
if (parentId) {
|
||||
li.setAttribute(D4SStorageTree.parentid_attr, parentId);
|
||||
}
|
||||
const icon = type.includes('FolderItem') ? "folder.svg" : "file-earmark.svg"
|
||||
//const icon = type.includes('FolderItem') || type.includes("SharedFolder")? "folder.svg" : "file-earmark.svg"
|
||||
var icon = "insert_drive_file";
|
||||
if(type.includes('FolderItem')){
|
||||
icon = "folder"
|
||||
}else if(type.includes("SharedFolder")){
|
||||
icon = "folder_shared"
|
||||
}else{
|
||||
const im = mime ? this.getIconByMIME(mime) : null
|
||||
if(im) icon = im;
|
||||
else{
|
||||
const ie = this.getIconByExtension(label)
|
||||
if(ie) icon = ie;
|
||||
}
|
||||
}
|
||||
li.innerHTML = `
|
||||
<img class="px-1" src="${this.srcBaseURL}/img/${icon}"</img>
|
||||
<div class="li-title d-flex gap-1">
|
||||
<span class="material-icons">${icon}</span>
|
||||
<span>${label}</span>
|
||||
</div>
|
||||
`
|
||||
|
||||
li.addEventListener('click', (ev) => {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault()
|
||||
this.select(ev.currentTarget.getAttribute(D4SStorageTree.dataid_attr));
|
||||
});
|
||||
|
||||
|
@ -668,6 +717,7 @@ class D4SStorageFolder extends D4SStorageHtmlElement {
|
|||
const isFolder = item['@class'].includes('FolderItem')
|
||||
filename.addEventListener('click', (ev) => {
|
||||
ev.stopPropagation()
|
||||
ev.preventDefault()
|
||||
if (isFolder) {
|
||||
const span = ev.currentTarget.querySelector(':nth-child(2)')
|
||||
if (span) {
|
||||
|
@ -786,7 +836,7 @@ class D4SWorkspace {
|
|||
if (uri) {
|
||||
url += uri.startsWith('/') ? uri : '/' + uri;
|
||||
}
|
||||
console.log("Invoking WS API with: " + url);
|
||||
//console.log("Invoking WS API with: " + url);
|
||||
return this.#d4sboot.secureFetch(url, req)
|
||||
.then(resp => {
|
||||
if (resp.ok) {
|
||||
|
@ -840,7 +890,7 @@ class D4SWorkspace {
|
|||
* @throws the error as string, if occurred
|
||||
*/
|
||||
listFolder(folderId, extraHeaders) {
|
||||
const uri = "/items/" + folderId + "/children";
|
||||
const uri = "/items/" + folderId + "/children?exclude=hl:accounting";
|
||||
return this.#callWorkspace("GET", uri, null, null, extraHeaders)
|
||||
.then(blob => {
|
||||
return blob.text().then(text => {
|
||||
|
@ -971,7 +1021,7 @@ class D4SWorkspace {
|
|||
tmplnk.click();
|
||||
document.body.removeChild(tmplnk);
|
||||
} else {
|
||||
console.log("Skipping local download");
|
||||
//console.log("Skipping local download");
|
||||
}
|
||||
return data;
|
||||
}).catch(err => {
|
||||
|
|
|
@ -10,8 +10,8 @@
|
|||
|
||||
<body class="m-4">
|
||||
<d4s-boot-2 context="%2Fgcube%2Fdevsec%2FdevVRE"
|
||||
gateway="next.d4science.org"
|
||||
redirect-url="https://cdn.dev.d4science.org/storage/"
|
||||
gateway="next.dev.d4science.org"
|
||||
redirect-url="https://cdn.cloud-dev.d4science.org/storage/"
|
||||
url="https://accounts.dev.d4science.org/auth">
|
||||
<!-- redirect-url="http://localhost:8080/storage/" -->
|
||||
<div class="row h-100">
|
||||
|
|
Loading…
Reference in New Issue