Merge branch 'main' into mannam11/file-override-fix-529
This commit is contained in:
commit
da50e4d212
81 changed files with 2152 additions and 984 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -15,8 +15,8 @@ local.properties
|
||||||
.classpath
|
.classpath
|
||||||
.project
|
.project
|
||||||
version.properties
|
version.properties
|
||||||
pipeline/
|
pipeline/watchedFolders/
|
||||||
|
pipeline/finishedFolders/
|
||||||
#### Stirling-PDF Files ###
|
#### Stirling-PDF Files ###
|
||||||
customFiles/
|
customFiles/
|
||||||
configs/
|
configs/
|
||||||
|
|
|
@ -6,7 +6,8 @@ ARG VERSION_TAG
|
||||||
# Set Environment Variables
|
# Set Environment Variables
|
||||||
ENV DOCKER_ENABLE_SECURITY=false \
|
ENV DOCKER_ENABLE_SECURITY=false \
|
||||||
HOME=/home/stirlingpdfuser \
|
HOME=/home/stirlingpdfuser \
|
||||||
VERSION_TAG=$VERSION_TAG
|
VERSION_TAG=$VERSION_TAG \
|
||||||
|
JAVA_TOOL_OPTIONS="$JAVA_TOOL_OPTIONS -XX:MaxRAMPercentage=75"
|
||||||
# PUID=1000 \
|
# PUID=1000 \
|
||||||
# PGID=1000 \
|
# PGID=1000 \
|
||||||
# UMASK=022 \
|
# UMASK=022 \
|
||||||
|
@ -18,13 +19,14 @@ ENV DOCKER_ENABLE_SECURITY=false \
|
||||||
## mkdir -p $HOME && chown stirlingpdfuser:stirlingpdfgroup $HOME
|
## mkdir -p $HOME && chown stirlingpdfuser:stirlingpdfgroup $HOME
|
||||||
|
|
||||||
# Set up necessary directories and permissions
|
# Set up necessary directories and permissions
|
||||||
RUN mkdir -p /scripts /usr/share/fonts/opentype/noto /usr/share/tesseract-ocr /configs /customFiles
|
RUN mkdir -p /scripts /usr/share/fonts/opentype/noto /usr/share/tesseract-ocr /configs /logs /customFiles /pipeline /pipeline/defaultWebUIConfigs /pipeline/watchedFolders /pipeline/finishedFolders
|
||||||
##&& \
|
##&& \
|
||||||
## chown -R stirlingpdfuser:stirlingpdfgroup /scripts /usr/share/fonts/opentype/noto /usr/share/tesseract-ocr /configs /customFiles && \
|
## chown -R stirlingpdfuser:stirlingpdfgroup /scripts /usr/share/fonts/opentype/noto /usr/share/tesseract-ocr /configs /customFiles && \
|
||||||
## chown -R stirlingpdfuser:stirlingpdfgroup /usr/share/tesseract-ocr-original
|
## chown -R stirlingpdfuser:stirlingpdfgroup /usr/share/tesseract-ocr-original
|
||||||
|
|
||||||
# Copy necessary files
|
# Copy necessary files
|
||||||
COPY ./scripts/* /scripts/
|
COPY ./scripts/* /scripts/
|
||||||
|
COPY ./pipeline/ /pipeline/
|
||||||
COPY src/main/resources/static/fonts/*.ttf /usr/share/fonts/opentype/noto/
|
COPY src/main/resources/static/fonts/*.ttf /usr/share/fonts/opentype/noto/
|
||||||
COPY src/main/resources/static/fonts/*.otf /usr/share/fonts/opentype/noto/
|
COPY src/main/resources/static/fonts/*.otf /usr/share/fonts/opentype/noto/
|
||||||
COPY build/libs/*.jar app.jar
|
COPY build/libs/*.jar app.jar
|
||||||
|
@ -42,4 +44,4 @@ EXPOSE 8080
|
||||||
# Set user and run command
|
# Set user and run command
|
||||||
##USER stirlingpdfuser
|
##USER stirlingpdfuser
|
||||||
ENTRYPOINT ["/scripts/init.sh"]
|
ENTRYPOINT ["/scripts/init.sh"]
|
||||||
CMD ["java", "-jar", "/app.jar"]
|
CMD ["java", "-Dfile.encoding=UTF-8", "-jar", "/app.jar"]
|
||||||
|
|
|
@ -17,7 +17,8 @@ RUN apt-get update && \
|
||||||
# Set Environment Variables
|
# Set Environment Variables
|
||||||
ENV DOCKER_ENABLE_SECURITY=false \
|
ENV DOCKER_ENABLE_SECURITY=false \
|
||||||
HOME=/home/stirlingpdfuser \
|
HOME=/home/stirlingpdfuser \
|
||||||
VERSION_TAG=$VERSION_TAG
|
VERSION_TAG=$VERSION_TAG \
|
||||||
|
JAVA_TOOL_OPTIONS="$JAVA_TOOL_OPTIONS -XX:MaxRAMPercentage=75"
|
||||||
# PUID=1000 \
|
# PUID=1000 \
|
||||||
# PGID=1000 \
|
# PGID=1000 \
|
||||||
# UMASK=022 \
|
# UMASK=022 \
|
||||||
|
@ -28,13 +29,14 @@ ENV DOCKER_ENABLE_SECURITY=false \
|
||||||
# mkdir -p $HOME && chown stirlingpdfuser:stirlingpdfgroup $HOME
|
# mkdir -p $HOME && chown stirlingpdfuser:stirlingpdfgroup $HOME
|
||||||
|
|
||||||
# Set up necessary directories and permissions
|
# Set up necessary directories and permissions
|
||||||
RUN mkdir -p /scripts /usr/share/fonts/opentype/noto /configs /customFiles
|
RUN mkdir -p /scripts /usr/share/fonts/opentype/noto /configs /customFiles /logs /pipeline /pipeline/defaultWebUIConfigs /pipeline/watchedFolders /pipeline/finishedFolders
|
||||||
|
|
||||||
# chown -R stirlingpdfuser:stirlingpdfgroup /usr/share/fonts/opentype/noto /configs /customFiles
|
# chown -R stirlingpdfuser:stirlingpdfgroup /usr/share/fonts/opentype/noto /configs /customFiles
|
||||||
|
|
||||||
# Copy necessary files
|
# Copy necessary files
|
||||||
COPY ./scripts/download-security-jar.sh /scripts/download-security-jar.sh
|
COPY ./scripts/download-security-jar.sh /scripts/download-security-jar.sh
|
||||||
COPY ./scripts/init-without-ocr.sh /scripts/init-without-ocr.sh
|
COPY ./scripts/init-without-ocr.sh /scripts/init-without-ocr.sh
|
||||||
|
COPY ./pipeline/ /pipeline/
|
||||||
COPY src/main/resources/static/fonts/*.ttf /usr/share/fonts/opentype/noto/
|
COPY src/main/resources/static/fonts/*.ttf /usr/share/fonts/opentype/noto/
|
||||||
COPY src/main/resources/static/fonts/*.otf /usr/share/fonts/opentype/noto/
|
COPY src/main/resources/static/fonts/*.otf /usr/share/fonts/opentype/noto/
|
||||||
COPY build/libs/*.jar app.jar
|
COPY build/libs/*.jar app.jar
|
||||||
|
@ -60,4 +62,4 @@ ENV DOCKER_ENABLE_SECURITY=false
|
||||||
# Run the application
|
# Run the application
|
||||||
#USER stirlingpdfuser
|
#USER stirlingpdfuser
|
||||||
ENTRYPOINT ["/scripts/init-without-ocr.sh"]
|
ENTRYPOINT ["/scripts/init-without-ocr.sh"]
|
||||||
CMD ["java", "-jar", "/app.jar"]
|
CMD ["java", "-Dfile.encoding=UTF-8", "-jar", "/app.jar"]
|
||||||
|
|
|
@ -6,7 +6,8 @@ ARG VERSION_TAG
|
||||||
# Set Environment Variables
|
# Set Environment Variables
|
||||||
ENV DOCKER_ENABLE_SECURITY=false \
|
ENV DOCKER_ENABLE_SECURITY=false \
|
||||||
HOME=/home/stirlingpdfuser \
|
HOME=/home/stirlingpdfuser \
|
||||||
VERSION_TAG=$VERSION_TAG
|
VERSION_TAG=$VERSION_TAG \
|
||||||
|
JAVA_TOOL_OPTIONS="$JAVA_TOOL_OPTIONS -XX:MaxRAMPercentage=75"
|
||||||
# PUID=1000 \
|
# PUID=1000 \
|
||||||
# PGID=1000 \
|
# PGID=1000 \
|
||||||
# UMASK=022 \
|
# UMASK=022 \
|
||||||
|
@ -18,12 +19,12 @@ ENV DOCKER_ENABLE_SECURITY=false \
|
||||||
|
|
||||||
# Set up necessary directories and permissions
|
# Set up necessary directories and permissions
|
||||||
#RUN mkdir -p /scripts /configs /customFiles && \
|
#RUN mkdir -p /scripts /configs /customFiles && \
|
||||||
# chown -R stirlingpdfuser:stirlingpdfgroup /scripts /configs /customFiles
|
# chown -R stirlingpdfuser:stirlingpdfgroup /scripts /configs /customFiles /logs /pipeline /pipeline/defaultWebUIConfigs /pipeline/watchedFolders /pipeline/finishedFolders
|
||||||
|
|
||||||
RUN mkdir -p /scripts /usr/share/fonts/opentype/noto /configs /customFiles
|
RUN mkdir -p /scripts /usr/share/fonts/opentype/noto /configs /customFiles
|
||||||
COPY ./scripts/download-security-jar.sh /scripts/download-security-jar.sh
|
COPY ./scripts/download-security-jar.sh /scripts/download-security-jar.sh
|
||||||
COPY ./scripts/init-without-ocr.sh /scripts/init-without-ocr.sh
|
COPY ./scripts/init-without-ocr.sh /scripts/init-without-ocr.sh
|
||||||
|
COPY ./pipeline/ /pipeline/
|
||||||
COPY build/libs/*.jar app.jar
|
COPY build/libs/*.jar app.jar
|
||||||
|
|
||||||
# Set font cache and permissions
|
# Set font cache and permissions
|
||||||
|
@ -42,4 +43,4 @@ ENV ENDPOINTS_GROUPS_TO_REMOVE=CLI
|
||||||
ENTRYPOINT ["/scripts/init-without-ocr.sh"]
|
ENTRYPOINT ["/scripts/init-without-ocr.sh"]
|
||||||
|
|
||||||
# Run the application
|
# Run the application
|
||||||
CMD ["java", "-jar", "/app.jar"]
|
CMD ["java", "-Dfile.encoding=UTF-8", "-jar", "/app.jar"]
|
||||||
|
|
|
@ -147,7 +147,7 @@ Note: Podman is CLI-compatible with Docker, so simply replace "docker" with "pod
|
||||||
Please view https://github.com/Frooodle/Stirling-PDF/blob/main/HowToUseOCR.md
|
Please view https://github.com/Frooodle/Stirling-PDF/blob/main/HowToUseOCR.md
|
||||||
|
|
||||||
## Want to add your own language?
|
## Want to add your own language?
|
||||||
Stirling PDF currently supports 20!
|
Stirling PDF currently supports 21!
|
||||||
- English (English) (en_GB)
|
- English (English) (en_GB)
|
||||||
- English (US) (en_US)
|
- English (US) (en_US)
|
||||||
- Arabic (العربية) (ar_AR)
|
- Arabic (العربية) (ar_AR)
|
||||||
|
@ -169,6 +169,7 @@ Stirling PDF currently supports 20!
|
||||||
- Greek (el_GR)
|
- Greek (el_GR)
|
||||||
- Turkish (Türkçe) (tr_TR)
|
- Turkish (Türkçe) (tr_TR)
|
||||||
- Indonesia (Bahasa Indonesia) (id_ID)
|
- Indonesia (Bahasa Indonesia) (id_ID)
|
||||||
|
- Hindi (हिंदी) (hi_IN)
|
||||||
|
|
||||||
If you want to add your own language to Stirling-PDF please refer
|
If you want to add your own language to Stirling-PDF please refer
|
||||||
https://github.com/Frooodle/Stirling-PDF/blob/main/HowToAddNewLanguage.md
|
https://github.com/Frooodle/Stirling-PDF/blob/main/HowToAddNewLanguage.md
|
||||||
|
|
12
build.gradle
12
build.gradle
|
@ -8,7 +8,7 @@ plugins {
|
||||||
}
|
}
|
||||||
|
|
||||||
group = 'stirling.software'
|
group = 'stirling.software'
|
||||||
version = '0.17.2'
|
version = '0.18.1'
|
||||||
sourceCompatibility = '17'
|
sourceCompatibility = '17'
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
|
@ -48,7 +48,7 @@ launch4j {
|
||||||
|
|
||||||
errTitle="Encountered error, Do you have Java 17?"
|
errTitle="Encountered error, Do you have Java 17?"
|
||||||
downloadUrl="https://download.oracle.com/java/17/latest/jdk-17_windows-x64_bin.exe"
|
downloadUrl="https://download.oracle.com/java/17/latest/jdk-17_windows-x64_bin.exe"
|
||||||
variables=["BROWSER_OPEN=true"]
|
variables=["BROWSER_OPEN=true", "ENDPOINTS_GROUPS_TO_REMOVE=CLI"]
|
||||||
jreMinVersion="17"
|
jreMinVersion="17"
|
||||||
|
|
||||||
mutexName="Stirling-PDF"
|
mutexName="Stirling-PDF"
|
||||||
|
@ -68,17 +68,17 @@ dependencies {
|
||||||
implementation 'org.springframework:spring-webmvc:6.0.15'
|
implementation 'org.springframework:spring-webmvc:6.0.15'
|
||||||
|
|
||||||
implementation 'org.yaml:snakeyaml:2.1'
|
implementation 'org.yaml:snakeyaml:2.1'
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-web:3.1.6'
|
implementation 'org.springframework.boot:spring-boot-starter-web:3.2.1'
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf:3.1.6'
|
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf:3.2.1'
|
||||||
|
|
||||||
if (System.getenv('DOCKER_ENABLE_SECURITY') != 'false') {
|
if (System.getenv('DOCKER_ENABLE_SECURITY') != 'false') {
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-security:3.1.6'
|
implementation 'org.springframework.boot:spring-boot-starter-security:3.2.1'
|
||||||
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5:3.1.2.RELEASE'
|
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5:3.1.2.RELEASE'
|
||||||
implementation "org.springframework.boot:spring-boot-starter-data-jpa"
|
implementation "org.springframework.boot:spring-boot-starter-data-jpa"
|
||||||
implementation "com.h2database:h2"
|
implementation "com.h2database:h2"
|
||||||
}
|
}
|
||||||
|
|
||||||
testImplementation 'org.springframework.boot:spring-boot-starter-test:3.1.6'
|
testImplementation 'org.springframework.boot:spring-boot-starter-test:3.2.1'
|
||||||
|
|
||||||
// Batik
|
// Batik
|
||||||
implementation 'org.apache.xmlgraphics:batik-all:1.17'
|
implementation 'org.apache.xmlgraphics:batik-all:1.17'
|
||||||
|
|
39
pipeline/defaultWebUIConfigs/Prepare-pdfs-for-email.json
Normal file
39
pipeline/defaultWebUIConfigs/Prepare-pdfs-for-email.json
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
{
|
||||||
|
"name": "Prepare-pdfs-for-email",
|
||||||
|
"pipeline": [
|
||||||
|
{
|
||||||
|
"operation": "/api/v1/misc/repair",
|
||||||
|
"parameters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"operation": "/api/v1/security/sanitize-pdf",
|
||||||
|
"parameters": {
|
||||||
|
"removeJavaScript": true,
|
||||||
|
"removeEmbeddedFiles": false,
|
||||||
|
"removeMetadata": false,
|
||||||
|
"removeLinks": false,
|
||||||
|
"removeFonts": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"operation": "/api/v1/misc/compress-pdf",
|
||||||
|
"parameters": {
|
||||||
|
"optimizeLevel": 2,
|
||||||
|
"expectedOutputSize": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"operation": "/api/v1/general/split-by-size-or-count",
|
||||||
|
"parameters": {
|
||||||
|
"splitType": 0,
|
||||||
|
"splitValue": "15MB"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"_examples": {
|
||||||
|
"outputDir": "{outputFolder}/{folderName}",
|
||||||
|
"outputFileName": "{filename}-{pipelineName}-{date}-{time}"
|
||||||
|
},
|
||||||
|
"outputDir": "httpWebRequest",
|
||||||
|
"outputFileName": "{filename}"
|
||||||
|
}
|
33
pipeline/defaultWebUIConfigs/split-rotate-auto-rename.json
Normal file
33
pipeline/defaultWebUIConfigs/split-rotate-auto-rename.json
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
{
|
||||||
|
"name": "split-rotate-auto-rename",
|
||||||
|
"pipeline": [
|
||||||
|
{
|
||||||
|
"operation": "/api/v1/general/split-pdf-by-sections",
|
||||||
|
"parameters": {
|
||||||
|
"horizontalDivisions": 2,
|
||||||
|
"verticalDivisions": 2,
|
||||||
|
"fileInput": "automated"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"operation": "/api/v1/general/rotate-pdf",
|
||||||
|
"parameters": {
|
||||||
|
"angle": 90,
|
||||||
|
"fileInput": "automated"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"operation": "/api/v1/misc/auto-rename",
|
||||||
|
"parameters": {
|
||||||
|
"useFirstTextAsFallback": false,
|
||||||
|
"fileInput": "automated"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"_examples": {
|
||||||
|
"outputDir": "{outputFolder}/{folderName}",
|
||||||
|
"outputFileName": "{filename}-{pipelineName}-{date}-{time}"
|
||||||
|
},
|
||||||
|
"outputDir": "{outputFolder}",
|
||||||
|
"outputFileName": "{filename}"
|
||||||
|
}
|
|
@ -8,13 +8,14 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
import org.springframework.core.env.Environment;
|
import org.springframework.core.env.Environment;
|
||||||
|
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||||
|
|
||||||
import jakarta.annotation.PostConstruct;
|
import jakarta.annotation.PostConstruct;
|
||||||
import stirling.software.SPDF.config.ConfigInitializer;
|
import stirling.software.SPDF.config.ConfigInitializer;
|
||||||
import stirling.software.SPDF.utils.GeneralUtils;
|
import stirling.software.SPDF.utils.GeneralUtils;
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
|
|
||||||
//@EnableScheduling
|
@EnableScheduling
|
||||||
public class SPdfApplication {
|
public class SPdfApplication {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
|
@ -28,11 +29,7 @@ public class SPdfApplication {
|
||||||
|
|
||||||
if (browserOpen) {
|
if (browserOpen) {
|
||||||
try {
|
try {
|
||||||
String port = env.getProperty("local.server.port");
|
String url = "http://localhost:" + getPort();
|
||||||
if(port == null || port.length() == 0) {
|
|
||||||
port="8080";
|
|
||||||
}
|
|
||||||
String url = "http://localhost:" + port;
|
|
||||||
|
|
||||||
String os = System.getProperty("os.name").toLowerCase();
|
String os = System.getProperty("os.name").toLowerCase();
|
||||||
Runtime rt = Runtime.getRuntime();
|
Runtime rt = Runtime.getRuntime();
|
||||||
|
@ -66,17 +63,17 @@ public class SPdfApplication {
|
||||||
GeneralUtils.createDir("customFiles/static/");
|
GeneralUtils.createDir("customFiles/static/");
|
||||||
GeneralUtils.createDir("customFiles/templates/");
|
GeneralUtils.createDir("customFiles/templates/");
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
System.out.println("Stirling-PDF Started.");
|
System.out.println("Stirling-PDF Started.");
|
||||||
|
|
||||||
String port = System.getProperty("local.server.port");
|
String url = "http://localhost:" + getPort();
|
||||||
if(port == null || port.length() == 0) {
|
|
||||||
port="8080";
|
|
||||||
}
|
|
||||||
String url = "http://localhost:" + port;
|
|
||||||
System.out.println("Navigate to " + url);
|
System.out.println("Navigate to " + url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getPort() {
|
||||||
|
String port = System.getProperty("local.server.port");
|
||||||
|
if (port == null || port.isEmpty()) {
|
||||||
|
port = "8080";
|
||||||
|
}
|
||||||
|
return port;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -41,12 +41,16 @@ public class AppConfig {
|
||||||
return (defaultNavBar != null) ? defaultNavBar : "Stirling PDF";
|
return (defaultNavBar != null) ? defaultNavBar : "Stirling PDF";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean(name = "enableAlphaFunctionality")
|
||||||
|
public boolean enableAlphaFunctionality() {
|
||||||
|
return applicationProperties.getSystem().getEnableAlphaFunctionality() != null ? applicationProperties.getSystem().getEnableAlphaFunctionality() : false;
|
||||||
|
}
|
||||||
|
|
||||||
@Bean(name = "rateLimit")
|
@Bean(name = "rateLimit")
|
||||||
public boolean rateLimit() {
|
public boolean rateLimit() {
|
||||||
String appName = System.getProperty("rateLimit");
|
String appName = System.getProperty("rateLimit");
|
||||||
if (appName == null)
|
if (appName == null)
|
||||||
appName = System.getenv("rateLimit");
|
appName = System.getenv("rateLimit");
|
||||||
System.out.println("rateLimit=" + appName);
|
|
||||||
return (appName != null) ? Boolean.valueOf(appName) : false;
|
return (appName != null) ? Boolean.valueOf(appName) : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,10 +30,14 @@ public class MetricsFilter extends OncePerRequestFilter {
|
||||||
|
|
||||||
// System.out.println("uri="+uri + ", method=" + request.getMethod() );
|
// System.out.println("uri="+uri + ", method=" + request.getMethod() );
|
||||||
// Ignore static resources
|
// Ignore static resources
|
||||||
if (!(uri.startsWith("/js") || uri.startsWith("api-docs") || uri.endsWith("robots.txt") || uri.startsWith("/images") || uri.endsWith(".png") || uri.endsWith(".ico") || uri.endsWith(".css") || uri.endsWith(".svg")|| uri.endsWith(".js") || uri.contains("swagger") || uri.startsWith("/api"))) {
|
if (!(uri.startsWith("/js") || uri.startsWith("/v1/api-docs") || uri.endsWith("robots.txt")
|
||||||
Counter counter = Counter.builder("http.requests")
|
|| uri.startsWith("/images") || uri.startsWith("/images")|| uri.endsWith(".png") || uri.endsWith(".ico") || uri.endsWith(".css") || uri.endsWith(".map")
|
||||||
.tag("uri", uri)
|
|| uri.endsWith(".svg") || uri.endsWith(".js") || uri.contains("swagger")
|
||||||
.tag("method", request.getMethod())
|
|| uri.startsWith("/api/v1/info") || uri.startsWith("/site.webmanifest") || uri.startsWith("/fonts") || uri.startsWith("/pdfjs") )) {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Counter counter = Counter.builder("http.requests").tag("uri", uri).tag("method", request.getMethod())
|
||||||
.register(meterRegistry);
|
.register(meterRegistry);
|
||||||
|
|
||||||
counter.increment();
|
counter.increment();
|
||||||
|
@ -43,6 +47,4 @@ public class MetricsFilter extends OncePerRequestFilter {
|
||||||
filterChain.doFilter(request, response);
|
filterChain.doFilter(request, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,27 +1,42 @@
|
||||||
package stirling.software.SPDF.config;
|
package stirling.software.SPDF.config;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
import io.swagger.v3.oas.models.Components;
|
import io.swagger.v3.oas.models.Components;
|
||||||
import io.swagger.v3.oas.models.OpenAPI;
|
import io.swagger.v3.oas.models.OpenAPI;
|
||||||
import io.swagger.v3.oas.models.info.Info;
|
import io.swagger.v3.oas.models.info.Info;
|
||||||
|
import io.swagger.v3.oas.models.security.SecurityRequirement;
|
||||||
|
import io.swagger.v3.oas.models.security.SecurityScheme;
|
||||||
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
public class OpenApiConfig {
|
public class OpenApiConfig {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public OpenAPI customOpenAPI() {
|
public OpenAPI customOpenAPI() {
|
||||||
String version = getClass().getPackage().getImplementationVersion();
|
String version = getClass().getPackage().getImplementationVersion();
|
||||||
if (version == null) {
|
if (version == null) {
|
||||||
|
|
||||||
version = "1.0.0"; // default version if all else fails
|
version = "1.0.0"; // default version if all else fails
|
||||||
|
}
|
||||||
|
|
||||||
|
SecurityScheme apiKeyScheme = new SecurityScheme().type(SecurityScheme.Type.APIKEY).in(SecurityScheme.In.HEADER)
|
||||||
|
.name("X-API-KEY");
|
||||||
|
if (!applicationProperties.getSecurity().getEnableLogin()) {
|
||||||
|
return new OpenAPI().components(new Components())
|
||||||
|
.info(new Info().title("Stirling PDF API").version(version).description(
|
||||||
|
"API documentation for all Server-Side processing.\nPlease note some functionality might be UI only and missing from here."));
|
||||||
|
} else {
|
||||||
|
return new OpenAPI().components(new Components().addSecuritySchemes("apiKey", apiKeyScheme))
|
||||||
|
.info(new Info().title("Stirling PDF API").version(version).description(
|
||||||
|
"API documentation for all Server-Side processing.\nPlease note some functionality might be UI only and missing from here."))
|
||||||
|
.addSecurityItem(new SecurityRequirement().addList("apiKey"));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new OpenAPI().components(new Components()).info(
|
|
||||||
new Info().title("Stirling PDF API").version(version).description("API documentation for all Server-Side processing.\nPlease note some functionality might be UI only and missing from here."));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -2,27 +2,46 @@ package stirling.software.SPDF.config.security;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.security.authentication.BadCredentialsException;
|
import org.springframework.security.authentication.BadCredentialsException;
|
||||||
import org.springframework.security.authentication.LockedException;
|
import org.springframework.security.authentication.LockedException;
|
||||||
import org.springframework.security.core.AuthenticationException;
|
import org.springframework.security.core.AuthenticationException;
|
||||||
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
|
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import jakarta.servlet.ServletException;
|
import jakarta.servlet.ServletException;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
@Component
|
||||||
public class CustomAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
|
public class CustomAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private final LoginAttemptService loginAttemptService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public CustomAuthenticationFailureHandler(LoginAttemptService loginAttemptService) {
|
||||||
|
this.loginAttemptService = loginAttemptService;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception)
|
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception)
|
||||||
throws IOException, ServletException {
|
throws IOException, ServletException {
|
||||||
String ip = request.getRemoteAddr();
|
String ip = request.getRemoteAddr();
|
||||||
logger.error("Failed login attempt from IP: " + ip);
|
logger.error("Failed login attempt from IP: " + ip);
|
||||||
|
|
||||||
|
String username = request.getParameter("username");
|
||||||
|
if(loginAttemptService.loginAttemptCheck(username)) {
|
||||||
|
setDefaultFailureUrl("/login?error=locked");
|
||||||
|
|
||||||
|
} else {
|
||||||
if (exception.getClass().isAssignableFrom(BadCredentialsException.class)) {
|
if (exception.getClass().isAssignableFrom(BadCredentialsException.class)) {
|
||||||
setDefaultFailureUrl("/login?error=badcredentials");
|
setDefaultFailureUrl("/login?error=badcredentials");
|
||||||
} else if (exception.getClass().isAssignableFrom(LockedException.class)) {
|
} else if (exception.getClass().isAssignableFrom(LockedException.class)) {
|
||||||
setDefaultFailureUrl("/login?error=locked");
|
setDefaultFailureUrl("/login?error=locked");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
super.onAuthenticationFailure(request, response, exception);
|
super.onAuthenticationFailure(request, response, exception);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
package stirling.software.SPDF.config.security;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
|
||||||
|
import org.springframework.security.web.savedrequest.SavedRequest;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import jakarta.servlet.ServletException;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import jakarta.servlet.http.HttpSession;
|
||||||
|
import stirling.software.SPDF.utils.RequestUriUtils;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class CustomAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private LoginAttemptService loginAttemptService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException {
|
||||||
|
String username = request.getParameter("username");
|
||||||
|
loginAttemptService.loginSucceeded(username);
|
||||||
|
|
||||||
|
|
||||||
|
// Get the saved request
|
||||||
|
HttpSession session = request.getSession(false);
|
||||||
|
SavedRequest savedRequest = session != null ? (SavedRequest) session.getAttribute("SPRING_SECURITY_SAVED_REQUEST") : null;
|
||||||
|
if (savedRequest != null && !RequestUriUtils.isStaticResource(savedRequest.getRedirectUrl())) {
|
||||||
|
// Redirect to the original destination
|
||||||
|
super.onAuthenticationSuccess(request, response, authentication);
|
||||||
|
} else {
|
||||||
|
// Redirect to the root URL (considering context path)
|
||||||
|
getRedirectStrategy().sendRedirect(request, response, "/");
|
||||||
|
}
|
||||||
|
|
||||||
|
//super.onAuthenticationSuccess(request, response, authentication);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -5,6 +5,7 @@ import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.security.authentication.LockedException;
|
||||||
import org.springframework.security.core.GrantedAuthority;
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||||
import org.springframework.security.core.userdetails.UserDetails;
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
@ -22,12 +23,18 @@ public class CustomUserDetailsService implements UserDetailsService {
|
||||||
@Autowired
|
@Autowired
|
||||||
private UserRepository userRepository;
|
private UserRepository userRepository;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private LoginAttemptService loginAttemptService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
||||||
User user = userRepository.findByUsername(username)
|
User user = userRepository.findByUsername(username)
|
||||||
.orElseThrow(() -> new UsernameNotFoundException("No user found with username: " + username));
|
.orElseThrow(() -> new UsernameNotFoundException("No user found with username: " + username));
|
||||||
|
|
||||||
|
if (loginAttemptService.isBlocked(username)) {
|
||||||
|
throw new LockedException("Your account has been locked due to too many failed login attempts.");
|
||||||
|
}
|
||||||
|
|
||||||
return new org.springframework.security.core.userdetails.User(
|
return new org.springframework.security.core.userdetails.User(
|
||||||
user.getUsername(),
|
user.getUsername(),
|
||||||
user.getPassword(),
|
user.getPassword(),
|
||||||
|
|
|
@ -15,6 +15,7 @@ import jakarta.servlet.ServletException;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import stirling.software.SPDF.model.User;
|
import stirling.software.SPDF.model.User;
|
||||||
|
import stirling.software.SPDF.utils.RequestUriUtils;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
public class FirstLoginFilter extends OncePerRequestFilter {
|
public class FirstLoginFilter extends OncePerRequestFilter {
|
||||||
|
@ -28,11 +29,7 @@ public class FirstLoginFilter extends OncePerRequestFilter {
|
||||||
String method = request.getMethod();
|
String method = request.getMethod();
|
||||||
String requestURI = request.getRequestURI();
|
String requestURI = request.getRequestURI();
|
||||||
// Check if the request is for static resources
|
// Check if the request is for static resources
|
||||||
boolean isStaticResource = requestURI.startsWith("/css/")
|
boolean isStaticResource = RequestUriUtils.isStaticResource(requestURI);
|
||||||
|| requestURI.startsWith("/js/")
|
|
||||||
|| requestURI.startsWith("/images/")
|
|
||||||
|| requestURI.startsWith("/public/")
|
|
||||||
|| requestURI.endsWith(".svg");
|
|
||||||
|
|
||||||
// If it's a static resource, just continue the filter chain and skip the logic below
|
// If it's a static resource, just continue the filter chain and skip the logic below
|
||||||
if (isStaticResource) {
|
if (isStaticResource) {
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
package stirling.software.SPDF.config.security;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
import jakarta.servlet.Filter;
|
||||||
|
import jakarta.servlet.FilterChain;
|
||||||
|
import jakarta.servlet.ServletException;
|
||||||
|
import jakarta.servlet.ServletRequest;
|
||||||
|
import jakarta.servlet.ServletResponse;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import stirling.software.SPDF.utils.RequestUriUtils;
|
||||||
|
|
||||||
|
public class IPRateLimitingFilter implements Filter {
|
||||||
|
|
||||||
|
private final ConcurrentHashMap<String, AtomicInteger> requestCounts = new ConcurrentHashMap<>();
|
||||||
|
private final ConcurrentHashMap<String, AtomicInteger> getCounts = new ConcurrentHashMap<>();
|
||||||
|
private final int maxRequests;
|
||||||
|
private final int maxGetRequests;
|
||||||
|
|
||||||
|
public IPRateLimitingFilter(int maxRequests, int maxGetRequests) {
|
||||||
|
this.maxRequests = maxRequests;
|
||||||
|
this.maxGetRequests = maxGetRequests;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
|
||||||
|
if (request instanceof HttpServletRequest) {
|
||||||
|
HttpServletRequest httpRequest = (HttpServletRequest) request;
|
||||||
|
String method = httpRequest.getMethod();
|
||||||
|
String requestURI = httpRequest.getRequestURI();
|
||||||
|
// Check if the request is for static resources
|
||||||
|
boolean isStaticResource = RequestUriUtils.isStaticResource(requestURI);
|
||||||
|
|
||||||
|
// If it's a static resource, just continue the filter chain and skip the logic below
|
||||||
|
if (isStaticResource) {
|
||||||
|
chain.doFilter(request, response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String clientIp = request.getRemoteAddr();
|
||||||
|
requestCounts.computeIfAbsent(clientIp, k -> new AtomicInteger(0));
|
||||||
|
if (!"GET".equalsIgnoreCase(method)) {
|
||||||
|
|
||||||
|
if (requestCounts.get(clientIp).incrementAndGet() > maxRequests) {
|
||||||
|
// Handle limit exceeded (e.g., send error response)
|
||||||
|
response.getWriter().write("Rate limit exceeded");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (requestCounts.get(clientIp).incrementAndGet() > maxGetRequests) {
|
||||||
|
// Handle limit exceeded (e.g., send error response)
|
||||||
|
response.getWriter().write("GET Rate limit exceeded");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
chain.doFilter(request, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void resetRequestCounts() {
|
||||||
|
requestCounts.clear();
|
||||||
|
getCounts.clear();
|
||||||
|
}
|
||||||
|
}
|
|
@ -37,8 +37,10 @@ public class InitialSecuritySetup {
|
||||||
initialPassword = "stirling";
|
initialPassword = "stirling";
|
||||||
userService.saveUser(initialUsername, initialPassword, Role.ADMIN.getRoleId(), true);
|
userService.saveUser(initialUsername, initialPassword, Role.ADMIN.getRoleId(), true);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if(!userService.usernameExists(Role.INTERNAL_API_USER.getRoleId())) {
|
||||||
|
userService.saveUser(Role.INTERNAL_API_USER.getRoleId(), UUID.randomUUID().toString(), Role.INTERNAL_API_USER.getRoleId());
|
||||||
|
userService.addApiKeyToUser(Role.INTERNAL_API_USER.getRoleId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
package stirling.software.SPDF.config.security;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import jakarta.annotation.PostConstruct;
|
||||||
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
|
import stirling.software.SPDF.model.AttemptCounter;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class LoginAttemptService {
|
||||||
|
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
|
private int MAX_ATTEMPTS;
|
||||||
|
private long ATTEMPT_INCREMENT_TIME;
|
||||||
|
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
public void init() {
|
||||||
|
MAX_ATTEMPTS = applicationProperties.getSecurity().getLoginAttemptCount();
|
||||||
|
ATTEMPT_INCREMENT_TIME = TimeUnit.MINUTES.toMillis(applicationProperties.getSecurity().getLoginResetTimeMinutes());
|
||||||
|
}
|
||||||
|
|
||||||
|
private final ConcurrentHashMap<String, AttemptCounter> attemptsCache = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
public void loginSucceeded(String key) {
|
||||||
|
attemptsCache.remove(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean loginAttemptCheck(String key) {
|
||||||
|
attemptsCache.compute(key, (k, attemptCounter) -> {
|
||||||
|
if (attemptCounter == null || attemptCounter.shouldReset(ATTEMPT_INCREMENT_TIME)) {
|
||||||
|
return new AttemptCounter();
|
||||||
|
} else {
|
||||||
|
attemptCounter.increment();
|
||||||
|
return attemptCounter;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return attemptsCache.get(key).getAttemptCount() >= MAX_ATTEMPTS;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public boolean isBlocked(String key) {
|
||||||
|
AttemptCounter attemptCounter = attemptsCache.get(key);
|
||||||
|
if (attemptCounter != null) {
|
||||||
|
return attemptCounter.getAttemptCount() >= MAX_ATTEMPTS;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
package stirling.software.SPDF.config.security;
|
||||||
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class RateLimitResetScheduler {
|
||||||
|
|
||||||
|
private final IPRateLimitingFilter rateLimitingFilter;
|
||||||
|
|
||||||
|
public RateLimitResetScheduler(IPRateLimitingFilter rateLimitingFilter) {
|
||||||
|
this.rateLimitingFilter = rateLimitingFilter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Scheduled(cron = "0 0 0 * * MON") // At 00:00 every Monday TODO: configurable
|
||||||
|
public void resetRateLimit() {
|
||||||
|
rateLimitingFilter.resetRequestCounts();
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,7 +6,7 @@ import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.context.annotation.Lazy;
|
import org.springframework.context.annotation.Lazy;
|
||||||
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
|
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
|
||||||
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
|
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
|
@ -15,12 +15,13 @@ import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||||
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
|
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
|
||||||
|
import org.springframework.security.web.savedrequest.NullRequestCache;
|
||||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||||
|
|
||||||
import stirling.software.SPDF.repository.JPATokenRepositoryImpl;
|
import stirling.software.SPDF.repository.JPATokenRepositoryImpl;
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableWebSecurity()
|
@EnableWebSecurity()
|
||||||
@EnableGlobalMethodSecurity(prePostEnabled = true)
|
@EnableMethodSecurity
|
||||||
public class SecurityConfiguration {
|
public class SecurityConfiguration {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
|
@ -41,6 +42,11 @@ public class SecurityConfiguration {
|
||||||
@Autowired
|
@Autowired
|
||||||
private UserAuthenticationFilter userAuthenticationFilter;
|
private UserAuthenticationFilter userAuthenticationFilter;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private LoginAttemptService loginAttemptService;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private FirstLoginFilter firstLoginFilter;
|
private FirstLoginFilter firstLoginFilter;
|
||||||
|
|
||||||
|
@ -51,13 +57,17 @@ public class SecurityConfiguration {
|
||||||
if(loginEnabledValue) {
|
if(loginEnabledValue) {
|
||||||
|
|
||||||
http.csrf(csrf -> csrf.disable());
|
http.csrf(csrf -> csrf.disable());
|
||||||
|
http.addFilterBefore(rateLimitingFilter(), UsernamePasswordAuthenticationFilter.class);
|
||||||
http.addFilterAfter(firstLoginFilter, UsernamePasswordAuthenticationFilter.class);
|
http.addFilterAfter(firstLoginFilter, UsernamePasswordAuthenticationFilter.class);
|
||||||
http
|
http
|
||||||
.formLogin(formLogin -> formLogin
|
.formLogin(formLogin -> formLogin
|
||||||
.loginPage("/login")
|
.loginPage("/login")
|
||||||
|
.successHandler(new CustomAuthenticationSuccessHandler())
|
||||||
.defaultSuccessUrl("/")
|
.defaultSuccessUrl("/")
|
||||||
.failureHandler(new CustomAuthenticationFailureHandler())
|
.failureHandler(new CustomAuthenticationFailureHandler(loginAttemptService))
|
||||||
.permitAll()
|
.permitAll()
|
||||||
|
).requestCache(requestCache -> requestCache
|
||||||
|
.requestCache(new NullRequestCache())
|
||||||
)
|
)
|
||||||
.logout(logout -> logout
|
.logout(logout -> logout
|
||||||
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
|
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
|
||||||
|
@ -70,8 +80,19 @@ public class SecurityConfiguration {
|
||||||
.tokenValiditySeconds(1209600) // 2 weeks
|
.tokenValiditySeconds(1209600) // 2 weeks
|
||||||
)
|
)
|
||||||
.authorizeHttpRequests(authz -> authz
|
.authorizeHttpRequests(authz -> authz
|
||||||
.requestMatchers(req -> req.getRequestURI().startsWith("/login") || req.getRequestURI().endsWith(".svg") || req.getRequestURI().startsWith("/register") || req.getRequestURI().startsWith("/error") || req.getRequestURI().startsWith("/images/") || req.getRequestURI().startsWith("/public/") || req.getRequestURI().startsWith("/css/") || req.getRequestURI().startsWith("/js/"))
|
.requestMatchers(req -> {
|
||||||
.permitAll()
|
String uri = req.getRequestURI();
|
||||||
|
String contextPath = req.getContextPath();
|
||||||
|
|
||||||
|
// Remove the context path from the URI
|
||||||
|
String trimmedUri = uri.startsWith(contextPath) ? uri.substring(contextPath.length()) : uri;
|
||||||
|
|
||||||
|
return trimmedUri.startsWith("/login") || trimmedUri.endsWith(".svg") ||
|
||||||
|
trimmedUri.startsWith("/register") || trimmedUri.startsWith("/error") ||
|
||||||
|
trimmedUri.startsWith("/images/") || trimmedUri.startsWith("/public/") ||
|
||||||
|
trimmedUri.startsWith("/css/") || trimmedUri.startsWith("/js/");
|
||||||
|
}
|
||||||
|
).permitAll()
|
||||||
.anyRequest().authenticated()
|
.anyRequest().authenticated()
|
||||||
)
|
)
|
||||||
.userDetailsService(userDetailsService)
|
.userDetailsService(userDetailsService)
|
||||||
|
@ -87,6 +108,15 @@ public class SecurityConfiguration {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public IPRateLimitingFilter rateLimitingFilter() {
|
||||||
|
int maxRequestsPerIp = 1000000; // Example limit TODO add config level
|
||||||
|
return new IPRateLimitingFilter(maxRequestsPerIp, maxRequestsPerIp);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public DaoAuthenticationProvider authenticationProvider() {
|
public DaoAuthenticationProvider authenticationProvider() {
|
||||||
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
|
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
|
||||||
|
|
|
@ -74,8 +74,10 @@ public class UserAuthenticationFilter extends OncePerRequestFilter {
|
||||||
// If we still don't have any authentication, deny the request
|
// If we still don't have any authentication, deny the request
|
||||||
if (authentication == null || !authentication.isAuthenticated()) {
|
if (authentication == null || !authentication.isAuthenticated()) {
|
||||||
String method = request.getMethod();
|
String method = request.getMethod();
|
||||||
if ("GET".equalsIgnoreCase(method) && !"/login".equals(requestURI)) {
|
String contextPath = request.getContextPath();
|
||||||
response.sendRedirect("/login"); // redirect to the login page
|
|
||||||
|
if ("GET".equalsIgnoreCase(method) && ! (contextPath + "/login").equals(requestURI)) {
|
||||||
|
response.sendRedirect(contextPath + "/login"); // redirect to the login page
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
response.setStatus(HttpStatus.UNAUTHORIZED.value());
|
response.setStatus(HttpStatus.UNAUTHORIZED.value());
|
||||||
|
@ -90,15 +92,17 @@ public class UserAuthenticationFilter extends OncePerRequestFilter {
|
||||||
@Override
|
@Override
|
||||||
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
|
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
|
||||||
String uri = request.getRequestURI();
|
String uri = request.getRequestURI();
|
||||||
|
String contextPath = request.getContextPath();
|
||||||
String[] permitAllPatterns = {
|
String[] permitAllPatterns = {
|
||||||
"/login",
|
contextPath + "/login",
|
||||||
"/register",
|
contextPath + "/register",
|
||||||
"/error",
|
contextPath + "/error",
|
||||||
"/images/",
|
contextPath + "/images/",
|
||||||
"/public/",
|
contextPath + "/public/",
|
||||||
"/css/",
|
contextPath + "/css/",
|
||||||
"/js/"
|
contextPath + "/js/",
|
||||||
|
contextPath + "/pdfjs/",
|
||||||
|
contextPath + "/site.webmanifest"
|
||||||
};
|
};
|
||||||
|
|
||||||
for (String pattern : permitAllPatterns) {
|
for (String pattern : permitAllPatterns) {
|
||||||
|
|
|
@ -16,11 +16,13 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import stirling.software.SPDF.controller.api.pipeline.UserServiceInterface;
|
||||||
import stirling.software.SPDF.model.Authority;
|
import stirling.software.SPDF.model.Authority;
|
||||||
|
import stirling.software.SPDF.model.Role;
|
||||||
import stirling.software.SPDF.model.User;
|
import stirling.software.SPDF.model.User;
|
||||||
import stirling.software.SPDF.repository.UserRepository;
|
import stirling.software.SPDF.repository.UserRepository;
|
||||||
@Service
|
@Service
|
||||||
public class UserService {
|
public class UserService implements UserServiceInterface{
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private UserRepository userRepository;
|
private UserRepository userRepository;
|
||||||
|
@ -136,6 +138,11 @@ public class UserService {
|
||||||
public void deleteUser(String username) {
|
public void deleteUser(String username) {
|
||||||
Optional<User> userOpt = userRepository.findByUsername(username);
|
Optional<User> userOpt = userRepository.findByUsername(username);
|
||||||
if (userOpt.isPresent()) {
|
if (userOpt.isPresent()) {
|
||||||
|
for (Authority authority : userOpt.get().getAuthorities()) {
|
||||||
|
if (authority.getAuthority().equals(Role.INTERNAL_API_USER.getRoleId())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
userRepository.delete(userOpt.get());
|
userRepository.delete(userOpt.get());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,8 +3,9 @@ import java.io.ByteArrayOutputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.ArrayList;
|
||||||
import org.apache.pdfbox.multipdf.Overlay;
|
import org.apache.pdfbox.multipdf.Overlay;
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
|
@ -33,6 +34,8 @@ public class PdfOverlayController {
|
||||||
|
|
||||||
MultipartFile[] overlayFiles = request.getOverlayFiles();
|
MultipartFile[] overlayFiles = request.getOverlayFiles();
|
||||||
File[] overlayPdfFiles = new File[overlayFiles.length];
|
File[] overlayPdfFiles = new File[overlayFiles.length];
|
||||||
|
List<File> tempFiles = new ArrayList<>(); // List to keep track of temporary files
|
||||||
|
|
||||||
try {
|
try {
|
||||||
for (int i = 0; i < overlayFiles.length; i++) {
|
for (int i = 0; i < overlayFiles.length; i++) {
|
||||||
overlayPdfFiles[i] = GeneralUtils.multipartToFile(overlayFiles[i]);
|
overlayPdfFiles[i] = GeneralUtils.multipartToFile(overlayFiles[i]);
|
||||||
|
@ -43,7 +46,7 @@ public class PdfOverlayController {
|
||||||
|
|
||||||
try (PDDocument basePdf = PDDocument.load(baseFile.getInputStream());
|
try (PDDocument basePdf = PDDocument.load(baseFile.getInputStream());
|
||||||
Overlay overlay = new Overlay()) {
|
Overlay overlay = new Overlay()) {
|
||||||
Map<Integer, String> overlayGuide = prepareOverlayGuide(basePdf.getNumberOfPages(), overlayPdfFiles, mode, counts);
|
Map<Integer, String> overlayGuide = prepareOverlayGuide(basePdf.getNumberOfPages(), overlayPdfFiles, mode, counts, tempFiles);
|
||||||
|
|
||||||
overlay.setInputPDF(basePdf);
|
overlay.setInputPDF(basePdf);
|
||||||
if (overlayPos == 0) {
|
if (overlayPos == 0) {
|
||||||
|
@ -61,16 +64,23 @@ public class PdfOverlayController {
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
for (File overlayPdfFile : overlayPdfFiles) {
|
for (File overlayPdfFile : overlayPdfFiles) {
|
||||||
if (overlayPdfFile != null) overlayPdfFile.delete();
|
if (overlayPdfFile != null) {
|
||||||
|
overlayPdfFile.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (File tempFile : tempFiles) { // Delete temporary files
|
||||||
|
if (tempFile != null) {
|
||||||
|
tempFile.delete();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<Integer, String> prepareOverlayGuide(int basePageCount, File[] overlayFiles, String mode, int[] counts) throws IOException {
|
private Map<Integer, String> prepareOverlayGuide(int basePageCount, File[] overlayFiles, String mode, int[] counts, List<File> tempFiles) throws IOException {
|
||||||
Map<Integer, String> overlayGuide = new HashMap<>();
|
Map<Integer, String> overlayGuide = new HashMap<>();
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case "SequentialOverlay":
|
case "SequentialOverlay":
|
||||||
sequentialOverlay(overlayGuide, overlayFiles, basePageCount);
|
sequentialOverlay(overlayGuide, overlayFiles, basePageCount, tempFiles);
|
||||||
break;
|
break;
|
||||||
case "InterleavedOverlay":
|
case "InterleavedOverlay":
|
||||||
interleavedOverlay(overlayGuide, overlayFiles, basePageCount);
|
interleavedOverlay(overlayGuide, overlayFiles, basePageCount);
|
||||||
|
@ -84,27 +94,57 @@ public class PdfOverlayController {
|
||||||
return overlayGuide;
|
return overlayGuide;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sequentialOverlay(Map<Integer, String> overlayGuide, File[] overlayFiles, int basePageCount) throws IOException {
|
private void sequentialOverlay(Map<Integer, String> overlayGuide, File[] overlayFiles, int basePageCount, List<File> tempFiles) throws IOException {
|
||||||
if (overlayFiles.length != 1 || basePageCount != PDDocument.load(overlayFiles[0]).getNumberOfPages()) {
|
int overlayFileIndex = 0;
|
||||||
throw new IllegalArgumentException("Overlay file count and base page count must match for sequential overlay.");
|
int pageCountInCurrentOverlay = 0;
|
||||||
|
|
||||||
|
for (int basePageIndex = 1; basePageIndex <= basePageCount; basePageIndex++) {
|
||||||
|
if (pageCountInCurrentOverlay == 0 || pageCountInCurrentOverlay >= getNumberOfPages(overlayFiles[overlayFileIndex])) {
|
||||||
|
pageCountInCurrentOverlay = 0;
|
||||||
|
overlayFileIndex = (overlayFileIndex + 1) % overlayFiles.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
File overlayFile = overlayFiles[0];
|
try (PDDocument overlayPdf = PDDocument.load(overlayFiles[overlayFileIndex])) {
|
||||||
try (PDDocument overlayPdf = PDDocument.load(overlayFile)) {
|
PDDocument singlePageDocument = new PDDocument();
|
||||||
for (int i = 1; i <= overlayPdf.getNumberOfPages(); i++) {
|
singlePageDocument.addPage(overlayPdf.getPage(pageCountInCurrentOverlay));
|
||||||
if (i > basePageCount) break;
|
File tempFile = File.createTempFile("overlay-page-", ".pdf");
|
||||||
overlayGuide.put(i, overlayFile.getAbsolutePath());
|
singlePageDocument.save(tempFile);
|
||||||
|
singlePageDocument.close();
|
||||||
|
|
||||||
|
overlayGuide.put(basePageIndex, tempFile.getAbsolutePath());
|
||||||
|
tempFiles.add(tempFile); // Keep track of the temporary file for cleanup
|
||||||
|
}
|
||||||
|
|
||||||
|
pageCountInCurrentOverlay++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int getNumberOfPages(File file) throws IOException {
|
||||||
|
try (PDDocument doc = PDDocument.load(file)) {
|
||||||
|
return doc.getNumberOfPages();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private void interleavedOverlay(Map<Integer, String> overlayGuide, File[] overlayFiles, int basePageCount) throws IOException {
|
private void interleavedOverlay(Map<Integer, String> overlayGuide, File[] overlayFiles, int basePageCount) throws IOException {
|
||||||
for (int i = 0; i < basePageCount; i++) {
|
for (int basePageIndex = 1; basePageIndex <= basePageCount; basePageIndex++) {
|
||||||
File overlayFile = overlayFiles[i % overlayFiles.length];
|
File overlayFile = overlayFiles[(basePageIndex - 1) % overlayFiles.length];
|
||||||
overlayGuide.put(i + 1, overlayFile.getAbsolutePath());
|
|
||||||
|
// Load the overlay document to check its page count
|
||||||
|
try (PDDocument overlayPdf = PDDocument.load(overlayFile)) {
|
||||||
|
int overlayPageCount = overlayPdf.getNumberOfPages();
|
||||||
|
if ((basePageIndex - 1) % overlayPageCount < overlayPageCount) {
|
||||||
|
overlayGuide.put(basePageIndex, overlayFile.getAbsolutePath());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private void fixedRepeatOverlay(Map<Integer, String> overlayGuide, File[] overlayFiles, int[] counts, int basePageCount) throws IOException {
|
private void fixedRepeatOverlay(Map<Integer, String> overlayGuide, File[] overlayFiles, int[] counts, int basePageCount) throws IOException {
|
||||||
if (overlayFiles.length != counts.length) {
|
if (overlayFiles.length != counts.length) {
|
||||||
|
@ -114,12 +154,20 @@ public class PdfOverlayController {
|
||||||
for (int i = 0; i < overlayFiles.length; i++) {
|
for (int i = 0; i < overlayFiles.length; i++) {
|
||||||
File overlayFile = overlayFiles[i];
|
File overlayFile = overlayFiles[i];
|
||||||
int repeatCount = counts[i];
|
int repeatCount = counts[i];
|
||||||
|
|
||||||
|
// Load the overlay document to check its page count
|
||||||
|
try (PDDocument overlayPdf = PDDocument.load(overlayFile)) {
|
||||||
|
int overlayPageCount = overlayPdf.getNumberOfPages();
|
||||||
for (int j = 0; j < repeatCount; j++) {
|
for (int j = 0; j < repeatCount; j++) {
|
||||||
|
for (int page = 0; page < overlayPageCount; page++) {
|
||||||
if (currentPage > basePageCount) break;
|
if (currentPage > basePageCount) break;
|
||||||
overlayGuide.put(currentPage++, overlayFile.getAbsolutePath());
|
overlayGuide.put(currentPage++, overlayFile.getAbsolutePath());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// Additional classes like OverlayPdfsRequest, WebResponseUtils, etc. are assumed to be defined elsewhere.
|
// Additional classes like OverlayPdfsRequest, WebResponseUtils, etc. are assumed to be defined elsewhere.
|
||||||
|
|
|
@ -29,12 +29,12 @@ import stirling.software.SPDF.model.api.SplitPdfBySectionsRequest;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v1/general")
|
@RequestMapping("/api/v1/general")
|
||||||
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
@Tag(name = "General", description = "General APIs")
|
||||||
public class SplitPdfBySectionsController {
|
public class SplitPdfBySectionsController {
|
||||||
|
|
||||||
|
|
||||||
@PostMapping(value = "/split-pdf-by-sections", consumes = "multipart/form-data")
|
@PostMapping(value = "/split-pdf-by-sections", consumes = "multipart/form-data")
|
||||||
@Operation(summary = "Split PDF pages into smaller sections", description = "Split each page of a PDF into smaller sections based on the user's choice (halves, thirds, quarters, etc.), both vertically and horizontally. Input: PDF, Split Parameters. Output: ZIP containing split documents.")
|
@Operation(summary = "Split PDF pages into smaller sections", description = "Split each page of a PDF into smaller sections based on the user's choice (halves, thirds, quarters, etc.), both vertically and horizontally. Input:PDF Output:ZIP-PDF Type:SISO")
|
||||||
public ResponseEntity<byte[]> splitPdf(@ModelAttribute SplitPdfBySectionsRequest request) throws Exception {
|
public ResponseEntity<byte[]> splitPdf(@ModelAttribute SplitPdfBySectionsRequest request) throws Exception {
|
||||||
List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<>();
|
List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<>();
|
||||||
|
|
||||||
|
|
|
@ -26,13 +26,13 @@ import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v1/general")
|
@RequestMapping("/api/v1/general")
|
||||||
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
@Tag(name = "General", description = "General APIs")
|
||||||
public class SplitPdfBySizeController {
|
public class SplitPdfBySizeController {
|
||||||
|
|
||||||
|
|
||||||
@PostMapping(value = "/split-by-size-or-count", consumes = "multipart/form-data")
|
@PostMapping(value = "/split-by-size-or-count", consumes = "multipart/form-data")
|
||||||
@Operation(summary = "Auto split PDF pages into separate documents based on size or count", description = "split PDF into multiple paged documents based on size/count, ie if 20 pages and split into 5, it does 5 documents each 4 pages\r\n"
|
@Operation(summary = "Auto split PDF pages into separate documents based on size or count", description = "split PDF into multiple paged documents based on size/count, ie if 20 pages and split into 5, it does 5 documents each 4 pages\r\n"
|
||||||
+ " if 10MB and each page is 1MB and you enter 2MB then 5 docs each 2MB (rounded so that it accepts 1.9MB but not 2.1MB) Input:PDF Output:ZIP Type:SIMO")
|
+ " if 10MB and each page is 1MB and you enter 2MB then 5 docs each 2MB (rounded so that it accepts 1.9MB but not 2.1MB) Input:PDF Output:ZIP-PDF Type:SISO")
|
||||||
public ResponseEntity<byte[]> autoSplitPdf(@ModelAttribute SplitPdfBySizeOrCountRequest request) throws Exception {
|
public ResponseEntity<byte[]> autoSplitPdf(@ModelAttribute SplitPdfBySizeOrCountRequest request) throws Exception {
|
||||||
List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<ByteArrayOutputStream>();
|
List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<ByteArrayOutputStream>();
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,7 @@ import org.springframework.web.servlet.view.RedirectView;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import stirling.software.SPDF.config.security.UserService;
|
import stirling.software.SPDF.config.security.UserService;
|
||||||
|
import stirling.software.SPDF.model.Role;
|
||||||
import stirling.software.SPDF.model.User;
|
import stirling.software.SPDF.model.User;
|
||||||
|
|
||||||
@Controller
|
@Controller
|
||||||
|
@ -32,6 +33,7 @@ public class UserController {
|
||||||
@Autowired
|
@Autowired
|
||||||
private UserService userService;
|
private UserService userService;
|
||||||
|
|
||||||
|
@PreAuthorize("!hasAuthority('ROLE_DEMO_USER')")
|
||||||
@PostMapping("/register")
|
@PostMapping("/register")
|
||||||
public String register(@RequestParam String username, @RequestParam String password, Model model) {
|
public String register(@RequestParam String username, @RequestParam String password, Model model) {
|
||||||
if(userService.usernameExists(username)) {
|
if(userService.usernameExists(username)) {
|
||||||
|
@ -43,6 +45,7 @@ public class UserController {
|
||||||
return "redirect:/login?registered=true";
|
return "redirect:/login?registered=true";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("!hasAuthority('ROLE_DEMO_USER')")
|
||||||
@PostMapping("/change-username-and-password")
|
@PostMapping("/change-username-and-password")
|
||||||
public RedirectView changeUsernameAndPassword(Principal principal,
|
public RedirectView changeUsernameAndPassword(Principal principal,
|
||||||
@RequestParam String currentPassword,
|
@RequestParam String currentPassword,
|
||||||
|
@ -85,7 +88,7 @@ public class UserController {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@PreAuthorize("!hasAuthority('ROLE_DEMO_USER')")
|
||||||
@PostMapping("/change-username")
|
@PostMapping("/change-username")
|
||||||
public RedirectView changeUsername(Principal principal,
|
public RedirectView changeUsername(Principal principal,
|
||||||
@RequestParam String currentPassword,
|
@RequestParam String currentPassword,
|
||||||
|
@ -123,6 +126,7 @@ public class UserController {
|
||||||
return new RedirectView("/login?messageType=credsUpdated");
|
return new RedirectView("/login?messageType=credsUpdated");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("!hasAuthority('ROLE_DEMO_USER')")
|
||||||
@PostMapping("/change-password")
|
@PostMapping("/change-password")
|
||||||
public RedirectView changePassword(Principal principal,
|
public RedirectView changePassword(Principal principal,
|
||||||
@RequestParam String currentPassword,
|
@RequestParam String currentPassword,
|
||||||
|
@ -154,7 +158,7 @@ public class UserController {
|
||||||
return new RedirectView("/login?messageType=credsUpdated");
|
return new RedirectView("/login?messageType=credsUpdated");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("!hasAuthority('ROLE_DEMO_USER')")
|
||||||
@PostMapping("/updateUserSettings")
|
@PostMapping("/updateUserSettings")
|
||||||
public String updateUserSettings(HttpServletRequest request, Principal principal) {
|
public String updateUserSettings(HttpServletRequest request, Principal principal) {
|
||||||
Map<String, String[]> paramMap = request.getParameterMap();
|
Map<String, String[]> paramMap = request.getParameterMap();
|
||||||
|
@ -182,6 +186,18 @@ public class UserController {
|
||||||
if(userService.usernameExists(username)) {
|
if(userService.usernameExists(username)) {
|
||||||
return new RedirectView("/addUsers?messageType=usernameExists");
|
return new RedirectView("/addUsers?messageType=usernameExists");
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
|
// Validate the role
|
||||||
|
Role roleEnum = Role.fromString(role);
|
||||||
|
if (roleEnum == Role.INTERNAL_API_USER) {
|
||||||
|
// If the role is INTERNAL_API_USER, reject the request
|
||||||
|
return new RedirectView("/addUsers?messageType=invalidRole");
|
||||||
|
}
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
// If the role ID is not valid, redirect with an error message
|
||||||
|
return new RedirectView("/addUsers?messageType=invalidRole");
|
||||||
|
}
|
||||||
|
|
||||||
userService.saveUser(username, password, role, forceChange);
|
userService.saveUser(username, password, role, forceChange);
|
||||||
return new RedirectView("/addUsers"); // Redirect to account page after adding the user
|
return new RedirectView("/addUsers"); // Redirect to account page after adding the user
|
||||||
}
|
}
|
||||||
|
@ -203,6 +219,7 @@ public class UserController {
|
||||||
return "redirect:/addUsers";
|
return "redirect:/addUsers";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("!hasAuthority('ROLE_DEMO_USER')")
|
||||||
@PostMapping("/get-api-key")
|
@PostMapping("/get-api-key")
|
||||||
public ResponseEntity<String> getApiKey(Principal principal) {
|
public ResponseEntity<String> getApiKey(Principal principal) {
|
||||||
if (principal == null) {
|
if (principal == null) {
|
||||||
|
@ -216,6 +233,7 @@ public class UserController {
|
||||||
return ResponseEntity.ok(apiKey);
|
return ResponseEntity.ok(apiKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("!hasAuthority('ROLE_DEMO_USER')")
|
||||||
@PostMapping("/update-api-key")
|
@PostMapping("/update-api-key")
|
||||||
public ResponseEntity<String> updateApiKey(Principal principal) {
|
public ResponseEntity<String> updateApiKey(Principal principal) {
|
||||||
if (principal == null) {
|
if (principal == null) {
|
||||||
|
|
|
@ -1,130 +0,0 @@
|
||||||
package stirling.software.SPDF.controller.api.converters;
|
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.StringReader;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.zip.ZipEntry;
|
|
||||||
import java.util.zip.ZipInputStream;
|
|
||||||
|
|
||||||
import javax.xml.parsers.DocumentBuilder;
|
|
||||||
import javax.xml.parsers.DocumentBuilderFactory;
|
|
||||||
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
|
||||||
import org.w3c.dom.Document;
|
|
||||||
import org.w3c.dom.Element;
|
|
||||||
import org.w3c.dom.NodeList;
|
|
||||||
import org.xml.sax.InputSource;
|
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Hidden;
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
|
||||||
import stirling.software.SPDF.model.api.GeneralFile;
|
|
||||||
import stirling.software.SPDF.utils.FileToPdf;
|
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
|
||||||
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/api/v1/convert")
|
|
||||||
@Tag(name = "Convert", description = "Convert APIs")
|
|
||||||
public class ConvertEpubToPdf {
|
|
||||||
//TODO
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/epub-to-single-pdf")
|
|
||||||
@Hidden
|
|
||||||
@Operation(
|
|
||||||
summary = "Convert an EPUB file to a single PDF",
|
|
||||||
description = "This endpoint takes an EPUB file input and converts it to a single PDF."
|
|
||||||
)
|
|
||||||
public ResponseEntity<byte[]> epubToSinglePdf(
|
|
||||||
@ModelAttribute GeneralFile request)
|
|
||||||
throws Exception {
|
|
||||||
MultipartFile fileInput = request.getFileInput();
|
|
||||||
if (fileInput == null) {
|
|
||||||
throw new IllegalArgumentException("Please provide an EPUB file for conversion.");
|
|
||||||
}
|
|
||||||
|
|
||||||
String originalFilename = fileInput.getOriginalFilename();
|
|
||||||
if (originalFilename == null || !originalFilename.endsWith(".epub")) {
|
|
||||||
throw new IllegalArgumentException("File must be in .epub format.");
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, byte[]> epubContents = extractEpubContent(fileInput);
|
|
||||||
List<String> htmlFilesOrder = getHtmlFilesOrderFromOpf(epubContents);
|
|
||||||
|
|
||||||
List<byte[]> individualPdfs = new ArrayList<>();
|
|
||||||
|
|
||||||
for (String htmlFile : htmlFilesOrder) {
|
|
||||||
byte[] htmlContent = epubContents.get(htmlFile);
|
|
||||||
byte[] pdfBytes = FileToPdf.convertHtmlToPdf(htmlContent, htmlFile.replace(".html", ".pdf"));
|
|
||||||
individualPdfs.add(pdfBytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pseudo-code to merge individual PDFs into one.
|
|
||||||
byte[] mergedPdfBytes = mergeMultiplePdfsIntoOne(individualPdfs);
|
|
||||||
|
|
||||||
return WebResponseUtils.bytesToWebResponse(mergedPdfBytes, originalFilename.replace(".epub", ".pdf"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assuming a pseudo-code function that merges multiple PDFs into one.
|
|
||||||
private byte[] mergeMultiplePdfsIntoOne(List<byte[]> individualPdfs) {
|
|
||||||
// You can use a library such as PDFBox to perform the merging here.
|
|
||||||
// Return the byte[] of the merged PDF.
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Map<String, byte[]> extractEpubContent(MultipartFile fileInput) throws IOException {
|
|
||||||
Map<String, byte[]> contentMap = new HashMap<>();
|
|
||||||
|
|
||||||
try (ZipInputStream zis = new ZipInputStream(fileInput.getInputStream())) {
|
|
||||||
ZipEntry zipEntry = zis.getNextEntry();
|
|
||||||
while (zipEntry != null) {
|
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
|
||||||
byte[] buffer = new byte[1024];
|
|
||||||
int read = 0;
|
|
||||||
while ((read = zis.read(buffer)) != -1) {
|
|
||||||
baos.write(buffer, 0, read);
|
|
||||||
}
|
|
||||||
contentMap.put(zipEntry.getName(), baos.toByteArray());
|
|
||||||
zipEntry = zis.getNextEntry();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return contentMap;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<String> getHtmlFilesOrderFromOpf(Map<String, byte[]> epubContents) throws Exception {
|
|
||||||
String opfContent = new String(epubContents.get("OEBPS/content.opf")); // Adjusting for given path
|
|
||||||
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
|
|
||||||
DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
|
|
||||||
InputSource is = new InputSource(new StringReader(opfContent));
|
|
||||||
Document doc = dBuilder.parse(is);
|
|
||||||
|
|
||||||
NodeList itemRefs = doc.getElementsByTagName("itemref");
|
|
||||||
List<String> htmlFilesOrder = new ArrayList<>();
|
|
||||||
|
|
||||||
for (int i = 0; i < itemRefs.getLength(); i++) {
|
|
||||||
Element itemRef = (Element) itemRefs.item(i);
|
|
||||||
String idref = itemRef.getAttribute("idref");
|
|
||||||
|
|
||||||
NodeList items = doc.getElementsByTagName("item");
|
|
||||||
for (int j = 0; j < items.getLength(); j++) {
|
|
||||||
Element item = (Element) items.item(j);
|
|
||||||
if (idref.equals(item.getAttribute("id"))) {
|
|
||||||
htmlFilesOrder.add(item.getAttribute("href")); // Fetching the actual href
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return htmlFilesOrder;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
|
@ -28,7 +28,7 @@ public class ConvertWebsiteToPDF {
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/url/pdf")
|
@PostMapping(consumes = "multipart/form-data", value = "/url/pdf")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Convert a URL to a PDF",
|
summary = "Convert a URL to a PDF",
|
||||||
description = "This endpoint fetches content from a URL and converts it to a PDF format."
|
description = "This endpoint fetches content from a URL and converts it to a PDF format. Input:N/A Output:PDF Type:SISO"
|
||||||
)
|
)
|
||||||
public ResponseEntity<byte[]> urlToPdf(@ModelAttribute UrlToPdfRequest request) throws IOException, InterruptedException {
|
public ResponseEntity<byte[]> urlToPdf(@ModelAttribute UrlToPdfRequest request) throws IOException, InterruptedException {
|
||||||
String URL = request.getUrlInput();
|
String URL = request.getUrlInput();
|
||||||
|
|
|
@ -28,12 +28,12 @@ import stirling.software.SPDF.model.api.extract.PDFFilePage;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v1/convert")
|
@RequestMapping("/api/v1/convert")
|
||||||
@Tag(name = "General", description = "General APIs")
|
@Tag(name = "Convert", description = "Convert APIs")
|
||||||
public class ExtractController {
|
public class ExtractController {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(CropController.class);
|
private static final Logger logger = LoggerFactory.getLogger(CropController.class);
|
||||||
|
|
||||||
@PostMapping(value = "/pdf-to-csv", consumes = "multipart/form-data")
|
@PostMapping(value = "/pdf/csv", consumes = "multipart/form-data")
|
||||||
@Operation(summary = "Extracts a PDF document to csv", description = "This operation takes an input PDF file and returns CSV file of whole page. Input:PDF Output:CSV Type:SISO")
|
@Operation(summary = "Extracts a PDF document to csv", description = "This operation takes an input PDF file and returns CSV file of whole page. Input:PDF Output:CSV Type:SISO")
|
||||||
public ResponseEntity<String> PdfToCsv(@ModelAttribute PDFFilePage form)
|
public ResponseEntity<String> PdfToCsv(@ModelAttribute PDFFilePage form)
|
||||||
throws Exception {
|
throws Exception {
|
||||||
|
|
|
@ -43,7 +43,7 @@ public class AutoSplitPdfController {
|
||||||
private static final String QR_CONTENT = "https://github.com/Frooodle/Stirling-PDF";
|
private static final String QR_CONTENT = "https://github.com/Frooodle/Stirling-PDF";
|
||||||
|
|
||||||
@PostMapping(value = "/auto-split-pdf", consumes = "multipart/form-data")
|
@PostMapping(value = "/auto-split-pdf", consumes = "multipart/form-data")
|
||||||
@Operation(summary = "Auto split PDF pages into separate documents", description = "This endpoint accepts a PDF file, scans each page for a specific QR code, and splits the document at the QR code boundaries. The output is a zip file containing each separate PDF document. Input:PDF Output:ZIP Type:SISO")
|
@Operation(summary = "Auto split PDF pages into separate documents", description = "This endpoint accepts a PDF file, scans each page for a specific QR code, and splits the document at the QR code boundaries. The output is a zip file containing each separate PDF document. Input:PDF Output:ZIP-PDF Type:SISO")
|
||||||
public ResponseEntity<byte[]> autoSplitPdf(@ModelAttribute AutoSplitPdfRequest request) throws IOException {
|
public ResponseEntity<byte[]> autoSplitPdf(@ModelAttribute AutoSplitPdfRequest request) throws IOException {
|
||||||
MultipartFile file = request.getFileInput();
|
MultipartFile file = request.getFileInput();
|
||||||
boolean duplexMode = request.isDuplexMode();
|
boolean duplexMode = request.isDuplexMode();
|
||||||
|
|
|
@ -15,6 +15,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import stirling.software.SPDF.model.api.PDFFile;
|
import stirling.software.SPDF.model.api.PDFFile;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
@ -25,6 +26,7 @@ public class ShowJavascript {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(ShowJavascript.class);
|
private static final Logger logger = LoggerFactory.getLogger(ShowJavascript.class);
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/show-javascript")
|
@PostMapping(consumes = "multipart/form-data", value = "/show-javascript")
|
||||||
|
@Operation(summary = "Grabs all JS from a PDF and returns a single JS file with all code", description = "desc. Input:PDF Output:JS Type:SISO")
|
||||||
public ResponseEntity<byte[]> extractHeader(@ModelAttribute PDFFile request) throws Exception {
|
public ResponseEntity<byte[]> extractHeader(@ModelAttribute PDFFile request) throws Exception {
|
||||||
MultipartFile inputFile = request.getFileInput();
|
MultipartFile inputFile = request.getFileInput();
|
||||||
String script = "";
|
String script = "";
|
||||||
|
|
|
@ -0,0 +1,123 @@
|
||||||
|
package stirling.software.SPDF.controller.api.pipeline;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.http.HttpEntity;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.HttpMethod;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
|
import jakarta.servlet.ServletContext;
|
||||||
|
|
||||||
|
import stirling.software.SPDF.SPdfApplication;
|
||||||
|
import stirling.software.SPDF.model.ApiEndpoint;
|
||||||
|
import stirling.software.SPDF.model.Role;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
@Service
|
||||||
|
public class ApiDocService {
|
||||||
|
|
||||||
|
private final Map<String, ApiEndpoint> apiDocumentation = new HashMap<>();
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(ApiDocService.class);
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ServletContext servletContext;
|
||||||
|
|
||||||
|
private String getApiDocsUrl() {
|
||||||
|
String contextPath = servletContext.getContextPath();
|
||||||
|
String port = SPdfApplication.getPort();
|
||||||
|
|
||||||
|
return "http://localhost:"+ port + contextPath + "/v1/api-docs";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Autowired(required=false)
|
||||||
|
private UserServiceInterface userService;
|
||||||
|
|
||||||
|
private String getApiKeyForUser() {
|
||||||
|
if(userService == null)
|
||||||
|
return "";
|
||||||
|
return userService.getApiKeyForUser(Role.INTERNAL_API_USER.getRoleId());
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonNode apiDocsJsonRootNode;
|
||||||
|
|
||||||
|
|
||||||
|
//@EventListener(ApplicationReadyEvent.class)
|
||||||
|
private synchronized void loadApiDocumentation() {
|
||||||
|
String apiDocsJson = "";
|
||||||
|
try {
|
||||||
|
HttpHeaders headers = new HttpHeaders();
|
||||||
|
String apiKey = getApiKeyForUser();
|
||||||
|
if (!apiKey.isEmpty()) {
|
||||||
|
headers.set("X-API-KEY", apiKey);
|
||||||
|
}
|
||||||
|
HttpEntity<String> entity = new HttpEntity<>(headers);
|
||||||
|
|
||||||
|
RestTemplate restTemplate = new RestTemplate();
|
||||||
|
ResponseEntity<String> response = restTemplate.exchange(getApiDocsUrl(), HttpMethod.GET, entity, String.class);
|
||||||
|
apiDocsJson = response.getBody();
|
||||||
|
|
||||||
|
ObjectMapper mapper = new ObjectMapper();
|
||||||
|
apiDocsJsonRootNode = mapper.readTree(apiDocsJson);
|
||||||
|
|
||||||
|
JsonNode paths = apiDocsJsonRootNode.path("paths");
|
||||||
|
paths.fields().forEachRemaining(entry -> {
|
||||||
|
String path = entry.getKey();
|
||||||
|
JsonNode pathNode = entry.getValue();
|
||||||
|
if (pathNode.has("post")) {
|
||||||
|
JsonNode postNode = pathNode.get("post");
|
||||||
|
ApiEndpoint endpoint = new ApiEndpoint(path, postNode);
|
||||||
|
apiDocumentation.put(path, endpoint);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Handle exceptions
|
||||||
|
logger.error("Error grabbing swagger doc, body result {}", apiDocsJson);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isValidOperation(String operationName, Map<String, Object> parameters) {
|
||||||
|
if(apiDocumentation.size() == 0) {
|
||||||
|
loadApiDocumentation();
|
||||||
|
}
|
||||||
|
if (!apiDocumentation.containsKey(operationName)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ApiEndpoint endpoint = apiDocumentation.get(operationName);
|
||||||
|
return endpoint.areParametersValid(parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isMultiInput(String operationName) {
|
||||||
|
if(apiDocsJsonRootNode == null || apiDocumentation.size() == 0) {
|
||||||
|
loadApiDocumentation();
|
||||||
|
}
|
||||||
|
if (!apiDocumentation.containsKey(operationName)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ApiEndpoint endpoint = apiDocumentation.get(operationName);
|
||||||
|
String description = endpoint.getDescription();
|
||||||
|
|
||||||
|
Pattern pattern = Pattern.compile("Type:(\\w+)");
|
||||||
|
Matcher matcher = pattern.matcher(description);
|
||||||
|
if (matcher.find()) {
|
||||||
|
String type = matcher.group(1);
|
||||||
|
return type.startsWith("MI");
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Model class for API Endpoint
|
||||||
|
|
|
@ -1,56 +1,31 @@
|
||||||
package stirling.software.SPDF.controller.api.pipeline;
|
package stirling.software.SPDF.controller.api.pipeline;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.io.PrintStream;
|
|
||||||
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.ArrayList;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
import java.util.zip.ZipEntry;
|
import java.util.zip.ZipEntry;
|
||||||
import java.util.zip.ZipInputStream;
|
|
||||||
import java.util.zip.ZipOutputStream;
|
import java.util.zip.ZipOutputStream;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.core.io.ByteArrayResource;
|
|
||||||
import org.springframework.core.io.Resource;
|
import org.springframework.core.io.Resource;
|
||||||
import org.springframework.http.HttpEntity;
|
|
||||||
import org.springframework.http.HttpHeaders;
|
|
||||||
import org.springframework.http.HttpMethod;
|
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.scheduling.annotation.Scheduled;
|
|
||||||
import org.springframework.util.LinkedMultiValueMap;
|
|
||||||
import org.springframework.util.MultiValueMap;
|
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.web.client.RestTemplate;
|
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
import com.fasterxml.jackson.databind.JsonMappingException;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
import stirling.software.SPDF.model.PipelineConfig;
|
import stirling.software.SPDF.model.PipelineConfig;
|
||||||
import stirling.software.SPDF.model.PipelineOperation;
|
|
||||||
import stirling.software.SPDF.model.api.HandleDataRequest;
|
import stirling.software.SPDF.model.api.HandleDataRequest;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
|
@ -60,373 +35,39 @@ import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
public class PipelineController {
|
public class PipelineController {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(PipelineController.class);
|
private static final Logger logger = LoggerFactory.getLogger(PipelineController.class);
|
||||||
@Autowired
|
|
||||||
private ObjectMapper objectMapper;
|
|
||||||
|
|
||||||
final String jsonFileName = "pipelineConfig.json";
|
|
||||||
final String watchedFoldersDir = "./pipeline/watchedFolders/";
|
final String watchedFoldersDir = "./pipeline/watchedFolders/";
|
||||||
final String finishedFoldersDir = "./pipeline/finishedFolders/";
|
final String finishedFoldersDir = "./pipeline/finishedFolders/";
|
||||||
|
@Autowired
|
||||||
|
PipelineProcessor processor;
|
||||||
|
|
||||||
@Scheduled(fixedRate = 25000)
|
|
||||||
public void scanFolders() {
|
|
||||||
logger.info("Scanning folders...");
|
|
||||||
Path watchedFolderPath = Paths.get(watchedFoldersDir);
|
|
||||||
if (!Files.exists(watchedFolderPath)) {
|
|
||||||
try {
|
|
||||||
Files.createDirectories(watchedFolderPath);
|
|
||||||
logger.info("Created directory: {}", watchedFolderPath);
|
|
||||||
} catch (IOException e) {
|
|
||||||
logger.error("Error creating directory: {}", watchedFolderPath, e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try (Stream<Path> paths = Files.walk(watchedFolderPath)) {
|
|
||||||
paths.filter(Files::isDirectory).forEach(t -> {
|
|
||||||
try {
|
|
||||||
if (!t.equals(watchedFolderPath) && !t.endsWith("processing")) {
|
|
||||||
handleDirectory(t);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.error("Error handling directory: {}", t, e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.error("Error walking through directory: {}", watchedFolderPath, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
ApplicationProperties applicationProperties;
|
ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ObjectMapper objectMapper;
|
||||||
|
|
||||||
private void handleDirectory(Path dir) throws Exception {
|
|
||||||
logger.info("Handling directory: {}", dir);
|
|
||||||
Path jsonFile = dir.resolve(jsonFileName);
|
|
||||||
Path processingDir = dir.resolve("processing"); // Directory to move files during processing
|
|
||||||
if (!Files.exists(processingDir)) {
|
|
||||||
Files.createDirectory(processingDir);
|
|
||||||
logger.info("Created processing directory: {}", processingDir);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Files.exists(jsonFile)) {
|
|
||||||
// Read JSON file
|
|
||||||
String jsonString;
|
|
||||||
try {
|
|
||||||
jsonString = new String(Files.readAllBytes(jsonFile));
|
|
||||||
logger.info("Read JSON file: {}", jsonFile);
|
|
||||||
} catch (IOException e) {
|
|
||||||
logger.error("Error reading JSON file: {}", jsonFile, e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decode JSON to PipelineConfig
|
|
||||||
PipelineConfig config;
|
|
||||||
try {
|
|
||||||
config = objectMapper.readValue(jsonString, PipelineConfig.class);
|
|
||||||
// Assuming your PipelineConfig class has getters for all necessary fields, you
|
|
||||||
// can perform checks here
|
|
||||||
if (config.getOperations() == null || config.getOutputDir() == null || config.getName() == null) {
|
|
||||||
throw new IOException("Invalid JSON format");
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
logger.error("Error parsing PipelineConfig: {}", jsonString, e);
|
|
||||||
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 -> !Files.isDirectory(path)) // exclude directories
|
|
||||||
.filter(path -> !path.equals(jsonFile)) // exclude 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) };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepare the files for processing
|
|
||||||
List<File> filesToProcess = new ArrayList<>();
|
|
||||||
for (File file : files) {
|
|
||||||
logger.info(file.getName());
|
|
||||||
logger.info("{} to {}",file.toPath(), processingDir.resolve(file.getName()));
|
|
||||||
Files.move(file.toPath(), processingDir.resolve(file.getName()));
|
|
||||||
filesToProcess.add(processingDir.resolve(file.getName()).toFile());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process the files
|
|
||||||
try {
|
|
||||||
List<Resource> resources = handleFiles(filesToProcess.toArray(new File[0]), jsonString);
|
|
||||||
|
|
||||||
if(resources == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Move resultant files and rename them as per config in JSON file
|
|
||||||
for (Resource resource : resources) {
|
|
||||||
String resourceName = resource.getFilename();
|
|
||||||
String baseName = resourceName.substring(0, resourceName.lastIndexOf("."));
|
|
||||||
String extension = resourceName.substring(resourceName.lastIndexOf(".")+1);
|
|
||||||
|
|
||||||
String outputFileName = config.getOutputPattern().replace("{filename}", baseName);
|
|
||||||
|
|
||||||
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));
|
|
||||||
|
|
||||||
outputFileName += "." + extension;
|
|
||||||
// {filename} {folder} {date} {tmime} {pipeline}
|
|
||||||
String outputDir = config.getOutputDir();
|
|
||||||
|
|
||||||
String outputFolder = applicationProperties.getAutoPipeline().getOutputFolder();
|
|
||||||
|
|
||||||
if (outputFolder == null || outputFolder.isEmpty()) {
|
|
||||||
// If the environment variable is not set, use the default value
|
|
||||||
outputFolder = finishedFoldersDir;
|
|
||||||
}
|
|
||||||
logger.info("outputDir 0={}", outputDir);
|
|
||||||
// Replace the placeholders in the outputDir string
|
|
||||||
outputDir = outputDir.replace("{outputFolder}", outputFolder);
|
|
||||||
outputDir = outputDir.replace("{folderName}", dir.toString());
|
|
||||||
logger.info("outputDir 1={}", outputDir);
|
|
||||||
outputDir = outputDir.replace("\\watchedFolders", "");
|
|
||||||
outputDir = outputDir.replace("//watchedFolders", "");
|
|
||||||
outputDir = outputDir.replace("\\\\watchedFolders", "");
|
|
||||||
outputDir = outputDir.replace("/watchedFolders", "");
|
|
||||||
|
|
||||||
Path outputPath;
|
|
||||||
logger.info("outputDir 2={}", outputDir);
|
|
||||||
if (Paths.get(outputDir).isAbsolute()) {
|
|
||||||
// If it's an absolute path, use it directly
|
|
||||||
outputPath = Paths.get(outputDir);
|
|
||||||
} else {
|
|
||||||
// If it's a relative path, make it relative to the current working directory
|
|
||||||
outputPath = Paths.get(".", outputDir);
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info("outputPath={}", outputPath);
|
|
||||||
|
|
||||||
if (!Files.exists(outputPath)) {
|
|
||||||
try {
|
|
||||||
Files.createDirectories(outputPath);
|
|
||||||
logger.info("Created directory: {}", outputPath);
|
|
||||||
} catch (IOException e) {
|
|
||||||
logger.error("Error creating directory: {}", outputPath, e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
logger.info("outputPath {}", outputPath);
|
|
||||||
logger.info("outputPath.resolve(outputFileName).toString() {}", outputPath.resolve(outputFileName).toString());
|
|
||||||
File newFile = new File(outputPath.resolve(outputFileName).toString());
|
|
||||||
OutputStream os = new FileOutputStream(newFile);
|
|
||||||
os.write(((ByteArrayResource)resource).getByteArray());
|
|
||||||
os.close();
|
|
||||||
logger.info("made {}", outputPath.resolve(outputFileName));
|
|
||||||
}
|
|
||||||
|
|
||||||
// If successful, delete the original files
|
|
||||||
for (File file : filesToProcess) {
|
|
||||||
Files.deleteIfExists(processingDir.resolve(file.getName()));
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
// If an error occurs, move the original files back
|
|
||||||
for (File file : filesToProcess) {
|
|
||||||
Files.move(processingDir.resolve(file.getName()), file.toPath());
|
|
||||||
}
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Resource> processFiles(List<Resource> outputFiles, String jsonString) throws Exception {
|
|
||||||
|
|
||||||
ObjectMapper mapper = new ObjectMapper();
|
|
||||||
JsonNode jsonNode = mapper.readTree(jsonString);
|
|
||||||
|
|
||||||
JsonNode pipelineNode = jsonNode.get("pipeline");
|
|
||||||
logger.info("Running pipelineNode: {}", pipelineNode);
|
|
||||||
ByteArrayOutputStream logStream = new ByteArrayOutputStream();
|
|
||||||
PrintStream logPrintStream = new PrintStream(logStream);
|
|
||||||
|
|
||||||
boolean hasErrors = false;
|
|
||||||
|
|
||||||
for (JsonNode operationNode : pipelineNode) {
|
|
||||||
String operation = operationNode.get("operation").asText();
|
|
||||||
logger.info("Running operation: {}", operation);
|
|
||||||
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 the operation is filter and the response body is null or empty, skip this file
|
|
||||||
if (operation.startsWith("filter-") && (response.getBody() == null || response.getBody().length == 0)) {
|
|
||||||
logger.info("Skipping file due to failing {}", operation);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!response.getStatusCode().equals(HttpStatus.OK)) {
|
|
||||||
logPrintStream.println("Error: " + response.getBody());
|
|
||||||
hasErrors = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Define filename
|
|
||||||
String filename;
|
|
||||||
if ("auto-rename".equals(operation)) {
|
|
||||||
// If the operation is "auto-rename", generate a new filename.
|
|
||||||
// This is a simple example of generating a filename using current timestamp.
|
|
||||||
// Modify as per your needs.
|
|
||||||
filename = "file_" + System.currentTimeMillis();
|
|
||||||
} else {
|
|
||||||
// Otherwise, keep the original filename.
|
|
||||||
filename = file.getFilename();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 filename;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
newOutputFiles.add(outputResource);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hasInputFileType) {
|
|
||||||
logPrintStream.println(
|
|
||||||
"No files with extension " + inputFileExtension + " found for operation " + operation);
|
|
||||||
hasErrors = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
outputFiles = newOutputFiles;
|
|
||||||
}
|
|
||||||
logPrintStream.close();
|
|
||||||
|
|
||||||
}
|
|
||||||
if (hasErrors) {
|
|
||||||
logger.error("Errors occurred during processing. Log: {}", logStream.toString());
|
|
||||||
}
|
|
||||||
return outputFiles;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Resource> handleFiles(File[] files, String jsonString) throws Exception {
|
|
||||||
if(files == null || files.length == 0) {
|
|
||||||
logger.info("No files");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info("Handling files: {} files, with JSON string of length: {}", files.length, jsonString.length());
|
|
||||||
|
|
||||||
ObjectMapper mapper = new ObjectMapper();
|
|
||||||
JsonNode jsonNode = mapper.readTree(jsonString);
|
|
||||||
|
|
||||||
JsonNode pipelineNode = jsonNode.get("pipeline");
|
|
||||||
|
|
||||||
boolean hasErrors = false;
|
|
||||||
List<Resource> outputFiles = new ArrayList<>();
|
|
||||||
|
|
||||||
for (File file : files) {
|
|
||||||
Path path = Paths.get(file.getAbsolutePath());
|
|
||||||
System.out.println("Reading file: " + path); // debug statement
|
|
||||||
|
|
||||||
if (Files.exists(path)) {
|
|
||||||
Resource fileResource = new ByteArrayResource(Files.readAllBytes(path)) {
|
|
||||||
@Override
|
|
||||||
public String getFilename() {
|
|
||||||
return file.getName();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
outputFiles.add(fileResource);
|
|
||||||
} else {
|
|
||||||
System.out.println("File not found: " + path); // debug statement
|
|
||||||
}
|
|
||||||
}
|
|
||||||
logger.info("Files successfully loaded. Starting processing...");
|
|
||||||
return processFiles(outputFiles, jsonString);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Resource> handleFiles(MultipartFile[] files, String jsonString) throws Exception {
|
|
||||||
if(files == null || files.length == 0) {
|
|
||||||
logger.info("No files");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
logger.info("Handling files: {} files, with JSON string of length: {}", files.length, jsonString.length());
|
|
||||||
ObjectMapper mapper = new ObjectMapper();
|
|
||||||
JsonNode jsonNode = mapper.readTree(jsonString);
|
|
||||||
|
|
||||||
JsonNode pipelineNode = jsonNode.get("pipeline");
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
logger.info("Files successfully loaded. Starting processing...");
|
|
||||||
return processFiles(outputFiles, jsonString);
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping("/handleData")
|
@PostMapping("/handleData")
|
||||||
public ResponseEntity<byte[]> handleData(@ModelAttribute HandleDataRequest request) {
|
public ResponseEntity<byte[]> handleData(@ModelAttribute HandleDataRequest request) throws JsonMappingException, JsonProcessingException {
|
||||||
MultipartFile[] files = request.getFileInputs();
|
if (!Boolean.TRUE.equals(applicationProperties.getSystem().getEnableAlphaFunctionality())) {
|
||||||
String jsonString = request.getJsonString();
|
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
MultipartFile[] files = request.getFileInput();
|
||||||
|
String jsonString = request.getJson();
|
||||||
|
if (files == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
PipelineConfig config = objectMapper.readValue(jsonString, PipelineConfig.class);
|
||||||
logger.info("Received POST request to /handleData with {} files", files.length);
|
logger.info("Received POST request to /handleData with {} files", files.length);
|
||||||
try {
|
try {
|
||||||
List<Resource> outputFiles = handleFiles(files, jsonString);
|
List<Resource> inputFiles = processor.generateInputFiles(files);
|
||||||
|
if(inputFiles == null || inputFiles.size() == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
List<Resource> outputFiles = processor.runPipelineAgainstFiles(inputFiles, config);
|
||||||
if (outputFiles != null && outputFiles.size() == 1) {
|
if (outputFiles != null && outputFiles.size() == 1) {
|
||||||
// If there is only one file, return it directly
|
// If there is only one file, return it directly
|
||||||
Resource singleFile = outputFiles.get(0);
|
Resource singleFile = outputFiles.get(0);
|
||||||
|
@ -473,52 +114,4 @@ public class PipelineController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isZip(byte[] data) {
|
|
||||||
if (data == null || data.length < 4) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check the first four bytes of the data against the standard zip magic number
|
|
||||||
return data[0] == 0x50 && data[1] == 0x4B && data[2] == 0x03 && data[3] == 0x04;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<Resource> unzip(byte[] data) throws IOException {
|
|
||||||
logger.info("Unzipping data of length: {}", data.length);
|
|
||||||
List<Resource> unzippedFiles = new ArrayList<>();
|
|
||||||
|
|
||||||
try (ByteArrayInputStream bais = new ByteArrayInputStream(data);
|
|
||||||
ZipInputStream zis = new ZipInputStream(bais)) {
|
|
||||||
|
|
||||||
ZipEntry entry;
|
|
||||||
while ((entry = zis.getNextEntry()) != null) {
|
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
|
||||||
byte[] buffer = new byte[1024];
|
|
||||||
int count;
|
|
||||||
|
|
||||||
while ((count = zis.read(buffer)) != -1) {
|
|
||||||
baos.write(buffer, 0, count);
|
|
||||||
}
|
|
||||||
|
|
||||||
final String filename = entry.getName();
|
|
||||||
Resource fileResource = new ByteArrayResource(baos.toByteArray()) {
|
|
||||||
@Override
|
|
||||||
public String getFilename() {
|
|
||||||
return filename;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// If the unzipped file is a zip file, unzip it
|
|
||||||
if (isZip(baos.toByteArray())) {
|
|
||||||
logger.info("File {} is a zip file. Unzipping...", filename);
|
|
||||||
unzippedFiles.addAll(unzip(baos.toByteArray()));
|
|
||||||
} else {
|
|
||||||
unzippedFiles.add(fileResource);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info("Unzipping completed. {} files were unzipped.", unzippedFiles.size());
|
|
||||||
return unzippedFiles;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,258 @@
|
||||||
|
package stirling.software.SPDF.controller.api.pipeline;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
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.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.core.io.ByteArrayResource;
|
||||||
|
import org.springframework.core.io.Resource;
|
||||||
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
|
import stirling.software.SPDF.model.PipelineConfig;
|
||||||
|
import stirling.software.SPDF.model.PipelineOperation;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class PipelineDirectoryProcessor {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(PipelineDirectoryProcessor.class);
|
||||||
|
@Autowired
|
||||||
|
private ObjectMapper objectMapper;
|
||||||
|
@Autowired
|
||||||
|
private ApiDocService apiDocService;
|
||||||
|
@Autowired
|
||||||
|
private ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
|
final String watchedFoldersDir = "./pipeline/watchedFolders/";
|
||||||
|
final String finishedFoldersDir = "./pipeline/finishedFolders/";
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
PipelineProcessor processor;
|
||||||
|
|
||||||
|
@Scheduled(fixedRate = 60000)
|
||||||
|
public void scanFolders() {
|
||||||
|
if (!Boolean.TRUE.equals(applicationProperties.getSystem().getEnableAlphaFunctionality())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Path watchedFolderPath = Paths.get(watchedFoldersDir);
|
||||||
|
if (!Files.exists(watchedFolderPath)) {
|
||||||
|
try {
|
||||||
|
Files.createDirectories(watchedFolderPath);
|
||||||
|
logger.info("Created directory: {}", watchedFolderPath);
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.error("Error creating directory: {}", watchedFolderPath, e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try (Stream<Path> paths = Files.walk(watchedFolderPath)) {
|
||||||
|
paths.filter(Files::isDirectory).forEach(t -> {
|
||||||
|
try {
|
||||||
|
if (!t.equals(watchedFolderPath) && !t.endsWith("processing")) {
|
||||||
|
handleDirectory(t);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Error handling directory: {}", t, e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Error walking through directory: {}", watchedFolderPath, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void handleDirectory(Path dir) throws IOException {
|
||||||
|
logger.info("Handling directory: {}", dir);
|
||||||
|
Path processingDir = createProcessingDirectory(dir);
|
||||||
|
|
||||||
|
Optional<Path> jsonFileOptional = findJsonFile(dir);
|
||||||
|
if (!jsonFileOptional.isPresent()) {
|
||||||
|
logger.warn("No .JSON settings file found. No processing will happen for dir {}.", dir);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Path jsonFile = jsonFileOptional.get();
|
||||||
|
PipelineConfig config = readAndParseJson(jsonFile);
|
||||||
|
processPipelineOperations(dir, processingDir, jsonFile, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Path createProcessingDirectory(Path dir) throws IOException {
|
||||||
|
Path processingDir = dir.resolve("processing");
|
||||||
|
if (!Files.exists(processingDir)) {
|
||||||
|
Files.createDirectory(processingDir);
|
||||||
|
logger.info("Created processing directory: {}", processingDir);
|
||||||
|
}
|
||||||
|
return processingDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Optional<Path> findJsonFile(Path dir) throws IOException {
|
||||||
|
try (Stream<Path> paths = Files.list(dir)) {
|
||||||
|
return paths.filter(file -> file.toString().endsWith(".json")).findFirst();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private PipelineConfig readAndParseJson(Path jsonFile) throws IOException {
|
||||||
|
String jsonString = new String(Files.readAllBytes(jsonFile), StandardCharsets.UTF_8);
|
||||||
|
logger.debug("Reading JSON file: {}", jsonFile);
|
||||||
|
return objectMapper.readValue(jsonString, PipelineConfig.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processPipelineOperations(Path dir, Path processingDir, Path jsonFile, PipelineConfig config) throws IOException {
|
||||||
|
for (PipelineOperation operation : config.getOperations()) {
|
||||||
|
validateOperation(operation);
|
||||||
|
File[] files = collectFilesForProcessing(dir, jsonFile, operation);
|
||||||
|
if(files == null || files.length == 0) {
|
||||||
|
logger.debug("No files detected for {} ", dir);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
List<File> filesToProcess = prepareFilesForProcessing(files, processingDir);
|
||||||
|
runPipelineAgainstFiles(filesToProcess, config, dir, processingDir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateOperation(PipelineOperation operation) throws IOException {
|
||||||
|
if (!apiDocService.isValidOperation(operation.getOperation(), operation.getParameters())) {
|
||||||
|
throw new IOException("Invalid operation: " + operation.getOperation());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private File[] collectFilesForProcessing(Path dir, Path jsonFile, PipelineOperation operation) throws IOException {
|
||||||
|
try (Stream<Path> paths = Files.list(dir)) {
|
||||||
|
if ("automated".equals(operation.getParameters().get("fileInput"))) {
|
||||||
|
return paths.filter(path -> !Files.isDirectory(path) && !path.equals(jsonFile))
|
||||||
|
.map(Path::toFile)
|
||||||
|
.toArray(File[]::new);
|
||||||
|
} else {
|
||||||
|
String fileInput = (String) operation.getParameters().get("fileInput");
|
||||||
|
return new File[]{new File(fileInput)};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<File> prepareFilesForProcessing(File[] files, Path processingDir) throws IOException {
|
||||||
|
List<File> filesToProcess = new ArrayList<>();
|
||||||
|
for (File file : files) {
|
||||||
|
Path targetPath = resolveUniqueFilePath(processingDir, file.getName());
|
||||||
|
Files.move(file.toPath(), targetPath);
|
||||||
|
filesToProcess.add(targetPath.toFile());
|
||||||
|
}
|
||||||
|
return filesToProcess;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Path resolveUniqueFilePath(Path directory, String originalFileName) {
|
||||||
|
Path filePath = directory.resolve(originalFileName);
|
||||||
|
int counter = 1;
|
||||||
|
|
||||||
|
while (Files.exists(filePath)) {
|
||||||
|
String newName = appendSuffixToFileName(originalFileName, "(" + counter + ")");
|
||||||
|
filePath = directory.resolve(newName);
|
||||||
|
counter++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return filePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String appendSuffixToFileName(String originalFileName, String suffix) {
|
||||||
|
int dotIndex = originalFileName.lastIndexOf('.');
|
||||||
|
if (dotIndex == -1) {
|
||||||
|
return originalFileName + suffix;
|
||||||
|
} else {
|
||||||
|
return originalFileName.substring(0, dotIndex) + suffix + originalFileName.substring(dotIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void runPipelineAgainstFiles(List<File> filesToProcess, PipelineConfig config, Path dir, Path processingDir) throws IOException {
|
||||||
|
try {
|
||||||
|
List<Resource> inputFiles = processor.generateInputFiles(filesToProcess.toArray(new File[0]));
|
||||||
|
if(inputFiles == null || inputFiles.size() == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
List<Resource> outputFiles = processor.runPipelineAgainstFiles(inputFiles, config);
|
||||||
|
if (outputFiles == null) return;
|
||||||
|
moveAndRenameFiles(outputFiles, config, dir);
|
||||||
|
deleteOriginalFiles(filesToProcess, processingDir);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("error during processing", e);
|
||||||
|
moveFilesBack(filesToProcess, processingDir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void moveAndRenameFiles(List<Resource> resources, PipelineConfig config, Path dir) throws IOException {
|
||||||
|
for (Resource resource : resources) {
|
||||||
|
String outputFileName = createOutputFileName(resource, config);
|
||||||
|
Path outputPath = determineOutputPath(config, dir);
|
||||||
|
|
||||||
|
if (!Files.exists(outputPath)) {
|
||||||
|
Files.createDirectories(outputPath);
|
||||||
|
logger.info("Created directory: {}", outputPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
Path outputFile = outputPath.resolve(outputFileName);
|
||||||
|
try (OutputStream os = new FileOutputStream(outputFile.toFile())) {
|
||||||
|
os.write(((ByteArrayResource) resource).getByteArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info("File moved and renamed to {}", outputFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String createOutputFileName(Resource resource, PipelineConfig config) {
|
||||||
|
String resourceName = resource.getFilename();
|
||||||
|
String baseName = resourceName.substring(0, resourceName.lastIndexOf('.'));
|
||||||
|
String extension = resourceName.substring(resourceName.lastIndexOf('.') + 1);
|
||||||
|
|
||||||
|
String outputFileName = config.getOutputPattern()
|
||||||
|
.replace("{filename}", baseName)
|
||||||
|
.replace("{pipelineName}", config.getName())
|
||||||
|
.replace("{date}", LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")))
|
||||||
|
.replace("{time}", LocalTime.now().format(DateTimeFormatter.ofPattern("HHmmss")))
|
||||||
|
+ "." + extension;
|
||||||
|
|
||||||
|
return outputFileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Path determineOutputPath(PipelineConfig config, Path dir) {
|
||||||
|
String outputDir = config.getOutputDir()
|
||||||
|
.replace("{outputFolder}", finishedFoldersDir)
|
||||||
|
.replace("{folderName}", dir.toString())
|
||||||
|
.replaceAll("\\\\?watchedFolders", "");
|
||||||
|
|
||||||
|
return Paths.get(outputDir).isAbsolute() ? Paths.get(outputDir) : Paths.get(".", outputDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deleteOriginalFiles(List<File> filesToProcess, Path processingDir) throws IOException {
|
||||||
|
for (File file : filesToProcess) {
|
||||||
|
Files.deleteIfExists(processingDir.resolve(file.getName()));
|
||||||
|
logger.info("Deleted original file: {}", file.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void moveFilesBack(List<File> filesToProcess, Path processingDir) {
|
||||||
|
for (File file : filesToProcess) {
|
||||||
|
try {
|
||||||
|
Files.move(processingDir.resolve(file.getName()), file.toPath());
|
||||||
|
logger.info("Moved file back to original location: {} , {}",file.toPath(), file.getName());
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.error("Error moving file back to original location: {}", file.getName(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,332 @@
|
||||||
|
package stirling.software.SPDF.controller.api.pipeline;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.PrintStream;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.zip.ZipEntry;
|
||||||
|
import java.util.zip.ZipInputStream;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.core.io.ByteArrayResource;
|
||||||
|
import org.springframework.core.io.Resource;
|
||||||
|
import org.springframework.http.HttpEntity;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.HttpMethod;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.util.LinkedMultiValueMap;
|
||||||
|
import org.springframework.util.MultiValueMap;
|
||||||
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import jakarta.servlet.ServletContext;
|
||||||
|
|
||||||
|
import stirling.software.SPDF.SPdfApplication;
|
||||||
|
import stirling.software.SPDF.model.PipelineConfig;
|
||||||
|
import stirling.software.SPDF.model.PipelineOperation;
|
||||||
|
import stirling.software.SPDF.model.Role;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class PipelineProcessor {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(PipelineProcessor.class);
|
||||||
|
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ApiDocService apiDocService;
|
||||||
|
|
||||||
|
@Autowired(required=false)
|
||||||
|
private UserServiceInterface userService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ServletContext servletContext;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private String getApiKeyForUser() {
|
||||||
|
if (userService == null)
|
||||||
|
return "";
|
||||||
|
return userService.getApiKeyForUser(Role.INTERNAL_API_USER.getRoleId());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private String getBaseUrl() {
|
||||||
|
String contextPath = servletContext.getContextPath();
|
||||||
|
String port = SPdfApplication.getPort();
|
||||||
|
|
||||||
|
return "http://localhost:" + port + contextPath + "/";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
List<Resource> runPipelineAgainstFiles(List<Resource> outputFiles, PipelineConfig config) throws Exception {
|
||||||
|
|
||||||
|
ByteArrayOutputStream logStream = new ByteArrayOutputStream();
|
||||||
|
PrintStream logPrintStream = new PrintStream(logStream);
|
||||||
|
|
||||||
|
boolean hasErrors = false;
|
||||||
|
|
||||||
|
for (PipelineOperation pipelineOperation : config.getOperations()) {
|
||||||
|
String operation = pipelineOperation.getOperation();
|
||||||
|
boolean isMultiInputOperation = apiDocService.isMultiInput(operation);
|
||||||
|
|
||||||
|
logger.info("Running operation: {} isMultiInputOperation {}", operation, isMultiInputOperation);
|
||||||
|
Map<String, Object> parameters = pipelineOperation.getParameters();
|
||||||
|
String inputFileExtension = "";
|
||||||
|
|
||||||
|
//TODO
|
||||||
|
//if (operationNode.has("inputFileType")) {
|
||||||
|
// inputFileExtension = operationNode.get("inputFileType").asText();
|
||||||
|
//} else {
|
||||||
|
inputFileExtension = ".pdf";
|
||||||
|
//}
|
||||||
|
final String finalInputFileExtension = inputFileExtension;
|
||||||
|
|
||||||
|
String url = getBaseUrl() + operation;
|
||||||
|
|
||||||
|
List<Resource> newOutputFiles = new ArrayList<>();
|
||||||
|
if (!isMultiInputOperation) {
|
||||||
|
for (Resource file : outputFiles) {
|
||||||
|
boolean hasInputFileType = false;
|
||||||
|
if (file.getFilename().endsWith(inputFileExtension)) {
|
||||||
|
hasInputFileType = true;
|
||||||
|
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
|
||||||
|
body.add("fileInput", file);
|
||||||
|
|
||||||
|
|
||||||
|
for(Entry<String, Object> entry : parameters.entrySet()) {
|
||||||
|
body.add(entry.getKey(), entry.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
ResponseEntity<byte[]> response = sendWebRequest(url, body);
|
||||||
|
|
||||||
|
// If the operation is filter and the response body is null or empty, skip this
|
||||||
|
// file
|
||||||
|
if (operation.startsWith("filter-")
|
||||||
|
&& (response.getBody() == null || response.getBody().length == 0)) {
|
||||||
|
logger.info("Skipping file due to failing {}", operation);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!response.getStatusCode().equals(HttpStatus.OK)) {
|
||||||
|
logPrintStream.println("Error: " + response.getBody());
|
||||||
|
hasErrors = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
processOutputFiles(operation, file.getFilename(), response, newOutputFiles);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasInputFileType) {
|
||||||
|
logPrintStream.println(
|
||||||
|
"No files with extension " + inputFileExtension + " found for operation " + operation);
|
||||||
|
hasErrors = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
outputFiles = newOutputFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Filter and collect all files that match the inputFileExtension
|
||||||
|
List<Resource> matchingFiles = outputFiles.stream()
|
||||||
|
.filter(file -> file.getFilename().endsWith(finalInputFileExtension))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
// Check if there are matching files
|
||||||
|
if (!matchingFiles.isEmpty()) {
|
||||||
|
// Create a new MultiValueMap for the request body
|
||||||
|
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
|
||||||
|
|
||||||
|
// Add all matching files to the body
|
||||||
|
for (Resource file : matchingFiles) {
|
||||||
|
body.add("fileInput", file);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(Entry<String, Object> entry : parameters.entrySet()) {
|
||||||
|
body.add(entry.getKey(), entry.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
ResponseEntity<byte[]> response = sendWebRequest(url, body);
|
||||||
|
|
||||||
|
// Handle the response
|
||||||
|
if (response.getStatusCode().equals(HttpStatus.OK)) {
|
||||||
|
processOutputFiles(operation, matchingFiles.get(0).getFilename(), response, newOutputFiles);
|
||||||
|
} else {
|
||||||
|
// Log error if the response status is not OK
|
||||||
|
logPrintStream.println("Error in multi-input operation: " + response.getBody());
|
||||||
|
hasErrors = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logPrintStream.println("No files with extension " + inputFileExtension + " found for multi-input operation " + operation);
|
||||||
|
hasErrors = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logPrintStream.close();
|
||||||
|
|
||||||
|
}
|
||||||
|
if (hasErrors) {
|
||||||
|
logger.error("Errors occurred during processing. Log: {}", logStream.toString());
|
||||||
|
}
|
||||||
|
return outputFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ResponseEntity<byte[]> sendWebRequest(String url, MultiValueMap<String, Object> body ){
|
||||||
|
RestTemplate restTemplate = new RestTemplate();
|
||||||
|
|
||||||
|
// Set up headers, including API key
|
||||||
|
HttpHeaders headers = new HttpHeaders();
|
||||||
|
String apiKey = getApiKeyForUser();
|
||||||
|
headers.add("X-API-Key", apiKey);
|
||||||
|
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
|
||||||
|
|
||||||
|
// Create HttpEntity with the body and headers
|
||||||
|
HttpEntity<MultiValueMap<String, Object>> entity = new HttpEntity<>(body, headers);
|
||||||
|
|
||||||
|
// Make the request to the REST endpoint
|
||||||
|
return restTemplate.exchange(url, HttpMethod.POST, entity, byte[].class);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Resource> processOutputFiles(String operation, String fileName, ResponseEntity<byte[]> response, List<Resource> newOutputFiles) throws IOException{
|
||||||
|
// Define filename
|
||||||
|
String newFilename;
|
||||||
|
if ("auto-rename".equals(operation)) {
|
||||||
|
// If the operation is "auto-rename", generate a new filename.
|
||||||
|
// This is a simple example of generating a filename using current timestamp.
|
||||||
|
// Modify as per your needs.
|
||||||
|
newFilename = "file_" + System.currentTimeMillis();
|
||||||
|
} else {
|
||||||
|
// Otherwise, keep the original filename.
|
||||||
|
newFilename = fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 newFilename;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
newOutputFiles.add(outputResource);
|
||||||
|
}
|
||||||
|
|
||||||
|
return newOutputFiles;
|
||||||
|
|
||||||
|
}
|
||||||
|
List<Resource> generateInputFiles(File[] files) throws Exception {
|
||||||
|
if (files == null || files.length == 0) {
|
||||||
|
logger.info("No files");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
List<Resource> outputFiles = new ArrayList<>();
|
||||||
|
|
||||||
|
for (File file : files) {
|
||||||
|
Path path = Paths.get(file.getAbsolutePath());
|
||||||
|
logger.info("Reading file: " + path); // debug statement
|
||||||
|
|
||||||
|
if (Files.exists(path)) {
|
||||||
|
Resource fileResource = new ByteArrayResource(Files.readAllBytes(path)) {
|
||||||
|
@Override
|
||||||
|
public String getFilename() {
|
||||||
|
return file.getName();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
outputFiles.add(fileResource);
|
||||||
|
} else {
|
||||||
|
logger.info("File not found: " + path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger.info("Files successfully loaded. Starting processing...");
|
||||||
|
return outputFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Resource> generateInputFiles(MultipartFile[] files) throws Exception {
|
||||||
|
if (files == null || files.length == 0) {
|
||||||
|
logger.info("No files");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
logger.info("Files successfully loaded. Starting processing...");
|
||||||
|
return outputFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isZip(byte[] data) {
|
||||||
|
if (data == null || data.length < 4) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the first four bytes of the data against the standard zip magic number
|
||||||
|
return data[0] == 0x50 && data[1] == 0x4B && data[2] == 0x03 && data[3] == 0x04;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Resource> unzip(byte[] data) throws IOException {
|
||||||
|
logger.info("Unzipping data of length: {}", data.length);
|
||||||
|
List<Resource> unzippedFiles = new ArrayList<>();
|
||||||
|
|
||||||
|
try (ByteArrayInputStream bais = new ByteArrayInputStream(data);
|
||||||
|
ZipInputStream zis = new ZipInputStream(bais)) {
|
||||||
|
|
||||||
|
ZipEntry entry;
|
||||||
|
while ((entry = zis.getNextEntry()) != null) {
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
byte[] buffer = new byte[1024];
|
||||||
|
int count;
|
||||||
|
|
||||||
|
while ((count = zis.read(buffer)) != -1) {
|
||||||
|
baos.write(buffer, 0, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
final String filename = entry.getName();
|
||||||
|
Resource fileResource = new ByteArrayResource(baos.toByteArray()) {
|
||||||
|
@Override
|
||||||
|
public String getFilename() {
|
||||||
|
return filename;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// If the unzipped file is a zip file, unzip it
|
||||||
|
if (isZip(baos.toByteArray())) {
|
||||||
|
logger.info("File {} is a zip file. Unzipping...", filename);
|
||||||
|
unzippedFiles.addAll(unzip(baos.toByteArray()));
|
||||||
|
} else {
|
||||||
|
unzippedFiles.add(fileResource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info("Unzipping completed. {} files were unzipped.", unzippedFiles.size());
|
||||||
|
return unzippedFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
package stirling.software.SPDF.controller.api.pipeline;
|
||||||
|
public interface UserServiceInterface {
|
||||||
|
String getApiKeyForUser(String username);
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
package stirling.software.SPDF.controller.web;
|
package stirling.software.SPDF.controller.web;
|
||||||
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@ -15,6 +16,8 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import stirling.software.SPDF.model.Authority;
|
||||||
|
import stirling.software.SPDF.model.Role;
|
||||||
import stirling.software.SPDF.model.User;
|
import stirling.software.SPDF.model.User;
|
||||||
import stirling.software.SPDF.repository.UserRepository;
|
import stirling.software.SPDF.repository.UserRepository;
|
||||||
@Controller
|
@Controller
|
||||||
|
@ -47,13 +50,27 @@ public class AccountWebController {
|
||||||
@GetMapping("/addUsers")
|
@GetMapping("/addUsers")
|
||||||
public String showAddUserForm(Model model, Authentication authentication) {
|
public String showAddUserForm(Model model, Authentication authentication) {
|
||||||
List<User> allUsers = userRepository.findAll();
|
List<User> allUsers = userRepository.findAll();
|
||||||
|
Iterator<User> iterator = allUsers.iterator();
|
||||||
|
|
||||||
|
while(iterator.hasNext()) {
|
||||||
|
User user = iterator.next();
|
||||||
|
if(user != null) {
|
||||||
|
for (Authority authority : user.getAuthorities()) {
|
||||||
|
if (authority.getAuthority().equals(Role.INTERNAL_API_USER.getRoleId())) {
|
||||||
|
iterator.remove();
|
||||||
|
break; // Break out of the inner loop once the user is removed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
model.addAttribute("users", allUsers);
|
model.addAttribute("users", allUsers);
|
||||||
model.addAttribute("currentUsername", authentication.getName());
|
model.addAttribute("currentUsername", authentication.getName());
|
||||||
return "addUsers";
|
return "addUsers";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@PreAuthorize("!hasAuthority('ROLE_DEMO_USER')")
|
||||||
@GetMapping("/account")
|
@GetMapping("/account")
|
||||||
public String account(HttpServletRequest request, Model model, Authentication authentication) {
|
public String account(HttpServletRequest request, Model model, Authentication authentication) {
|
||||||
if (authentication == null || !authentication.isAuthenticated()) {
|
if (authentication == null || !authentication.isAuthenticated()) {
|
||||||
|
@ -100,7 +117,7 @@ public class AccountWebController {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@PreAuthorize("!hasAuthority('ROLE_DEMO_USER')")
|
||||||
@GetMapping("/change-creds")
|
@GetMapping("/change-creds")
|
||||||
public String changeCreds(HttpServletRequest request, Model model, Authentication authentication) {
|
public String changeCreds(HttpServletRequest request, Model model, Authentication authentication) {
|
||||||
if (authentication == null || !authentication.isAuthenticated()) {
|
if (authentication == null || !authentication.isAuthenticated()) {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package stirling.software.SPDF.controller.web;
|
package stirling.software.SPDF.controller.web;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
|
@ -41,6 +42,9 @@ public class GeneralWebController {
|
||||||
model.addAttribute("currentPage", "pipeline");
|
model.addAttribute("currentPage", "pipeline");
|
||||||
|
|
||||||
List<String> pipelineConfigs = new ArrayList<>();
|
List<String> pipelineConfigs = new ArrayList<>();
|
||||||
|
List<Map<String, String>> pipelineConfigsWithNames = new ArrayList<>();
|
||||||
|
|
||||||
|
if(new File("./pipeline/defaultWebUIConfigs/").exists()) {
|
||||||
try (Stream<Path> paths = Files.walk(Paths.get("./pipeline/defaultWebUIConfigs/"))) {
|
try (Stream<Path> paths = Files.walk(Paths.get("./pipeline/defaultWebUIConfigs/"))) {
|
||||||
List<Path> jsonFiles = paths
|
List<Path> jsonFiles = paths
|
||||||
.filter(Files::isRegularFile)
|
.filter(Files::isRegularFile)
|
||||||
|
@ -51,7 +55,7 @@ public class GeneralWebController {
|
||||||
String content = Files.readString(jsonFile, StandardCharsets.UTF_8);
|
String content = Files.readString(jsonFile, StandardCharsets.UTF_8);
|
||||||
pipelineConfigs.add(content);
|
pipelineConfigs.add(content);
|
||||||
}
|
}
|
||||||
List<Map<String, String>> pipelineConfigsWithNames = new ArrayList<>();
|
|
||||||
for (String config : pipelineConfigs) {
|
for (String config : pipelineConfigs) {
|
||||||
Map<String, Object> jsonContent = new ObjectMapper().readValue(config, new TypeReference<Map<String, Object>>(){});
|
Map<String, Object> jsonContent = new ObjectMapper().readValue(config, new TypeReference<Map<String, Object>>(){});
|
||||||
|
|
||||||
|
@ -61,11 +65,21 @@ public class GeneralWebController {
|
||||||
configWithName.put("name", name);
|
configWithName.put("name", name);
|
||||||
pipelineConfigsWithNames.add(configWithName);
|
pipelineConfigsWithNames.add(configWithName);
|
||||||
}
|
}
|
||||||
model.addAttribute("pipelineConfigsWithNames", pipelineConfigsWithNames);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if(pipelineConfigsWithNames.size() == 0) {
|
||||||
|
Map<String, String> configWithName = new HashMap<>();
|
||||||
|
configWithName.put("json", "");
|
||||||
|
configWithName.put("name", "No preloaded configs found");
|
||||||
|
pipelineConfigsWithNames.add(configWithName);
|
||||||
|
}
|
||||||
|
model.addAttribute("pipelineConfigsWithNames", pipelineConfigsWithNames);
|
||||||
|
|
||||||
model.addAttribute("pipelineConfigs", pipelineConfigs);
|
model.addAttribute("pipelineConfigs", pipelineConfigs);
|
||||||
|
|
||||||
|
|
|
@ -27,8 +27,8 @@ import stirling.software.SPDF.config.StartupApplicationListener;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v1")
|
@RequestMapping("/api/v1/info")
|
||||||
@Tag(name = "API", description = "Info APIs")
|
@Tag(name = "Info", description = "Info APIs")
|
||||||
public class MetricsController {
|
public class MetricsController {
|
||||||
|
|
||||||
|
|
||||||
|
|
42
src/main/java/stirling/software/SPDF/model/ApiEndpoint.java
Normal file
42
src/main/java/stirling/software/SPDF/model/ApiEndpoint.java
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
package stirling.software.SPDF.model;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
|
||||||
|
public class ApiEndpoint {
|
||||||
|
private String name;
|
||||||
|
private Map<String, JsonNode> parameters;
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
public ApiEndpoint(String name, JsonNode postNode) {
|
||||||
|
this.name = name;
|
||||||
|
this.parameters = new HashMap<>();
|
||||||
|
postNode.path("parameters").forEach(paramNode -> {
|
||||||
|
String paramName = paramNode.path("name").asText();
|
||||||
|
parameters.put(paramName, paramNode);
|
||||||
|
});
|
||||||
|
this.description = postNode.path("description").asText();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean areParametersValid(Map<String, Object> providedParams) {
|
||||||
|
for (String requiredParam : parameters.keySet()) {
|
||||||
|
if (!providedParams.containsKey(requiredParam)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDescription() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "ApiEndpoint [name=" + name + ", parameters=" + parameters + "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -106,6 +106,25 @@ public class ApplicationProperties {
|
||||||
private Boolean enableLogin;
|
private Boolean enableLogin;
|
||||||
private Boolean csrfDisabled;
|
private Boolean csrfDisabled;
|
||||||
private InitialLogin initialLogin;
|
private InitialLogin initialLogin;
|
||||||
|
private int loginAttemptCount;
|
||||||
|
private long loginResetTimeMinutes;
|
||||||
|
|
||||||
|
|
||||||
|
public int getLoginAttemptCount() {
|
||||||
|
return loginAttemptCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLoginAttemptCount(int loginAttemptCount) {
|
||||||
|
this.loginAttemptCount = loginAttemptCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getLoginResetTimeMinutes() {
|
||||||
|
return loginResetTimeMinutes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLoginResetTimeMinutes(long loginResetTimeMinutes) {
|
||||||
|
this.loginResetTimeMinutes = loginResetTimeMinutes;
|
||||||
|
}
|
||||||
|
|
||||||
public InitialLogin getInitialLogin() {
|
public InitialLogin getInitialLogin() {
|
||||||
return initialLogin != null ? initialLogin : new InitialLogin();
|
return initialLogin != null ? initialLogin : new InitialLogin();
|
||||||
|
@ -176,6 +195,19 @@ public class ApplicationProperties {
|
||||||
private String customStaticFilePath;
|
private String customStaticFilePath;
|
||||||
private Integer maxFileSize;
|
private Integer maxFileSize;
|
||||||
|
|
||||||
|
private Boolean enableAlphaFunctionality;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public Boolean getEnableAlphaFunctionality() {
|
||||||
|
return enableAlphaFunctionality;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEnableAlphaFunctionality(Boolean enableAlphaFunctionality) {
|
||||||
|
this.enableAlphaFunctionality = enableAlphaFunctionality;
|
||||||
|
}
|
||||||
|
|
||||||
public String getDefaultLocale() {
|
public String getDefaultLocale() {
|
||||||
return defaultLocale;
|
return defaultLocale;
|
||||||
}
|
}
|
||||||
|
@ -218,12 +250,13 @@ public class ApplicationProperties {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "System [defaultLocale=" + defaultLocale + ", googlevisibility=" + googlevisibility + ", rootURIPath="
|
return "System [defaultLocale=" + defaultLocale + ", googlevisibility=" + googlevisibility
|
||||||
+ rootURIPath + ", customStaticFilePath=" + customStaticFilePath + ", maxFileSize=" + maxFileSize
|
+ ", rootURIPath=" + rootURIPath + ", customStaticFilePath=" + customStaticFilePath
|
||||||
+ "]";
|
+ ", maxFileSize=" + maxFileSize + ", enableAlphaFunctionality=" + enableAlphaFunctionality + "]";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Ui {
|
public static class Ui {
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
package stirling.software.SPDF.model;
|
||||||
|
public class AttemptCounter {
|
||||||
|
private int attemptCount;
|
||||||
|
private long lastAttemptTime;
|
||||||
|
|
||||||
|
public AttemptCounter() {
|
||||||
|
this.attemptCount = 1;
|
||||||
|
this.lastAttemptTime = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void increment() {
|
||||||
|
this.attemptCount++;
|
||||||
|
this.lastAttemptTime = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getAttemptCount() {
|
||||||
|
return attemptCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getlastAttemptTime() {
|
||||||
|
return lastAttemptTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean shouldReset(long ATTEMPT_INCREMENT_TIME) {
|
||||||
|
return System.currentTimeMillis() - lastAttemptTime > ATTEMPT_INCREMENT_TIME;
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,7 +14,12 @@ public enum Role {
|
||||||
EXTRA_LIMITED_API_USER("ROLE_EXTRA_LIMITED_API_USER", 20, 20),
|
EXTRA_LIMITED_API_USER("ROLE_EXTRA_LIMITED_API_USER", 20, 20),
|
||||||
|
|
||||||
// 0 API calls per day and 20 web calls
|
// 0 API calls per day and 20 web calls
|
||||||
WEB_ONLY_USER("ROLE_WEB_ONLY_USER", 0, 20);
|
WEB_ONLY_USER("ROLE_WEB_ONLY_USER", 0, 20),
|
||||||
|
|
||||||
|
|
||||||
|
INTERNAL_API_USER("STIRLING-PDF-BACKEND-API-USER", Integer.MAX_VALUE, Integer.MAX_VALUE),
|
||||||
|
|
||||||
|
DEMO_USER("ROLE_DEMO_USER", 100, 100);
|
||||||
|
|
||||||
private final String roleId;
|
private final String roleId;
|
||||||
private final int apiCallsPerDay;
|
private final int apiCallsPerDay;
|
||||||
|
|
|
@ -13,8 +13,8 @@ import lombok.NoArgsConstructor;
|
||||||
public class HandleDataRequest {
|
public class HandleDataRequest {
|
||||||
|
|
||||||
@Schema(description = "The input files")
|
@Schema(description = "The input files")
|
||||||
private MultipartFile[] fileInputs;
|
private MultipartFile[] fileInput;
|
||||||
|
|
||||||
@Schema(description = "JSON String")
|
@Schema(description = "JSON String")
|
||||||
private String jsonString;
|
private String json;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import java.util.List;
|
||||||
|
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.Hidden;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
|
@ -18,7 +19,7 @@ public class PDFWithPageNums extends PDFFile {
|
||||||
@Schema(description = "The pages to select, Supports ranges (e.g., '1,3,5-9'), or 'all' 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')\"")
|
@Schema(description = "The pages to select, Supports ranges (e.g., '1,3,5-9'), or 'all' 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')\"")
|
||||||
private String pageNumbers;
|
private String pageNumbers;
|
||||||
|
|
||||||
|
@Hidden
|
||||||
public List<Integer> getPageNumbersList(){
|
public List<Integer> getPageNumbersList(){
|
||||||
int pageCount = 0;
|
int pageCount = 0;
|
||||||
try {
|
try {
|
||||||
|
@ -30,6 +31,8 @@ public class PDFWithPageNums extends PDFFile {
|
||||||
return GeneralUtils.parsePageString(pageNumbers, pageCount);
|
return GeneralUtils.parsePageString(pageNumbers, pageCount);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Hidden
|
||||||
public List<Integer> getPageNumbersList(PDDocument doc){
|
public List<Integer> getPageNumbersList(PDDocument doc){
|
||||||
int pageCount = 0;
|
int pageCount = 0;
|
||||||
pageCount = doc.getNumberOfPages();
|
pageCount = doc.getNumberOfPages();
|
||||||
|
|
|
@ -4,22 +4,17 @@ import java.awt.Graphics;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.zip.ZipEntry;
|
import java.util.zip.ZipEntry;
|
||||||
import java.util.zip.ZipOutputStream;
|
import java.util.zip.ZipOutputStream;
|
||||||
|
|
||||||
import javax.imageio.ImageIO;
|
|
||||||
import javax.imageio.IIOImage;
|
import javax.imageio.IIOImage;
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
import javax.imageio.ImageReader;
|
import javax.imageio.ImageReader;
|
||||||
import javax.imageio.ImageWriter;
|
|
||||||
import javax.imageio.ImageWriteParam;
|
import javax.imageio.ImageWriteParam;
|
||||||
|
import javax.imageio.ImageWriter;
|
||||||
import javax.imageio.stream.ImageOutputStream;
|
import javax.imageio.stream.ImageOutputStream;
|
||||||
|
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
package stirling.software.SPDF.utils;
|
||||||
|
|
||||||
|
public class RequestUriUtils {
|
||||||
|
|
||||||
|
public static boolean isStaticResource(String requestURI) {
|
||||||
|
|
||||||
|
return requestURI.startsWith("/css/")
|
||||||
|
|| requestURI.startsWith("/js/")
|
||||||
|
|| requestURI.startsWith("/images/")
|
||||||
|
|| requestURI.startsWith("/public/")
|
||||||
|
|| requestURI.startsWith("/pdfjs/")
|
||||||
|
|| requestURI.endsWith(".svg");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
6
src/main/resources/banner.txt
Normal file
6
src/main/resources/banner.txt
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
____ _____ ___ ____ _ ___ _ _ ____ ____ ____ _____
|
||||||
|
/ ___|_ _|_ _| _ \| | |_ _| \ | |/ ___| | _ \| _ \| ___|
|
||||||
|
\___ \ | | | || |_) | | | || \| | | _ _____| |_) | | | | |_
|
||||||
|
___) || | | || _ <| |___ | || |\ | |_| |_____| __/| |_| | _|
|
||||||
|
|____/ |_| |___|_| \_\_____|___|_| \_|\____| |_| |____/|_|
|
||||||
|
Powered by Spring Boot ${spring-boot.version}
|
|
@ -119,6 +119,7 @@ adminUserSettings.role=Role
|
||||||
adminUserSettings.actions=Actions
|
adminUserSettings.actions=Actions
|
||||||
adminUserSettings.apiUser=Limited API User
|
adminUserSettings.apiUser=Limited API User
|
||||||
adminUserSettings.webOnlyUser=Web Only User
|
adminUserSettings.webOnlyUser=Web Only User
|
||||||
|
adminUserSettings.demoUser=Demo User (No custom settings)
|
||||||
adminUserSettings.forceChange=Force user to change username/password on login
|
adminUserSettings.forceChange=Force user to change username/password on login
|
||||||
adminUserSettings.submit=Save User
|
adminUserSettings.submit=Save User
|
||||||
|
|
||||||
|
@ -255,6 +256,10 @@ home.removeBlanks.title=إزالة الصفحات الفارغة
|
||||||
home.removeBlanks.desc=يكتشف ويزيل الصفحات الفارغة من المستند
|
home.removeBlanks.desc=يكتشف ويزيل الصفحات الفارغة من المستند
|
||||||
removeBlanks.tags=cleanup,streamline,non-content,organize
|
removeBlanks.tags=cleanup,streamline,non-content,organize
|
||||||
|
|
||||||
|
home.removeAnnotations.title=Remove Annotations
|
||||||
|
home.removeAnnotations.desc=Removes all comments/annotations from a PDF
|
||||||
|
removeAnnotations.tags=comments,highlight,notes,markup,remove
|
||||||
|
|
||||||
home.compare.title=قارن
|
home.compare.title=قارن
|
||||||
home.compare.desc=يقارن ويظهر الاختلافات بين 2 من مستندات PDF
|
home.compare.desc=يقارن ويظهر الاختلافات بين 2 من مستندات PDF
|
||||||
compare.tags=differentiate,contrast,changes,analysis
|
compare.tags=differentiate,contrast,changes,analysis
|
||||||
|
@ -539,6 +544,12 @@ removeBlanks.whitePercentDesc=النسبة المئوية للصفحة التي
|
||||||
removeBlanks.submit=إزالة الفراغات
|
removeBlanks.submit=إزالة الفراغات
|
||||||
|
|
||||||
|
|
||||||
|
#removeAnnotations
|
||||||
|
removeAnnotations.title=Remove Annotations
|
||||||
|
removeAnnotations.header=Remove Annotations
|
||||||
|
removeAnnotations.submit=Remove
|
||||||
|
|
||||||
|
|
||||||
#compare
|
#compare
|
||||||
compare.title=يقارن
|
compare.title=يقارن
|
||||||
compare.header=قارن ملفات PDF
|
compare.header=قارن ملفات PDF
|
||||||
|
|
|
@ -119,6 +119,7 @@ adminUserSettings.role=Роля
|
||||||
adminUserSettings.actions=Действия
|
adminUserSettings.actions=Действия
|
||||||
adminUserSettings.apiUser=Ограничен API потребител
|
adminUserSettings.apiUser=Ограничен API потребител
|
||||||
adminUserSettings.webOnlyUser=Само за уеб-потребител
|
adminUserSettings.webOnlyUser=Само за уеб-потребител
|
||||||
|
adminUserSettings.demoUser=Demo User (No custom settings)
|
||||||
adminUserSettings.forceChange=Принудете потребителя да промени потребителското име/парола при влизане
|
adminUserSettings.forceChange=Принудете потребителя да промени потребителското име/парола при влизане
|
||||||
adminUserSettings.submit=Съхранете потребителя
|
adminUserSettings.submit=Съхранете потребителя
|
||||||
|
|
||||||
|
@ -255,6 +256,10 @@ home.removeBlanks.title=Премахване на празни страници
|
||||||
home.removeBlanks.desc=Открива и премахва празни страници от документ
|
home.removeBlanks.desc=Открива и премахва празни страници от документ
|
||||||
removeBlanks.tags=почистване,рационализиране,без съдържание,организиране
|
removeBlanks.tags=почистване,рационализиране,без съдържание,организиране
|
||||||
|
|
||||||
|
home.removeAnnotations.title=Remove Annotations
|
||||||
|
home.removeAnnotations.desc=Removes all comments/annotations from a PDF
|
||||||
|
removeAnnotations.tags=comments,highlight,notes,markup,remove
|
||||||
|
|
||||||
home.compare.title=Сравнете
|
home.compare.title=Сравнете
|
||||||
home.compare.desc=Сравнява и показва разликите между 2 PDF документа
|
home.compare.desc=Сравнява и показва разликите между 2 PDF документа
|
||||||
compare.tags=разграничаване,контраст,промени,анализ
|
compare.tags=разграничаване,контраст,промени,анализ
|
||||||
|
@ -539,6 +544,12 @@ removeBlanks.whitePercentDesc=Процент от страницата, коят
|
||||||
removeBlanks.submit=Премахване на празни места
|
removeBlanks.submit=Премахване на празни места
|
||||||
|
|
||||||
|
|
||||||
|
#removeAnnotations
|
||||||
|
removeAnnotations.title=Remove Annotations
|
||||||
|
removeAnnotations.header=Remove Annotations
|
||||||
|
removeAnnotations.submit=Remove
|
||||||
|
|
||||||
|
|
||||||
#compare
|
#compare
|
||||||
compare.title=Сравнявай
|
compare.title=Сравнявай
|
||||||
compare.header=Сравнявай PDF-и
|
compare.header=Сравнявай PDF-и
|
||||||
|
|
|
@ -119,6 +119,7 @@ adminUserSettings.role=Rol
|
||||||
adminUserSettings.actions=Accions
|
adminUserSettings.actions=Accions
|
||||||
adminUserSettings.apiUser=Usuari amb API limitada
|
adminUserSettings.apiUser=Usuari amb API limitada
|
||||||
adminUserSettings.webOnlyUser=Usuari només WEB
|
adminUserSettings.webOnlyUser=Usuari només WEB
|
||||||
|
adminUserSettings.demoUser=Demo User (No custom settings)
|
||||||
adminUserSettings.forceChange=Force user to change username/password on login
|
adminUserSettings.forceChange=Force user to change username/password on login
|
||||||
adminUserSettings.submit=Desar Usuari
|
adminUserSettings.submit=Desar Usuari
|
||||||
|
|
||||||
|
@ -255,6 +256,10 @@ home.removeBlanks.title=Elimina les pàgines en blanc
|
||||||
home.removeBlanks.desc=Detecta i elimina les pàgines en blanc d'un document
|
home.removeBlanks.desc=Detecta i elimina les pàgines en blanc d'un document
|
||||||
removeBlanks.tags=cleanup,streamline,non-content,organize
|
removeBlanks.tags=cleanup,streamline,non-content,organize
|
||||||
|
|
||||||
|
home.removeAnnotations.title=Remove Annotations
|
||||||
|
home.removeAnnotations.desc=Removes all comments/annotations from a PDF
|
||||||
|
removeAnnotations.tags=comments,highlight,notes,markup,remove
|
||||||
|
|
||||||
home.compare.title=Compara
|
home.compare.title=Compara
|
||||||
home.compare.desc=Compara i mostra les diferències entre 2 documents PDF
|
home.compare.desc=Compara i mostra les diferències entre 2 documents PDF
|
||||||
compare.tags=differentiate,contrast,changes,analysis
|
compare.tags=differentiate,contrast,changes,analysis
|
||||||
|
@ -539,6 +544,12 @@ removeBlanks.whitePercentDesc=Percentatge de pàgina que ha de ser blanca per el
|
||||||
removeBlanks.submit=Elimina els espais en blanc
|
removeBlanks.submit=Elimina els espais en blanc
|
||||||
|
|
||||||
|
|
||||||
|
#removeAnnotations
|
||||||
|
removeAnnotations.title=Remove Annotations
|
||||||
|
removeAnnotations.header=Remove Annotations
|
||||||
|
removeAnnotations.submit=Remove
|
||||||
|
|
||||||
|
|
||||||
#compare
|
#compare
|
||||||
compare.title=Comparar
|
compare.title=Comparar
|
||||||
compare.header=Compara PDF
|
compare.header=Compara PDF
|
||||||
|
|
|
@ -119,6 +119,7 @@ adminUserSettings.role=Rolle
|
||||||
adminUserSettings.actions=Aktion
|
adminUserSettings.actions=Aktion
|
||||||
adminUserSettings.apiUser=Eingeschränkter API-Benutzer
|
adminUserSettings.apiUser=Eingeschränkter API-Benutzer
|
||||||
adminUserSettings.webOnlyUser=Nur Web-Benutzer
|
adminUserSettings.webOnlyUser=Nur Web-Benutzer
|
||||||
|
adminUserSettings.demoUser=Demo User (No custom settings)
|
||||||
adminUserSettings.forceChange=Benutzer dazu zwingen, Benutzernamen/Passwort bei der Anmeldung zu ändern
|
adminUserSettings.forceChange=Benutzer dazu zwingen, Benutzernamen/Passwort bei der Anmeldung zu ändern
|
||||||
adminUserSettings.submit=Benutzer speichern
|
adminUserSettings.submit=Benutzer speichern
|
||||||
|
|
||||||
|
@ -255,6 +256,10 @@ home.removeBlanks.title=Leere Seiten entfernen
|
||||||
home.removeBlanks.desc=Erkennt und entfernt leere Seiten aus einem Dokument
|
home.removeBlanks.desc=Erkennt und entfernt leere Seiten aus einem Dokument
|
||||||
removeBlanks.tags=cleanup,streamline,non-content,organize
|
removeBlanks.tags=cleanup,streamline,non-content,organize
|
||||||
|
|
||||||
|
home.removeAnnotations.title=Remove Annotations
|
||||||
|
home.removeAnnotations.desc=Removes all comments/annotations from a PDF
|
||||||
|
removeAnnotations.tags=comments,highlight,notes,markup,remove
|
||||||
|
|
||||||
home.compare.title=Vergleichen
|
home.compare.title=Vergleichen
|
||||||
home.compare.desc=Vergleicht und zeigt die Unterschiede zwischen zwei PDF-Dokumenten an
|
home.compare.desc=Vergleicht und zeigt die Unterschiede zwischen zwei PDF-Dokumenten an
|
||||||
compare.tags=differentiate,contrast,changes,analysis
|
compare.tags=differentiate,contrast,changes,analysis
|
||||||
|
@ -539,6 +544,12 @@ removeBlanks.whitePercentDesc=Prozentsatz der Seite, die weiß sein muss, um ent
|
||||||
removeBlanks.submit=Leere Seiten entfernen
|
removeBlanks.submit=Leere Seiten entfernen
|
||||||
|
|
||||||
|
|
||||||
|
#removeAnnotations
|
||||||
|
removeAnnotations.title=Remove Annotations
|
||||||
|
removeAnnotations.header=Remove Annotations
|
||||||
|
removeAnnotations.submit=Remove
|
||||||
|
|
||||||
|
|
||||||
#compare
|
#compare
|
||||||
compare.title=Vergleichen
|
compare.title=Vergleichen
|
||||||
compare.header=PDFs vergleichen
|
compare.header=PDFs vergleichen
|
||||||
|
|
|
@ -119,6 +119,7 @@ adminUserSettings.role=\u03A1\u03CC\u03BB\u03BF\u03C2
|
||||||
adminUserSettings.actions=\u0395\u03BD\u03AD\u03C1\u03B3\u03B5\u03B9\u03B5\u03C2
|
adminUserSettings.actions=\u0395\u03BD\u03AD\u03C1\u03B3\u03B5\u03B9\u03B5\u03C2
|
||||||
adminUserSettings.apiUser=\u03A0\u03B5\u03C1\u03B9\u03BF\u03C1\u03B9\u03C3\u03BC\u03AD\u03BD\u03BF\u03C2 \u03A7\u03C1\u03AE\u03C3\u03C4\u03B7\u03C2 \u03B3\u03B9\u03B1 \u03B4\u03B9\u03B5\u03C0\u03B1\u03C6\u03AE \u03C0\u03C1\u03BF\u03B3\u03C1\u03B1\u03BC\u03BC\u03B1\u03C4\u03B9\u03C3\u03BC\u03BF\u03CD \u03B5\u03C6\u03B1\u03C1\u03BC\u03BF\u03B3\u03CE\u03BD (API User)
|
adminUserSettings.apiUser=\u03A0\u03B5\u03C1\u03B9\u03BF\u03C1\u03B9\u03C3\u03BC\u03AD\u03BD\u03BF\u03C2 \u03A7\u03C1\u03AE\u03C3\u03C4\u03B7\u03C2 \u03B3\u03B9\u03B1 \u03B4\u03B9\u03B5\u03C0\u03B1\u03C6\u03AE \u03C0\u03C1\u03BF\u03B3\u03C1\u03B1\u03BC\u03BC\u03B1\u03C4\u03B9\u03C3\u03BC\u03BF\u03CD \u03B5\u03C6\u03B1\u03C1\u03BC\u03BF\u03B3\u03CE\u03BD (API User)
|
||||||
adminUserSettings.webOnlyUser=\u03A7\u03C1\u03AE\u03C3\u03C4\u03B7\u03C2 \u03BC\u03CC\u03BD\u03BF \u0399\u03C3\u03C4\u03BF\u03CD
|
adminUserSettings.webOnlyUser=\u03A7\u03C1\u03AE\u03C3\u03C4\u03B7\u03C2 \u03BC\u03CC\u03BD\u03BF \u0399\u03C3\u03C4\u03BF\u03CD
|
||||||
|
adminUserSettings.demoUser=Demo User (No custom settings)
|
||||||
adminUserSettings.forceChange=\u0391\u03BD\u03B1\u03B3\u03BA\u03AC\u03C3\u03C4\u03B5 \u03C4\u03BF\u03BD \u03C7\u03C1\u03AE\u03C3\u03C4\u03B7 \u03BD\u03B1 \u03B1\u03BB\u03BB\u03AC\u03BE\u03B5\u03B9 \u03C4\u03BF \u03CC\u03BD\u03BF\u03BC\u03B1 \u03C7\u03C1\u03AE\u03C3\u03C4\u03B7/\u03BA\u03C9\u03B4\u03B9\u03BA\u03CC \u03C0\u03C1\u03CC\u03C3\u03B2\u03B1\u03C3\u03B7\u03C2 \u03BA\u03B1\u03C4\u03AC \u03C4\u03B7 \u03C3\u03CD\u03BD\u03B4\u03B5\u03C3\u03B7
|
adminUserSettings.forceChange=\u0391\u03BD\u03B1\u03B3\u03BA\u03AC\u03C3\u03C4\u03B5 \u03C4\u03BF\u03BD \u03C7\u03C1\u03AE\u03C3\u03C4\u03B7 \u03BD\u03B1 \u03B1\u03BB\u03BB\u03AC\u03BE\u03B5\u03B9 \u03C4\u03BF \u03CC\u03BD\u03BF\u03BC\u03B1 \u03C7\u03C1\u03AE\u03C3\u03C4\u03B7/\u03BA\u03C9\u03B4\u03B9\u03BA\u03CC \u03C0\u03C1\u03CC\u03C3\u03B2\u03B1\u03C3\u03B7\u03C2 \u03BA\u03B1\u03C4\u03AC \u03C4\u03B7 \u03C3\u03CD\u03BD\u03B4\u03B5\u03C3\u03B7
|
||||||
adminUserSettings.submit=\u0391\u03C0\u03BF\u03B8\u03AE\u03BA\u03B5\u03C5\u03C3\u03B7 \u03A7\u03C1\u03AE\u03C3\u03C4\u03B7
|
adminUserSettings.submit=\u0391\u03C0\u03BF\u03B8\u03AE\u03BA\u03B5\u03C5\u03C3\u03B7 \u03A7\u03C1\u03AE\u03C3\u03C4\u03B7
|
||||||
|
|
||||||
|
@ -255,6 +256,10 @@ home.removeBlanks.title=\u0391\u03C6\u03B1\u03AF\u03C1\u03B5\u03C3\u03B7 \u03BA\
|
||||||
home.removeBlanks.desc=\u0391\u03BD\u03AF\u03C7\u03B5\u03C5\u03C3\u03B7 \u03BA\u03B1\u03B9 \u03B1\u03C6\u03B1\u03AF\u03C1\u03B5\u03C3\u03B7 \u03BA\u03B5\u03BD\u03CE\u03BD \u03C3\u03B5\u03BB\u03AF\u03B4\u03C9\u03BD \u03B1\u03C0\u03CC \u03AD\u03BD\u03B1 \u03AD\u03B3\u03B3\u03C1\u03B1\u03C6\u03BF
|
home.removeBlanks.desc=\u0391\u03BD\u03AF\u03C7\u03B5\u03C5\u03C3\u03B7 \u03BA\u03B1\u03B9 \u03B1\u03C6\u03B1\u03AF\u03C1\u03B5\u03C3\u03B7 \u03BA\u03B5\u03BD\u03CE\u03BD \u03C3\u03B5\u03BB\u03AF\u03B4\u03C9\u03BD \u03B1\u03C0\u03CC \u03AD\u03BD\u03B1 \u03AD\u03B3\u03B3\u03C1\u03B1\u03C6\u03BF
|
||||||
removeBlanks.tags=cleanup,streamline,non-content,organize
|
removeBlanks.tags=cleanup,streamline,non-content,organize
|
||||||
|
|
||||||
|
home.removeAnnotations.title=Remove Annotations
|
||||||
|
home.removeAnnotations.desc=Removes all comments/annotations from a PDF
|
||||||
|
removeAnnotations.tags=comments,highlight,notes,markup,remove
|
||||||
|
|
||||||
home.compare.title=\u03A3\u03CD\u03B3\u03BA\u03C1\u03B9\u03C3\u03B7
|
home.compare.title=\u03A3\u03CD\u03B3\u03BA\u03C1\u03B9\u03C3\u03B7
|
||||||
home.compare.desc=\u03A3\u03CD\u03B3\u03BA\u03C1\u03B9\u03C3\u03B7 \u03BA\u03B1\u03B9 \u03B5\u03BC\u03C6\u03AC\u03BD\u03B9\u03C3\u03B7 \u03C4\u03C9\u03BD \u03B4\u03B9\u03B1\u03C6\u03BF\u03C1\u03CE\u03BD \u03BC\u03B5\u03C4\u03B1\u03BE\u03CD \u03B4\u03CD\u03BF PDF \u03B1\u03C1\u03C7\u03B5\u03AF\u03C9\u03BD
|
home.compare.desc=\u03A3\u03CD\u03B3\u03BA\u03C1\u03B9\u03C3\u03B7 \u03BA\u03B1\u03B9 \u03B5\u03BC\u03C6\u03AC\u03BD\u03B9\u03C3\u03B7 \u03C4\u03C9\u03BD \u03B4\u03B9\u03B1\u03C6\u03BF\u03C1\u03CE\u03BD \u03BC\u03B5\u03C4\u03B1\u03BE\u03CD \u03B4\u03CD\u03BF PDF \u03B1\u03C1\u03C7\u03B5\u03AF\u03C9\u03BD
|
||||||
compare.tags=differentiate,contrast,changes,analysis
|
compare.tags=differentiate,contrast,changes,analysis
|
||||||
|
@ -539,6 +544,12 @@ removeBlanks.whitePercentDesc=\u03A4\u03BF \u03C0\u03BF\u03C3\u03BF\u03C3\u03C4\
|
||||||
removeBlanks.submit=\u0391\u03C6\u03B1\u03AF\u03C1\u03B5\u03C3\u03B7 \u039A\u03B5\u03BD\u03CE\u03BD
|
removeBlanks.submit=\u0391\u03C6\u03B1\u03AF\u03C1\u03B5\u03C3\u03B7 \u039A\u03B5\u03BD\u03CE\u03BD
|
||||||
|
|
||||||
|
|
||||||
|
#removeAnnotations
|
||||||
|
removeAnnotations.title=Remove Annotations
|
||||||
|
removeAnnotations.header=Remove Annotations
|
||||||
|
removeAnnotations.submit=Remove
|
||||||
|
|
||||||
|
|
||||||
#compare
|
#compare
|
||||||
compare.title=\u03A3\u03CD\u03B3\u03BA\u03C1\u03B9\u03C3\u03B7
|
compare.title=\u03A3\u03CD\u03B3\u03BA\u03C1\u03B9\u03C3\u03B7
|
||||||
compare.header=\u03A3\u03CD\u03B3\u03BA\u03C1\u03B9\u03C3\u03B7 PDFs
|
compare.header=\u03A3\u03CD\u03B3\u03BA\u03C1\u03B9\u03C3\u03B7 PDFs
|
||||||
|
|
|
@ -119,6 +119,7 @@ adminUserSettings.role=Role
|
||||||
adminUserSettings.actions=Actions
|
adminUserSettings.actions=Actions
|
||||||
adminUserSettings.apiUser=Limited API User
|
adminUserSettings.apiUser=Limited API User
|
||||||
adminUserSettings.webOnlyUser=Web Only User
|
adminUserSettings.webOnlyUser=Web Only User
|
||||||
|
adminUserSettings.demoUser=Demo User (No custom settings)
|
||||||
adminUserSettings.forceChange = Force user to change username/password on login
|
adminUserSettings.forceChange = Force user to change username/password on login
|
||||||
adminUserSettings.submit=Save User
|
adminUserSettings.submit=Save User
|
||||||
|
|
||||||
|
|
|
@ -119,6 +119,7 @@ adminUserSettings.role=Role
|
||||||
adminUserSettings.actions=Actions
|
adminUserSettings.actions=Actions
|
||||||
adminUserSettings.apiUser=Limited API User
|
adminUserSettings.apiUser=Limited API User
|
||||||
adminUserSettings.webOnlyUser=Web Only User
|
adminUserSettings.webOnlyUser=Web Only User
|
||||||
|
adminUserSettings.demoUser=Demo User (No custom settings)
|
||||||
adminUserSettings.forceChange=Force user to change username/password on login
|
adminUserSettings.forceChange=Force user to change username/password on login
|
||||||
adminUserSettings.submit=Save User
|
adminUserSettings.submit=Save User
|
||||||
|
|
||||||
|
|
|
@ -119,6 +119,7 @@ adminUserSettings.role=Rol
|
||||||
adminUserSettings.actions=Acciones
|
adminUserSettings.actions=Acciones
|
||||||
adminUserSettings.apiUser=Usuario limitado de API
|
adminUserSettings.apiUser=Usuario limitado de API
|
||||||
adminUserSettings.webOnlyUser=Usuario solo web
|
adminUserSettings.webOnlyUser=Usuario solo web
|
||||||
|
adminUserSettings.demoUser=Demo User (No custom settings)
|
||||||
adminUserSettings.forceChange=Forzar usuario a cambiar usuario/contraseña en el acceso
|
adminUserSettings.forceChange=Forzar usuario a cambiar usuario/contraseña en el acceso
|
||||||
adminUserSettings.submit=Guardar Usuario
|
adminUserSettings.submit=Guardar Usuario
|
||||||
|
|
||||||
|
@ -255,6 +256,10 @@ home.removeBlanks.title=Eliminar páginas en blanco
|
||||||
home.removeBlanks.desc=Detectar y eliminar páginas en blanco de un documento
|
home.removeBlanks.desc=Detectar y eliminar páginas en blanco de un documento
|
||||||
removeBlanks.tags=limpieza,dinámica,sin contenido,organizar
|
removeBlanks.tags=limpieza,dinámica,sin contenido,organizar
|
||||||
|
|
||||||
|
home.removeAnnotations.title=Eliminar Anotaciones
|
||||||
|
home.removeAnnotations.desc=Eliminar todos los comentarios/anotaciones de un PDF
|
||||||
|
removeAnnotations.tags=comentarios,subrayar,notas,margen,eliminar
|
||||||
|
|
||||||
home.compare.title=Comparar
|
home.compare.title=Comparar
|
||||||
home.compare.desc=Comparar y mostrar las diferencias entre 2 documentos PDF
|
home.compare.desc=Comparar y mostrar las diferencias entre 2 documentos PDF
|
||||||
compare.tags=diferenciar,contrastar,cambios,análisis
|
compare.tags=diferenciar,contrastar,cambios,análisis
|
||||||
|
@ -350,7 +355,7 @@ home.overlay-pdfs.title=Superponer PDFs
|
||||||
home.overlay-pdfs.desc=Superponer PDFs encima de otro PDF
|
home.overlay-pdfs.desc=Superponer PDFs encima de otro PDF
|
||||||
overlay-pdfs.tags=Superponer
|
overlay-pdfs.tags=Superponer
|
||||||
|
|
||||||
home.split-by-sections.title=Dividir PDF por Seccioned
|
home.split-by-sections.title=Dividir PDF por Secciones
|
||||||
home.split-by-sections.desc=Dividir cada página de un PDF en secciones verticales y horizontales más pequeñas
|
home.split-by-sections.desc=Dividir cada página de un PDF en secciones verticales y horizontales más pequeñas
|
||||||
split-by-sections.tags=Dividir sección, Dividir, Personalizar
|
split-by-sections.tags=Dividir sección, Dividir, Personalizar
|
||||||
|
|
||||||
|
@ -539,6 +544,12 @@ removeBlanks.whitePercentDesc=Porcentaje de página que debe ser blanca para ser
|
||||||
removeBlanks.submit=Eliminar espacios en blanco
|
removeBlanks.submit=Eliminar espacios en blanco
|
||||||
|
|
||||||
|
|
||||||
|
#removeAnnotations
|
||||||
|
removeAnnotations.title=Eliminar anotaciones
|
||||||
|
removeAnnotations.header=Eliminar anotaciones
|
||||||
|
removeAnnotations.submit=Eliminar
|
||||||
|
|
||||||
|
|
||||||
#compare
|
#compare
|
||||||
compare.title=Comparar
|
compare.title=Comparar
|
||||||
compare.header=Comparar archivos PDF
|
compare.header=Comparar archivos PDF
|
||||||
|
@ -861,7 +872,7 @@ split-by-size-or-count.submit=Enviar
|
||||||
|
|
||||||
#overlay-pdfs
|
#overlay-pdfs
|
||||||
overlay-pdfs.header=Superponer archivos PDF
|
overlay-pdfs.header=Superponer archivos PDF
|
||||||
overlay-pdfs.baseFile.label=Selleccione archivo PDF de base
|
overlay-pdfs.baseFile.label=Seleccione archivo PDF de base
|
||||||
overlay-pdfs.overlayFiles.label=Seleccione archivos PDF a superponer
|
overlay-pdfs.overlayFiles.label=Seleccione archivos PDF a superponer
|
||||||
overlay-pdfs.mode.label=Seleccione modo de superposición
|
overlay-pdfs.mode.label=Seleccione modo de superposición
|
||||||
overlay-pdfs.mode.sequential=Superposición Sequencial
|
overlay-pdfs.mode.sequential=Superposición Sequencial
|
||||||
|
|
|
@ -119,6 +119,7 @@ adminUserSettings.role=Rol
|
||||||
adminUserSettings.actions=Ekintzak
|
adminUserSettings.actions=Ekintzak
|
||||||
adminUserSettings.apiUser=APIren erabiltzaile mugatua
|
adminUserSettings.apiUser=APIren erabiltzaile mugatua
|
||||||
adminUserSettings.webOnlyUser=Web-erabiltzailea bakarrik
|
adminUserSettings.webOnlyUser=Web-erabiltzailea bakarrik
|
||||||
|
adminUserSettings.demoUser=Demo User (No custom settings)
|
||||||
adminUserSettings.forceChange=Force user to change username/password on login
|
adminUserSettings.forceChange=Force user to change username/password on login
|
||||||
adminUserSettings.submit=Gorde Erabiltzailea
|
adminUserSettings.submit=Gorde Erabiltzailea
|
||||||
|
|
||||||
|
@ -255,6 +256,10 @@ home.removeBlanks.title=Ezabatu orrialde zuriak
|
||||||
home.removeBlanks.desc=Detektatu orrialde zuriak eta dokumentutik ezabatu
|
home.removeBlanks.desc=Detektatu orrialde zuriak eta dokumentutik ezabatu
|
||||||
removeBlanks.tags=cleanup,streamline,non-content,organize
|
removeBlanks.tags=cleanup,streamline,non-content,organize
|
||||||
|
|
||||||
|
home.removeAnnotations.title=Remove Annotations
|
||||||
|
home.removeAnnotations.desc=Removes all comments/annotations from a PDF
|
||||||
|
removeAnnotations.tags=comments,highlight,notes,markup,remove
|
||||||
|
|
||||||
home.compare.title=Konparatu
|
home.compare.title=Konparatu
|
||||||
home.compare.desc=Konparatu eta erakutsi 2 PDF dokumenturen aldeak
|
home.compare.desc=Konparatu eta erakutsi 2 PDF dokumenturen aldeak
|
||||||
compare.tags=differentiate,contrast,changes,analysis
|
compare.tags=differentiate,contrast,changes,analysis
|
||||||
|
@ -539,6 +544,12 @@ removeBlanks.whitePercentDesc=Zuria izan behar den orriaren ehunekoa ezabatua iz
|
||||||
removeBlanks.submit=Ezabatu zuriuneak
|
removeBlanks.submit=Ezabatu zuriuneak
|
||||||
|
|
||||||
|
|
||||||
|
#removeAnnotations
|
||||||
|
removeAnnotations.title=Remove Annotations
|
||||||
|
removeAnnotations.header=Remove Annotations
|
||||||
|
removeAnnotations.submit=Remove
|
||||||
|
|
||||||
|
|
||||||
#compare
|
#compare
|
||||||
compare.title=Konparatu
|
compare.title=Konparatu
|
||||||
compare.header=Konparatu PDF fitxategiak
|
compare.header=Konparatu PDF fitxategiak
|
||||||
|
|
|
@ -119,6 +119,7 @@ adminUserSettings.role=Rôle
|
||||||
adminUserSettings.actions=Actions
|
adminUserSettings.actions=Actions
|
||||||
adminUserSettings.apiUser=Utilisateur API limité
|
adminUserSettings.apiUser=Utilisateur API limité
|
||||||
adminUserSettings.webOnlyUser=Utilisateur Web uniquement
|
adminUserSettings.webOnlyUser=Utilisateur Web uniquement
|
||||||
|
adminUserSettings.demoUser=Demo User (No custom settings)
|
||||||
adminUserSettings.forceChange=Forcer l\u2019utilisateur à changer son nom d\u2019utilisateur/mot de passe lors de la connexion
|
adminUserSettings.forceChange=Forcer l\u2019utilisateur à changer son nom d\u2019utilisateur/mot de passe lors de la connexion
|
||||||
adminUserSettings.submit=Ajouter
|
adminUserSettings.submit=Ajouter
|
||||||
|
|
||||||
|
@ -255,6 +256,10 @@ home.removeBlanks.title=Supprimer les pages vierges
|
||||||
home.removeBlanks.desc=Détectez et supprimez les pages vierges d\u2019un PDF.
|
home.removeBlanks.desc=Détectez et supprimez les pages vierges d\u2019un PDF.
|
||||||
removeBlanks.tags=pages vierges,supprimer,nettoyer,cleanup,streamline,non-content,organize
|
removeBlanks.tags=pages vierges,supprimer,nettoyer,cleanup,streamline,non-content,organize
|
||||||
|
|
||||||
|
home.removeAnnotations.title=Remove Annotations
|
||||||
|
home.removeAnnotations.desc=Removes all comments/annotations from a PDF
|
||||||
|
removeAnnotations.tags=comments,highlight,notes,markup,remove
|
||||||
|
|
||||||
home.compare.title=Comparer
|
home.compare.title=Comparer
|
||||||
home.compare.desc=Comparez et visualisez les différences entre deux PDF.
|
home.compare.desc=Comparez et visualisez les différences entre deux PDF.
|
||||||
compare.tags=comparer,analyser,differentiate,contrast,changes,analysis
|
compare.tags=comparer,analyser,differentiate,contrast,changes,analysis
|
||||||
|
@ -539,6 +544,12 @@ removeBlanks.whitePercentDesc=Pourcentage de la page qui doit contenir des pixel
|
||||||
removeBlanks.submit=Supprimer les pages vierges
|
removeBlanks.submit=Supprimer les pages vierges
|
||||||
|
|
||||||
|
|
||||||
|
#removeAnnotations
|
||||||
|
removeAnnotations.title=Remove Annotations
|
||||||
|
removeAnnotations.header=Remove Annotations
|
||||||
|
removeAnnotations.submit=Remove
|
||||||
|
|
||||||
|
|
||||||
#compare
|
#compare
|
||||||
compare.title=Comparer
|
compare.title=Comparer
|
||||||
compare.header=Comparer
|
compare.header=Comparer
|
||||||
|
|
|
@ -92,7 +92,7 @@ account.title=खाता सेटिंग्स
|
||||||
account.accountSettings=खाता सेटिंग्स
|
account.accountSettings=खाता सेटिंग्स
|
||||||
account.adminSettings=व्यवस्थापक सेटिंग्स - उपयोगकर्ताओं को देखें और जोड़ें
|
account.adminSettings=व्यवस्थापक सेटिंग्स - उपयोगकर्ताओं को देखें और जोड़ें
|
||||||
account.userControlSettings=उपयोगकर्ता नियंत्रण सेटिंग्स
|
account.userControlSettings=उपयोगकर्ता नियंत्रण सेटिंग्स
|
||||||
account.changeUsername=नया उपयोगकर्ता नाम
|
account.changeUsername=उपयोगकर्ता नाम परिवर्तन करें
|
||||||
account.changeUsername=उपयोगकर्ता नाम परिवर्तन करें
|
account.changeUsername=उपयोगकर्ता नाम परिवर्तन करें
|
||||||
account.password=पासवर्ड पुष्टि
|
account.password=पासवर्ड पुष्टि
|
||||||
account.oldPassword=पुराना पासवर्ड
|
account.oldPassword=पुराना पासवर्ड
|
||||||
|
@ -119,6 +119,7 @@ adminUserSettings.role=रोल
|
||||||
adminUserSettings.actions=क्रियाएँ
|
adminUserSettings.actions=क्रियाएँ
|
||||||
adminUserSettings.apiUser=सीमित API उपयोगकर्ता
|
adminUserSettings.apiUser=सीमित API उपयोगकर्ता
|
||||||
adminUserSettings.webOnlyUser=केवल वेब उपयोगकर्ता
|
adminUserSettings.webOnlyUser=केवल वेब उपयोगकर्ता
|
||||||
|
adminUserSettings.demoUser=Demo User (No custom settings)
|
||||||
adminUserSettings.forceChange=उपयोगकर्ता को लॉगिन पर उपयोगकर्ता नाम/पासवर्ड बदलने के लिए मजबूर करें
|
adminUserSettings.forceChange=उपयोगकर्ता को लॉगिन पर उपयोगकर्ता नाम/पासवर्ड बदलने के लिए मजबूर करें
|
||||||
adminUserSettings.submit=उपयोगकर्ता को सहेजें
|
adminUserSettings.submit=उपयोगकर्ता को सहेजें
|
||||||
|
|
||||||
|
@ -175,24 +176,24 @@ home.permissions.title=अनुमतियाँ बदलें
|
||||||
home.permissions.desc=अपने पीडीएफ़ दस्तावेज़ की अनुमतियाँ बदलें
|
home.permissions.desc=अपने पीडीएफ़ दस्तावेज़ की अनुमतियाँ बदलें
|
||||||
permissions.tags=पढ़ें, लिखें, संपादित करें, प्रिंट
|
permissions.tags=पढ़ें, लिखें, संपादित करें, प्रिंट
|
||||||
|
|
||||||
|
|
||||||
home.removePages.title=हटाएं
|
home.removePages.title=हटाएं
|
||||||
home.removePages.desc=अपने पीडीएफ़ दस्तावेज़ से अनचाहे पृष्ठों को हटाएं।
|
home.removePages.desc=अपने पीडीएफ़ दस्तावेज़ से अनचाहे पृष्ठों को हटाएं।
|
||||||
removePages.tags=पृष्ठ हटाएं, पृष्ठ मिटाएं
|
removePages.tags=पृष्ठ हटाएं, पृष्ठ मिटाएं
|
||||||
|
|
||||||
home.addPassword.title=पासवर्ड जोड़ें
|
home.addPassword.title=पासवर्ड जोड़ें
|
||||||
home.addPassword.desc=अपने पीडीएफ़ दस्तावेज़ को एक पासवर्ड से एन्क्रिप्ट करें।
|
home.addPassword.desc=अपने पीडीएफ़ दस्तावेज़ को एक पासवर्ड से एन्क्रिप्ट करें।
|
||||||
addPassword.tags=सुरक्षित, सुरक्षा
|
addPassword.tags=सुरक्षित, सुरक्षा
|
||||||
|
|
||||||
home.removePassword.title=पासवर्ड हटाएं
|
home.removePassword.title=पासवर्ड हटाएं
|
||||||
home.removePassword.desc=अपने पीडीएफ़ दस्तावेज़ से पासवर्ड सुरक्षा को हटाएं।
|
home.removePassword.desc=अपने पीडीएफ़ दस्तावेज़ से पासवर्ड सुरक्षा को हटाएं।
|
||||||
removePassword.tags=सुरक्षित, डिक्रिप्ट, सुरक्षा, पासवर्ड हटाएं, पासवर्ड मिटाएं
|
removePassword.tags=सुरक्षित, डिक्रिप्ट, सुरक्षा, पासवर्ड हटाएं, पासवर्ड मिटाएं
|
||||||
home.compressPdfs.title=Compress
|
|
||||||
home.compressPdfs.desc=Compress PDFs to reduce their file size.
|
|
||||||
compressPdfs.tags=squish,small,tiny
|
|
||||||
|
|
||||||
|
home.compressPdfs.title=संकुचित करें (कम्प्रेस)
|
||||||
home.compressPdfs.title=कम्प्रेस
|
|
||||||
home.compressPdfs.desc=फ़ाइल का आकार कम करने के लिए PDF को कम्प्रेस करें।
|
home.compressPdfs.desc=फ़ाइल का आकार कम करने के लिए PDF को कम्प्रेस करें।
|
||||||
compressPdfs.tags=स्क्विश, छोटा, छोटा
|
compressPdfs.tags=स्क्विश, छोटा, छोटा
|
||||||
|
|
||||||
|
|
||||||
home.changeMetadata.title=मेटाडेटा बदलें
|
home.changeMetadata.title=मेटाडेटा बदलें
|
||||||
home.changeMetadata.desc=PDF दस्तावेज़ से मेटाडेटा बदलें/हटाएं/जोड़ें।
|
home.changeMetadata.desc=PDF दस्तावेज़ से मेटाडेटा बदलें/हटाएं/जोड़ें।
|
||||||
changeMetadata.tags=शीर्षक, लेखक, तारीख, निर्माण, समय, प्रकाशक, उत्पादक, आँकड़े
|
changeMetadata.tags=शीर्षक, लेखक, तारीख, निर्माण, समय, प्रकाशक, उत्पादक, आँकड़े
|
||||||
|
@ -205,6 +206,7 @@ home.ocr.title=OCR / स्कैन को साफ करें
|
||||||
home.ocr.desc=स्कैन को साफ करता है और पीडीएफ़ में छवियों से पाठ को पहचानता है और टेक्स्ट के रूप में फिर से जोड़ता है।
|
home.ocr.desc=स्कैन को साफ करता है और पीडीएफ़ में छवियों से पाठ को पहचानता है और टेक्स्ट के रूप में फिर से जोड़ता है।
|
||||||
ocr.tags=पहचान, टेक्स्ट, छवि, स्कैन, पढ़ें, पहचान, पता लगाना, संपादनीय
|
ocr.tags=पहचान, टेक्स्ट, छवि, स्कैन, पढ़ें, पहचान, पता लगाना, संपादनीय
|
||||||
|
|
||||||
|
|
||||||
home.extractImages.title=छवियां निकालें
|
home.extractImages.title=छवियां निकालें
|
||||||
home.extractImages.desc=पीडीएफ़ से सभी छवियों को निकालता है और उन्हें ज़िप में सहेजता है
|
home.extractImages.desc=पीडीएफ़ से सभी छवियों को निकालता है और उन्हें ज़िप में सहेजता है
|
||||||
extractImages.tags=चित्र, फोटो, सहेजें, संग्रह, ज़िप, कैप्चर, ग्रैब
|
extractImages.tags=चित्र, फोटो, सहेजें, संग्रह, ज़िप, कैप्चर, ग्रैब
|
||||||
|
@ -213,7 +215,6 @@ home.pdfToPDFA.title=PDF से PDF/A में
|
||||||
home.pdfToPDFA.desc=लंबे समय के लिए स्टोरेज के लिए पीडीएफ़ को पीडीएफ़/ए में रूपांतरित करें
|
home.pdfToPDFA.desc=लंबे समय के लिए स्टोरेज के लिए पीडीएफ़ को पीडीएफ़/ए में रूपांतरित करें
|
||||||
pdfToPDFA.tags=संग्रह, लंबे समय के लिए, मानक, परिवर्तन, स्टोरेज, संरक्षण
|
pdfToPDFA.tags=संग्रह, लंबे समय के लिए, मानक, परिवर्तन, स्टोरेज, संरक्षण
|
||||||
|
|
||||||
|
|
||||||
home.PDFToWord.title=PDF से वर्ड में
|
home.PDFToWord.title=PDF से वर्ड में
|
||||||
home.PDFToWord.desc=PDF को वर्ड प्रारूपों में रूपांतरित करें (DOC, DOCX और ODT)
|
home.PDFToWord.desc=PDF को वर्ड प्रारूपों में रूपांतरित करें (DOC, DOCX और ODT)
|
||||||
PDFToWord.tags=doc,docx,odt,word,परिवर्तन,प्रारूप,रूपांतरण,ऑफिस,माइक्रोसॉफ्ट,डॉक फ़ाइल
|
PDFToWord.tags=doc,docx,odt,word,परिवर्तन,प्रारूप,रूपांतरण,ऑफिस,माइक्रोसॉफ्ट,डॉक फ़ाइल
|
||||||
|
@ -230,6 +231,7 @@ home.PDFToHTML.title=PDF से HTML में
|
||||||
home.PDFToHTML.desc=PDF को HTML प्रारूप में रूपांतरित करें
|
home.PDFToHTML.desc=PDF को HTML प्रारूप में रूपांतरित करें
|
||||||
PDFToHTML.tags=वेब सामग्री, ब्राउज़र अनुकूल
|
PDFToHTML.tags=वेब सामग्री, ब्राउज़र अनुकूल
|
||||||
|
|
||||||
|
|
||||||
home.PDFToXML.title=PDF से XML में
|
home.PDFToXML.title=PDF से XML में
|
||||||
home.PDFToXML.desc=PDF को XML प्रारूप में रूपांतरित करें
|
home.PDFToXML.desc=PDF को XML प्रारूप में रूपांतरित करें
|
||||||
PDFToXML.tags=डेटा-निकालन, संरचित सामग्री, अंतरसंवाद, परिवर्तन, रूपांतरण
|
PDFToXML.tags=डेटा-निकालन, संरचित सामग्री, अंतरसंवाद, परिवर्तन, रूपांतरण
|
||||||
|
@ -238,7 +240,6 @@ home.ScannerImageSplit.title=स्कैन की गई फोटो का
|
||||||
home.ScannerImageSplit.desc=एक फोटो/PDF के भीतर से कई फोटो को विभाजित करता है
|
home.ScannerImageSplit.desc=एक फोटो/PDF के भीतर से कई फोटो को विभाजित करता है
|
||||||
ScannerImageSplit.tags=अलग, ऑटो-डिटेक्ट, स्कैन, मल्टी-फोटो, संगठित
|
ScannerImageSplit.tags=अलग, ऑटो-डिटेक्ट, स्कैन, मल्टी-फोटो, संगठित
|
||||||
|
|
||||||
|
|
||||||
home.sign.title=हस्ताक्षर
|
home.sign.title=हस्ताक्षर
|
||||||
home.sign.desc=हस्ताक्षर को ड्राइंग, पाठ या छवि के रूप में पीडीएफ़ में जोड़ता है।
|
home.sign.desc=हस्ताक्षर को ड्राइंग, पाठ या छवि के रूप में पीडीएफ़ में जोड़ता है।
|
||||||
sign.tags=अधिकृत करें, आदेश, ड्राइंग-हस्ताक्षर, पाठ-हस्ताक्षर, छवि-हस्ताक्षर
|
sign.tags=अधिकृत करें, आदेश, ड्राइंग-हस्ताक्षर, पाठ-हस्ताक्षर, छवि-हस्ताक्षर
|
||||||
|
@ -331,10 +332,10 @@ home.PdfToSinglePage.title=पीडीएफ़ से एक बड़े प
|
||||||
home.PdfToSinglePage.desc=सभी पीडीएफ़ पेजों को एक बड़े एकल पृष्ठ में मर्ज करता है
|
home.PdfToSinglePage.desc=सभी पीडीएफ़ पेजों को एक बड़े एकल पृष्ठ में मर्ज करता है
|
||||||
PdfToSinglePage.tags=एकल पृष्ठ
|
PdfToSinglePage.tags=एकल पृष्ठ
|
||||||
|
|
||||||
|
|
||||||
home.showJS.title=जावास्क्रिप्ट दिखाएं
|
home.showJS.title=जावास्क्रिप्ट दिखाएं
|
||||||
home.showJS.desc=पीडीएफ़ में डाला गया कोई भी जावास्क्रिप्ट खोजता है और प्रदर्शित करता है
|
home.showJS.desc=पीडीएफ़ में डाला गया कोई भी जावास्क्रिप्ट खोजता है और प्रदर्शित करता है
|
||||||
showJS.tags=जेएस
|
showJS.tags=गोपनीयकरण, छिपाना, काला करना, काला, मार्कर, छिपा हुआ
|
||||||
|
|
||||||
|
|
||||||
home.autoRedact.title=स्वतः गोपनीयकरण
|
home.autoRedact.title=स्वतः गोपनीयकरण
|
||||||
home.autoRedact.desc=प्रविष्ट पाठ के आधार पर पीडीएफ़ में पाठ को स्वतः गोपनीयकरित(काला करें)
|
home.autoRedact.desc=प्रविष्ट पाठ के आधार पर पीडीएफ़ में पाठ को स्वतः गोपनीयकरित(काला करें)
|
||||||
|
@ -462,9 +463,9 @@ addPageNumbers.submit=पृष्ठ संख्या जोड़ें
|
||||||
|
|
||||||
|
|
||||||
#auto-rename
|
#auto-rename
|
||||||
auto-rename.title=Auto Rename
|
auto-rename.title=स्वतः नाम परिवर्तन (खुद ब खुद नाम बदलें)
|
||||||
auto-rename.header=Auto Rename PDF
|
auto-rename.header=स्वतः नाम परिवर्तन पीडीएफ़
|
||||||
auto-rename.submit=Auto Rename
|
auto-rename.submit=स्वतः नाम परिवर्तन
|
||||||
|
|
||||||
|
|
||||||
#adjustContrast
|
#adjustContrast
|
||||||
|
@ -508,6 +509,7 @@ pageLayout.pagesPerSheet=प्रति पृष्ठ पेज:
|
||||||
pageLayout.addBorder=सीमा जोड़ें
|
pageLayout.addBorder=सीमा जोड़ें
|
||||||
pageLayout.submit=प्रस्तुत क
|
pageLayout.submit=प्रस्तुत क
|
||||||
|
|
||||||
|
|
||||||
#scalePages
|
#scalePages
|
||||||
scalePages.title=पृष्ठ-स्केल समायोजित करें
|
scalePages.title=पृष्ठ-स्केल समायोजित करें
|
||||||
scalePages.header=पृष्ठ-स्केल समायोजित करें
|
scalePages.header=पृष्ठ-स्केल समायोजित करें
|
||||||
|
@ -516,7 +518,6 @@ scalePages.scaleFactor=पृष्ठ का ज़ूम स्तर (क्
|
||||||
scalePages.submit=प्रस्तुत करें
|
scalePages.submit=प्रस्तुत करें
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#certSign
|
#certSign
|
||||||
certSign.title=प्रमाणपत्र साइनिंग
|
certSign.title=प्रमाणपत्र साइनिंग
|
||||||
certSign.header=अपने प्रमाणपत्र के साथ एक पीडीएफ़ पर हस्ताक्षर करें (काम जारी है)
|
certSign.header=अपने प्रमाणपत्र के साथ एक पीडीएफ़ पर हस्ताक्षर करें (काम जारी है)
|
||||||
|
@ -789,7 +790,7 @@ removePassword.submit=हटाएं
|
||||||
|
|
||||||
|
|
||||||
#changeMetadata
|
#changeMetadata
|
||||||
changeMetadata.title=मेटाडेटा बदलें
|
changeMetadata.title=शीर्षक:
|
||||||
changeMetadata.header=मेटाडेटा बदलें
|
changeMetadata.header=मेटाडेटा बदलें
|
||||||
changeMetadata.selectText.1=कृपया उन चरों को संपादित करें जिन्हें आप बदलना चाहते हैं
|
changeMetadata.selectText.1=कृपया उन चरों को संपादित करें जिन्हें आप बदलना चाहते हैं
|
||||||
changeMetadata.selectText.2=सभी मेटाडेटा हटाएं
|
changeMetadata.selectText.2=सभी मेटाडेटा हटाएं
|
||||||
|
|
|
@ -50,6 +50,7 @@ incorrectPasswordMessage=A jelenlegi jelszó helytelen.
|
||||||
usernameExistsMessage=Az új felhasználónév már létezik.
|
usernameExistsMessage=Az új felhasználónév már létezik.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# NAVBAR #
|
# NAVBAR #
|
||||||
#############
|
#############
|
||||||
|
@ -60,7 +61,6 @@ navbar.darkmode=Sötét mód
|
||||||
navbar.pageOps=Lap műveletek
|
navbar.pageOps=Lap műveletek
|
||||||
navbar.settings=Beállítások
|
navbar.settings=Beállítások
|
||||||
|
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# SETTINGS #
|
# SETTINGS #
|
||||||
#############
|
#############
|
||||||
|
@ -76,6 +76,7 @@ settings.signOut=Kijelentkezés
|
||||||
settings.accountSettings=Fiókbeállítások
|
settings.accountSettings=Fiókbeállítások
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
changeCreds.title=Hitelesítés megváltoztatása
|
changeCreds.title=Hitelesítés megváltoztatása
|
||||||
changeCreds.header=Frissítse fiókadatait
|
changeCreds.header=Frissítse fiókadatait
|
||||||
changeCreds.changeUserAndPassword=Alapértelmezett bejelentkezési adatokat használ. Adjon meg egy új jelszót (és felhasználónevet, ha szeretné)
|
changeCreds.changeUserAndPassword=Alapértelmezett bejelentkezési adatokat használ. Adjon meg egy új jelszót (és felhasználónevet, ha szeretné)
|
||||||
|
@ -86,11 +87,13 @@ changeCreds.confirmNewPassword=Új jelszó megerősítése
|
||||||
changeCreds.submit=Változtatások elküldése
|
changeCreds.submit=Változtatások elküldése
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
account.title=Fiókbeállítások
|
account.title=Fiókbeállítások
|
||||||
account.accountSettings=Fiókbeállítások
|
account.accountSettings=Fiókbeállítások
|
||||||
account.adminSettings=Admin Beállítások - Felhasználók megtekintése és hozzáadása
|
account.adminSettings=Admin Beállítások - Felhasználók megtekintése és hozzáadása
|
||||||
account.userControlSettings=Felhasználói vezérlési beállítások
|
account.userControlSettings=Felhasználói vezérlési beállítások
|
||||||
account.changeUsername=Új felhasználónév
|
account.changeUsername=Új felhasználónév
|
||||||
|
account.changeUsername=Új felhasználónév
|
||||||
account.password=Megerősítő jelszó
|
account.password=Megerősítő jelszó
|
||||||
account.oldPassword=Régi jelszó
|
account.oldPassword=Régi jelszó
|
||||||
account.newPassword=Új jelszó
|
account.newPassword=Új jelszó
|
||||||
|
@ -116,16 +119,17 @@ adminUserSettings.role=Szerep
|
||||||
adminUserSettings.actions=Műveletek
|
adminUserSettings.actions=Műveletek
|
||||||
adminUserSettings.apiUser=Korlátozott API-felhasználó
|
adminUserSettings.apiUser=Korlátozott API-felhasználó
|
||||||
adminUserSettings.webOnlyUser=Csak webes felhasználó
|
adminUserSettings.webOnlyUser=Csak webes felhasználó
|
||||||
|
adminUserSettings.demoUser=Demo User (No custom settings)
|
||||||
adminUserSettings.forceChange=Kényszerítse a felhasználót a felhasználónév/jelszó megváltoztatására bejelentkezéskor
|
adminUserSettings.forceChange=Kényszerítse a felhasználót a felhasználónév/jelszó megváltoztatására bejelentkezéskor
|
||||||
adminUserSettings.submit=Felhasználó mentése
|
adminUserSettings.submit=Felhasználó mentése
|
||||||
|
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# HOME-PAGE #
|
# HOME-PAGE #
|
||||||
#############
|
#############
|
||||||
home.desc=Lokálisan hostolt egyszerű megoldás minden PDF igényéhez.
|
home.desc=Lokálisan hostolt egyszerű megoldás minden PDF igényéhez.
|
||||||
home.searchBar=Keresés funkciókra...
|
home.searchBar=Keresés funkciókra...
|
||||||
|
|
||||||
|
|
||||||
home.viewPdf.title=PDF Megtekintése
|
home.viewPdf.title=PDF Megtekintése
|
||||||
home.viewPdf.desc=Megtekintés, annotálás, szöveg vagy képek hozzáadása
|
home.viewPdf.desc=Megtekintés, annotálás, szöveg vagy képek hozzáadása
|
||||||
viewPdf.tags=megtekintés,olvasás,annotálás,szöveg,kép
|
viewPdf.tags=megtekintés,olvasás,annotálás,szöveg,kép
|
||||||
|
@ -146,6 +150,7 @@ home.rotate.title=Forgatás
|
||||||
home.rotate.desc=PDF-ek egyszerű forgatása.
|
home.rotate.desc=PDF-ek egyszerű forgatása.
|
||||||
rotate.tags=server oldal
|
rotate.tags=server oldal
|
||||||
|
|
||||||
|
|
||||||
home.imageToPdf.title=Kép PDF-be
|
home.imageToPdf.title=Kép PDF-be
|
||||||
home.imageToPdf.desc=Kép (PNG, JPEG, GIF) konvertálása PDF-fé.
|
home.imageToPdf.desc=Kép (PNG, JPEG, GIF) konvertálása PDF-fé.
|
||||||
imageToPdf.tags=konverzió,img,jpg,kép,fotó
|
imageToPdf.tags=konverzió,img,jpg,kép,fotó
|
||||||
|
@ -158,6 +163,7 @@ home.pdfOrganiser.title=Szervezés
|
||||||
home.pdfOrganiser.desc=Lapok eltávolítása/átszervezése bármilyen sorrendben
|
home.pdfOrganiser.desc=Lapok eltávolítása/átszervezése bármilyen sorrendben
|
||||||
pdfOrganiser.tags=duplex,páros,páratlan,rendezés,mozgatás
|
pdfOrganiser.tags=duplex,páros,páratlan,rendezés,mozgatás
|
||||||
|
|
||||||
|
|
||||||
home.addImage.title=Kép hozzáadása
|
home.addImage.title=Kép hozzáadása
|
||||||
home.addImage.desc=Kép hozzáadása a PDF megadott helyére
|
home.addImage.desc=Kép hozzáadása a PDF megadott helyére
|
||||||
addImage.tags=img,jpg,kép,fotó
|
addImage.tags=img,jpg,kép,fotó
|
||||||
|
@ -170,6 +176,7 @@ home.permissions.title=Engedélyek módosítása
|
||||||
home.permissions.desc=Változtassa meg a PDF dokumentum engedélyeit
|
home.permissions.desc=Változtassa meg a PDF dokumentum engedélyeit
|
||||||
permissions.tags=olvasás,írás,szerkesztés,nyomtatás
|
permissions.tags=olvasás,írás,szerkesztés,nyomtatás
|
||||||
|
|
||||||
|
|
||||||
home.removePages.title=Eltávolítás
|
home.removePages.title=Eltávolítás
|
||||||
home.removePages.desc=Szükségtelen lapok törlése a PDF dokumentumból.
|
home.removePages.desc=Szükségtelen lapok törlése a PDF dokumentumból.
|
||||||
removePages.tags=Lapok eltávolítása,lapok törlése
|
removePages.tags=Lapok eltávolítása,lapok törlése
|
||||||
|
@ -186,6 +193,7 @@ home.compressPdfs.title=Tömörítés
|
||||||
home.compressPdfs.desc=PDF-ek tömörítése a fájlméret csökkentése érdekében.
|
home.compressPdfs.desc=PDF-ek tömörítése a fájlméret csökkentése érdekében.
|
||||||
compressPdfs.tags=szorít,kicsi,miniatűr
|
compressPdfs.tags=szorít,kicsi,miniatűr
|
||||||
|
|
||||||
|
|
||||||
home.changeMetadata.title=Metaadatok Módosítása
|
home.changeMetadata.title=Metaadatok Módosítása
|
||||||
home.changeMetadata.desc=Metaadatok Módosítása/Eltávolítása/Hozzáadása egy PDF dokumentumból
|
home.changeMetadata.desc=Metaadatok Módosítása/Eltávolítása/Hozzáadása egy PDF dokumentumból
|
||||||
changeMetadata.tags=Cím,szerző,dátum,alkotás,idő,közzétevő,gyártó,statisztika
|
changeMetadata.tags=Cím,szerző,dátum,alkotás,idő,közzétevő,gyártó,statisztika
|
||||||
|
@ -198,6 +206,7 @@ home.ocr.title=OCR / Tisztítás szkennelésekből
|
||||||
home.ocr.desc=Tisztítás szkennelésekből és szöveg észlelése képeken belül egy PDF-ben, majd visszahozza szövegként.
|
home.ocr.desc=Tisztítás szkennelésekből és szöveg észlelése képeken belül egy PDF-ben, majd visszahozza szövegként.
|
||||||
ocr.tags=felismerés,szöveg,kép,szken,gép,felismert,azonosítás,szerkeszthető
|
ocr.tags=felismerés,szöveg,kép,szken,gép,felismert,azonosítás,szerkeszthető
|
||||||
|
|
||||||
|
|
||||||
home.extractImages.title=Képek kinyerése
|
home.extractImages.title=Képek kinyerése
|
||||||
home.extractImages.desc=Az összes kép kinyerése egy PDF-ből és mentése zip-be
|
home.extractImages.desc=Az összes kép kinyerése egy PDF-ből és mentése zip-be
|
||||||
extractImages.tags=kép,fotó,mentés,archívum,zip,rögzítés,gyűjtés
|
extractImages.tags=kép,fotó,mentés,archívum,zip,rögzítés,gyűjtés
|
||||||
|
@ -222,6 +231,7 @@ home.PDFToHTML.title=PDF >> HTML
|
||||||
home.PDFToHTML.desc=PDF konvertálása HTML formátumra
|
home.PDFToHTML.desc=PDF konvertálása HTML formátumra
|
||||||
PDFToHTML.tags=web tartalom,böngészőbarát
|
PDFToHTML.tags=web tartalom,böngészőbarát
|
||||||
|
|
||||||
|
|
||||||
home.PDFToXML.title=PDF >> XML
|
home.PDFToXML.title=PDF >> XML
|
||||||
home.PDFToXML.desc=PDF konvertálása XML formátumra
|
home.PDFToXML.desc=PDF konvertálása XML formátumra
|
||||||
PDFToXML.tags=adat-kinyerés,strukturált tartalom,interop,konverzió
|
PDFToXML.tags=adat-kinyerés,strukturált tartalom,interop,konverzió
|
||||||
|
@ -246,6 +256,10 @@ home.removeBlanks.title=Üres lapok eltávolítása
|
||||||
home.removeBlanks.desc=Felismeri és eltávolítja az üres lapokat a dokumentumból
|
home.removeBlanks.desc=Felismeri és eltávolítja az üres lapokat a dokumentumból
|
||||||
removeBlanks.tags=takarítás,egyszerűsítés,nem-tartalom,szervez
|
removeBlanks.tags=takarítás,egyszerűsítés,nem-tartalom,szervez
|
||||||
|
|
||||||
|
home.removeAnnotations.title=Remove Annotations
|
||||||
|
home.removeAnnotations.desc=Removes all comments/annotations from a PDF
|
||||||
|
removeAnnotations.tags=comments,highlight,notes,markup,remove
|
||||||
|
|
||||||
home.compare.title=Összehasonlítás
|
home.compare.title=Összehasonlítás
|
||||||
home.compare.desc=Összehasonlítja és megmutatja a különbségeket két PDF dokumentum között
|
home.compare.desc=Összehasonlítja és megmutatja a különbségeket két PDF dokumentum között
|
||||||
compare.tags=kiemel,ellentét,változások,elemzés
|
compare.tags=kiemel,ellentét,változások,elemzés
|
||||||
|
@ -298,25 +312,30 @@ home.HTMLToPDF.title=HTML PDF-be
|
||||||
home.HTMLToPDF.desc=Bármely HTML fájl vagy tömörített fájl átalakítása PDF-be
|
home.HTMLToPDF.desc=Bármely HTML fájl vagy tömörített fájl átalakítása PDF-be
|
||||||
HTMLToPDF.tags=markup,web-tartalom,transzformáció,konverzió
|
HTMLToPDF.tags=markup,web-tartalom,transzformáció,konverzió
|
||||||
|
|
||||||
|
|
||||||
home.MarkdownToPDF.title=Markdown PDF-be
|
home.MarkdownToPDF.title=Markdown PDF-be
|
||||||
home.MarkdownToPDF.desc=Bármely Markdown fájl átalakítása PDF-be
|
home.MarkdownToPDF.desc=Bármely Markdown fájl átalakítása PDF-be
|
||||||
MarkdownToPDF.tags=markup,web-tartalom,transzformáció,konverzió
|
MarkdownToPDF.tags=markup,web-tartalom,transzformáció,konverzió
|
||||||
|
|
||||||
|
|
||||||
home.getPdfInfo.title=Összes információ a PDF-ről
|
home.getPdfInfo.title=Összes információ a PDF-ről
|
||||||
home.getPdfInfo.desc=Az összes lehetséges információ beszerzése a PDF-ekről
|
home.getPdfInfo.desc=Az összes lehetséges információ beszerzése a PDF-ekről
|
||||||
getPdfInfo.tags=információ,adat,statisztika,statisztika
|
getPdfInfo.tags=információ,adat,statisztika,statisztika
|
||||||
|
|
||||||
|
|
||||||
home.extractPage.title=Lapok kinyerése
|
home.extractPage.title=Lapok kinyerése
|
||||||
home.extractPage.desc=Válassza ki a lapokat a PDF-ből
|
home.extractPage.desc=Válassza ki a lapokat a PDF-ből
|
||||||
extractPage.tags=kinyer
|
extractPage.tags=kinyer
|
||||||
|
|
||||||
|
|
||||||
home.PdfToSinglePage.title=PDF egyetlen nagy lapba
|
home.PdfToSinglePage.title=PDF egyetlen nagy lapba
|
||||||
home.PdfToSinglePage.desc=Az összes PDF lap egyesítése egyetlen nagy lapba
|
home.PdfToSinglePage.desc=Az összes PDF lap egyesítése egyetlen nagy lapba
|
||||||
PdfToSinglePage.tags=egyetlen lap
|
PdfToSinglePage.tags=egyetlen lap
|
||||||
|
|
||||||
|
|
||||||
home.showJS.title=JavaScript megjelenítése
|
home.showJS.title=JavaScript megjelenítése
|
||||||
home.showJS.desc=Keres és megjelenít bármilyen JS-t, amit beinjektáltak a PDF-be
|
home.showJS.desc=Keres és megjelenít bármilyen JS-t, amit beinjektáltak a PDF-be
|
||||||
showJS.tags=JS
|
showJS.tags=Elrejt,Elrejtés,kitakarás,fekete,fekete,marker,elrejtett
|
||||||
|
|
||||||
home.autoRedact.title=Automatikus Elrejtés
|
home.autoRedact.title=Automatikus Elrejtés
|
||||||
home.autoRedact.desc=Automatikusan kitakar (elrejt) szöveget egy PDF-ben az input szöveg alapján
|
home.autoRedact.desc=Automatikusan kitakar (elrejt) szöveget egy PDF-ben az input szöveg alapján
|
||||||
|
@ -326,10 +345,12 @@ home.tableExtraxt.title=PDF to CSV
|
||||||
home.tableExtraxt.desc=Táblázatok kinyerése a PDF-ből CSV formátumra konvertálva
|
home.tableExtraxt.desc=Táblázatok kinyerése a PDF-ből CSV formátumra konvertálva
|
||||||
tableExtraxt.tags=CSV,Táblázat kinyerése,kinyer,konvertál
|
tableExtraxt.tags=CSV,Táblázat kinyerése,kinyer,konvertál
|
||||||
|
|
||||||
|
|
||||||
home.autoSizeSplitPDF.title=Automatikus szétválasztás méret/számláló alapján
|
home.autoSizeSplitPDF.title=Automatikus szétválasztás méret/számláló alapján
|
||||||
home.autoSizeSplitPDF.desc=Egyetlen PDF szétválasztása több dokumentummá méret, oldalszám vagy dokumentum szám alapján
|
home.autoSizeSplitPDF.desc=Egyetlen PDF szétválasztása több dokumentummá méret, oldalszám vagy dokumentum szám alapján
|
||||||
autoSizeSplitPDF.tags=pdf,szétválasztás,dokumentum,szervezet
|
autoSizeSplitPDF.tags=pdf,szétválasztás,dokumentum,szervezet
|
||||||
|
|
||||||
|
|
||||||
home.overlay-pdfs.title=PDF fájlok átlapolása
|
home.overlay-pdfs.title=PDF fájlok átlapolása
|
||||||
home.overlay-pdfs.desc=PDF fájlok átlapolása egyik dokumentum a másik fölé helyezésével
|
home.overlay-pdfs.desc=PDF fájlok átlapolása egyik dokumentum a másik fölé helyezésével
|
||||||
overlay-pdfs.tags=Átlapolás
|
overlay-pdfs.tags=Átlapolás
|
||||||
|
@ -338,7 +359,6 @@ home.split-by-sections.title=PDF Szakaszokra osztása
|
||||||
home.split-by-sections.desc=Minden oldal felosztása kisebb vízszintes és függőleges szakaszokra
|
home.split-by-sections.desc=Minden oldal felosztása kisebb vízszintes és függőleges szakaszokra
|
||||||
split-by-sections.tags=Szakasz elosztás, felosztás, testreszabás
|
split-by-sections.tags=Szakasz elosztás, felosztás, testreszabás
|
||||||
|
|
||||||
|
|
||||||
###########################
|
###########################
|
||||||
# #
|
# #
|
||||||
# WEB PAGES #
|
# WEB PAGES #
|
||||||
|
@ -400,6 +420,7 @@ MarkdownToPDF.help=Az átalakítás folyamatban
|
||||||
MarkdownToPDF.credit=WeasyPrint alkalmazása
|
MarkdownToPDF.credit=WeasyPrint alkalmazása
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#url-to-pdf
|
#url-to-pdf
|
||||||
URLToPDF.title=URL >> PDF
|
URLToPDF.title=URL >> PDF
|
||||||
URLToPDF.header=URL >> PDF
|
URLToPDF.header=URL >> PDF
|
||||||
|
@ -523,6 +544,12 @@ removeBlanks.whitePercentDesc=Az oldalakon található 'fehér' pixelek százal
|
||||||
removeBlanks.submit=Üres oldalak eltávolítása
|
removeBlanks.submit=Üres oldalak eltávolítása
|
||||||
|
|
||||||
|
|
||||||
|
#removeAnnotations
|
||||||
|
removeAnnotations.title=Remove Annotations
|
||||||
|
removeAnnotations.header=Remove Annotations
|
||||||
|
removeAnnotations.submit=Remove
|
||||||
|
|
||||||
|
|
||||||
#compare
|
#compare
|
||||||
compare.title=Összehasonlítás
|
compare.title=Összehasonlítás
|
||||||
compare.header=PDF-ek összehasonlítása
|
compare.header=PDF-ek összehasonlítása
|
||||||
|
@ -639,12 +666,10 @@ pdfOrganiser.submit=Oldalak átrendezése
|
||||||
multiTool.title=PDF többfunkciós eszköz
|
multiTool.title=PDF többfunkciós eszköz
|
||||||
multiTool.header=PDF többfunkciós eszköz
|
multiTool.header=PDF többfunkciós eszköz
|
||||||
|
|
||||||
|
|
||||||
#view pdf
|
#view pdf
|
||||||
viewPdf.title=PDF megtekintése
|
viewPdf.title=PDF megtekintése
|
||||||
viewPdf.header=PDF megtekintése
|
viewPdf.header=PDF megtekintése
|
||||||
|
|
||||||
|
|
||||||
#pageRemover
|
#pageRemover
|
||||||
pageRemover.title=Oldaltörlő
|
pageRemover.title=Oldaltörlő
|
||||||
pageRemover.header=PDF oldaltörlő
|
pageRemover.header=PDF oldaltörlő
|
||||||
|
@ -765,7 +790,7 @@ removePassword.submit=Eltávolítás
|
||||||
|
|
||||||
|
|
||||||
#changeMetadata
|
#changeMetadata
|
||||||
changeMetadata.title=Metaadatok módosítása
|
changeMetadata.title=Cím:
|
||||||
changeMetadata.header=Metaadatok módosítása
|
changeMetadata.header=Metaadatok módosítása
|
||||||
changeMetadata.selectText.1=Kérjük, szerkessze azokat a változókat, amelyeket módosítani szeretne
|
changeMetadata.selectText.1=Kérjük, szerkessze azokat a változókat, amelyeket módosítani szeretne
|
||||||
changeMetadata.selectText.2=Minden metaadat törlése
|
changeMetadata.selectText.2=Minden metaadat törlése
|
||||||
|
@ -828,14 +853,12 @@ PDFToXML.header=PDF >> XML
|
||||||
PDFToXML.credit=Ez a szolgáltatás a LibreOffice-t használja a fájlkonverzióhoz.
|
PDFToXML.credit=Ez a szolgáltatás a LibreOffice-t használja a fájlkonverzióhoz.
|
||||||
PDFToXML.submit=Konvertálás
|
PDFToXML.submit=Konvertálás
|
||||||
|
|
||||||
|
|
||||||
#PDFToCSV
|
#PDFToCSV
|
||||||
PDFToCSV.title=PDF >> CSV
|
PDFToCSV.title=PDF >> CSV
|
||||||
PDFToCSV.header=PDF >> CSV
|
PDFToCSV.header=PDF >> CSV
|
||||||
PDFToCSV.prompt=Válassza ki az oldalt a táblázat kinyeréséhez
|
PDFToCSV.prompt=Válassza ki az oldalt a táblázat kinyeréséhez
|
||||||
PDFToCSV.submit=Kinyerés
|
PDFToCSV.submit=Kinyerés
|
||||||
|
|
||||||
|
|
||||||
#split-by-size-or-count
|
#split-by-size-or-count
|
||||||
split-by-size-or-count.header=PDF felosztása méret vagy oldalszám alapján
|
split-by-size-or-count.header=PDF felosztása méret vagy oldalszám alapján
|
||||||
split-by-size-or-count.type.label=Válassza ki a felosztás típusát
|
split-by-size-or-count.type.label=Válassza ki a felosztás típusát
|
||||||
|
@ -871,4 +894,3 @@ split-by-sections.vertical.label=Vízszintes szakaszok
|
||||||
split-by-sections.horizontal.placeholder=Adja meg a vízszintes szakaszok számát
|
split-by-sections.horizontal.placeholder=Adja meg a vízszintes szakaszok számát
|
||||||
split-by-sections.vertical.placeholder=Adja meg a függőleges szakaszok számát
|
split-by-sections.vertical.placeholder=Adja meg a függőleges szakaszok számát
|
||||||
split-by-sections.submit=Felosztás
|
split-by-sections.submit=Felosztás
|
||||||
|
|
||||||
|
|
|
@ -92,7 +92,7 @@ account.title=Pengaturan Akun
|
||||||
account.accountSettings=Pengaturan Akun
|
account.accountSettings=Pengaturan Akun
|
||||||
account.adminSettings=Pengaturan Admin - Melihat dan Menambahkan Pengguna
|
account.adminSettings=Pengaturan Admin - Melihat dan Menambahkan Pengguna
|
||||||
account.userControlSettings=Pengaturan Kontrol Pengguna
|
account.userControlSettings=Pengaturan Kontrol Pengguna
|
||||||
account.changeUsername=Nama Pengguna Baru
|
account.changeUsername=Ubah Nama Pengguna
|
||||||
account.changeUsername=Ubah Nama Pengguna
|
account.changeUsername=Ubah Nama Pengguna
|
||||||
account.password=Konfirmasi Kata sandi
|
account.password=Konfirmasi Kata sandi
|
||||||
account.oldPassword=Kata sandi lama
|
account.oldPassword=Kata sandi lama
|
||||||
|
@ -119,6 +119,7 @@ adminUserSettings.role=Peran
|
||||||
adminUserSettings.actions=Tindakan
|
adminUserSettings.actions=Tindakan
|
||||||
adminUserSettings.apiUser=Pengguna API Terbatas
|
adminUserSettings.apiUser=Pengguna API Terbatas
|
||||||
adminUserSettings.webOnlyUser=Pengguna Khusus Web
|
adminUserSettings.webOnlyUser=Pengguna Khusus Web
|
||||||
|
adminUserSettings.demoUser=Demo User (No custom settings)
|
||||||
adminUserSettings.forceChange=Memaksa pengguna untuk mengubah nama pengguna/kata sandi saat masuk
|
adminUserSettings.forceChange=Memaksa pengguna untuk mengubah nama pengguna/kata sandi saat masuk
|
||||||
adminUserSettings.submit=Simpan Pengguna
|
adminUserSettings.submit=Simpan Pengguna
|
||||||
|
|
||||||
|
@ -331,9 +332,10 @@ home.PdfToSinglePage.title=PDF ke Satu Halaman Besar
|
||||||
home.PdfToSinglePage.desc=Menggabungkan semua halaman PDF menjadi satu halaman besar
|
home.PdfToSinglePage.desc=Menggabungkan semua halaman PDF menjadi satu halaman besar
|
||||||
PdfToSinglePage.tags=halaman tunggal
|
PdfToSinglePage.tags=halaman tunggal
|
||||||
|
|
||||||
|
|
||||||
home.showJS.title=Tampilkan Javascript
|
home.showJS.title=Tampilkan Javascript
|
||||||
home.showJS.desc=Mencari dan menampilkan JS apa pun yang disuntikkan ke dalam PDF
|
home.showJS.desc=Mencari dan menampilkan JS apa pun yang disuntikkan ke dalam PDF
|
||||||
showJS.tags=JS
|
showJS.tags=Hapus, Sembunyikan, padamkan, hitam, hitam, penanda, tersembunyi
|
||||||
|
|
||||||
home.autoRedact.title=Redaksional Otomatis
|
home.autoRedact.title=Redaksional Otomatis
|
||||||
home.autoRedact.desc=Menyunting Otomatis (Menghitamkan) teks dalam PDF berdasarkan teks masukan
|
home.autoRedact.desc=Menyunting Otomatis (Menghitamkan) teks dalam PDF berdasarkan teks masukan
|
||||||
|
@ -409,6 +411,7 @@ getPdfInfo.header=Dapatkan Info tentang PDF
|
||||||
getPdfInfo.submit=Dapatkan Info
|
getPdfInfo.submit=Dapatkan Info
|
||||||
getPdfInfo.downloadJson=Unduh JSON
|
getPdfInfo.downloadJson=Unduh JSON
|
||||||
|
|
||||||
|
|
||||||
#markdown-to-pdf
|
#markdown-to-pdf
|
||||||
MarkdownToPDF.title=Markdown ke PDF
|
MarkdownToPDF.title=Markdown ke PDF
|
||||||
MarkdownToPDF.header=Markdown Ke PDF
|
MarkdownToPDF.header=Markdown Ke PDF
|
||||||
|
@ -417,6 +420,7 @@ MarkdownToPDF.help=Pekerjaan sedang berlangsung
|
||||||
MarkdownToPDF.credit=Menggunakan WeasyPrint
|
MarkdownToPDF.credit=Menggunakan WeasyPrint
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#url-to-pdf
|
#url-to-pdf
|
||||||
URLToPDF.title=URL ke PDF
|
URLToPDF.title=URL ke PDF
|
||||||
URLToPDF.header=URL Ke PDF
|
URLToPDF.header=URL Ke PDF
|
||||||
|
@ -532,7 +536,7 @@ certSign.submit=Tanda tangani PDF
|
||||||
|
|
||||||
#removeBlanks
|
#removeBlanks
|
||||||
removeBlanks.title=Hapus Halaman Kosong
|
removeBlanks.title=Hapus Halaman Kosong
|
||||||
hapusKosong.header=Hapus Halaman Kosong
|
removeBlanks.header=Remove Blank Pages
|
||||||
removeBlanks.threshold=Ambang Batas Keputihan Piksel:
|
removeBlanks.threshold=Ambang Batas Keputihan Piksel:
|
||||||
removeBlanks.thresholdDesc=Ambang batas untuk menentukan seberapa putih piksel putih yang harus diklasifikasikan sebagai 'Putih'. 0=Hitam, 255 putih murni.
|
removeBlanks.thresholdDesc=Ambang batas untuk menentukan seberapa putih piksel putih yang harus diklasifikasikan sebagai 'Putih'. 0=Hitam, 255 putih murni.
|
||||||
removeBlanks.whitePercent=Persen Putih (%):
|
removeBlanks.whitePercent=Persen Putih (%):
|
||||||
|
@ -553,6 +557,7 @@ compare.document.1=Dokumen 1
|
||||||
compare.document.2=Dokumen 2
|
compare.document.2=Dokumen 2
|
||||||
compare.submit=Bandingkan
|
compare.submit=Bandingkan
|
||||||
|
|
||||||
|
|
||||||
#sign
|
#sign
|
||||||
sign.title=Tanda
|
sign.title=Tanda
|
||||||
sign.header=Tandatangani PDF
|
sign.header=Tandatangani PDF
|
||||||
|
@ -607,6 +612,7 @@ ocr.help=Silakan baca dokumentasi ini tentang cara menggunakan ini untuk bahasa
|
||||||
ocr.credit=Layanan ini menggunakan OCRmyPDF dan Tesseract untuk OCR.
|
ocr.credit=Layanan ini menggunakan OCRmyPDF dan Tesseract untuk OCR.
|
||||||
ocr.submit=Memproses PDF dengan OCR
|
ocr.submit=Memproses PDF dengan OCR
|
||||||
|
|
||||||
|
|
||||||
#extractImages
|
#extractImages
|
||||||
extractImages.title=Ekstrak Gambar
|
extractImages.title=Ekstrak Gambar
|
||||||
extractImages.header=Mengekstrak Gambar
|
extractImages.header=Mengekstrak Gambar
|
||||||
|
@ -784,7 +790,7 @@ removePassword.submit=Hapus
|
||||||
|
|
||||||
|
|
||||||
#changeMetadata
|
#changeMetadata
|
||||||
changeMetadata.title=Ganti Metadata
|
changeMetadata.title=Judul:
|
||||||
changeMetadata.header=Ganti Metadata
|
changeMetadata.header=Ganti Metadata
|
||||||
changeMetadata.selectText.1=Silakan edit variabel yang ingin Anda ubah
|
changeMetadata.selectText.1=Silakan edit variabel yang ingin Anda ubah
|
||||||
changeMetadata.selectText.2=Hapus semua metadata
|
changeMetadata.selectText.2=Hapus semua metadata
|
||||||
|
|
|
@ -119,6 +119,7 @@ adminUserSettings.role=Ruolo
|
||||||
adminUserSettings.actions=Azioni
|
adminUserSettings.actions=Azioni
|
||||||
adminUserSettings.apiUser=Utente API limitato
|
adminUserSettings.apiUser=Utente API limitato
|
||||||
adminUserSettings.webOnlyUser=Utente solo Web
|
adminUserSettings.webOnlyUser=Utente solo Web
|
||||||
|
adminUserSettings.demoUser=Demo User (No custom settings)
|
||||||
adminUserSettings.forceChange=Forza l'utente a cambiare nome username/password all'accesso
|
adminUserSettings.forceChange=Forza l'utente a cambiare nome username/password all'accesso
|
||||||
adminUserSettings.submit=Salva utente
|
adminUserSettings.submit=Salva utente
|
||||||
|
|
||||||
|
@ -255,6 +256,10 @@ home.removeBlanks.title=Rimuovi pagine vuote
|
||||||
home.removeBlanks.desc=Trova e rimuovi pagine vuote da un PDF.
|
home.removeBlanks.desc=Trova e rimuovi pagine vuote da un PDF.
|
||||||
removeBlanks.tags=pulire,semplificare,non contenere contenuti,organizzare
|
removeBlanks.tags=pulire,semplificare,non contenere contenuti,organizzare
|
||||||
|
|
||||||
|
home.removeAnnotations.title=Remove Annotations
|
||||||
|
home.removeAnnotations.desc=Removes all comments/annotations from a PDF
|
||||||
|
removeAnnotations.tags=comments,highlight,notes,markup,remove
|
||||||
|
|
||||||
home.compare.title=Compara
|
home.compare.title=Compara
|
||||||
home.compare.desc=Vedi e compara le differenze tra due PDF.
|
home.compare.desc=Vedi e compara le differenze tra due PDF.
|
||||||
compare.tags=differenziare,contrastare,cambiare,analisi
|
compare.tags=differenziare,contrastare,cambiare,analisi
|
||||||
|
@ -539,6 +544,12 @@ removeBlanks.whitePercentDesc=Percentuale della pagina che deve essere bianca pe
|
||||||
removeBlanks.submit=Rimuovi
|
removeBlanks.submit=Rimuovi
|
||||||
|
|
||||||
|
|
||||||
|
#removeAnnotations
|
||||||
|
removeAnnotations.title=Remove Annotations
|
||||||
|
removeAnnotations.header=Remove Annotations
|
||||||
|
removeAnnotations.submit=Remove
|
||||||
|
|
||||||
|
|
||||||
#compare
|
#compare
|
||||||
compare.title=Compara
|
compare.title=Compara
|
||||||
compare.header=Compara PDF
|
compare.header=Compara PDF
|
||||||
|
|
|
@ -119,6 +119,7 @@ adminUserSettings.role=役割
|
||||||
adminUserSettings.actions=アクション
|
adminUserSettings.actions=アクション
|
||||||
adminUserSettings.apiUser=限定されたAPIユーザー
|
adminUserSettings.apiUser=限定されたAPIユーザー
|
||||||
adminUserSettings.webOnlyUser=ウェブ専用ユーザー
|
adminUserSettings.webOnlyUser=ウェブ専用ユーザー
|
||||||
|
adminUserSettings.demoUser=Demo User (No custom settings)
|
||||||
adminUserSettings.forceChange=ログイン時にユーザー名/パスワードを強制的に変更する
|
adminUserSettings.forceChange=ログイン時にユーザー名/パスワードを強制的に変更する
|
||||||
adminUserSettings.submit=ユーザーの保存
|
adminUserSettings.submit=ユーザーの保存
|
||||||
|
|
||||||
|
@ -255,6 +256,10 @@ home.removeBlanks.title=空白ページの削除
|
||||||
home.removeBlanks.desc=ドキュメントから空白ページを検出して削除します。
|
home.removeBlanks.desc=ドキュメントから空白ページを検出して削除します。
|
||||||
removeBlanks.tags=cleanup,streamline,non-content,organize
|
removeBlanks.tags=cleanup,streamline,non-content,organize
|
||||||
|
|
||||||
|
home.removeAnnotations.title=Remove Annotations
|
||||||
|
home.removeAnnotations.desc=Removes all comments/annotations from a PDF
|
||||||
|
removeAnnotations.tags=comments,highlight,notes,markup,remove
|
||||||
|
|
||||||
home.compare.title=比較
|
home.compare.title=比較
|
||||||
home.compare.desc=2つのPDFを比較して表示します。
|
home.compare.desc=2つのPDFを比較して表示します。
|
||||||
compare.tags=differentiate,contrast,changes,analysis
|
compare.tags=differentiate,contrast,changes,analysis
|
||||||
|
@ -539,6 +544,12 @@ removeBlanks.whitePercentDesc=削除するページの白の割合
|
||||||
removeBlanks.submit=空白ページの削除
|
removeBlanks.submit=空白ページの削除
|
||||||
|
|
||||||
|
|
||||||
|
#removeAnnotations
|
||||||
|
removeAnnotations.title=Remove Annotations
|
||||||
|
removeAnnotations.header=Remove Annotations
|
||||||
|
removeAnnotations.submit=Remove
|
||||||
|
|
||||||
|
|
||||||
#compare
|
#compare
|
||||||
compare.title=比較
|
compare.title=比較
|
||||||
compare.header=PDFの比較
|
compare.header=PDFの比較
|
||||||
|
|
|
@ -119,6 +119,7 @@ adminUserSettings.role=역할
|
||||||
adminUserSettings.actions=동작
|
adminUserSettings.actions=동작
|
||||||
adminUserSettings.apiUser=제한된 API 사용
|
adminUserSettings.apiUser=제한된 API 사용
|
||||||
adminUserSettings.webOnlyUser=웹 사용만 허용
|
adminUserSettings.webOnlyUser=웹 사용만 허용
|
||||||
|
adminUserSettings.demoUser=Demo User (No custom settings)
|
||||||
adminUserSettings.forceChange=다음 로그인 때 사용자명과 비밀번호를 변경하도록 강제
|
adminUserSettings.forceChange=다음 로그인 때 사용자명과 비밀번호를 변경하도록 강제
|
||||||
adminUserSettings.submit=사용자 저장
|
adminUserSettings.submit=사용자 저장
|
||||||
|
|
||||||
|
@ -255,6 +256,10 @@ home.removeBlanks.title=빈 페이지 제거
|
||||||
home.removeBlanks.desc=PDF 문서에서 빈 페이지를 감지하고 제거합니다.
|
home.removeBlanks.desc=PDF 문서에서 빈 페이지를 감지하고 제거합니다.
|
||||||
removeBlanks.tags=cleanup,streamline,non-content,organize
|
removeBlanks.tags=cleanup,streamline,non-content,organize
|
||||||
|
|
||||||
|
home.removeAnnotations.title=Remove Annotations
|
||||||
|
home.removeAnnotations.desc=Removes all comments/annotations from a PDF
|
||||||
|
removeAnnotations.tags=comments,highlight,notes,markup,remove
|
||||||
|
|
||||||
home.compare.title=비교
|
home.compare.title=비교
|
||||||
home.compare.desc=2개의 PDF 문서를 비교하고 차이를 표시합니다.
|
home.compare.desc=2개의 PDF 문서를 비교하고 차이를 표시합니다.
|
||||||
compare.tags=differentiate,contrast,changes,analysis
|
compare.tags=differentiate,contrast,changes,analysis
|
||||||
|
@ -539,6 +544,12 @@ removeBlanks.whitePercentDesc=제거될 페이지의 흰색 픽셀 비율
|
||||||
removeBlanks.submit=빈 페이지 제거
|
removeBlanks.submit=빈 페이지 제거
|
||||||
|
|
||||||
|
|
||||||
|
#removeAnnotations
|
||||||
|
removeAnnotations.title=Remove Annotations
|
||||||
|
removeAnnotations.header=Remove Annotations
|
||||||
|
removeAnnotations.submit=Remove
|
||||||
|
|
||||||
|
|
||||||
#compare
|
#compare
|
||||||
compare.title=비교
|
compare.title=비교
|
||||||
compare.header=PDF 문서 비교
|
compare.header=PDF 문서 비교
|
||||||
|
|
|
@ -119,6 +119,7 @@ adminUserSettings.role=Rol
|
||||||
adminUserSettings.actions=Acties
|
adminUserSettings.actions=Acties
|
||||||
adminUserSettings.apiUser=Beperkte API gebruiker
|
adminUserSettings.apiUser=Beperkte API gebruiker
|
||||||
adminUserSettings.webOnlyUser=Alleen web gebruiker
|
adminUserSettings.webOnlyUser=Alleen web gebruiker
|
||||||
|
adminUserSettings.demoUser=Demo User (No custom settings)
|
||||||
adminUserSettings.forceChange=Force user to change username/password on login
|
adminUserSettings.forceChange=Force user to change username/password on login
|
||||||
adminUserSettings.submit=Sla gebruiker op
|
adminUserSettings.submit=Sla gebruiker op
|
||||||
|
|
||||||
|
@ -255,6 +256,10 @@ home.removeBlanks.title=Verwijder lege pagina''s
|
||||||
home.removeBlanks.desc=Detecteert en verwijdert lege pagina''s uit een document
|
home.removeBlanks.desc=Detecteert en verwijdert lege pagina''s uit een document
|
||||||
removeBlanks.tags=opruimen,stroomlijnen,geen-inhoud,organiseren
|
removeBlanks.tags=opruimen,stroomlijnen,geen-inhoud,organiseren
|
||||||
|
|
||||||
|
home.removeAnnotations.title=Remove Annotations
|
||||||
|
home.removeAnnotations.desc=Removes all comments/annotations from a PDF
|
||||||
|
removeAnnotations.tags=comments,highlight,notes,markup,remove
|
||||||
|
|
||||||
home.compare.title=Vergelijken
|
home.compare.title=Vergelijken
|
||||||
home.compare.desc=Vergelijkt en toont de verschillen tussen 2 PDF-documenten
|
home.compare.desc=Vergelijkt en toont de verschillen tussen 2 PDF-documenten
|
||||||
compare.tags=onderscheiden,contrasteren,veranderingen,analyse
|
compare.tags=onderscheiden,contrasteren,veranderingen,analyse
|
||||||
|
@ -539,6 +544,12 @@ removeBlanks.whitePercentDesc=Percentage van de pagina dat ''witte'' pixels moet
|
||||||
removeBlanks.submit=Blanco''s verwijderen
|
removeBlanks.submit=Blanco''s verwijderen
|
||||||
|
|
||||||
|
|
||||||
|
#removeAnnotations
|
||||||
|
removeAnnotations.title=Remove Annotations
|
||||||
|
removeAnnotations.header=Remove Annotations
|
||||||
|
removeAnnotations.submit=Remove
|
||||||
|
|
||||||
|
|
||||||
#compare
|
#compare
|
||||||
compare.title=Vergelijken
|
compare.title=Vergelijken
|
||||||
compare.header=PDF''s vergelijken
|
compare.header=PDF''s vergelijken
|
||||||
|
|
|
@ -119,6 +119,7 @@ adminUserSettings.role=Role
|
||||||
adminUserSettings.actions=Actions
|
adminUserSettings.actions=Actions
|
||||||
adminUserSettings.apiUser=Limited API User
|
adminUserSettings.apiUser=Limited API User
|
||||||
adminUserSettings.webOnlyUser=Web Only User
|
adminUserSettings.webOnlyUser=Web Only User
|
||||||
|
adminUserSettings.demoUser=Demo User (No custom settings)
|
||||||
adminUserSettings.forceChange=Force user to change username/password on login
|
adminUserSettings.forceChange=Force user to change username/password on login
|
||||||
adminUserSettings.submit=Save User
|
adminUserSettings.submit=Save User
|
||||||
|
|
||||||
|
@ -255,6 +256,10 @@ home.removeBlanks.title=Usuń puste strony
|
||||||
home.removeBlanks.desc=Wykrywa i usuwa puste strony z dokumentu PDF
|
home.removeBlanks.desc=Wykrywa i usuwa puste strony z dokumentu PDF
|
||||||
removeBlanks.tags=cleanup,streamline,non-content,organize
|
removeBlanks.tags=cleanup,streamline,non-content,organize
|
||||||
|
|
||||||
|
home.removeAnnotations.title=Remove Annotations
|
||||||
|
home.removeAnnotations.desc=Removes all comments/annotations from a PDF
|
||||||
|
removeAnnotations.tags=comments,highlight,notes,markup,remove
|
||||||
|
|
||||||
home.compare.title=Porównaj
|
home.compare.title=Porównaj
|
||||||
home.compare.desc=Porównuje i pokazuje różnice między dwoma dokumentami PDF
|
home.compare.desc=Porównuje i pokazuje różnice między dwoma dokumentami PDF
|
||||||
compare.tags=differentiate,contrast,changes,analysis
|
compare.tags=differentiate,contrast,changes,analysis
|
||||||
|
@ -539,6 +544,12 @@ removeBlanks.whitePercentDesc=Procent strony, która musi być biała, aby zosta
|
||||||
removeBlanks.submit=Usuń puste
|
removeBlanks.submit=Usuń puste
|
||||||
|
|
||||||
|
|
||||||
|
#removeAnnotations
|
||||||
|
removeAnnotations.title=Remove Annotations
|
||||||
|
removeAnnotations.header=Remove Annotations
|
||||||
|
removeAnnotations.submit=Remove
|
||||||
|
|
||||||
|
|
||||||
#compare
|
#compare
|
||||||
compare.title=Porównaj
|
compare.title=Porównaj
|
||||||
compare.header=Porównaj PDF(y)
|
compare.header=Porównaj PDF(y)
|
||||||
|
|
|
@ -119,6 +119,7 @@ adminUserSettings.role=Role
|
||||||
adminUserSettings.actions=Actions
|
adminUserSettings.actions=Actions
|
||||||
adminUserSettings.apiUser=Limited API User
|
adminUserSettings.apiUser=Limited API User
|
||||||
adminUserSettings.webOnlyUser=Web Only User
|
adminUserSettings.webOnlyUser=Web Only User
|
||||||
|
adminUserSettings.demoUser=Demo User (No custom settings)
|
||||||
adminUserSettings.forceChange=Force user to change username/password on login
|
adminUserSettings.forceChange=Force user to change username/password on login
|
||||||
adminUserSettings.submit=Save User
|
adminUserSettings.submit=Save User
|
||||||
|
|
||||||
|
@ -255,6 +256,10 @@ home.removeBlanks.title=Удалить пустые страницы
|
||||||
home.removeBlanks.desc=Обнаруживает и удаляет пустые страницы из документа
|
home.removeBlanks.desc=Обнаруживает и удаляет пустые страницы из документа
|
||||||
removeBlanks.tags=cleanup,streamline,non-content,organize
|
removeBlanks.tags=cleanup,streamline,non-content,organize
|
||||||
|
|
||||||
|
home.removeAnnotations.title=Remove Annotations
|
||||||
|
home.removeAnnotations.desc=Removes all comments/annotations from a PDF
|
||||||
|
removeAnnotations.tags=comments,highlight,notes,markup,remove
|
||||||
|
|
||||||
home.compare.title=Сравнение
|
home.compare.title=Сравнение
|
||||||
home.compare.desc=Сравнивает и показывает различия между двумя PDF-документами
|
home.compare.desc=Сравнивает и показывает различия между двумя PDF-документами
|
||||||
compare.tags=differentiate,contrast,changes,analysis
|
compare.tags=differentiate,contrast,changes,analysis
|
||||||
|
@ -539,6 +544,12 @@ removeBlanks.whitePercentDesc=Общий процент белого на стр
|
||||||
removeBlanks.submit=Удалить Пустые
|
removeBlanks.submit=Удалить Пустые
|
||||||
|
|
||||||
|
|
||||||
|
#removeAnnotations
|
||||||
|
removeAnnotations.title=Remove Annotations
|
||||||
|
removeAnnotations.header=Remove Annotations
|
||||||
|
removeAnnotations.submit=Remove
|
||||||
|
|
||||||
|
|
||||||
#compare
|
#compare
|
||||||
compare.title=Сравнение
|
compare.title=Сравнение
|
||||||
compare.header=Сравнение PDFы
|
compare.header=Сравнение PDFы
|
||||||
|
|
|
@ -119,6 +119,7 @@ adminUserSettings.role=Role
|
||||||
adminUserSettings.actions=Actions
|
adminUserSettings.actions=Actions
|
||||||
adminUserSettings.apiUser=Limited API User
|
adminUserSettings.apiUser=Limited API User
|
||||||
adminUserSettings.webOnlyUser=Web Only User
|
adminUserSettings.webOnlyUser=Web Only User
|
||||||
|
adminUserSettings.demoUser=Demo User (No custom settings)
|
||||||
adminUserSettings.forceChange=Force user to change username/password on login
|
adminUserSettings.forceChange=Force user to change username/password on login
|
||||||
adminUserSettings.submit=Save User
|
adminUserSettings.submit=Save User
|
||||||
|
|
||||||
|
@ -255,6 +256,10 @@ home.removeBlanks.title=Ta bort tomma sidor
|
||||||
home.removeBlanks.desc=Känner av och tar bort tomma sidor från ett dokument
|
home.removeBlanks.desc=Känner av och tar bort tomma sidor från ett dokument
|
||||||
removeBlanks.tags=cleanup,streamline,non-content,organize
|
removeBlanks.tags=cleanup,streamline,non-content,organize
|
||||||
|
|
||||||
|
home.removeAnnotations.title=Remove Annotations
|
||||||
|
home.removeAnnotations.desc=Removes all comments/annotations from a PDF
|
||||||
|
removeAnnotations.tags=comments,highlight,notes,markup,remove
|
||||||
|
|
||||||
home.compare.title=Jämför
|
home.compare.title=Jämför
|
||||||
home.compare.desc=Jämför och visar skillnaderna mellan 2 PDF-dokument
|
home.compare.desc=Jämför och visar skillnaderna mellan 2 PDF-dokument
|
||||||
compare.tags=differentiate,contrast,changes,analysis
|
compare.tags=differentiate,contrast,changes,analysis
|
||||||
|
@ -539,6 +544,12 @@ removeBlanks.whitePercentDesc=Procentandel av sidan som måste vara vit för att
|
||||||
removeBlanks.submit=Ta bort tomrum
|
removeBlanks.submit=Ta bort tomrum
|
||||||
|
|
||||||
|
|
||||||
|
#removeAnnotations
|
||||||
|
removeAnnotations.title=Remove Annotations
|
||||||
|
removeAnnotations.header=Remove Annotations
|
||||||
|
removeAnnotations.submit=Remove
|
||||||
|
|
||||||
|
|
||||||
#compare
|
#compare
|
||||||
compare.title=Jämför
|
compare.title=Jämför
|
||||||
compare.header=Jämför PDF-filer
|
compare.header=Jämför PDF-filer
|
||||||
|
|
|
@ -119,6 +119,7 @@ adminUserSettings.role=Rol
|
||||||
adminUserSettings.actions=Eylemler
|
adminUserSettings.actions=Eylemler
|
||||||
adminUserSettings.apiUser=Sınırlı API Kullanıcısı
|
adminUserSettings.apiUser=Sınırlı API Kullanıcısı
|
||||||
adminUserSettings.webOnlyUser=Sadece Web Kullanıcısı
|
adminUserSettings.webOnlyUser=Sadece Web Kullanıcısı
|
||||||
|
adminUserSettings.demoUser=Demo User (No custom settings)
|
||||||
adminUserSettings.forceChange=Kullanıcının girişte kullanıcı adı/şifre değiştirmesini zorla
|
adminUserSettings.forceChange=Kullanıcının girişte kullanıcı adı/şifre değiştirmesini zorla
|
||||||
adminUserSettings.submit=Kullanıcıyı Kaydet
|
adminUserSettings.submit=Kullanıcıyı Kaydet
|
||||||
|
|
||||||
|
@ -255,6 +256,10 @@ home.removeBlanks.title=Boş Sayfaları Kaldır
|
||||||
home.removeBlanks.desc=Bir belgeden boş sayfaları tespit eder ve kaldırır
|
home.removeBlanks.desc=Bir belgeden boş sayfaları tespit eder ve kaldırır
|
||||||
removeBlanks.tags=temizle,sadeleştir,içeriksiz,düzenle
|
removeBlanks.tags=temizle,sadeleştir,içeriksiz,düzenle
|
||||||
|
|
||||||
|
home.removeAnnotations.title=Remove Annotations
|
||||||
|
home.removeAnnotations.desc=Removes all comments/annotations from a PDF
|
||||||
|
removeAnnotations.tags=comments,highlight,notes,markup,remove
|
||||||
|
|
||||||
home.compare.title=Karşılaştır
|
home.compare.title=Karşılaştır
|
||||||
home.compare.desc=2 PDF Belgesi arasındaki farkları karşılaştırır ve gösterir
|
home.compare.desc=2 PDF Belgesi arasındaki farkları karşılaştırır ve gösterir
|
||||||
compare.tags=farklılaştır,karşılaştır,değişiklikler,analiz
|
compare.tags=farklılaştır,karşılaştır,değişiklikler,analiz
|
||||||
|
@ -539,6 +544,12 @@ removeBlanks.whitePercentDesc=Bir sayfanın 'beyaz' pixel olması gereken yüzde
|
||||||
removeBlanks.submit=Boşları Kaldır
|
removeBlanks.submit=Boşları Kaldır
|
||||||
|
|
||||||
|
|
||||||
|
#removeAnnotations
|
||||||
|
removeAnnotations.title=Remove Annotations
|
||||||
|
removeAnnotations.header=Remove Annotations
|
||||||
|
removeAnnotations.submit=Remove
|
||||||
|
|
||||||
|
|
||||||
#compare
|
#compare
|
||||||
compare.title=Karşılaştır
|
compare.title=Karşılaştır
|
||||||
compare.header=PDF'leri Karşılaştır
|
compare.header=PDF'leri Karşılaştır
|
||||||
|
|
|
@ -119,6 +119,7 @@ adminUserSettings.role=角色
|
||||||
adminUserSettings.actions=操作
|
adminUserSettings.actions=操作
|
||||||
adminUserSettings.apiUser=有限 API 用户
|
adminUserSettings.apiUser=有限 API 用户
|
||||||
adminUserSettings.webOnlyUser=仅限 Web 用户
|
adminUserSettings.webOnlyUser=仅限 Web 用户
|
||||||
|
adminUserSettings.demoUser=Demo User (No custom settings)
|
||||||
adminUserSettings.forceChange=强制用户在登录时更改用户名/密码
|
adminUserSettings.forceChange=强制用户在登录时更改用户名/密码
|
||||||
adminUserSettings.submit=保存用户
|
adminUserSettings.submit=保存用户
|
||||||
|
|
||||||
|
@ -126,12 +127,12 @@ adminUserSettings.submit=保存用户
|
||||||
# HOME-PAGE #
|
# HOME-PAGE #
|
||||||
#############
|
#############
|
||||||
home.desc=CZL一站式服务,满足您的所有PDF需求。
|
home.desc=CZL一站式服务,满足您的所有PDF需求。
|
||||||
home.searchBar=Search for features...
|
home.searchBar=搜索您需要的功能...
|
||||||
|
|
||||||
|
|
||||||
home.viewPdf.title=View PDF
|
home.viewPdf.title=浏览PDF
|
||||||
home.viewPdf.desc=View, annotate, add text or images
|
home.viewPdf.desc=浏览、注释、添加文本或图像
|
||||||
viewPdf.tags=view,read,annotate,text,image
|
viewPdf.tags=浏览、阅读、注释、文本、图像
|
||||||
|
|
||||||
home.multiTool.title=PDF多功能工具
|
home.multiTool.title=PDF多功能工具
|
||||||
home.multiTool.desc=合并、旋转、重新排列和删除PDF页面
|
home.multiTool.desc=合并、旋转、重新排列和删除PDF页面
|
||||||
|
@ -255,6 +256,10 @@ home.removeBlanks.title=删除空白页
|
||||||
home.removeBlanks.desc=检测并删除文档中的空白页
|
home.removeBlanks.desc=检测并删除文档中的空白页
|
||||||
removeBlanks.tags=清理、简化、非内容、整理
|
removeBlanks.tags=清理、简化、非内容、整理
|
||||||
|
|
||||||
|
home.removeAnnotations.title=删除标注
|
||||||
|
home.removeAnnotations.desc=删除PDF中的所有标注/评论
|
||||||
|
removeAnnotations.tags=评论、高亮、笔记、标注、删除
|
||||||
|
|
||||||
home.compare.title=比较
|
home.compare.title=比较
|
||||||
home.compare.desc=比较并显示两个PDF文档之间的差异
|
home.compare.desc=比较并显示两个PDF文档之间的差异
|
||||||
compare.tags=区分、对比、更改、分析
|
compare.tags=区分、对比、更改、分析
|
||||||
|
@ -337,22 +342,22 @@ home.autoRedact.desc=根据输入文本自动删除(覆盖)PDF中的文本
|
||||||
showJS.tags=JavaScript
|
showJS.tags=JavaScript
|
||||||
|
|
||||||
home.tableExtraxt.title=PDF to CSV
|
home.tableExtraxt.title=PDF to CSV
|
||||||
home.tableExtraxt.desc=Extracts Tables from a PDF converting it to CSV
|
home.tableExtraxt.desc=从PDF中提取表格并将其转换为CSV
|
||||||
tableExtraxt.tags=CSV,Table Extraction,extract,convert
|
tableExtraxt.tags=CSV、表格提取、提取、转换
|
||||||
|
|
||||||
|
|
||||||
home.autoSizeSplitPDF.title=Auto Split by Size/Count
|
home.autoSizeSplitPDF.title=自动根据大小/数目拆分PDF
|
||||||
home.autoSizeSplitPDF.desc=Split a single PDF into multiple documents based on size, page count, or document count
|
home.autoSizeSplitPDF.desc=将单个PDF拆分为多个文档,基于大小、页数或文档数
|
||||||
autoSizeSplitPDF.tags=pdf,split,document,organization
|
autoSizeSplitPDF.tags=pdf、拆分、文件、组织
|
||||||
|
|
||||||
|
|
||||||
home.overlay-pdfs.title=Overlay PDFs
|
home.overlay-pdfs.title=叠加PDF
|
||||||
home.overlay-pdfs.desc=Overlays PDFs on-top of another PDF
|
home.overlay-pdfs.desc=将PDF叠加在另一个PDF上
|
||||||
overlay-pdfs.tags=Overlay
|
overlay-pdfs.tags=叠加
|
||||||
|
|
||||||
home.split-by-sections.title=Split PDF by Sections
|
home.split-by-sections.title=拆分PDF成小块
|
||||||
home.split-by-sections.desc=Divide each page of a PDF into smaller horizontal and vertical sections
|
home.split-by-sections.desc=将PDF的每一页分割成更小的水平和垂直的部分
|
||||||
split-by-sections.tags=Section Split, Divide, Customize
|
split-by-sections.tags=章节拆分、分割、自定义
|
||||||
|
|
||||||
###########################
|
###########################
|
||||||
# #
|
# #
|
||||||
|
@ -539,6 +544,12 @@ removeBlanks.whitePercentDesc=必须为白色才能删除的页面百分比
|
||||||
removeBlanks.submit=删除空白
|
removeBlanks.submit=删除空白
|
||||||
|
|
||||||
|
|
||||||
|
#removeAnnotations
|
||||||
|
removeAnnotations.title=Remove Annotations
|
||||||
|
removeAnnotations.header=Remove Annotations
|
||||||
|
removeAnnotations.submit=Remove
|
||||||
|
|
||||||
|
|
||||||
#compare
|
#compare
|
||||||
compare.title=比较
|
compare.title=比较
|
||||||
compare.header=比较 PDF
|
compare.header=比较 PDF
|
||||||
|
@ -843,43 +854,43 @@ PDFToXML.credit=此服务使用LibreOffice进行文件转换。
|
||||||
PDFToXML.submit=转换
|
PDFToXML.submit=转换
|
||||||
|
|
||||||
#PDFToCSV
|
#PDFToCSV
|
||||||
PDFToCSV.title=PDF ? CSV
|
PDFToCSV.title=PDF To CSV
|
||||||
PDFToCSV.header=PDF ? CSV
|
PDFToCSV.header=将 PDF 转换为 CSV
|
||||||
PDFToCSV.prompt=Choose page to extract table
|
PDFToCSV.prompt=选择需要提取表格的页面
|
||||||
PDFToCSV.submit=??
|
PDFToCSV.submit=提取
|
||||||
|
|
||||||
#split-by-size-or-count
|
#split-by-size-or-count
|
||||||
split-by-size-or-count.header=Split PDF by Size or Count
|
split-by-size-or-count.header=按照大小或数目拆分PDF
|
||||||
split-by-size-or-count.type.label=Select Split Type
|
split-by-size-or-count.type.label=选择拆分类型
|
||||||
split-by-size-or-count.type.size=By Size
|
split-by-size-or-count.type.size=按照大小
|
||||||
split-by-size-or-count.type.pageCount=By Page Count
|
split-by-size-or-count.type.pageCount=按照页数
|
||||||
split-by-size-or-count.type.docCount=By Document Count
|
split-by-size-or-count.type.docCount=按照文档数
|
||||||
split-by-size-or-count.value.label=Enter Value
|
split-by-size-or-count.value.label=输入数值
|
||||||
split-by-size-or-count.value.placeholder=Enter size (e.g., 2MB or 3KB) or count (e.g., 5)
|
split-by-size-or-count.value.placeholder=输入大小(例如,2MB或3KB)或数目(例如,5)
|
||||||
split-by-size-or-count.submit=Submit
|
split-by-size-or-count.submit=提交
|
||||||
|
|
||||||
|
|
||||||
#overlay-pdfs
|
#overlay-pdfs
|
||||||
overlay-pdfs.header=Overlay PDF Files
|
overlay-pdfs.header=叠加PDF文件
|
||||||
overlay-pdfs.baseFile.label=Select Base PDF File
|
overlay-pdfs.baseFile.label=选择基础PDF文件
|
||||||
overlay-pdfs.overlayFiles.label=Select Overlay PDF Files
|
overlay-pdfs.overlayFiles.label=选择需要叠加在基础上的PDF文件
|
||||||
overlay-pdfs.mode.label=Select Overlay Mode
|
overlay-pdfs.mode.label=选择叠加模式
|
||||||
overlay-pdfs.mode.sequential=Sequential Overlay
|
overlay-pdfs.mode.sequential=按顺序叠加
|
||||||
overlay-pdfs.mode.interleaved=Interleaved Overlay
|
overlay-pdfs.mode.interleaved=交错叠加
|
||||||
overlay-pdfs.mode.fixedRepeat=Fixed Repeat Overlay
|
overlay-pdfs.mode.fixedRepeat=固定重复叠加
|
||||||
overlay-pdfs.counts.label=Overlay Counts (for Fixed Repeat Mode)
|
overlay-pdfs.counts.label=叠加次数(仅限固定重复叠加模式)
|
||||||
overlay-pdfs.counts.placeholder=Enter comma-separated counts (e.g., 2,3,1)
|
overlay-pdfs.counts.placeholder=输入用逗号分隔的次数(例如,2,3,1)
|
||||||
overlay-pdfs.position.label=Select Overlay Position
|
overlay-pdfs.position.label=选择叠加位置
|
||||||
overlay-pdfs.position.foreground=Foreground
|
overlay-pdfs.position.foreground=前面(上面)
|
||||||
overlay-pdfs.position.background=Background
|
overlay-pdfs.position.background=后面(下面)
|
||||||
overlay-pdfs.submit=Submit
|
overlay-pdfs.submit=提交
|
||||||
|
|
||||||
|
|
||||||
#split-by-sections
|
#split-by-sections
|
||||||
split-by-sections.title=Split PDF by Sections
|
split-by-sections.title=按照块(Section)拆分PDF
|
||||||
split-by-sections.header=Split PDF into Sections
|
split-by-sections.header=将PDF拆分成块
|
||||||
split-by-sections.horizontal.label=Horizontal Divisions
|
split-by-sections.horizontal.label=水平分割
|
||||||
split-by-sections.vertical.label=Vertical Divisions
|
split-by-sections.vertical.label=垂直分割
|
||||||
split-by-sections.horizontal.placeholder=Enter number of horizontal divisions
|
split-by-sections.horizontal.placeholder=输入水平分割数
|
||||||
split-by-sections.vertical.placeholder=Enter number of vertical divisions
|
split-by-sections.vertical.placeholder=输入垂直分割数
|
||||||
split-by-sections.submit=Split PDF
|
split-by-sections.submit=分割PDF
|
||||||
|
|
|
@ -5,10 +5,13 @@
|
||||||
security:
|
security:
|
||||||
enableLogin: false # set to 'true' to enable login
|
enableLogin: false # set to 'true' to enable login
|
||||||
csrfDisabled: true
|
csrfDisabled: true
|
||||||
|
loginAttemptCount: 5 # lock user account after 5 tries
|
||||||
|
loginResetTimeMinutes : 120 # lock account for 2 hours after x attempts
|
||||||
|
|
||||||
system:
|
system:
|
||||||
defaultLocale: 'en-US' # Set the default language (e.g. 'de-DE', 'fr-FR', etc)
|
defaultLocale: 'en-US' # Set the default language (e.g. 'de-DE', 'fr-FR', etc)
|
||||||
googlevisibility: false # 'true' to allow Google visibility (via robots.txt), 'false' to disallow
|
googlevisibility: false # 'true' to allow Google visibility (via robots.txt), 'false' to disallow
|
||||||
|
enableAlphaFunctionality: false # Set to enable functionality which might need more testing before it fully goes live (This feature might make no changes)
|
||||||
|
|
||||||
#ui:
|
#ui:
|
||||||
# appName: exampleAppName # Application's visible name
|
# appName: exampleAppName # Application's visible name
|
||||||
|
|
|
@ -75,6 +75,13 @@ table th, table td {
|
||||||
border: none;
|
border: none;
|
||||||
color: #fff !important;
|
color: #fff !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn-warning {
|
||||||
|
background-color: #ffc107 !important;
|
||||||
|
border: none;
|
||||||
|
color: #000 !important;
|
||||||
|
}
|
||||||
|
|
||||||
.btn-outline-secondary {
|
.btn-outline-secondary {
|
||||||
color: #fff !important;
|
color: #fff !important;
|
||||||
border-color: #fff;
|
border-color: #fff;
|
||||||
|
@ -92,6 +99,11 @@ hr {
|
||||||
background-color: rgba(255, 255, 255, 0.6); /* for some browsers that might use background instead of border for <hr> */
|
background-color: rgba(255, 255, 255, 0.6); /* for some browsers that might use background instead of border for <hr> */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
color: #fff !important;
|
||||||
|
border-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
#global-buttons-container input {
|
#global-buttons-container input {
|
||||||
background-color: #323948;
|
background-color: #323948;
|
||||||
caret-color: #ffffff;
|
caret-color: #ffffff;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#searchBar {
|
#searchBar {
|
||||||
background-image: url('/images/search.svg');
|
background-image: url('../images/search.svg');
|
||||||
background-position: 16px 16px;
|
background-position: 16px 16px;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
|
@ -12,20 +12,17 @@ function validatePipeline() {
|
||||||
if (currentOperation === '/add-password') {
|
if (currentOperation === '/add-password') {
|
||||||
containsAddPassword = true;
|
containsAddPassword = true;
|
||||||
}
|
}
|
||||||
console.log(currentOperation);
|
|
||||||
console.log(apiDocs[currentOperation]);
|
|
||||||
let currentOperationDescription = apiDocs[currentOperation]?.post?.description || "";
|
let currentOperationDescription = apiDocs[currentOperation]?.post?.description || "";
|
||||||
let nextOperationDescription = apiDocs[nextOperation]?.post?.description || "";
|
let nextOperationDescription = apiDocs[nextOperation]?.post?.description || "";
|
||||||
|
|
||||||
console.log("currentOperationDescription", currentOperationDescription);
|
// Strip off 'ZIP-' prefix
|
||||||
console.log("nextOperationDescription", nextOperationDescription);
|
currentOperationDescription = currentOperationDescription.replace("ZIP-", '');
|
||||||
|
nextOperationDescription = nextOperationDescription.replace("ZIP-", '');
|
||||||
|
|
||||||
let currentOperationOutput = currentOperationDescription.match(/Output:([A-Z\/]*)/)?.[1] || "";
|
let currentOperationOutput = currentOperationDescription.match(/Output:([A-Z\/]*)/)?.[1] || "";
|
||||||
let nextOperationInput = nextOperationDescription.match(/Input:([A-Z\/]*)/)?.[1] || "";
|
let nextOperationInput = nextOperationDescription.match(/Input:([A-Z\/]*)/)?.[1] || "";
|
||||||
|
|
||||||
console.log("Operation " + currentOperation + " Output: " + currentOperationOutput);
|
|
||||||
console.log("Operation " + nextOperation + " Input: " + nextOperationInput);
|
|
||||||
|
|
||||||
// Splitting in case of multiple possible output/input
|
// Splitting in case of multiple possible output/input
|
||||||
let currentOperationOutputArr = currentOperationOutput.split('/');
|
let currentOperationOutputArr = currentOperationOutput.split('/');
|
||||||
let nextOperationInputArr = nextOperationInput.split('/');
|
let nextOperationInputArr = nextOperationInput.split('/');
|
||||||
|
@ -35,6 +32,7 @@ function validatePipeline() {
|
||||||
console.log(`Intersection: ${intersection}`);
|
console.log(`Intersection: ${intersection}`);
|
||||||
|
|
||||||
if (intersection.length === 0) {
|
if (intersection.length === 0) {
|
||||||
|
updateValidateButton(false);
|
||||||
isValid = false;
|
isValid = false;
|
||||||
console.log(`Incompatible operations: The output of operation '${currentOperation}' (${currentOperationOutput}) is not compatible with the input of the following operation '${nextOperation}' (${nextOperationInput}).`);
|
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}).`);
|
alert(`Incompatible operations: The output of operation '${currentOperation}' (${currentOperationOutput}) is not compatible with the input of the following operation '${nextOperation}' (${nextOperationInput}).`);
|
||||||
|
@ -43,6 +41,7 @@ function validatePipeline() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (containsAddPassword && pipelineListItems[pipelineListItems.length - 1].querySelector('.operationName').textContent !== '/add-password') {
|
if (containsAddPassword && pipelineListItems[pipelineListItems.length - 1].querySelector('.operationName').textContent !== '/add-password') {
|
||||||
|
updateValidateButton(false);
|
||||||
alert('The "add-password" operation should be at the end of the operations sequence. Please adjust the operations order.');
|
alert('The "add-password" operation should be at the end of the operations sequence. Please adjust the operations order.');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -53,10 +52,20 @@ function validatePipeline() {
|
||||||
console.error('Pipeline is not valid');
|
console.error('Pipeline is not valid');
|
||||||
// Stop operation, maybe display an error to the user
|
// Stop operation, maybe display an error to the user
|
||||||
}
|
}
|
||||||
|
updateValidateButton(isValid);
|
||||||
return isValid;
|
return isValid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateValidateButton(isValid) {
|
||||||
|
var validateButton = document.getElementById('validateButton');
|
||||||
|
if (isValid) {
|
||||||
|
validateButton.classList.remove('btn-danger');
|
||||||
|
validateButton.classList.add('btn-success');
|
||||||
|
} else {
|
||||||
|
validateButton.classList.remove('btn-success');
|
||||||
|
validateButton.classList.add('btn-danger');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -67,14 +76,14 @@ document.getElementById('submitConfigBtn').addEventListener('click', function()
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let selectedOperation = document.getElementById('operationsDropdown').value;
|
let selectedOperation = document.getElementById('operationsDropdown').value;
|
||||||
let parameters = operationSettings[selectedOperation] || {};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
var pipelineName = document.getElementById('pipelineName').value;
|
||||||
|
let pipelineList = document.getElementById('pipelineList').children;
|
||||||
let pipelineConfig = {
|
let pipelineConfig = {
|
||||||
"name": "uniquePipelineName",
|
"name": pipelineName,
|
||||||
"pipeline": [{
|
"pipeline": [],
|
||||||
"operation": selectedOperation,
|
|
||||||
"parameters": parameters
|
|
||||||
}],
|
|
||||||
"_examples": {
|
"_examples": {
|
||||||
"outputDir": "{outputFolder}/{folderName}",
|
"outputDir": "{outputFolder}/{folderName}",
|
||||||
"outputFileName": "{filename}-{pipelineName}-{date}-{time}"
|
"outputFileName": "{filename}-{pipelineName}-{date}-{time}"
|
||||||
|
@ -83,6 +92,28 @@ document.getElementById('submitConfigBtn').addEventListener('click', function()
|
||||||
"outputFileName": "{filename}"
|
"outputFileName": "{filename}"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
for (let i = 0; i < pipelineList.length; i++) {
|
||||||
|
let operationName = pipelineList[i].querySelector('.operationName').textContent;
|
||||||
|
let parameters = operationSettings[operationName] || {};
|
||||||
|
|
||||||
|
pipelineConfig.pipeline.push({
|
||||||
|
"operation": operationName,
|
||||||
|
"parameters": parameters
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
let pipelineConfigJson = JSON.stringify(pipelineConfig, null, 2);
|
let pipelineConfigJson = JSON.stringify(pipelineConfig, null, 2);
|
||||||
|
|
||||||
let formData = new FormData();
|
let formData = new FormData();
|
||||||
|
@ -99,37 +130,50 @@ document.getElementById('submitConfigBtn').addEventListener('click', function()
|
||||||
formData.append('json', pipelineConfigJson);
|
formData.append('json', pipelineConfigJson);
|
||||||
console.log("formData", formData);
|
console.log("formData", formData);
|
||||||
|
|
||||||
fetch('/handleData', {
|
fetch('api/v1/pipeline/handleData', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: formData
|
body: formData
|
||||||
})
|
})
|
||||||
.then(response => response.blob())
|
.then(response => {
|
||||||
.then(blob => {
|
// Save the response to use it later
|
||||||
|
const responseToUseLater = response;
|
||||||
|
|
||||||
|
return response.blob().then(blob => {
|
||||||
let url = window.URL.createObjectURL(blob);
|
let url = window.URL.createObjectURL(blob);
|
||||||
let a = document.createElement('a');
|
let a = document.createElement('a');
|
||||||
a.href = url;
|
a.href = url;
|
||||||
a.download = 'outputfile';
|
|
||||||
|
// Use responseToUseLater instead of response
|
||||||
|
const contentDisposition = responseToUseLater.headers.get('Content-Disposition');
|
||||||
|
let filename = 'download';
|
||||||
|
if (contentDisposition && contentDisposition.indexOf('attachment') !== -1) {
|
||||||
|
filename = decodeURIComponent(contentDisposition.split('filename=')[1].replace(/"/g, '')).trim();
|
||||||
|
}
|
||||||
|
a.download = filename;
|
||||||
|
|
||||||
document.body.appendChild(a);
|
document.body.appendChild(a);
|
||||||
a.click();
|
a.click();
|
||||||
a.remove();
|
a.remove();
|
||||||
|
});
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('Error:', error);
|
console.error('Error:', error);
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let apiDocs = {};
|
let apiDocs = {};
|
||||||
|
let apiSchemas = {};
|
||||||
let operationSettings = {};
|
let operationSettings = {};
|
||||||
|
|
||||||
fetch('v3/api-docs')
|
fetch('v1/api-docs')
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
|
|
||||||
apiDocs = data.paths;
|
apiDocs = data.paths;
|
||||||
|
apiSchemas = data.components.schemas;
|
||||||
let operationsDropdown = document.getElementById('operationsDropdown');
|
let operationsDropdown = document.getElementById('operationsDropdown');
|
||||||
const ignoreOperations = ["/handleData", "operationToIgnore"]; // Add the operations you want to ignore here
|
const ignoreOperations = ["/api/v1/pipeline/handleData", "/api/v1/pipeline/operationToIgnore"]; // Add the operations you want to ignore here
|
||||||
|
|
||||||
operationsDropdown.innerHTML = '';
|
operationsDropdown.innerHTML = '';
|
||||||
|
|
||||||
|
@ -138,6 +182,9 @@ fetch('v3/api-docs')
|
||||||
// Group operations by tags
|
// Group operations by tags
|
||||||
Object.keys(data.paths).forEach(operationPath => {
|
Object.keys(data.paths).forEach(operationPath => {
|
||||||
let operation = data.paths[operationPath].post;
|
let operation = data.paths[operationPath].post;
|
||||||
|
if(!operation || !operation.description) {
|
||||||
|
console.log(operationPath);
|
||||||
|
}
|
||||||
if (operation && !ignoreOperations.includes(operationPath) && !operation.description.includes("Type:MISO")) {
|
if (operation && !ignoreOperations.includes(operationPath) && !operation.description.includes("Type:MISO")) {
|
||||||
let operationTag = operation.tags[0]; // This assumes each operation has exactly one tag
|
let operationTag = operation.tags[0]; // This assumes each operation has exactly one tag
|
||||||
if (!operationsByTag[operationTag]) {
|
if (!operationsByTag[operationTag]) {
|
||||||
|
@ -146,9 +193,8 @@ fetch('v3/api-docs')
|
||||||
operationsByTag[operationTag].push(operationPath);
|
operationsByTag[operationTag].push(operationPath);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Specify the order of tags
|
// Specify the order of tags
|
||||||
let tagOrder = ["General", "Security", "Convert", "Other", "Filter"];
|
let tagOrder = ["General", "Security", "Convert", "Misc", "Filter"];
|
||||||
|
|
||||||
// Create dropdown options
|
// Create dropdown options
|
||||||
tagOrder.forEach(tag => {
|
tagOrder.forEach(tag => {
|
||||||
|
@ -158,8 +204,18 @@ fetch('v3/api-docs')
|
||||||
|
|
||||||
operationsByTag[tag].forEach(operationPath => {
|
operationsByTag[tag].forEach(operationPath => {
|
||||||
let option = document.createElement('option');
|
let option = document.createElement('option');
|
||||||
let operationWithoutSlash = operationPath.replace(/\//g, ''); // Remove slashes
|
|
||||||
option.textContent = operationWithoutSlash;
|
let operationPathDisplay = operationPath
|
||||||
|
operationPathDisplay = operationPath.replace(new RegExp("api/v1/" + tag.toLowerCase() + "/", 'i'), "");
|
||||||
|
|
||||||
|
|
||||||
|
if(operationPath.includes("/convert")){
|
||||||
|
operationPathDisplay = operationPathDisplay.replace(/^\//, '').replaceAll("/", " to ");
|
||||||
|
} else {
|
||||||
|
operationPathDisplay = operationPathDisplay.replace(/\//g, ''); // Remove slashes
|
||||||
|
}
|
||||||
|
operationPathDisplay = operationPathDisplay.replaceAll(" ","-");
|
||||||
|
option.textContent = operationPathDisplay;
|
||||||
option.value = operationPath; // Keep the value with slashes for querying
|
option.value = operationPath; // Keep the value with slashes for querying
|
||||||
group.appendChild(option);
|
group.appendChild(option);
|
||||||
});
|
});
|
||||||
|
@ -176,10 +232,22 @@ document.getElementById('addOperationBtn').addEventListener('click', function()
|
||||||
|
|
||||||
let listItem = document.createElement('li');
|
let listItem = document.createElement('li');
|
||||||
listItem.className = "list-group-item";
|
listItem.className = "list-group-item";
|
||||||
let hasSettings = (apiDocs[selectedOperation] && apiDocs[selectedOperation].post &&
|
let hasSettings = false;
|
||||||
((apiDocs[selectedOperation].post.parameters && apiDocs[selectedOperation].post.parameters.length > 0) ||
|
if (apiDocs[selectedOperation] && apiDocs[selectedOperation].post) {
|
||||||
(apiDocs[selectedOperation].post.requestBody &&
|
const postMethod = apiDocs[selectedOperation].post;
|
||||||
apiDocs[selectedOperation].post.requestBody.content['multipart/form-data'].schema.properties)));
|
|
||||||
|
// Check if parameters exist
|
||||||
|
if (postMethod.parameters && postMethod.parameters.length > 0) {
|
||||||
|
hasSettings = true;
|
||||||
|
} else if (postMethod.requestBody && postMethod.requestBody.content['multipart/form-data']) {
|
||||||
|
// Extract the reference key
|
||||||
|
const refKey = postMethod.requestBody.content['multipart/form-data'].schema['$ref'].split('/').pop();
|
||||||
|
// Check if the referenced schema exists and has properties
|
||||||
|
if (apiSchemas[refKey] && Object.keys(apiSchemas[refKey].properties).length > 0) {
|
||||||
|
hasSettings = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -188,14 +256,17 @@ document.getElementById('addOperationBtn').addEventListener('click', function()
|
||||||
<div class="d-flex justify-content-between align-items-center w-100">
|
<div class="d-flex justify-content-between align-items-center w-100">
|
||||||
<div class="operationName">${selectedOperation}</div>
|
<div class="operationName">${selectedOperation}</div>
|
||||||
<div class="arrows d-flex">
|
<div class="arrows d-flex">
|
||||||
<button class="btn btn-secondary move-up btn-margin"><span>↑</span></button>
|
<button class="btn btn-secondary move-up ms-1"><span>↑</span></button>
|
||||||
<button class="btn btn-secondary move-down btn-margin"><span>↓</span></button>
|
<button class="btn btn-secondary move-down ms-1"><span>↓</span></button>
|
||||||
<button class="btn btn-warning pipelineSettings btn-margin" ${hasSettings ? "" : "disabled"}><span style="color: ${hasSettings ? "black" : "grey"};">⚙️</span></button>
|
<button class="btn ${hasSettings ? 'btn-warning' : 'btn-secondary'} pipelineSettings ms-1" ${hasSettings ? "" : "disabled"}>
|
||||||
<button class="btn btn-danger remove"><span>X</span></button>
|
<span style="color: ${hasSettings ? "white" : "grey"};">⚙️</span>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-danger remove ms-1"><span>X</span></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|
||||||
pipelineList.appendChild(listItem);
|
pipelineList.appendChild(listItem);
|
||||||
|
|
||||||
listItem.querySelector('.move-up').addEventListener('click', function(event) {
|
listItem.querySelector('.move-up').addEventListener('click', function(event) {
|
||||||
|
@ -215,18 +286,23 @@ document.getElementById('addOperationBtn').addEventListener('click', function()
|
||||||
listItem.querySelector('.remove').addEventListener('click', function(event) {
|
listItem.querySelector('.remove').addEventListener('click', function(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
pipelineList.removeChild(listItem);
|
pipelineList.removeChild(listItem);
|
||||||
|
hideOrShowPipelineHeader();
|
||||||
});
|
});
|
||||||
|
|
||||||
listItem.querySelector('.pipelineSettings').addEventListener('click', function(event) {
|
listItem.querySelector('.pipelineSettings').addEventListener('click', function(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
showpipelineSettingsModal(selectedOperation);
|
showpipelineSettingsModal(selectedOperation);
|
||||||
|
hideOrShowPipelineHeader();
|
||||||
});
|
});
|
||||||
|
|
||||||
function showpipelineSettingsModal(operation) {
|
function showpipelineSettingsModal(operation) {
|
||||||
let pipelineSettingsModal = document.getElementById('pipelineSettingsModal');
|
let pipelineSettingsModal = document.getElementById('pipelineSettingsModal');
|
||||||
let pipelineSettingsContent = document.getElementById('pipelineSettingsContent');
|
let pipelineSettingsContent = document.getElementById('pipelineSettingsContent');
|
||||||
let operationData = apiDocs[operation].post.parameters || [];
|
let operationData = apiDocs[operation].post.parameters || [];
|
||||||
let requestBodyData = apiDocs[operation].post.requestBody.content['multipart/form-data'].schema.properties || {};
|
|
||||||
|
// Resolve the $ref reference to get actual schema properties
|
||||||
|
let refKey = apiDocs[operation].post.requestBody.content['multipart/form-data'].schema['$ref'].split('/').pop();
|
||||||
|
let requestBodyData = apiSchemas[refKey].properties || {};
|
||||||
|
|
||||||
// Combine operationData and requestBodyData into a single array
|
// Combine operationData and requestBodyData into a single array
|
||||||
operationData = operationData.concat(Object.keys(requestBodyData).map(key => ({
|
operationData = operationData.concat(Object.keys(requestBodyData).map(key => ({
|
||||||
|
@ -245,9 +321,13 @@ document.getElementById('addOperationBtn').addEventListener('click', function()
|
||||||
|
|
||||||
let parameterLabel = document.createElement('label');
|
let parameterLabel = document.createElement('label');
|
||||||
parameterLabel.textContent = `${parameter.name} (${parameter.schema.type}): `;
|
parameterLabel.textContent = `${parameter.name} (${parameter.schema.type}): `;
|
||||||
parameterLabel.title = parameter.description;
|
parameterLabel.title = parameter.schema.description;
|
||||||
|
parameterLabel.setAttribute('for', parameter.name);
|
||||||
parameterDiv.appendChild(parameterLabel);
|
parameterDiv.appendChild(parameterLabel);
|
||||||
|
|
||||||
|
let defaultValue = parameter.schema.example;
|
||||||
|
if (defaultValue === undefined) defaultValue = parameter.schema.default;
|
||||||
|
|
||||||
let parameterInput;
|
let parameterInput;
|
||||||
|
|
||||||
// check if enum exists in schema
|
// check if enum exists in schema
|
||||||
|
@ -277,11 +357,12 @@ document.getElementById('addOperationBtn').addEventListener('click', function()
|
||||||
parameterInput = document.createElement('input');
|
parameterInput = document.createElement('input');
|
||||||
parameterInput.type = 'text';
|
parameterInput.type = 'text';
|
||||||
parameterInput.className = "form-control";
|
parameterInput.className = "form-control";
|
||||||
parameterInput.value = "automatedFileInput";
|
parameterInput.value = "FileInputPathToBeInputtedManuallyOffline";
|
||||||
} else {
|
} else {
|
||||||
parameterInput = document.createElement('input');
|
parameterInput = document.createElement('input');
|
||||||
parameterInput.type = 'text';
|
parameterInput.type = 'text';
|
||||||
parameterInput.className = "form-control";
|
parameterInput.className = "form-control";
|
||||||
|
if (defaultValue !== undefined) parameterInput.value = defaultValue;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'number':
|
case 'number':
|
||||||
|
@ -289,10 +370,12 @@ document.getElementById('addOperationBtn').addEventListener('click', function()
|
||||||
parameterInput = document.createElement('input');
|
parameterInput = document.createElement('input');
|
||||||
parameterInput.type = 'number';
|
parameterInput.type = 'number';
|
||||||
parameterInput.className = "form-control";
|
parameterInput.className = "form-control";
|
||||||
|
if (defaultValue !== undefined) parameterInput.value = defaultValue;
|
||||||
break;
|
break;
|
||||||
case 'boolean':
|
case 'boolean':
|
||||||
parameterInput = document.createElement('input');
|
parameterInput = document.createElement('input');
|
||||||
parameterInput.type = 'checkbox';
|
parameterInput.type = 'checkbox';
|
||||||
|
if (defaultValue === true) parameterInput.checked = true;
|
||||||
break;
|
break;
|
||||||
case 'array':
|
case 'array':
|
||||||
case 'object':
|
case 'object':
|
||||||
|
@ -304,10 +387,13 @@ document.getElementById('addOperationBtn').addEventListener('click', function()
|
||||||
parameterInput = document.createElement('input');
|
parameterInput = document.createElement('input');
|
||||||
parameterInput.type = 'text';
|
parameterInput.type = 'text';
|
||||||
parameterInput.className = "form-control";
|
parameterInput.className = "form-control";
|
||||||
|
if (defaultValue !== undefined) parameterInput.value = defaultValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
parameterInput.id = parameter.name;
|
parameterInput.id = parameter.name;
|
||||||
|
|
||||||
|
console.log("defaultValue", defaultValue);
|
||||||
|
console.log("parameterInput", parameterInput);
|
||||||
if (operationSettings[operation] && operationSettings[operation][parameter.name] !== undefined) {
|
if (operationSettings[operation] && operationSettings[operation][parameter.name] !== undefined) {
|
||||||
let savedValue = operationSettings[operation][parameter.name];
|
let savedValue = operationSettings[operation][parameter.name];
|
||||||
|
|
||||||
|
@ -327,7 +413,7 @@ document.getElementById('addOperationBtn').addEventListener('click', function()
|
||||||
parameterInput.value = savedValue;
|
parameterInput.value = savedValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
console.log("parameterInput2", parameterInput);
|
||||||
parameterDiv.appendChild(parameterInput);
|
parameterDiv.appendChild(parameterInput);
|
||||||
|
|
||||||
pipelineSettingsContent.appendChild(parameterDiv);
|
pipelineSettingsContent.appendChild(parameterDiv);
|
||||||
|
@ -340,6 +426,7 @@ document.getElementById('addOperationBtn').addEventListener('click', function()
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
let settings = {};
|
let settings = {};
|
||||||
operationData.forEach(parameter => {
|
operationData.forEach(parameter => {
|
||||||
|
if(parameter.name !== "fileInput"){
|
||||||
let value = document.getElementById(parameter.name).value;
|
let value = document.getElementById(parameter.name).value;
|
||||||
switch (parameter.schema.type) {
|
switch (parameter.schema.type) {
|
||||||
case 'number':
|
case 'number':
|
||||||
|
@ -360,30 +447,44 @@ document.getElementById('addOperationBtn').addEventListener('click', function()
|
||||||
default:
|
default:
|
||||||
settings[parameter.name] = value;
|
settings[parameter.name] = value;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
operationSettings[operation] = settings;
|
operationSettings[operation] = settings;
|
||||||
console.log(settings);
|
//pipelineSettingsModal.style.display = "none";
|
||||||
pipelineSettingsModal.style.display = "none";
|
|
||||||
});
|
});
|
||||||
pipelineSettingsContent.appendChild(saveButton);
|
pipelineSettingsContent.appendChild(saveButton);
|
||||||
|
|
||||||
pipelineSettingsModal.style.display = "block";
|
//pipelineSettingsModal.style.display = "block";
|
||||||
|
|
||||||
pipelineSettingsModal.getElementsByClassName("close")[0].onclick = function() {
|
//pipelineSettingsModal.getElementsByClassName("close")[0].onclick = function() {
|
||||||
pipelineSettingsModal.style.display = "none";
|
// pipelineSettingsModal.style.display = "none";
|
||||||
}
|
//}
|
||||||
|
|
||||||
window.onclick = function(event) {
|
//window.onclick = function(event) {
|
||||||
if (event.target == pipelineSettingsModal) {
|
// if (event.target == pipelineSettingsModal) {
|
||||||
pipelineSettingsModal.style.display = "none";
|
// pipelineSettingsModal.style.display = "none";
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
}
|
}
|
||||||
|
hideOrShowPipelineHeader();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
var saveBtn = document.getElementById('savePipelineBtn');
|
||||||
|
|
||||||
|
// Remove any existing event listeners
|
||||||
|
saveBtn.removeEventListener('click', savePipeline);
|
||||||
|
|
||||||
|
// Add the event listener
|
||||||
|
saveBtn.addEventListener('click', savePipeline);
|
||||||
|
console.log("saveBtn", saveBtn)
|
||||||
|
function savePipeline() {
|
||||||
|
|
||||||
document.getElementById('savePipelineBtn').addEventListener('click', function() {
|
|
||||||
if (validatePipeline() === false) {
|
if (validatePipeline() === false) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var pipelineName = document.getElementById('pipelineName').value;
|
var pipelineName = document.getElementById('pipelineName').value;
|
||||||
let pipelineList = document.getElementById('pipelineList').children;
|
let pipelineList = document.getElementById('pipelineList').children;
|
||||||
let pipelineConfig = {
|
let pipelineConfig = {
|
||||||
|
@ -393,7 +494,7 @@ document.getElementById('addOperationBtn').addEventListener('click', function()
|
||||||
"outputDir": "{outputFolder}/{folderName}",
|
"outputDir": "{outputFolder}/{folderName}",
|
||||||
"outputFileName": "{filename}-{pipelineName}-{date}-{time}"
|
"outputFileName": "{filename}-{pipelineName}-{date}-{time}"
|
||||||
},
|
},
|
||||||
"outputDir": "httpWebRequest",
|
"outputDir": "{outputFolder}",
|
||||||
"outputFileName": "{filename}"
|
"outputFileName": "{filename}"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -401,23 +502,25 @@ document.getElementById('addOperationBtn').addEventListener('click', function()
|
||||||
let operationName = pipelineList[i].querySelector('.operationName').textContent;
|
let operationName = pipelineList[i].querySelector('.operationName').textContent;
|
||||||
let parameters = operationSettings[operationName] || {};
|
let parameters = operationSettings[operationName] || {};
|
||||||
|
|
||||||
|
parameters['fileInput'] = 'automated';
|
||||||
|
|
||||||
pipelineConfig.pipeline.push({
|
pipelineConfig.pipeline.push({
|
||||||
"operation": operationName,
|
"operation": operationName,
|
||||||
"parameters": parameters
|
"parameters": parameters
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
console.log("Downloading..");
|
||||||
let a = document.createElement('a');
|
let a = document.createElement('a');
|
||||||
a.href = URL.createObjectURL(new Blob([JSON.stringify(pipelineConfig, null, 2)], {
|
a.href = URL.createObjectURL(new Blob([JSON.stringify(pipelineConfig, null, 2)], {
|
||||||
type: 'application/json'
|
type: 'application/json'
|
||||||
}));
|
}));
|
||||||
a.download = 'pipelineConfig.json';
|
a.download = pipelineName + '.json';
|
||||||
a.style.display = 'none';
|
a.style.display = 'none';
|
||||||
|
|
||||||
document.body.appendChild(a);
|
document.body.appendChild(a);
|
||||||
a.click();
|
a.click();
|
||||||
document.body.removeChild(a);
|
document.body.removeChild(a);
|
||||||
});
|
}
|
||||||
|
|
||||||
async function processPipelineConfig(configString) {
|
async function processPipelineConfig(configString) {
|
||||||
let pipelineConfig = JSON.parse(configString);
|
let pipelineConfig = JSON.parse(configString);
|
||||||
|
@ -483,6 +586,7 @@ document.getElementById('addOperationBtn').addEventListener('click', function()
|
||||||
processPipelineConfig(event.target.result);
|
processPipelineConfig(event.target.result);
|
||||||
};
|
};
|
||||||
reader.readAsText(e.target.files[0]);
|
reader.readAsText(e.target.files[0]);
|
||||||
|
hideOrShowPipelineHeader();
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById('pipelineSelect').addEventListener('change', function(e) {
|
document.getElementById('pipelineSelect').addEventListener('change', function(e) {
|
||||||
|
@ -491,4 +595,15 @@ document.getElementById('addOperationBtn').addEventListener('click', function()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
});
|
function hideOrShowPipelineHeader() {
|
||||||
|
var pipelineHeader = document.getElementById('pipelineHeader');
|
||||||
|
var pipelineList = document.getElementById('pipelineList');
|
||||||
|
|
||||||
|
if (pipelineList.children.length === 0) {
|
||||||
|
// Hide the pipeline header if there are no items in the pipeline list
|
||||||
|
pipelineHeader.style.display = 'none';
|
||||||
|
} else {
|
||||||
|
// Show the pipeline header if there are items in the pipeline list
|
||||||
|
pipelineHeader.style.display = 'block';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -306,7 +306,7 @@
|
||||||
|
|
||||||
|
|
||||||
<div class="mb-3 mt-4">
|
<div class="mb-3 mt-4">
|
||||||
<a href="/logout">
|
<a href="logout">
|
||||||
<button type="button" class="btn btn-danger" th:text="#{account.signOut}">Sign Out</button>
|
<button type="button" class="btn btn-danger" th:text="#{account.signOut}">Sign Out</button>
|
||||||
</a>
|
</a>
|
||||||
<a th:if="${role == 'ROLE_ADMIN'}" href="addUsers" target="_blank">
|
<a th:if="${role == 'ROLE_ADMIN'}" href="addUsers" target="_blank">
|
||||||
|
|
|
@ -62,6 +62,7 @@
|
||||||
<option value="ROLE_USER" th:text="#{adminUserSettings.user}">User</option>
|
<option value="ROLE_USER" th:text="#{adminUserSettings.user}">User</option>
|
||||||
<option value="ROLE_LIMITED_API_USER" th:text="#{adminUserSettings.apiUser}">Limited API User</option>
|
<option value="ROLE_LIMITED_API_USER" th:text="#{adminUserSettings.apiUser}">Limited API User</option>
|
||||||
<option value="ROLE_WEB_ONLY_USER" th:text="#{adminUserSettings.webOnlyUser}">Web Only User</option>
|
<option value="ROLE_WEB_ONLY_USER" th:text="#{adminUserSettings.webOnlyUser}">Web Only User</option>
|
||||||
|
<option value="ROLE_DEMO_USER" th:text="#{adminUserSettings.demoUser}">Demo User</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
<div class="row justify-content-center">
|
<div class="row justify-content-center">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<h2 th:text="#{PDFToCSV.header}"></h2>
|
<h2 th:text="#{PDFToCSV.header}"></h2>
|
||||||
<form id="PDFToCSVForm" th:action="@{api/v1/convert/pdf-to-csv}" method="post" enctype="multipart/form-data">
|
<form id="PDFToCSVForm" th:action="@{api/v1/convert/pdf/csv}" method="post" enctype="multipart/form-data">
|
||||||
<input id="pageId" type="hidden" name="pageId" />
|
<input id="pageId" type="hidden" name="pageId" />
|
||||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||||
<button type="submit" class="btn btn-primary" th:text="#{PDFToCSV.submit}"></button>
|
<button type="submit" class="btn btn-primary" th:text="#{PDFToCSV.submit}"></button>
|
||||||
|
|
|
@ -32,12 +32,12 @@
|
||||||
<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">
|
<li th:if="${@enableAlphaFunctionality}" class="nav-item">
|
||||||
<a class="nav-link" href="#" th:href="@{pipeline}" th:classappend="${currentPage}=='pipeline' ? 'active' : ''" th:title="#{home.pipeline.desc}">
|
<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">
|
<img class="icon" src="images/pipeline.svg" alt="icon">
|
||||||
<span class="icon-text" th:text="#{home.pipeline.title}"></span>
|
<span class="icon-text" th:text="#{home.pipeline.title}"></span>
|
||||||
</a>
|
</a>
|
||||||
</li>-->
|
</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}=='crop' OR ${currentPage}=='adjust-contrast' OR ${currentPage}=='pdf-organizer' OR ${currentPage}=='rotate-pdf' OR ${currentPage}=='multi-page-layout' OR ${currentPage}=='scale-pages' OR ${currentPage}=='auto-split-pdf' OR ${currentPage}=='extract-page' OR ${currentPage}=='pdf-to-single-page' ? 'active' : ''">
|
<li class="nav-item dropdown" th:classappend="${currentPage}=='remove-pages' OR ${currentPage}=='merge-pdfs' OR ${currentPage}=='split-pdfs' OR ${currentPage}=='crop' OR ${currentPage}=='adjust-contrast' OR ${currentPage}=='pdf-organizer' OR ${currentPage}=='rotate-pdf' OR ${currentPage}=='multi-page-layout' OR ${currentPage}=='scale-pages' OR ${currentPage}=='auto-split-pdf' OR ${currentPage}=='extract-page' OR ${currentPage}=='pdf-to-single-page' ? 'active' : ''">
|
||||||
|
@ -185,7 +185,7 @@
|
||||||
<!-- Search Bar -->
|
<!-- Search Bar -->
|
||||||
<div class="collapse position-absolute" id="navbarSearch">
|
<div class="collapse position-absolute" id="navbarSearch">
|
||||||
<form class="d-flex p-2 bg-white border search-form" id="searchForm">
|
<form class="d-flex p-2 bg-white border search-form" id="searchForm">
|
||||||
<input class="form-control search-input" type="search" placeholder="Search" aria-label="Search" id="navbarSearchInput">
|
<input class="form-control search-input" type="search" th:placeholder="#{home.searchBar}" aria-label="Search" id="navbarSearchInput">
|
||||||
</form>
|
</form>
|
||||||
<!-- Search Results -->
|
<!-- Search Results -->
|
||||||
<div id="searchResults" class="border p-2 bg-white search-results"></div>
|
<div id="searchResults" class="border p-2 bg-white search-results"></div>
|
||||||
|
@ -293,7 +293,7 @@
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<a th:if="${@loginEnabled}" href="/logout">
|
<a th:if="${@loginEnabled}" href="logout">
|
||||||
<button type="button" class="btn btn-danger" th:text="#{settings.signOut}">Sign Out</button>
|
<button type="button" class="btn btn-danger" th:text="#{settings.signOut}">Sign Out</button>
|
||||||
</a>
|
</a>
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" th:text="#{close}"></button>
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" th:text="#{close}"></button>
|
||||||
|
|
|
@ -27,8 +27,9 @@
|
||||||
<input type="text" id="searchBar" onkeyup="filterCards()" th:placeholder="#{home.searchBar}">
|
<input type="text" id="searchBar" onkeyup="filterCards()" th:placeholder="#{home.searchBar}">
|
||||||
<div class="features-container ">
|
<div class="features-container ">
|
||||||
|
|
||||||
|
<th:block th:if="${@enableAlphaFunctionality}">
|
||||||
<!-- <div th:replace="~{fragments/card :: card(id='pipeline', cardTitle=#{home.pipeline.title}, cardText=#{home.pipeline.desc}, cardLink='pipeline', svgPath='images/pipeline.svg')}"></div> -->
|
<div th:replace="~{fragments/card :: card(id='pipeline', cardTitle=#{home.pipeline.title}, cardText=#{home.pipeline.desc}, cardLink='pipeline', svgPath='images/pipeline.svg')}"></div>
|
||||||
|
</th:block>
|
||||||
|
|
||||||
<div th:replace="~{fragments/card :: card(id='view-pdf', cardTitle=#{home.viewPdf.title}, cardText=#{home.viewPdf.desc}, cardLink='view-pdf', svgPath='images/book-opened.svg')}"></div>
|
<div th:replace="~{fragments/card :: card(id='view-pdf', cardTitle=#{home.viewPdf.title}, cardText=#{home.viewPdf.desc}, cardLink='view-pdf', svgPath='images/book-opened.svg')}"></div>
|
||||||
<div th:replace="~{fragments/card :: card(id='multi-tool', cardTitle=#{home.multiTool.title}, cardText=#{home.multiTool.desc}, cardLink='multi-tool', svgPath='images/tools.svg')}"></div>
|
<div th:replace="~{fragments/card :: card(id='multi-tool', cardTitle=#{home.multiTool.title}, cardText=#{home.multiTool.desc}, cardLink='multi-tool', svgPath='images/tools.svg')}"></div>
|
||||||
|
|
|
@ -36,6 +36,7 @@
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row justify-content-center">
|
<div class="row justify-content-center">
|
||||||
|
|
||||||
|
<h1>(Alpha) Pipeline Menu (Huge work in progress, very buggy!)</h1>
|
||||||
<div class="bordered-box">
|
<div class="bordered-box">
|
||||||
<div class="text-end text-top">
|
<div class="text-end text-top">
|
||||||
<button id="uploadPipelineBtn" class="btn btn-primary">Upload
|
<button id="uploadPipelineBtn" class="btn btn-primary">Upload
|
||||||
|
@ -47,7 +48,6 @@
|
||||||
<div class="center-element">
|
<div class="center-element">
|
||||||
<div class="element-margin">
|
<div class="element-margin">
|
||||||
<select id="pipelineSelect" class="custom-select">
|
<select id="pipelineSelect" class="custom-select">
|
||||||
<option value="">Select a pipeline</option>
|
|
||||||
<th:block th:each="config : ${pipelineConfigsWithNames}">
|
<th:block th:each="config : ${pipelineConfigsWithNames}">
|
||||||
<option th:value="${config.json}" th:text="${config.name}"></option>
|
<option th:value="${config.json}" th:text="${config.name}"></option>
|
||||||
</th:block>
|
</th:block>
|
||||||
|
@ -63,15 +63,82 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<h3>Current Limitations</h3>
|
||||||
|
<ul>
|
||||||
|
<li>Cannot have more than one of the same operation</li>
|
||||||
|
<li>Cannot input additional files via UI</li>
|
||||||
|
<li>Does not work with multi-input functions yet (like merges)</li>
|
||||||
|
<li>All files and operations run in serial mode</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3>How it Works Notes</h3>
|
||||||
|
<ul>
|
||||||
|
<li>Configure the pipeline config file and input files to run files against it</li>
|
||||||
|
<li>For reuse, download the config file and re-upload it when needed, or place it in /pipeline/defaultWebUIConfigs/ to auto-load in the web UI for all users</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3>How to use pre-load configs in web UI</h3>
|
||||||
|
<ul>
|
||||||
|
<li>Download config files</li>
|
||||||
|
<li>For reuse, download the config file and re-upload it when needed, or place it in /pipeline/defaultWebUIConfigs/ to auto-load in the web UI for all users</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3>Todo</h3>
|
||||||
|
<ul>
|
||||||
|
<li>fix initial config selected not loading</li>
|
||||||
|
<li>Fix operation adding requering settings to be openned and saved instead of saving defaults</li>
|
||||||
|
<li>multiInput support</li>
|
||||||
|
<li>Translation support</li>
|
||||||
|
<li>offline mode checks and testing</li>
|
||||||
|
<li>Improve operation config settings UI</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
|
<h2>User Guide for Local Directory Scanning and File Processing</h2>
|
||||||
|
|
||||||
|
<h3>Setting Up Watched Folders:</h3>
|
||||||
|
<p>Create a folder where you want your files to be monitored. This is your 'watched folder'.</p>
|
||||||
|
<p>The default directory for this is <code>./pipeline/watchedFolders/</code></p>
|
||||||
|
<p>Place any directories you want to be scanned into this folder, this folder should contain multiple folders each for their own tasks and pipelines.</p>
|
||||||
|
|
||||||
|
<h3>Configuring Processing with JSON Files:</h3>
|
||||||
|
<p>In each directory you want processed (e.g <code>./pipeline/watchedFolders/officePrinter</code>), include a JSON configuration file.</p>
|
||||||
|
<p>This JSON file should specify how you want the files in the directory to be handled (e.g., what operations to perform on them) which can be made, configured and downloaded from Stirling-PDF Pipeline interface.</p>
|
||||||
|
|
||||||
|
<h3>Automatic Scanning and Processing:</h3>
|
||||||
|
<p>The system automatically checks the watched folder every minute for new directories and files to process.</p>
|
||||||
|
<p>When a directory with a valid JSON configuration file is found, it begins processing the files inside as per the configuration.</p>
|
||||||
|
|
||||||
|
<h3>Processing Steps:</h3>
|
||||||
|
<p>Files in each directory are processed according to the instructions in the JSON file.</p>
|
||||||
|
<p>This might involve file conversions, data filtering, renaming files, etc. If the output of a step is a zip, this zip will be automatically unzipped as it passes to next process.</p>
|
||||||
|
|
||||||
|
<h3>Results and Output:</h3>
|
||||||
|
<p>After processing, the results are saved in a specified output location. This could be a different folder or location as defined in the JSON file or the default location <code>./pipeline/finishedFolders/</code>.</p>
|
||||||
|
<p>Each processed file is named and organized according to the rules set in the JSON configuration.</p>
|
||||||
|
|
||||||
|
<h3>Completion and Cleanup:</h3>
|
||||||
|
<p>Once processing is complete, the original files in the watched folder's directory are removed.</p>
|
||||||
|
<p>You can find the processed files in the designated output location.</p>
|
||||||
|
|
||||||
|
<h3>Error Handling:</h3>
|
||||||
|
<p>If there's an error during processing, the system will not delete the original files, allowing you to check and retry if necessary.</p>
|
||||||
|
|
||||||
|
<h3>User Interaction:</h3>
|
||||||
|
<p>As a user, your main tasks are to set up the watched folders, place directories with files for processing, and create the corresponding JSON configuration files.</p>
|
||||||
|
<p>The system handles the rest, including scanning, processing, and outputting results.</p>
|
||||||
|
|
||||||
|
|
||||||
<!-- The Modal -->
|
<!-- The Modal -->
|
||||||
<div class="modal" id="pipelineSettingsModal">
|
<div class="modal" id="pipelineSettingsModal">
|
||||||
<div class="modal-dialog">
|
<div class="modal-dialog">
|
||||||
<div class="modal-content">
|
<div class="modal-content dark-card">
|
||||||
|
|
||||||
<!-- Modal Header -->
|
<!-- Modal Header -->
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h2 class="modal-title">Pipeline Configuration</h2>
|
<h2 class="modal-title">Pipeline Configuration</h2>
|
||||||
<button type="button" class="close" data-bs-dismiss="modal">×</button>
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Modal body -->
|
<!-- Modal body -->
|
||||||
|
@ -90,7 +157,7 @@
|
||||||
<button id="addOperationBtn" class="btn btn-primary">Add
|
<button id="addOperationBtn" class="btn btn-primary">Add
|
||||||
operation</button>
|
operation</button>
|
||||||
</div>
|
</div>
|
||||||
<h3>Pipeline:</h3>
|
<h3 id="pipelineHeader" style="display: none;">Pipeline:</h3>
|
||||||
<ol id="pipelineList" class="list-group">
|
<ol id="pipelineList" class="list-group">
|
||||||
<!-- Pipeline operations will be dynamically populated here -->
|
<!-- Pipeline operations will be dynamically populated here -->
|
||||||
</ol>
|
</ol>
|
||||||
|
|
Loading…
Reference in a new issue