document.getElementById('validateButton').addEventListener('click', function(event) { event.preventDefault(); validatePipeline(); }); function validatePipeline() { let pipelineListItems = document.getElementById('pipelineList').children; let isValid = true; let containsAddPassword = false; for (let i = 0; i < pipelineListItems.length - 1; i++) { let currentOperation = pipelineListItems[i].querySelector('.operationName').textContent; let nextOperation = pipelineListItems[i + 1].querySelector('.operationName').textContent; if (currentOperation === '/add-password') { containsAddPassword = true; } let currentOperationDescription = apiDocs[currentOperation]?.post?.description || ""; let nextOperationDescription = apiDocs[nextOperation]?.post?.description || ""; // Strip off 'ZIP-' prefix currentOperationDescription = currentOperationDescription.replace("ZIP-", ''); nextOperationDescription = nextOperationDescription.replace("ZIP-", ''); let currentOperationOutput = currentOperationDescription.match(/Output:([A-Z\/]*)/)?.[1] || ""; let nextOperationInput = nextOperationDescription.match(/Input:([A-Z\/]*)/)?.[1] || ""; // 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) { updateValidateButton(false); 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; } } } if (containsAddPassword && pipelineListItems[pipelineListItems.length - 1].querySelector('.operationName').textContent !== '/add-password') { updateValidateButton(false); alert('The "add-password" operation should be at the end of the operations sequence. Please adjust the operations order.'); return false; } 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 } updateValidateButton(isValid); return isValid; } function updateValidateButton(isValid) { var validateButton = document.getElementById('validateButton'); if (isValid) { validateButton.classList.remove('btn-danger'); validateButton.classList.add('btn-success'); } else { validateButton.classList.remove('btn-success'); validateButton.classList.add('btn-danger'); } } document.getElementById('submitConfigBtn').addEventListener('click', function() { if (validatePipeline() === false) { return; } let selectedOperation = document.getElementById('operationsDropdown').value; var pipelineName = document.getElementById('pipelineName').value; let pipelineList = document.getElementById('pipelineList').children; let pipelineConfig = { "name": pipelineName, "pipeline": [], "_examples": { "outputDir": "{outputFolder}/{folderName}", "outputFileName": "{filename}-{pipelineName}-{date}-{time}" }, "outputDir": "httpWebRequest", "outputFileName": "{filename}" }; for (let i = 0; i < pipelineList.length; i++) { let operationName = pipelineList[i].querySelector('.operationName').textContent; let parameters = operationSettings[operationName] || {}; pipelineConfig.pipeline.push({ "operation": operationName, "parameters": parameters }); } let pipelineConfigJson = JSON.stringify(pipelineConfig, null, 2); let formData = new FormData(); let fileInput = document.getElementById('fileInput-input'); 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); fetch('api/v1/pipeline/handleData', { method: 'POST', body: formData }) .then(response => { // Save the response to use it later const responseToUseLater = response; return response.blob().then(blob => { let url = window.URL.createObjectURL(blob); let a = document.createElement('a'); a.href = url; // Use responseToUseLater instead of response const contentDisposition = responseToUseLater.headers.get('Content-Disposition'); let filename = 'download'; if (contentDisposition && contentDisposition.indexOf('attachment') !== -1) { filename = decodeURIComponent(contentDisposition.split('filename=')[1].replace(/"/g, '')).trim(); } a.download = filename; document.body.appendChild(a); a.click(); a.remove(); }); }) .catch((error) => { console.error('Error:', error); }); }); let apiDocs = {}; let apiSchemas = {}; let operationSettings = {}; fetch('v1/api-docs') .then(response => response.json()) .then(data => { apiDocs = data.paths; apiSchemas = data.components.schemas; let operationsDropdown = document.getElementById('operationsDropdown'); const ignoreOperations = ["/api/v1/pipeline/handleData", "/api/v1/pipeline/operationToIgnore"]; // Add the operations you want to ignore here operationsDropdown.innerHTML = ''; let operationsByTag = {}; // Group operations by tags Object.keys(data.paths).forEach(operationPath => { let operation = data.paths[operationPath].post; if (!operation || !operation.description) { console.log(operationPath); } //!operation.description.includes("Type:MISO") if (operation && !ignoreOperations.includes(operationPath)) { let operationTag = operation.tags[0]; // This assumes each operation has exactly one tag if (!operationsByTag[operationTag]) { operationsByTag[operationTag] = []; } operationsByTag[operationTag].push(operationPath); } }); // Sort operations within each tag alphabetically Object.keys(operationsByTag).forEach(tag => { operationsByTag[tag].sort(); }); // Specify the order of tags let tagOrder = ["General", "Security", "Convert", "Misc", "Filter"]; // Create dropdown options tagOrder.forEach(tag => { if (operationsByTag[tag]) { let group = document.createElement('optgroup'); group.label = tag; operationsByTag[tag].forEach(operationPath => { let option = document.createElement('option'); let operationPathDisplay = operationPath operationPathDisplay = operationPath.replace(new RegExp("api/v1/" + tag.toLowerCase() + "/", 'i'), ""); if (operationPath.includes("/convert")) { operationPathDisplay = operationPathDisplay.replace(/^\//, '').replaceAll("/", " to "); } else { operationPathDisplay = operationPathDisplay.replace(/\//g, ''); // Remove slashes } operationPathDisplay = operationPathDisplay.replaceAll(" ", "-"); option.textContent = operationPathDisplay; option.value = operationPath; // Keep the value with slashes for querying group.appendChild(option); }); operationsDropdown.appendChild(group); } }); }); 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"; let hasSettings = false; if (apiDocs[selectedOperation] && apiDocs[selectedOperation].post) { const postMethod = apiDocs[selectedOperation].post; // Check if parameters exist if (postMethod.parameters && postMethod.parameters.length > 0) { hasSettings = true; } else if (postMethod.requestBody && postMethod.requestBody.content['multipart/form-data']) { // Extract the reference key const refKey = postMethod.requestBody.content['multipart/form-data'].schema['$ref'].split('/').pop(); // Check if the referenced schema exists and has properties more than just its input file if (apiSchemas[refKey]) { const properties = apiSchemas[refKey].properties; const propertyKeys = Object.keys(properties); // Check if there's more than one property or if there's exactly one property and its format is not 'binary' if (propertyKeys.length > 1 || (propertyKeys.length === 1 && properties[propertyKeys[0]].format !== 'binary')) { hasSettings = true; } } } } listItem.innerHTML = `
${selectedOperation}
`; pipelineList.appendChild(listItem); listItem.querySelector('.move-up').addEventListener('click', function(event) { event.preventDefault(); if (listItem.previousElementSibling) { pipelineList.insertBefore(listItem, listItem.previousElementSibling); updateConfigInDropdown(); } }); listItem.querySelector('.move-down').addEventListener('click', function(event) { event.preventDefault(); if (listItem.nextElementSibling) { pipelineList.insertBefore(listItem.nextElementSibling, listItem); updateConfigInDropdown(); } }); listItem.querySelector('.remove').addEventListener('click', function(event) { event.preventDefault(); pipelineList.removeChild(listItem); hideOrShowPipelineHeader(); updateConfigInDropdown(); }); listItem.querySelector('.pipelineSettings').addEventListener('click', function(event) { event.preventDefault(); showpipelineSettingsModal(selectedOperation); hideOrShowPipelineHeader(); }); function showpipelineSettingsModal(operation) { let pipelineSettingsModal = document.getElementById('pipelineSettingsModal'); let pipelineSettingsContent = document.getElementById('pipelineSettingsContent'); let operationData = apiDocs[operation].post.parameters || []; // Resolve the $ref reference to get actual schema properties let refKey = apiDocs[operation].post.requestBody.content['multipart/form-data'].schema['$ref'].split('/').pop(); let requestBodyData = apiSchemas[refKey].properties || {}; // Combine operationData and requestBodyData into a single array operationData = operationData.concat(Object.keys(requestBodyData).map(key => ({ name: key, schema: requestBodyData[key] }))); pipelineSettingsContent.innerHTML = ''; operationData.forEach(parameter => { // If the parameter name is 'fileInput', return early to skip the rest of this iteration if (parameter.name === 'fileInput') return; let parameterDiv = document.createElement('div'); parameterDiv.className = "mb-3"; let parameterLabel = document.createElement('label'); parameterLabel.textContent = `${parameter.name} (${parameter.schema.type}): `; parameterLabel.title = parameter.schema.description; parameterLabel.setAttribute('for', parameter.name); parameterDiv.appendChild(parameterLabel); let defaultValue = parameter.schema.example; if (defaultValue === undefined) defaultValue = parameter.schema.default; let parameterInput; // check if enum exists in schema if (parameter.schema.enum) { // if enum exists, create a select element parameterInput = document.createElement('select'); parameterInput.className = "form-control"; // iterate over each enum value and create an option for it parameter.schema.enum.forEach(value => { let option = document.createElement('option'); option.value = value; option.text = value; parameterInput.appendChild(option); }); } else { // switch-case statement for handling non-enum types switch (parameter.schema.type) { case 'string': if (parameter.schema.format === 'binary') { // This is a file input //parameterInput = document.createElement('input'); //parameterInput.type = 'file'; //parameterInput.className = "form-control"; parameterInput = document.createElement('input'); parameterInput.type = 'text'; parameterInput.className = "form-control"; parameterInput.value = "FileInputPathToBeInputtedManuallyForOffline"; } else { parameterInput = document.createElement('input'); parameterInput.type = 'text'; parameterInput.className = "form-control"; if (defaultValue !== undefined) parameterInput.value = defaultValue; } break; case 'number': case 'integer': parameterInput = document.createElement('input'); parameterInput.type = 'number'; parameterInput.className = "form-control"; if (defaultValue !== undefined) parameterInput.value = defaultValue; break; case 'boolean': parameterInput = document.createElement('input'); parameterInput.type = 'checkbox'; if (defaultValue === true) parameterInput.checked = true; break; case 'array': case 'object': //TODO compare to doc and check if fileInput array? parameter.schema.format === 'binary' parameterInput = document.createElement('textarea'); parameterInput.placeholder = `Enter a JSON formatted ${parameter.schema.type}, If this is a fileInput, it is not currently supported`; parameterInput.className = "form-control"; break; default: parameterInput = document.createElement('input'); parameterInput.type = 'text'; parameterInput.className = "form-control"; if (defaultValue !== undefined) parameterInput.value = defaultValue; } } parameterInput.id = parameter.name; console.log("defaultValue", defaultValue); console.log("parameterInput", parameterInput); 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; } } console.log("parameterInput2", parameterInput); parameterDiv.appendChild(parameterInput); pipelineSettingsContent.appendChild(parameterDiv); }); if(hasSettings) { let saveButton = document.createElement('button'); saveButton.textContent = saveSettings; saveButton.className = "btn btn-primary"; saveButton.addEventListener('click', function(event) { event.preventDefault(); let settings = {}; operationData.forEach(parameter => { if (parameter.name !== "fileInput") { 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': if (value === null || value === '') { settings[parameter.name] = ''; } else { try { settings[parameter.name] = JSON.parse(value); } catch (err) { console.error(`Invalid JSON format for ${parameter.name}`); } } break; default: settings[parameter.name] = value; } } }); operationSettings[operation] = settings; //pipelineSettingsModal.style.display = "none"; }); pipelineSettingsContent.appendChild(saveButton); saveButton.click(); } //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"; // } //} } showpipelineSettingsModal(selectedOperation); updateConfigInDropdown(); hideOrShowPipelineHeader(); }); function updateConfigInDropdown() { let pipelineSelect = document.getElementById('pipelineSelect'); let selectedOption = pipelineSelect.options[pipelineSelect.selectedIndex]; // Get the current configuration as JSON let pipelineConfigJson = configToJson(); console.log("pipelineConfigJson", pipelineConfigJson); if (!pipelineConfigJson) { console.error("Failed to update configuration: Invalid configuration"); return; } // Update the value of the selected option with the new configuration selectedOption.value = pipelineConfigJson; } var saveBtn = document.getElementById('savePipelineBtn'); // Remove any existing event listeners saveBtn.removeEventListener('click', savePipeline); // Add the event listener saveBtn.addEventListener('click', savePipeline); console.log("saveBtn", saveBtn) function configToJson() { if (!validatePipeline()) { return null; // Return null if validation fails } var pipelineName = document.getElementById('pipelineName').value; let pipelineList = document.getElementById('pipelineList').children; let pipelineConfig = { "name": pipelineName, "pipeline": [], "_examples": { "outputDir": "{outputFolder}/{folderName}", "outputFileName": "{filename}-{pipelineName}-{date}-{time}" }, "outputDir": "{outputFolder}", "outputFileName": "{filename}" }; for (let i = 0; i < pipelineList.length; i++) { let operationName = pipelineList[i].querySelector('.operationName').textContent; let parameters = operationSettings[operationName] || {}; parameters['fileInput'] = 'automated'; pipelineConfig.pipeline.push({ "operation": operationName, "parameters": parameters }); } return JSON.stringify(pipelineConfig, null, 2); } function savePipeline() { let pipelineConfigJson = configToJson(); if (!pipelineConfigJson) { console.error("Failed to save pipeline: Invalid configuration"); return; } let pipelineName = document.getElementById('pipelineName').value; console.log("Downloading..."); let a = document.createElement('a'); a.href = URL.createObjectURL(new Blob([pipelineConfigJson], { type: 'application/json' })); a.download = pipelineName + '.json'; a.style.display = 'none'; document.body.appendChild(a); a.click(); document.body.removeChild(a); } async function processPipelineConfig(configString) { console.log("configString", configString); let pipelineConfig = JSON.parse(configString); let pipelineList = document.getElementById('pipelineList'); while (pipelineList.firstChild) { pipelineList.removeChild(pipelineList.firstChild); } document.getElementById('pipelineName').value = pipelineConfig.name for (const operationConfig of pipelineConfig.pipeline) { let operationsDropdown = document.getElementById('operationsDropdown'); operationsDropdown.value = operationConfig.operation; operationSettings[operationConfig.operation] = operationConfig.parameters; // assuming addOperation is async await new Promise((resolve) => { document.getElementById('addOperationBtn').addEventListener('click', resolve, { once: true }); document.getElementById('addOperationBtn').click(); }); let lastOperation = pipelineList.lastChild; 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 'file': if (parameterName !== 'fileInput') { // Create a new file input element let newInput = document.createElement('input'); newInput.type = 'file'; newInput.id = parameterName; // Add the new file input to the main page (change the selector according to your needs) document.querySelector('#main').appendChild(newInput); } break; case 'text': case 'textarea': default: input.value = JSON.stringify(operationConfig.parameters[parameterName]); } } }); } } 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) { processPipelineConfig(event.target.result); }; reader.readAsText(e.target.files[0]); hideOrShowPipelineHeader(); }); document.getElementById('pipelineSelect').addEventListener('change', function(e) { let selectedPipelineJson = e.target.value; // assuming the selected value is the JSON string of the pipeline config processPipelineConfig(selectedPipelineJson); }); function hideOrShowPipelineHeader() { var pipelineHeader = document.getElementById('pipelineHeader'); var pipelineList = document.getElementById('pipelineList'); if (pipelineList.children.length === 0) { // Hide the pipeline header if there are no items in the pipeline list pipelineHeader.style.display = 'none'; } else { // Show the pipeline header if there are items in the pipeline list pipelineHeader.style.display = 'block'; } }