Compare commits
29 Commits
v0.33.0
...
Frooodle-p
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d19d87b8f0 | ||
|
|
a22ce69bc3 | ||
|
|
59b60ebfb8 | ||
|
|
a6ae3734ca | ||
|
|
b9a014b5c7 | ||
|
|
9e30918aae | ||
|
|
c239d95131 | ||
|
|
d591874da6 | ||
|
|
6c623d8d84 | ||
|
|
e059caa14e | ||
|
|
8eab35761d | ||
|
|
c43af24ffe | ||
|
|
e1b3cc736c | ||
|
|
0fb9e18636 | ||
|
|
5e1aac0b84 | ||
|
|
60bf649260 | ||
|
|
a58696a38e | ||
|
|
44abc67678 | ||
|
|
d1e690ff8d | ||
|
|
5dc8fa08ee | ||
|
|
db028dfe27 | ||
|
|
c24c504350 | ||
|
|
5dcfe64d1c | ||
|
|
d843696703 | ||
|
|
67de8a9460 | ||
|
|
b26aa3417e | ||
|
|
8dfb5940ca | ||
|
|
0ce479e1e3 | ||
|
|
cca3b6b525 |
179
.github/workflows/PR-Demo-Comment.yml
vendored
Normal file
179
.github/workflows/PR-Demo-Comment.yml
vendored
Normal 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
78
.github/workflows/PR-Demo-cleanup.yml
vendored
Normal 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
|
||||||
|
});
|
||||||
54
.github/workflows/check_properties.yml
vendored
54
.github/workflows/check_properties.yml
vendored
@@ -9,6 +9,7 @@ on:
|
|||||||
paths:
|
paths:
|
||||||
- "src/main/resources/messages_en_GB.properties"
|
- "src/main/resources/messages_en_GB.properties"
|
||||||
|
|
||||||
|
# Permissions required for the workflow
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
@@ -18,6 +19,13 @@ jobs:
|
|||||||
if: github.event_name == 'pull_request_target'
|
if: github.event_name == 'pull_request_target'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
- name: Checkout main branch first
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: main
|
||||||
|
path: main-branch
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Checkout PR branch
|
- name: Checkout PR branch
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
@@ -26,13 +34,6 @@ jobs:
|
|||||||
path: pr-branch
|
path: pr-branch
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Checkout main branch
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
ref: main
|
|
||||||
path: main-branch
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
@@ -49,34 +50,46 @@ jobs:
|
|||||||
echo "Fetching PR changed files..."
|
echo "Fetching PR changed files..."
|
||||||
cd pr-branch
|
cd pr-branch
|
||||||
gh repo set-default ${{ github.repository }}
|
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 ..
|
cd ..
|
||||||
echo $(cat changed_files.txt)
|
echo "Setting branch path..."
|
||||||
BRANCH_PATH="pr-branch"
|
BRANCH_PATH="pr-branch"
|
||||||
|
|
||||||
echo "BRANCH_PATH=${BRANCH_PATH}" >> $GITHUB_ENV
|
echo "BRANCH_PATH=${BRANCH_PATH}" >> $GITHUB_ENV
|
||||||
CHANGED_FILES=$(cat changed_files.txt | tr '\n' ' ')
|
echo "Processing changed files..."
|
||||||
echo "CHANGED_FILES=${CHANGED_FILES}" >> $GITHUB_ENV
|
mapfile -t CHANGED_FILES < changed_files.txt
|
||||||
echo "Changed files: ${CHANGED_FILES}"
|
|
||||||
|
CHANGED_FILES_STR="${CHANGED_FILES[*]}"
|
||||||
|
echo "CHANGED_FILES=${CHANGED_FILES_STR}" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
echo "Changed files: ${CHANGED_FILES_STR}"
|
||||||
echo "Branch: ${BRANCH_PATH}"
|
echo "Branch: ${BRANCH_PATH}"
|
||||||
|
|
||||||
- name: Determine reference file
|
- name: Determine reference file
|
||||||
id: determine-file
|
id: determine-file
|
||||||
run: |
|
run: |
|
||||||
echo "Determining reference file..."
|
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
|
echo "REFERENCE_FILE=pr-branch/src/main/resources/messages_en_GB.properties" >> $GITHUB_ENV
|
||||||
else
|
else
|
||||||
|
echo "Using main branch reference file"
|
||||||
echo "REFERENCE_FILE=main-branch/src/main/resources/messages_en_GB.properties" >> $GITHUB_ENV
|
echo "REFERENCE_FILE=main-branch/src/main/resources/messages_en_GB.properties" >> $GITHUB_ENV
|
||||||
fi
|
fi
|
||||||
echo "REFERENCE_FILE=${{ env.REFERENCE_FILE }}"
|
|
||||||
|
|
||||||
- name: Show 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
|
- name: Run Python script to check files
|
||||||
id: run-check
|
id: run-check
|
||||||
run: |
|
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
|
- name: Capture output
|
||||||
id: capture-output
|
id: capture-output
|
||||||
@@ -87,7 +100,7 @@ jobs:
|
|||||||
echo "ERROR_OUTPUT<<EOF" >> $GITHUB_ENV
|
echo "ERROR_OUTPUT<<EOF" >> $GITHUB_ENV
|
||||||
echo "$ERROR_OUTPUT" >> $GITHUB_ENV
|
echo "$ERROR_OUTPUT" >> $GITHUB_ENV
|
||||||
echo "EOF" >> $GITHUB_ENV
|
echo "EOF" >> $GITHUB_ENV
|
||||||
echo $ERROR_OUTPUT
|
echo "${ERROR_OUTPUT}"
|
||||||
else
|
else
|
||||||
echo "No errors found."
|
echo "No errors found."
|
||||||
echo "ERROR_OUTPUT=" >> $GITHUB_ENV
|
echo "ERROR_OUTPUT=" >> $GITHUB_ENV
|
||||||
@@ -110,7 +123,7 @@ jobs:
|
|||||||
});
|
});
|
||||||
|
|
||||||
const comment = comments.data.find(c => c.body.includes("## 🚀 Translation Verification Summary"));
|
const comment = comments.data.find(c => c.body.includes("## 🚀 Translation Verification Summary"));
|
||||||
|
|
||||||
// Only allow the action user to update comments
|
// Only allow the action user to update comments
|
||||||
const expectedActor = "github-actions[bot]";
|
const expectedActor = "github-actions[bot]";
|
||||||
|
|
||||||
@@ -169,7 +182,10 @@ jobs:
|
|||||||
- name: Run Python script to check files
|
- name: Run Python script to check files
|
||||||
id: run-check
|
id: run-check
|
||||||
run: |
|
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
|
- name: Set up git config
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
3
.github/workflows/push-docker.yml
vendored
3
.github/workflows/push-docker.yml
vendored
@@ -67,6 +67,7 @@ jobs:
|
|||||||
images: |
|
images: |
|
||||||
${{ secrets.DOCKER_HUB_USERNAME }}/s-pdf
|
${{ secrets.DOCKER_HUB_USERNAME }}/s-pdf
|
||||||
ghcr.io/${{ steps.repoowner.outputs.lowercase }}/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
|
${{ secrets.DOCKER_HUB_ORG_USERNAME }}/stirling-pdf
|
||||||
tags: |
|
tags: |
|
||||||
type=raw,value=${{ steps.versionNumber.outputs.versionNumber }},enable=${{ github.ref == 'refs/heads/master' }}
|
type=raw,value=${{ steps.versionNumber.outputs.versionNumber }},enable=${{ github.ref == 'refs/heads/master' }}
|
||||||
@@ -95,6 +96,7 @@ jobs:
|
|||||||
images: |
|
images: |
|
||||||
${{ secrets.DOCKER_HUB_USERNAME }}/s-pdf
|
${{ secrets.DOCKER_HUB_USERNAME }}/s-pdf
|
||||||
ghcr.io/${{ steps.repoowner.outputs.lowercase }}/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
|
${{ secrets.DOCKER_HUB_ORG_USERNAME }}/stirling-pdf
|
||||||
tags: |
|
tags: |
|
||||||
type=raw,value=${{ steps.versionNumber.outputs.versionNumber }}-ultra-lite,enable=${{ github.ref == 'refs/heads/master' }}
|
type=raw,value=${{ steps.versionNumber.outputs.versionNumber }}-ultra-lite,enable=${{ github.ref == 'refs/heads/master' }}
|
||||||
@@ -122,6 +124,7 @@ jobs:
|
|||||||
images: |
|
images: |
|
||||||
${{ secrets.DOCKER_HUB_USERNAME }}/s-pdf
|
${{ secrets.DOCKER_HUB_USERNAME }}/s-pdf
|
||||||
ghcr.io/${{ steps.repoowner.outputs.lowercase }}/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
|
${{ secrets.DOCKER_HUB_ORG_USERNAME }}/stirling-pdf
|
||||||
tags: |
|
tags: |
|
||||||
type=raw,value=${{ steps.versionNumber.outputs.versionNumber }}-fat,enable=${{ github.ref == 'refs/heads/master' }}
|
type=raw,value=${{ steps.versionNumber.outputs.versionNumber }}-fat,enable=${{ github.ref == 'refs/heads/master' }}
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ These files provide pre-configured setups for different scenarios. For example,
|
|||||||
services:
|
services:
|
||||||
stirling-pdf:
|
stirling-pdf:
|
||||||
container_name: Stirling-PDF-Security
|
container_name: Stirling-PDF-Security
|
||||||
image: frooodle/s-pdf:latest
|
image: stirlingtools/stirling-pdf:latest
|
||||||
deploy:
|
deploy:
|
||||||
resources:
|
resources:
|
||||||
limits:
|
limits:
|
||||||
@@ -173,20 +173,20 @@ Stirling-PDF uses different Docker images for various configurations. The build
|
|||||||
For the latest version:
|
For the latest version:
|
||||||
|
|
||||||
```bash
|
```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:
|
For the ultra-lite version:
|
||||||
|
|
||||||
```bash
|
```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):
|
For the fat version (with security enabled):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
export DOCKER_ENABLE_SECURITY=true
|
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
|
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
45
Jenkinsfile
vendored
@@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
12
README.md
12
README.md
@@ -120,13 +120,13 @@ Please view the [LocalRunGuide](https://github.com/Stirling-Tools/Stirling-PDF/b
|
|||||||
### Docker / Podman
|
### Docker / Podman
|
||||||
|
|
||||||
> [!NOTE]
|
> [!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.
|
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.
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||

|

|
||||||
|
|
||||||
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`.
|
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 INSTALL_BOOK_AND_ADVANCED_HTML_OPS=false \
|
||||||
-e LANGS=en_GB \
|
-e LANGS=en_GB \
|
||||||
--name stirling-pdf \
|
--name stirling-pdf \
|
||||||
frooodle/s-pdf:latest
|
stirlingtools/stirling-pdf:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
### Docker Compose
|
### Docker Compose
|
||||||
@@ -153,7 +153,7 @@ docker run -d \
|
|||||||
version: '3.3'
|
version: '3.3'
|
||||||
services:
|
services:
|
||||||
stirling-pdf:
|
stirling-pdf:
|
||||||
image: frooodle/s-pdf:latest
|
image: stirlingtools/stirling-pdf:latest
|
||||||
ports:
|
ports:
|
||||||
- '8080:8080'
|
- '8080:8080'
|
||||||
volumes:
|
volumes:
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ plugins {
|
|||||||
//id "nebula.lint" version "19.0.3"
|
//id "nebula.lint" version "19.0.3"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
import com.github.jk1.license.render.*
|
import com.github.jk1.license.render.*
|
||||||
|
|
||||||
ext {
|
ext {
|
||||||
@@ -17,12 +19,12 @@ ext {
|
|||||||
pdfboxVersion = "3.0.3"
|
pdfboxVersion = "3.0.3"
|
||||||
logbackVersion = "1.5.7"
|
logbackVersion = "1.5.7"
|
||||||
imageioVersion = "3.12.0"
|
imageioVersion = "3.12.0"
|
||||||
lombokVersion = "1.18.34"
|
lombokVersion = "1.18.36"
|
||||||
bouncycastleVersion = "1.78.1"
|
bouncycastleVersion = "1.78.1"
|
||||||
}
|
}
|
||||||
|
|
||||||
group = "stirling.software"
|
group = "stirling.software"
|
||||||
version = "0.33.0"
|
version = "0.33.1"
|
||||||
|
|
||||||
java {
|
java {
|
||||||
// 17 is lowest but we support and recommend 21
|
// 17 is lowest but we support and recommend 21
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
services:
|
services:
|
||||||
stirling-pdf:
|
stirling-pdf:
|
||||||
container_name: Stirling-PDF-Security-Fat
|
container_name: Stirling-PDF-Security-Fat
|
||||||
image: frooodle/s-pdf:latest-fat
|
image: stirlingtools/stirling-pdf:latest-fat
|
||||||
deploy:
|
deploy:
|
||||||
resources:
|
resources:
|
||||||
limits:
|
limits:
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
services:
|
services:
|
||||||
stirling-pdf:
|
stirling-pdf:
|
||||||
container_name: Stirling-PDF-Security
|
container_name: Stirling-PDF-Security
|
||||||
image: frooodle/s-pdf:latest
|
image: stirlingtools/stirling-pdf:latest
|
||||||
deploy:
|
deploy:
|
||||||
resources:
|
resources:
|
||||||
limits:
|
limits:
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
services:
|
services:
|
||||||
stirling-pdf:
|
stirling-pdf:
|
||||||
container_name: Stirling-PDF-Security
|
container_name: Stirling-PDF-Security
|
||||||
image: frooodle/s-pdf:latest
|
image: stirlingtools/stirling-pdf:latest
|
||||||
deploy:
|
deploy:
|
||||||
resources:
|
resources:
|
||||||
limits:
|
limits:
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
services:
|
services:
|
||||||
stirling-pdf:
|
stirling-pdf:
|
||||||
container_name: Stirling-PDF-Ultra-Lite-Security
|
container_name: Stirling-PDF-Ultra-Lite-Security
|
||||||
image: frooodle/s-pdf:latest-ultra-lite
|
image: stirlingtools/stirling-pdf:latest-ultra-lite
|
||||||
deploy:
|
deploy:
|
||||||
resources:
|
resources:
|
||||||
limits:
|
limits:
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
services:
|
services:
|
||||||
stirling-pdf:
|
stirling-pdf:
|
||||||
container_name: Stirling-PDF-Ultra-Lite
|
container_name: Stirling-PDF-Ultra-Lite
|
||||||
image: frooodle/s-pdf:latest-ultra-lite
|
image: stirlingtools/stirling-pdf:latest-ultra-lite
|
||||||
deploy:
|
deploy:
|
||||||
resources:
|
resources:
|
||||||
limits:
|
limits:
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
services:
|
services:
|
||||||
stirling-pdf:
|
stirling-pdf:
|
||||||
container_name: Stirling-PDF
|
container_name: Stirling-PDF
|
||||||
image: frooodle/s-pdf:latest
|
image: stirlingtools/stirling-pdf:latest
|
||||||
deploy:
|
deploy:
|
||||||
resources:
|
resources:
|
||||||
limits:
|
limits:
|
||||||
|
|||||||
@@ -34,6 +34,12 @@ public class DatabaseBackupHelper implements DatabaseBackupInterface {
|
|||||||
@Value("${spring.datasource.url}")
|
@Value("${spring.datasource.url}")
|
||||||
private String 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/");
|
private Path backupPath = Paths.get("configs/db/backup/");
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -134,7 +140,8 @@ public class DatabaseBackupHelper implements DatabaseBackupInterface {
|
|||||||
this.getBackupFilePath("backup_" + dateNow.format(myFormatObj) + ".sql");
|
this.getBackupFilePath("backup_" + dateNow.format(myFormatObj) + ".sql");
|
||||||
String query = "SCRIPT SIMPLE COLUMNS DROP to ?;";
|
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)) {
|
PreparedStatement stmt = conn.prepareStatement(query)) {
|
||||||
stmt.setString(1, insertOutputFilePath.toString());
|
stmt.setString(1, insertOutputFilePath.toString());
|
||||||
stmt.execute();
|
stmt.execute();
|
||||||
@@ -147,7 +154,8 @@ public class DatabaseBackupHelper implements DatabaseBackupInterface {
|
|||||||
// Retrieves the H2 database version.
|
// Retrieves the H2 database version.
|
||||||
public String getH2Version() {
|
public String getH2Version() {
|
||||||
String version = "Unknown";
|
String version = "Unknown";
|
||||||
try (Connection conn = DriverManager.getConnection(url, "sa", "")) {
|
try (Connection conn =
|
||||||
|
DriverManager.getConnection(url, databaseUsername, databasePassword)) {
|
||||||
try (Statement stmt = conn.createStatement();
|
try (Statement stmt = conn.createStatement();
|
||||||
ResultSet rs = stmt.executeQuery("SELECT H2VERSION() AS version")) {
|
ResultSet rs = stmt.executeQuery("SELECT H2VERSION() AS version")) {
|
||||||
if (rs.next()) {
|
if (rs.next()) {
|
||||||
@@ -189,7 +197,8 @@ public class DatabaseBackupHelper implements DatabaseBackupInterface {
|
|||||||
private boolean executeDatabaseScript(Path scriptPath) {
|
private boolean executeDatabaseScript(Path scriptPath) {
|
||||||
String query = "RUNSCRIPT from ?;";
|
String query = "RUNSCRIPT from ?;";
|
||||||
|
|
||||||
try (Connection conn = DriverManager.getConnection(url, "sa", "");
|
try (Connection conn =
|
||||||
|
DriverManager.getConnection(url, databaseUsername, databasePassword);
|
||||||
PreparedStatement stmt = conn.prepareStatement(query)) {
|
PreparedStatement stmt = conn.prepareStatement(query)) {
|
||||||
stmt.setString(1, scriptPath.toString());
|
stmt.setString(1, scriptPath.toString());
|
||||||
stmt.execute();
|
stmt.execute();
|
||||||
|
|||||||
@@ -32,11 +32,19 @@ public class MetricsAggregatorService {
|
|||||||
.counters()
|
.counters()
|
||||||
.forEach(
|
.forEach(
|
||||||
counter -> {
|
counter -> {
|
||||||
String key =
|
String method = counter.getId().getTag("method");
|
||||||
String.format(
|
String uri = counter.getId().getTag("uri");
|
||||||
"http_requests_%s_%s",
|
|
||||||
counter.getId().getTag("method"),
|
// Skip if either method or uri is null
|
||||||
counter.getId().getTag("uri").replace("/", "_"));
|
if (method == null || uri == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String key = String.format(
|
||||||
|
"http_requests_%s_%s",
|
||||||
|
method,
|
||||||
|
uri.replace("/", "_")
|
||||||
|
);
|
||||||
|
|
||||||
double currentCount = counter.count();
|
double currentCount = counter.count();
|
||||||
double lastCount = lastSentMetrics.getOrDefault(key, 0.0);
|
double lastCount = lastSentMetrics.getOrDefault(key, 0.0);
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ span.icon-text::after {
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tooltip-text {
|
/* .tooltip-text {
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
background-color: rgb(71 67 67 / 80%);
|
background-color: rgb(71 67 67 / 80%);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
@@ -162,7 +162,7 @@ span.icon-text::after {
|
|||||||
.nav-item:hover .tooltip-text {
|
.nav-item:hover .tooltip-text {
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
} */
|
||||||
|
|
||||||
.dropdown-menu.scrollable-y {
|
.dropdown-menu.scrollable-y {
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
@@ -324,6 +324,18 @@ span.icon-text::after {
|
|||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* .icon-hide {
|
||||||
|
display: none;
|
||||||
|
} */
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width:1199px) {
|
||||||
|
.icon-hide {
|
||||||
|
display: inline-flex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width:1200px) {
|
||||||
.icon-hide {
|
.icon-hide {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
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 {
|
try {
|
||||||
const response = await fetch(url, { method: "POST", body: formData });
|
const response = await fetch(url, { method: "POST", body: formData });
|
||||||
const contentType = response.headers.get("content-type");
|
const contentType = response.headers.get("content-type");
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
errorMessage = response.status;
|
||||||
if (response.status === 401) {
|
if (response.status === 401) {
|
||||||
// Handle 401 Unauthorized error
|
|
||||||
showSessionExpiredPrompt();
|
showSessionExpiredPrompt();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -118,6 +135,8 @@
|
|||||||
let filename = getFilenameFromContentDisposition(contentDisposition);
|
let filename = getFilenameFromContentDisposition(contentDisposition);
|
||||||
|
|
||||||
const blob = await response.blob();
|
const blob = await response.blob();
|
||||||
|
success = true;
|
||||||
|
|
||||||
if (contentType.includes("application/pdf") || contentType.includes("image/")) {
|
if (contentType.includes("application/pdf") || contentType.includes("image/")) {
|
||||||
clearFileInput();
|
clearFileInput();
|
||||||
return handleResponse(blob, filename, !isMulti, isZip);
|
return handleResponse(blob, filename, !isMulti, isZip);
|
||||||
@@ -127,13 +146,29 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
success = false;
|
||||||
|
errorMessage = error.message;
|
||||||
clearFileInput();
|
clearFileInput();
|
||||||
console.error("Error in handleSingleDownload:", error);
|
console.error("Error in handleSingleDownload:", error);
|
||||||
throw 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;
|
let filename;
|
||||||
|
|
||||||
if (contentDisposition && contentDisposition.indexOf("attachment") !== -1) {
|
if (contentDisposition && contentDisposition.indexOf("attachment") !== -1) {
|
||||||
@@ -145,7 +180,7 @@
|
|||||||
|
|
||||||
return filename;
|
return filename;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleJsonResponse(response) {
|
async function handleJsonResponse(response) {
|
||||||
const json = await response.json();
|
const json = await response.json();
|
||||||
const errorMessage = JSON.stringify(json, null, 2);
|
const errorMessage = JSON.stringify(json, null, 2);
|
||||||
@@ -308,14 +343,14 @@
|
|||||||
if(editSectionElement){
|
if(editSectionElement){
|
||||||
editSectionElement.style.display = "none";
|
editSectionElement.style.display = "none";
|
||||||
}
|
}
|
||||||
let cropPdfCanvas = document.querySelector("#crop-pdf-canvas");
|
let cropPdfCanvas = document.querySelector("#cropPdfCanvas");
|
||||||
let overlayCanvas = document.querySelector("#overlayCanvas");
|
let overlayCanvas = document.querySelector("#overlayCanvas");
|
||||||
if(cropPdfCanvas && overlayCanvas){
|
if(cropPdfCanvas && overlayCanvas){
|
||||||
cropPdfCanvas.width = 0;
|
cropPdfCanvas.width = 0;
|
||||||
cropPdfCanvas.heigth = 0;
|
cropPdfCanvas.height = 0;
|
||||||
|
|
||||||
overlayCanvas.width = 0;
|
overlayCanvas.width = 0;
|
||||||
overlayCanvas.heigth = 0;
|
overlayCanvas.height = 0;
|
||||||
}
|
}
|
||||||
} else{
|
} else{
|
||||||
console.log("Disabled for 'Merge'");
|
console.log("Disabled for 'Merge'");
|
||||||
|
|||||||
@@ -9,81 +9,81 @@ const DraggableUtils = {
|
|||||||
|
|
||||||
init() {
|
init() {
|
||||||
interact(".draggable-canvas")
|
interact(".draggable-canvas")
|
||||||
.draggable({
|
.draggable({
|
||||||
listeners: {
|
listeners: {
|
||||||
move: (event) => {
|
move: (event) => {
|
||||||
const target = event.target;
|
const target = event.target;
|
||||||
const x = (parseFloat(target.getAttribute("data-bs-x")) || 0)
|
const x = (parseFloat(target.getAttribute("data-bs-x")) || 0)
|
||||||
+ event.dx;
|
+ event.dx;
|
||||||
const y = (parseFloat(target.getAttribute("data-bs-y")) || 0)
|
const y = (parseFloat(target.getAttribute("data-bs-y")) || 0)
|
||||||
+ event.dy;
|
+ event.dy;
|
||||||
|
|
||||||
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-x", x);
|
||||||
target.setAttribute("data-bs-y", y);
|
target.setAttribute("data-bs-y", y);
|
||||||
|
|
||||||
this.onInteraction(target);
|
this.onInteraction(target);
|
||||||
//update the last interacted element
|
//update the last interacted element
|
||||||
this.lastInteracted = event.target;
|
this.lastInteracted = event.target;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
})
|
||||||
})
|
.resizable({
|
||||||
.resizable({
|
edges: { left: true, right: true, bottom: true, top: true },
|
||||||
edges: { left: true, right: true, bottom: true, top: true },
|
listeners: {
|
||||||
listeners: {
|
move: (event) => {
|
||||||
move: (event) => {
|
var target = event.target;
|
||||||
var target = event.target;
|
var x = parseFloat(target.getAttribute("data-bs-x")) || 0;
|
||||||
var x = parseFloat(target.getAttribute("data-bs-x")) || 0;
|
var y = parseFloat(target.getAttribute("data-bs-y")) || 0;
|
||||||
var y = parseFloat(target.getAttribute("data-bs-y")) || 0;
|
|
||||||
|
|
||||||
// check if control key is pressed
|
// check if control key is pressed
|
||||||
if (event.ctrlKey) {
|
if (event.ctrlKey) {
|
||||||
const aspectRatio = target.offsetWidth / target.offsetHeight;
|
const aspectRatio = target.offsetWidth / target.offsetHeight;
|
||||||
// preserve aspect ratio
|
// preserve aspect ratio
|
||||||
let width = event.rect.width;
|
let width = event.rect.width;
|
||||||
let height = event.rect.height;
|
let height = event.rect.height;
|
||||||
|
|
||||||
if (Math.abs(event.deltaRect.width) >= Math.abs(
|
if (Math.abs(event.deltaRect.width) >= Math.abs(
|
||||||
event.deltaRect.height)) {
|
event.deltaRect.height)) {
|
||||||
height = width / aspectRatio;
|
height = width / aspectRatio;
|
||||||
} else {
|
} else {
|
||||||
width = height * aspectRatio;
|
width = height * aspectRatio;
|
||||||
|
}
|
||||||
|
|
||||||
|
event.rect.width = width;
|
||||||
|
event.rect.height = height;
|
||||||
}
|
}
|
||||||
|
|
||||||
event.rect.width = width;
|
target.style.width = event.rect.width + "px";
|
||||||
event.rect.height = height;
|
target.style.height = event.rect.height + "px";
|
||||||
}
|
|
||||||
|
|
||||||
target.style.width = event.rect.width + "px";
|
// translate when resizing from top or left edges
|
||||||
target.style.height = event.rect.height + "px";
|
x += event.deltaRect.left;
|
||||||
|
y += event.deltaRect.top;
|
||||||
|
|
||||||
// translate when resizing from top or left edges
|
target.style.transform = "translate(" + x + "px," + y + "px)";
|
||||||
x += event.deltaRect.left;
|
|
||||||
y += event.deltaRect.top;
|
|
||||||
|
|
||||||
target.style.transform = "translate(" + x + "px," + y + "px)";
|
target.setAttribute("data-bs-x", x);
|
||||||
|
target.setAttribute("data-bs-y", y);
|
||||||
target.setAttribute("data-bs-x", x);
|
target.textContent = Math.round(event.rect.width) + "\u00D7"
|
||||||
target.setAttribute("data-bs-y", y);
|
|
||||||
target.textContent = Math.round(event.rect.width) + "\u00D7"
|
|
||||||
+ Math.round(event.rect.height);
|
+ Math.round(event.rect.height);
|
||||||
|
|
||||||
this.onInteraction(target);
|
this.onInteraction(target);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
|
||||||
|
|
||||||
modifiers: [
|
modifiers: [
|
||||||
interact.modifiers.restrictSize({
|
interact.modifiers.restrictSize({
|
||||||
min: {width: 5, height: 5},
|
min: { width: 5, height: 5 },
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
inertia: true,
|
inertia: true,
|
||||||
});
|
});
|
||||||
//Arrow key Support for Add-Image and Sign pages
|
//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) => {
|
window.addEventListener('keydown', (event) => {
|
||||||
//Check for last interacted element
|
//Check for last interacted element
|
||||||
if (!this.lastInteracted){
|
if (!this.lastInteracted) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Get the currently selected element
|
// Get the currently selected element
|
||||||
@@ -288,7 +288,7 @@ const DraggableUtils = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
parseTransform(element) {},
|
parseTransform(element) { },
|
||||||
async getOverlayedPdfDocument() {
|
async getOverlayedPdfDocument() {
|
||||||
const pdfBytes = await this.pdfDoc.getData();
|
const pdfBytes = await this.pdfDoc.getData();
|
||||||
const pdfDocModified = await PDFLib.PDFDocument.load(pdfBytes, {
|
const pdfDocModified = await PDFLib.PDFDocument.load(pdfBytes, {
|
||||||
@@ -308,6 +308,7 @@ const DraggableUtils = {
|
|||||||
const offsetWidth = pagesMap[pageIdx + "-offsetWidth"];
|
const offsetWidth = pagesMap[pageIdx + "-offsetWidth"];
|
||||||
const offsetHeight = pagesMap[pageIdx + "-offsetHeight"];
|
const offsetHeight = pagesMap[pageIdx + "-offsetHeight"];
|
||||||
|
|
||||||
|
|
||||||
for (const draggableData of draggablesData) {
|
for (const draggableData of draggablesData) {
|
||||||
// embed the draggable canvas
|
// embed the draggable canvas
|
||||||
const draggableElement = draggableData.element;
|
const draggableElement = draggableData.element;
|
||||||
@@ -324,6 +325,24 @@ const DraggableUtils = {
|
|||||||
width: draggableData.offsetWidth,
|
width: draggableData.offsetWidth,
|
||||||
height: draggableData.offsetHeight,
|
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 = {
|
const draggablePositionRelative = {
|
||||||
x: draggablePositionPixels.x / offsetWidth,
|
x: draggablePositionPixels.x / offsetWidth,
|
||||||
y: draggablePositionPixels.y / offsetHeight,
|
y: draggablePositionPixels.y / offsetHeight,
|
||||||
@@ -331,18 +350,36 @@ const DraggableUtils = {
|
|||||||
height: draggablePositionPixels.height / offsetHeight,
|
height: draggablePositionPixels.height / offsetHeight,
|
||||||
};
|
};
|
||||||
const draggablePositionPdf = {
|
const draggablePositionPdf = {
|
||||||
x: draggablePositionRelative.x * page.getWidth(),
|
x: draggablePositionRelative.x * widthAdjusted,
|
||||||
y: draggablePositionRelative.y * page.getHeight(),
|
y: draggablePositionRelative.y * heightAdjusted,
|
||||||
width: draggablePositionRelative.width * page.getWidth(),
|
width: draggablePositionRelative.width * widthAdjusted,
|
||||||
height: draggablePositionRelative.height * page.getHeight(),
|
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
|
// draw the image
|
||||||
page.drawImage(pdfImageObject, {
|
page.drawImage(pdfImageObject, {
|
||||||
x: draggablePositionPdf.x,
|
x: x,
|
||||||
y: page.getHeight() - draggablePositionPdf.y - draggablePositionPdf.height,
|
y: y,
|
||||||
width: draggablePositionPdf.width,
|
width: draggablePositionPdf.width,
|
||||||
height: draggablePositionPdf.height,
|
height: draggablePositionPdf.height,
|
||||||
|
rotate: rotation
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,23 +23,26 @@
|
|||||||
</form>
|
</form>
|
||||||
<p id="instruction-text" style="margin: 0; display: none" th:text="#{PDFToCSV.prompt}"></p>
|
<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>
|
||||||
<div style="display:none ;margin: 3px;position: absolute;top: 0;width: 120px;justify-content:space-between;z-index: 10" id="pagination-button-container">
|
<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;'> < </button>
|
<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;'> < </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;'> > </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;'> > </button>
|
||||||
</div>
|
</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>
|
</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>
|
</div>
|
||||||
<script type="module" th:src="@{'/pdfjs-legacy/pdf.mjs'}"></script>
|
<script type="module" th:src="@{'/pdfjs-legacy/pdf.mjs'}"></script>
|
||||||
<script>
|
<script>
|
||||||
let pdfCanvas = document.getElementById('crop-pdf-canvas');
|
let pdfCanvas = document.getElementById('cropPdfCanvas');
|
||||||
let overlayCanvas = document.getElementById('overlayCanvas');
|
let overlayCanvas = document.getElementById('overlayCanvas');
|
||||||
|
let canvasesContainer = document.getElementById('canvasesContainer');
|
||||||
|
canvasesContainer.style.display = "none";
|
||||||
// let paginationBtnContainer = ;
|
// let paginationBtnContainer = ;
|
||||||
|
|
||||||
let context = pdfCanvas.getContext('2d');
|
let context = pdfCanvas.getContext('2d');
|
||||||
|
let overlayContext = overlayCanvas.getContext('2d');
|
||||||
|
|
||||||
let btn1Object = document.getElementById('previous-page-btn');
|
let btn1Object = document.getElementById('previous-page-btn');
|
||||||
let btn2Object = document.getElementById('next-page-btn');
|
let btn2Object = document.getElementById('next-page-btn');
|
||||||
@@ -60,6 +63,8 @@
|
|||||||
let rectWidth = 0;
|
let rectWidth = 0;
|
||||||
let rectHeight = 0;
|
let rectHeight = 0;
|
||||||
|
|
||||||
|
let timeId = null; // timeout id for resizing canvases event
|
||||||
|
|
||||||
btn1Object.addEventListener('click',function (e){
|
btn1Object.addEventListener('click',function (e){
|
||||||
if (currentPage !== 1) {
|
if (currentPage !== 1) {
|
||||||
currentPage = currentPage - 1;
|
currentPage = currentPage - 1;
|
||||||
@@ -102,14 +107,13 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
fileInput.addEventListener('change', function(e) {
|
function renderPageFromFile(file) {
|
||||||
file = e.target.files[0];
|
|
||||||
if (file.type === 'application/pdf') {
|
if (file.type === 'application/pdf') {
|
||||||
let reader = new FileReader();
|
let reader = new FileReader();
|
||||||
reader.onload = function(ev) {
|
reader.onload = function (ev) {
|
||||||
let typedArray = new Uint8Array(reader.result);
|
let typedArray = new Uint8Array(reader.result);
|
||||||
pdfjsLib.GlobalWorkerOptions.workerSrc = './pdfjs-legacy/pdf.worker.mjs';
|
pdfjsLib.GlobalWorkerOptions.workerSrc = './pdfjs-legacy/pdf.worker.mjs';
|
||||||
pdfjsLib.getDocument(typedArray).promise.then(function(pdf) {
|
pdfjsLib.getDocument(typedArray).promise.then(function (pdf) {
|
||||||
pdfDoc = pdf;
|
pdfDoc = pdf;
|
||||||
totalPages = pdf.numPages;
|
totalPages = pdf.numPages;
|
||||||
renderPage(currentPage);
|
renderPage(currentPage);
|
||||||
@@ -117,9 +121,37 @@
|
|||||||
pageId.value = currentPage;
|
pageId.value = currentPage;
|
||||||
};
|
};
|
||||||
reader.readAsArrayBuffer(file);
|
reader.readAsArrayBuffer(file);
|
||||||
document.getElementById("pagination-button-container").style.display="flex";
|
document.getElementById("pagination-button-container").style.display = "flex";
|
||||||
document.getElementById("instruction-text").style.display="block";
|
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) {
|
function renderPage(pageNumber) {
|
||||||
|
|||||||
@@ -201,6 +201,7 @@
|
|||||||
window.stirlingPDF.error = /*[[#{error}]]*/ "Error";
|
window.stirlingPDF.error = /*[[#{error}]]*/ "Error";
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
<script type="module" th:src="@{'/pdfjs-legacy/pdf.mjs'}"></script>
|
||||||
<script th:src="@{'/js/downloader.js'}"></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}">
|
<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>
|
||||||
</div>
|
</div>
|
||||||
<script th:src="@{'/js/fileInput.js'}"></script>
|
<script th:src="@{'/js/fileInput.js'}"></script>
|
||||||
</th:block>
|
</th:block>
|
||||||
|
|||||||
@@ -330,7 +330,7 @@
|
|||||||
<span class="material-symbols-rounded" id="dark-mode-icon">
|
<span class="material-symbols-rounded" id="dark-mode-icon">
|
||||||
dark_mode
|
dark_mode
|
||||||
</span>
|
</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>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item dropdown">
|
<li class="nav-item dropdown">
|
||||||
@@ -380,7 +380,7 @@
|
|||||||
<span class="material-symbols-rounded">
|
<span class="material-symbols-rounded">
|
||||||
settings
|
settings
|
||||||
</span>
|
</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>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -411,7 +411,7 @@
|
|||||||
th:title="#{visitGithub}">
|
th:title="#{visitGithub}">
|
||||||
<img th:src="@{'/images/github.svg'}" alt="github">
|
<img th:src="@{'/images/github.svg'}" alt="github">
|
||||||
</a>
|
</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">
|
<img th:src="@{'/images/docker.svg'}" alt="docker">
|
||||||
</a>
|
</a>
|
||||||
<a href="https://discord.gg/Cn8pWhQRxZ" class="mx-1" role="button" target="_blank" th:title="#{joinDiscord}">
|
<a href="https://discord.gg/Cn8pWhQRxZ" class="mx-1" role="button" target="_blank" th:title="#{joinDiscord}">
|
||||||
|
|||||||
@@ -83,9 +83,6 @@
|
|||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</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 id="selected-pages-display" class="selected-pages-container hidden">
|
||||||
<div style="display:flex; height:3rem; margin-right:1rem">
|
<div style="display:flex; height:3rem; margin-right:1rem">
|
||||||
<h5 th:text="#{multiTool.selectedPages}" style="white-space: nowrap; margin-right: 1rem;">Selected
|
<h5 th:text="#{multiTool.selectedPages}" style="white-space: nowrap; margin-right: 1rem;">Selected
|
||||||
@@ -165,4 +162,4 @@
|
|||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
2
test2.sh
2
test2.sh
@@ -49,7 +49,7 @@ check_health() {
|
|||||||
build_and_test() {
|
build_and_test() {
|
||||||
local version_tag="alpha"
|
local version_tag="alpha"
|
||||||
local dockerfile_name="./Dockerfile"
|
local dockerfile_name="./Dockerfile"
|
||||||
local image_base="frooodle/s-pdf"
|
local image_base="stirlingtools/stirling-pdf"
|
||||||
local security_suffix=""
|
local security_suffix=""
|
||||||
local docker_compose_base="./exampleYmlFiles/docker-compose-latest"
|
local docker_compose_base="./exampleYmlFiles/docker-compose-latest"
|
||||||
local compose_suffix=".yml"
|
local compose_suffix=".yml"
|
||||||
|
|||||||
Reference in New Issue
Block a user