2023-06-13 00:32:15 +01:00
<!DOCTYPE html>
2023-06-13 19:18:40 +01:00
< html th:lang = "${#locale.toString()}"
th:lang-direction = "#{language.direction}"
xmlns:th = "http://www.thymeleaf.org" >
2023-06-13 00:32:15 +01:00
< th:block th:insert = "~{fragments/common :: head(title=#{merge.title})}" ></ th:block >
< body >
2023-06-21 21:41:58 +01:00
< style >
. modal {
display : none ; /* Hidden by default */
position : fixed ; /* Stay in place */
z-index : 1 ; /* Sit on top */
padding-top : 100 px ; /* Location of the box */
left : 0 ;
top : 0 ;
width : 100 % ; /* Full width */
height : 100 % ; /* Full height */
overflow : auto ; /* Enable scroll if needed */
background-color : rgb ( 0 , 0 , 0 ); /* Fallback color */
background-color : rgba ( 0 , 0 , 0 , 0.4 ); /* Black w/ opacity */
}
2023-06-13 19:18:40 +01:00
2023-06-21 21:41:58 +01:00
/* Modal Content */
. modal-content {
background-color : #fefefe ;
margin : auto ;
padding : 20 px ;
border : 1 px solid #888 ;
width : 50 % ;
}
2023-06-13 19:18:40 +01:00
2023-06-21 21:41:58 +01:00
. btn-margin {
margin-right : 2 px ;
}
2023-06-13 19:18:40 +01:00
2023-06-21 21:41:58 +01:00
. modal-body {
display : flex ;
flex-direction : column ;
}
</ style >
2023-06-13 19:18:40 +01:00
2023-06-21 21:41:58 +01:00
< div id = "page-container" >
< div id = "content-wrap" >
< div th:insert = "~{fragments/navbar.html :: navbar}" ></ div >
< div class = "container" id = "dropContainer" >
< div class = "row justify-content-center" >
< div class = "col-md-6" >
< div class = "mb-3" >
< button id = "savePipelineBtn" class = "btn btn-success" > Save Configuration</ button >
< button id = "validateButton" class = "btn btn-success" > Validate</ button >
< div class = "btn-group" >
< button id = "uploadPipelineBtn" class = "btn btn-primary" > Upload Configuration</ button >
< input type = "file" id = "uploadPipelineInput" accept = ".json" style = "display: none;" >
</ div >
</ div >
< div id = "pipelineContainer" class = "card" >
<!-- Pipeline Configuration Card Header -->
< div class = "card-header" >
< h2 class = "card-title" > Pipeline Configuration</ h2 >
</ div >
<!-- Pipeline Configuration Body -->
< div class = "card-body" >
< div class = "mb-3" >
< select id = "operationsDropdown" class = "form-select" >
<!-- Options will be dynamically populated here -->
</ select >
</ div >
2023-06-13 19:18:40 +01:00
2023-06-21 21:41:58 +01:00
< div class = "mb-3" >
< button id = "addOperationBtn" class = "btn btn-primary" > Add operation to pipeline</ button >
</ div >
< h3 > Pipeline:</ h3 >
< ol id = "pipelineList" class = "list-group" >
<!-- Pipeline operations will be dynamically populated here -->
</ ol >
</ div >
< div th:replace = "~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}" ></ div >
< button class = "btn btn-primary" id = "submitConfigBtn" > Submit</ button >
</ div >
<!-- pipelineSettings modal -->
< div id = "pipelineSettingsModal" class = "modal" >
< div class = "modal-content" >
< div class = "modal-body" >
< span class = "close" > × </ span >
< h2 > Operation Settings</ h2 >
< div id = "pipelineSettingsContent" >
<!-- pipelineSettings will be dynamically populated here -->
</ div >
</ div >
</ div >
</ div >
2023-06-13 19:18:40 +01:00
2023-06-13 00:32:15 +01:00
2023-06-13 19:18:40 +01:00
< script >
2023-06-13 00:32:15 +01:00
2023-06-20 23:49:53 +01:00
document . getElementById ( 'validateButton' ). addEventListener ( 'click' , function ( event ) {
event . preventDefault ();
validatePipeline ();
});
function validatePipeline () {
let pipelineListItems = document . getElementById ( 'pipelineList' ). children ;
let isValid = true ;
2023-06-21 21:19:52 +01:00
let containsAddPassword = false ;
2023-06-20 23:49:53 +01:00
for ( let i = 0 ; i < pipelineListItems . length - 1 ; i ++ ) {
let currentOperation = pipelineListItems [ i ]. querySelector ( '.operationName' ). textContent ;
let nextOperation = pipelineListItems [ i + 1 ]. querySelector ( '.operationName' ). textContent ;
2023-06-21 21:19:52 +01:00
if ( currentOperation === '/add-password' ){
containsAddPassword = true ;
2023-06-20 23:49:53 +01:00
}
2023-06-21 21:19:52 +01:00
console . log ( currentOperation );
console . log ( apiDocs [ currentOperation ]);
let currentOperationDescription = apiDocs [ currentOperation ] ? . post ? . description || "" ;
let nextOperationDescription = apiDocs [ nextOperation ] ? . post ? . description || "" ;
console . log ( "currentOperationDescription" , currentOperationDescription );
console . log ( "nextOperationDescription" , nextOperationDescription );
let currentOperationOutput = currentOperationDescription . match ( /Output:([A-Z\/]*)/ ) ? .[ 1 ] || "" ;
let nextOperationInput = nextOperationDescription . match ( /Input:([A-Z\/]*)/ ) ? .[ 1 ] || "" ;
console . log ( "Operation " + currentOperation + " Output: " + currentOperationOutput );
console . log ( "Operation " + nextOperation + " Input: " + nextOperationInput );
// Splitting in case of multiple possible output/input
let currentOperationOutputArr = currentOperationOutput . split ( '/' );
let nextOperationInputArr = nextOperationInput . split ( '/' );
if ( currentOperationOutput !== 'ANY' && nextOperationInput !== 'ANY' ) {
let intersection = currentOperationOutputArr . filter ( value => nextOperationInputArr . includes ( value ));
console . log ( `Intersection: ${ intersection } ` );
if ( intersection . length === 0 ) {
isValid = false ;
console . log ( `Incompatible operations: The output of operation ' ${ currentOperation } ' ( ${ currentOperationOutput } ) is not compatible with the input of the following operation ' ${ nextOperation } ' ( ${ nextOperationInput } ).` );
alert ( `Incompatible operations: The output of operation ' ${ currentOperation } ' ( ${ currentOperationOutput } ) is not compatible with the input of the following operation ' ${ nextOperation } ' ( ${ nextOperationInput } ).` );
break ;
}
2023-06-20 23:49:53 +01:00
}
}
2023-06-21 21:19:52 +01:00
if ( containsAddPassword && pipelineListItems [ pipelineListItems . length - 1 ]. querySelector ( '.operationName' ). textContent !== '/add-password' ) {
alert ( 'The "add-password" operation should be at the end of the operations sequence. Please adjust the operations order.' );
return false ;
}
2023-06-20 23:49:53 +01:00
if ( isValid ) {
console . log ( 'Pipeline is valid' );
// Continue with the pipeline operation
} else {
console . error ( 'Pipeline is not valid' );
// Stop operation, maybe display an error to the user
}
2023-06-21 21:19:52 +01:00
return isValid ;
2023-06-20 23:49:53 +01:00
}
2023-06-21 21:19:52 +01:00
2023-06-20 23:49:53 +01:00
2023-06-13 23:50:46 +01:00
document . getElementById ( 'submitConfigBtn' ). addEventListener ( 'click' , function () {
2023-06-21 21:19:52 +01:00
if ( validatePipeline () === false ){
return ;
}
2023-06-13 23:50:46 +01:00
let selectedOperation = document . getElementById ( 'operationsDropdown' ). value ;
let parameters = operationSettings [ selectedOperation ] || {};
let pipelineConfig = {
"name" : "uniquePipelineName" ,
"pipeline" : [{
"operation" : selectedOperation ,
"parameters" : parameters
}]
};
let pipelineConfigJson = JSON . stringify ( pipelineConfig , null , 2 );
let formData = new FormData ();
let fileInput = document . getElementById ( 'fileInput' );
let files = fileInput . files ;
for ( let i = 0 ; i < files . length ; i ++ ) {
console . log ( "files[i]" , files [ i ]. name );
formData . append ( 'fileInput' , files [ i ], files [ i ]. name );
}
console . log ( "pipelineConfigJson" , pipelineConfigJson );
formData . append ( 'json' , pipelineConfigJson );
console . log ( "formData" , formData );
2023-06-20 23:49:53 +01:00
2023-06-13 23:50:46 +01:00
fetch ( '/handleData' , {
method : 'POST' ,
body : formData
})
. then ( response => response . blob ())
. then ( blob => {
2023-06-20 23:49:53 +01:00
2023-06-13 23:50:46 +01:00
let url = window . URL . createObjectURL ( blob );
let a = document . createElement ( 'a' );
a . href = url ;
2023-06-20 23:49:53 +01:00
a . download = 'outputfile' ;
document . body . appendChild ( a );
2023-06-13 23:50:46 +01:00
a . click ();
2023-06-20 23:49:53 +01:00
a . remove ();
2023-06-13 23:50:46 +01:00
})
. catch (( error ) => {
console . error ( 'Error:' , error );
});
});
2023-06-13 00:32:15 +01:00
let apiDocs = {};
2023-06-20 23:49:53 +01:00
2023-06-13 19:18:40 +01:00
let operationSettings = {};
2023-06-13 00:32:15 +01:00
fetch ( 'v3/api-docs' )
. then ( response => response . json ())
. then ( data => {
apiDocs = data . paths ;
let operationsDropdown = document . getElementById ( 'operationsDropdown' );
operationsDropdown . innerHTML = '' ;
Object . keys ( apiDocs ). forEach ( operation => {
2023-06-21 21:19:52 +01:00
if ( apiDocs [ operation ]. hasOwnProperty ( 'post' )) {
let option = document . createElement ( 'option' );
option . textContent = operation ;
operationsDropdown . appendChild ( option );
}
2023-06-13 00:32:15 +01:00
});
});
document . getElementById ( 'addOperationBtn' ). addEventListener ( 'click' , function () {
let selectedOperation = document . getElementById ( 'operationsDropdown' ). value ;
let pipelineList = document . getElementById ( 'pipelineList' );
let listItem = document . createElement ( 'li' );
listItem . className = "list-group-item" ;
listItem . innerHTML = `
<div class="d-flex justify-content-between align-items-center w-100">
<div class="operationName"> ${ selectedOperation } </div>
<div class="arrows d-flex">
<button class="btn btn-secondary move-up btn-margin"><span>↑</span></button>
<button class="btn btn-secondary move-down btn-margin"><span>↓</span></button>
<button class="btn btn-warning pipelineSettings btn-margin"><span>⚙️</span></button>
<button class="btn btn-danger remove"><span>X</span></button>
</div>
</div>
` ;
pipelineList . appendChild ( listItem );
listItem . querySelector ( '.move-up' ). addEventListener ( 'click' , function ( event ) {
event . preventDefault ();
if ( listItem . previousElementSibling ) {
pipelineList . insertBefore ( listItem , listItem . previousElementSibling );
}
});
listItem . querySelector ( '.move-down' ). addEventListener ( 'click' , function ( event ) {
event . preventDefault ();
if ( listItem . nextElementSibling ) {
pipelineList . insertBefore ( listItem . nextElementSibling , listItem );
}
});
listItem . querySelector ( '.remove' ). addEventListener ( 'click' , function ( event ) {
event . preventDefault ();
pipelineList . removeChild ( listItem );
});
2023-06-20 23:49:53 +01:00
2023-06-13 00:32:15 +01:00
listItem . querySelector ( '.pipelineSettings' ). addEventListener ( 'click' , function ( event ) {
event . preventDefault ();
showpipelineSettingsModal ( selectedOperation );
});
2023-06-20 23:49:53 +01:00
2023-06-13 00:32:15 +01:00
function showpipelineSettingsModal ( operation ) {
let pipelineSettingsModal = document . getElementById ( 'pipelineSettingsModal' );
let pipelineSettingsContent = document . getElementById ( 'pipelineSettingsContent' );
let operationData = apiDocs [ operation ]. post . parameters || [];
pipelineSettingsContent . innerHTML = '' ;
operationData . forEach ( parameter => {
let parameterDiv = document . createElement ( 'div' );
parameterDiv . className = "form-group" ;
let parameterLabel = document . createElement ( 'label' );
parameterLabel . textContent = ` ${ parameter . name } ( ${ parameter . schema . type } ): ` ;
2023-06-20 23:49:53 +01:00
parameterLabel . title = parameter . description ;
2023-06-13 00:32:15 +01:00
parameterDiv . appendChild ( parameterLabel );
let parameterInput ;
switch ( parameter . schema . type ) {
case 'string' :
case 'number' :
case 'integer' :
parameterInput = document . createElement ( 'input' );
parameterInput . type = parameter . schema . type === 'string' ? 'text' : 'number' ;
parameterInput . className = "form-control" ;
break ;
case 'boolean' :
parameterInput = document . createElement ( 'input' );
parameterInput . type = 'checkbox' ;
break ;
case 'array' :
case 'object' :
parameterInput = document . createElement ( 'textarea' );
parameterInput . placeholder = `Enter a JSON formatted ${ parameter . schema . type } ` ;
parameterInput . className = "form-control" ;
break ;
case 'enum' :
parameterInput = document . createElement ( 'select' );
parameterInput . className = "form-control" ;
parameter . schema . enum . forEach ( option => {
let optionElement = document . createElement ( 'option' );
optionElement . value = option ;
optionElement . text = option ;
parameterInput . appendChild ( optionElement );
});
break ;
default :
parameterInput = document . createElement ( 'input' );
parameterInput . type = 'text' ;
parameterInput . className = "form-control" ;
}
parameterInput . id = parameter . name ;
2023-06-20 23:49:53 +01:00
2023-06-13 19:18:40 +01:00
if ( operationSettings [ operation ] && operationSettings [ operation ][ parameter . name ] !== undefined ) {
let savedValue = operationSettings [ operation ][ parameter . name ];
switch ( parameter . schema . type ) {
case 'number' :
case 'integer' :
parameterInput . value = savedValue . toString ();
break ;
case 'boolean' :
parameterInput . checked = savedValue ;
break ;
case 'array' :
case 'object' :
parameterInput . value = JSON . stringify ( savedValue );
break ;
default :
parameterInput . value = savedValue ;
}
}
2023-06-20 23:49:53 +01:00
2023-06-13 00:32:15 +01:00
parameterDiv . appendChild ( parameterInput );
pipelineSettingsContent . appendChild ( parameterDiv );
});
let saveButton = document . createElement ( 'button' );
saveButton . textContent = "Save Settings" ;
saveButton . className = "btn btn-primary" ;
saveButton . addEventListener ( 'click' , function ( event ) {
event . preventDefault ();
let settings = {};
operationData . forEach ( parameter => {
let value = document . getElementById ( parameter . name ). value ;
switch ( parameter . schema . type ) {
case 'number' :
case 'integer' :
settings [ parameter . name ] = Number ( value );
break ;
case 'boolean' :
settings [ parameter . name ] = document . getElementById ( parameter . name ). checked ;
break ;
case 'array' :
case 'object' :
try {
settings [ parameter . name ] = JSON . parse ( value );
} catch ( err ) {
console . error ( `Invalid JSON format for ${ parameter . name } ` );
}
break ;
default :
settings [ parameter . name ] = value ;
}
});
2023-06-13 19:18:40 +01:00
operationSettings [ operation ] = settings ;
2023-06-20 23:49:53 +01:00
console . log ( settings );
2023-06-13 00:32:15 +01:00
pipelineSettingsModal . style . display = "none" ;
});
pipelineSettingsContent . appendChild ( saveButton );
pipelineSettingsModal . style . display = "block" ;
pipelineSettingsModal . getElementsByClassName ( "close" )[ 0 ]. onclick = function () {
pipelineSettingsModal . style . display = "none" ;
}
window . onclick = function ( event ) {
if ( event . target == pipelineSettingsModal ) {
pipelineSettingsModal . style . display = "none" ;
}
}
}
2023-06-20 23:49:53 +01:00
2023-06-13 19:18:40 +01:00
document . getElementById ( 'savePipelineBtn' ). addEventListener ( 'click' , function () {
2023-06-21 21:19:52 +01:00
if ( validatePipeline () === false ){
return ;
}
2023-06-13 19:18:40 +01:00
let pipelineList = document . getElementById ( 'pipelineList' ). children ;
let pipelineConfig = {
"name" : "uniquePipelineName" ,
"pipeline" : []
};
for ( let i = 0 ; i < pipelineList . length ; i ++ ) {
let operationName = pipelineList [ i ]. querySelector ( '.operationName' ). textContent ;
2023-06-20 23:49:53 +01:00
let parameters = operationSettings [ operationName ] || {};
2023-06-13 19:18:40 +01:00
pipelineConfig . pipeline . push ({
"operation" : operationName ,
"parameters" : parameters
});
}
let a = document . createElement ( 'a' );
a . href = URL . createObjectURL ( new Blob ([ JSON . stringify ( pipelineConfig , null , 2 )], {
type : 'application/json'
}));
a . download = 'pipelineConfig.json' ;
a . style . display = 'none' ;
document . body . appendChild ( a );
a . click ();
document . body . removeChild ( a );
});
document . getElementById ( 'uploadPipelineBtn' ). addEventListener ( 'click' , function () {
document . getElementById ( 'uploadPipelineInput' ). click ();
});
document . getElementById ( 'uploadPipelineInput' ). addEventListener ( 'change' , function ( e ) {
let reader = new FileReader ();
reader . onload = function ( event ) {
let pipelineConfig = JSON . parse ( event . target . result );
let pipelineList = document . getElementById ( 'pipelineList' );
2023-06-20 23:49:53 +01:00
2023-06-13 19:18:40 +01:00
while ( pipelineList . firstChild ) {
pipelineList . removeChild ( pipelineList . firstChild );
}
2023-06-20 23:49:53 +01:00
2023-06-13 19:18:40 +01:00
pipelineConfig . pipeline . forEach ( operationConfig => {
let operationsDropdown = document . getElementById ( 'operationsDropdown' );
operationsDropdown . value = operationConfig . operation ;
operationSettings [ operationConfig . operation ] = operationConfig . parameters ;
document . getElementById ( 'addOperationBtn' ). click ();
let lastOperation = pipelineList . lastChild ;
lastOperation . querySelector ( '.pipelineSettings' ). click ();
Object . keys ( operationConfig . parameters ). forEach ( parameterName => {
let input = document . getElementById ( parameterName );
if ( input ) {
switch ( input . type ) {
case 'checkbox' :
input . checked = operationConfig . parameters [ parameterName ];
break ;
case 'number' :
input . value = operationConfig . parameters [ parameterName ]. toString ();
break ;
case 'text' :
case 'textarea' :
default :
input . value = JSON . stringify ( operationConfig . parameters [ parameterName ]);
}
}
});
document . querySelector ( '#pipelineSettingsModal .btn-primary' ). click ();
});
};
reader . readAsText ( e . target . files [ 0 ]);
});
2023-06-13 00:32:15 +01:00
});
</ script >
2023-06-13 19:18:40 +01:00
</ div >
</ div >
</ div >
</ div >
< div th:insert = "~{fragments/footer.html :: footer}" ></ div >
</ div >
2023-06-13 00:32:15 +01:00
</ body >
</ html >