Merge branch 'pipeline' of git@github.com:Frooodle/Stirling-PDF.git into pipeline
This commit is contained in:
commit
f313857f96
35 changed files with 885 additions and 602 deletions
|
@ -47,7 +47,7 @@ public class MergeController {
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/merge-pdfs")
|
@PostMapping(consumes = "multipart/form-data", value = "/merge-pdfs")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Merge multiple PDF files into one",
|
summary = "Merge multiple PDF files into one",
|
||||||
description = "This endpoint merges multiple PDF files into a single PDF file. The merged file will contain all pages from the input files in the order they were provided."
|
description = "This endpoint merges multiple PDF files into a single PDF file. The merged file will contain all pages from the input files in the order they were provided. Input:PDF Output:PDF Type:MISO"
|
||||||
)
|
)
|
||||||
public ResponseEntity<byte[]> mergePdfs(
|
public ResponseEntity<byte[]> mergePdfs(
|
||||||
@RequestPart(required = true, value = "fileInput")
|
@RequestPart(required = true, value = "fileInput")
|
||||||
|
|
|
@ -32,7 +32,7 @@ public class MultiPageLayoutController {
|
||||||
private static final Logger logger = LoggerFactory.getLogger(MultiPageLayoutController.class);
|
private static final Logger logger = LoggerFactory.getLogger(MultiPageLayoutController.class);
|
||||||
|
|
||||||
@PostMapping(value = "/multi-page-layout", consumes = "multipart/form-data")
|
@PostMapping(value = "/multi-page-layout", consumes = "multipart/form-data")
|
||||||
@Operation(summary = "Merge multiple pages of a PDF document into a single page", description = "This operation takes an input PDF file and the number of pages to merge into a single sheet in the output PDF file.")
|
@Operation(summary = "Merge multiple pages of a PDF document into a single page", description = "This operation takes an input PDF file and the number of pages to merge into a single sheet in the output PDF file. Input:PDF Output:PDF Type:SISO")
|
||||||
public ResponseEntity<byte[]> mergeMultiplePagesIntoOne(
|
public ResponseEntity<byte[]> mergeMultiplePagesIntoOne(
|
||||||
@Parameter(description = "The input PDF file", required = true) @RequestParam("fileInput") MultipartFile file,
|
@Parameter(description = "The input PDF file", required = true) @RequestParam("fileInput") MultipartFile file,
|
||||||
@Parameter(description = "The number of pages to fit onto a single sheet in the output PDF. Acceptable values are 2, 3, 4, 9, 16.", required = true, schema = @Schema(type = "integer", allowableValues = {
|
@Parameter(description = "The number of pages to fit onto a single sheet in the output PDF. Acceptable values are 2, 3, 4, 9, 16.", required = true, schema = @Schema(type = "integer", allowableValues = {
|
||||||
|
|
|
@ -27,7 +27,7 @@ public class RearrangePagesPDFController {
|
||||||
private static final Logger logger = LoggerFactory.getLogger(RearrangePagesPDFController.class);
|
private static final Logger logger = LoggerFactory.getLogger(RearrangePagesPDFController.class);
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/remove-pages")
|
@PostMapping(consumes = "multipart/form-data", value = "/remove-pages")
|
||||||
@Operation(summary = "Remove pages from a PDF file", description = "This endpoint removes specified pages from a given PDF file. Users can provide a comma-separated list of page numbers or ranges to delete.")
|
@Operation(summary = "Remove pages from a PDF file", description = "This endpoint removes specified pages from a given PDF file. Users can provide a comma-separated list of page numbers or ranges to delete. Input:PDF Output:PDF Type:SISO")
|
||||||
public ResponseEntity<byte[]> deletePages(
|
public ResponseEntity<byte[]> deletePages(
|
||||||
@RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file from which pages will be removed") MultipartFile pdfFile,
|
@RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file from which pages will be removed") MultipartFile pdfFile,
|
||||||
@RequestParam("pagesToDelete") @Parameter(description = "Comma-separated list of pages or page ranges to delete, e.g., '1,3,5-8'") String pagesToDelete)
|
@RequestParam("pagesToDelete") @Parameter(description = "Comma-separated list of pages or page ranges to delete, e.g., '1,3,5-8'") String pagesToDelete)
|
||||||
|
@ -151,7 +151,7 @@ public class RearrangePagesPDFController {
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/rearrange-pages")
|
@PostMapping(consumes = "multipart/form-data", value = "/rearrange-pages")
|
||||||
@Operation(summary = "Rearrange pages in a PDF file", description = "This endpoint rearranges pages in a given PDF file based on the specified page order or custom mode. Users can provide a page order as a comma-separated list of page numbers or page ranges, or a custom mode.")
|
@Operation(summary = "Rearrange pages in a PDF file", description = "This endpoint rearranges pages in a given PDF file based on the specified page order or custom mode. Users can provide a page order as a comma-separated list of page numbers or page ranges, or a custom mode. Input:PDF Output:PDF")
|
||||||
public ResponseEntity<byte[]> rearrangePages(
|
public ResponseEntity<byte[]> rearrangePages(
|
||||||
@RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file to rearrange pages") MultipartFile pdfFile,
|
@RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file to rearrange pages") MultipartFile pdfFile,
|
||||||
@RequestParam(required = false, value = "pageOrder") @Parameter(description = "The new page order as a comma-separated list of page numbers, page ranges (e.g., '1,3,5-7'), or functions in the format 'an+b' where 'a' is the multiplier of the page number 'n', and 'b' is a constant (e.g., '2n+1', '3n', '6n-5')") String pageOrder,
|
@RequestParam(required = false, value = "pageOrder") @Parameter(description = "The new page order as a comma-separated list of page numbers, page ranges (e.g., '1,3,5-7'), or functions in the format 'an+b' where 'a' is the multiplier of the page number 'n', and 'b' is a constant (e.g., '2n+1', '3n', '6n-5')") String pageOrder,
|
||||||
|
|
|
@ -26,7 +26,7 @@ public class RotationController {
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/rotate-pdf")
|
@PostMapping(consumes = "multipart/form-data", value = "/rotate-pdf")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Rotate a PDF file",
|
summary = "Rotate a PDF file",
|
||||||
description = "This endpoint rotates a given PDF file by a specified angle. The angle must be a multiple of 90."
|
description = "This endpoint rotates a given PDF file by a specified angle. The angle must be a multiple of 90. Input:PDF Output:PDF Type:SISO"
|
||||||
)
|
)
|
||||||
public ResponseEntity<byte[]> rotatePDF(
|
public ResponseEntity<byte[]> rotatePDF(
|
||||||
@RequestPart(required = true, value = "fileInput")
|
@RequestPart(required = true, value = "fileInput")
|
||||||
|
|
|
@ -47,7 +47,7 @@ public class ScalePagesController {
|
||||||
private static final Logger logger = LoggerFactory.getLogger(ScalePagesController.class);
|
private static final Logger logger = LoggerFactory.getLogger(ScalePagesController.class);
|
||||||
|
|
||||||
@PostMapping(value = "/scale-pages", consumes = "multipart/form-data")
|
@PostMapping(value = "/scale-pages", consumes = "multipart/form-data")
|
||||||
@Operation(summary = "Change the size of a PDF page/document", description = "This operation takes an input PDF file and the size to scale the pages to in the output PDF file.")
|
@Operation(summary = "Change the size of a PDF page/document", description = "This operation takes an input PDF file and the size to scale the pages to in the output PDF file. Input:PDF Output:PDF Type:SISO")
|
||||||
public ResponseEntity<byte[]> scalePages(
|
public ResponseEntity<byte[]> scalePages(
|
||||||
@Parameter(description = "The input PDF file", required = true) @RequestParam("fileInput") MultipartFile file,
|
@Parameter(description = "The input PDF file", required = true) @RequestParam("fileInput") MultipartFile file,
|
||||||
@Parameter(description = "The scale of pages in the output PDF. Acceptable values are A0-A10, B0-B9, LETTER, TABLOID, LEDGER, LEGAL, EXECUTIVE.", required = true, schema = @Schema(type = "String", allowableValues = {
|
@Parameter(description = "The scale of pages in the output PDF. Acceptable values are A0-A10, B0-B9, LETTER, TABLOID, LEDGER, LEGAL, EXECUTIVE.", required = true, schema = @Schema(type = "String", allowableValues = {
|
||||||
|
|
|
@ -38,7 +38,7 @@ public class SplitPDFController {
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/split-pages")
|
@PostMapping(consumes = "multipart/form-data", value = "/split-pages")
|
||||||
@Operation(summary = "Split a PDF file into separate documents",
|
@Operation(summary = "Split a PDF file into separate documents",
|
||||||
description = "This endpoint splits a given PDF file into separate documents based on the specified page numbers or ranges. Users can specify pages using individual numbers, ranges, or 'all' for every page.")
|
description = "This endpoint splits a given PDF file into separate documents based on the specified page numbers or ranges. Users can specify pages using individual numbers, ranges, or 'all' for every page. Input:PDF Output:PDF Type:SIMO")
|
||||||
public ResponseEntity<byte[]> splitPdf(
|
public ResponseEntity<byte[]> splitPdf(
|
||||||
@RequestPart(required = true, value = "fileInput")
|
@RequestPart(required = true, value = "fileInput")
|
||||||
@Parameter(description = "The input PDF file to be split")
|
@Parameter(description = "The input PDF file to be split")
|
||||||
|
|
|
@ -29,7 +29,7 @@ public class ConvertImgPDFController {
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/pdf-to-img")
|
@PostMapping(consumes = "multipart/form-data", value = "/pdf-to-img")
|
||||||
@Operation(summary = "Convert PDF to image(s)",
|
@Operation(summary = "Convert PDF to image(s)",
|
||||||
description = "This endpoint converts a PDF file to image(s) with the specified image format, color type, and DPI. Users can choose to get a single image or multiple images.")
|
description = "This endpoint converts a PDF file to image(s) with the specified image format, color type, and DPI. Users can choose to get a single image or multiple images. Input:PDF Output:Image Type:SI-Conditional")
|
||||||
public ResponseEntity<Resource> convertToImage(
|
public ResponseEntity<Resource> convertToImage(
|
||||||
@RequestPart(required = true, value = "fileInput")
|
@RequestPart(required = true, value = "fileInput")
|
||||||
@Parameter(description = "The input PDF file to be converted")
|
@Parameter(description = "The input PDF file to be converted")
|
||||||
|
@ -83,7 +83,7 @@ public class ConvertImgPDFController {
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/img-to-pdf")
|
@PostMapping(consumes = "multipart/form-data", value = "/img-to-pdf")
|
||||||
@Operation(summary = "Convert images to a PDF file",
|
@Operation(summary = "Convert images to a PDF file",
|
||||||
description = "This endpoint converts one or more images to a PDF file. Users can specify whether to stretch the images to fit the PDF page, and whether to automatically rotate the images.")
|
description = "This endpoint converts one or more images to a PDF file. Users can specify whether to stretch the images to fit the PDF page, and whether to automatically rotate the images. Input:Image Output:PDF Type:MISO")
|
||||||
public ResponseEntity<byte[]> convertToPdf(
|
public ResponseEntity<byte[]> convertToPdf(
|
||||||
@RequestPart(required = true, value = "fileInput")
|
@RequestPart(required = true, value = "fileInput")
|
||||||
@Parameter(description = "The input images to be converted to a PDF file")
|
@Parameter(description = "The input images to be converted to a PDF file")
|
||||||
|
|
|
@ -57,8 +57,8 @@ public class ConvertOfficeController {
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/file-to-pdf")
|
@PostMapping(consumes = "multipart/form-data", value = "/file-to-pdf")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Convert a file to a PDF using OCR",
|
summary = "Convert a file to a PDF using LibreOffice",
|
||||||
description = "This endpoint converts a given file to a PDF using Optical Character Recognition (OCR). The filename of the resulting PDF will be the original filename with '_convertedToPDF.pdf' appended."
|
description = "This endpoint converts a given file to a PDF using LibreOffice API Input:Any Output:PDF Type:SISO"
|
||||||
)
|
)
|
||||||
public ResponseEntity<byte[]> processPdfWithOCR(
|
public ResponseEntity<byte[]> processPdfWithOCR(
|
||||||
@RequestPart(required = true, value = "fileInput")
|
@RequestPart(required = true, value = "fileInput")
|
||||||
|
|
|
@ -18,7 +18,7 @@ import stirling.software.SPDF.utils.PDFToFile;
|
||||||
public class ConvertPDFToOffice {
|
public class ConvertPDFToOffice {
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/pdf-to-html")
|
@PostMapping(consumes = "multipart/form-data", value = "/pdf-to-html")
|
||||||
@Operation(summary = "Convert PDF to HTML", description = "This endpoint converts a PDF file to HTML format.")
|
@Operation(summary = "Convert PDF to HTML", description = "This endpoint converts a PDF file to HTML format. Input:PDF Output:HTML Type:SISO")
|
||||||
public ResponseEntity<byte[]> processPdfToHTML(
|
public ResponseEntity<byte[]> processPdfToHTML(
|
||||||
@RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file to be converted to HTML format", required = true) MultipartFile inputFile)
|
@RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file to be converted to HTML format", required = true) MultipartFile inputFile)
|
||||||
throws IOException, InterruptedException {
|
throws IOException, InterruptedException {
|
||||||
|
@ -27,7 +27,7 @@ public class ConvertPDFToOffice {
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/pdf-to-presentation")
|
@PostMapping(consumes = "multipart/form-data", value = "/pdf-to-presentation")
|
||||||
@Operation(summary = "Convert PDF to Presentation format", description = "This endpoint converts a given PDF file to a Presentation format.")
|
@Operation(summary = "Convert PDF to Presentation format", description = "This endpoint converts a given PDF file to a Presentation format. Input:PDF Output:PPT Type:SISO")
|
||||||
public ResponseEntity<byte[]> processPdfToPresentation(
|
public ResponseEntity<byte[]> processPdfToPresentation(
|
||||||
@RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file") MultipartFile inputFile,
|
@RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file") MultipartFile inputFile,
|
||||||
@RequestParam("outputFormat") @Parameter(description = "The output Presentation format", schema = @Schema(allowableValues = {
|
@RequestParam("outputFormat") @Parameter(description = "The output Presentation format", schema = @Schema(allowableValues = {
|
||||||
|
@ -38,7 +38,7 @@ public class ConvertPDFToOffice {
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/pdf-to-text")
|
@PostMapping(consumes = "multipart/form-data", value = "/pdf-to-text")
|
||||||
@Operation(summary = "Convert PDF to Text or RTF format", description = "This endpoint converts a given PDF file to Text or RTF format.")
|
@Operation(summary = "Convert PDF to Text or RTF format", description = "This endpoint converts a given PDF file to Text or RTF format. Input:PDF Output:TXT Type:SISO")
|
||||||
public ResponseEntity<byte[]> processPdfToRTForTXT(
|
public ResponseEntity<byte[]> processPdfToRTForTXT(
|
||||||
@RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file") MultipartFile inputFile,
|
@RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file") MultipartFile inputFile,
|
||||||
@RequestParam("outputFormat") @Parameter(description = "The output Text or RTF format", schema = @Schema(allowableValues = {
|
@RequestParam("outputFormat") @Parameter(description = "The output Text or RTF format", schema = @Schema(allowableValues = {
|
||||||
|
@ -49,7 +49,7 @@ public class ConvertPDFToOffice {
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/pdf-to-word")
|
@PostMapping(consumes = "multipart/form-data", value = "/pdf-to-word")
|
||||||
@Operation(summary = "Convert PDF to Word document", description = "This endpoint converts a given PDF file to a Word document format.")
|
@Operation(summary = "Convert PDF to Word document", description = "This endpoint converts a given PDF file to a Word document format. Input:PDF Output:WORD Type:SISO")
|
||||||
public ResponseEntity<byte[]> processPdfToWord(
|
public ResponseEntity<byte[]> processPdfToWord(
|
||||||
@RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file") MultipartFile inputFile,
|
@RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file") MultipartFile inputFile,
|
||||||
@RequestParam("outputFormat") @Parameter(description = "The output Word document format", schema = @Schema(allowableValues = {
|
@RequestParam("outputFormat") @Parameter(description = "The output Word document format", schema = @Schema(allowableValues = {
|
||||||
|
@ -60,7 +60,7 @@ public class ConvertPDFToOffice {
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/pdf-to-xml")
|
@PostMapping(consumes = "multipart/form-data", value = "/pdf-to-xml")
|
||||||
@Operation(summary = "Convert PDF to XML", description = "This endpoint converts a PDF file to an XML file.")
|
@Operation(summary = "Convert PDF to XML", description = "This endpoint converts a PDF file to an XML file. Input:PDF Output:XML Type:SISO")
|
||||||
public ResponseEntity<byte[]> processPdfToXML(
|
public ResponseEntity<byte[]> processPdfToXML(
|
||||||
@RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file to be converted to an XML file", required = true) MultipartFile inputFile)
|
@RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file to be converted to an XML file", required = true) MultipartFile inputFile)
|
||||||
throws IOException, InterruptedException {
|
throws IOException, InterruptedException {
|
||||||
|
|
|
@ -23,7 +23,7 @@ public class ConvertPDFToPDFA {
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/pdf-to-pdfa")
|
@PostMapping(consumes = "multipart/form-data", value = "/pdf-to-pdfa")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Convert a PDF to a PDF/A",
|
summary = "Convert a PDF to a PDF/A",
|
||||||
description = "This endpoint converts a PDF file to a PDF/A file. PDF/A is a format designed for long-term archiving of digital documents."
|
description = "This endpoint converts a PDF file to a PDF/A file. PDF/A is a format designed for long-term archiving of digital documents. Input:PDF Output:PDF Type:SISO"
|
||||||
)
|
)
|
||||||
public ResponseEntity<byte[]> pdfToPdfA(
|
public ResponseEntity<byte[]> pdfToPdfA(
|
||||||
@RequestPart(required = true, value = "fileInput")
|
@RequestPart(required = true, value = "fileInput")
|
||||||
|
|
|
@ -29,6 +29,7 @@ import org.springframework.web.multipart.MultipartFile;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
import stirling.software.SPDF.pdf.ImageFinder;
|
import stirling.software.SPDF.pdf.ImageFinder;
|
||||||
|
import stirling.software.SPDF.utils.PdfUtils;
|
||||||
import stirling.software.SPDF.utils.ProcessExecutor;
|
import stirling.software.SPDF.utils.ProcessExecutor;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
|
@ -38,7 +39,7 @@ public class BlankPageController {
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/remove-blanks")
|
@PostMapping(consumes = "multipart/form-data", value = "/remove-blanks")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Remove blank pages from a PDF file",
|
summary = "Remove blank pages from a PDF file",
|
||||||
description = "This endpoint removes blank pages from a given PDF file. Users can specify the threshold and white percentage to tune the detection of blank pages."
|
description = "This endpoint removes blank pages from a given PDF file. Users can specify the threshold and white percentage to tune the detection of blank pages. Input:PDF Output:PDF Type:SISO"
|
||||||
)
|
)
|
||||||
public ResponseEntity<byte[]> removeBlankPages(
|
public ResponseEntity<byte[]> removeBlankPages(
|
||||||
@RequestPart(required = true, value = "fileInput")
|
@RequestPart(required = true, value = "fileInput")
|
||||||
|
@ -71,7 +72,7 @@ public class BlankPageController {
|
||||||
pagesToKeepIndex.add(pageIndex);
|
pagesToKeepIndex.add(pageIndex);
|
||||||
System.out.println("page " + pageIndex + " has text");
|
System.out.println("page " + pageIndex + " has text");
|
||||||
} else {
|
} else {
|
||||||
boolean hasImages = hasImagesOnPage(page);
|
boolean hasImages = PdfUtils.hasImagesOnPage(page);
|
||||||
if (hasImages) {
|
if (hasImages) {
|
||||||
System.out.println("page " + pageIndex + " has image");
|
System.out.println("page " + pageIndex + " has image");
|
||||||
|
|
||||||
|
@ -120,9 +121,5 @@ public class BlankPageController {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static boolean hasImagesOnPage(PDPage page) throws IOException {
|
|
||||||
ImageFinder imageFinder = new ImageFinder(page);
|
|
||||||
imageFinder.processPage(page);
|
|
||||||
return imageFinder.hasImages();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,7 +41,7 @@ public class CompressController {
|
||||||
private static final Logger logger = LoggerFactory.getLogger(CompressController.class);
|
private static final Logger logger = LoggerFactory.getLogger(CompressController.class);
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/compress-pdf")
|
@PostMapping(consumes = "multipart/form-data", value = "/compress-pdf")
|
||||||
@Operation(summary = "Optimize PDF file", description = "This endpoint accepts a PDF file and optimizes it based on the provided parameters.")
|
@Operation(summary = "Optimize PDF file", description = "This endpoint accepts a PDF file and optimizes it based on the provided parameters. Input:PDF Output:PDF Type:SISO")
|
||||||
public ResponseEntity<byte[]> optimizePdf(
|
public ResponseEntity<byte[]> optimizePdf(
|
||||||
@RequestPart(value = "fileInput") @Parameter(description = "The input PDF file to be optimized.", required = true) MultipartFile inputFile,
|
@RequestPart(value = "fileInput") @Parameter(description = "The input PDF file to be optimized.", required = true) MultipartFile inputFile,
|
||||||
@RequestParam(required = false, value = "optimizeLevel") @Parameter(description = "The level of optimization to apply to the PDF file. Higher values indicate greater compression but may reduce quality.", schema = @Schema(allowableValues = {
|
@RequestParam(required = false, value = "optimizeLevel") @Parameter(description = "The level of optimization to apply to the PDF file. Higher values indicate greater compression but may reduce quality.", schema = @Schema(allowableValues = {
|
||||||
|
|
|
@ -41,7 +41,7 @@ public class ExtractImageScansController {
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/extract-image-scans")
|
@PostMapping(consumes = "multipart/form-data", value = "/extract-image-scans")
|
||||||
@Operation(summary = "Extract image scans from an input file",
|
@Operation(summary = "Extract image scans from an input file",
|
||||||
description = "This endpoint extracts image scans from a given file based on certain parameters. Users can specify angle threshold, tolerance, minimum area, minimum contour area, and border size.")
|
description = "This endpoint extracts image scans from a given file based on certain parameters. Users can specify angle threshold, tolerance, minimum area, minimum contour area, and border size. Input:PDF Output:IMAGE/ZIP Type:SIMO")
|
||||||
public ResponseEntity<byte[]> extractImageScans(
|
public ResponseEntity<byte[]> extractImageScans(
|
||||||
@RequestPart(required = true, value = "fileInput")
|
@RequestPart(required = true, value = "fileInput")
|
||||||
@Parameter(description = "The input file containing image scans")
|
@Parameter(description = "The input file containing image scans")
|
||||||
|
|
|
@ -37,7 +37,7 @@ public class ExtractImagesController {
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/extract-images")
|
@PostMapping(consumes = "multipart/form-data", value = "/extract-images")
|
||||||
@Operation(summary = "Extract images from a PDF file",
|
@Operation(summary = "Extract images from a PDF file",
|
||||||
description = "This endpoint extracts images from a given PDF file and returns them in a zip file. Users can specify the output image format.")
|
description = "This endpoint extracts images from a given PDF file and returns them in a zip file. Users can specify the output image format. Input:PDF Output:IMAGE/ZIP Type:SIMO")
|
||||||
public ResponseEntity<byte[]> extractImages(
|
public ResponseEntity<byte[]> extractImages(
|
||||||
@RequestPart(required = true, value = "fileInput")
|
@RequestPart(required = true, value = "fileInput")
|
||||||
@Parameter(description = "The input PDF file containing images")
|
@Parameter(description = "The input PDF file containing images")
|
||||||
|
|
|
@ -54,9 +54,9 @@ import java.util.Random;
|
||||||
import io.swagger.v3.oas.annotations.Hidden;
|
import io.swagger.v3.oas.annotations.Hidden;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
public class FakeScanController {
|
public class FakeScanControllerWIP {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(FakeScanController.class);
|
private static final Logger logger = LoggerFactory.getLogger(FakeScanControllerWIP.class);
|
||||||
|
|
||||||
//TODO
|
//TODO
|
||||||
@Hidden
|
@Hidden
|
|
@ -38,7 +38,7 @@ public class MetadataController {
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/update-metadata")
|
@PostMapping(consumes = "multipart/form-data", value = "/update-metadata")
|
||||||
@Operation(summary = "Update metadata of a PDF file",
|
@Operation(summary = "Update metadata of a PDF file",
|
||||||
description = "This endpoint allows you to update the metadata of a given PDF file. You can add, modify, or delete standard and custom metadata fields.")
|
description = "This endpoint allows you to update the metadata of a given PDF file. You can add, modify, or delete standard and custom metadata fields. Input:PDF Output:PDF Type:SISO")
|
||||||
public ResponseEntity<byte[]> metadata(
|
public ResponseEntity<byte[]> metadata(
|
||||||
@RequestPart(required = true, value = "fileInput")
|
@RequestPart(required = true, value = "fileInput")
|
||||||
@Parameter(description = "The input PDF file to update metadata")
|
@Parameter(description = "The input PDF file to update metadata")
|
||||||
|
|
|
@ -47,7 +47,7 @@ public class OCRController {
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/ocr-pdf")
|
@PostMapping(consumes = "multipart/form-data", value = "/ocr-pdf")
|
||||||
@Operation(summary = "Process a PDF file with OCR",
|
@Operation(summary = "Process a PDF file with OCR",
|
||||||
description = "This endpoint processes a PDF file using OCR (Optical Character Recognition). Users can specify languages, sidecar, deskew, clean, cleanFinal, ocrType, ocrRenderType, and removeImagesAfter options.")
|
description = "This endpoint processes a PDF file using OCR (Optical Character Recognition). Users can specify languages, sidecar, deskew, clean, cleanFinal, ocrType, ocrRenderType, and removeImagesAfter options. Input:PDF Output:PDF Type:SI-Conditional")
|
||||||
public ResponseEntity<byte[]> processPdfWithOCR(
|
public ResponseEntity<byte[]> processPdfWithOCR(
|
||||||
@RequestPart(required = true, value = "fileInput")
|
@RequestPart(required = true, value = "fileInput")
|
||||||
@Parameter(description = "The input PDF file to be processed with OCR")
|
@Parameter(description = "The input PDF file to be processed with OCR")
|
||||||
|
|
|
@ -25,7 +25,7 @@ public class OverlayImageController {
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/add-image")
|
@PostMapping(consumes = "multipart/form-data", value = "/add-image")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Overlay image onto a PDF file",
|
summary = "Overlay image onto a PDF file",
|
||||||
description = "This endpoint overlays an image onto a PDF file at the specified coordinates. The image can be overlaid on every page of the PDF if specified."
|
description = "This endpoint overlays an image onto a PDF file at the specified coordinates. The image can be overlaid on every page of the PDF if specified. Input:PDF/IMAGE Output:PDF Type:MF-SISO"
|
||||||
)
|
)
|
||||||
public ResponseEntity<byte[]> overlayImage(
|
public ResponseEntity<byte[]> overlayImage(
|
||||||
@RequestPart(required = true, value = "fileInput")
|
@RequestPart(required = true, value = "fileInput")
|
||||||
|
|
|
@ -27,7 +27,7 @@ public class RepairController {
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/repair")
|
@PostMapping(consumes = "multipart/form-data", value = "/repair")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Repair a PDF file",
|
summary = "Repair a PDF file",
|
||||||
description = "This endpoint repairs a given PDF file by running Ghostscript command. The PDF is first saved to a temporary location, repaired, read back, and then returned as a response."
|
description = "This endpoint repairs a given PDF file by running Ghostscript command. The PDF is first saved to a temporary location, repaired, read back, and then returned as a response. Input:PDF Output:PDF Type:SISO"
|
||||||
)
|
)
|
||||||
public ResponseEntity<byte[]> repairPdf(
|
public ResponseEntity<byte[]> repairPdf(
|
||||||
@RequestPart(required = true, value = "fileInput")
|
@RequestPart(required = true, value = "fileInput")
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
package stirling.software.SPDF.controller.api.pipeline;
|
package stirling.software.SPDF.controller.api.pipeline;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.core.io.ByteArrayResource;
|
import org.springframework.core.io.ByteArrayResource;
|
||||||
import org.springframework.core.io.Resource;
|
import org.springframework.core.io.Resource;
|
||||||
import org.springframework.http.*;
|
import org.springframework.http.*;
|
||||||
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
import org.springframework.util.LinkedMultiValueMap;
|
import org.springframework.util.LinkedMultiValueMap;
|
||||||
import org.springframework.util.MultiValueMap;
|
import org.springframework.util.MultiValueMap;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
@ -12,15 +14,26 @@ import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.web.client.RestTemplate;
|
import org.springframework.web.client.RestTemplate;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
import com.fasterxml.jackson.databind.JsonMappingException;
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
|
import stirling.software.SPDF.model.PipelineConfig;
|
||||||
|
import stirling.software.SPDF.model.PipelineOperation;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.LocalTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.stream.Stream;
|
||||||
import java.util.zip.ZipEntry;
|
import java.util.zip.ZipEntry;
|
||||||
import java.util.zip.ZipInputStream;
|
import java.util.zip.ZipInputStream;
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
|
@ -32,97 +45,226 @@ import java.util.zip.ZipOutputStream;
|
||||||
@RestController
|
@RestController
|
||||||
public class Controller {
|
public class Controller {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
|
||||||
|
final String jsonFileName = "pipelineCofig.json";
|
||||||
|
final String watchedFoldersDir = "watchedFolders/";
|
||||||
|
@Scheduled(fixedRate = 5000)
|
||||||
|
public void scanFolders() {
|
||||||
|
try (Stream<Path> paths = Files.walk(Paths.get(watchedFoldersDir))) {
|
||||||
|
paths.filter(Files::isDirectory).forEach(t -> {
|
||||||
|
try {
|
||||||
|
handleDirectory(t);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleDirectory(Path dir) throws Exception {
|
||||||
|
Path jsonFile = dir.resolve(jsonFileName);
|
||||||
|
if (Files.exists(jsonFile)) {
|
||||||
|
// Read JSON file
|
||||||
|
String jsonString;
|
||||||
|
try {
|
||||||
|
jsonString = new String(Files.readAllBytes(jsonFile));
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode JSON to PipelineConfig
|
||||||
|
PipelineConfig config;
|
||||||
|
try {
|
||||||
|
config = objectMapper.readValue(jsonString, PipelineConfig.class);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For each operation in the pipeline
|
||||||
|
for (PipelineOperation operation : config.getOperations()) {
|
||||||
|
// Collect all files based on fileInput
|
||||||
|
File[] files;
|
||||||
|
String fileInput = (String) operation.getParameters().get("fileInput");
|
||||||
|
if ("automated".equals(fileInput)) {
|
||||||
|
// If fileInput is "automated", process all files in the directory
|
||||||
|
try (Stream<Path> paths = Files.list(dir)) {
|
||||||
|
files = paths.filter(path -> !path.equals(jsonFile))
|
||||||
|
.map(Path::toFile)
|
||||||
|
.toArray(File[]::new);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If fileInput contains a path, process only this file
|
||||||
|
files = new File[]{new File(fileInput)};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call handleData for each operation
|
||||||
|
try {
|
||||||
|
List<Resource> resources = handleFiles(files, jsonString);
|
||||||
|
|
||||||
|
// Move resultant files and rename them as per config in JSON file
|
||||||
|
for (Resource resource : resources) {
|
||||||
|
String outputFileName = config.getOutputPattern().replace("{filename}", resource.getFile().getName());
|
||||||
|
outputFileName = outputFileName.replace("{pipelineName}", config.getName());
|
||||||
|
DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyyMMdd");
|
||||||
|
outputFileName = outputFileName.replace("{date}", LocalDate.now().format(dateFormatter));
|
||||||
|
DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("HHmmss");
|
||||||
|
outputFileName = outputFileName.replace("{time}", LocalTime.now().format(timeFormatter));
|
||||||
|
// ... Replace other placeholders
|
||||||
|
|
||||||
|
Files.move(resource.getFile().toPath(), Paths.get(config.getOutputDir(), outputFileName));
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
List<Resource> processFiles(List<Resource> outputFiles, String jsonString) throws Exception{
|
||||||
|
ObjectMapper mapper = new ObjectMapper();
|
||||||
|
JsonNode jsonNode = mapper.readTree(jsonString);
|
||||||
|
|
||||||
|
JsonNode pipelineNode = jsonNode.get("pipeline");
|
||||||
|
ByteArrayOutputStream logStream = new ByteArrayOutputStream();
|
||||||
|
PrintStream logPrintStream = new PrintStream(logStream);
|
||||||
|
|
||||||
|
boolean hasErrors = false;
|
||||||
|
|
||||||
|
for (JsonNode operationNode : pipelineNode) {
|
||||||
|
String operation = operationNode.get("operation").asText();
|
||||||
|
JsonNode parametersNode = operationNode.get("parameters");
|
||||||
|
String inputFileExtension = "";
|
||||||
|
if(operationNode.has("inputFileType")) {
|
||||||
|
inputFileExtension = operationNode.get("inputFileType").asText();
|
||||||
|
} else {
|
||||||
|
inputFileExtension=".pdf";
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Resource> newOutputFiles = new ArrayList<>();
|
||||||
|
boolean hasInputFileType = false;
|
||||||
|
|
||||||
|
for (Resource file : outputFiles) {
|
||||||
|
if (file.getFilename().endsWith(inputFileExtension)) {
|
||||||
|
hasInputFileType = true;
|
||||||
|
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
|
||||||
|
body.add("fileInput", file);
|
||||||
|
|
||||||
|
Iterator<Map.Entry<String, JsonNode>> parameters = parametersNode.fields();
|
||||||
|
while (parameters.hasNext()) {
|
||||||
|
Map.Entry<String, JsonNode> parameter = parameters.next();
|
||||||
|
body.add(parameter.getKey(), parameter.getValue().asText());
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpHeaders headers = new HttpHeaders();
|
||||||
|
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
|
||||||
|
|
||||||
|
HttpEntity<MultiValueMap<String, Object>> entity = new HttpEntity<>(body, headers);
|
||||||
|
|
||||||
|
RestTemplate restTemplate = new RestTemplate();
|
||||||
|
String url = "http://localhost:8080/" + operation;
|
||||||
|
|
||||||
|
ResponseEntity<byte[]> response = restTemplate.exchange(url, HttpMethod.POST, entity, byte[].class);
|
||||||
|
|
||||||
|
if (!response.getStatusCode().equals(HttpStatus.OK)) {
|
||||||
|
logPrintStream.println("Error: " + response.getBody());
|
||||||
|
hasErrors = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the response body is a zip file
|
||||||
|
if (isZip(response.getBody())) {
|
||||||
|
// Unzip the file and add all the files to the new output files
|
||||||
|
newOutputFiles.addAll(unzip(response.getBody()));
|
||||||
|
} else {
|
||||||
|
Resource outputResource = new ByteArrayResource(response.getBody()) {
|
||||||
|
@Override
|
||||||
|
public String getFilename() {
|
||||||
|
return file.getFilename(); // Preserving original filename
|
||||||
|
}
|
||||||
|
};
|
||||||
|
newOutputFiles.add(outputResource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasInputFileType) {
|
||||||
|
logPrintStream.println("No files with extension " + inputFileExtension + " found for operation " + operation);
|
||||||
|
hasErrors = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
outputFiles = newOutputFiles;
|
||||||
|
}
|
||||||
|
logPrintStream.close();
|
||||||
|
|
||||||
|
}
|
||||||
|
return outputFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
List<Resource> handleFiles(File[] files, String jsonString) throws Exception{
|
||||||
|
ObjectMapper mapper = new ObjectMapper();
|
||||||
|
JsonNode jsonNode = mapper.readTree(jsonString);
|
||||||
|
|
||||||
|
JsonNode pipelineNode = jsonNode.get("pipeline");
|
||||||
|
ByteArrayOutputStream logStream = new ByteArrayOutputStream();
|
||||||
|
PrintStream logPrintStream = new PrintStream(logStream);
|
||||||
|
|
||||||
|
boolean hasErrors = false;
|
||||||
|
List<Resource> outputFiles = new ArrayList<>();
|
||||||
|
|
||||||
|
for (File file : files) {
|
||||||
|
Path path = Paths.get(file.getAbsolutePath());
|
||||||
|
Resource fileResource = new ByteArrayResource(Files.readAllBytes(path)) {
|
||||||
|
@Override
|
||||||
|
public String getFilename() {
|
||||||
|
return file.getName();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
outputFiles.add(fileResource);
|
||||||
|
}
|
||||||
|
return processFiles(outputFiles, jsonString);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Resource> handleFiles(MultipartFile[] files, String jsonString) throws Exception{
|
||||||
|
ObjectMapper mapper = new ObjectMapper();
|
||||||
|
JsonNode jsonNode = mapper.readTree(jsonString);
|
||||||
|
|
||||||
|
JsonNode pipelineNode = jsonNode.get("pipeline");
|
||||||
|
ByteArrayOutputStream logStream = new ByteArrayOutputStream();
|
||||||
|
PrintStream logPrintStream = new PrintStream(logStream);
|
||||||
|
|
||||||
|
boolean hasErrors = false;
|
||||||
|
List<Resource> outputFiles = new ArrayList<>();
|
||||||
|
|
||||||
|
for (MultipartFile file : files) {
|
||||||
|
Resource fileResource = new ByteArrayResource(file.getBytes()) {
|
||||||
|
@Override
|
||||||
|
public String getFilename() {
|
||||||
|
return file.getOriginalFilename();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
outputFiles.add(fileResource);
|
||||||
|
}
|
||||||
|
return processFiles(outputFiles, jsonString);
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping("/handleData")
|
@PostMapping("/handleData")
|
||||||
public ResponseEntity<byte[]> handleData(@RequestPart("fileInput") MultipartFile[] files,
|
public ResponseEntity<byte[]> handleData(@RequestPart("fileInput") MultipartFile[] files,
|
||||||
@RequestParam("json") String jsonString) {
|
@RequestParam("json") String jsonString) {
|
||||||
try {
|
try {
|
||||||
ObjectMapper mapper = new ObjectMapper();
|
|
||||||
JsonNode jsonNode = mapper.readTree(jsonString);
|
|
||||||
|
|
||||||
JsonNode pipelineNode = jsonNode.get("pipeline");
|
|
||||||
ByteArrayOutputStream logStream = new ByteArrayOutputStream();
|
|
||||||
PrintStream logPrintStream = new PrintStream(logStream);
|
|
||||||
|
|
||||||
boolean hasErrors = false;
|
|
||||||
List<Resource> outputFiles = new ArrayList<>();
|
|
||||||
|
|
||||||
for (MultipartFile file : files) {
|
|
||||||
Resource fileResource = new ByteArrayResource(file.getBytes()) {
|
|
||||||
@Override
|
|
||||||
public String getFilename() {
|
|
||||||
return file.getOriginalFilename();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
outputFiles.add(fileResource);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (JsonNode operationNode : pipelineNode) {
|
|
||||||
String operation = operationNode.get("operation").asText();
|
|
||||||
JsonNode parametersNode = operationNode.get("parameters");
|
|
||||||
String inputFileExtension = "";
|
|
||||||
if(operationNode.has("inputFileType")) {
|
|
||||||
inputFileExtension = operationNode.get("inputFileType").asText();
|
|
||||||
} else {
|
|
||||||
inputFileExtension=".pdf";
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Resource> newOutputFiles = new ArrayList<>();
|
|
||||||
boolean hasInputFileType = false;
|
|
||||||
|
|
||||||
for (Resource file : outputFiles) {
|
|
||||||
if (file.getFilename().endsWith(inputFileExtension)) {
|
|
||||||
hasInputFileType = true;
|
|
||||||
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
|
|
||||||
body.add("fileInput", file);
|
|
||||||
|
|
||||||
Iterator<Map.Entry<String, JsonNode>> parameters = parametersNode.fields();
|
|
||||||
while (parameters.hasNext()) {
|
|
||||||
Map.Entry<String, JsonNode> parameter = parameters.next();
|
|
||||||
body.add(parameter.getKey(), parameter.getValue().asText());
|
|
||||||
}
|
|
||||||
|
|
||||||
HttpHeaders headers = new HttpHeaders();
|
|
||||||
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
|
|
||||||
|
|
||||||
HttpEntity<MultiValueMap<String, Object>> entity = new HttpEntity<>(body, headers);
|
|
||||||
|
|
||||||
RestTemplate restTemplate = new RestTemplate();
|
|
||||||
String url = "http://localhost:8080/" + operation;
|
|
||||||
|
|
||||||
ResponseEntity<byte[]> response = restTemplate.exchange(url, HttpMethod.POST, entity, byte[].class);
|
|
||||||
|
|
||||||
if (!response.getStatusCode().equals(HttpStatus.OK)) {
|
|
||||||
logPrintStream.println("Error: " + response.getBody());
|
|
||||||
hasErrors = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the response body is a zip file
|
|
||||||
if (isZip(response.getBody())) {
|
|
||||||
// Unzip the file and add all the files to the new output files
|
|
||||||
newOutputFiles.addAll(unzip(response.getBody()));
|
|
||||||
} else {
|
|
||||||
Resource outputResource = new ByteArrayResource(response.getBody()) {
|
|
||||||
@Override
|
|
||||||
public String getFilename() {
|
|
||||||
return file.getFilename(); // Preserving original filename
|
|
||||||
}
|
|
||||||
};
|
|
||||||
newOutputFiles.add(outputResource);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hasInputFileType) {
|
|
||||||
logPrintStream.println("No files with extension " + inputFileExtension + " found for operation " + operation);
|
|
||||||
hasErrors = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
outputFiles = newOutputFiles;
|
|
||||||
}
|
|
||||||
logPrintStream.close();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
List<Resource> outputFiles = handleFiles(files, jsonString);
|
||||||
|
|
||||||
if (outputFiles.size() == 1) {
|
if (outputFiles.size() == 1) {
|
||||||
// If there is only one file, return it directly
|
// If there is only one file, return it directly
|
||||||
|
|
|
@ -63,7 +63,7 @@ public class CertSignController {
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/cert-sign")
|
@PostMapping(consumes = "multipart/form-data", value = "/cert-sign")
|
||||||
@Operation(summary = "Sign PDF with a Digital Certificate",
|
@Operation(summary = "Sign PDF with a Digital Certificate",
|
||||||
description = "This endpoint accepts a PDF file, a digital certificate and related information to sign the PDF. It then returns the digitally signed PDF file.")
|
description = "This endpoint accepts a PDF file, a digital certificate and related information to sign the PDF. It then returns the digitally signed PDF file. Input:PDF Output:PDF Type:MF-SISO")
|
||||||
public ResponseEntity<byte[]> signPDF(
|
public ResponseEntity<byte[]> signPDF(
|
||||||
@RequestPart(required = true, value = "fileInput")
|
@RequestPart(required = true, value = "fileInput")
|
||||||
@Parameter(description = "The input PDF file to be signed")
|
@Parameter(description = "The input PDF file to be signed")
|
||||||
|
|
|
@ -27,7 +27,7 @@ public class PasswordController {
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/remove-password")
|
@PostMapping(consumes = "multipart/form-data", value = "/remove-password")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Remove password from a PDF file",
|
summary = "Remove password from a PDF file",
|
||||||
description = "This endpoint removes the password from a protected PDF file. Users need to provide the existing password."
|
description = "This endpoint removes the password from a protected PDF file. Users need to provide the existing password. Input:PDF Output:PDF Type:SISO"
|
||||||
)
|
)
|
||||||
public ResponseEntity<byte[]> removePassword(
|
public ResponseEntity<byte[]> removePassword(
|
||||||
@RequestPart(required = true, value = "fileInput")
|
@RequestPart(required = true, value = "fileInput")
|
||||||
|
@ -44,7 +44,7 @@ public class PasswordController {
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/add-password")
|
@PostMapping(consumes = "multipart/form-data", value = "/add-password")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Add password to a PDF file",
|
summary = "Add password to a PDF file",
|
||||||
description = "This endpoint adds password protection to a PDF file. Users can specify a set of permissions that should be applied to the file."
|
description = "This endpoint adds password protection to a PDF file. Users can specify a set of permissions that should be applied to the file. Input:PDF Output:PDF"
|
||||||
)
|
)
|
||||||
public ResponseEntity<byte[]> addPassword(
|
public ResponseEntity<byte[]> addPassword(
|
||||||
@RequestPart(required = true, value = "fileInput")
|
@RequestPart(required = true, value = "fileInput")
|
||||||
|
|
|
@ -37,7 +37,7 @@ public class WatermarkController {
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/add-watermark")
|
@PostMapping(consumes = "multipart/form-data", value = "/add-watermark")
|
||||||
@Operation(summary = "Add watermark to a PDF file",
|
@Operation(summary = "Add watermark to a PDF file",
|
||||||
description = "This endpoint adds a watermark to a given PDF file. Users can specify the watermark text, font size, rotation, opacity, width spacer, and height spacer.")
|
description = "This endpoint adds a watermark to a given PDF file. Users can specify the watermark text, font size, rotation, opacity, width spacer, and height spacer. Input:PDF Output:PDF Type:SISO")
|
||||||
public ResponseEntity<byte[]> addWatermark(
|
public ResponseEntity<byte[]> addWatermark(
|
||||||
@RequestPart(required = true, value = "fileInput")
|
@RequestPart(required = true, value = "fileInput")
|
||||||
@Parameter(description = "The input PDF file to add a watermark")
|
@Parameter(description = "The input PDF file to add a watermark")
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
package stirling.software.SPDF.model;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class PipelineConfig {
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@JsonProperty("pipeline")
|
||||||
|
private List<PipelineOperation> operations;
|
||||||
|
|
||||||
|
private String outputDir;
|
||||||
|
|
||||||
|
@JsonProperty("outputFileName")
|
||||||
|
private String outputPattern;
|
||||||
|
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<PipelineOperation> getOperations() {
|
||||||
|
return operations;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOperations(List<PipelineOperation> operations) {
|
||||||
|
this.operations = operations;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getOutputDir() {
|
||||||
|
return outputDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOutputDir(String outputDir) {
|
||||||
|
this.outputDir = outputDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getOutputPattern() {
|
||||||
|
return outputPattern;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOutputPattern(String outputPattern) {
|
||||||
|
this.outputPattern = outputPattern;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
package stirling.software.SPDF.model;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class PipelineOperation {
|
||||||
|
private String operation;
|
||||||
|
private Map<String, Object> parameters;
|
||||||
|
|
||||||
|
|
||||||
|
public String getOperation() {
|
||||||
|
return operation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOperation(String operation) {
|
||||||
|
this.operation = operation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Object> getParameters() {
|
||||||
|
return parameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setParameters(Map<String, Object> parameters) {
|
||||||
|
this.parameters = parameters;
|
||||||
|
}
|
||||||
|
}
|
|
@ -27,14 +27,38 @@ import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory;
|
||||||
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
|
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
|
||||||
import org.apache.pdfbox.rendering.ImageType;
|
import org.apache.pdfbox.rendering.ImageType;
|
||||||
import org.apache.pdfbox.rendering.PDFRenderer;
|
import org.apache.pdfbox.rendering.PDFRenderer;
|
||||||
|
import org.apache.pdfbox.text.PDFTextStripper;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import com.itextpdf.kernel.pdf.PdfPage;
|
||||||
|
import com.itextpdf.kernel.pdf.canvas.parser.PdfTextExtractor;
|
||||||
|
import com.itextpdf.kernel.pdf.canvas.parser.listener.SimpleTextExtractionStrategy;
|
||||||
|
|
||||||
|
import stirling.software.SPDF.pdf.ImageFinder;
|
||||||
|
|
||||||
public class PdfUtils {
|
public class PdfUtils {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(PdfUtils.class);
|
private static final Logger logger = LoggerFactory.getLogger(PdfUtils.class);
|
||||||
|
|
||||||
|
public static boolean hasImagesOnPage(PDPage page) throws IOException {
|
||||||
|
ImageFinder imageFinder = new ImageFinder(page);
|
||||||
|
imageFinder.processPage(page);
|
||||||
|
return imageFinder.hasImages();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean hasTextOnPage(PdfPage page, String phrase) throws IOException {
|
||||||
|
String text = PdfTextExtractor.getTextFromPage(page, new SimpleTextExtractionStrategy());
|
||||||
|
return text.contains(phrase);
|
||||||
|
}
|
||||||
|
public static boolean hasText(PDDocument document, String phrase) throws IOException {
|
||||||
|
PDFTextStripper pdfStripper = new PDFTextStripper();
|
||||||
|
String text = pdfStripper.getText(document);
|
||||||
|
return text.contains(phrase);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public static byte[] convertFromPdf(byte[] inputStream, String imageType, ImageType colorType, boolean singleImage, int DPI, String filename) throws IOException, Exception {
|
public static byte[] convertFromPdf(byte[] inputStream, String imageType, ImageType colorType, boolean singleImage, int DPI, String filename) throws IOException, Exception {
|
||||||
try (PDDocument document = PDDocument.load(new ByteArrayInputStream(inputStream))) {
|
try (PDDocument document = PDDocument.load(new ByteArrayInputStream(inputStream))) {
|
||||||
PDFRenderer pdfRenderer = new PDFRenderer(document);
|
PDFRenderer pdfRenderer = new PDFRenderer(document);
|
||||||
|
@ -43,7 +67,7 @@ public class PdfUtils {
|
||||||
|
|
||||||
// Create images of all pages
|
// Create images of all pages
|
||||||
for (int i = 0; i < pageCount; i++) {
|
for (int i = 0; i < pageCount; i++) {
|
||||||
images.add(pdfRenderer.renderImageWithDPI(i, 300, colorType));
|
images.add(pdfRenderer.renderImageWithDPI(i, DPI, colorType));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (singleImage) {
|
if (singleImage) {
|
||||||
|
|
|
@ -131,6 +131,9 @@ home.pageLayout.desc=Merge multiple pages of a PDF document into a single page
|
||||||
home.scalePages.title=Adjust page size/scale
|
home.scalePages.title=Adjust page size/scale
|
||||||
home.scalePages.desc=Change the size/scale of a page and/or its contents.
|
home.scalePages.desc=Change the size/scale of a page and/or its contents.
|
||||||
|
|
||||||
|
home.pipeline.title=Pipeline
|
||||||
|
home.pipeline.desc=Pipeline desc.
|
||||||
|
|
||||||
error.pdfPassword=The PDF Document is passworded and either the password was not provided or was incorrect
|
error.pdfPassword=The PDF Document is passworded and either the password was not provided or was incorrect
|
||||||
|
|
||||||
downloadPdf=Download PDF
|
downloadPdf=Download PDF
|
||||||
|
|
3
src/main/resources/static/images/pipeline.svg
Normal file
3
src/main/resources/static/images/pipeline.svg
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-bezier2" viewBox="0 0 16 16">
|
||||||
|
<path fill-rule="evenodd" d="M1 2.5A1.5 1.5 0 0 1 2.5 1h1A1.5 1.5 0 0 1 5 2.5h4.134a1 1 0 1 1 0 1h-2.01c.18.18.34.381.484.605.638.992.892 2.354.892 3.895 0 1.993.257 3.092.713 3.7.356.476.895.721 1.787.784A1.5 1.5 0 0 1 12.5 11h1a1.5 1.5 0 0 1 1.5 1.5v1a1.5 1.5 0 0 1-1.5 1.5h-1a1.5 1.5 0 0 1-1.5-1.5H6.866a1 1 0 1 1 0-1h1.711a2.839 2.839 0 0 1-.165-.2C7.743 11.407 7.5 10.007 7.5 8c0-1.46-.246-2.597-.733-3.355-.39-.605-.952-1-1.767-1.112A1.5 1.5 0 0 1 3.5 5h-1A1.5 1.5 0 0 1 1 3.5v-1zM2.5 2a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5h-1zm10 10a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5h-1z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 790 B |
|
@ -218,7 +218,7 @@ const DraggableUtils = {
|
||||||
},
|
},
|
||||||
async getOverlayedPdfDocument() {
|
async getOverlayedPdfDocument() {
|
||||||
const pdfBytes = await this.pdfDoc.getData();
|
const pdfBytes = await this.pdfDoc.getData();
|
||||||
const pdfDocModified = await PDFLib.PDFDocument.load(pdfBytes);
|
const pdfDocModified = await PDFLib.PDFDocument.load(pdfBytes, { ignoreEncryption: true });
|
||||||
this.storePageContents();
|
this.storePageContents();
|
||||||
|
|
||||||
const pagesMap = this.documentsMap.get(this.pdfDoc);
|
const pagesMap = this.documentsMap.get(this.pdfDoc);
|
||||||
|
|
|
@ -152,7 +152,7 @@ class PdfContainer {
|
||||||
|
|
||||||
async toPdfLib(objectUrl) {
|
async toPdfLib(objectUrl) {
|
||||||
const existingPdfBytes = await fetch(objectUrl).then(res => res.arrayBuffer());
|
const existingPdfBytes = await fetch(objectUrl).then(res => res.arrayBuffer());
|
||||||
const pdfDoc = await PDFLib.PDFDocument.load(existingPdfBytes);
|
const pdfDoc = await PDFLib.PDFDocument.load(existingPdfBytes, { ignoreEncryption: true });
|
||||||
return pdfDoc;
|
return pdfDoc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
385
src/main/resources/static/js/pipeline.js
Normal file
385
src/main/resources/static/js/pipeline.js
Normal file
|
@ -0,0 +1,385 @@
|
||||||
|
document.getElementById('validateButton').addEventListener('click', function(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
validatePipeline();
|
||||||
|
});
|
||||||
|
function validatePipeline() {
|
||||||
|
let pipelineListItems = document.getElementById('pipelineList').children;
|
||||||
|
let isValid = true;
|
||||||
|
let containsAddPassword = false;
|
||||||
|
for (let i = 0; i < pipelineListItems.length - 1; i++) {
|
||||||
|
let currentOperation = pipelineListItems[i].querySelector('.operationName').textContent;
|
||||||
|
let nextOperation = pipelineListItems[i + 1].querySelector('.operationName').textContent;
|
||||||
|
if (currentOperation === '/add-password') {
|
||||||
|
containsAddPassword = true;
|
||||||
|
}
|
||||||
|
console.log(currentOperation);
|
||||||
|
console.log(apiDocs[currentOperation]);
|
||||||
|
let currentOperationDescription = apiDocs[currentOperation]?.post?.description || "";
|
||||||
|
let nextOperationDescription = apiDocs[nextOperation]?.post?.description || "";
|
||||||
|
|
||||||
|
console.log("currentOperationDescription", currentOperationDescription);
|
||||||
|
console.log("nextOperationDescription", nextOperationDescription);
|
||||||
|
|
||||||
|
let currentOperationOutput = currentOperationDescription.match(/Output:([A-Z\/]*)/)?.[1] || "";
|
||||||
|
let nextOperationInput = nextOperationDescription.match(/Input:([A-Z\/]*)/)?.[1] || "";
|
||||||
|
|
||||||
|
console.log("Operation " + currentOperation + " Output: " + currentOperationOutput);
|
||||||
|
console.log("Operation " + nextOperation + " Input: " + nextOperationInput);
|
||||||
|
|
||||||
|
// Splitting in case of multiple possible output/input
|
||||||
|
let currentOperationOutputArr = currentOperationOutput.split('/');
|
||||||
|
let nextOperationInputArr = nextOperationInput.split('/');
|
||||||
|
|
||||||
|
if (currentOperationOutput !== 'ANY' && nextOperationInput !== 'ANY') {
|
||||||
|
let intersection = currentOperationOutputArr.filter(value => nextOperationInputArr.includes(value));
|
||||||
|
console.log(`Intersection: ${intersection}`);
|
||||||
|
|
||||||
|
if (intersection.length === 0) {
|
||||||
|
isValid = false;
|
||||||
|
console.log(`Incompatible operations: The output of operation '${currentOperation}' (${currentOperationOutput}) is not compatible with the input of the following operation '${nextOperation}' (${nextOperationInput}).`);
|
||||||
|
alert(`Incompatible operations: The output of operation '${currentOperation}' (${currentOperationOutput}) is not compatible with the input of the following operation '${nextOperation}' (${nextOperationInput}).`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (containsAddPassword && pipelineListItems[pipelineListItems.length - 1].querySelector('.operationName').textContent !== '/add-password') {
|
||||||
|
alert('The "add-password" operation should be at the end of the operations sequence. Please adjust the operations order.');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (isValid) {
|
||||||
|
console.log('Pipeline is valid');
|
||||||
|
// Continue with the pipeline operation
|
||||||
|
} else {
|
||||||
|
console.error('Pipeline is not valid');
|
||||||
|
// Stop operation, maybe display an error to the user
|
||||||
|
}
|
||||||
|
|
||||||
|
return isValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
document.getElementById('submitConfigBtn').addEventListener('click', function() {
|
||||||
|
|
||||||
|
if (validatePipeline() === false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let selectedOperation = document.getElementById('operationsDropdown').value;
|
||||||
|
let parameters = operationSettings[selectedOperation] || {};
|
||||||
|
|
||||||
|
let pipelineConfig = {
|
||||||
|
"name": "uniquePipelineName",
|
||||||
|
"pipeline": [{
|
||||||
|
"operation": selectedOperation,
|
||||||
|
"parameters": parameters
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
|
||||||
|
let pipelineConfigJson = JSON.stringify(pipelineConfig, null, 2);
|
||||||
|
|
||||||
|
let formData = new FormData();
|
||||||
|
|
||||||
|
let fileInput = document.getElementById('fileInput');
|
||||||
|
let files = fileInput.files;
|
||||||
|
|
||||||
|
for (let i = 0; i < files.length; i++) {
|
||||||
|
console.log("files[i]", files[i].name);
|
||||||
|
formData.append('fileInput', files[i], files[i].name);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("pipelineConfigJson", pipelineConfigJson);
|
||||||
|
formData.append('json', pipelineConfigJson);
|
||||||
|
console.log("formData", formData);
|
||||||
|
|
||||||
|
fetch('/handleData', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
|
})
|
||||||
|
.then(response => response.blob())
|
||||||
|
.then(blob => {
|
||||||
|
|
||||||
|
let url = window.URL.createObjectURL(blob);
|
||||||
|
let a = document.createElement('a');
|
||||||
|
a.href = url;
|
||||||
|
a.download = 'outputfile';
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
a.remove();
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Error:', error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
let apiDocs = {};
|
||||||
|
|
||||||
|
let operationSettings = {};
|
||||||
|
|
||||||
|
fetch('v3/api-docs')
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
apiDocs = data.paths;
|
||||||
|
let operationsDropdown = document.getElementById('operationsDropdown');
|
||||||
|
|
||||||
|
operationsDropdown.innerHTML = '';
|
||||||
|
|
||||||
|
Object.keys(apiDocs).forEach(operation => {
|
||||||
|
if (apiDocs[operation].hasOwnProperty('post')) {
|
||||||
|
let option = document.createElement('option');
|
||||||
|
option.textContent = operation;
|
||||||
|
operationsDropdown.appendChild(option);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('addOperationBtn').addEventListener('click', function() {
|
||||||
|
let selectedOperation = document.getElementById('operationsDropdown').value;
|
||||||
|
let pipelineList = document.getElementById('pipelineList');
|
||||||
|
|
||||||
|
let listItem = document.createElement('li');
|
||||||
|
listItem.className = "list-group-item";
|
||||||
|
listItem.innerHTML = `
|
||||||
|
<div class="d-flex justify-content-between align-items-center w-100">
|
||||||
|
<div class="operationName">${selectedOperation}</div>
|
||||||
|
<div class="arrows d-flex">
|
||||||
|
<button class="btn btn-secondary move-up btn-margin"><span>↑</span></button>
|
||||||
|
<button class="btn btn-secondary move-down btn-margin"><span>↓</span></button>
|
||||||
|
<button class="btn btn-warning pipelineSettings btn-margin"><span>⚙️</span></button>
|
||||||
|
<button class="btn btn-danger remove"><span>X</span></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
pipelineList.appendChild(listItem);
|
||||||
|
|
||||||
|
listItem.querySelector('.move-up').addEventListener('click', function(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
if (listItem.previousElementSibling) {
|
||||||
|
pipelineList.insertBefore(listItem, listItem.previousElementSibling);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
listItem.querySelector('.move-down').addEventListener('click', function(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
if (listItem.nextElementSibling) {
|
||||||
|
pipelineList.insertBefore(listItem.nextElementSibling, listItem);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
listItem.querySelector('.remove').addEventListener('click', function(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
pipelineList.removeChild(listItem);
|
||||||
|
});
|
||||||
|
|
||||||
|
listItem.querySelector('.pipelineSettings').addEventListener('click', function(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
showpipelineSettingsModal(selectedOperation);
|
||||||
|
});
|
||||||
|
|
||||||
|
function showpipelineSettingsModal(operation) {
|
||||||
|
let pipelineSettingsModal = document.getElementById('pipelineSettingsModal');
|
||||||
|
let pipelineSettingsContent = document.getElementById('pipelineSettingsContent');
|
||||||
|
let operationData = apiDocs[operation].post.parameters || [];
|
||||||
|
|
||||||
|
pipelineSettingsContent.innerHTML = '';
|
||||||
|
|
||||||
|
operationData.forEach(parameter => {
|
||||||
|
let parameterDiv = document.createElement('div');
|
||||||
|
parameterDiv.className = "form-group";
|
||||||
|
|
||||||
|
let parameterLabel = document.createElement('label');
|
||||||
|
parameterLabel.textContent = `${parameter.name} (${parameter.schema.type}): `;
|
||||||
|
parameterLabel.title = parameter.description;
|
||||||
|
parameterDiv.appendChild(parameterLabel);
|
||||||
|
|
||||||
|
let parameterInput;
|
||||||
|
switch (parameter.schema.type) {
|
||||||
|
case 'string':
|
||||||
|
case 'number':
|
||||||
|
case 'integer':
|
||||||
|
parameterInput = document.createElement('input');
|
||||||
|
parameterInput.type = parameter.schema.type === 'string' ? 'text' : 'number';
|
||||||
|
parameterInput.className = "form-control";
|
||||||
|
break;
|
||||||
|
case 'boolean':
|
||||||
|
parameterInput = document.createElement('input');
|
||||||
|
parameterInput.type = 'checkbox';
|
||||||
|
break;
|
||||||
|
case 'array':
|
||||||
|
case 'object':
|
||||||
|
parameterInput = document.createElement('textarea');
|
||||||
|
parameterInput.placeholder = `Enter a JSON formatted ${parameter.schema.type}`;
|
||||||
|
parameterInput.className = "form-control";
|
||||||
|
break;
|
||||||
|
case 'enum':
|
||||||
|
parameterInput = document.createElement('select');
|
||||||
|
parameterInput.className = "form-control";
|
||||||
|
parameter.schema.enum.forEach(option => {
|
||||||
|
let optionElement = document.createElement('option');
|
||||||
|
optionElement.value = option;
|
||||||
|
optionElement.text = option;
|
||||||
|
parameterInput.appendChild(optionElement);
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
parameterInput = document.createElement('input');
|
||||||
|
parameterInput.type = 'text';
|
||||||
|
parameterInput.className = "form-control";
|
||||||
|
}
|
||||||
|
parameterInput.id = parameter.name;
|
||||||
|
|
||||||
|
if (operationSettings[operation] && operationSettings[operation][parameter.name] !== undefined) {
|
||||||
|
let savedValue = operationSettings[operation][parameter.name];
|
||||||
|
|
||||||
|
switch (parameter.schema.type) {
|
||||||
|
case 'number':
|
||||||
|
case 'integer':
|
||||||
|
parameterInput.value = savedValue.toString();
|
||||||
|
break;
|
||||||
|
case 'boolean':
|
||||||
|
parameterInput.checked = savedValue;
|
||||||
|
break;
|
||||||
|
case 'array':
|
||||||
|
case 'object':
|
||||||
|
parameterInput.value = JSON.stringify(savedValue);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
parameterInput.value = savedValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parameterDiv.appendChild(parameterInput);
|
||||||
|
|
||||||
|
pipelineSettingsContent.appendChild(parameterDiv);
|
||||||
|
});
|
||||||
|
|
||||||
|
let saveButton = document.createElement('button');
|
||||||
|
saveButton.textContent = "Save Settings";
|
||||||
|
saveButton.className = "btn btn-primary";
|
||||||
|
saveButton.addEventListener('click', function(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
let settings = {};
|
||||||
|
operationData.forEach(parameter => {
|
||||||
|
let value = document.getElementById(parameter.name).value;
|
||||||
|
switch (parameter.schema.type) {
|
||||||
|
case 'number':
|
||||||
|
case 'integer':
|
||||||
|
settings[parameter.name] = Number(value);
|
||||||
|
break;
|
||||||
|
case 'boolean':
|
||||||
|
settings[parameter.name] = document.getElementById(parameter.name).checked;
|
||||||
|
break;
|
||||||
|
case 'array':
|
||||||
|
case 'object':
|
||||||
|
try {
|
||||||
|
settings[parameter.name] = JSON.parse(value);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`Invalid JSON format for ${parameter.name}`);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
settings[parameter.name] = value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
operationSettings[operation] = settings;
|
||||||
|
console.log(settings);
|
||||||
|
pipelineSettingsModal.style.display = "none";
|
||||||
|
});
|
||||||
|
pipelineSettingsContent.appendChild(saveButton);
|
||||||
|
|
||||||
|
pipelineSettingsModal.style.display = "block";
|
||||||
|
|
||||||
|
pipelineSettingsModal.getElementsByClassName("close")[0].onclick = function() {
|
||||||
|
pipelineSettingsModal.style.display = "none";
|
||||||
|
}
|
||||||
|
|
||||||
|
window.onclick = function(event) {
|
||||||
|
if (event.target == pipelineSettingsModal) {
|
||||||
|
pipelineSettingsModal.style.display = "none";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('savePipelineBtn').addEventListener('click', function() {
|
||||||
|
if (validatePipeline() === false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let pipelineList = document.getElementById('pipelineList').children;
|
||||||
|
let pipelineConfig = {
|
||||||
|
"name": "uniquePipelineName",
|
||||||
|
"pipeline": []
|
||||||
|
};
|
||||||
|
|
||||||
|
for (let i = 0; i < pipelineList.length; i++) {
|
||||||
|
let operationName = pipelineList[i].querySelector('.operationName').textContent;
|
||||||
|
let parameters = operationSettings[operationName] || {};
|
||||||
|
|
||||||
|
pipelineConfig.pipeline.push({
|
||||||
|
"operation": operationName,
|
||||||
|
"parameters": parameters
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let a = document.createElement('a');
|
||||||
|
a.href = URL.createObjectURL(new Blob([JSON.stringify(pipelineConfig, null, 2)], {
|
||||||
|
type: 'application/json'
|
||||||
|
}));
|
||||||
|
a.download = 'pipelineConfig.json';
|
||||||
|
a.style.display = 'none';
|
||||||
|
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
document.body.removeChild(a);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('uploadPipelineBtn').addEventListener('click', function() {
|
||||||
|
document.getElementById('uploadPipelineInput').click();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('uploadPipelineInput').addEventListener('change', function(e) {
|
||||||
|
let reader = new FileReader();
|
||||||
|
reader.onload = function(event) {
|
||||||
|
let pipelineConfig = JSON.parse(event.target.result);
|
||||||
|
let pipelineList = document.getElementById('pipelineList');
|
||||||
|
|
||||||
|
while (pipelineList.firstChild) {
|
||||||
|
pipelineList.removeChild(pipelineList.firstChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
pipelineConfig.pipeline.forEach(operationConfig => {
|
||||||
|
let operationsDropdown = document.getElementById('operationsDropdown');
|
||||||
|
operationsDropdown.value = operationConfig.operation;
|
||||||
|
operationSettings[operationConfig.operation] = operationConfig.parameters;
|
||||||
|
document.getElementById('addOperationBtn').click();
|
||||||
|
|
||||||
|
let lastOperation = pipelineList.lastChild;
|
||||||
|
|
||||||
|
lastOperation.querySelector('.pipelineSettings').click();
|
||||||
|
|
||||||
|
Object.keys(operationConfig.parameters).forEach(parameterName => {
|
||||||
|
let input = document.getElementById(parameterName);
|
||||||
|
if (input) {
|
||||||
|
switch (input.type) {
|
||||||
|
case 'checkbox':
|
||||||
|
input.checked = operationConfig.parameters[parameterName];
|
||||||
|
break;
|
||||||
|
case 'number':
|
||||||
|
input.value = operationConfig.parameters[parameterName].toString();
|
||||||
|
break;
|
||||||
|
case 'text':
|
||||||
|
case 'textarea':
|
||||||
|
default:
|
||||||
|
input.value = JSON.stringify(operationConfig.parameters[parameterName]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelector('#pipelineSettingsModal .btn-primary').click();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
reader.readAsText(e.target.files[0]);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
|
@ -32,6 +32,13 @@
|
||||||
<span class="icon-text" th:text="#{home.multiTool.title}"></span>
|
<span class="icon-text" th:text="#{home.multiTool.title}"></span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="#" th:href="@{pipeline}" th:classappend="${currentPage}=='pipeline' ? 'active' : ''" th:title="#{home.pipeline.desc}">
|
||||||
|
<img class="icon" src="images/pipeline.svg" alt="icon">
|
||||||
|
<span class="icon-text" th:text="#{home.pipeline.title}"></span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
<li class="nav-item nav-item-separator"></li>
|
<li class="nav-item nav-item-separator"></li>
|
||||||
<li class="nav-item dropdown" th:classappend="${currentPage}=='remove-pages' OR ${currentPage}=='merge-pdfs' OR ${currentPage}=='split-pdfs' OR ${currentPage}=='pdf-organizer' OR ${currentPage}=='rotate-pdf' ? 'active' : ''">
|
<li class="nav-item dropdown" th:classappend="${currentPage}=='remove-pages' OR ${currentPage}=='merge-pdfs' OR ${currentPage}=='split-pdfs' OR ${currentPage}=='pdf-organizer' OR ${currentPage}=='rotate-pdf' ? 'active' : ''">
|
||||||
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
<!-- Features -->
|
<!-- Features -->
|
||||||
<div class="features-container container">
|
<div class="features-container 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='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>
|
||||||
<div th:replace="~{fragments/card :: card(id='merge-pdfs', cardTitle=#{home.merge.title}, cardText=#{home.merge.desc}, cardLink='merge-pdfs', svgPath='images/union.svg')}"></div>
|
<div th:replace="~{fragments/card :: card(id='merge-pdfs', cardTitle=#{home.merge.title}, cardText=#{home.merge.desc}, cardLink='merge-pdfs', svgPath='images/union.svg')}"></div>
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
const processFile = async (file) => {
|
const processFile = async (file) => {
|
||||||
const origFileUrl = URL.createObjectURL(file);
|
const origFileUrl = URL.createObjectURL(file);
|
||||||
const formPdfBytes = await fetch(origFileUrl).then(res => res.arrayBuffer());
|
const formPdfBytes = await fetch(origFileUrl).then(res => res.arrayBuffer());
|
||||||
const pdfDoc = await PDFDocument.load(formPdfBytes);
|
const pdfDoc = await PDFDocument.load(formPdfBytes, { ignoreEncryption: true });
|
||||||
|
|
||||||
const form = pdfDoc.getForm();
|
const form = pdfDoc.getForm();
|
||||||
form.flatten();
|
form.flatten();
|
||||||
|
|
|
@ -4,482 +4,126 @@
|
||||||
xmlns:th="http://www.thymeleaf.org">
|
xmlns:th="http://www.thymeleaf.org">
|
||||||
|
|
||||||
<th:block th:insert="~{fragments/common :: head(title=#{merge.title})}"></th:block>
|
<th:block th:insert="~{fragments/common :: head(title=#{merge.title})}"></th:block>
|
||||||
|
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div id="page-container">
|
<style>
|
||||||
<div id="content-wrap">
|
.modal {
|
||||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
display: none; /* Hidden by default */
|
||||||
<br> <br>
|
position: fixed; /* Stay in place */
|
||||||
<div class="container" id="dropContainer">
|
z-index: 1; /* Sit on top */
|
||||||
<div class="row justify-content-center">
|
padding-top: 100px; /* Location of the box */
|
||||||
<div class="col-md-6">
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%; /* Full width */
|
||||||
|
height: 100%; /* Full height */
|
||||||
|
overflow: auto; /* Enable scroll if needed */
|
||||||
|
background-color: rgb(0, 0, 0); /* Fallback color */
|
||||||
|
background-color: rgba(0, 0, 0, 0.4); /* Black w/ opacity */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Modal Content */
|
||||||
|
.modal-content {
|
||||||
|
background-color: #fefefe;
|
||||||
|
margin: auto;
|
||||||
|
padding: 20px;
|
||||||
|
border: 1px solid #888;
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-margin {
|
||||||
|
margin-right: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-body {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div id="page-container">
|
||||||
|
<div id="content-wrap">
|
||||||
|
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||||
|
|
||||||
|
<div class="container" id="dropContainer">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-md-6">
|
||||||
|
|
||||||
|
<!-- Homepage -->
|
||||||
|
<div class="mb-3">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Pipelines</label>
|
||||||
|
<select class="form-control" name="Pipelines">
|
||||||
|
<option value="example">example</option>
|
||||||
|
<option value="Pipelines2">example2</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<button id="customizeBtn" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#pipelineModal">Customize</button>
|
||||||
|
<button id="addNewPipelineBtn" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#pipelineModal">Add New Pipeline</button>
|
||||||
|
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||||
|
<button class="btn btn-primary" id="submitBtn">Submit</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Pipeline Modal -->
|
||||||
|
<div id="pipelineModal" class="modal fade" tabindex="-1" aria-labelledby="pipelineModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-xl">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h2 class="modal-title">Pipeline Configuration</h2>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-body">
|
||||||
|
<!-- Buttons -->
|
||||||
|
<div class="mb-3">
|
||||||
|
<button id="savePipelineBtn" class="btn btn-success">Save Configuration</button>
|
||||||
|
<button id="validateButton" class="btn btn-success">Validate</button>
|
||||||
|
<div class="btn-group">
|
||||||
|
<button id="uploadPipelineBtn" class="btn btn-primary">Upload Configuration</button>
|
||||||
|
<input type="file" id="uploadPipelineInput" accept=".json" style="display: none;">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Operations dropdown and Add operation button -->
|
||||||
|
<div class="mb-3">
|
||||||
|
<select id="operationsDropdown" class="form-select">
|
||||||
|
<!-- Options will be dynamically populated here -->
|
||||||
|
</select>
|
||||||
|
<button id="addOperationBtn" class="btn btn-primary">Add operation to pipeline</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Pipeline operations list -->
|
||||||
|
<h3>Pipeline:</h3>
|
||||||
|
<ol id="pipelineList" class="list-group">
|
||||||
|
<!-- Pipeline operations will be dynamically populated here -->
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button class="btn btn-primary" id="submitConfigBtn">Submit</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- pipelineSettings modal -->
|
||||||
|
<div id="pipelineSettingsModal" class="modal">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-body">
|
||||||
|
<span class="close">×</span>
|
||||||
|
<h2>Operation Settings</h2>
|
||||||
|
<div id="pipelineSettingsContent">
|
||||||
|
<!-- pipelineSettings will be dynamically populated here -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="js/pipeline.js"></script>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
|
||||||
<button id="savePipelineBtn" class="btn btn-success">Save
|
|
||||||
Pipeline Configuration</button>
|
|
||||||
<div class="btn-group">
|
|
||||||
<button id="uploadPipelineBtn" class="btn btn-primary">Upload
|
|
||||||
Pipeline Configuration</button>
|
|
||||||
<input type="file" id="uploadPipelineInput" accept=".json"
|
|
||||||
style="display: none;">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="pipelineContainer" class="card">
|
|
||||||
|
|
||||||
<!-- Pipeline Configuration Card Header -->
|
|
||||||
<div class="card-header">
|
|
||||||
<h2 class="card-title">Pipeline Configuration</h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Pipeline Configuration Body -->
|
|
||||||
<div class="card-body">
|
|
||||||
<div class="mb-3">
|
|
||||||
<select id="operationsDropdown" class="form-select">
|
|
||||||
<!-- Options will be dynamically populated here -->
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<button id="addOperationBtn" class="btn btn-primary">Add
|
|
||||||
operation to pipeline</button>
|
|
||||||
</div>
|
|
||||||
<h3>Pipeline:</h3>
|
|
||||||
<ol id="pipelineList" class="list-group">
|
|
||||||
<!-- Pipeline operations will be dynamically populated here -->
|
|
||||||
</ol>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<input type="file" id="fileInput" multiple>
|
|
||||||
|
|
||||||
<button class="btn btn-primary" id="submitConfigBtn">Submit</button>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- pipelineSettings modal -->
|
|
||||||
<div id="pipelineSettingsModal" class="modal">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-body">
|
|
||||||
<span class="close">×</span>
|
|
||||||
<h2>Operation Settings</h2>
|
|
||||||
<div id="pipelineSettingsContent">
|
|
||||||
<!-- pipelineSettings will be dynamically populated here -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<style>
|
|
||||||
.modal {
|
|
||||||
display: none; /* Hidden by default */
|
|
||||||
position: fixed; /* Stay in place */
|
|
||||||
z-index: 1; /* Sit on top */
|
|
||||||
padding-top: 100px; /* Location of the box */
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
width: 100%; /* Full width */
|
|
||||||
height: 100%; /* Full height */
|
|
||||||
overflow: auto; /* Enable scroll if needed */
|
|
||||||
background-color: rgb(0, 0, 0); /* Fallback color */
|
|
||||||
background-color: rgba(0, 0, 0, 0.4); /* Black w/ opacity */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Modal Content */
|
|
||||||
.modal-content {
|
|
||||||
background-color: #fefefe;
|
|
||||||
margin: auto;
|
|
||||||
padding: 20px;
|
|
||||||
border: 1px solid #888;
|
|
||||||
width: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-margin {
|
|
||||||
margin-right: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-body {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<script>
|
|
||||||
|
|
||||||
document.getElementById('submitConfigBtn').addEventListener('click', function() {
|
|
||||||
// Get the selected configuration
|
|
||||||
let selectedOperation = document.getElementById('operationsDropdown').value;
|
|
||||||
let parameters = operationSettings[selectedOperation] || {};
|
|
||||||
|
|
||||||
// Create the pipelineConfig object
|
|
||||||
let pipelineConfig = {
|
|
||||||
"name": "uniquePipelineName",
|
|
||||||
"pipeline": [{
|
|
||||||
"operation": selectedOperation,
|
|
||||||
"parameters": parameters
|
|
||||||
}]
|
|
||||||
};
|
|
||||||
|
|
||||||
let pipelineConfigJson = JSON.stringify(pipelineConfig, null, 2);
|
|
||||||
|
|
||||||
// Create new FormData instance
|
|
||||||
let formData = new FormData();
|
|
||||||
|
|
||||||
// Get the selected files from the file input
|
|
||||||
let fileInput = document.getElementById('fileInput');
|
|
||||||
let files = fileInput.files;
|
|
||||||
|
|
||||||
// Add files to formData
|
|
||||||
for(let i = 0; i < files.length; i++) {
|
|
||||||
console.log("files[i]",files[i].name);
|
|
||||||
formData.append('fileInput', files[i], files[i].name);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the JSON string to formData
|
|
||||||
console.log("pipelineConfigJson",pipelineConfigJson);
|
|
||||||
formData.append('json', pipelineConfigJson);
|
|
||||||
console.log("formData",formData);
|
|
||||||
// Make a POST request to the server
|
|
||||||
fetch('/handleData', {
|
|
||||||
method: 'POST',
|
|
||||||
body: formData
|
|
||||||
})
|
|
||||||
.then(response => response.blob())
|
|
||||||
.then(blob => {
|
|
||||||
// Create a link element
|
|
||||||
let url = window.URL.createObjectURL(blob);
|
|
||||||
let a = document.createElement('a');
|
|
||||||
a.href = url;
|
|
||||||
a.download = 'outputfile'; // or you can name your output file here
|
|
||||||
document.body.appendChild(a); // Required for Firefox
|
|
||||||
a.click();
|
|
||||||
a.remove(); // After triggering download we remove the element
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error('Error:', error);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
let apiDocs = {};
|
|
||||||
|
|
||||||
let operationSettings = {};
|
|
||||||
|
|
||||||
fetch('v3/api-docs')
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(data => {
|
|
||||||
apiDocs = data.paths;
|
|
||||||
let operationsDropdown = document.getElementById('operationsDropdown');
|
|
||||||
|
|
||||||
operationsDropdown.innerHTML = '';
|
|
||||||
|
|
||||||
Object.keys(apiDocs).forEach(operation => {
|
|
||||||
let option = document.createElement('option');
|
|
||||||
option.textContent = operation;
|
|
||||||
operationsDropdown.appendChild(option);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById('addOperationBtn').addEventListener('click', function() {
|
|
||||||
let selectedOperation = document.getElementById('operationsDropdown').value;
|
|
||||||
let pipelineList = document.getElementById('pipelineList');
|
|
||||||
|
|
||||||
// Add the selected operation to the pipeline list
|
|
||||||
let listItem = document.createElement('li');
|
|
||||||
listItem.className = "list-group-item";
|
|
||||||
listItem.innerHTML = `
|
|
||||||
<div class="d-flex justify-content-between align-items-center w-100">
|
|
||||||
<div class="operationName">${selectedOperation}</div>
|
|
||||||
<div class="arrows d-flex">
|
|
||||||
<button class="btn btn-secondary move-up btn-margin"><span>↑</span></button>
|
|
||||||
<button class="btn btn-secondary move-down btn-margin"><span>↓</span></button>
|
|
||||||
<button class="btn btn-warning pipelineSettings btn-margin"><span>⚙️</span></button>
|
|
||||||
<button class="btn btn-danger remove"><span>X</span></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
pipelineList.appendChild(listItem);
|
|
||||||
|
|
||||||
listItem.querySelector('.move-up').addEventListener('click', function(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
if (listItem.previousElementSibling) {
|
|
||||||
pipelineList.insertBefore(listItem, listItem.previousElementSibling);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
listItem.querySelector('.move-down').addEventListener('click', function(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
if (listItem.nextElementSibling) {
|
|
||||||
pipelineList.insertBefore(listItem.nextElementSibling, listItem);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
listItem.querySelector('.remove').addEventListener('click', function(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
pipelineList.removeChild(listItem);
|
|
||||||
});
|
|
||||||
|
|
||||||
listItem.querySelector('.pipelineSettings').addEventListener('click', function(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
showpipelineSettingsModal(selectedOperation);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
function showpipelineSettingsModal(operation) {
|
|
||||||
let pipelineSettingsModal = document.getElementById('pipelineSettingsModal');
|
|
||||||
let pipelineSettingsContent = document.getElementById('pipelineSettingsContent');
|
|
||||||
let operationData = apiDocs[operation].post.parameters || [];
|
|
||||||
|
|
||||||
// Clear the modal
|
|
||||||
pipelineSettingsContent.innerHTML = '';
|
|
||||||
|
|
||||||
// Populate the modal with operation parameters
|
|
||||||
operationData.forEach(parameter => {
|
|
||||||
let parameterDiv = document.createElement('div');
|
|
||||||
parameterDiv.className = "form-group";
|
|
||||||
|
|
||||||
let parameterLabel = document.createElement('label');
|
|
||||||
parameterLabel.textContent = `${parameter.name} (${parameter.schema.type}): `;
|
|
||||||
parameterLabel.title = parameter.description; // Add description as tooltip
|
|
||||||
parameterDiv.appendChild(parameterLabel);
|
|
||||||
|
|
||||||
let parameterInput;
|
|
||||||
switch(parameter.schema.type) {
|
|
||||||
case 'string':
|
|
||||||
case 'number':
|
|
||||||
case 'integer':
|
|
||||||
parameterInput = document.createElement('input');
|
|
||||||
parameterInput.type = parameter.schema.type === 'string' ? 'text' : 'number';
|
|
||||||
parameterInput.className = "form-control";
|
|
||||||
break;
|
|
||||||
case 'boolean':
|
|
||||||
parameterInput = document.createElement('input');
|
|
||||||
parameterInput.type = 'checkbox';
|
|
||||||
break;
|
|
||||||
case 'array':
|
|
||||||
case 'object':
|
|
||||||
parameterInput = document.createElement('textarea');
|
|
||||||
parameterInput.placeholder = `Enter a JSON formatted ${parameter.schema.type}`;
|
|
||||||
parameterInput.className = "form-control";
|
|
||||||
break;
|
|
||||||
case 'enum':
|
|
||||||
parameterInput = document.createElement('select');
|
|
||||||
parameterInput.className = "form-control";
|
|
||||||
parameter.schema.enum.forEach(option => {
|
|
||||||
let optionElement = document.createElement('option');
|
|
||||||
optionElement.value = option;
|
|
||||||
optionElement.text = option;
|
|
||||||
parameterInput.appendChild(optionElement);
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
parameterInput = document.createElement('input');
|
|
||||||
parameterInput.type = 'text';
|
|
||||||
parameterInput.className = "form-control";
|
|
||||||
}
|
|
||||||
parameterInput.id = parameter.name;
|
|
||||||
|
|
||||||
// Check if there are saved settings for this operation and this parameter
|
|
||||||
if(operationSettings[operation] && operationSettings[operation][parameter.name] !== undefined) {
|
|
||||||
let savedValue = operationSettings[operation][parameter.name];
|
|
||||||
|
|
||||||
// Set the value in the input field according to the type of the parameter
|
|
||||||
switch(parameter.schema.type) {
|
|
||||||
case 'number':
|
|
||||||
case 'integer':
|
|
||||||
parameterInput.value = savedValue.toString();
|
|
||||||
break;
|
|
||||||
case 'boolean':
|
|
||||||
parameterInput.checked = savedValue;
|
|
||||||
break;
|
|
||||||
case 'array':
|
|
||||||
case 'object':
|
|
||||||
parameterInput.value = JSON.stringify(savedValue);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
parameterInput.value = savedValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
parameterDiv.appendChild(parameterInput);
|
|
||||||
|
|
||||||
pipelineSettingsContent.appendChild(parameterDiv);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add a save button
|
|
||||||
let saveButton = document.createElement('button');
|
|
||||||
saveButton.textContent = "Save Settings";
|
|
||||||
saveButton.className = "btn btn-primary";
|
|
||||||
saveButton.addEventListener('click', function(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
let settings = {};
|
|
||||||
operationData.forEach(parameter => {
|
|
||||||
let value = document.getElementById(parameter.name).value;
|
|
||||||
switch(parameter.schema.type) {
|
|
||||||
case 'number':
|
|
||||||
case 'integer':
|
|
||||||
settings[parameter.name] = Number(value);
|
|
||||||
break;
|
|
||||||
case 'boolean':
|
|
||||||
settings[parameter.name] = document.getElementById(parameter.name).checked;
|
|
||||||
break;
|
|
||||||
case 'array':
|
|
||||||
case 'object':
|
|
||||||
try {
|
|
||||||
settings[parameter.name] = JSON.parse(value);
|
|
||||||
} catch(err) {
|
|
||||||
console.error(`Invalid JSON format for ${parameter.name}`);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
settings[parameter.name] = value;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
operationSettings[operation] = settings;
|
|
||||||
console.log(settings); // TODO: Save these settings in your desired format
|
|
||||||
pipelineSettingsModal.style.display = "none";
|
|
||||||
});
|
|
||||||
pipelineSettingsContent.appendChild(saveButton);
|
|
||||||
|
|
||||||
// Show the modal
|
|
||||||
pipelineSettingsModal.style.display = "block";
|
|
||||||
|
|
||||||
// When the user clicks on <span> (x), close the modal
|
|
||||||
pipelineSettingsModal.getElementsByClassName("close")[0].onclick = function() {
|
|
||||||
pipelineSettingsModal.style.display = "none";
|
|
||||||
}
|
|
||||||
|
|
||||||
// When the user clicks anywhere outside of the modal, close it
|
|
||||||
window.onclick = function(event) {
|
|
||||||
if (event.target == pipelineSettingsModal) {
|
|
||||||
pipelineSettingsModal.style.display = "none";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
document.getElementById('savePipelineBtn').addEventListener('click', function() {
|
|
||||||
let pipelineList = document.getElementById('pipelineList').children;
|
|
||||||
let pipelineConfig = {
|
|
||||||
"name": "uniquePipelineName",
|
|
||||||
"pipeline": []
|
|
||||||
};
|
|
||||||
|
|
||||||
if (pipelineList[pipelineList.length - 1].querySelector('.operationName').textContent !== '/add-password') {
|
|
||||||
alert('The "add-password" operation should be at the end of the operations sequence. Please adjust the operations order.');
|
|
||||||
return; // Stop the function execution
|
|
||||||
}
|
|
||||||
|
|
||||||
for(let i=0; i<pipelineList.length; i++) {
|
|
||||||
let operationName = pipelineList[i].querySelector('.operationName').textContent;
|
|
||||||
let parameters = operationSettings[operationName] || {}; // Retrieve saved parameters for this operation
|
|
||||||
|
|
||||||
pipelineConfig.pipeline.push({
|
|
||||||
"operation": operationName,
|
|
||||||
"parameters": parameters
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let a = document.createElement('a');
|
|
||||||
a.href = URL.createObjectURL(new Blob([JSON.stringify(pipelineConfig, null, 2)], {
|
|
||||||
type: 'application/json'
|
|
||||||
}));
|
|
||||||
a.download = 'pipelineConfig.json';
|
|
||||||
a.style.display = 'none';
|
|
||||||
|
|
||||||
document.body.appendChild(a);
|
|
||||||
a.click();
|
|
||||||
document.body.removeChild(a);
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById('uploadPipelineBtn').addEventListener('click', function() {
|
|
||||||
document.getElementById('uploadPipelineInput').click();
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById('uploadPipelineInput').addEventListener('change', function(e) {
|
|
||||||
let reader = new FileReader();
|
|
||||||
reader.onload = function(event) {
|
|
||||||
let pipelineConfig = JSON.parse(event.target.result);
|
|
||||||
let pipelineList = document.getElementById('pipelineList');
|
|
||||||
|
|
||||||
// clear the existing pipeline list
|
|
||||||
while(pipelineList.firstChild) {
|
|
||||||
pipelineList.removeChild(pipelineList.firstChild);
|
|
||||||
}
|
|
||||||
|
|
||||||
// populate the pipeline list with operations from the uploaded configuration
|
|
||||||
pipelineConfig.pipeline.forEach(operationConfig => {
|
|
||||||
let operationsDropdown = document.getElementById('operationsDropdown');
|
|
||||||
operationsDropdown.value = operationConfig.operation;
|
|
||||||
operationSettings[operationConfig.operation] = operationConfig.parameters;
|
|
||||||
document.getElementById('addOperationBtn').click();
|
|
||||||
|
|
||||||
// get the last added operation
|
|
||||||
let lastOperation = pipelineList.lastChild;
|
|
||||||
|
|
||||||
// open the settings modal
|
|
||||||
lastOperation.querySelector('.pipelineSettings').click();
|
|
||||||
|
|
||||||
// set the parameters for the added operation
|
|
||||||
Object.keys(operationConfig.parameters).forEach(parameterName => {
|
|
||||||
let input = document.getElementById(parameterName);
|
|
||||||
if(input) {
|
|
||||||
switch(input.type) {
|
|
||||||
case 'checkbox':
|
|
||||||
input.checked = operationConfig.parameters[parameterName];
|
|
||||||
break;
|
|
||||||
case 'number':
|
|
||||||
input.value = operationConfig.parameters[parameterName].toString();
|
|
||||||
break;
|
|
||||||
case 'text':
|
|
||||||
case 'textarea':
|
|
||||||
default:
|
|
||||||
input.value = JSON.stringify(operationConfig.parameters[parameterName]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// save the settings
|
|
||||||
document.querySelector('#pipelineSettingsModal .btn-primary').click();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
reader.readAsText(e.target.files[0]);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
|
||||||
</div>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
Loading…
Reference in a new issue