changes pipeline

This commit is contained in:
Anthony Stirling 2023-12-20 19:29:13 +00:00
parent 1ea3fb209b
commit eab9e3cffc
15 changed files with 289 additions and 100 deletions

View file

@ -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<String, AtomicInteger> requestCounts = new ConcurrentHashMap<>();
private final ConcurrentHashMap<String, AtomicInteger> 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();
}
}

View file

@ -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();
}
}

View file

@ -13,6 +13,7 @@ import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain; 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.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository; import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
@ -51,11 +52,13 @@ public class SecurityConfiguration {
if(loginEnabledValue) { if(loginEnabledValue) {
http.csrf(csrf -> csrf.disable()); http.csrf(csrf -> csrf.disable());
http.addFilterAfter(firstLoginFilter, UsernamePasswordAuthenticationFilter.class); //http.addFilterBefore(rateLimitingFilter(), UsernamePasswordAuthenticationFilter.class);
//http.addFilterAfter(firstLoginFilter, UsernamePasswordAuthenticationFilter.class);
http http
.formLogin(formLogin -> formLogin .formLogin(formLogin -> formLogin
.loginPage("/login") .loginPage("/login")
.defaultSuccessUrl("/") // .defaultSuccessUrl("/")
.successHandler(new SavedRequestAwareAuthenticationSuccessHandler())
.failureHandler(new CustomAuthenticationFailureHandler()) .failureHandler(new CustomAuthenticationFailureHandler())
.permitAll() .permitAll()
) )
@ -86,6 +89,13 @@ public class SecurityConfiguration {
} }
@Bean
public IPRateLimitingFilter rateLimitingFilter() {
int maxRequestsPerIp = 10000; // Example limit
return new IPRateLimitingFilter(maxRequestsPerIp, maxRequestsPerIp);
}
@Bean @Bean
public DaoAuthenticationProvider authenticationProvider() { public DaoAuthenticationProvider authenticationProvider() {

View file

@ -29,12 +29,12 @@ import stirling.software.SPDF.model.api.SplitPdfBySectionsRequest;
import stirling.software.SPDF.utils.WebResponseUtils; import stirling.software.SPDF.utils.WebResponseUtils;
@RestController @RestController
@RequestMapping("/api/v1/general") @RequestMapping("/api/v1/general")
@Tag(name = "Misc", description = "Miscellaneous APIs") @Tag(name = "General", description = "General APIs")
public class SplitPdfBySectionsController { public class SplitPdfBySectionsController {
@PostMapping(value = "/split-pdf-by-sections", consumes = "multipart/form-data") @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<byte[]> splitPdf(@ModelAttribute SplitPdfBySectionsRequest request) throws Exception { public ResponseEntity<byte[]> splitPdf(@ModelAttribute SplitPdfBySectionsRequest request) throws Exception {
List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<>(); List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<>();

View file

@ -26,13 +26,13 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@RestController @RestController
@RequestMapping("/api/v1/general") @RequestMapping("/api/v1/general")
@Tag(name = "Misc", description = "Miscellaneous APIs") @Tag(name = "General", description = "General APIs")
public class SplitPdfBySizeController { public class SplitPdfBySizeController {
@PostMapping(value = "/split-by-size-or-count", consumes = "multipart/form-data") @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" @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<byte[]> autoSplitPdf(@ModelAttribute SplitPdfBySizeOrCountRequest request) throws Exception { public ResponseEntity<byte[]> autoSplitPdf(@ModelAttribute SplitPdfBySizeOrCountRequest request) throws Exception {
List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<ByteArrayOutputStream>(); List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<ByteArrayOutputStream>();

View file

@ -28,7 +28,7 @@ import stirling.software.SPDF.model.api.extract.PDFFilePage;
@RestController @RestController
@RequestMapping("/api/v1/convert") @RequestMapping("/api/v1/convert")
@Tag(name = "General", description = "General APIs") @Tag(name = "Convert", description = "Convert APIs")
public class ExtractController { public class ExtractController {
private static final Logger logger = LoggerFactory.getLogger(CropController.class); private static final Logger logger = LoggerFactory.getLogger(CropController.class);

View file

@ -43,7 +43,7 @@ public class AutoSplitPdfController {
private static final String QR_CONTENT = "https://github.com/Frooodle/Stirling-PDF"; private static final String QR_CONTENT = "https://github.com/Frooodle/Stirling-PDF";
@PostMapping(value = "/auto-split-pdf", consumes = "multipart/form-data") @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<byte[]> autoSplitPdf(@ModelAttribute AutoSplitPdfRequest request) throws IOException { public ResponseEntity<byte[]> autoSplitPdf(@ModelAttribute AutoSplitPdfRequest request) throws IOException {
MultipartFile file = request.getFileInput(); MultipartFile file = request.getFileInput();
boolean duplexMode = request.isDuplexMode(); boolean duplexMode = request.isDuplexMode();

View file

@ -15,6 +15,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.model.api.PDFFile; import stirling.software.SPDF.model.api.PDFFile;
import stirling.software.SPDF.utils.WebResponseUtils; import stirling.software.SPDF.utils.WebResponseUtils;
@ -25,6 +26,7 @@ public class ShowJavascript {
private static final Logger logger = LoggerFactory.getLogger(ShowJavascript.class); private static final Logger logger = LoggerFactory.getLogger(ShowJavascript.class);
@PostMapping(consumes = "multipart/form-data", value = "/show-javascript") @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<byte[]> extractHeader(@ModelAttribute PDFFile request) throws Exception { public ResponseEntity<byte[]> extractHeader(@ModelAttribute PDFFile request) throws Exception {
MultipartFile inputFile = request.getFileInput(); MultipartFile inputFile = request.getFileInput();
String script = ""; String script = "";

View file

@ -421,8 +421,11 @@ public class PipelineController {
@PostMapping("/handleData") @PostMapping("/handleData")
public ResponseEntity<byte[]> handleData(@ModelAttribute HandleDataRequest request) { public ResponseEntity<byte[]> handleData(@ModelAttribute HandleDataRequest request) {
MultipartFile[] files = request.getFileInputs(); MultipartFile[] files = request.getFileInput();
String jsonString = request.getJsonString(); String jsonString = request.getJson();
if(files == null) {
return null;
}
logger.info("Received POST request to /handleData with {} files", files.length); logger.info("Received POST request to /handleData with {} files", files.length);
try { try {
List<Resource> outputFiles = handleFiles(files, jsonString); List<Resource> outputFiles = handleFiles(files, jsonString);

View file

@ -1,5 +1,6 @@
package stirling.software.SPDF.controller.web; package stirling.software.SPDF.controller.web;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
@ -41,6 +42,7 @@ public class GeneralWebController {
model.addAttribute("currentPage", "pipeline"); model.addAttribute("currentPage", "pipeline");
List<String> pipelineConfigs = new ArrayList<>(); List<String> pipelineConfigs = new ArrayList<>();
if(new File("./pipeline/defaultWebUIConfigs/").exists()) {
try (Stream<Path> paths = Files.walk(Paths.get("./pipeline/defaultWebUIConfigs/"))) { try (Stream<Path> paths = Files.walk(Paths.get("./pipeline/defaultWebUIConfigs/"))) {
List<Path> jsonFiles = paths List<Path> jsonFiles = paths
.filter(Files::isRegularFile) .filter(Files::isRegularFile)
@ -66,6 +68,7 @@ public class GeneralWebController {
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
} }
}
model.addAttribute("pipelineConfigs", pipelineConfigs); model.addAttribute("pipelineConfigs", pipelineConfigs);

View file

@ -13,8 +13,8 @@ import lombok.NoArgsConstructor;
public class HandleDataRequest { public class HandleDataRequest {
@Schema(description = "The input files") @Schema(description = "The input files")
private MultipartFile[] fileInputs; private MultipartFile[] fileInput;
@Schema(description = "JSON String") @Schema(description = "JSON String")
private String jsonString; private String json;
} }

View file

@ -75,6 +75,13 @@ table th, table td {
border: none; border: none;
color: #fff !important; color: #fff !important;
} }
.btn-warning {
background-color: #ffc107 !important;
border: none;
color: #000 !important;
}
.btn-outline-secondary { .btn-outline-secondary {
color: #fff !important; color: #fff !important;
border-color: #fff; 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 <hr> */ background-color: rgba(255, 255, 255, 0.6); /* for some browsers that might use background instead of border for <hr> */
} }
.modal-content {
color: #fff !important;
border-color: #fff;
}
#global-buttons-container input { #global-buttons-container input {
background-color: #323948; background-color: #323948;
caret-color: #ffffff; caret-color: #ffffff;

View file

@ -20,9 +20,20 @@ function validatePipeline() {
console.log("currentOperationDescription", currentOperationDescription); console.log("currentOperationDescription", currentOperationDescription);
console.log("nextOperationDescription", nextOperationDescription); 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 currentOperationOutput = currentOperationDescription.match(/Output:([A-Z\/]*)/)?.[1] || "";
let nextOperationInput = nextOperationDescription.match(/Input:([A-Z\/]*)/)?.[1] || ""; let nextOperationInput = nextOperationDescription.match(/Input:([A-Z\/]*)/)?.[1] || "";
console.log("Operation " + currentOperation + " Output: " + currentOperationOutput); console.log("Operation " + currentOperation + " Output: " + currentOperationOutput);
console.log("Operation " + nextOperation + " Input: " + nextOperationInput); console.log("Operation " + nextOperation + " Input: " + nextOperationInput);
@ -99,7 +110,7 @@ document.getElementById('submitConfigBtn').addEventListener('click', function()
formData.append('json', pipelineConfigJson); formData.append('json', pipelineConfigJson);
console.log("formData", formData); console.log("formData", formData);
fetch('/handleData', { fetch('/api/v1/pipeline/handleData', {
method: 'POST', method: 'POST',
body: formData body: formData
}) })
@ -120,16 +131,17 @@ document.getElementById('submitConfigBtn').addEventListener('click', function()
}); });
let apiDocs = {}; let apiDocs = {};
let apiSchemas = {};
let operationSettings = {}; let operationSettings = {};
fetch('v3/api-docs') fetch('v1/api-docs')
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
apiDocs = data.paths; apiDocs = data.paths;
apiSchemas = data.components.schemas;
let operationsDropdown = document.getElementById('operationsDropdown'); 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 = ''; operationsDropdown.innerHTML = '';
@ -138,6 +150,9 @@ fetch('v3/api-docs')
// Group operations by tags // Group operations by tags
Object.keys(data.paths).forEach(operationPath => { Object.keys(data.paths).forEach(operationPath => {
let operation = data.paths[operationPath].post; let operation = data.paths[operationPath].post;
if(!operation || !operation.description) {
console.log(operationPath);
}
if (operation && !ignoreOperations.includes(operationPath) && !operation.description.includes("Type:MISO")) { if (operation && !ignoreOperations.includes(operationPath) && !operation.description.includes("Type:MISO")) {
let operationTag = operation.tags[0]; // This assumes each operation has exactly one tag let operationTag = operation.tags[0]; // This assumes each operation has exactly one tag
if (!operationsByTag[operationTag]) { if (!operationsByTag[operationTag]) {
@ -146,9 +161,9 @@ fetch('v3/api-docs')
operationsByTag[operationTag].push(operationPath); operationsByTag[operationTag].push(operationPath);
} }
}); });
console.log("operationsByTag", operationsByTag);
// Specify the order of tags // Specify the order of tags
let tagOrder = ["General", "Security", "Convert", "Other", "Filter"]; let tagOrder = ["General", "Security", "Convert", "Misc", "Filter"];
// Create dropdown options // Create dropdown options
tagOrder.forEach(tag => { tagOrder.forEach(tag => {
@ -158,8 +173,17 @@ fetch('v3/api-docs')
operationsByTag[tag].forEach(operationPath => { operationsByTag[tag].forEach(operationPath => {
let option = document.createElement('option'); let option = document.createElement('option');
let operationWithoutSlash = operationPath.replace(/\//g, ''); // Remove slashes console.log("operationPath", operationPath);
option.textContent = operationWithoutSlash; let operationPathDisplay = operationPath
operationPathDisplay = operationPath.replace(new RegExp("api/v1/" + tag.toLowerCase() + "/", 'i'), "");
console.log("operationPath2", operationPath);
if(operationPath.includes("/convert")){
operationPathDisplay = operationPathDisplay.replaceAll("(?<!^)/", " to ");
} else {
operationPathDisplay = operationPathDisplay.replace(/\//g, ''); // Remove slashes
}
option.textContent = operationPathDisplay;
option.value = operationPath; // Keep the value with slashes for querying option.value = operationPath; // Keep the value with slashes for querying
group.appendChild(option); group.appendChild(option);
}); });
@ -176,10 +200,23 @@ document.getElementById('addOperationBtn').addEventListener('click', function()
let listItem = document.createElement('li'); let listItem = document.createElement('li');
listItem.className = "list-group-item"; listItem.className = "list-group-item";
let hasSettings = (apiDocs[selectedOperation] && apiDocs[selectedOperation].post && let hasSettings = false;
((apiDocs[selectedOperation].post.parameters && apiDocs[selectedOperation].post.parameters.length > 0) || if (apiDocs[selectedOperation] && apiDocs[selectedOperation].post) {
(apiDocs[selectedOperation].post.requestBody && const postMethod = apiDocs[selectedOperation].post;
apiDocs[selectedOperation].post.requestBody.content['multipart/form-data'].schema.properties)));
// 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;
}
}
}
@ -188,14 +225,17 @@ document.getElementById('addOperationBtn').addEventListener('click', function()
<div class="d-flex justify-content-between align-items-center w-100"> <div class="d-flex justify-content-between align-items-center w-100">
<div class="operationName">${selectedOperation}</div> <div class="operationName">${selectedOperation}</div>
<div class="arrows d-flex"> <div class="arrows d-flex">
<button class="btn btn-secondary move-up btn-margin"><span>&uarr;</span></button> <button class="btn btn-secondary move-up ms-1"><span>&uarr;</span></button>
<button class="btn btn-secondary move-down btn-margin"><span>&darr;</span></button> <button class="btn btn-secondary move-down ms-1"><span>&darr;</span></button>
<button class="btn btn-warning pipelineSettings btn-margin" ${hasSettings ? "" : "disabled"}><span style="color: ${hasSettings ? "black" : "grey"};"></span></button> <button class="btn ${hasSettings ? 'btn-warning' : 'btn-secondary'} pipelineSettings ms-1" ${hasSettings ? "" : "disabled"}>
<button class="btn btn-danger remove"><span>X</span></button> <span style="color: ${hasSettings ? "white" : "grey"};"></span>
</button>
<button class="btn btn-danger remove ms-1"><span>X</span></button>
</div> </div>
</div> </div>
`; `;
pipelineList.appendChild(listItem); pipelineList.appendChild(listItem);
listItem.querySelector('.move-up').addEventListener('click', function(event) { listItem.querySelector('.move-up').addEventListener('click', function(event) {
@ -226,7 +266,10 @@ document.getElementById('addOperationBtn').addEventListener('click', function()
let pipelineSettingsModal = document.getElementById('pipelineSettingsModal'); let pipelineSettingsModal = document.getElementById('pipelineSettingsModal');
let pipelineSettingsContent = document.getElementById('pipelineSettingsContent'); let pipelineSettingsContent = document.getElementById('pipelineSettingsContent');
let operationData = apiDocs[operation].post.parameters || []; 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 // Combine operationData and requestBodyData into a single array
operationData = operationData.concat(Object.keys(requestBodyData).map(key => ({ operationData = operationData.concat(Object.keys(requestBodyData).map(key => ({
@ -240,14 +283,19 @@ document.getElementById('addOperationBtn').addEventListener('click', function()
// If the parameter name is 'fileInput', return early to skip the rest of this iteration // If the parameter name is 'fileInput', return early to skip the rest of this iteration
if (parameter.name === 'fileInput') return; if (parameter.name === 'fileInput') return;
console.log("parameter", parameter);
let parameterDiv = document.createElement('div'); let parameterDiv = document.createElement('div');
parameterDiv.className = "mb-3"; parameterDiv.className = "mb-3";
let parameterLabel = document.createElement('label'); let parameterLabel = document.createElement('label');
parameterLabel.textContent = `${parameter.name} (${parameter.schema.type}): `; parameterLabel.textContent = `${parameter.name} (${parameter.schema.type}): `;
parameterLabel.title = parameter.description; parameterLabel.title = parameter.schema.description;
parameterLabel.setAttribute('for', parameter.name);
parameterDiv.appendChild(parameterLabel); parameterDiv.appendChild(parameterLabel);
let defaultValue = parameter.schema.example;
if (defaultValue === undefined) defaultValue = parameter.schema.default;
let parameterInput; let parameterInput;
// check if enum exists in schema // check if enum exists in schema
@ -282,6 +330,7 @@ document.getElementById('addOperationBtn').addEventListener('click', function()
parameterInput = document.createElement('input'); parameterInput = document.createElement('input');
parameterInput.type = 'text'; parameterInput.type = 'text';
parameterInput.className = "form-control"; parameterInput.className = "form-control";
if (defaultValue !== undefined) parameterInput.value = defaultValue;
} }
break; break;
case 'number': case 'number':
@ -289,10 +338,12 @@ document.getElementById('addOperationBtn').addEventListener('click', function()
parameterInput = document.createElement('input'); parameterInput = document.createElement('input');
parameterInput.type = 'number'; parameterInput.type = 'number';
parameterInput.className = "form-control"; parameterInput.className = "form-control";
if (defaultValue !== undefined) parameterInput.value = defaultValue;
break; break;
case 'boolean': case 'boolean':
parameterInput = document.createElement('input'); parameterInput = document.createElement('input');
parameterInput.type = 'checkbox'; parameterInput.type = 'checkbox';
if (defaultValue === true) parameterInput.checked = true;
break; break;
case 'array': case 'array':
case 'object': case 'object':
@ -304,10 +355,13 @@ document.getElementById('addOperationBtn').addEventListener('click', function()
parameterInput = document.createElement('input'); parameterInput = document.createElement('input');
parameterInput.type = 'text'; parameterInput.type = 'text';
parameterInput.className = "form-control"; parameterInput.className = "form-control";
if (defaultValue !== undefined) parameterInput.value = defaultValue;
} }
} }
parameterInput.id = parameter.name; parameterInput.id = parameter.name;
console.log("defaultValue", defaultValue);
console.log("parameterInput", parameterInput);
if (operationSettings[operation] && operationSettings[operation][parameter.name] !== undefined) { if (operationSettings[operation] && operationSettings[operation][parameter.name] !== undefined) {
let savedValue = operationSettings[operation][parameter.name]; let savedValue = operationSettings[operation][parameter.name];
@ -327,7 +381,7 @@ document.getElementById('addOperationBtn').addEventListener('click', function()
parameterInput.value = savedValue; parameterInput.value = savedValue;
} }
} }
console.log("parameterInput2", parameterInput);
parameterDiv.appendChild(parameterInput); parameterDiv.appendChild(parameterInput);
pipelineSettingsContent.appendChild(parameterDiv); pipelineSettingsContent.appendChild(parameterDiv);
@ -340,6 +394,8 @@ document.getElementById('addOperationBtn').addEventListener('click', function()
event.preventDefault(); event.preventDefault();
let settings = {}; let settings = {};
operationData.forEach(parameter => { operationData.forEach(parameter => {
console.log("parameter.name", parameter.name);
if(parameter.name !== "fileInput"){
let value = document.getElementById(parameter.name).value; let value = document.getElementById(parameter.name).value;
switch (parameter.schema.type) { switch (parameter.schema.type) {
case 'number': case 'number':
@ -360,30 +416,45 @@ document.getElementById('addOperationBtn').addEventListener('click', function()
default: default:
settings[parameter.name] = value; settings[parameter.name] = value;
} }
}
}); });
operationSettings[operation] = settings; operationSettings[operation] = settings;
console.log(settings); console.log(settings);
pipelineSettingsModal.style.display = "none"; //pipelineSettingsModal.style.display = "none";
}); });
pipelineSettingsContent.appendChild(saveButton); pipelineSettingsContent.appendChild(saveButton);
pipelineSettingsModal.style.display = "block"; //pipelineSettingsModal.style.display = "block";
pipelineSettingsModal.getElementsByClassName("close")[0].onclick = function() { //pipelineSettingsModal.getElementsByClassName("close")[0].onclick = function() {
pipelineSettingsModal.style.display = "none"; // 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');
}
// Remove any existing event listeners
saveBtn.removeEventListener('click', savePipeline);
// Add the event listener
saveBtn.addEventListener('click', savePipeline);
console.log("saveBtn", saveBtn)
function savePipeline() {
document.getElementById('savePipelineBtn').addEventListener('click', function() {
if (validatePipeline() === false) { if (validatePipeline() === false) {
return; return;
} }
var pipelineName = document.getElementById('pipelineName').value; var pipelineName = document.getElementById('pipelineName').value;
let pipelineList = document.getElementById('pipelineList').children; let pipelineList = document.getElementById('pipelineList').children;
let pipelineConfig = { let pipelineConfig = {
@ -406,7 +477,7 @@ document.getElementById('addOperationBtn').addEventListener('click', function()
"parameters": parameters "parameters": parameters
}); });
} }
console.log("Downloading..");
let a = document.createElement('a'); let a = document.createElement('a');
a.href = URL.createObjectURL(new Blob([JSON.stringify(pipelineConfig, null, 2)], { a.href = URL.createObjectURL(new Blob([JSON.stringify(pipelineConfig, null, 2)], {
type: 'application/json' type: 'application/json'
@ -417,7 +488,7 @@ document.getElementById('addOperationBtn').addEventListener('click', function()
document.body.appendChild(a); document.body.appendChild(a);
a.click(); a.click();
document.body.removeChild(a); document.body.removeChild(a);
}); }
async function processPipelineConfig(configString) { async function processPipelineConfig(configString) {
let pipelineConfig = JSON.parse(configString); let pipelineConfig = JSON.parse(configString);
@ -491,4 +562,3 @@ document.getElementById('addOperationBtn').addEventListener('click', function()
}); });
});

View file

@ -28,7 +28,7 @@
<div class="features-container "> <div class="features-container ">
<!-- <div th:replace="~{fragments/card :: card(id='pipeline', cardTitle=#{home.pipeline.title}, cardText=#{home.pipeline.desc}, cardLink='pipeline', svgPath='images/pipeline.svg')}"></div> --> <div th:replace="~{fragments/card :: card(id='pipeline', cardTitle=#{home.pipeline.title}, cardText=#{home.pipeline.desc}, cardLink='pipeline', svgPath='images/pipeline.svg')}"></div>
<div th:replace="~{fragments/card :: card(id='view-pdf', cardTitle=#{home.viewPdf.title}, cardText=#{home.viewPdf.desc}, cardLink='view-pdf', svgPath='images/book-opened.svg')}"></div> <div th:replace="~{fragments/card :: card(id='view-pdf', cardTitle=#{home.viewPdf.title}, cardText=#{home.viewPdf.desc}, cardLink='view-pdf', svgPath='images/book-opened.svg')}"></div>
<div th:replace="~{fragments/card :: card(id='multi-tool', cardTitle=#{home.multiTool.title}, cardText=#{home.multiTool.desc}, cardLink='multi-tool', svgPath='images/tools.svg')}"></div> <div th:replace="~{fragments/card :: card(id='multi-tool', cardTitle=#{home.multiTool.title}, cardText=#{home.multiTool.desc}, cardLink='multi-tool', svgPath='images/tools.svg')}"></div>

View file

@ -34,6 +34,7 @@
<br> <br> <br> <br>
<div class="container"> <div class="container">
<div class="row justify-content-center"> <div class="row justify-content-center">
<div class="bordered-box"> <div class="bordered-box">
@ -66,7 +67,7 @@
<!-- The Modal --> <!-- The Modal -->
<div class="modal" id="pipelineSettingsModal"> <div class="modal" id="pipelineSettingsModal">
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content dark-card">
<!-- Modal Header --> <!-- Modal Header -->
<div class="modal-header"> <div class="modal-header">