Compare commits

..

29 Commits

Author SHA1 Message Date
Anthony Stirling
d19d87b8f0 Update downloader.js 2024-11-20 10:23:29 +00:00
Anthony Stirling
a22ce69bc3 Merge branch 'main' into Frooodle-patch-7 2024-11-20 10:16:44 +00:00
Anthony Stirling
59b60ebfb8 Update common.html 2024-11-20 10:15:23 +00:00
Anthony Stirling
a6ae3734ca Frooodle patch 8 (#2275)
* Update check_properties.yml

* Update messages_en_GB.properties

* Update messages_en_GB.properties
2024-11-20 09:44:51 +00:00
Anthony Stirling
b9a014b5c7 Update downloader.js 2024-11-20 09:23:41 +00:00
Anthony Stirling
9e30918aae Update downloader.js 2024-11-20 09:04:00 +00:00
Anthony Stirling
c239d95131 Update PR-Demo-cleanup.yml 2024-11-20 08:41:29 +00:00
Anthony Stirling
d591874da6 Update PR-Demo-cleanup.yml 2024-11-20 08:28:51 +00:00
Anthony Stirling
6c623d8d84 Update MetricsAggregatorService.java (#2272) 2024-11-20 08:20:01 +00:00
Rafael Encinas
e059caa14e Fix id typo for "cropPdfCanvas" querySelector (#2271)
Fix id typo
2024-11-20 07:53:14 +00:00
dependabot[bot]
8eab35761d Bump org.projectlombok:lombok from 1.18.34 to 1.18.36 (#2266)
Bumps [org.projectlombok:lombok](https://github.com/projectlombok/lombok) from 1.18.34 to 1.18.36.
- [Changelog](https://github.com/projectlombok/lombok/blob/master/doc/changelog.markdown)
- [Commits](https://github.com/projectlombok/lombok/compare/v1.18.34...v1.18.36)

---
updated-dependencies:
- dependency-name: org.projectlombok:lombok
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-18 23:16:31 +00:00
Anthony Stirling
c43af24ffe Update PR-Demo-Comment.yml 2024-11-17 16:17:44 +00:00
Anthony Stirling
e1b3cc736c Update PR-Demo-Comment.yml 2024-11-17 15:54:50 +00:00
Anthony Stirling
0fb9e18636 Update PR-Demo-Comment.yml 2024-11-17 15:53:29 +00:00
Ludy
5e1aac0b84 Read login data from application.properties (#2263)
Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
2024-11-17 14:08:41 +00:00
Anthony Stirling
60bf649260 Update and rename PR-Demos.yml to PR-Demo-cleanup.yml 2024-11-17 13:43:48 +00:00
Anthony Stirling
a58696a38e Create PR-Demo-Comment.yml (#2261)
* Create PR-Demo-Comment.yml

* Update PR-Demo-Comment.yml
2024-11-17 13:32:14 +00:00
Ludy
44abc67678 shows the titles of the buttons (#2262)
* shows the titles of the buttons

* Update navbar.css
2024-11-17 12:33:41 +00:00
Anthony Stirling
d1e690ff8d Update PR-Demos.yml 2024-11-17 10:13:59 +00:00
Anthony Stirling
5dc8fa08ee Create PR-Demos.yml (#2260)
* Create PR-Demos.yml

* Update build.gradle

* Update build.gradle

* Update PR-Demos.yml

* Update PR-Demos.yml

* Update PR-Demos.yml

* Update PR-Demos.yml

* Update PR-Demos.yml

* Update PR-Demos.yml

* Update PR-Demos.yml

* Update PR-Demos.yml

* Update PR-Demos.yml
2024-11-16 22:24:00 +00:00
Anthony Stirling
db028dfe27 docker move 2024-11-16 11:31:26 +00:00
Anthony Stirling
c24c504350 Delete Jenkinsfile 2024-11-16 11:22:17 +00:00
Anthony Stirling
5dcfe64d1c Update README.md 2024-11-16 11:11:12 +00:00
Anthony Stirling
d843696703 Update README.md 2024-11-16 11:07:54 +00:00
Dimitris Kaitantzidis
67de8a9460 Fix canvas pdf to csv (#2228)
* WIP: fixes canvas and rect to crop - small problem in smaller screens - neew to fix re render page on resize

* Closes #2209

* Closes #2227
2024-11-16 11:02:20 +00:00
Anthony Stirling
b26aa3417e Update build.gradle 2024-11-16 11:01:44 +00:00
Renan
8dfb5940ca Fixing bug: Add Image makes random changes to image (#2246) (#2256)
Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
2024-11-16 08:55:40 +00:00
Anthony Stirling
0ce479e1e3 Update push-docker.yml 2024-11-16 08:43:42 +00:00
Anthony Stirling
cca3b6b525 Update multi-tool.html 2024-11-16 08:40:34 +00:00
24 changed files with 543 additions and 179 deletions

179
.github/workflows/PR-Demo-Comment.yml vendored Normal file
View File

@@ -0,0 +1,179 @@
name: PR Deployment via Comment
on:
issue_comment:
types: [created]
jobs:
check-comment:
runs-on: ubuntu-latest
if: |
github.event.issue.pull_request &&
(
contains(github.event.comment.body, 'prdeploy') ||
contains(github.event.comment.body, 'deploypr')
)
&&
(
github.event.comment.user.login == 'frooodle' ||
github.event.comment.user.login == 'sf298' ||
github.event.comment.user.login == 'Ludy87' ||
github.event.comment.user.login == 'LaserKaspar' ||
github.event.comment.user.login == 'sbplat' ||
github.event.comment.user.login == 'reecebrowne'
)
outputs:
pr_number: ${{ steps.get-pr.outputs.pr_number }}
pr_repository: ${{ steps.get-pr-info.outputs.repository }}
pr_ref: ${{ steps.get-pr-info.outputs.ref }}
steps:
- name: Get PR data
id: get-pr
uses: actions/github-script@v7
with:
script: |
const prNumber = context.payload.issue.number;
console.log(`PR Number: ${prNumber}`);
core.setOutput('pr_number', prNumber);
- name: Get PR repository and ref
id: get-pr-info
uses: actions/github-script@v7
with:
script: |
const { owner, repo } = context.repo;
const prNumber = context.payload.issue.number;
const { data: pr } = await github.rest.pulls.get({
owner,
repo,
pull_number: prNumber,
});
// For forks, use the full repository name, for internal PRs use the current repo
const repository = pr.head.repo.fork ? pr.head.repo.full_name : `${owner}/${repo}`;
console.log(`PR Repository: ${repository}`);
console.log(`PR Branch: ${pr.head.ref}`);
core.setOutput('repository', repository);
core.setOutput('ref', pr.head.ref);
deploy-pr:
needs: check-comment
runs-on: ubuntu-latest
steps:
- name: Checkout PR
uses: actions/checkout@v4
with:
repository: ${{ needs.check-comment.outputs.pr_repository }}
ref: ${{ needs.check-comment.outputs.pr_ref }}
token: ${{ secrets.GITHUB_TOKEN }}
- name: Set up JDK
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
- name: Run Gradle Command
run: ./gradlew clean build
env:
DOCKER_ENABLE_SECURITY: false
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Get version number
id: versionNumber
run: echo "versionNumber=$(./gradlew printVersion --quiet | tail -1)" >> $GITHUB_OUTPUT
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_API }}
- name: Build and push PR-specific image
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile
push: true
tags: ${{ secrets.DOCKER_HUB_USERNAME }}/test:pr-${{ needs.check-comment.outputs.pr_number }}
build-args: VERSION_TAG=${{ steps.versionNumber.outputs.versionNumber }}
platforms: linux/amd64
- name: Set up SSH
run: |
mkdir -p ~/.ssh/
echo "${{ secrets.VPS_SSH_KEY }}" > ../private.key
sudo chmod 600 ../private.key
- name: Deploy to VPS
run: |
# First create the docker-compose content locally
cat > docker-compose.yml << 'EOF'
version: '3.3'
services:
stirling-pdf:
container_name: stirling-pdf-pr-${{ needs.check-comment.outputs.pr_number }}
image: ${{ secrets.DOCKER_HUB_USERNAME }}/test:pr-${{ needs.check-comment.outputs.pr_number }}
ports:
- "${{ needs.check-comment.outputs.pr_number }}:8080"
volumes:
- /stirling/PR-${{ needs.check-comment.outputs.pr_number }}/data:/usr/share/tessdata:rw
- /stirling/PR-${{ needs.check-comment.outputs.pr_number }}/config:/configs:rw
- /stirling/PR-${{ needs.check-comment.outputs.pr_number }}/logs:/logs:rw
environment:
DOCKER_ENABLE_SECURITY: "false"
SECURITY_ENABLELOGIN: "false"
SYSTEM_DEFAULTLOCALE: en-GB
UI_APPNAME: "Stirling-PDF PR#${{ needs.check-comment.outputs.pr_number }}"
UI_HOMEDESCRIPTION: "PR#${{ needs.check-comment.outputs.pr_number }} for Stirling-PDF Latest"
UI_APPNAMENAVBAR: "PR#${{ needs.check-comment.outputs.pr_number }}"
SYSTEM_MAXFILESIZE: "100"
METRICS_ENABLED: "true"
SYSTEM_GOOGLEVISIBILITY: "false"
restart: on-failure:5
EOF
# Then copy the file and execute commands
scp -i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null docker-compose.yml ${{ secrets.VPS_USERNAME }}@${{ secrets.VPS_HOST }}:/tmp/docker-compose.yml
ssh -i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -T ${{ secrets.VPS_USERNAME }}@${{ secrets.VPS_HOST }} << 'ENDSSH'
# Create PR-specific directories
mkdir -p /stirling/PR-${{ needs.check-comment.outputs.pr_number }}/{data,config,logs}
# Move docker-compose file to correct location
mv /tmp/docker-compose.yml /stirling/PR-${{ needs.check-comment.outputs.pr_number }}/docker-compose.yml
# Start or restart the container
cd /stirling/PR-${{ needs.check-comment.outputs.pr_number }}
docker-compose pull
docker-compose up -d
ENDSSH
- name: Post deployment URL to PR
if: success()
uses: actions/github-script@v7
with:
script: |
const { GITHUB_REPOSITORY } = process.env;
const [repoOwner, repoName] = GITHUB_REPOSITORY.split('/');
const prNumber = ${{ needs.check-comment.outputs.pr_number }};
const deploymentUrl = `http://${{ secrets.VPS_HOST }}:${prNumber}`;
const commentBody = `## 🚀 PR Test Deployment\n\n` +
`Your PR has been deployed for testing!\n\n` +
`🔗 **Test URL:** [${deploymentUrl}](${deploymentUrl})\n\n` +
`This deployment will be automatically cleaned up when the PR is closed.\n\n`;
await github.rest.issues.createComment({
owner: repoOwner,
repo: repoName,
issue_number: prNumber,
body: commentBody
});

78
.github/workflows/PR-Demo-cleanup.yml vendored Normal file
View File

@@ -0,0 +1,78 @@
name: PR Deployment cleanup
on:
pull_request:
types: [opened, synchronize, reopened, closed]
permissions:
contents: write
pull-requests: write
env:
SERVER_IP: ${{ secrets.VPS_IP }} # Add this to your GitHub secrets
CLEANUP_PERFORMED: 'false' # Add flag to track if cleanup occurred
jobs:
cleanup:
runs-on: ubuntu-latest
if: github.event.action == 'closed'
steps:
- name: Set up SSH
run: |
mkdir -p ~/.ssh/
echo "${{ secrets.VPS_SSH_KEY }}" > ../private.key
sudo chmod 600 ../private.key
- name: Cleanup PR deployment
id: cleanup
run: |
CLEANUP_STATUS=$(ssh -i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -T ${{ secrets.VPS_USERNAME }}@${{ secrets.VPS_HOST }} << 'ENDSSH'
if [ -d "/stirling/PR-${{ github.event.pull_request.number }}" ]; then
echo "Found PR directory, proceeding with cleanup..."
# Stop and remove containers
cd /stirling/PR-${{ github.event.pull_request.number }}
docker-compose down || true
# Go back to root before removal
cd /
# Remove PR-specific directories
rm -rf /stirling/PR-${{ github.event.pull_request.number }}
# Remove the Docker image
docker rmi --no-prune ${{ secrets.DOCKER_HUB_USERNAME }}/test:pr-${{ github.event.pull_request.number }} || true
echo "PERFORMED_CLEANUP"
else
echo "PR directory not found, nothing to clean up"
echo "NO_CLEANUP_NEEDED"
fi
ENDSSH
)
if [[ $CLEANUP_STATUS == *"PERFORMED_CLEANUP"* ]]; then
echo "cleanup_performed=true" >> $GITHUB_OUTPUT
else
echo "cleanup_performed=false" >> $GITHUB_OUTPUT
fi
- name: Post cleanup notice to PR
if: steps.cleanup.outputs.cleanup_performed == 'true'
uses: actions/github-script@v7
with:
script: |
const { GITHUB_REPOSITORY } = process.env;
const [repoOwner, repoName] = GITHUB_REPOSITORY.split('/');
const prNumber = context.issue.number;
const commentBody = `## 🧹 Deployment Cleanup\n\n` +
`The test deployment for this PR has been cleaned up.`;
await github.rest.issues.createComment({
owner: repoOwner,
repo: repoName,
issue_number: prNumber,
body: commentBody
});

View File

@@ -9,6 +9,7 @@ on:
paths:
- "src/main/resources/messages_en_GB.properties"
# Permissions required for the workflow
permissions:
contents: write
pull-requests: write
@@ -18,6 +19,13 @@ jobs:
if: github.event_name == 'pull_request_target'
runs-on: ubuntu-latest
steps:
- name: Checkout main branch first
uses: actions/checkout@v4
with:
ref: main
path: main-branch
fetch-depth: 0
- name: Checkout PR branch
uses: actions/checkout@v4
with:
@@ -26,13 +34,6 @@ jobs:
path: pr-branch
fetch-depth: 0
- name: Checkout main branch
uses: actions/checkout@v4
with:
ref: main
path: main-branch
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v5
with:
@@ -49,34 +50,46 @@ jobs:
echo "Fetching PR changed files..."
cd pr-branch
gh repo set-default ${{ github.repository }}
gh pr view ${{ github.event.pull_request.number }} --json files -q ".files[].path" > ../changed_files.txt
# Store files in a safe way, only allowing valid properties files
echo "Getting list of changed files from PR..."
gh pr view ${{ github.event.pull_request.number }} --json files -q ".files[].path" | grep -E '^src/main/resources/messages_[a-zA-Z_]+\.properties$' > ../changed_files.txt
cd ..
echo $(cat changed_files.txt)
echo "Setting branch path..."
BRANCH_PATH="pr-branch"
echo "BRANCH_PATH=${BRANCH_PATH}" >> $GITHUB_ENV
CHANGED_FILES=$(cat changed_files.txt | tr '\n' ' ')
echo "CHANGED_FILES=${CHANGED_FILES}" >> $GITHUB_ENV
echo "Changed files: ${CHANGED_FILES}"
echo "Processing changed files..."
mapfile -t CHANGED_FILES < changed_files.txt
CHANGED_FILES_STR="${CHANGED_FILES[*]}"
echo "CHANGED_FILES=${CHANGED_FILES_STR}" >> $GITHUB_ENV
echo "Changed files: ${CHANGED_FILES_STR}"
echo "Branch: ${BRANCH_PATH}"
- name: Determine reference file
id: determine-file
run: |
echo "Determining reference file..."
if echo "${{ env.CHANGED_FILES }}" | grep -q 'src/main/resources/messages_en_GB.properties'; then
if grep -Fxq "src/main/resources/messages_en_GB.properties" changed_files.txt; then
echo "Using PR branch reference file"
echo "REFERENCE_FILE=pr-branch/src/main/resources/messages_en_GB.properties" >> $GITHUB_ENV
else
echo "Using main branch reference file"
echo "REFERENCE_FILE=main-branch/src/main/resources/messages_en_GB.properties" >> $GITHUB_ENV
fi
echo "REFERENCE_FILE=${{ env.REFERENCE_FILE }}"
- name: Show REFERENCE_FILE
run: echo "Reference file is set to ${{ env.REFERENCE_FILE }}"
run: echo "Reference file is set to ${REFERENCE_FILE}"
- name: Run Python script to check files
id: run-check
run: |
python main-branch/.github/scripts/check_language_properties.py --reference-file ${{ env.REFERENCE_FILE }} --branch ${{ env.BRANCH_PATH }} --files ${{ env.CHANGED_FILES }} > failure.txt || true
echo "Running Python script to check files..."
python main-branch/.github/scripts/check_language_properties.py \
--reference-file "${REFERENCE_FILE}" \
--branch "${BRANCH_PATH}" \
--files ${CHANGED_FILES} > failure.txt || true
- name: Capture output
id: capture-output
@@ -87,7 +100,7 @@ jobs:
echo "ERROR_OUTPUT<<EOF" >> $GITHUB_ENV
echo "$ERROR_OUTPUT" >> $GITHUB_ENV
echo "EOF" >> $GITHUB_ENV
echo $ERROR_OUTPUT
echo "${ERROR_OUTPUT}"
else
echo "No errors found."
echo "ERROR_OUTPUT=" >> $GITHUB_ENV
@@ -110,7 +123,7 @@ jobs:
});
const comment = comments.data.find(c => c.body.includes("## 🚀 Translation Verification Summary"));
// Only allow the action user to update comments
const expectedActor = "github-actions[bot]";
@@ -169,7 +182,10 @@ jobs:
- name: Run Python script to check files
id: run-check
run: |
python .github/scripts/check_language_properties.py --reference-file src/main/resources/messages_en_GB.properties --branch main
echo "Running Python script to check files..."
python .github/scripts/check_language_properties.py \
--reference-file src/main/resources/messages_en_GB.properties \
--branch main
- name: Set up git config
run: |

View File

@@ -67,6 +67,7 @@ jobs:
images: |
${{ secrets.DOCKER_HUB_USERNAME }}/s-pdf
ghcr.io/${{ steps.repoowner.outputs.lowercase }}/s-pdf
ghcr.io/${{ steps.repoowner.outputs.lowercase }}/stirling-pdf
${{ secrets.DOCKER_HUB_ORG_USERNAME }}/stirling-pdf
tags: |
type=raw,value=${{ steps.versionNumber.outputs.versionNumber }},enable=${{ github.ref == 'refs/heads/master' }}
@@ -95,6 +96,7 @@ jobs:
images: |
${{ secrets.DOCKER_HUB_USERNAME }}/s-pdf
ghcr.io/${{ steps.repoowner.outputs.lowercase }}/s-pdf
ghcr.io/${{ steps.repoowner.outputs.lowercase }}/stirling-pdf
${{ secrets.DOCKER_HUB_ORG_USERNAME }}/stirling-pdf
tags: |
type=raw,value=${{ steps.versionNumber.outputs.versionNumber }}-ultra-lite,enable=${{ github.ref == 'refs/heads/master' }}
@@ -122,6 +124,7 @@ jobs:
images: |
${{ secrets.DOCKER_HUB_USERNAME }}/s-pdf
ghcr.io/${{ steps.repoowner.outputs.lowercase }}/s-pdf
ghcr.io/${{ steps.repoowner.outputs.lowercase }}/stirling-pdf
${{ secrets.DOCKER_HUB_ORG_USERNAME }}/stirling-pdf
tags: |
type=raw,value=${{ steps.versionNumber.outputs.versionNumber }}-fat,enable=${{ github.ref == 'refs/heads/master' }}

View File

@@ -114,7 +114,7 @@ These files provide pre-configured setups for different scenarios. For example,
services:
stirling-pdf:
container_name: Stirling-PDF-Security
image: frooodle/s-pdf:latest
image: stirlingtools/stirling-pdf:latest
deploy:
resources:
limits:
@@ -173,20 +173,20 @@ Stirling-PDF uses different Docker images for various configurations. The build
For the latest version:
```bash
docker build --no-cache --pull --build-arg VERSION_TAG=alpha -t frooodle/s-pdf:latest -f ./Dockerfile .
docker build --no-cache --pull --build-arg VERSION_TAG=alpha -t stirlingtools/stirling-pdf:latest -f ./Dockerfile .
```
For the ultra-lite version:
```bash
docker build --no-cache --pull --build-arg VERSION_TAG=alpha -t frooodle/s-pdf:latest-ultra-lite -f ./Dockerfile-ultra-lite .
docker build --no-cache --pull --build-arg VERSION_TAG=alpha -t stirlingtools/stirling-pdf:latest-ultra-lite -f ./Dockerfile-ultra-lite .
```
For the fat version (with security enabled):
```bash
export DOCKER_ENABLE_SECURITY=true
docker build --no-cache --pull --build-arg VERSION_TAG=alpha -t frooodle/s-pdf:latest-fat -f ./Dockerfile-fat .
docker build --no-cache --pull --build-arg VERSION_TAG=alpha -t stirlingtools/stirling-pdf:latest-fat -f ./Dockerfile-fat .
```
Note: The `--no-cache` and `--pull` flags ensure that the build process uses the latest base images and doesn't use cached layers, which is useful for testing and ensuring reproducible builds. however to improve build times these can often be removed depending on your usecase

45
Jenkinsfile vendored
View File

@@ -1,45 +0,0 @@
pipeline {
agent any
stages {
stage('Build') {
steps {
sh 'chmod 755 gradlew'
sh './gradlew build'
}
}
stage('Docker Build') {
steps {
script {
def appVersion = sh(returnStdout: true, script: './gradlew printVersion -q').trim()
def image = "frooodle/s-pdf:$appVersion"
sh "docker build -t $image ."
}
}
}
stage('Docker Push') {
steps {
script {
def appVersion = sh(returnStdout: true, script: './gradlew printVersion -q').trim()
def image = "frooodle/s-pdf:$appVersion"
withCredentials([string(credentialsId: 'docker_hub_access_token', variable: 'DOCKER_HUB_ACCESS_TOKEN')]) {
sh "docker login --username frooodle --password $DOCKER_HUB_ACCESS_TOKEN"
sh "docker push $image"
}
}
}
}
stage('Helm Push') {
steps {
script {
//TODO: Read chartVersion from Chart.yaml
def chartVersion = '1.0.0'
withCredentials([string(credentialsId: 'docker_hub_access_token', variable: 'DOCKER_HUB_ACCESS_TOKEN')]) {
sh "docker login --username frooodle --password $DOCKER_HUB_ACCESS_TOKEN"
sh "helm package chart/stirling-pdf"
sh "helm push stirling-pdf-chart-1.0.0.tgz oci://registry-1.docker.io/frooodle"
}
}
}
}
}
}

View File

@@ -120,13 +120,13 @@ Please view the [LocalRunGuide](https://github.com/Stirling-Tools/Stirling-PDF/b
### Docker / Podman
> [!NOTE]
> <https://hub.docker.com/r/frooodle/s-pdf>
> <https://hub.docker.com/r/stirlingtools/stirling-pdf>
Stirling-PDF has three different versions: a full version, an ultra-lite version, and a 'fat' version. Depending on the types of features you use, you may want a smaller image to save on space. To see what the different versions offer, please look at our [version mapping](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/Version-groups.md). For people that don't mind space optimization, just use the latest tag.
![Docker Image Size (tag)](https://img.shields.io/docker/image-size/frooodle/s-pdf/latest?label=Stirling-PDF%20Full)
![Docker Image Size (tag)](https://img.shields.io/docker/image-size/frooodle/s-pdf/latest-ultra-lite?label=Stirling-PDF%20Ultra-Lite)
![Docker Image Size (tag)](https://img.shields.io/docker/image-size/frooodle/s-pdf/latest-fat?label=Stirling-PDF%20Fat)
![Docker Image Size (tag)](https://img.shields.io/docker/image-size/stirlingtools/stirling-pdf/latest?label=Stirling-PDF%20Full)
![Docker Image Size (tag)](https://img.shields.io/docker/image-size/stirlingtools/stirling-pdf/latest-ultra-lite?label=Stirling-PDF%20Ultra-Lite)
![Docker Image Size (tag)](https://img.shields.io/docker/image-size/stirlingtools/stirling-pdf/latest-fat?label=Stirling-PDF%20Fat)
Please note in the examples below, you may need to change the volume paths as needed, e.g., `./extraConfigs:/configs` to `/opt/stirlingpdf/extraConfigs:/configs`.
@@ -144,7 +144,7 @@ docker run -d \
-e INSTALL_BOOK_AND_ADVANCED_HTML_OPS=false \
-e LANGS=en_GB \
--name stirling-pdf \
frooodle/s-pdf:latest
stirlingtools/stirling-pdf:latest
```
### Docker Compose
@@ -153,7 +153,7 @@ docker run -d \
version: '3.3'
services:
stirling-pdf:
image: frooodle/s-pdf:latest
image: stirlingtools/stirling-pdf:latest
ports:
- '8080:8080'
volumes:

View File

@@ -10,6 +10,8 @@ plugins {
//id "nebula.lint" version "19.0.3"
}
import com.github.jk1.license.render.*
ext {
@@ -17,12 +19,12 @@ ext {
pdfboxVersion = "3.0.3"
logbackVersion = "1.5.7"
imageioVersion = "3.12.0"
lombokVersion = "1.18.34"
lombokVersion = "1.18.36"
bouncycastleVersion = "1.78.1"
}
group = "stirling.software"
version = "0.33.0"
version = "0.33.1"
java {
// 17 is lowest but we support and recommend 21

View File

@@ -1,7 +1,7 @@
services:
stirling-pdf:
container_name: Stirling-PDF-Security-Fat
image: frooodle/s-pdf:latest-fat
image: stirlingtools/stirling-pdf:latest-fat
deploy:
resources:
limits:

View File

@@ -1,7 +1,7 @@
services:
stirling-pdf:
container_name: Stirling-PDF-Security
image: frooodle/s-pdf:latest
image: stirlingtools/stirling-pdf:latest
deploy:
resources:
limits:

View File

@@ -1,7 +1,7 @@
services:
stirling-pdf:
container_name: Stirling-PDF-Security
image: frooodle/s-pdf:latest
image: stirlingtools/stirling-pdf:latest
deploy:
resources:
limits:

View File

@@ -1,7 +1,7 @@
services:
stirling-pdf:
container_name: Stirling-PDF-Ultra-Lite-Security
image: frooodle/s-pdf:latest-ultra-lite
image: stirlingtools/stirling-pdf:latest-ultra-lite
deploy:
resources:
limits:

View File

@@ -1,7 +1,7 @@
services:
stirling-pdf:
container_name: Stirling-PDF-Ultra-Lite
image: frooodle/s-pdf:latest-ultra-lite
image: stirlingtools/stirling-pdf:latest-ultra-lite
deploy:
resources:
limits:

View File

@@ -1,7 +1,7 @@
services:
stirling-pdf:
container_name: Stirling-PDF
image: frooodle/s-pdf:latest
image: stirlingtools/stirling-pdf:latest
deploy:
resources:
limits:

View File

@@ -34,6 +34,12 @@ public class DatabaseBackupHelper implements DatabaseBackupInterface {
@Value("${spring.datasource.url}")
private String url;
@Value("${spring.datasource.username}")
private String databaseUsername;
@Value("${spring.datasource.password}")
private String databasePassword;
private Path backupPath = Paths.get("configs/db/backup/");
@Override
@@ -134,7 +140,8 @@ public class DatabaseBackupHelper implements DatabaseBackupInterface {
this.getBackupFilePath("backup_" + dateNow.format(myFormatObj) + ".sql");
String query = "SCRIPT SIMPLE COLUMNS DROP to ?;";
try (Connection conn = DriverManager.getConnection(url, "sa", "");
try (Connection conn =
DriverManager.getConnection(url, databaseUsername, databasePassword);
PreparedStatement stmt = conn.prepareStatement(query)) {
stmt.setString(1, insertOutputFilePath.toString());
stmt.execute();
@@ -147,7 +154,8 @@ public class DatabaseBackupHelper implements DatabaseBackupInterface {
// Retrieves the H2 database version.
public String getH2Version() {
String version = "Unknown";
try (Connection conn = DriverManager.getConnection(url, "sa", "")) {
try (Connection conn =
DriverManager.getConnection(url, databaseUsername, databasePassword)) {
try (Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT H2VERSION() AS version")) {
if (rs.next()) {
@@ -189,7 +197,8 @@ public class DatabaseBackupHelper implements DatabaseBackupInterface {
private boolean executeDatabaseScript(Path scriptPath) {
String query = "RUNSCRIPT from ?;";
try (Connection conn = DriverManager.getConnection(url, "sa", "");
try (Connection conn =
DriverManager.getConnection(url, databaseUsername, databasePassword);
PreparedStatement stmt = conn.prepareStatement(query)) {
stmt.setString(1, scriptPath.toString());
stmt.execute();

View File

@@ -32,11 +32,19 @@ public class MetricsAggregatorService {
.counters()
.forEach(
counter -> {
String key =
String.format(
"http_requests_%s_%s",
counter.getId().getTag("method"),
counter.getId().getTag("uri").replace("/", "_"));
String method = counter.getId().getTag("method");
String uri = counter.getId().getTag("uri");
// Skip if either method or uri is null
if (method == null || uri == null) {
return;
}
String key = String.format(
"http_requests_%s_%s",
method,
uri.replace("/", "_")
);
double currentCount = counter.count();
double lastCount = lastSentMetrics.getOrDefault(key, 0.0);

View File

@@ -140,7 +140,7 @@ span.icon-text::after {
position: relative;
}
.tooltip-text {
/* .tooltip-text {
visibility: hidden;
background-color: rgb(71 67 67 / 80%);
color: #fff;
@@ -162,7 +162,7 @@ span.icon-text::after {
.nav-item:hover .tooltip-text {
visibility: visible;
opacity: 1;
}
} */
.dropdown-menu.scrollable-y {
overflow-y: scroll;
@@ -324,6 +324,18 @@ span.icon-text::after {
margin-top: 0;
}
/* .icon-hide {
display: none;
} */
}
@media (max-width:1199px) {
.icon-hide {
display: inline-flex;
}
}
@media (min-width:1200px) {
.icon-hide {
display: none;
}

View File

@@ -96,14 +96,31 @@
});
});
async function getPDFPageCount(file) {
try {
const arrayBuffer = await file.arrayBuffer();
pdfjsLib.GlobalWorkerOptions.workerSrc = '/pdfjs-legacy/pdf.worker.mjs'
const pdf = await pdfjsLib.getDocument({ data: arrayBuffer }).promise;
return pdf.numPages;
} catch (error) {
console.error('Error getting PDF page count:', error);
return null;
}
}
async function handleSingleDownload(url, formData, isMulti = false, isZip = false) {
const startTime = performance.now();
const file = formData.get('fileInput');
let success = false;
let errorMessage = null;
try {
const response = await fetch(url, { method: "POST", body: formData });
const contentType = response.headers.get("content-type");
if (!response.ok) {
errorMessage = response.status;
if (response.status === 401) {
// Handle 401 Unauthorized error
showSessionExpiredPrompt();
return;
}
@@ -118,6 +135,8 @@
let filename = getFilenameFromContentDisposition(contentDisposition);
const blob = await response.blob();
success = true;
if (contentType.includes("application/pdf") || contentType.includes("image/")) {
clearFileInput();
return handleResponse(blob, filename, !isMulti, isZip);
@@ -127,13 +146,29 @@
}
} catch (error) {
success = false;
errorMessage = error.message;
clearFileInput();
console.error("Error in handleSingleDownload:", error);
throw error;
} finally {
const processingTime = performance.now() - startTime;
// Capture analytics
const pageCount = file && file.type === 'application/pdf' ? await getPDFPageCount(file) : null;
posthog.capture('file_processing', {
success: success,
file_type: file ? file.type || 'unknown' : 'unknown',
file_size: file ? file.size : 0,
processing_time: processingTime,
error_message: errorMessage,
pdf_pages: pageCount
});
}
}
function getFilenameFromContentDisposition(contentDisposition) {
function getFilenameFromContentDisposition(contentDisposition) {
let filename;
if (contentDisposition && contentDisposition.indexOf("attachment") !== -1) {
@@ -145,7 +180,7 @@
return filename;
}
async function handleJsonResponse(response) {
const json = await response.json();
const errorMessage = JSON.stringify(json, null, 2);
@@ -308,14 +343,14 @@
if(editSectionElement){
editSectionElement.style.display = "none";
}
let cropPdfCanvas = document.querySelector("#crop-pdf-canvas");
let cropPdfCanvas = document.querySelector("#cropPdfCanvas");
let overlayCanvas = document.querySelector("#overlayCanvas");
if(cropPdfCanvas && overlayCanvas){
cropPdfCanvas.width = 0;
cropPdfCanvas.heigth = 0;
cropPdfCanvas.height = 0;
overlayCanvas.width = 0;
overlayCanvas.heigth = 0;
overlayCanvas.height = 0;
}
} else{
console.log("Disabled for 'Merge'");

View File

@@ -9,81 +9,81 @@ const DraggableUtils = {
init() {
interact(".draggable-canvas")
.draggable({
listeners: {
move: (event) => {
const target = event.target;
const x = (parseFloat(target.getAttribute("data-bs-x")) || 0)
.draggable({
listeners: {
move: (event) => {
const target = event.target;
const x = (parseFloat(target.getAttribute("data-bs-x")) || 0)
+ event.dx;
const y = (parseFloat(target.getAttribute("data-bs-y")) || 0)
const y = (parseFloat(target.getAttribute("data-bs-y")) || 0)
+ event.dy;
target.style.transform = `translate(${x}px, ${y}px)`;
target.setAttribute("data-bs-x", x);
target.setAttribute("data-bs-y", y);
target.style.transform = `translate(${x}px, ${y}px)`;
target.setAttribute("data-bs-x", x);
target.setAttribute("data-bs-y", y);
this.onInteraction(target);
//update the last interacted element
this.lastInteracted = event.target;
this.onInteraction(target);
//update the last interacted element
this.lastInteracted = event.target;
},
},
},
})
.resizable({
edges: { left: true, right: true, bottom: true, top: true },
listeners: {
move: (event) => {
var target = event.target;
var x = parseFloat(target.getAttribute("data-bs-x")) || 0;
var y = parseFloat(target.getAttribute("data-bs-y")) || 0;
})
.resizable({
edges: { left: true, right: true, bottom: true, top: true },
listeners: {
move: (event) => {
var target = event.target;
var x = parseFloat(target.getAttribute("data-bs-x")) || 0;
var y = parseFloat(target.getAttribute("data-bs-y")) || 0;
// check if control key is pressed
if (event.ctrlKey) {
const aspectRatio = target.offsetWidth / target.offsetHeight;
// preserve aspect ratio
let width = event.rect.width;
let height = event.rect.height;
// check if control key is pressed
if (event.ctrlKey) {
const aspectRatio = target.offsetWidth / target.offsetHeight;
// preserve aspect ratio
let width = event.rect.width;
let height = event.rect.height;
if (Math.abs(event.deltaRect.width) >= Math.abs(
if (Math.abs(event.deltaRect.width) >= Math.abs(
event.deltaRect.height)) {
height = width / aspectRatio;
} else {
width = height * aspectRatio;
height = width / aspectRatio;
} else {
width = height * aspectRatio;
}
event.rect.width = width;
event.rect.height = height;
}
event.rect.width = width;
event.rect.height = height;
}
target.style.width = event.rect.width + "px";
target.style.height = event.rect.height + "px";
target.style.width = event.rect.width + "px";
target.style.height = event.rect.height + "px";
// translate when resizing from top or left edges
x += event.deltaRect.left;
y += event.deltaRect.top;
// translate when resizing from top or left edges
x += event.deltaRect.left;
y += event.deltaRect.top;
target.style.transform = "translate(" + x + "px," + y + "px)";
target.style.transform = "translate(" + x + "px," + y + "px)";
target.setAttribute("data-bs-x", x);
target.setAttribute("data-bs-y", y);
target.textContent = Math.round(event.rect.width) + "\u00D7"
target.setAttribute("data-bs-x", x);
target.setAttribute("data-bs-y", y);
target.textContent = Math.round(event.rect.width) + "\u00D7"
+ Math.round(event.rect.height);
this.onInteraction(target);
this.onInteraction(target);
},
},
},
modifiers: [
interact.modifiers.restrictSize({
min: {width: 5, height: 5},
}),
],
inertia: true,
});
modifiers: [
interact.modifiers.restrictSize({
min: { width: 5, height: 5 },
}),
],
inertia: true,
});
//Arrow key Support for Add-Image and Sign pages
if(window.location.pathname.endsWith('sign') || window.location.pathname.endsWith('add-image')) {
if (window.location.pathname.endsWith('sign') || window.location.pathname.endsWith('add-image')) {
window.addEventListener('keydown', (event) => {
//Check for last interacted element
if (!this.lastInteracted){
if (!this.lastInteracted) {
return;
}
// Get the currently selected element
@@ -288,7 +288,7 @@ const DraggableUtils = {
}
},
parseTransform(element) {},
parseTransform(element) { },
async getOverlayedPdfDocument() {
const pdfBytes = await this.pdfDoc.getData();
const pdfDocModified = await PDFLib.PDFDocument.load(pdfBytes, {
@@ -308,6 +308,7 @@ const DraggableUtils = {
const offsetWidth = pagesMap[pageIdx + "-offsetWidth"];
const offsetHeight = pagesMap[pageIdx + "-offsetHeight"];
for (const draggableData of draggablesData) {
// embed the draggable canvas
const draggableElement = draggableData.element;
@@ -324,6 +325,24 @@ const DraggableUtils = {
width: draggableData.offsetWidth,
height: draggableData.offsetHeight,
};
//Auxiliary variables
let widthAdjusted = page.getWidth();
let heightAdjusted = page.getHeight();
const rotation = page.getRotation();
//Normalizing angle
let normalizedAngle = rotation.angle % 360;
if (normalizedAngle < 0) {
normalizedAngle += 360;
}
//Changing the page dimension if the angle is 90 or 270
if (normalizedAngle === 90 || normalizedAngle === 270) {
let widthTemp = widthAdjusted;
widthAdjusted = heightAdjusted;
heightAdjusted = widthTemp;
}
const draggablePositionRelative = {
x: draggablePositionPixels.x / offsetWidth,
y: draggablePositionPixels.y / offsetHeight,
@@ -331,18 +350,36 @@ const DraggableUtils = {
height: draggablePositionPixels.height / offsetHeight,
};
const draggablePositionPdf = {
x: draggablePositionRelative.x * page.getWidth(),
y: draggablePositionRelative.y * page.getHeight(),
width: draggablePositionRelative.width * page.getWidth(),
height: draggablePositionRelative.height * page.getHeight(),
x: draggablePositionRelative.x * widthAdjusted,
y: draggablePositionRelative.y * heightAdjusted,
width: draggablePositionRelative.width * widthAdjusted,
height: draggablePositionRelative.height * heightAdjusted,
};
//Defining the image if the page has a 0-degree angle
let x = draggablePositionPdf.x
let y = heightAdjusted - draggablePositionPdf.y - draggablePositionPdf.height
//Defining the image position if it is at other angles
if (normalizedAngle === 90) {
x = draggablePositionPdf.y + draggablePositionPdf.height;
y = draggablePositionPdf.x;
} else if (normalizedAngle === 180) {
x = widthAdjusted - draggablePositionPdf.x;
y = draggablePositionPdf.y + draggablePositionPdf.height;
} else if (normalizedAngle === 270) {
x = heightAdjusted - draggablePositionPdf.y - draggablePositionPdf.height;
y = widthAdjusted - draggablePositionPdf.x;
}
// draw the image
page.drawImage(pdfImageObject, {
x: draggablePositionPdf.x,
y: page.getHeight() - draggablePositionPdf.y - draggablePositionPdf.height,
x: x,
y: y,
width: draggablePositionPdf.width,
height: draggablePositionPdf.height,
rotate: rotation
});
}
}

View File

@@ -23,23 +23,26 @@
</form>
<p id="instruction-text" style="margin: 0; display: none" th:text="#{PDFToCSV.prompt}"></p>
<div style="position: relative; display: inline-block;">
<div style="position: relative; width: auto;" id="canvasesContainer">
<div>
<div style="display:none ;margin: 3px;position: absolute;top: 0;width: 120px;justify-content:space-between;z-index: 10" id="pagination-button-container">
<button id='previous-page-btn' style='opacity: 80% ; width: 50px; height: 30px; display: flex;align-items: center;justify-content: center; background: grey; color: #ffffff; ;border: none;outline: none; border-radius: 4px;'> &lt; </button>
<button id='next-page-btn' style='opacity: 80% ; width: 50px; height: 30px; display: flex;align-items: center;justify-content: center; background: grey; color: #ffffff; ;border: none;outline: none; border-radius: 4px;'> &gt; </button>
</div>
<canvas id="crop-pdf-canvas" style="position: absolute; top: 0; left: 0; z-index: 1;"></canvas>
<canvas id="cropPdfCanvas" style="width: 100%"></canvas>
</div>
<canvas id="overlayCanvas" style="position: absolute; top: 0; left: 0; z-index: 2;"></canvas>
<canvas id="overlayCanvas" style="position: absolute; top: 0; left: 0; z-index: 2; width: 100%"></canvas>
</div>
<script type="module" th:src="@{'/pdfjs-legacy/pdf.mjs'}"></script>
<script>
let pdfCanvas = document.getElementById('crop-pdf-canvas');
let pdfCanvas = document.getElementById('cropPdfCanvas');
let overlayCanvas = document.getElementById('overlayCanvas');
let canvasesContainer = document.getElementById('canvasesContainer');
canvasesContainer.style.display = "none";
// let paginationBtnContainer = ;
let context = pdfCanvas.getContext('2d');
let overlayContext = overlayCanvas.getContext('2d');
let btn1Object = document.getElementById('previous-page-btn');
let btn2Object = document.getElementById('next-page-btn');
@@ -60,6 +63,8 @@
let rectWidth = 0;
let rectHeight = 0;
let timeId = null; // timeout id for resizing canvases event
btn1Object.addEventListener('click',function (e){
if (currentPage !== 1) {
currentPage = currentPage - 1;
@@ -102,14 +107,13 @@
}
});
fileInput.addEventListener('change', function(e) {
file = e.target.files[0];
function renderPageFromFile(file) {
if (file.type === 'application/pdf') {
let reader = new FileReader();
reader.onload = function(ev) {
reader.onload = function (ev) {
let typedArray = new Uint8Array(reader.result);
pdfjsLib.GlobalWorkerOptions.workerSrc = './pdfjs-legacy/pdf.worker.mjs';
pdfjsLib.getDocument(typedArray).promise.then(function(pdf) {
pdfjsLib.getDocument(typedArray).promise.then(function (pdf) {
pdfDoc = pdf;
totalPages = pdf.numPages;
renderPage(currentPage);
@@ -117,9 +121,37 @@
pageId.value = currentPage;
};
reader.readAsArrayBuffer(file);
document.getElementById("pagination-button-container").style.display="flex";
document.getElementById("instruction-text").style.display="block";
document.getElementById("pagination-button-container").style.display = "flex";
document.getElementById("instruction-text").style.display = "block";
}
}
window.addEventListener("resize", function() {
clearTimeout(timeId);
timeId = setTimeout(function () {
if (fileInput.files.length == 0) return;
let canvasesContainer = document.getElementById('canvasesContainer');
let containerRect = canvasesContainer.getBoundingClientRect();
context.clearRect(0, 0, pdfCanvas.width, pdfCanvas.height);
overlayContext.clearRect(0, 0, overlayCanvas.width, overlayCanvas.height);
pdfCanvas.width = containerRect.width;
pdfCanvas.height = containerRect.height;
overlayCanvas.width = containerRect.width;
overlayCanvas.height = containerRect.height;
let file = fileInput.files[0];
renderPageFromFile(file);
}, 1000);
});
fileInput.addEventListener('change', function(e) {
canvasesContainer.style.display = "block"; // set for visual purposes
file = e.target.files[0];
renderPageFromFile(file);
});
function renderPage(pageNumber) {

View File

@@ -201,6 +201,7 @@
window.stirlingPDF.error = /*[[#{error}]]*/ "Error";
})();
</script>
<script type="module" th:src="@{'/pdfjs-legacy/pdf.mjs'}"></script>
<script th:src="@{'/js/downloader.js'}"></script>
<div class="custom-file-chooser" th:attr="data-bs-unique-id=${name}, data-bs-element-id=${name+'-input'}, data-bs-files-selected=#{filesSelected}, data-bs-pdf-prompt=#{pdfPrompt}">
@@ -218,4 +219,4 @@
</div>
</div>
<script th:src="@{'/js/fileInput.js'}"></script>
</th:block>
</th:block>

View File

@@ -330,7 +330,7 @@
<span class="material-symbols-rounded" id="dark-mode-icon">
dark_mode
</span>
<span class="icon-text icon-hide tooltip-text" id="dark-mode-text" th:data-text="#{navbar.darkmode}" th:text="#{navbar.darkmode}"></span>
<span class="icon-text icon-hide" id="dark-mode-text" th:data-text="#{navbar.darkmode}" th:text="#{navbar.darkmode}"></span>
</a>
</li>
<li class="nav-item dropdown">
@@ -380,7 +380,7 @@
<span class="material-symbols-rounded">
settings
</span>
<span class="icon-text icon-hide tooltip-text" th:data-text="#{navbar.settings}" th:text="#{navbar.settings}"></span>
<span class="icon-text icon-hide" th:data-text="#{navbar.settings}" th:text="#{navbar.settings}"></span>
</a>
</li>
</ul>
@@ -411,7 +411,7 @@
th:title="#{visitGithub}">
<img th:src="@{'/images/github.svg'}" alt="github">
</a>
<a href="https://hub.docker.com/r/frooodle/s-pdf" class="mx-1" role="button" target="_blank"th:title="#{seeDockerHub}">
<a href="https://hub.docker.com/r/stirlingtools/stirling-pdf" class="mx-1" role="button" target="_blank"th:title="#{seeDockerHub}">
<img th:src="@{'/images/docker.svg'}" alt="docker">
</a>
<a href="https://discord.gg/Cn8pWhQRxZ" class="mx-1" role="button" target="_blank" th:title="#{joinDiscord}">

View File

@@ -83,9 +83,6 @@
</span>
</button>
</div>
<div class="mb-3">
<button type="button" id="resetFileInputBtn" class="btn btn-danger" onclick="removeAllElements()" th:text="#{reset}">Reset</button>
</div>
<div id="selected-pages-display" class="selected-pages-container hidden">
<div style="display:flex; height:3rem; margin-right:1rem">
<h5 th:text="#{multiTool.selectedPages}" style="white-space: nowrap; margin-right: 1rem;">Selected
@@ -165,4 +162,4 @@
</script>
</body>
</html>
</html>

View File

@@ -49,7 +49,7 @@ check_health() {
build_and_test() {
local version_tag="alpha"
local dockerfile_name="./Dockerfile"
local image_base="frooodle/s-pdf"
local image_base="stirlingtools/stirling-pdf"
local security_suffix=""
local docker_compose_base="./exampleYmlFiles/docker-compose-latest"
local compose_suffix=".yml"