changes pipeline
This commit is contained in:
parent
1ea3fb209b
commit
eab9e3cffc
15 changed files with 289 additions and 100 deletions
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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() {
|
||||||
|
|
|
@ -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<>();
|
||||||
|
|
||||||
|
|
|
@ -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>();
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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 = "";
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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,31 +42,33 @@ public class GeneralWebController {
|
||||||
model.addAttribute("currentPage", "pipeline");
|
model.addAttribute("currentPage", "pipeline");
|
||||||
|
|
||||||
List<String> pipelineConfigs = new ArrayList<>();
|
List<String> pipelineConfigs = new ArrayList<>();
|
||||||
try (Stream<Path> paths = Files.walk(Paths.get("./pipeline/defaultWebUIConfigs/"))) {
|
if(new File("./pipeline/defaultWebUIConfigs/").exists()) {
|
||||||
List<Path> jsonFiles = paths
|
try (Stream<Path> paths = Files.walk(Paths.get("./pipeline/defaultWebUIConfigs/"))) {
|
||||||
.filter(Files::isRegularFile)
|
List<Path> jsonFiles = paths
|
||||||
.filter(p -> p.toString().endsWith(".json"))
|
.filter(Files::isRegularFile)
|
||||||
.collect(Collectors.toList());
|
.filter(p -> p.toString().endsWith(".json"))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
for (Path jsonFile : jsonFiles) {
|
for (Path jsonFile : jsonFiles) {
|
||||||
String content = Files.readString(jsonFile, StandardCharsets.UTF_8);
|
String content = Files.readString(jsonFile, StandardCharsets.UTF_8);
|
||||||
pipelineConfigs.add(content);
|
pipelineConfigs.add(content);
|
||||||
}
|
}
|
||||||
List<Map<String, String>> pipelineConfigsWithNames = new ArrayList<>();
|
List<Map<String, String>> pipelineConfigsWithNames = new ArrayList<>();
|
||||||
for (String config : pipelineConfigs) {
|
for (String config : pipelineConfigs) {
|
||||||
Map<String, Object> jsonContent = new ObjectMapper().readValue(config, new TypeReference<Map<String, Object>>(){});
|
Map<String, Object> jsonContent = new ObjectMapper().readValue(config, new TypeReference<Map<String, Object>>(){});
|
||||||
|
|
||||||
String name = (String) jsonContent.get("name");
|
String name = (String) jsonContent.get("name");
|
||||||
Map<String, String> configWithName = new HashMap<>();
|
Map<String, String> configWithName = new HashMap<>();
|
||||||
configWithName.put("json", config);
|
configWithName.put("json", config);
|
||||||
configWithName.put("name", name);
|
configWithName.put("name", name);
|
||||||
pipelineConfigsWithNames.add(configWithName);
|
pipelineConfigsWithNames.add(configWithName);
|
||||||
}
|
}
|
||||||
model.addAttribute("pipelineConfigsWithNames", pipelineConfigsWithNames);
|
model.addAttribute("pipelineConfigsWithNames", pipelineConfigsWithNames);
|
||||||
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
model.addAttribute("pipelineConfigs", pipelineConfigs);
|
model.addAttribute("pipelineConfigs", pipelineConfigs);
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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,25 +200,41 @@ 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
listItem.innerHTML = `
|
listItem.innerHTML = `
|
||||||
<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>↑</span></button>
|
<button class="btn btn-secondary move-up ms-1"><span>↑</span></button>
|
||||||
<button class="btn btn-secondary move-down btn-margin"><span>↓</span></button>
|
<button class="btn btn-secondary move-down ms-1"><span>↓</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>
|
||||||
</div>
|
</button>
|
||||||
</div>
|
<button class="btn btn-danger remove ms-1"><span>X</span></button>
|
||||||
`;
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
|
||||||
pipelineList.appendChild(listItem);
|
pipelineList.appendChild(listItem);
|
||||||
|
|
||||||
|
@ -226,12 +266,15 @@ 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 => ({
|
||||||
name: key,
|
name: key,
|
||||||
schema: requestBodyData[key]
|
schema: requestBodyData[key]
|
||||||
})));
|
})));
|
||||||
|
|
||||||
pipelineSettingsContent.innerHTML = '';
|
pipelineSettingsContent.innerHTML = '';
|
||||||
|
@ -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,50 +394,67 @@ document.getElementById('addOperationBtn').addEventListener('click', function()
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
let settings = {};
|
let settings = {};
|
||||||
operationData.forEach(parameter => {
|
operationData.forEach(parameter => {
|
||||||
let value = document.getElementById(parameter.name).value;
|
console.log("parameter.name", parameter.name);
|
||||||
switch (parameter.schema.type) {
|
if(parameter.name !== "fileInput"){
|
||||||
case 'number':
|
let value = document.getElementById(parameter.name).value;
|
||||||
case 'integer':
|
switch (parameter.schema.type) {
|
||||||
settings[parameter.name] = Number(value);
|
case 'number':
|
||||||
break;
|
case 'integer':
|
||||||
case 'boolean':
|
settings[parameter.name] = Number(value);
|
||||||
settings[parameter.name] = document.getElementById(parameter.name).checked;
|
break;
|
||||||
break;
|
case 'boolean':
|
||||||
case 'array':
|
settings[parameter.name] = document.getElementById(parameter.name).checked;
|
||||||
case 'object':
|
break;
|
||||||
try {
|
case 'array':
|
||||||
settings[parameter.name] = JSON.parse(value);
|
case 'object':
|
||||||
} catch (err) {
|
try {
|
||||||
console.error(`Invalid JSON format for ${parameter.name}`);
|
settings[parameter.name] = JSON.parse(value);
|
||||||
}
|
} catch (err) {
|
||||||
break;
|
console.error(`Invalid JSON format for ${parameter.name}`);
|
||||||
default:
|
}
|
||||||
settings[parameter.name] = value;
|
break;
|
||||||
|
default:
|
||||||
|
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) {
|
//window.onclick = function(event) {
|
||||||
if (event.target == pipelineSettingsModal) {
|
// if (event.target == pipelineSettingsModal) {
|
||||||
pipelineSettingsModal.style.display = "none";
|
// pipelineSettingsModal.style.display = "none";
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('savePipelineBtn').addEventListener('click', function() {
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
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() {
|
||||||
|
|
||||||
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()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
});
|
|
|
@ -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>
|
||||||
|
|
|
@ -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">
|
||||||
|
|
Loading…
Reference in a new issue