From eab9e3cffcf85393b649fcffa6ce3008900ed3e5 Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Wed, 20 Dec 2023 19:29:13 +0000 Subject: [PATCH 01/36] changes pipeline --- .../config/security/IPRateLimitingFilter.java | 70 +++++++ .../security/RateLimitResetScheduler.java | 18 ++ .../security/SecurityConfiguration.java | 14 +- .../api/SplitPdfBySectionsController.java | 4 +- .../api/SplitPdfBySizeController.java | 4 +- .../api/converters/ExtractController.java | 2 +- .../api/misc/AutoSplitPdfController.java | 2 +- .../controller/api/misc/ShowJavascript.java | 2 + .../api/pipeline/PipelineController.java | 7 +- .../controller/web/GeneralWebController.java | 53 ++--- .../SPDF/model/api/HandleDataRequest.java | 4 +- src/main/resources/static/css/dark-mode.css | 12 ++ src/main/resources/static/js/pipeline.js | 192 ++++++++++++------ src/main/resources/templates/home.html | 2 +- src/main/resources/templates/pipeline.html | 3 +- 15 files changed, 289 insertions(+), 100 deletions(-) create mode 100644 src/main/java/stirling/software/SPDF/config/security/IPRateLimitingFilter.java create mode 100644 src/main/java/stirling/software/SPDF/config/security/RateLimitResetScheduler.java diff --git a/src/main/java/stirling/software/SPDF/config/security/IPRateLimitingFilter.java b/src/main/java/stirling/software/SPDF/config/security/IPRateLimitingFilter.java new file mode 100644 index 00000000..6c47604c --- /dev/null +++ b/src/main/java/stirling/software/SPDF/config/security/IPRateLimitingFilter.java @@ -0,0 +1,70 @@ +package stirling.software.SPDF.config.security; +import java.io.IOException; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; + +import jakarta.servlet.Filter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.HttpServletRequest; + +public class IPRateLimitingFilter implements Filter { + + private final ConcurrentHashMap requestCounts = new ConcurrentHashMap<>(); + private final ConcurrentHashMap getCounts = new ConcurrentHashMap<>(); + private final int maxRequests; + private final int maxGetRequests; + + public IPRateLimitingFilter(int maxRequests, int maxGetRequests) { + this.maxRequests = maxRequests; + this.maxGetRequests = maxGetRequests; + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + if (request instanceof HttpServletRequest) { + HttpServletRequest httpRequest = (HttpServletRequest) request; + String method = httpRequest.getMethod(); + String requestURI = httpRequest.getRequestURI(); + // Check if the request is for static resources + boolean isStaticResource = requestURI.startsWith("/css/") + || requestURI.startsWith("/js/") + || requestURI.startsWith("/images/") + || requestURI.startsWith("/public/") + || requestURI.startsWith("/pdfjs/") + || requestURI.endsWith(".svg"); + + // If it's a static resource, just continue the filter chain and skip the logic below + if (isStaticResource) { + chain.doFilter(request, response); + return; + } + + String clientIp = request.getRemoteAddr(); + requestCounts.computeIfAbsent(clientIp, k -> new AtomicInteger(0)); + System.out.println(requestCounts.get(clientIp).get() + ", " + requestURI ); + if (!"GET".equalsIgnoreCase(method)) { + + if (requestCounts.get(clientIp).incrementAndGet() > maxRequests) { + // Handle limit exceeded (e.g., send error response) + response.getWriter().write("Rate limit exceeded"); + return; + } + } else { + if (requestCounts.get(clientIp).incrementAndGet() > maxGetRequests) { + // Handle limit exceeded (e.g., send error response) + response.getWriter().write("GET Rate limit exceeded"); + return; + } + } + } + chain.doFilter(request, response); + } + + public void resetRequestCounts() { + requestCounts.clear(); + getCounts.clear(); + } +} diff --git a/src/main/java/stirling/software/SPDF/config/security/RateLimitResetScheduler.java b/src/main/java/stirling/software/SPDF/config/security/RateLimitResetScheduler.java new file mode 100644 index 00000000..1a044582 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/config/security/RateLimitResetScheduler.java @@ -0,0 +1,18 @@ +package stirling.software.SPDF.config.security; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +@Component +public class RateLimitResetScheduler { + + private final IPRateLimitingFilter rateLimitingFilter; + + public RateLimitResetScheduler(IPRateLimitingFilter rateLimitingFilter) { + this.rateLimitingFilter = rateLimitingFilter; + } + + @Scheduled(cron = "0 0 0 * * MON") // At 00:00 every Monday + public void resetRateLimit() { + rateLimitingFilter.resetRequestCounts(); + } +} diff --git a/src/main/java/stirling/software/SPDF/config/security/SecurityConfiguration.java b/src/main/java/stirling/software/SPDF/config/security/SecurityConfiguration.java index dfe782ac..7a2541af 100644 --- a/src/main/java/stirling/software/SPDF/config/security/SecurityConfiguration.java +++ b/src/main/java/stirling/software/SPDF/config/security/SecurityConfiguration.java @@ -13,6 +13,7 @@ import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; @@ -51,11 +52,13 @@ public class SecurityConfiguration { if(loginEnabledValue) { http.csrf(csrf -> csrf.disable()); - http.addFilterAfter(firstLoginFilter, UsernamePasswordAuthenticationFilter.class); + //http.addFilterBefore(rateLimitingFilter(), UsernamePasswordAuthenticationFilter.class); + //http.addFilterAfter(firstLoginFilter, UsernamePasswordAuthenticationFilter.class); http .formLogin(formLogin -> formLogin .loginPage("/login") - .defaultSuccessUrl("/") + // .defaultSuccessUrl("/") + .successHandler(new SavedRequestAwareAuthenticationSuccessHandler()) .failureHandler(new CustomAuthenticationFailureHandler()) .permitAll() ) @@ -85,7 +88,14 @@ public class SecurityConfiguration { return http.build(); } + + @Bean + public IPRateLimitingFilter rateLimitingFilter() { + int maxRequestsPerIp = 10000; // Example limit + return new IPRateLimitingFilter(maxRequestsPerIp, maxRequestsPerIp); + } + @Bean public DaoAuthenticationProvider authenticationProvider() { diff --git a/src/main/java/stirling/software/SPDF/controller/api/SplitPdfBySectionsController.java b/src/main/java/stirling/software/SPDF/controller/api/SplitPdfBySectionsController.java index 29a2f426..f36f64da 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/SplitPdfBySectionsController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/SplitPdfBySectionsController.java @@ -29,12 +29,12 @@ import stirling.software.SPDF.model.api.SplitPdfBySectionsRequest; import stirling.software.SPDF.utils.WebResponseUtils; @RestController @RequestMapping("/api/v1/general") -@Tag(name = "Misc", description = "Miscellaneous APIs") +@Tag(name = "General", description = "General APIs") public class SplitPdfBySectionsController { @PostMapping(value = "/split-pdf-by-sections", consumes = "multipart/form-data") - @Operation(summary = "Split PDF pages into smaller sections", description = "Split each page of a PDF into smaller sections based on the user's choice (halves, thirds, quarters, etc.), both vertically and horizontally. Input: PDF, Split Parameters. Output: ZIP containing split documents.") + @Operation(summary = "Split PDF pages into smaller sections", description = "Split each page of a PDF into smaller sections based on the user's choice (halves, thirds, quarters, etc.), both vertically and horizontally. Input:PDF Output:ZIP-PDF Type:SISO") public ResponseEntity splitPdf(@ModelAttribute SplitPdfBySectionsRequest request) throws Exception { List splitDocumentsBoas = new ArrayList<>(); diff --git a/src/main/java/stirling/software/SPDF/controller/api/SplitPdfBySizeController.java b/src/main/java/stirling/software/SPDF/controller/api/SplitPdfBySizeController.java index 83e7b723..9b25e8be 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/SplitPdfBySizeController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/SplitPdfBySizeController.java @@ -26,13 +26,13 @@ import stirling.software.SPDF.utils.WebResponseUtils; @RestController @RequestMapping("/api/v1/general") -@Tag(name = "Misc", description = "Miscellaneous APIs") +@Tag(name = "General", description = "General APIs") public class SplitPdfBySizeController { @PostMapping(value = "/split-by-size-or-count", consumes = "multipart/form-data") @Operation(summary = "Auto split PDF pages into separate documents based on size or count", description = "split PDF into multiple paged documents based on size/count, ie if 20 pages and split into 5, it does 5 documents each 4 pages\r\n" - + " if 10MB and each page is 1MB and you enter 2MB then 5 docs each 2MB (rounded so that it accepts 1.9MB but not 2.1MB) Input:PDF Output:ZIP Type:SIMO") + + " if 10MB and each page is 1MB and you enter 2MB then 5 docs each 2MB (rounded so that it accepts 1.9MB but not 2.1MB) Input:PDF Output:ZIP-PDF Type:SISO") public ResponseEntity autoSplitPdf(@ModelAttribute SplitPdfBySizeOrCountRequest request) throws Exception { List splitDocumentsBoas = new ArrayList(); diff --git a/src/main/java/stirling/software/SPDF/controller/api/converters/ExtractController.java b/src/main/java/stirling/software/SPDF/controller/api/converters/ExtractController.java index f6fb69c9..d9e35c6d 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/converters/ExtractController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/converters/ExtractController.java @@ -28,7 +28,7 @@ import stirling.software.SPDF.model.api.extract.PDFFilePage; @RestController @RequestMapping("/api/v1/convert") -@Tag(name = "General", description = "General APIs") +@Tag(name = "Convert", description = "Convert APIs") public class ExtractController { private static final Logger logger = LoggerFactory.getLogger(CropController.class); diff --git a/src/main/java/stirling/software/SPDF/controller/api/misc/AutoSplitPdfController.java b/src/main/java/stirling/software/SPDF/controller/api/misc/AutoSplitPdfController.java index b4b9b951..e62fc35f 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/misc/AutoSplitPdfController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/misc/AutoSplitPdfController.java @@ -43,7 +43,7 @@ public class AutoSplitPdfController { private static final String QR_CONTENT = "https://github.com/Frooodle/Stirling-PDF"; @PostMapping(value = "/auto-split-pdf", consumes = "multipart/form-data") - @Operation(summary = "Auto split PDF pages into separate documents", description = "This endpoint accepts a PDF file, scans each page for a specific QR code, and splits the document at the QR code boundaries. The output is a zip file containing each separate PDF document. Input:PDF Output:ZIP Type:SISO") + @Operation(summary = "Auto split PDF pages into separate documents", description = "This endpoint accepts a PDF file, scans each page for a specific QR code, and splits the document at the QR code boundaries. The output is a zip file containing each separate PDF document. Input:PDF Output:ZIP-PDF Type:SISO") public ResponseEntity autoSplitPdf(@ModelAttribute AutoSplitPdfRequest request) throws IOException { MultipartFile file = request.getFileInput(); boolean duplexMode = request.isDuplexMode(); diff --git a/src/main/java/stirling/software/SPDF/controller/api/misc/ShowJavascript.java b/src/main/java/stirling/software/SPDF/controller/api/misc/ShowJavascript.java index 27431346..cef32ac7 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/misc/ShowJavascript.java +++ b/src/main/java/stirling/software/SPDF/controller/api/misc/ShowJavascript.java @@ -15,6 +15,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; +import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import stirling.software.SPDF.model.api.PDFFile; import stirling.software.SPDF.utils.WebResponseUtils; @@ -25,6 +26,7 @@ public class ShowJavascript { private static final Logger logger = LoggerFactory.getLogger(ShowJavascript.class); @PostMapping(consumes = "multipart/form-data", value = "/show-javascript") + @Operation(summary = "Grabs all JS from a PDF and returns a single JS file with all code", description = "desc. Input:PDF Output:JS Type:SISO") public ResponseEntity extractHeader(@ModelAttribute PDFFile request) throws Exception { MultipartFile inputFile = request.getFileInput(); String script = ""; diff --git a/src/main/java/stirling/software/SPDF/controller/api/pipeline/PipelineController.java b/src/main/java/stirling/software/SPDF/controller/api/pipeline/PipelineController.java index c12fe724..568d4430 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/pipeline/PipelineController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/pipeline/PipelineController.java @@ -421,8 +421,11 @@ public class PipelineController { @PostMapping("/handleData") public ResponseEntity handleData(@ModelAttribute HandleDataRequest request) { - MultipartFile[] files = request.getFileInputs(); - String jsonString = request.getJsonString(); + MultipartFile[] files = request.getFileInput(); + String jsonString = request.getJson(); + if(files == null) { + return null; + } logger.info("Received POST request to /handleData with {} files", files.length); try { List outputFiles = handleFiles(files, jsonString); diff --git a/src/main/java/stirling/software/SPDF/controller/web/GeneralWebController.java b/src/main/java/stirling/software/SPDF/controller/web/GeneralWebController.java index 4b038e52..411014a8 100644 --- a/src/main/java/stirling/software/SPDF/controller/web/GeneralWebController.java +++ b/src/main/java/stirling/software/SPDF/controller/web/GeneralWebController.java @@ -1,5 +1,6 @@ package stirling.software.SPDF.controller.web; +import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; @@ -41,31 +42,33 @@ public class GeneralWebController { model.addAttribute("currentPage", "pipeline"); List pipelineConfigs = new ArrayList<>(); - try (Stream paths = Files.walk(Paths.get("./pipeline/defaultWebUIConfigs/"))) { - List jsonFiles = paths - .filter(Files::isRegularFile) - .filter(p -> p.toString().endsWith(".json")) - .collect(Collectors.toList()); - - for (Path jsonFile : jsonFiles) { - String content = Files.readString(jsonFile, StandardCharsets.UTF_8); - pipelineConfigs.add(content); - } - List> pipelineConfigsWithNames = new ArrayList<>(); - for (String config : pipelineConfigs) { - Map jsonContent = new ObjectMapper().readValue(config, new TypeReference>(){}); - - String name = (String) jsonContent.get("name"); - Map configWithName = new HashMap<>(); - configWithName.put("json", config); - configWithName.put("name", name); - pipelineConfigsWithNames.add(configWithName); - } - model.addAttribute("pipelineConfigsWithNames", pipelineConfigsWithNames); - - } catch (IOException e) { - e.printStackTrace(); - } + if(new File("./pipeline/defaultWebUIConfigs/").exists()) { + try (Stream paths = Files.walk(Paths.get("./pipeline/defaultWebUIConfigs/"))) { + List jsonFiles = paths + .filter(Files::isRegularFile) + .filter(p -> p.toString().endsWith(".json")) + .collect(Collectors.toList()); + + for (Path jsonFile : jsonFiles) { + String content = Files.readString(jsonFile, StandardCharsets.UTF_8); + pipelineConfigs.add(content); + } + List> pipelineConfigsWithNames = new ArrayList<>(); + for (String config : pipelineConfigs) { + Map jsonContent = new ObjectMapper().readValue(config, new TypeReference>(){}); + + String name = (String) jsonContent.get("name"); + Map configWithName = new HashMap<>(); + configWithName.put("json", config); + configWithName.put("name", name); + pipelineConfigsWithNames.add(configWithName); + } + model.addAttribute("pipelineConfigsWithNames", pipelineConfigsWithNames); + + } catch (IOException e) { + e.printStackTrace(); + } + } model.addAttribute("pipelineConfigs", pipelineConfigs); diff --git a/src/main/java/stirling/software/SPDF/model/api/HandleDataRequest.java b/src/main/java/stirling/software/SPDF/model/api/HandleDataRequest.java index 1d7a8afe..d830ffeb 100644 --- a/src/main/java/stirling/software/SPDF/model/api/HandleDataRequest.java +++ b/src/main/java/stirling/software/SPDF/model/api/HandleDataRequest.java @@ -13,8 +13,8 @@ import lombok.NoArgsConstructor; public class HandleDataRequest { @Schema(description = "The input files") - private MultipartFile[] fileInputs; + private MultipartFile[] fileInput; @Schema(description = "JSON String") - private String jsonString; + private String json; } diff --git a/src/main/resources/static/css/dark-mode.css b/src/main/resources/static/css/dark-mode.css index 82e01676..2f070c9c 100644 --- a/src/main/resources/static/css/dark-mode.css +++ b/src/main/resources/static/css/dark-mode.css @@ -75,6 +75,13 @@ table th, table td { border: none; color: #fff !important; } + +.btn-warning { + background-color: #ffc107 !important; + border: none; + color: #000 !important; +} + .btn-outline-secondary { color: #fff !important; border-color: #fff; @@ -92,6 +99,11 @@ hr { background-color: rgba(255, 255, 255, 0.6); /* for some browsers that might use background instead of border for
*/ } +.modal-content { + color: #fff !important; + border-color: #fff; +} + #global-buttons-container input { background-color: #323948; caret-color: #ffffff; diff --git a/src/main/resources/static/js/pipeline.js b/src/main/resources/static/js/pipeline.js index 06810743..05b0e861 100644 --- a/src/main/resources/static/js/pipeline.js +++ b/src/main/resources/static/js/pipeline.js @@ -20,9 +20,20 @@ function validatePipeline() { console.log("currentOperationDescription", currentOperationDescription); console.log("nextOperationDescription", nextOperationDescription); + + // Strip off 'ZIP-' prefix + currentOperationDescription = currentOperationDescription.replace("ZIP-", ''); + nextOperationDescription = nextOperationDescription.replace("ZIP-", ''); + + 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); @@ -99,7 +110,7 @@ document.getElementById('submitConfigBtn').addEventListener('click', function() formData.append('json', pipelineConfigJson); console.log("formData", formData); - fetch('/handleData', { + fetch('/api/v1/pipeline/handleData', { method: 'POST', body: formData }) @@ -120,16 +131,17 @@ document.getElementById('submitConfigBtn').addEventListener('click', function() }); let apiDocs = {}; - +let apiSchemas = {}; let operationSettings = {}; -fetch('v3/api-docs') +fetch('v1/api-docs') .then(response => response.json()) .then(data => { apiDocs = data.paths; + apiSchemas = data.components.schemas; let operationsDropdown = document.getElementById('operationsDropdown'); - const ignoreOperations = ["/handleData", "operationToIgnore"]; // Add the operations you want to ignore here + const ignoreOperations = ["/api/v1/pipeline/handleData", "/api/v1/pipeline/operationToIgnore"]; // Add the operations you want to ignore here operationsDropdown.innerHTML = ''; @@ -138,6 +150,9 @@ fetch('v3/api-docs') // Group operations by tags Object.keys(data.paths).forEach(operationPath => { let operation = data.paths[operationPath].post; + if(!operation || !operation.description) { + console.log(operationPath); + } if (operation && !ignoreOperations.includes(operationPath) && !operation.description.includes("Type:MISO")) { let operationTag = operation.tags[0]; // This assumes each operation has exactly one tag if (!operationsByTag[operationTag]) { @@ -146,9 +161,9 @@ fetch('v3/api-docs') operationsByTag[operationTag].push(operationPath); } }); - + console.log("operationsByTag", operationsByTag); // Specify the order of tags - let tagOrder = ["General", "Security", "Convert", "Other", "Filter"]; + let tagOrder = ["General", "Security", "Convert", "Misc", "Filter"]; // Create dropdown options tagOrder.forEach(tag => { @@ -158,8 +173,17 @@ fetch('v3/api-docs') operationsByTag[tag].forEach(operationPath => { let option = document.createElement('option'); - let operationWithoutSlash = operationPath.replace(/\//g, ''); // Remove slashes - option.textContent = operationWithoutSlash; + console.log("operationPath", operationPath); + let operationPathDisplay = operationPath + operationPathDisplay = operationPath.replace(new RegExp("api/v1/" + tag.toLowerCase() + "/", 'i'), ""); + + console.log("operationPath2", operationPath); + if(operationPath.includes("/convert")){ + operationPathDisplay = operationPathDisplay.replaceAll("(? 0) || - (apiDocs[selectedOperation].post.requestBody && - apiDocs[selectedOperation].post.requestBody.content['multipart/form-data'].schema.properties))); + 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(); + console.log("refKey", refKey); + // Check if the referenced schema exists and has properties + if (apiSchemas[refKey] && Object.keys(apiSchemas[refKey].properties).length > 0) { + hasSettings = true; + } + } + } listItem.innerHTML = ` -
-
${selectedOperation}
-
- - - - -
-
- `; +
+
${selectedOperation}
+
+ + + + +
+
+`; + pipelineList.appendChild(listItem); @@ -226,12 +266,15 @@ document.getElementById('addOperationBtn').addEventListener('click', function() let pipelineSettingsModal = document.getElementById('pipelineSettingsModal'); let pipelineSettingsContent = document.getElementById('pipelineSettingsContent'); let operationData = apiDocs[operation].post.parameters || []; - let requestBodyData = apiDocs[operation].post.requestBody.content['multipart/form-data'].schema.properties || {}; + // 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] + name: key, + schema: requestBodyData[key] }))); pipelineSettingsContent.innerHTML = ''; @@ -240,16 +283,21 @@ document.getElementById('addOperationBtn').addEventListener('click', function() // If the parameter name is 'fileInput', return early to skip the rest of this iteration if (parameter.name === 'fileInput') return; + console.log("parameter", parameter); let parameterDiv = document.createElement('div'); parameterDiv.className = "mb-3"; let parameterLabel = document.createElement('label'); parameterLabel.textContent = `${parameter.name} (${parameter.schema.type}): `; - parameterLabel.title = parameter.description; + 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 @@ -282,6 +330,7 @@ document.getElementById('addOperationBtn').addEventListener('click', function() parameterInput = document.createElement('input'); parameterInput.type = 'text'; parameterInput.className = "form-control"; + if (defaultValue !== undefined) parameterInput.value = defaultValue; } break; case 'number': @@ -289,10 +338,12 @@ document.getElementById('addOperationBtn').addEventListener('click', function() 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': @@ -304,10 +355,13 @@ document.getElementById('addOperationBtn').addEventListener('click', function() 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]; @@ -327,7 +381,7 @@ document.getElementById('addOperationBtn').addEventListener('click', function() parameterInput.value = savedValue; } } - + console.log("parameterInput2", parameterInput); parameterDiv.appendChild(parameterInput); pipelineSettingsContent.appendChild(parameterDiv); @@ -340,50 +394,67 @@ document.getElementById('addOperationBtn').addEventListener('click', function() 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; + console.log("parameter.name", parameter.name); + 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': + 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; console.log(settings); - pipelineSettingsModal.style.display = "none"; + //pipelineSettingsModal.style.display = "none"; }); pipelineSettingsContent.appendChild(saveButton); - pipelineSettingsModal.style.display = "block"; + //pipelineSettingsModal.style.display = "block"; - pipelineSettingsModal.getElementsByClassName("close")[0].onclick = function() { - pipelineSettingsModal.style.display = "none"; - } + //pipelineSettingsModal.getElementsByClassName("close")[0].onclick = function() { + // pipelineSettingsModal.style.display = "none"; + //} - window.onclick = function(event) { - if (event.target == pipelineSettingsModal) { - pipelineSettingsModal.style.display = "none"; - } - } + //window.onclick = function(event) { + // if (event.target == pipelineSettingsModal) { + // pipelineSettingsModal.style.display = "none"; + // } + //} } + +}); + + + + var saveBtn = document.getElementById('savePipelineBtn'); - document.getElementById('savePipelineBtn').addEventListener('click', function() { + // Remove any existing event listeners + saveBtn.removeEventListener('click', savePipeline); + + // Add the event listener + saveBtn.addEventListener('click', savePipeline); + console.log("saveBtn", saveBtn) + function savePipeline() { + if (validatePipeline() === false) { return; } + var pipelineName = document.getElementById('pipelineName').value; let pipelineList = document.getElementById('pipelineList').children; let pipelineConfig = { @@ -406,7 +477,7 @@ document.getElementById('addOperationBtn').addEventListener('click', function() "parameters": parameters }); } - + console.log("Downloading.."); let a = document.createElement('a'); a.href = URL.createObjectURL(new Blob([JSON.stringify(pipelineConfig, null, 2)], { type: 'application/json' @@ -417,7 +488,7 @@ document.getElementById('addOperationBtn').addEventListener('click', function() document.body.appendChild(a); a.click(); document.body.removeChild(a); - }); + } async function processPipelineConfig(configString) { let pipelineConfig = JSON.parse(configString); @@ -491,4 +562,3 @@ document.getElementById('addOperationBtn').addEventListener('click', function() }); -}); \ No newline at end of file diff --git a/src/main/resources/templates/home.html b/src/main/resources/templates/home.html index 9f4b3f8f..2b82a7d4 100644 --- a/src/main/resources/templates/home.html +++ b/src/main/resources/templates/home.html @@ -28,7 +28,7 @@
- +
diff --git a/src/main/resources/templates/pipeline.html b/src/main/resources/templates/pipeline.html index fa26ff29..1b4882f9 100644 --- a/src/main/resources/templates/pipeline.html +++ b/src/main/resources/templates/pipeline.html @@ -34,6 +34,7 @@

+
@@ -66,7 +67,7 @@