mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2026-09-03 13:20:08 +03:00
Compare commits
98
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1a8e47898e | ||
|
|
34316ec6c9 | ||
|
|
d7e590f24e | ||
|
|
49a10a3865 | ||
|
|
190178a471 | ||
|
|
8e8b417f5e | ||
|
|
a57373b968 | ||
|
|
7dad484aa7 | ||
|
|
cfdb6eaa1e | ||
|
|
8a367aab54 | ||
|
|
f3fd85d777 | ||
|
|
9d723eae69 | ||
|
|
494ef801a2 | ||
|
|
e8af4f6b35 | ||
|
|
c25985e49e | ||
|
|
316be5eac5 | ||
|
|
11d23a2d43 | ||
|
|
1898df0df9 | ||
|
|
da359d329d | ||
|
|
bd13f6bf57 | ||
|
|
87c63efcec | ||
|
|
5caec41d96 | ||
|
|
d558bb5fac | ||
|
|
cd1fc682ab | ||
|
|
b9cf7e7820 | ||
|
|
94e8f603ff | ||
|
|
74609e54fe | ||
|
|
003285506f | ||
|
|
6d3b08d9b6 | ||
|
|
295e682e03 | ||
|
|
1a3e8e7ecf | ||
|
|
96aa43860b | ||
|
|
6dbaff5342 | ||
|
|
2f2f966ee9 | ||
|
|
3b28b398d9 | ||
|
|
eecc410b77 | ||
|
|
62c929b89b | ||
|
|
7c9769f5b9 | ||
|
|
cf771075f4 | ||
|
|
a7d5c80188 | ||
|
|
5b20f11e20 | ||
|
|
442b373ff4 | ||
|
|
e142af2863 | ||
|
|
581bafbd37 | ||
|
|
2cac8e8edf | ||
|
|
0002043266 | ||
|
|
00f0194b32 | ||
|
|
1c8b20b6e9 | ||
|
|
1eee5c9a11 | ||
|
|
68d59fd377 | ||
|
|
47ccb6a6ed | ||
|
|
3d26b054f1 | ||
|
|
9360a36c31 | ||
|
|
d64c56a897 | ||
|
|
ca423f9646 | ||
|
|
9b8091a630 | ||
|
|
4aac124e76 | ||
|
|
6d7f76353e | ||
|
|
16e028a8ef | ||
|
|
95b3e22229 | ||
|
|
4b70ef1298 | ||
|
|
1cc803545a | ||
|
|
fe9d2367d5 | ||
|
|
42d7664e25 | ||
|
|
bbd658d3b8 | ||
|
|
ed61c71db7 | ||
|
|
73deece29e | ||
|
|
55ebf9ebd0 | ||
|
|
e6f4cfb318 | ||
|
|
895bcebc7b | ||
|
|
61f5221c58 | ||
|
|
888bac9408 | ||
|
|
23d86deae7 | ||
|
|
7d9c0b0298 | ||
|
|
949ffa01ad | ||
|
|
a33e51351b | ||
|
|
a6706fcb0c | ||
|
|
d06cbcaa91 | ||
|
|
cd2b82d614 | ||
|
|
eada9e43ec | ||
|
|
d1cb3f0b30 | ||
|
|
857b2b5c53 | ||
|
|
d74c3f4598 | ||
|
|
f1cb8ce5b3 | ||
|
|
8f32082145 | ||
|
|
c1b7911518 | ||
|
|
acbebd67a3 | ||
|
|
4c17c520d7 | ||
|
|
1468df3e21 | ||
|
|
7e60eb40b4 | ||
|
|
129e4d00e9 | ||
|
|
8149cb157c | ||
|
|
22db7bec76 | ||
|
|
ecf30d1028 | ||
|
|
0ea4410dd3 | ||
|
|
8eeb4c148c | ||
|
|
adf6feea27 | ||
|
|
299e0b2475 |
@@ -11,9 +11,12 @@
|
||||
"Bash(npm test:*)",
|
||||
"Bash(ls:*)",
|
||||
"Bash(npx tsc:*)",
|
||||
"Bash(echo)",
|
||||
"Bash(rm:*)"
|
||||
"Bash(node:*)",
|
||||
"Bash(npm run dev:*)",
|
||||
"Bash(sed:*)",
|
||||
"Bash(npm run typecheck:*)"
|
||||
],
|
||||
"deny": []
|
||||
"deny": [],
|
||||
"defaultMode": "acceptEdits"
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -24,7 +24,7 @@ indent_size = 2
|
||||
insert_final_newline = false
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[{*.js,*.jsx,*.ts,*.tsx}]
|
||||
[{*.js,*.jsx,*.mjs,*.ts,*.tsx}]
|
||||
indent_size = 2
|
||||
|
||||
[*.css]
|
||||
|
||||
@@ -46,6 +46,9 @@ labels:
|
||||
- label: 'API'
|
||||
title: '.*openapi.*|.*swagger.*|.*api.*'
|
||||
|
||||
- label: 'v2'
|
||||
base-branch: 'V2'
|
||||
|
||||
- label: 'Translation'
|
||||
files:
|
||||
- 'app/core/src/main/resources/messages_[a-zA-Z_]{2}_[a-zA-Z_]{2,7}.properties'
|
||||
@@ -62,6 +65,7 @@ labels:
|
||||
- 'app/core/src/main/java/stirling/software/SPDF/controller/web/.*'
|
||||
- 'app/core/src/main/java/stirling/software/SPDF/UI/.*'
|
||||
- 'app/proprietary/src/main/java/stirling/software/proprietary/security/controller/web/.*'
|
||||
- 'frontend/**'
|
||||
|
||||
- label: 'Java'
|
||||
files:
|
||||
@@ -120,6 +124,7 @@ labels:
|
||||
- 'scripts/installFonts.sh'
|
||||
- 'test.sh'
|
||||
- 'test2.sh'
|
||||
- 'docker/**'
|
||||
|
||||
- label: 'Devtools'
|
||||
files:
|
||||
@@ -131,7 +136,6 @@ labels:
|
||||
- '.github/workflows/pre_commit.yml'
|
||||
- 'devGuide/.*'
|
||||
- 'devTools/.*'
|
||||
- 'devTools/.*'
|
||||
|
||||
- label: 'Test'
|
||||
files:
|
||||
|
||||
@@ -83,6 +83,7 @@
|
||||
color: "DEDEDE"
|
||||
- name: "v2"
|
||||
color: "FFFF00"
|
||||
description: "Issues or pull requests related to the v2 branch"
|
||||
- name: "wontfix"
|
||||
description: "This will not be worked on"
|
||||
color: "FFFFFF"
|
||||
|
||||
@@ -318,7 +318,8 @@ jobs:
|
||||
SYSTEM_MAXFILESIZE: "100"
|
||||
METRICS_ENABLED: "true"
|
||||
SYSTEM_GOOGLEVISIBILITY: "false"
|
||||
SWAGGER_SERVER_URL: "http://${{ secrets.VPS_HOST }}:${V2_PORT}"
|
||||
SWAGGER_SERVER_URL: "https://${V2_PORT}.ssl.stirlingpdf.cloud"
|
||||
baseUrl: "https://${V2_PORT}.ssl.stirlingpdf.cloud"
|
||||
restart: on-failure:5
|
||||
|
||||
stirling-pdf-v2-frontend:
|
||||
|
||||
@@ -47,7 +47,7 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout PR
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
|
||||
- name: Setup GitHub App Bot
|
||||
if: github.actor != 'dependabot[bot]'
|
||||
@@ -158,7 +158,7 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout PR
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
|
||||
- name: Setup GitHub App Bot
|
||||
if: github.actor != 'dependabot[bot]'
|
||||
@@ -170,7 +170,7 @@ jobs:
|
||||
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
|
||||
|
||||
- name: Checkout PR
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
with:
|
||||
repository: ${{ needs.check-comment.outputs.pr_repository }}
|
||||
ref: ${{ needs.check-comment.outputs.pr_ref }}
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout PR
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
|
||||
- name: Setup GitHub App Bot
|
||||
if: github.actor != 'dependabot[bot]'
|
||||
|
||||
@@ -23,7 +23,7 @@ jobs:
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -87,7 +87,7 @@ jobs:
|
||||
- name: AI PR Title Analysis
|
||||
if: steps.actor.outputs.is_repo_dev == 'true'
|
||||
id: ai-title-analysis
|
||||
uses: actions/ai-inference@0cbed4a10641c75090de5968e66d70eb4660f751 # v1.2.7
|
||||
uses: actions/ai-inference@b81b2afb8390ee6839b494a404766bef6493c7d9 # v1.2.8
|
||||
with:
|
||||
model: openai/gpt-4o
|
||||
system-prompt-file: ".github/config/system-prompt.txt"
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
|
||||
- name: Setup GitHub App Bot
|
||||
id: setup-bot
|
||||
|
||||
+19
-12
@@ -31,8 +31,7 @@ jobs:
|
||||
project: ${{ steps.changes.outputs.project }}
|
||||
openapi: ${{ steps.changes.outputs.openapi }}
|
||||
steps:
|
||||
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/checkout@v4.3.0
|
||||
|
||||
- name: Check for file changes
|
||||
uses: dorny/paths-filter@v3.0.2
|
||||
@@ -56,14 +55,15 @@ jobs:
|
||||
with:
|
||||
egress-policy: audit
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4.2.2
|
||||
uses: actions/checkout@v4.3.0
|
||||
|
||||
- name: Set up JDK ${{ matrix.jdk-version }}
|
||||
uses: actions/setup-java@v4.7.1
|
||||
with:
|
||||
java-version: ${{ matrix.jdk-version }}
|
||||
distribution: "temurin"
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v4.4.1
|
||||
uses: gradle/actions/setup-gradle@v4.4.2
|
||||
with:
|
||||
gradle-version: 8.14
|
||||
- name: Build with Gradle and spring security ${{ matrix.spring-security }}
|
||||
@@ -106,19 +106,24 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@v2.12.2
|
||||
uses: step-security/harden-runner@v2.13.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
- uses: actions/checkout@v4.2.2
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4.3.0
|
||||
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v4.7.1
|
||||
with:
|
||||
java-version: "17"
|
||||
distribution: "temurin"
|
||||
- uses: gradle/actions/setup-gradle@v4.4.1
|
||||
- uses: gradle/actions/setup-gradle@v4.4.2
|
||||
- name: Generate OpenAPI documentation
|
||||
run: ./gradlew :stirling-pdf:generateOpenApiDocs
|
||||
|
||||
env:
|
||||
DISABLE_ADDITIONAL_FEATURES: true
|
||||
|
||||
- name: Upload OpenAPI Documentation
|
||||
uses: actions/upload-artifact@v4.6.2
|
||||
with:
|
||||
@@ -142,6 +147,8 @@ jobs:
|
||||
cache-dependency-path: frontend/package-lock.json
|
||||
- name: Install frontend dependencies
|
||||
run: cd frontend && npm ci
|
||||
- name: Lint frontend
|
||||
run: cd frontend && npm run lint
|
||||
- name: Build frontend
|
||||
run: cd frontend && npm run build
|
||||
- name: Run frontend tests
|
||||
@@ -163,7 +170,7 @@ jobs:
|
||||
with:
|
||||
egress-policy: audit
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4.2.2
|
||||
uses: actions/checkout@v4.3.0
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v4.7.1
|
||||
with:
|
||||
@@ -205,7 +212,7 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
|
||||
- name: Set up Java 17
|
||||
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
|
||||
@@ -254,7 +261,7 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
|
||||
@@ -263,7 +270,7 @@ jobs:
|
||||
distribution: "temurin"
|
||||
|
||||
- name: Set up Gradle
|
||||
uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
|
||||
uses: gradle/actions/setup-gradle@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2
|
||||
with:
|
||||
gradle-version: 8.14
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout main branch first
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
|
||||
- name: Setup GitHub App Bot
|
||||
id: setup-bot
|
||||
|
||||
@@ -22,6 +22,6 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: "Checkout Repository"
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
- name: "Dependency Review"
|
||||
uses: actions/dependency-review-action@da24556b548a50705dd671f47852072ea4c105d9 # v4.7.1
|
||||
|
||||
@@ -151,7 +151,8 @@ jobs:
|
||||
SYSTEM_MAXFILESIZE: "100"
|
||||
METRICS_ENABLED: "true"
|
||||
SYSTEM_GOOGLEVISIBILITY: "false"
|
||||
SWAGGER_SERVER_URL: "http://${{ secrets.VPS_HOST }}:3000"
|
||||
SWAGGER_SERVER_URL: "https://demo.stirlingpdf.cloud"
|
||||
baseUrl: "https://demo.stirlingpdf.cloud"
|
||||
restart: on-failure:5
|
||||
|
||||
frontend:
|
||||
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Check out code
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -54,7 +54,7 @@ jobs:
|
||||
distribution: "temurin"
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
|
||||
uses: gradle/actions/setup-gradle@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2
|
||||
|
||||
- name: Check licenses for compatibility
|
||||
run: ./gradlew clean checkLicense
|
||||
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Check out the repository
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
|
||||
- name: Run Labeler
|
||||
uses: crazy-max/ghaction-github-labeler@24d110aa46a59976b8a7f35518cb7f14f434c916 # v5.3.0
|
||||
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
|
||||
@@ -64,7 +64,7 @@ jobs:
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
|
||||
- name: Set up JDK 21
|
||||
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
|
||||
@@ -72,7 +72,7 @@ jobs:
|
||||
java-version: "21"
|
||||
distribution: "temurin"
|
||||
|
||||
- uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
|
||||
- uses: gradle/actions/setup-gradle@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2
|
||||
with:
|
||||
gradle-version: 8.14
|
||||
|
||||
@@ -115,7 +115,7 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Download build artifacts
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
||||
with:
|
||||
name: stirling-${{ matrix.file_suffix }}binaries
|
||||
|
||||
@@ -152,7 +152,7 @@ jobs:
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
|
||||
- name: Set up JDK 21
|
||||
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
|
||||
@@ -160,7 +160,7 @@ jobs:
|
||||
java-version: "21"
|
||||
distribution: "temurin"
|
||||
|
||||
- uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
|
||||
- uses: gradle/actions/setup-gradle@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2
|
||||
with:
|
||||
gradle-version: 8.14
|
||||
|
||||
@@ -243,7 +243,7 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Download build artifacts
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
||||
with:
|
||||
name: ${{ matrix.platform }}binaries
|
||||
|
||||
@@ -306,7 +306,7 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Download signed artifacts
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
||||
- name: Display structure of downloaded files
|
||||
run: ls -R
|
||||
- name: Upload binaries, attestations and signatures to Release and create GitHub Release
|
||||
|
||||
@@ -22,7 +22,7 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
java-version: "17"
|
||||
distribution: "temurin"
|
||||
|
||||
- uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
|
||||
- uses: gradle/actions/setup-gradle@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2
|
||||
with:
|
||||
gradle-version: 8.14
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ jobs:
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
java-version: "17"
|
||||
distribution: "temurin"
|
||||
|
||||
- uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
|
||||
- uses: gradle/actions/setup-gradle@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2
|
||||
with:
|
||||
gradle-version: 8.14
|
||||
|
||||
@@ -88,7 +88,7 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Download build artifacts
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
||||
with:
|
||||
name: binaries${{ matrix.file_suffix }}
|
||||
- name: Display structure of downloaded files
|
||||
@@ -166,7 +166,7 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Download signed artifacts
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
||||
with:
|
||||
name: signed${{ matrix.file_suffix }}
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: "Checkout code"
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -74,6 +74,6 @@ jobs:
|
||||
|
||||
# Upload the results to GitHub's code scanning dashboard.
|
||||
- name: "Upload to code-scanning"
|
||||
uses: github/codeql-action/upload-sarif@51f77329afa6477de8c49fc9c7046c15b9a4e79d # v3.29.5
|
||||
uses: github/codeql-action/upload-sarif@76621b61decf072c1cee8dd1ce2d2a82d33c17ed # v3.29.5
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
|
||||
@@ -34,12 +34,12 @@ jobs:
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
|
||||
uses: gradle/actions/setup-gradle@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2
|
||||
|
||||
- name: Build and analyze with Gradle
|
||||
env:
|
||||
|
||||
@@ -30,7 +30,7 @@ jobs:
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
java-version: "17"
|
||||
distribution: "temurin"
|
||||
|
||||
- uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
|
||||
- uses: gradle/actions/setup-gradle@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2
|
||||
|
||||
- name: Generate Swagger documentation
|
||||
run: ./gradlew :stirling-pdf:generateOpenApiDocs
|
||||
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
|
||||
- name: Setup GitHub App Bot
|
||||
id: setup-bot
|
||||
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
distribution: 'temurin'
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
|
||||
uses: gradle/actions/setup-gradle@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2
|
||||
with:
|
||||
gradle-version: 8.14
|
||||
|
||||
@@ -126,7 +126,7 @@ jobs:
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
|
||||
Vendored
+1
@@ -19,5 +19,6 @@
|
||||
"yzhang.markdown-all-in-one", // Markdown All-in-One extension for enhanced Markdown editing
|
||||
"stylelint.vscode-stylelint", // Stylelint extension for CSS and SCSS linting
|
||||
"redhat.vscode-yaml", // YAML extension for Visual Studio Code
|
||||
"dbaeumer.vscode-eslint", // ESLint extension for TypeScript linting
|
||||
]
|
||||
}
|
||||
|
||||
Vendored
+4
-1
@@ -139,5 +139,8 @@
|
||||
"app/core/src/main/java",
|
||||
"app/common/src/main/java",
|
||||
"app/proprietary/src/main/java"
|
||||
]
|
||||
],
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "vscode.typescript-language-features"
|
||||
}
|
||||
}
|
||||
|
||||
+300
@@ -0,0 +1,300 @@
|
||||
# Adding New React Tools to Stirling PDF
|
||||
|
||||
This guide covers how to add new PDF tools to the React frontend, either by migrating existing Thymeleaf templates or creating entirely new tools.
|
||||
|
||||
## Overview
|
||||
|
||||
When adding tools, follow this systematic approach using the established patterns and architecture.
|
||||
|
||||
## 1. Create Tool Structure
|
||||
|
||||
Create these files in the correct directories:
|
||||
```
|
||||
frontend/src/hooks/tools/[toolName]/
|
||||
├── use[ToolName]Parameters.ts # Parameter definitions and validation
|
||||
└── use[ToolName]Operation.ts # Tool operation logic using useToolOperation
|
||||
|
||||
frontend/src/components/tools/[toolName]/
|
||||
└── [ToolName]Settings.tsx # Settings UI component (if needed)
|
||||
|
||||
frontend/src/tools/
|
||||
└── [ToolName].tsx # Main tool component
|
||||
```
|
||||
|
||||
## 2. Implementation Pattern
|
||||
|
||||
Use `useBaseTool` for simplified hook management. This is the recommended approach for all new tools:
|
||||
|
||||
**Parameters Hook** (`use[ToolName]Parameters.ts`):
|
||||
```typescript
|
||||
import { BaseParameters } from '../../../types/parameters';
|
||||
import { useBaseParameters, BaseParametersHook } from '../shared/useBaseParameters';
|
||||
|
||||
export interface [ToolName]Parameters extends BaseParameters {
|
||||
// Define your tool-specific parameters here
|
||||
someOption: boolean;
|
||||
}
|
||||
|
||||
export const defaultParameters: [ToolName]Parameters = {
|
||||
someOption: false,
|
||||
};
|
||||
|
||||
export const use[ToolName]Parameters = (): BaseParametersHook<[ToolName]Parameters> => {
|
||||
return useBaseParameters({
|
||||
defaultParameters,
|
||||
endpointName: 'your-endpoint-name',
|
||||
validateFn: (params) => true, // Add validation logic
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||
**Operation Hook** (`use[ToolName]Operation.ts`):
|
||||
```typescript
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ToolType, useToolOperation } from '../shared/useToolOperation';
|
||||
import { createStandardErrorHandler } from '../../../utils/toolErrorHandler';
|
||||
|
||||
export const build[ToolName]FormData = (parameters: [ToolName]Parameters, file: File): FormData => {
|
||||
const formData = new FormData();
|
||||
formData.append('fileInput', file);
|
||||
// Add parameters to formData
|
||||
return formData;
|
||||
};
|
||||
|
||||
export const [toolName]OperationConfig = {
|
||||
toolType: ToolType.singleFile, // or ToolType.multiFile (buildFormData's file parameter will need to be updated)
|
||||
buildFormData: build[ToolName]FormData,
|
||||
operationType: '[toolName]',
|
||||
endpoint: '/api/v1/category/endpoint-name',
|
||||
filePrefix: 'processed_', // Will be overridden with translation
|
||||
defaultParameters,
|
||||
} as const;
|
||||
|
||||
export const use[ToolName]Operation = () => {
|
||||
const { t } = useTranslation();
|
||||
return useToolOperation({
|
||||
...[toolName]OperationConfig,
|
||||
filePrefix: t('[toolName].filenamePrefix', 'processed') + '_',
|
||||
getErrorMessage: createStandardErrorHandler(t('[toolName].error.failed', 'Operation failed'))
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||
**Main Component** (`[ToolName].tsx`):
|
||||
```typescript
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { createToolFlow } from "../components/tools/shared/createToolFlow";
|
||||
import { use[ToolName]Parameters } from "../hooks/tools/[toolName]/use[ToolName]Parameters";
|
||||
import { use[ToolName]Operation } from "../hooks/tools/[toolName]/use[ToolName]Operation";
|
||||
import { useBaseTool } from "../hooks/tools/shared/useBaseTool";
|
||||
import { BaseToolProps, ToolComponent } from "../types/tool";
|
||||
|
||||
const [ToolName] = (props: BaseToolProps) => {
|
||||
const { t } = useTranslation();
|
||||
const base = useBaseTool('[toolName]', use[ToolName]Parameters, use[ToolName]Operation, props);
|
||||
|
||||
return createToolFlow({
|
||||
files: {
|
||||
selectedFiles: base.selectedFiles,
|
||||
isCollapsed: base.hasResults,
|
||||
placeholder: t("[toolName].files.placeholder", "Select files to get started"),
|
||||
},
|
||||
steps: [
|
||||
// Add settings steps if needed
|
||||
],
|
||||
executeButton: {
|
||||
text: t("[toolName].submit", "Process"),
|
||||
isVisible: !base.hasResults,
|
||||
loadingText: t("loading"),
|
||||
onClick: base.handleExecute,
|
||||
disabled: !base.params.validateParameters() || !base.hasFiles || !base.endpointEnabled,
|
||||
},
|
||||
review: {
|
||||
isVisible: base.hasResults,
|
||||
operation: base.operation,
|
||||
title: t("[toolName].results.title", "Results"),
|
||||
onFileClick: base.handleThumbnailClick,
|
||||
onUndo: base.handleUndo,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
[ToolName].tool = () => use[ToolName]Operation;
|
||||
export default [ToolName] as ToolComponent;
|
||||
```
|
||||
|
||||
**Note**: Some existing tools (like AddPassword, Compress) use a legacy pattern with manual hook management. **Always use the Modern Pattern above for new tools** - it's cleaner, more maintainable, and includes automation support.
|
||||
|
||||
## 3. Register Tool in System
|
||||
Update these files to register your new tool:
|
||||
|
||||
**Tool Registry** (`frontend/src/data/useTranslatedToolRegistry.tsx`):
|
||||
1. Add imports at the top:
|
||||
```typescript
|
||||
import [ToolName] from "../tools/[ToolName]";
|
||||
import { [toolName]OperationConfig } from "../hooks/tools/[toolName]/use[ToolName]Operation";
|
||||
import [ToolName]Settings from "../components/tools/[toolName]/[ToolName]Settings";
|
||||
```
|
||||
|
||||
2. Add tool entry in the `allTools` object:
|
||||
```typescript
|
||||
[toolName]: {
|
||||
icon: <LocalIcon icon="your-icon-name" width="1.5rem" height="1.5rem" />,
|
||||
name: t("home.[toolName].title", "Tool Name"),
|
||||
component: [ToolName],
|
||||
description: t("home.[toolName].desc", "Tool description"),
|
||||
categoryId: ToolCategoryId.STANDARD_TOOLS, // or appropriate category
|
||||
subcategoryId: SubcategoryId.APPROPRIATE_SUBCATEGORY,
|
||||
maxFiles: -1, // or specific number
|
||||
endpoints: ["endpoint-name"],
|
||||
operationConfig: [toolName]OperationConfig,
|
||||
settingsComponent: [ToolName]Settings, // if settings exist
|
||||
},
|
||||
```
|
||||
|
||||
## 4. Add Tooltips (Optional but Recommended)
|
||||
Create user-friendly tooltips to help non-technical users understand your tool. **Use simple, clear language - avoid technical jargon:**
|
||||
|
||||
**Tooltip Hook** (`frontend/src/components/tooltips/use[ToolName]Tips.ts`):
|
||||
```typescript
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { TooltipContent } from '../../types/tips';
|
||||
|
||||
export const use[ToolName]Tips = (): TooltipContent => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return {
|
||||
header: {
|
||||
title: t("[toolName].tooltip.header.title", "Tool Overview")
|
||||
},
|
||||
tips: [
|
||||
{
|
||||
title: t("[toolName].tooltip.description.title", "What does this tool do?"),
|
||||
description: t("[toolName].tooltip.description.text", "Simple explanation in everyday language that non-technical users can understand."),
|
||||
bullets: [
|
||||
t("[toolName].tooltip.description.bullet1", "Easy-to-understand benefit 1"),
|
||||
t("[toolName].tooltip.description.bullet2", "Easy-to-understand benefit 2")
|
||||
]
|
||||
}
|
||||
// Add more tip sections as needed
|
||||
]
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
**Add tooltip to your main component:**
|
||||
```typescript
|
||||
import { use[ToolName]Tips } from "../components/tooltips/use[ToolName]Tips";
|
||||
|
||||
const [ToolName] = (props: BaseToolProps) => {
|
||||
const tips = use[ToolName]Tips();
|
||||
|
||||
// In your steps array:
|
||||
steps: [
|
||||
{
|
||||
title: t("[toolName].steps.settings", "Settings"),
|
||||
tooltip: tips, // Add this line
|
||||
content: <[ToolName]Settings ... />
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## 5. Add Translations
|
||||
Update translation files. **Important: Only update `en-GB` files** - other languages are handled separately.
|
||||
|
||||
**File to update:** `frontend/public/locales/en-GB/translation.json`
|
||||
|
||||
**Required Translation Keys**:
|
||||
```json
|
||||
{
|
||||
"home": {
|
||||
"[toolName]": {
|
||||
"title": "Tool Name",
|
||||
"desc": "Tool description"
|
||||
}
|
||||
},
|
||||
"[toolName]": {
|
||||
"title": "Tool Name",
|
||||
"submit": "Process",
|
||||
"filenamePrefix": "processed",
|
||||
"files": {
|
||||
"placeholder": "Select files to get started"
|
||||
},
|
||||
"steps": {
|
||||
"settings": "Settings"
|
||||
},
|
||||
"options": {
|
||||
"title": "Tool Options",
|
||||
"someOption": "Option Label",
|
||||
"someOption.desc": "Option description",
|
||||
"note": "General information about the tool."
|
||||
},
|
||||
"results": {
|
||||
"title": "Results"
|
||||
},
|
||||
"error": {
|
||||
"failed": "Operation failed"
|
||||
},
|
||||
"tooltip": {
|
||||
"header": {
|
||||
"title": "Tool Overview"
|
||||
},
|
||||
"description": {
|
||||
"title": "What does this tool do?",
|
||||
"text": "Simple explanation in everyday language",
|
||||
"bullet1": "Easy-to-understand benefit 1",
|
||||
"bullet2": "Easy-to-understand benefit 2"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Translation Notes:**
|
||||
- **Only update `en-GB/translation.json`** - other locale files are managed separately
|
||||
- Use descriptive keys that match your component's `t()` calls
|
||||
- Include tooltip translations if you created tooltip hooks
|
||||
- Add `options.*` keys if your tool has settings with descriptions
|
||||
|
||||
**Tooltip Writing Guidelines:**
|
||||
- **Use simple, everyday language** - avoid technical terms like "converts interactive elements"
|
||||
- **Focus on benefits** - explain what the user gains, not how it works internally
|
||||
- **Use concrete examples** - "text boxes become regular text" vs "form fields are flattened"
|
||||
- **Answer user questions** - "What does this do?", "When should I use this?", "What's this option for?"
|
||||
- **Keep descriptions concise** - 1-2 sentences maximum per section
|
||||
- **Use bullet points** for multiple benefits or features
|
||||
|
||||
## 6. Migration from Thymeleaf
|
||||
When migrating existing Thymeleaf templates:
|
||||
|
||||
1. **Identify Form Parameters**: Look at the original `<form>` inputs to determine parameter structure
|
||||
2. **Extract Translation Keys**: Find `#{key.name}` references and add them to JSON translations (For many tools these translations will already exist but some parts will be missing)
|
||||
3. **Map API Endpoint**: Note the `th:action` URL for the operation hook
|
||||
4. **Preserve Functionality**: Ensure all original form behaviour is replicated which is applicable to V2 react UI
|
||||
|
||||
## 7. Testing Your Tool
|
||||
- Verify tool appears in UI with correct icon and description
|
||||
- Test with various file sizes and types
|
||||
- Confirm translations work
|
||||
- Check error handling
|
||||
- Test undo functionality
|
||||
- Verify results display correctly
|
||||
|
||||
## Tool Development Patterns
|
||||
|
||||
### Three Tool Patterns:
|
||||
|
||||
**Pattern 1: Single-File Tools** (Individual processing)
|
||||
- Backend processes one file per API call
|
||||
- Set `multiFileEndpoint: false`
|
||||
- Examples: Compress, Rotate
|
||||
|
||||
**Pattern 2: Multi-File Tools** (Batch processing)
|
||||
- Backend accepts `MultipartFile[]` arrays in single API call
|
||||
- Set `multiFileEndpoint: true`
|
||||
- Examples: Split, Merge, Overlay
|
||||
|
||||
**Pattern 3: Complex Tools** (Custom processing)
|
||||
- Tools with complex routing logic or non-standard processing
|
||||
- Provide `customProcessor` for full control
|
||||
- Examples: Convert, OCR
|
||||
@@ -208,6 +208,7 @@ return useToolOperation({
|
||||
- **Tool Development**: New tools should follow `useToolOperation` hook pattern (see `useCompressOperation.ts`)
|
||||
- **Performance Target**: Must handle PDFs up to 100GB+ without browser crashes
|
||||
- **Preview System**: Tools can preview results without polluting main file context (see Split tool implementation)
|
||||
- **Adding Tools**: See `ADDING_TOOLS.md` for complete guide to creating new PDF tools
|
||||
|
||||
## Communication Style
|
||||
- Be direct and to the point
|
||||
|
||||
+7
-8
@@ -7,7 +7,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
|
||||
/**
|
||||
* Shortcut for a POST endpoint that is executed through the Stirling "auto-job" framework.
|
||||
* Shortcut for a POST endpoint that is executed through the Stirling "auto‑job" framework.
|
||||
*
|
||||
* <p>Behaviour notes:
|
||||
*
|
||||
@@ -16,7 +16,7 @@ import org.springframework.web.bind.annotation.RequestMethod;
|
||||
* multipart/form-data} unless you override {@link #consumes()}.
|
||||
* <li>When the client supplies {@code ?async=true} the call is handed to {@link
|
||||
* stirling.software.common.service.JobExecutorService JobExecutorService} where it may be
|
||||
* queued, retried, tracked and subject to time-outs. For synchronous (default) invocations
|
||||
* queued, retried, tracked and subject to time‑outs. For synchronous (default) invocations
|
||||
* these advanced options are ignored.
|
||||
* <li>Progress information (see {@link #trackProgress()}) is stored in {@link
|
||||
* stirling.software.common.service.TaskManager TaskManager} and can be polled via <code>
|
||||
@@ -48,8 +48,8 @@ public @interface AutoJobPostMapping {
|
||||
long timeout() default -1;
|
||||
|
||||
/**
|
||||
* Total number of attempts (initial + retries). Must be at least 1. Retries are executed
|
||||
* with exponential back-off.
|
||||
* Total number of attempts (initial + retries). Must be at least 1. Retries are executed
|
||||
* with exponential back‑off.
|
||||
*
|
||||
* <p>Only honoured when {@code async=true}.
|
||||
*/
|
||||
@@ -71,9 +71,8 @@ public @interface AutoJobPostMapping {
|
||||
boolean queueable() default false;
|
||||
|
||||
/**
|
||||
* Credit cost for this endpoint in the API credit system. Also used as relative resource weight
|
||||
* (1-100) by the scheduler to prioritise / throttle jobs. Values below 1 are clamped to 1,
|
||||
* values above 100 to 100.
|
||||
* Relative resource weight (1–100) used by the scheduler to prioritise / throttle jobs. Values
|
||||
* below 1 are clamped to 1, values above 100 to 100.
|
||||
*/
|
||||
int resourceWeight() default 1;
|
||||
int resourceWeight() default 50;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Properties;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
@@ -48,6 +49,14 @@ public class AppConfig {
|
||||
@Value("${server.port:8080}")
|
||||
private String serverPort;
|
||||
|
||||
@Value("${v2}")
|
||||
public boolean v2Enabled;
|
||||
|
||||
@Bean
|
||||
public boolean v2Enabled() {
|
||||
return v2Enabled;
|
||||
}
|
||||
|
||||
/* Commented out Thymeleaf template engine bean - to be removed when frontend migration is complete
|
||||
@Bean
|
||||
@ConditionalOnProperty(name = "system.customHTMLFiles", havingValue = "true")
|
||||
@@ -119,7 +128,7 @@ public class AppConfig {
|
||||
public boolean rateLimit() {
|
||||
String rateLimit = System.getProperty("rateLimit");
|
||||
if (rateLimit == null) rateLimit = System.getenv("rateLimit");
|
||||
return (rateLimit != null) ? Boolean.valueOf(rateLimit) : false;
|
||||
return Boolean.parseBoolean(rateLimit);
|
||||
}
|
||||
|
||||
@Bean(name = "RunningInDocker")
|
||||
@@ -139,8 +148,8 @@ public class AppConfig {
|
||||
if (!Files.exists(mountInfo)) {
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
return Files.lines(mountInfo).anyMatch(line -> line.contains(" /configs "));
|
||||
try (Stream<String> lines = Files.lines(mountInfo)) {
|
||||
return lines.anyMatch(line -> line.contains(" /configs "));
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
+6
@@ -25,6 +25,7 @@ public class InstallationPathConfig {
|
||||
private static final String STATIC_PATH;
|
||||
private static final String TEMPLATES_PATH;
|
||||
private static final String SIGNATURES_PATH;
|
||||
private static final String PRIVATE_KEY_PATH;
|
||||
|
||||
static {
|
||||
BASE_PATH = initializeBasePath();
|
||||
@@ -45,6 +46,7 @@ public class InstallationPathConfig {
|
||||
STATIC_PATH = CUSTOM_FILES_PATH + "static" + File.separator;
|
||||
TEMPLATES_PATH = CUSTOM_FILES_PATH + "templates" + File.separator;
|
||||
SIGNATURES_PATH = CUSTOM_FILES_PATH + "signatures" + File.separator;
|
||||
PRIVATE_KEY_PATH = CONFIG_PATH + "db" + File.separator + "keys" + File.separator;
|
||||
}
|
||||
|
||||
private static String initializeBasePath() {
|
||||
@@ -120,4 +122,8 @@ public class InstallationPathConfig {
|
||||
public static String getSignaturesPath() {
|
||||
return SIGNATURES_PATH;
|
||||
}
|
||||
|
||||
public static String getPrivateKeyPath() {
|
||||
return PRIVATE_KEY_PATH;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,6 +119,7 @@ public class ApplicationProperties {
|
||||
private long loginResetTimeMinutes;
|
||||
private String loginMethod = "all";
|
||||
private String customGlobalAPIKey;
|
||||
private Jwt jwt = new Jwt();
|
||||
|
||||
public Boolean isAltLogin() {
|
||||
return saml2.getEnabled() || oauth2.getEnabled();
|
||||
@@ -298,6 +299,15 @@ public class ApplicationProperties {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class Jwt {
|
||||
private boolean enableKeystore = true;
|
||||
private boolean enableKeyRotation = false;
|
||||
private boolean enableKeyCleanup = true;
|
||||
private int keyRetentionDays = 7;
|
||||
private boolean secureCookie;
|
||||
}
|
||||
}
|
||||
|
||||
@Data
|
||||
@@ -362,7 +372,8 @@ public class ApplicationProperties {
|
||||
public String getBaseTmpDir() {
|
||||
return baseTmpDir != null && !baseTmpDir.isEmpty()
|
||||
? baseTmpDir
|
||||
: java.lang.System.getProperty("java.io.tmpdir") + "/stirling-pdf";
|
||||
: java.lang.System.getProperty("java.io.tmpdir").replaceAll("/+$", "")
|
||||
+ "/stirling-pdf";
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
|
||||
@@ -10,25 +10,21 @@ import lombok.RequiredArgsConstructor;
|
||||
@RequiredArgsConstructor
|
||||
public enum Role {
|
||||
|
||||
// System-wide administrator - can manage all organizations
|
||||
SYSTEM_ADMIN(
|
||||
"ROLE_SYSTEM_ADMIN",
|
||||
Integer.MAX_VALUE,
|
||||
Integer.MAX_VALUE,
|
||||
"adminUserSettings.systemAdmin"),
|
||||
|
||||
// Organization administrator - can manage their organization and all its teams
|
||||
ORG_ADMIN("ROLE_ORG_ADMIN", Integer.MAX_VALUE, Integer.MAX_VALUE, "adminUserSettings.orgAdmin"),
|
||||
|
||||
// Team leader - can manage users in their specific team
|
||||
TEAM_LEAD("ROLE_TEAM_LEAD", Integer.MAX_VALUE, Integer.MAX_VALUE, "adminUserSettings.teamLead"),
|
||||
|
||||
// Legacy admin role - equivalent to SYSTEM_ADMIN for backward compatibility
|
||||
// Unlimited access
|
||||
ADMIN("ROLE_ADMIN", Integer.MAX_VALUE, Integer.MAX_VALUE, "adminUserSettings.admin"),
|
||||
|
||||
// Regular user with unlimited access within their team/org
|
||||
// Unlimited access
|
||||
USER("ROLE_USER", Integer.MAX_VALUE, Integer.MAX_VALUE, "adminUserSettings.user"),
|
||||
|
||||
// 40 API calls Per Day, 40 web calls
|
||||
LIMITED_API_USER("ROLE_LIMITED_API_USER", 40, 40, "adminUserSettings.apiUser"),
|
||||
|
||||
// 20 API calls Per Day, 20 web calls
|
||||
EXTRA_LIMITED_API_USER("ROLE_EXTRA_LIMITED_API_USER", 20, 20, "adminUserSettings.extraApiUser"),
|
||||
|
||||
// 0 API calls per day and 20 web calls
|
||||
WEB_ONLY_USER("ROLE_WEB_ONLY_USER", 0, 20, "adminUserSettings.webOnlyUser"),
|
||||
|
||||
INTERNAL_API_USER(
|
||||
"STIRLING-PDF-BACKEND-API-USER",
|
||||
Integer.MAX_VALUE,
|
||||
@@ -67,35 +63,4 @@ public enum Role {
|
||||
}
|
||||
throw new IllegalArgumentException("No Role defined for id: " + roleId);
|
||||
}
|
||||
|
||||
/** Checks if this role can manage users across all organizations */
|
||||
public boolean isSystemAdmin() {
|
||||
return this == SYSTEM_ADMIN || this == ADMIN; // ADMIN for backward compatibility
|
||||
}
|
||||
|
||||
/** Checks if this role can manage an organization and all its teams */
|
||||
public boolean isOrgAdmin() {
|
||||
return isSystemAdmin() || this == ORG_ADMIN;
|
||||
}
|
||||
|
||||
/** Checks if this role can manage a specific team */
|
||||
public boolean isTeamLead() {
|
||||
return isOrgAdmin() || this == TEAM_LEAD;
|
||||
}
|
||||
|
||||
/** Gets the hierarchy level of this role (higher number = more permissions) */
|
||||
public int getHierarchyLevel() {
|
||||
return switch (this) {
|
||||
case SYSTEM_ADMIN, ADMIN -> 4;
|
||||
case ORG_ADMIN -> 3;
|
||||
case TEAM_LEAD -> 2;
|
||||
case USER -> 1;
|
||||
default -> 0; // Limited users
|
||||
};
|
||||
}
|
||||
|
||||
/** Checks if this role has higher or equal authority than another role */
|
||||
public boolean hasAuthorityOver(Role otherRole) {
|
||||
return this.getHierarchyLevel() >= otherRole.getHierarchyLevel();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,8 +14,10 @@ public class RequestUriUtils {
|
||||
|| requestURI.startsWith(contextPath + "/images/")
|
||||
|| requestURI.startsWith(contextPath + "/public/")
|
||||
|| requestURI.startsWith(contextPath + "/pdfjs/")
|
||||
|| requestURI.startsWith(contextPath + "/pdfjs-legacy/")
|
||||
|| requestURI.startsWith(contextPath + "/login")
|
||||
|| requestURI.startsWith(contextPath + "/error")
|
||||
|| requestURI.startsWith(contextPath + "/favicon")
|
||||
|| requestURI.endsWith(".svg")
|
||||
|| requestURI.endsWith(".png")
|
||||
|| requestURI.endsWith(".ico")
|
||||
|
||||
@@ -45,11 +45,6 @@ public class CleanUrlInterceptor implements HandlerInterceptor {
|
||||
|
||||
String queryString = request.getQueryString();
|
||||
if (queryString != null && !queryString.isEmpty()) {
|
||||
// Reuse the requestURI variable from above
|
||||
if (requestURI.contains("/api/")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Map<String, String> allowedParameters = new HashMap<>();
|
||||
|
||||
// Keep only the allowed parameters
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
multipart.enabled=true
|
||||
logging.level.org.springframework=WARN
|
||||
logging.level.org.hibernate=WARN
|
||||
#logging.level.root=DEBUG
|
||||
logging.level.org.eclipse.jetty=WARN
|
||||
#logging.level.org.springframework.security.saml2=TRACE
|
||||
#logging.level.org.springframework.security=DEBUG
|
||||
#logging.level.org.opensaml=DEBUG
|
||||
#logging.level.stirling.software.SPDF.config.security: DEBUG
|
||||
#logging.level.stirling.software.proprietary.security=DEBUG
|
||||
logging.level.com.zaxxer.hikari=WARN
|
||||
spring.jpa.open-in-view=false
|
||||
server.forward-headers-strategy=NATIVE
|
||||
@@ -35,7 +34,6 @@ spring.datasource.driver-class-name=org.h2.Driver
|
||||
spring.datasource.username=sa
|
||||
spring.datasource.password=
|
||||
spring.h2.console.enabled=false
|
||||
spring.h2.console.path=/h2-console
|
||||
spring.jpa.hibernate.ddl-auto=update
|
||||
# Defer datasource initialization to ensure that the database is fully set up
|
||||
# before Hibernate attempts to access it. This is particularly useful when
|
||||
@@ -59,11 +57,5 @@ spring.main.allow-bean-definition-overriding=true
|
||||
# Set up a consistent temporary directory location
|
||||
java.io.tmpdir=${stirling.tempfiles.directory:${java.io.tmpdir}/stirling-pdf}
|
||||
|
||||
# API Credit System Configuration
|
||||
api.credit-system.enabled=true
|
||||
api.credit-system.anonymous.enabled=true
|
||||
api.credit-system.anonymous.monthly-credits=10
|
||||
api.credit-system.anonymous.abuse-threshold=3
|
||||
api.credit-system.exclude-settings=true
|
||||
api.credit-system.exclude-actuator=true
|
||||
api.credit-system.default-credit-cost=1
|
||||
# V2 features
|
||||
v2=false
|
||||
|
||||
@@ -455,6 +455,9 @@ adminUserSettings.confirmChangeUserStatus=هل يجب تعطيل/تمكين ال
|
||||
adminUserSettings.usernameInfo=يمكن أن يحتوي اسم المستخدم فقط على أحرف وأرقام والرموز الخاصة التالية @._+- أو يجب أن يكون عنوان بريد إلكتروني صالح.
|
||||
adminUserSettings.role=الدور
|
||||
adminUserSettings.actions=الإجراءات
|
||||
adminUserSettings.apiUser=مستخدم API محدود
|
||||
adminUserSettings.extraApiUser=مستخدم API محدود إضافي
|
||||
adminUserSettings.webOnlyUser=مستخدم الويب فقط
|
||||
adminUserSettings.demoUser=مستخدم تجريبي (بدون إعدادات مخصصة)
|
||||
adminUserSettings.internalApiUser=مستخدم API داخلي
|
||||
adminUserSettings.forceChange=إجبار المستخدم على تغيير كلمة المرور عند تسجيل الدخول
|
||||
@@ -905,6 +908,7 @@ login.alreadyLoggedIn=لقد تسجل دخولًا إلى
|
||||
login.alreadyLoggedIn2=أجهزة أخرى. يرجى تسجيل الخروج من الأجهزة وحاول مرة أخرى.
|
||||
login.toManySessions=لديك عدة جلسات نشطة
|
||||
login.logoutMessage=You have been logged out.
|
||||
login.invalidInResponseTo=The requested SAML response is invalid or has expired. Please contact the administrator.
|
||||
|
||||
#auto-redact
|
||||
autoRedact.title=حجب تلقائي
|
||||
|
||||
@@ -455,6 +455,9 @@ adminUserSettings.confirmChangeUserStatus=İstifadəçi aktivləşdirilməli/dea
|
||||
adminUserSettings.usernameInfo=İstifadəçi adı sadəcə hərflərdən, rəqəmlərdən və @._+- xüsusi simvollarından ibarət ola bilər və ya düzgün email ünvanı olmalıdır.
|
||||
adminUserSettings.role=Rol
|
||||
adminUserSettings.actions=Fəaliyyətlər
|
||||
adminUserSettings.apiUser=Məhdudlaşdırılmış API İstifadəçisi
|
||||
adminUserSettings.extraApiUser=Əlavə Məhdudlaşdırılmış API İstifadəçisi
|
||||
adminUserSettings.webOnlyUser=Yalnız Veb İstifadəçisi
|
||||
adminUserSettings.demoUser=Demo İstifadəçisi (Fərdi parametrlər yoxdur)
|
||||
adminUserSettings.internalApiUser=Daxili API İstifadəçisi
|
||||
adminUserSettings.forceChange=İstifadəçini giriş zamanı parolu dəyişməyə məcbur et
|
||||
@@ -905,6 +908,7 @@ login.alreadyLoggedIn=Siz artıq daxil olmusunuz
|
||||
login.alreadyLoggedIn2=cihazlar. Zəhmət olmasa, cihazlardan çıxış edin və yenidən cəhd edin.
|
||||
login.toManySessions=Həddindən artıq aktiv sessiyanız var
|
||||
login.logoutMessage=You have been logged out.
|
||||
login.invalidInResponseTo=The requested SAML response is invalid or has expired. Please contact the administrator.
|
||||
|
||||
#auto-redact
|
||||
autoRedact.title=Avtomatik Gizlətmə
|
||||
|
||||
@@ -455,6 +455,9 @@ adminUserSettings.confirmChangeUserStatus=Трябва ли потребител
|
||||
adminUserSettings.usernameInfo=Потребителското име може да съдържа само букви, цифри и следните специални символи @._+- или трябва да е валиден имейл адрес.
|
||||
adminUserSettings.role=Роля
|
||||
adminUserSettings.actions=Действия
|
||||
adminUserSettings.apiUser=Ограничен API потребител
|
||||
adminUserSettings.extraApiUser=Допълнителен ограничен API потребител
|
||||
adminUserSettings.webOnlyUser=Само за уеб-потребител
|
||||
adminUserSettings.demoUser=Демо потребител (без персонализирани настройки)
|
||||
adminUserSettings.internalApiUser=Вътрешен API потребител
|
||||
adminUserSettings.forceChange=Принудете потребителя да промени потребителското име/парола при влизане
|
||||
@@ -905,6 +908,7 @@ login.alreadyLoggedIn=Вече сте влезли в
|
||||
login.alreadyLoggedIn2=устройства. Моля, излезте от устройствата и опитайте отново.
|
||||
login.toManySessions=Имате твърде много активни сесии
|
||||
login.logoutMessage=You have been logged out.
|
||||
login.invalidInResponseTo=The requested SAML response is invalid or has expired. Please contact the administrator.
|
||||
|
||||
#auto-redact
|
||||
autoRedact.title=Автоматично редактиране
|
||||
|
||||
@@ -455,6 +455,9 @@ adminUserSettings.confirmChangeUserStatus=སྤྱོད་མཁན་སྤ
|
||||
adminUserSettings.usernameInfo=སྤྱོད་མཁན་མིང་ནང་ཡི་གེ་དང་ཨང་ཀི། དམིགས་བསལ་མཚོན་རྟགས་ @._+- ཡང་ན་གློག་འཕྲིན་ཁ་བྱང་ཚད་ལྡན་ཞིག་དགོས།
|
||||
adminUserSettings.role=འགན་འཁུར།
|
||||
adminUserSettings.actions=བྱ་སྤྱོད།
|
||||
adminUserSettings.apiUser=ཚད་བཀག་ཅན་གྱི་ API སྤྱོད་མཁན།
|
||||
adminUserSettings.extraApiUser=ཚད་བཀག་ཅན་གྱི་ API སྤྱོད་མཁན་འཕར་མ།
|
||||
adminUserSettings.webOnlyUser=དྲ་ཚིགས་ཁོ་ནའི་སྤྱོད་མཁན།
|
||||
adminUserSettings.demoUser=བརྟག་དཔྱད་སྤྱོད་མཁན། (རང་སྒྲིག་མེད་པ།)
|
||||
adminUserSettings.internalApiUser=ནང་ཁུལ་ API སྤྱོད་མཁན།
|
||||
adminUserSettings.forceChange=ནང་འཛུལ་སྐབས་གསང་ཚིག་བསྒྱུར་དགོས་པ་བཟོ་བ།
|
||||
@@ -905,6 +908,7 @@ login.alreadyLoggedIn=ཁྱེད་རང་
|
||||
login.alreadyLoggedIn2=སྒྲིག་ཆས་ནང་ནང་འཛུལ་བྱས་ཟིན། སྒྲིག་ཆས་ནས་ཕྱིར་འཐེན་བྱས་ནས་ཡང་བསྐྱར་ཚོད་ལྟ་བྱེད་རོགས།
|
||||
login.toManySessions=ཁྱེད་ལ་འཛུལ་ཞུགས་བྱས་པའི་གནས་སྐབས་མང་དྲགས་འདུག
|
||||
login.logoutMessage=You have been logged out.
|
||||
login.invalidInResponseTo=The requested SAML response is invalid or has expired. Please contact the administrator.
|
||||
|
||||
#auto-redact
|
||||
autoRedact.title=རང་འགུལ་སྒྲིབ་སྲུང་།
|
||||
|
||||
@@ -455,6 +455,9 @@ adminUserSettings.confirmChangeUserStatus=Vols deshabilitar/habilitar aquest usu
|
||||
adminUserSettings.usernameInfo=El nom d'usuari només pot contenir lletres, números i els següents caràcters especials: @._+- o ha de ser una adreça de correu electrònic vàlida.
|
||||
adminUserSettings.role=Rol
|
||||
adminUserSettings.actions=Accions
|
||||
adminUserSettings.apiUser=Usuari amb API limitada
|
||||
adminUserSettings.extraApiUser=Usuari Addicional amb API limitada
|
||||
adminUserSettings.webOnlyUser=Usuari només WEB
|
||||
adminUserSettings.demoUser=Usuari de Demo (Sense configuracions personalitzades)
|
||||
adminUserSettings.internalApiUser=Usuari d'API Interna
|
||||
adminUserSettings.forceChange=Força l'usuari a canviar la contrasenya en iniciar sessió
|
||||
@@ -905,6 +908,7 @@ login.alreadyLoggedIn=Ja has iniciat sessió a
|
||||
login.alreadyLoggedIn2=dispositius. Si us plau, tanca la sessió en els dispositius i torna-ho a intentar.
|
||||
login.toManySessions=Tens massa sessions actives
|
||||
login.logoutMessage=You have been logged out.
|
||||
login.invalidInResponseTo=The requested SAML response is invalid or has expired. Please contact the administrator.
|
||||
|
||||
#auto-redact
|
||||
autoRedact.title=Redacció Automàtica
|
||||
|
||||
@@ -455,6 +455,9 @@ adminUserSettings.confirmChangeUserStatus=Má být uživatel deaktivován/aktivo
|
||||
adminUserSettings.usernameInfo=Uživatelské jméno může obsahovat pouze písmena, číslice a následující speciální znaky @._+- nebo musí být platná e-mailová adresa.
|
||||
adminUserSettings.role=Role
|
||||
adminUserSettings.actions=Akce
|
||||
adminUserSettings.apiUser=Omezený API uživatel
|
||||
adminUserSettings.extraApiUser=Další omezený API uživatel
|
||||
adminUserSettings.webOnlyUser=Pouze webový uživatel
|
||||
adminUserSettings.demoUser=Demo uživatel (Bez vlastních nastavení)
|
||||
adminUserSettings.internalApiUser=Interní API uživatel
|
||||
adminUserSettings.forceChange=Vynutit změnu hesla při přihlášení
|
||||
@@ -905,6 +908,7 @@ login.alreadyLoggedIn=Již jste přihlášeni na
|
||||
login.alreadyLoggedIn2=zařízeních. Odhlaste se prosím z těchto zařízení a zkuste to znovu.
|
||||
login.toManySessions=Máte příliš mnoho aktivních relací
|
||||
login.logoutMessage=You have been logged out.
|
||||
login.invalidInResponseTo=The requested SAML response is invalid or has expired. Please contact the administrator.
|
||||
|
||||
#auto-redact
|
||||
autoRedact.title=Automatické začernění
|
||||
|
||||
@@ -455,6 +455,9 @@ adminUserSettings.confirmChangeUserStatus=Skal brugeren deaktiveres/aktiveres?
|
||||
adminUserSettings.usernameInfo=Brugernavn må kun indeholde bogstaver, tal og følgende specialtegn @._+- eller skal være en gyldig e-mailadresse.
|
||||
adminUserSettings.role=Rolle
|
||||
adminUserSettings.actions=Handlinger
|
||||
adminUserSettings.apiUser=Begrænset API-bruger
|
||||
adminUserSettings.extraApiUser=Yderligere Begrænset API-bruger
|
||||
adminUserSettings.webOnlyUser=Kun Web-bruger
|
||||
adminUserSettings.demoUser=Demo-bruger (Ingen brugerdefinerede indstillinger)
|
||||
adminUserSettings.internalApiUser=Intern API-bruger
|
||||
adminUserSettings.forceChange=Tving bruger til at ændre adgangskode ved login
|
||||
@@ -905,6 +908,7 @@ login.alreadyLoggedIn=Du er allerede logget ind på
|
||||
login.alreadyLoggedIn2=enheder. Log ud af disse enheder og prøv igen.
|
||||
login.toManySessions=Du har for mange aktive sessoner
|
||||
login.logoutMessage=You have been logged out.
|
||||
login.invalidInResponseTo=The requested SAML response is invalid or has expired. Please contact the administrator.
|
||||
|
||||
#auto-redact
|
||||
autoRedact.title=Auto Rediger
|
||||
|
||||
@@ -455,6 +455,9 @@ adminUserSettings.confirmChangeUserStatus=Soll der Benutzer deaktiviert/aktivier
|
||||
adminUserSettings.usernameInfo=Der Benutzername darf nur Buchstaben, Zahlen und die folgenden Sonderzeichen @._+- enthalten oder muss eine gültige E-Mail-Adresse sein.
|
||||
adminUserSettings.role=Rolle
|
||||
adminUserSettings.actions=Aktionen
|
||||
adminUserSettings.apiUser=Eingeschränkter API-Benutzer
|
||||
adminUserSettings.extraApiUser=Zusätzlicher eingeschränkter API-Benutzer
|
||||
adminUserSettings.webOnlyUser=Nur Web-Benutzer
|
||||
adminUserSettings.demoUser=Demo-Benutzer (Keine benutzerdefinierten Einstellungen)
|
||||
adminUserSettings.internalApiUser=Interner API-Benutzer
|
||||
adminUserSettings.forceChange=Benutzer dazu zwingen, Benutzernamen/Passwort bei der Anmeldung zu ändern
|
||||
@@ -905,6 +908,7 @@ login.alreadyLoggedIn=Sie sind bereits an
|
||||
login.alreadyLoggedIn2=Geräten angemeldet. Bitte melden Sie sich dort ab und versuchen es dann erneut.
|
||||
login.toManySessions=Sie haben zu viele aktive Sitzungen
|
||||
login.logoutMessage=Sie wurden erfolgreich abgemeldet.
|
||||
login.invalidInResponseTo=The requested SAML response is invalid or has expired. Please contact the administrator.
|
||||
|
||||
#auto-redact
|
||||
autoRedact.title=Automatisch zensieren/schwärzen
|
||||
|
||||
@@ -455,6 +455,9 @@ adminUserSettings.confirmChangeUserStatus=Θέλετε να απενεργοπο
|
||||
adminUserSettings.usernameInfo=Το όνομα χρήστη μπορεί να περιέχει μόνο γράμματα, αριθμούς και τους ειδικούς χαρακτήρες @._+- ή πρέπει να είναι έγκυρη διεύθυνση email.
|
||||
adminUserSettings.role=Ρόλος
|
||||
adminUserSettings.actions=Ενέργειες
|
||||
adminUserSettings.apiUser=Περιορισμένος χρήστης API
|
||||
adminUserSettings.extraApiUser=Επιπλέον περιορισμένος χρήστης API
|
||||
adminUserSettings.webOnlyUser=Χρήστης μόνο web
|
||||
adminUserSettings.demoUser=Δοκιμαστικός χρήστης (Χωρίς προσαρμοσμένες ρυθμίσεις)
|
||||
adminUserSettings.internalApiUser=Εσωτερικός χρήστης API
|
||||
adminUserSettings.forceChange=Υποχρεωτική αλλαγή κωδικού κατά τη σύνδεση
|
||||
@@ -905,6 +908,7 @@ login.alreadyLoggedIn=Είστε ήδη συνδεδεμένοι σε
|
||||
login.alreadyLoggedIn2=συσκευές. Παρακαλώ αποσυνδεθείτε από τις συσκευές και προσπαθήστε ξανά.
|
||||
login.toManySessions=Έχετε πάρα πολλές ενεργές συνεδρίες
|
||||
login.logoutMessage=You have been logged out.
|
||||
login.invalidInResponseTo=The requested SAML response is invalid or has expired. Please contact the administrator.
|
||||
|
||||
#auto-redact
|
||||
autoRedact.title=Αυτόματη απόκρυψη
|
||||
|
||||
@@ -446,9 +446,6 @@ account.adminNotif=You have admin privileges. Access system settings and user ma
|
||||
|
||||
adminUserSettings.title=User Control Settings
|
||||
adminUserSettings.header=Admin User Control Settings
|
||||
adminUserSettings.systemAdmin=System Administrator
|
||||
adminUserSettings.orgAdmin=Organisation Administrator
|
||||
adminUserSettings.teamLead=Team Leader
|
||||
adminUserSettings.admin=Admin
|
||||
adminUserSettings.user=User
|
||||
adminUserSettings.addUser=Add New User
|
||||
@@ -458,6 +455,9 @@ adminUserSettings.confirmChangeUserStatus=Should the user be disabled/enabled?
|
||||
adminUserSettings.usernameInfo=Username can only contain letters, numbers and the following special characters @._+- or must be a valid email address.
|
||||
adminUserSettings.role=Role
|
||||
adminUserSettings.actions=Actions
|
||||
adminUserSettings.apiUser=Limited API User
|
||||
adminUserSettings.extraApiUser=Additional Limited API User
|
||||
adminUserSettings.webOnlyUser=Web Only User
|
||||
adminUserSettings.demoUser=Demo User (No custom settings)
|
||||
adminUserSettings.internalApiUser=Internal API User
|
||||
adminUserSettings.forceChange=Force user to change password on login
|
||||
@@ -893,7 +893,7 @@ login.rememberme=Remember me
|
||||
login.invalid=Invalid username or password.
|
||||
login.locked=Your account has been locked.
|
||||
login.signinTitle=Please sign in
|
||||
login.ssoSignIn=Login via Single Sign-on
|
||||
login.ssoSignIn=Login via Single Sign-On
|
||||
login.oAuth2AutoCreateDisabled=OAUTH2 Auto-Create User Disabled
|
||||
login.oAuth2AdminBlockedUser=Registration or logging in of non-registered users is currently blocked. Please contact the administrator.
|
||||
login.oauth2RequestNotFound=Authorization request not found
|
||||
@@ -908,6 +908,7 @@ login.alreadyLoggedIn=You are already logged in to
|
||||
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
||||
login.toManySessions=You have too many active sessions
|
||||
login.logoutMessage=You have been logged out.
|
||||
login.invalidInResponseTo=The requested SAML response is invalid or has expired. Please contact the administrator.
|
||||
|
||||
#auto-redact
|
||||
autoRedact.title=Auto Redact
|
||||
|
||||
@@ -455,6 +455,9 @@ adminUserSettings.confirmChangeUserStatus=Should the user be disabled/enabled?
|
||||
adminUserSettings.usernameInfo=Username can only contain letters, numbers and the following special characters @._+- or must be a valid email address.
|
||||
adminUserSettings.role=Role
|
||||
adminUserSettings.actions=Actions
|
||||
adminUserSettings.apiUser=Limited API User
|
||||
adminUserSettings.extraApiUser=Additional Limited API User
|
||||
adminUserSettings.webOnlyUser=Web Only User
|
||||
adminUserSettings.demoUser=Demo User (No custom settings)
|
||||
adminUserSettings.internalApiUser=Internal API User
|
||||
adminUserSettings.forceChange=Force user to change password on login
|
||||
@@ -905,6 +908,7 @@ login.alreadyLoggedIn=You are already logged in to
|
||||
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
||||
login.toManySessions=You have too many active sessions
|
||||
login.logoutMessage=You have been logged out.
|
||||
login.invalidInResponseTo=The requested SAML response is invalid or has expired. Please contact the administrator.
|
||||
|
||||
#auto-redact
|
||||
autoRedact.title=Auto Redact
|
||||
|
||||
@@ -455,6 +455,9 @@ adminUserSettings.confirmChangeUserStatus=¿Se debe habilitar/deshabilitar el us
|
||||
adminUserSettings.usernameInfo=El nombre de usuario solo puede contener letras, números y los siguientes caracteres especiales @._+- o debe ser una dirección de correo electrónico válida.
|
||||
adminUserSettings.role=Rol
|
||||
adminUserSettings.actions=Acciones
|
||||
adminUserSettings.apiUser=Usuario limitado de API
|
||||
adminUserSettings.extraApiUser=Otro usuario limitado de API
|
||||
adminUserSettings.webOnlyUser=Usuario solo web
|
||||
adminUserSettings.demoUser=Usuario Demo (Sin ajustes personalizados)
|
||||
adminUserSettings.internalApiUser=Usuario interno de API
|
||||
adminUserSettings.forceChange=Forzar usuario a cambiar usuario/contraseña en el acceso
|
||||
@@ -905,6 +908,7 @@ login.alreadyLoggedIn=Ya ha iniciado sesión en
|
||||
login.alreadyLoggedIn2=dispositivos. Cierre sesión en los dispositivos y vuelva a intentarlo.
|
||||
login.toManySessions=Tiene demasiadas sesiones activas
|
||||
login.logoutMessage=You have been logged out.
|
||||
login.invalidInResponseTo=The requested SAML response is invalid or has expired. Please contact the administrator.
|
||||
|
||||
#auto-redact
|
||||
autoRedact.title=Auto Censurar Texto
|
||||
|
||||
@@ -455,6 +455,9 @@ adminUserSettings.confirmChangeUserStatus=Should the user be disabled/enabled?
|
||||
adminUserSettings.usernameInfo=Username can only contain letters, numbers and the following special characters @._+- or must be a valid email address.
|
||||
adminUserSettings.role=Rol
|
||||
adminUserSettings.actions=Ekintzak
|
||||
adminUserSettings.apiUser=APIren erabiltzaile mugatua
|
||||
adminUserSettings.extraApiUser=Additional Limited API User
|
||||
adminUserSettings.webOnlyUser=Web-erabiltzailea bakarrik
|
||||
adminUserSettings.demoUser=Demo User (No custom settings)
|
||||
adminUserSettings.internalApiUser=Internal API User
|
||||
adminUserSettings.forceChange=Force user to change password on login
|
||||
@@ -905,6 +908,7 @@ login.alreadyLoggedIn=You are already logged in to
|
||||
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
||||
login.toManySessions=You have too many active sessions
|
||||
login.logoutMessage=You have been logged out.
|
||||
login.invalidInResponseTo=The requested SAML response is invalid or has expired. Please contact the administrator.
|
||||
|
||||
#auto-redact
|
||||
autoRedact.title=Auto Idatzi
|
||||
|
||||
@@ -455,6 +455,9 @@ adminUserSettings.confirmChangeUserStatus=آیا باید وضعیت کاربر
|
||||
adminUserSettings.usernameInfo=نام کاربری فقط میتواند شامل حروف، اعداد و کاراکترهای خاص @._+- باشد یا باید یک آدرس ایمیل معتبر باشد.
|
||||
adminUserSettings.role=نقش
|
||||
adminUserSettings.actions=اقدامات
|
||||
adminUserSettings.apiUser=کاربر محدود API
|
||||
adminUserSettings.extraApiUser=کاربر محدود اضافی API
|
||||
adminUserSettings.webOnlyUser=فقط کاربر وب
|
||||
adminUserSettings.demoUser=کاربر دمو (بدون تنظیمات سفارشی)
|
||||
adminUserSettings.internalApiUser=کاربر داخلی API
|
||||
adminUserSettings.forceChange=مجبور کردن کاربر به تغییر رمز عبور هنگام ورود
|
||||
@@ -905,6 +908,7 @@ login.alreadyLoggedIn=شما قبلاً وارد شدهاید در
|
||||
login.alreadyLoggedIn2=دستگاهها. لطفاً از دستگاهها خارج شده و دوباره تلاش کنید.
|
||||
login.toManySessions=شما تعداد زیادی نشست فعال دارید.
|
||||
login.logoutMessage=You have been logged out.
|
||||
login.invalidInResponseTo=The requested SAML response is invalid or has expired. Please contact the administrator.
|
||||
|
||||
#auto-redact
|
||||
autoRedact.title=سانسور خودکار
|
||||
|
||||
@@ -455,6 +455,9 @@ adminUserSettings.confirmChangeUserStatus=Voulez vous vraiment déactiver/réact
|
||||
adminUserSettings.usernameInfo=Le nom d'utilisateur ne peut contenir que des lettres, des chiffres et les caractères spéciaux suivants @._+- ou doit être une adresse e-mail valide.
|
||||
adminUserSettings.role=Rôle
|
||||
adminUserSettings.actions=Actions
|
||||
adminUserSettings.apiUser=Utilisateur API limité
|
||||
adminUserSettings.extraApiUser=Utilisateur limité supplémentaire de l'API
|
||||
adminUserSettings.webOnlyUser=Utilisateur Web uniquement
|
||||
adminUserSettings.demoUser=Demo User (Paramètres par défaut)
|
||||
adminUserSettings.internalApiUser=Utilisateur de l'API interne
|
||||
adminUserSettings.forceChange=Forcer l'utilisateur à changer son nom d'utilisateur/mot de passe lors de la connexion
|
||||
@@ -905,6 +908,7 @@ login.alreadyLoggedIn=Vous êtes déjà connecté sur
|
||||
login.alreadyLoggedIn2=appareils. Veuillez vous déconnecter des appareils et réessayer.
|
||||
login.toManySessions=Vous avez trop de sessions actives.
|
||||
login.logoutMessage=Vous avez été déconnecté.
|
||||
login.invalidInResponseTo=The requested SAML response is invalid or has expired. Please contact the administrator.
|
||||
|
||||
#auto-redact
|
||||
autoRedact.title=Caviarder automatiquement
|
||||
|
||||
@@ -455,6 +455,9 @@ adminUserSettings.confirmChangeUserStatus=Ar cheart an t-úsáideoir a dhíchuma
|
||||
adminUserSettings.usernameInfo=Ní féidir ach litreacha, uimhreacha agus na carachtair speisialta seo a leanas @._+- a bheith san ainm úsáideora nó ní mór gur seoladh ríomhphoist bailí é.
|
||||
adminUserSettings.role=Ról
|
||||
adminUserSettings.actions=Gníomhartha
|
||||
adminUserSettings.apiUser=Úsáideoir API Teoranta
|
||||
adminUserSettings.extraApiUser=Úsáideoir API Teoranta breise
|
||||
adminUserSettings.webOnlyUser=Úsáideoir Gréasáin Amháin
|
||||
adminUserSettings.demoUser=Úsáideoir Taispeána (Gan socruithe saincheaptha)
|
||||
adminUserSettings.internalApiUser=Úsáideoir API Inmheánach
|
||||
adminUserSettings.forceChange=Cuir iallach ar an úsáideoir pasfhocal a athrú ar logáil isteach
|
||||
@@ -905,6 +908,7 @@ login.alreadyLoggedIn=Tá tú logáilte isteach cheana
|
||||
login.alreadyLoggedIn2=gléasanna. Logáil amach as na gléasanna agus bain triail eile as.
|
||||
login.toManySessions=Tá an iomarca seisiún gníomhach agat
|
||||
login.logoutMessage=You have been logged out.
|
||||
login.invalidInResponseTo=The requested SAML response is invalid or has expired. Please contact the administrator.
|
||||
|
||||
#auto-redact
|
||||
autoRedact.title=Auto Redact
|
||||
|
||||
@@ -455,6 +455,9 @@ adminUserSettings.confirmChangeUserStatus=क्या उपयोगकर्
|
||||
adminUserSettings.usernameInfo=उपयोगकर्ता नाम में केवल अक्षर, संख्याएं और निम्नलिखित विशेष वर्ण @._+- हो सकते हैं या एक वैध ईमेल पता होना चाहिए।
|
||||
adminUserSettings.role=भूमिका
|
||||
adminUserSettings.actions=कार्रवाइयां
|
||||
adminUserSettings.apiUser=सीमित API उपयोगकर्ता
|
||||
adminUserSettings.extraApiUser=अतिरिक्त सीमित API उपयोगकर्ता
|
||||
adminUserSettings.webOnlyUser=केवल वेब उपयोगकर्ता
|
||||
adminUserSettings.demoUser=डेमो उपयोगकर्ता (कोई कस्टम सेटिंग्स नहीं)
|
||||
adminUserSettings.internalApiUser=आंतरिक API उपयोगकर्ता
|
||||
adminUserSettings.forceChange=लॉगिन पर उपयोगकर्ता को पासवर्ड बदलने के लिए मजबूर करें
|
||||
@@ -905,6 +908,7 @@ login.alreadyLoggedIn=आप पहले से ही
|
||||
login.alreadyLoggedIn2=उपकरणों में लॉग इन हैं। कृपया उपकरणों से लॉग आउट करें और पुनः प्रयास करें।
|
||||
login.toManySessions=आपके बहुत सारे सक्रिय सत्र हैं
|
||||
login.logoutMessage=You have been logged out.
|
||||
login.invalidInResponseTo=The requested SAML response is invalid or has expired. Please contact the administrator.
|
||||
|
||||
#auto-redact
|
||||
autoRedact.title=स्वतः गोपनीयकरण
|
||||
|
||||
@@ -455,6 +455,9 @@ adminUserSettings.confirmChangeUserStatus=Treba li isključiti/uključiti ovog k
|
||||
adminUserSettings.usernameInfo=Korisničko ime može sadržavati samo slova, brojke i sljedeće posebne znakove @._+- ili mora biti važeća adresa e-pošte.
|
||||
adminUserSettings.role=Uloga
|
||||
adminUserSettings.actions=Akcije
|
||||
adminUserSettings.apiUser=Korisnik s ograničenim API pristupom
|
||||
adminUserSettings.extraApiUser=Dodatni korisnik s ograničenim API pristupom
|
||||
adminUserSettings.webOnlyUser=Web Korisnik
|
||||
adminUserSettings.demoUser=Demo korisnik (Bez prilagođenih Postavki)
|
||||
adminUserSettings.internalApiUser=Interni API Korisnik
|
||||
adminUserSettings.forceChange=Prisiliti korisnika da promijeni lozinku prilikom prijave
|
||||
@@ -905,6 +908,7 @@ login.alreadyLoggedIn=Već ste se prijavili na
|
||||
login.alreadyLoggedIn2=ure. Odjavite se s ure i pokušajte ponovo.
|
||||
login.toManySessions=Imate preko mrežne sesije aktivnih
|
||||
login.logoutMessage=You have been logged out.
|
||||
login.invalidInResponseTo=The requested SAML response is invalid or has expired. Please contact the administrator.
|
||||
|
||||
#auto-redact
|
||||
autoRedact.title=Automatsko uređivanje
|
||||
|
||||
@@ -455,6 +455,9 @@ adminUserSettings.confirmChangeUserStatus=Biztosan módosítja a felhasználó
|
||||
adminUserSettings.usernameInfo=A felhasználónév csak betűket, számokat és a következő speciális karaktereket tartalmazhatja: @._+- vagy érvényes e-mail címnek kell lennie.
|
||||
adminUserSettings.role=Szerepkör
|
||||
adminUserSettings.actions=Műveletek
|
||||
adminUserSettings.apiUser=Korlátozott API felhasználó
|
||||
adminUserSettings.extraApiUser=További korlátozott API felhasználó
|
||||
adminUserSettings.webOnlyUser=Csak webes felhasználó
|
||||
adminUserSettings.demoUser=Demo felhasználó (egyedi beállítások nélkül)
|
||||
adminUserSettings.internalApiUser=Belső API felhasználó
|
||||
adminUserSettings.forceChange=Jelszóváltoztatás kikényszerítése bejelentkezéskor
|
||||
@@ -905,6 +908,7 @@ login.alreadyLoggedIn=Már be van jelentkezve
|
||||
login.alreadyLoggedIn2=eszközön. Kérjük, jelentkezzen ki az eszközökről és próbálja újra.
|
||||
login.toManySessions=Túl sok aktív munkamenet
|
||||
login.logoutMessage=Sikeresen kijelentkezett.
|
||||
login.invalidInResponseTo=The requested SAML response is invalid or has expired. Please contact the administrator.
|
||||
|
||||
#auto-redact
|
||||
autoRedact.title=Automatikus kitakarás
|
||||
|
||||
@@ -455,6 +455,9 @@ adminUserSettings.confirmChangeUserStatus=Haruskah pengguna dinonaktifkan/diakti
|
||||
adminUserSettings.usernameInfo=Nama pengguna hanya boleh mengandung huruf, angka, dan karakter khusus berikut @._+- atau harus berupa alamat email yang valid.
|
||||
adminUserSettings.role=Peran
|
||||
adminUserSettings.actions=Tindakan
|
||||
adminUserSettings.apiUser=Pengguna API Terbatas
|
||||
adminUserSettings.extraApiUser=Pengguna API Terbatas Tambahan
|
||||
adminUserSettings.webOnlyUser=Pengguna Khusus Web
|
||||
adminUserSettings.demoUser=Pengguna Demo (Tanpa pengaturan kustom)
|
||||
adminUserSettings.internalApiUser=Pengguna API Internal
|
||||
adminUserSettings.forceChange=Memaksa pengguna untuk mengubah nama pengguna/kata sandi saat masuk
|
||||
@@ -905,6 +908,7 @@ login.alreadyLoggedIn=Anda sudah login ke
|
||||
login.alreadyLoggedIn2=perangkat. Silakan keluar dari perangkat dan coba lagi.
|
||||
login.toManySessions=Anda memiliki terlalu banyak sesi aktif
|
||||
login.logoutMessage=You have been logged out.
|
||||
login.invalidInResponseTo=The requested SAML response is invalid or has expired. Please contact the administrator.
|
||||
|
||||
#auto-redact
|
||||
autoRedact.title=Redaksional Otomatis
|
||||
|
||||
@@ -455,6 +455,9 @@ adminUserSettings.confirmChangeUserStatus=L'utente dovrebbe essere disabilitato/
|
||||
adminUserSettings.usernameInfo=Il nome utente può contenere solo lettere, numeri e i seguenti caratteri speciali @._+- oppure deve essere un indirizzo email valido.
|
||||
adminUserSettings.role=Ruolo
|
||||
adminUserSettings.actions=Azioni
|
||||
adminUserSettings.apiUser=Utente API limitato
|
||||
adminUserSettings.extraApiUser=API utente limitato aggiuntivo
|
||||
adminUserSettings.webOnlyUser=Utente solo Web
|
||||
adminUserSettings.demoUser=Utente demo (nessuna impostazione personalizzata)
|
||||
adminUserSettings.internalApiUser=API utente interna
|
||||
adminUserSettings.forceChange=Forza l'utente a cambiare nome utente/password all'accesso
|
||||
@@ -905,6 +908,7 @@ login.alreadyLoggedIn=Hai già effettuato l'accesso a
|
||||
login.alreadyLoggedIn2=dispositivi. Esci dai dispositivi e riprova.
|
||||
login.toManySessions=Hai troppe sessioni attive
|
||||
login.logoutMessage=Sei stato disconnesso.
|
||||
login.invalidInResponseTo=The requested SAML response is invalid or has expired. Please contact the administrator.
|
||||
|
||||
#auto-redact
|
||||
autoRedact.title=Redazione automatica
|
||||
@@ -1770,7 +1774,7 @@ audit.dashboard.filter.userPlaceholder=Filtra per utente
|
||||
audit.dashboard.filter.startDate=Data di inizio
|
||||
audit.dashboard.filter.endDate=Data di fine
|
||||
audit.dashboard.filter.apply=Applica filtri
|
||||
audit.dashboard.filter.reset=Resetta Filtri
|
||||
audit.dashboard.filter.reset=Resetta filtri
|
||||
|
||||
# Table Headers
|
||||
audit.dashboard.table.id=ID
|
||||
@@ -1860,7 +1864,7 @@ scannerEffect.submit=Crea una falsa scansione
|
||||
#home.scannerEffect
|
||||
home.scannerEffect.title=Falsa scansione
|
||||
home.scannerEffect.desc=Crea un PDF che sembra scansionato
|
||||
scannerEffect.tags=scansiona, simula, realistico, converti
|
||||
scannerEffect.tags=scansiona,simula,realistico,converti
|
||||
|
||||
# ScannerEffect advanced settings (frontend)
|
||||
scannerEffect.advancedSettings=Abilita impostazioni di scansione avanzate
|
||||
@@ -1882,7 +1886,7 @@ scannerEffect.resolution=Risoluzione (DPI)
|
||||
home.editTableOfContents.title=Modifica indice
|
||||
home.editTableOfContents.desc=Aggiungi o modifica segnalibri e sommario nei documenti PDF
|
||||
|
||||
editTableOfContents.tags=segnalibri, indice, navigazione, indice analitico, sommario, capitoli, sezioni, struttura
|
||||
editTableOfContents.tags=segnalibri,indice,navigazione,indice analitico,sommario,capitoli,sezioni,struttura
|
||||
editTableOfContents.title=Modifica indice
|
||||
editTableOfContents.header=Aggiungi o modifica sommario PDF
|
||||
editTableOfContents.replaceExisting=Sostituisci i segnalibri esistenti (deseleziona per aggiungerli a quelli esistenti)
|
||||
|
||||
@@ -455,6 +455,9 @@ adminUserSettings.confirmChangeUserStatus=ユーザーを無効/有効にする
|
||||
adminUserSettings.usernameInfo=ユーザー名には、文字、数字、および次の特殊文字 @._+- のみを含めることができます。または、有効な電子メール アドレスである必要があります。
|
||||
adminUserSettings.role=ロール
|
||||
adminUserSettings.actions=アクション
|
||||
adminUserSettings.apiUser=限定されたAPIユーザー
|
||||
adminUserSettings.extraApiUser=追加の制限付きAPIユーザー
|
||||
adminUserSettings.webOnlyUser=ウェブ専用ユーザー
|
||||
adminUserSettings.demoUser=デモユーザー (カスタム設定なし)
|
||||
adminUserSettings.internalApiUser=内部APIユーザー
|
||||
adminUserSettings.forceChange=ログイン時にユーザー名/パスワードを強制的に変更する
|
||||
@@ -905,6 +908,7 @@ login.alreadyLoggedIn=すでにログインしています
|
||||
login.alreadyLoggedIn2=デバイスからログアウトしてもう一度お試しください。
|
||||
login.toManySessions=アクティブなセッションが多すぎます
|
||||
login.logoutMessage=ログアウトしました
|
||||
login.invalidInResponseTo=The requested SAML response is invalid or has expired. Please contact the administrator.
|
||||
|
||||
#auto-redact
|
||||
autoRedact.title=自動墨消し
|
||||
|
||||
@@ -455,6 +455,9 @@ adminUserSettings.confirmChangeUserStatus=사용자를 비활성화/활성화하
|
||||
adminUserSettings.usernameInfo=사용자 이름은 문자, 숫자 및 @._+- 특수문자만 포함하거나 유효한 이메일 주소여야 합니다.
|
||||
adminUserSettings.role=역할
|
||||
adminUserSettings.actions=작업
|
||||
adminUserSettings.apiUser=제한된 API 사용자
|
||||
adminUserSettings.extraApiUser=추가 제한된 API 사용자
|
||||
adminUserSettings.webOnlyUser=웹 전용 사용자
|
||||
adminUserSettings.demoUser=데모 사용자 (사용자 지정 설정 없음)
|
||||
adminUserSettings.internalApiUser=내부 API 사용자
|
||||
adminUserSettings.forceChange=로그인 시 사용자 비밀번호 변경 강제
|
||||
@@ -905,6 +908,7 @@ login.alreadyLoggedIn=이미 다음에 로그인되어 있습니다
|
||||
login.alreadyLoggedIn2=개의 기기. 해당 기기에서 로그아웃한 후 다시 시도하세요.
|
||||
login.toManySessions=활성 세션이 너무 많습니다
|
||||
login.logoutMessage=You have been logged out.
|
||||
login.invalidInResponseTo=The requested SAML response is invalid or has expired. Please contact the administrator.
|
||||
|
||||
#auto-redact
|
||||
autoRedact.title=자동 검열
|
||||
|
||||
@@ -455,6 +455,9 @@ adminUserSettings.confirmChangeUserStatus=ഉപയോക്താവിനെ
|
||||
adminUserSettings.usernameInfo=ഉപയോക്തൃനാമത്തിൽ അക്ഷരങ്ങൾ, അക്കങ്ങൾ, താഴെ പറയുന്ന പ്രത്യേക പ്രതീകങ്ങൾ @._+- എന്നിവ മാത്രമേ ഉണ്ടാകാവൂ അല്ലെങ്കിൽ സാധുവായ ഒരു ഇമെയിൽ വിലാസം ആയിരിക്കണം.
|
||||
adminUserSettings.role=റോൾ
|
||||
adminUserSettings.actions=പ്രവർത്തനങ്ങൾ
|
||||
adminUserSettings.apiUser=പരിമിതമായ API ഉപയോക്താവ്
|
||||
adminUserSettings.extraApiUser=അധിക പരിമിതമായ API ഉപയോക്താവ്
|
||||
adminUserSettings.webOnlyUser=വെബ് മാത്രം ഉപയോക്താവ്
|
||||
adminUserSettings.demoUser=ഡെമോ ഉപയോക്താവ് (ഇഷ്ടാനുസൃത ക്രമീകരണങ്ങളില്ല)
|
||||
adminUserSettings.internalApiUser=ആന്തരിക API ഉപയോക്താവ്
|
||||
adminUserSettings.forceChange=ലോഗിൻ ചെയ്യുമ്പോൾ പാസ്വേഡ് മാറ്റാൻ ഉപയോക്താവിനെ നിർബന്ധിക്കുക
|
||||
@@ -905,6 +908,7 @@ login.alreadyLoggedIn=നിങ്ങൾ ഇതിനകം ലോഗിൻ ച
|
||||
login.alreadyLoggedIn2=ഉപകരണങ്ങളിൽ. ദയവായി ഉപകരണങ്ങളിൽ നിന്ന് ലോഗ് ഔട്ട് ചെയ്ത് വീണ്ടും ശ്രമിക്കുക.
|
||||
login.toManySessions=നിങ്ങൾക്ക് വളരെയധികം സജീവ സെഷനുകൾ ഉണ്ട്
|
||||
login.logoutMessage=നിങ്ങൾ ലോഗ് ഔട്ട് ചെയ്തു.
|
||||
login.invalidInResponseTo=The requested SAML response is invalid or has expired. Please contact the administrator.
|
||||
|
||||
#auto-redact
|
||||
autoRedact.title=സ്വയം റെഡാക്റ്റ് ചെയ്യുക
|
||||
|
||||
@@ -455,6 +455,9 @@ adminUserSettings.confirmChangeUserStatus=Should the user be disabled/enabled?
|
||||
adminUserSettings.usernameInfo=Gebruikersnaam kan alleen letters, nummers en de volgende speciale tekens @._+- bevatten of moet een geldig emailadres zijn.
|
||||
adminUserSettings.role=Rol
|
||||
adminUserSettings.actions=Acties
|
||||
adminUserSettings.apiUser=Beperkte API gebruiker
|
||||
adminUserSettings.extraApiUser=Extra beperkte API gebruiker
|
||||
adminUserSettings.webOnlyUser=Alleen web gebruiker
|
||||
adminUserSettings.demoUser=Demogebruiker (geen aangepaste instellingen)
|
||||
adminUserSettings.internalApiUser=Interne API gebruiker
|
||||
adminUserSettings.forceChange=Forceer gebruiker om gebruikersnaam/wachtwoord te wijzigen bij inloggen
|
||||
@@ -905,6 +908,7 @@ login.alreadyLoggedIn=U zit reeds ingelogd bij
|
||||
login.alreadyLoggedIn2=apparaten. U moet u a.u.b. uitloggen van de apparaten en opnieuw proberen.
|
||||
login.toManySessions=U heeft te veel actieve sessies
|
||||
login.logoutMessage=You have been logged out.
|
||||
login.invalidInResponseTo=The requested SAML response is invalid or has expired. Please contact the administrator.
|
||||
|
||||
#auto-redact
|
||||
autoRedact.title=Automatisch censureren
|
||||
|
||||
@@ -455,6 +455,9 @@ adminUserSettings.confirmChangeUserStatus=Skal brukeren deaktiveres/aktiveres?
|
||||
adminUserSettings.usernameInfo=Brukernavn kan bare inneholde bokstaver, tall og følgende spesialtegn @._+- eller må være en gyldig e-postadresse.
|
||||
adminUserSettings.role=Rolle
|
||||
adminUserSettings.actions=Handlinger
|
||||
adminUserSettings.apiUser=Begrenset API Bruker
|
||||
adminUserSettings.extraApiUser=Ekstra Begrenset API Bruker
|
||||
adminUserSettings.webOnlyUser=Kun Web Bruker
|
||||
adminUserSettings.demoUser=Demo Bruker (Ingen tilpassede innstillinger)
|
||||
adminUserSettings.internalApiUser=Intern API Bruker
|
||||
adminUserSettings.forceChange=Tving bruker til å endre passord ved innlogging
|
||||
@@ -905,6 +908,7 @@ login.alreadyLoggedIn=Du er allerede innlogget på
|
||||
login.alreadyLoggedIn2=enheter. Logg ut og forsøk igjen
|
||||
login.toManySessions=Du har for mange aktive økter
|
||||
login.logoutMessage=You have been logged out.
|
||||
login.invalidInResponseTo=The requested SAML response is invalid or has expired. Please contact the administrator.
|
||||
|
||||
#auto-redact
|
||||
autoRedact.title=Automatisk Sensurering
|
||||
|
||||
@@ -455,6 +455,9 @@ adminUserSettings.confirmChangeUserStatus=Czy użytkownik powinien zostać wył
|
||||
adminUserSettings.usernameInfo=Niewłaściwa nazwa użytkownika - musi zawierać litery, cyfry i @._+- LUB być adresem email.
|
||||
adminUserSettings.role=Rola
|
||||
adminUserSettings.actions=Akcje
|
||||
adminUserSettings.apiUser=Ograniczony Użytkownik API
|
||||
adminUserSettings.extraApiUser=Dodatkowy ograniczony Użytkownik API
|
||||
adminUserSettings.webOnlyUser=Użytkownik tylko WEB
|
||||
adminUserSettings.demoUser=Użytkownik DEMO
|
||||
adminUserSettings.internalApiUser=Wewnętrzny użytkownik API
|
||||
adminUserSettings.forceChange=Wymuś zmianę hasło po zalogowaniu
|
||||
@@ -905,6 +908,7 @@ login.alreadyLoggedIn=Jesteś już zalogowany na
|
||||
login.alreadyLoggedIn2=urządzeniach. Wyloguj się z tych urządzeń i spróbuj ponownie.
|
||||
login.toManySessions=Masz zbyt wiele aktywnych sesji
|
||||
login.logoutMessage=You have been logged out.
|
||||
login.invalidInResponseTo=The requested SAML response is invalid or has expired. Please contact the administrator.
|
||||
|
||||
#auto-redact
|
||||
autoRedact.title=Automatyczne zaciemnienie
|
||||
|
||||
@@ -455,6 +455,9 @@ adminUserSettings.confirmChangeUserStatus=O usuário deve ser desabilitado/habil
|
||||
adminUserSettings.usernameInfo=Nome de usuário só pode incluir letras, números e os seguintes caracteres especiais @._+- ou deve ser um e-mail válido.
|
||||
adminUserSettings.role=Função
|
||||
adminUserSettings.actions=Ações
|
||||
adminUserSettings.apiUser=Usuário de API limitado
|
||||
adminUserSettings.extraApiUser=Usuário de API limitado adicional
|
||||
adminUserSettings.webOnlyUser=Usuário web apenas
|
||||
adminUserSettings.demoUser=Usuário demo (Sem configurações personalizadas)
|
||||
adminUserSettings.internalApiUser=Usuário de API interno
|
||||
adminUserSettings.forceChange=Forçar usuário a trocar a senha ao iniciar sessão
|
||||
@@ -905,6 +908,7 @@ login.alreadyLoggedIn=Você já está conectado em
|
||||
login.alreadyLoggedIn2=aparelhos. Por favor saia dos aparelhos e tente novamente.
|
||||
login.toManySessions=Você tem muitas sessões ativas
|
||||
login.logoutMessage=Você foi desconectado.
|
||||
login.invalidInResponseTo=The requested SAML response is invalid or has expired. Please contact the administrator.
|
||||
|
||||
#auto-redact
|
||||
autoRedact.title=Ocultação de Texto Automática
|
||||
|
||||
@@ -455,6 +455,9 @@ adminUserSettings.confirmChangeUserStatus=Deve o utilizador ser desativado/ativa
|
||||
adminUserSettings.usernameInfo=O nome de utilizador só pode conter letras, números e os seguintes caracteres especiais @._+- ou deve ser um endereço de email válido.
|
||||
adminUserSettings.role=Função
|
||||
adminUserSettings.actions=Ações
|
||||
adminUserSettings.apiUser=Utilizador API Limitado
|
||||
adminUserSettings.extraApiUser=Utilizador API Limitado Adicional
|
||||
adminUserSettings.webOnlyUser=Utilizador Apenas Web
|
||||
adminUserSettings.demoUser=Utilizador Demo (Sem Definições Personalizadas)
|
||||
adminUserSettings.internalApiUser=Utilizador API Interno
|
||||
adminUserSettings.forceChange=Forçar utilizador a alterar palavra-passe no login
|
||||
@@ -905,6 +908,7 @@ login.alreadyLoggedIn=Já tem sessão iniciada em
|
||||
login.alreadyLoggedIn2=dispositivos. Por favor termine sessão nesses dispositivos e tente novamente.
|
||||
login.toManySessions=Tem demasiadas sessões ativas
|
||||
login.logoutMessage=You have been logged out.
|
||||
login.invalidInResponseTo=The requested SAML response is invalid or has expired. Please contact the administrator.
|
||||
|
||||
#auto-redact
|
||||
autoRedact.title=Redação Automática
|
||||
|
||||
@@ -455,6 +455,9 @@ adminUserSettings.confirmChangeUserStatus=Ar trebui dezactivat/activat utilizato
|
||||
adminUserSettings.usernameInfo=Numele de utilizator poate conține doar litere, numere și următoarele caractere speciale @._+- sau trebuie să fie o adresă de email validă.
|
||||
adminUserSettings.role=Rol
|
||||
adminUserSettings.actions=Acțiuni
|
||||
adminUserSettings.apiUser=Utilizator API Limitat
|
||||
adminUserSettings.extraApiUser=Utilizator API Limitat Suplimentar
|
||||
adminUserSettings.webOnlyUser=Utilizator Doar Web
|
||||
adminUserSettings.demoUser=Utilizator Demo (Fără setări personalizate)
|
||||
adminUserSettings.internalApiUser=Utilizator API Intern
|
||||
adminUserSettings.forceChange=Forțează utilizatorul să schimbe parola la conectare
|
||||
@@ -905,6 +908,7 @@ login.alreadyLoggedIn=You are already logged in to
|
||||
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
||||
login.toManySessions=You have too many active sessions
|
||||
login.logoutMessage=You have been logged out.
|
||||
login.invalidInResponseTo=The requested SAML response is invalid or has expired. Please contact the administrator.
|
||||
|
||||
#auto-redact
|
||||
autoRedact.title=Redactare Automată
|
||||
|
||||
@@ -455,6 +455,9 @@ adminUserSettings.confirmChangeUserStatus=Отключить/включить п
|
||||
adminUserSettings.usernameInfo=Имя пользователя может содержать только буквы, цифры и следующие специальные символы @._+- или должно быть действительным адресом электронной почты.
|
||||
adminUserSettings.role=Роль
|
||||
adminUserSettings.actions=Действия
|
||||
adminUserSettings.apiUser=Ограниченный пользователь API
|
||||
adminUserSettings.extraApiUser=Дополнительный ограниченный пользователь API
|
||||
adminUserSettings.webOnlyUser=Только веб-пользователь
|
||||
adminUserSettings.demoUser=Демо-пользователь (без настраиваемых параметров)
|
||||
adminUserSettings.internalApiUser=Внутренний пользователь API
|
||||
adminUserSettings.forceChange=Требовать смену пароля при входе
|
||||
@@ -905,6 +908,7 @@ login.alreadyLoggedIn=Вы уже вошли в
|
||||
login.alreadyLoggedIn2=устройств(а). Пожалуйста, выйдите из этих устройств и попробуйте снова.
|
||||
login.toManySessions=У вас слишком много активных сессий
|
||||
login.logoutMessage=Вы вышли из системы.
|
||||
login.invalidInResponseTo=The requested SAML response is invalid or has expired. Please contact the administrator.
|
||||
|
||||
#auto-redact
|
||||
autoRedact.title=Автоматическое редактирование
|
||||
|
||||
@@ -455,6 +455,9 @@ adminUserSettings.confirmChangeUserStatus=Should the user be disabled/enabled?
|
||||
adminUserSettings.usernameInfo=Používateľské meno musí obsahovať iba písmená a čísla, žiadne medzery alebo špeciálne znaky.
|
||||
adminUserSettings.role=Rola
|
||||
adminUserSettings.actions=Akcie
|
||||
adminUserSettings.apiUser=Obmedzený API používateľ
|
||||
adminUserSettings.extraApiUser=Ďalší obmedzený API používateľ
|
||||
adminUserSettings.webOnlyUser=Používateľ iba pre web
|
||||
adminUserSettings.demoUser=Demo používateľ (Bez vlastných nastavení)
|
||||
adminUserSettings.internalApiUser=Interný API používateľ
|
||||
adminUserSettings.forceChange=Donútiť používateľa zmeniť heslo pri prihlásení
|
||||
@@ -905,6 +908,7 @@ login.alreadyLoggedIn=You are already logged in to
|
||||
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
||||
login.toManySessions=You have too many active sessions
|
||||
login.logoutMessage=You have been logged out.
|
||||
login.invalidInResponseTo=The requested SAML response is invalid or has expired. Please contact the administrator.
|
||||
|
||||
#auto-redact
|
||||
autoRedact.title=Automatické redigovanie
|
||||
|
||||
@@ -455,6 +455,9 @@ adminUserSettings.confirmChangeUserStatus=Ali naj bo uporabnik onemogočen/omogo
|
||||
adminUserSettings.usernameInfo=Uporabniško ime lahko vsebuje samo črke, številke in naslednje posebne znake @._+- ali mora biti veljaven e-poštni naslov.
|
||||
adminUserSettings.role=Vloga
|
||||
adminUserSettings.actions=Dejanja
|
||||
adminUserSettings.apiUser=Omejen uporabnik API-ja
|
||||
adminUserSettings.extraApiUser=Dodatni omejeni uporabnik API-ja
|
||||
adminUserSettings.webOnlyUser=Samo spletni uporabnik
|
||||
adminUserSettings.demoUser=Demo uporabnik (brez nastavitev po meri)
|
||||
adminUserSettings.internalApiUser=Notranji uporabnik API-ja
|
||||
adminUserSettings.forceChange=Prisili uporabnika, da spremeni geslo ob prijavi
|
||||
@@ -905,6 +908,7 @@ login.alreadyLoggedIn=Prijavljeni ste že v
|
||||
login.alreadyLoggedIn2=naprave. Odjavite se iz naprav in poskusite znova.
|
||||
login.toManySessions=Imate preveč aktivnih sej
|
||||
login.logoutMessage=You have been logged out.
|
||||
login.invalidInResponseTo=The requested SAML response is invalid or has expired. Please contact the administrator.
|
||||
|
||||
#auto-redact
|
||||
autoRedact.title=Samodejno redigiraj
|
||||
|
||||
@@ -455,6 +455,9 @@ adminUserSettings.confirmChangeUserStatus=Da onemogućim/omogućim korisnika?
|
||||
adminUserSettings.usernameInfo=Korisničko ime može sadržati samo slova, brojeve i specijalne karaktere @._+- ili mora biti validna email adresa.
|
||||
adminUserSettings.role=Uloga
|
||||
adminUserSettings.actions=Akcije
|
||||
adminUserSettings.apiUser=Korisnik s ograničenim API pristupom
|
||||
adminUserSettings.extraApiUser=Dodatni ograničeni API korisnik
|
||||
adminUserSettings.webOnlyUser=Korisnik samo za web
|
||||
adminUserSettings.demoUser=Demo korisnik (Bez prilagođenih podešavanja)
|
||||
adminUserSettings.internalApiUser=Interni API korisnik
|
||||
adminUserSettings.forceChange=Prisili korisnika da promeni korisničko ime/lozinku pri prijavi
|
||||
@@ -905,6 +908,7 @@ login.alreadyLoggedIn=Već si prijavljen na
|
||||
login.alreadyLoggedIn2=uređaja. Odjavi se sa uređaja i pokušaj ponovo.
|
||||
login.toManySessions=Imaš previše aktivnih sesija
|
||||
login.logoutMessage=Odjavljen si.
|
||||
login.invalidInResponseTo=The requested SAML response is invalid or has expired. Please contact the administrator.
|
||||
|
||||
#auto-redact
|
||||
autoRedact.title=Automatsko cenzurisanje
|
||||
|
||||
@@ -455,6 +455,9 @@ adminUserSettings.confirmChangeUserStatus=Ska användaren inaktiveras/aktiveras?
|
||||
adminUserSettings.usernameInfo=Användarnamn kan endast innehålla bokstäver, siffror och följande specialtecken @._+- eller måste vara en giltig e-postadress.
|
||||
adminUserSettings.role=Roll
|
||||
adminUserSettings.actions=Åtgärder
|
||||
adminUserSettings.apiUser=Begränsad API-användare
|
||||
adminUserSettings.extraApiUser=Ytterligare begränsad API-användare
|
||||
adminUserSettings.webOnlyUser=Endast webbanvändare
|
||||
adminUserSettings.demoUser=Demoanvändare (Inga anpassade inställningar)
|
||||
adminUserSettings.internalApiUser=Intern API-användare
|
||||
adminUserSettings.forceChange=Tvinga användare att ändra lösenord vid inloggning
|
||||
@@ -905,6 +908,7 @@ login.alreadyLoggedIn=Du är redan inloggad på
|
||||
login.alreadyLoggedIn2=enheter. Logga ut från enheterna och försök igen.
|
||||
login.toManySessions=Du har för många aktiva sessioner
|
||||
login.logoutMessage=You have been logged out.
|
||||
login.invalidInResponseTo=The requested SAML response is invalid or has expired. Please contact the administrator.
|
||||
|
||||
#auto-redact
|
||||
autoRedact.title=Auto-redigera
|
||||
|
||||
@@ -455,6 +455,9 @@ adminUserSettings.confirmChangeUserStatus=ผู้ใช้นี้ควร
|
||||
adminUserSettings.usernameInfo=ชื่อผู้ใช้สามารถประกอบด้วยตัวอักษร ตัวเลข และอักขระพิเศษต่อไปนี้ @._+- หรือจะต้องเป็นที่อยู่อีเมลที่ถูกต้อง
|
||||
adminUserSettings.role=บทบาท
|
||||
adminUserSettings.actions=การดำเนินการ
|
||||
adminUserSettings.apiUser=ผู้ใช้ API จำกัด
|
||||
adminUserSettings.extraApiUser=ผู้ใช้ API เพิ่มเติม
|
||||
adminUserSettings.webOnlyUser=ผู้ใช้เว็บเท่านั้น
|
||||
adminUserSettings.demoUser=ผู้ใช้ทดลอง (ไม่มีการตั้งค่าปรับแต่ง)
|
||||
adminUserSettings.internalApiUser=ผู้ใช้ API ภายใน
|
||||
adminUserSettings.forceChange=บังคับให้ผู้ใช้เปลี่ยนรหัสผ่านในการเข้าสู่ระบบ
|
||||
@@ -905,6 +908,7 @@ login.alreadyLoggedIn=คุณได้เข้าสู่ระบบใน
|
||||
login.alreadyLoggedIn2=อุปกรณ์แล้ว กรุณาออกจากระบบจากอุปกรณ์ที่ใช้งานอยู่แล้ว จากนั้นลองใหม่อีกครั้ง
|
||||
login.toManySessions=คุณมีการเข้าสู่ระบบพร้อมกันเกินกว่ากำหนด
|
||||
login.logoutMessage=You have been logged out.
|
||||
login.invalidInResponseTo=The requested SAML response is invalid or has expired. Please contact the administrator.
|
||||
|
||||
#auto-redact
|
||||
autoRedact.title=ซ่อนข้อมูลอัตโนมัติ
|
||||
|
||||
@@ -455,6 +455,9 @@ adminUserSettings.confirmChangeUserStatus=Kullanıcı devre dışı bırakılmal
|
||||
adminUserSettings.usernameInfo=Kullanıcı adı yalnızca harf, rakam ve aşağıdaki özel karakterleri @._+- içerebilir veya geçerli bir e-posta adresi olmalıdır.
|
||||
adminUserSettings.role=Rol
|
||||
adminUserSettings.actions=Eylemler
|
||||
adminUserSettings.apiUser=Sınırlı API Kullanıcısı
|
||||
adminUserSettings.extraApiUser=Ek Sınırlı API Kullanıcısı
|
||||
adminUserSettings.webOnlyUser=Sadece Web Kullanıcısı
|
||||
adminUserSettings.demoUser=Demo Kullanıcısı (Özel ayar yok)
|
||||
adminUserSettings.internalApiUser=Dahili API Kullanıcısı
|
||||
adminUserSettings.forceChange=Kullanıcının girişte kullanıcı adı/şifre değiştirmesini zorla
|
||||
@@ -905,6 +908,7 @@ login.alreadyLoggedIn=Zaten şu cihazlarda oturum açılmış:
|
||||
login.alreadyLoggedIn2=Lütfen bu cihazlardan çıkış yaparak tekrar deneyin.
|
||||
login.toManySessions=Çok fazla aktif oturumunuz var
|
||||
login.logoutMessage=You have been logged out.
|
||||
login.invalidInResponseTo=The requested SAML response is invalid or has expired. Please contact the administrator.
|
||||
|
||||
#auto-redact
|
||||
autoRedact.title=Otomatik Karartma
|
||||
|
||||
@@ -455,6 +455,9 @@ adminUserSettings.confirmChangeUserStatus=Чи потрібно вимкнути
|
||||
adminUserSettings.usernameInfo=Ім’я користувача може містити лише літери, цифри та наступні спеціальні символи @._+- або має бути дійсною електронною адресою.
|
||||
adminUserSettings.role=Роль
|
||||
adminUserSettings.actions=Дії
|
||||
adminUserSettings.apiUser=Обмежений користувач API
|
||||
adminUserSettings.extraApiUser=Додатковий обмежений користувач API
|
||||
adminUserSettings.webOnlyUser=Тільки веб-користувач
|
||||
adminUserSettings.demoUser=Демо-користувач (без налаштованих параметрів)
|
||||
adminUserSettings.internalApiUser=Внутрішній користувач API
|
||||
adminUserSettings.forceChange=Примусити користувача змінити пароль при вході в систему
|
||||
@@ -905,6 +908,7 @@ login.alreadyLoggedIn=Ви вже увійшли до
|
||||
login.alreadyLoggedIn2=пристроїв (а). Будь ласка, вийдіть із цих пристроїв і спробуйте знову.
|
||||
login.toManySessions=У вас дуже багато активних сесій
|
||||
login.logoutMessage=You have been logged out.
|
||||
login.invalidInResponseTo=The requested SAML response is invalid or has expired. Please contact the administrator.
|
||||
|
||||
#auto-redact
|
||||
autoRedact.title=Автоматичне редагування
|
||||
|
||||
@@ -455,6 +455,9 @@ adminUserSettings.confirmChangeUserStatus=Should the user be disabled/enabled?
|
||||
adminUserSettings.usernameInfo=Tên người dùng chỉ có thể chứa chữ cái, số và các ký tự đặc biệt sau @._+- hoặc phải là một địa chỉ email hợp lệ.
|
||||
adminUserSettings.role=Vai trò
|
||||
adminUserSettings.actions=Hành động
|
||||
adminUserSettings.apiUser=Người dùng API giới hạn
|
||||
adminUserSettings.extraApiUser=Người dùng API giới hạn bổ sung
|
||||
adminUserSettings.webOnlyUser=Chỉ người dùng web
|
||||
adminUserSettings.demoUser=Người dùng demo (Không có cài đặt tùy chỉnh)
|
||||
adminUserSettings.internalApiUser=Người dùng API nội bộ
|
||||
adminUserSettings.forceChange=Buộc người dùng thay đổi mật khẩu khi đăng nhập
|
||||
@@ -905,6 +908,7 @@ login.alreadyLoggedIn=You are already logged in to
|
||||
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
||||
login.toManySessions=You have too many active sessions
|
||||
login.logoutMessage=You have been logged out.
|
||||
login.invalidInResponseTo=The requested SAML response is invalid or has expired. Please contact the administrator.
|
||||
|
||||
#auto-redact
|
||||
autoRedact.title=Tự động biên tập
|
||||
|
||||
@@ -455,6 +455,9 @@ adminUserSettings.confirmChangeUserStatus=是否应禁用/启用该用户?
|
||||
adminUserSettings.usernameInfo=用户名只能包含字母、数字和以下特殊字符@._+-,或者必须是有效的电子邮件地址。
|
||||
adminUserSettings.role=角色
|
||||
adminUserSettings.actions=操作
|
||||
adminUserSettings.apiUser=受限制的 API 用户
|
||||
adminUserSettings.extraApiUser=额外受限制的 API 用户
|
||||
adminUserSettings.webOnlyUser=仅限 Web 用户
|
||||
adminUserSettings.demoUser=演示用户(无自定义设置)
|
||||
adminUserSettings.internalApiUser=内部 API 用户
|
||||
adminUserSettings.forceChange=强制用户在登录时更改用户名/密码
|
||||
@@ -905,6 +908,7 @@ login.alreadyLoggedIn=您已经登录到了
|
||||
login.alreadyLoggedIn2=设备,请注销设备后重试。
|
||||
login.toManySessions=你已经有太多的会话了。请注销一些设备后重试。
|
||||
login.logoutMessage=您已退出登录。
|
||||
login.invalidInResponseTo=The requested SAML response is invalid or has expired. Please contact the administrator.
|
||||
|
||||
#auto-redact
|
||||
autoRedact.title=自动删除
|
||||
|
||||
@@ -455,6 +455,9 @@ adminUserSettings.confirmChangeUserStatus=是否要停用/啟用此使用者?
|
||||
adminUserSettings.usernameInfo=使用者名稱只能包含字母、數字和以下特殊字元 @._+- 或必須是有效的電子郵件地址。
|
||||
adminUserSettings.role=角色
|
||||
adminUserSettings.actions=操作
|
||||
adminUserSettings.apiUser=受限制的 API 使用者
|
||||
adminUserSettings.extraApiUser=額外受限制的 API 使用者
|
||||
adminUserSettings.webOnlyUser=僅網頁版使用者
|
||||
adminUserSettings.demoUser=示範使用者(無自訂設定)
|
||||
adminUserSettings.internalApiUser=內部 API 使用者
|
||||
adminUserSettings.forceChange=強制使用者在登入時變更密碼
|
||||
@@ -905,6 +908,7 @@ login.alreadyLoggedIn=您已經登入了
|
||||
login.alreadyLoggedIn2=部裝置。請先從這些裝置登出後再試一次。
|
||||
login.toManySessions=您有太多使用中的工作階段
|
||||
login.logoutMessage=您已登出。
|
||||
login.invalidInResponseTo=The requested SAML response is invalid or has expired. Please contact the administrator.
|
||||
|
||||
#auto-redact
|
||||
autoRedact.title=自動塗黑
|
||||
|
||||
@@ -59,12 +59,17 @@ security:
|
||||
idpCert: classpath:okta.cert # The certificate your Provider will use to authenticate your app's SAML authentication requests. Provided by your Provider
|
||||
privateKey: classpath:saml-private-key.key # Your private key. Generated from your keypair
|
||||
spCert: classpath:saml-public-cert.crt # Your signing certificate. Generated from your keypair
|
||||
jwt: # This feature is currently under development and not yet fully supported. Do not use in production.
|
||||
persistence: true # Set to 'true' to enable JWT key store
|
||||
enableKeyRotation: true # Set to 'true' to enable key pair rotation
|
||||
enableKeyCleanup: true # Set to 'true' to enable key pair cleanup
|
||||
keyRetentionDays: 7 # Number of days to retain old keys. The default is 7 days.
|
||||
secureCookie: false # Set to 'true' to use secure cookies for JWTs
|
||||
|
||||
premium:
|
||||
key: 00000000-0000-0000-0000-000000000000
|
||||
enabled: false # Enable license key checks for pro/enterprise features
|
||||
proFeatures:
|
||||
database: true # Enable database features
|
||||
SSOAutoLogin: false
|
||||
CustomMetadata:
|
||||
autoUpdateMetadata: false
|
||||
|
||||
@@ -132,6 +132,13 @@
|
||||
"moduleLicense": "Apache License, Version 2.0",
|
||||
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt"
|
||||
},
|
||||
{
|
||||
"moduleName": "com.github.ben-manes.caffeine:caffeine",
|
||||
"moduleUrl": "https://github.com/ben-manes/caffeine",
|
||||
"moduleVersion": "3.2.2",
|
||||
"moduleLicense": "Apache License, Version 2.0",
|
||||
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt"
|
||||
},
|
||||
{
|
||||
"moduleName": "com.github.jai-imageio:jai-imageio-core",
|
||||
"moduleUrl": "https://github.com/jai-imageio/jai-imageio-core",
|
||||
@@ -168,7 +175,7 @@
|
||||
{
|
||||
"moduleName": "com.google.errorprone:error_prone_annotations",
|
||||
"moduleUrl": "https://errorprone.info/error_prone_annotations",
|
||||
"moduleVersion": "2.38.0",
|
||||
"moduleVersion": "2.40.0",
|
||||
"moduleLicense": "Apache 2.0",
|
||||
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
|
||||
},
|
||||
@@ -549,6 +556,27 @@
|
||||
"moduleLicense": "MIT License",
|
||||
"moduleLicenseUrl": "http://www.opensource.org/licenses/mit-license.php"
|
||||
},
|
||||
{
|
||||
"moduleName": "io.jsonwebtoken:jjwt-api",
|
||||
"moduleUrl": "https://github.com/jwtk/jjwt",
|
||||
"moduleVersion": "0.12.6",
|
||||
"moduleLicense": "Apache License, Version 2.0",
|
||||
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
|
||||
},
|
||||
{
|
||||
"moduleName": "io.jsonwebtoken:jjwt-impl",
|
||||
"moduleUrl": "https://github.com/jwtk/jjwt",
|
||||
"moduleVersion": "0.12.6",
|
||||
"moduleLicense": "Apache License, Version 2.0",
|
||||
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
|
||||
},
|
||||
{
|
||||
"moduleName": "io.jsonwebtoken:jjwt-jackson",
|
||||
"moduleUrl": "https://github.com/jwtk/jjwt",
|
||||
"moduleVersion": "0.12.6",
|
||||
"moduleLicense": "Apache License, Version 2.0",
|
||||
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
|
||||
},
|
||||
{
|
||||
"moduleName": "io.micrometer:micrometer-commons",
|
||||
"moduleUrl": "https://github.com/micrometer-metrics/micrometer",
|
||||
@@ -1507,6 +1535,13 @@
|
||||
"moduleLicense": "Apache License, Version 2.0",
|
||||
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
|
||||
},
|
||||
{
|
||||
"moduleName": "org.springframework.boot:spring-boot-starter-cache",
|
||||
"moduleUrl": "https://spring.io/projects/spring-boot",
|
||||
"moduleVersion": "3.5.4",
|
||||
"moduleLicense": "Apache License, Version 2.0",
|
||||
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
|
||||
},
|
||||
{
|
||||
"moduleName": "org.springframework.boot:spring-boot-starter-data-jpa",
|
||||
"moduleUrl": "https://spring.io/projects/spring-boot",
|
||||
|
||||
@@ -46,10 +46,9 @@ export class DecryptFile {
|
||||
formData.append('password', password);
|
||||
}
|
||||
// Send decryption request
|
||||
const response = await fetch('/api/v1/security/remove-password', {
|
||||
const response = await fetchWithCsrf('/api/v1/security/remove-password', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
headers: csrfToken ? {'X-XSRF-TOKEN': csrfToken} : undefined,
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
|
||||
@@ -218,7 +218,7 @@
|
||||
formData.append('password', password);
|
||||
|
||||
// Use handleSingleDownload to send the request
|
||||
const decryptionResult = await fetch(removePasswordUrl, {method: 'POST', body: formData});
|
||||
const decryptionResult = await fetchWithCsrf(removePasswordUrl, {method: 'POST', body: formData});
|
||||
|
||||
if (decryptionResult && decryptionResult.blob) {
|
||||
const decryptedBlob = await decryptionResult.blob();
|
||||
|
||||
@@ -1,3 +1,29 @@
|
||||
// Authentication utility for cookie-based JWT
|
||||
window.JWTManager = {
|
||||
|
||||
// Logout - clear cookies and redirect to login
|
||||
logout: function() {
|
||||
|
||||
// Clear JWT cookie manually (fallback)
|
||||
document.cookie = 'stirling_jwt=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT; SameSite=None; Secure';
|
||||
|
||||
// Perform logout request to clear server-side session
|
||||
fetch('/logout', {
|
||||
method: 'POST',
|
||||
credentials: 'include'
|
||||
}).then(response => {
|
||||
if (response.redirected) {
|
||||
window.location.href = response.url;
|
||||
} else {
|
||||
window.location.href = '/login?logout=true';
|
||||
}
|
||||
}).catch(() => {
|
||||
// If logout fails, let server handle it
|
||||
window.location.href = '/logout';
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
window.fetchWithCsrf = async function(url, options = {}) {
|
||||
function getCsrfToken() {
|
||||
const cookieValue = document.cookie
|
||||
@@ -24,5 +50,18 @@ window.fetchWithCsrf = async function(url, options = {}) {
|
||||
fetchOptions.headers['X-XSRF-TOKEN'] = csrfToken;
|
||||
}
|
||||
|
||||
return fetch(url, fetchOptions);
|
||||
// Always include credentials to send JWT cookies
|
||||
fetchOptions.credentials = 'include';
|
||||
|
||||
// Make the request
|
||||
const response = await fetch(url, fetchOptions);
|
||||
|
||||
// Handle 401 responses (unauthorized)
|
||||
if (response.status === 401) {
|
||||
console.warn('Authentication failed, redirecting to login');
|
||||
window.JWTManager.logout();
|
||||
return response;
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
// JWT Authentication Management Script
|
||||
// This script handles cookie-based JWT authentication and page access control
|
||||
|
||||
(function() {
|
||||
// Clean up JWT token from URL parameters after OAuth/Login flows
|
||||
function cleanupTokenFromUrl() {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const hasToken = urlParams.get('jwt') || urlParams.get('token');
|
||||
if (hasToken) {
|
||||
// Clean up URL by removing token parameter
|
||||
// Token should now be set as cookie by server
|
||||
urlParams.delete('jwt');
|
||||
urlParams.delete('token');
|
||||
const newUrl = window.location.pathname + (urlParams.toString() ? '?' + urlParams.toString() : '');
|
||||
window.history.replaceState({}, '', newUrl);
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize JWT handling when page loads
|
||||
function initializeJWT() {
|
||||
// Clean up any JWT tokens from URL (OAuth flow)
|
||||
cleanupTokenFromUrl();
|
||||
|
||||
// Authentication is handled server-side
|
||||
// If user is not authenticated, server will redirect to login
|
||||
console.log('JWT initialization complete - authentication handled server-side');
|
||||
}
|
||||
|
||||
// No form enhancement needed for cookie-based JWT
|
||||
// Cookies are automatically sent with form submissions
|
||||
function enhanceFormSubmissions() {
|
||||
// Cookie-based JWT is automatically included in form submissions
|
||||
// No additional processing needed
|
||||
}
|
||||
|
||||
// Initialize when DOM is ready
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
initializeJWT();
|
||||
});
|
||||
} else {
|
||||
initializeJWT();
|
||||
}
|
||||
})();
|
||||
@@ -138,5 +138,19 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
tooltipSetup();
|
||||
setupDropdowns();
|
||||
fixNavbarDropdownStyles();
|
||||
// Setup logout button functionality
|
||||
const logoutButton = document.querySelector('a[href="/logout"]');
|
||||
if (logoutButton) {
|
||||
logoutButton.addEventListener('click', function(event) {
|
||||
event.preventDefault();
|
||||
if (window.JWTManager) {
|
||||
window.JWTManager.logout();
|
||||
} else {
|
||||
// Fallback if JWTManager is not available
|
||||
window.location.href = '/logout';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
window.addEventListener('resize', fixNavbarDropdownStyles);
|
||||
|
||||
@@ -102,7 +102,7 @@ async function fetchEndpointData() {
|
||||
refreshBtn.classList.add('refreshing');
|
||||
refreshBtn.disabled = true;
|
||||
|
||||
const response = await fetch('/api/v1/info/load/all');
|
||||
const response = await fetchWithCsrf('/api/v1/info/load/all');
|
||||
if (!response.ok) {
|
||||
throw new Error('Network response was not ok');
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Admin Settings Banner (for admins only) -->
|
||||
<div th:if="${isSystemAdmin}" class="data-panel data-mb-3" style="background-color: var(--md-sys-color-secondary-container);">
|
||||
<div th:if="${role == 'ROLE_ADMIN'}" class="data-panel data-mb-3" style="background-color: var(--md-sys-color-secondary-container);">
|
||||
<div class="data-body" style="display: flex; align-items: center; justify-content: space-between; padding: 1rem 1.5rem; background-color: var(--md-sys-color-secondary-container);">
|
||||
<div style="display: flex; align-items: center; gap: 1rem;">
|
||||
<span class="material-symbols-rounded" style="font-size: 2rem; color: var(--md-sys-color-secondary);">
|
||||
@@ -390,8 +390,13 @@
|
||||
key.includes('clientSubmissionOrder') ||
|
||||
key.includes('lastSubmitTime') ||
|
||||
key.includes('lastClientId') ||
|
||||
|
||||
|
||||
key.includes('stirling_jwt') ||
|
||||
key.includes('JSESSIONID') ||
|
||||
key.includes('XSRF-TOKEN') ||
|
||||
key.includes('remember-me') ||
|
||||
key.includes('auth') ||
|
||||
key.includes('token') ||
|
||||
key.includes('session') ||
|
||||
key.includes('posthog') || key.includes('ssoRedirectAttempts') || key.includes('lastRedirectAttempt') || key.includes('surveyVersion') ||
|
||||
key.includes('pageViews');
|
||||
}
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
repositories {
|
||||
maven { url = "https://build.shibboleth.net/maven/releases" }
|
||||
}
|
||||
|
||||
ext {
|
||||
jwtVersion = '0.12.6'
|
||||
}
|
||||
|
||||
bootRun {
|
||||
enabled = false
|
||||
}
|
||||
|
||||
spotless {
|
||||
java {
|
||||
target 'src/**/java/**/*.java'
|
||||
@@ -43,6 +49,8 @@ dependencies {
|
||||
api 'org.springframework.boot:spring-boot-starter-data-jpa'
|
||||
api 'org.springframework.boot:spring-boot-starter-oauth2-client'
|
||||
api 'org.springframework.boot:spring-boot-starter-mail'
|
||||
api 'org.springframework.boot:spring-boot-starter-cache'
|
||||
api 'com.github.ben-manes.caffeine:caffeine'
|
||||
api 'io.swagger.core.v3:swagger-core-jakarta:2.2.35'
|
||||
implementation 'com.bucket4j:bucket4j_jdk17-core:8.14.0'
|
||||
|
||||
@@ -52,6 +60,10 @@ dependencies {
|
||||
// implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5:3.1.3.RELEASE' // Removed - UI moved to React frontend
|
||||
api 'io.micrometer:micrometer-registry-prometheus'
|
||||
implementation 'com.unboundid.product.scim2:scim2-sdk-client:4.0.0'
|
||||
|
||||
api "io.jsonwebtoken:jjwt-api:$jwtVersion"
|
||||
runtimeOnly "io.jsonwebtoken:jjwt-impl:$jwtVersion"
|
||||
runtimeOnly "io.jsonwebtoken:jjwt-jackson:$jwtVersion"
|
||||
runtimeOnly 'com.h2database:h2:2.3.232' // Don't upgrade h2database
|
||||
runtimeOnly 'org.postgresql:postgresql:42.7.7'
|
||||
constraints {
|
||||
|
||||
-71
@@ -1,71 +0,0 @@
|
||||
package stirling.software.proprietary.config;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.boot.CommandLineRunner;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import stirling.software.common.model.enumeration.Role;
|
||||
import stirling.software.proprietary.service.ApiCreditService;
|
||||
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "api.credit-system")
|
||||
@Data
|
||||
@Slf4j
|
||||
public class ApiCreditConfiguration {
|
||||
|
||||
private boolean enabled = true;
|
||||
private boolean excludeSettings = true;
|
||||
private boolean excludeActuator = true;
|
||||
private int defaultCreditCost = 1;
|
||||
|
||||
/**
|
||||
* Default monthly credit limits by role. Override in application.yml/properties. Note:
|
||||
* Integer.MAX_VALUE is treated as "unlimited".
|
||||
*/
|
||||
private Map<String, Integer> defaultCreditLimits =
|
||||
Map.of(
|
||||
"ROLE_SYSTEM_ADMIN", Integer.MAX_VALUE,
|
||||
"ROLE_ORG_ADMIN", 10000,
|
||||
"ROLE_TEAM_LEAD", 5000,
|
||||
"ROLE_ADMIN", Integer.MAX_VALUE,
|
||||
"ROLE_USER", 50,
|
||||
"ROLE_DEMO_USER", 20,
|
||||
"STIRLING-PDF-BACKEND-API-USER", Integer.MAX_VALUE);
|
||||
|
||||
@Bean
|
||||
public CommandLineRunner initializeDefaultCreditLimits(ApiCreditService creditService) {
|
||||
return args -> {
|
||||
if (!enabled) {
|
||||
log.info("API credit system is disabled");
|
||||
return;
|
||||
}
|
||||
log.info("Initializing default API credit limits...");
|
||||
initializeDefaults(creditService);
|
||||
log.info("Default API credit limits initialized successfully");
|
||||
};
|
||||
}
|
||||
|
||||
private void initializeDefaults(ApiCreditService creditService) {
|
||||
for (Map.Entry<String, Integer> entry : defaultCreditLimits.entrySet()) {
|
||||
String roleName = entry.getKey();
|
||||
Integer creditLimit = entry.getValue();
|
||||
|
||||
try {
|
||||
Role.fromString(roleName);
|
||||
creditService.createOrUpdateRoleDefault(roleName, creditLimit);
|
||||
log.debug(
|
||||
"Set default credit limit for role {} to {}/month",
|
||||
roleName,
|
||||
creditLimit == Integer.MAX_VALUE ? "unlimited" : creditLimit);
|
||||
} catch (IllegalArgumentException e) {
|
||||
log.warn("Skipping unknown role in credit system config: {}", roleName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package stirling.software.proprietary.config;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
|
||||
/** Configuration to explicitly enable JPA repositories and scheduling for the audit system. */
|
||||
@Configuration
|
||||
@EnableTransactionManagement
|
||||
@EnableJpaRepositories(basePackages = "stirling.software.proprietary.repository")
|
||||
@EnableScheduling
|
||||
public class AuditJpaConfig {
|
||||
// This configuration enables JPA repositories in the specified package
|
||||
// and enables scheduling for audit cleanup tasks
|
||||
// No additional beans or methods needed
|
||||
}
|
||||
+3
-3
@@ -18,7 +18,7 @@ import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import stirling.software.proprietary.model.security.PersistentAuditEvent;
|
||||
import stirling.software.proprietary.security.repository.PersistentAuditEventRepository;
|
||||
import stirling.software.proprietary.repository.PersistentAuditEventRepository;
|
||||
import stirling.software.proprietary.util.SecretMasker;
|
||||
|
||||
@Component
|
||||
@@ -30,13 +30,13 @@ public class CustomAuditEventRepository implements AuditEventRepository {
|
||||
private final PersistentAuditEventRepository repo;
|
||||
private final ObjectMapper mapper;
|
||||
|
||||
/* READ side intentionally inert (endpoint disabled) */
|
||||
/* ── READ side intentionally inert (endpoint disabled) ── */
|
||||
@Override
|
||||
public List<AuditEvent> find(String p, Instant after, String type) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
/* WRITE side (async) */
|
||||
/* ── WRITE side (async) ───────────────────────────────── */
|
||||
@Async("auditExecutor")
|
||||
@Override
|
||||
public void add(AuditEvent ev) {
|
||||
|
||||
+3
-3
@@ -33,7 +33,7 @@ public class AdminJobController {
|
||||
* @return Job statistics
|
||||
*/
|
||||
@GetMapping("/api/v1/admin/job/stats")
|
||||
@PreAuthorize("@roleBasedAuthorizationService.canManageAllUsers()")
|
||||
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
||||
public ResponseEntity<JobStats> getJobStats() {
|
||||
JobStats stats = taskManager.getJobStats();
|
||||
log.info(
|
||||
@@ -49,7 +49,7 @@ public class AdminJobController {
|
||||
* @return Queue statistics
|
||||
*/
|
||||
@GetMapping("/api/v1/admin/job/queue/stats")
|
||||
@PreAuthorize("@roleBasedAuthorizationService.canManageAllUsers()")
|
||||
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
||||
public ResponseEntity<?> getQueueStats() {
|
||||
Map<String, Object> queueStats = jobQueue.getQueueStats();
|
||||
log.info("Admin requested queue stats: {} queued jobs", queueStats.get("queuedJobs"));
|
||||
@@ -62,7 +62,7 @@ public class AdminJobController {
|
||||
* @return A response indicating how many jobs were cleaned up
|
||||
*/
|
||||
@PostMapping("/api/v1/admin/job/cleanup")
|
||||
@PreAuthorize("@roleBasedAuthorizationService.canManageAllUsers()")
|
||||
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
||||
public ResponseEntity<?> cleanupOldJobs() {
|
||||
int beforeCount = taskManager.getJobStats().getTotalJobs();
|
||||
taskManager.cleanupOldJobs();
|
||||
|
||||
+2
-2
@@ -40,14 +40,14 @@ import stirling.software.proprietary.audit.AuditEventType;
|
||||
import stirling.software.proprietary.audit.AuditLevel;
|
||||
import stirling.software.proprietary.config.AuditConfigurationProperties;
|
||||
import stirling.software.proprietary.model.security.PersistentAuditEvent;
|
||||
import stirling.software.proprietary.security.repository.PersistentAuditEventRepository;
|
||||
import stirling.software.proprietary.repository.PersistentAuditEventRepository;
|
||||
import stirling.software.proprietary.security.config.EnterpriseEndpoint;
|
||||
|
||||
/** Controller for the audit dashboard. Admin-only access. */
|
||||
@Slf4j
|
||||
// @Controller // Disabled - Backend-only mode, no Thymeleaf UI
|
||||
@RequestMapping("/audit")
|
||||
@PreAuthorize("@roleBasedAuthorizationService.canManageAllUsers()")
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
@RequiredArgsConstructor
|
||||
@EnterpriseEndpoint
|
||||
public class AuditDashboardController {
|
||||
|
||||
-173
@@ -1,173 +0,0 @@
|
||||
package stirling.software.proprietary.controller.api;
|
||||
|
||||
import java.time.YearMonth;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.ZonedDateTime;
|
||||
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponses;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import stirling.software.proprietary.security.model.User;
|
||||
import stirling.software.proprietary.security.service.UserService;
|
||||
import stirling.software.proprietary.service.ApiCreditService;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/credits")
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
@Tag(
|
||||
name = "API Credits",
|
||||
description = "Endpoints for managing and viewing API credit limits and usage")
|
||||
public class ApiCreditController {
|
||||
|
||||
private final ApiCreditService creditService;
|
||||
private final UserService userService;
|
||||
|
||||
public record CreditMetricsResponse(
|
||||
int creditsConsumed,
|
||||
int monthlyCredits,
|
||||
int remaining,
|
||||
String scope,
|
||||
String month,
|
||||
boolean isPooled,
|
||||
long resetEpochMillis) {}
|
||||
|
||||
public record UpdateCreditLimitRequest(int monthlyCredits, Boolean isActive) {}
|
||||
|
||||
public record CreateUserCreditConfigRequest(
|
||||
String username, int monthlyCredits, boolean isActive) {}
|
||||
|
||||
public record CreateOrgCreditConfigRequest(
|
||||
String organizationName, int monthlyCredits, boolean isPooled, boolean isActive) {}
|
||||
|
||||
@Operation(
|
||||
summary = "Get current user's credit metrics",
|
||||
description =
|
||||
"Returns the current user's credit consumption, limits, and remaining credits for the current month")
|
||||
@ApiResponses({
|
||||
@ApiResponse(responseCode = "200", description = "Credit metrics retrieved successfully"),
|
||||
@ApiResponse(responseCode = "401", description = "User not authenticated")
|
||||
})
|
||||
@GetMapping("/my-usage")
|
||||
public ResponseEntity<CreditMetricsResponse> getMyCredits(Authentication authentication) {
|
||||
if (authentication == null || !authentication.isAuthenticated()) {
|
||||
return ResponseEntity.status(401).build();
|
||||
}
|
||||
|
||||
User user =
|
||||
userService
|
||||
.findByUsername(authentication.getName())
|
||||
.orElseThrow(() -> new RuntimeException("User not found"));
|
||||
|
||||
ApiCreditService.CreditMetrics metrics = creditService.getUserCreditMetrics(user);
|
||||
|
||||
YearMonth nextMonth = metrics.month().plusMonths(1);
|
||||
ZonedDateTime resetTime = nextMonth.atDay(1).atStartOfDay(ZoneOffset.UTC);
|
||||
long resetEpochMillis = resetTime.toInstant().toEpochMilli();
|
||||
|
||||
CreditMetricsResponse response =
|
||||
new CreditMetricsResponse(
|
||||
metrics.creditsConsumed(),
|
||||
metrics.monthlyCredits(),
|
||||
metrics.remaining(),
|
||||
metrics.scope(),
|
||||
metrics.month().toString(),
|
||||
metrics.isPooled(),
|
||||
resetEpochMillis);
|
||||
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
|
||||
@Operation(
|
||||
summary = "Get user credit metrics",
|
||||
description = "Returns credit metrics for a specific user (admin only)")
|
||||
@PreAuthorize("@roleBasedAuthorizationService.canManageAllUsers()")
|
||||
@GetMapping("/user/{username}")
|
||||
public ResponseEntity<CreditMetricsResponse> getUserCredits(
|
||||
@Parameter(description = "Username to get credit metrics for") @PathVariable
|
||||
String username) {
|
||||
User user = userService.findByUsername(username).orElse(null);
|
||||
|
||||
if (user == null) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
|
||||
ApiCreditService.CreditMetrics metrics = creditService.getUserCreditMetrics(user);
|
||||
|
||||
YearMonth nextMonth = metrics.month().plusMonths(1);
|
||||
ZonedDateTime resetTime = nextMonth.atDay(1).atStartOfDay(ZoneOffset.UTC);
|
||||
long resetEpochMillis = resetTime.toInstant().toEpochMilli();
|
||||
|
||||
CreditMetricsResponse response =
|
||||
new CreditMetricsResponse(
|
||||
metrics.creditsConsumed(),
|
||||
metrics.monthlyCredits(),
|
||||
metrics.remaining(),
|
||||
metrics.scope(),
|
||||
metrics.month().toString(),
|
||||
metrics.isPooled(),
|
||||
resetEpochMillis);
|
||||
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
|
||||
@Operation(
|
||||
summary = "Create user-specific credit limit",
|
||||
description = "Create a credit limit configuration for a specific user (admin only)")
|
||||
@PreAuthorize("@roleBasedAuthorizationService.canManageAllUsers()")
|
||||
@PostMapping("/config/user")
|
||||
public ResponseEntity<?> createUserCreditConfig(
|
||||
@RequestBody CreateUserCreditConfigRequest request) {
|
||||
try {
|
||||
User user = userService.findByUsername(request.username()).orElse(null);
|
||||
|
||||
if (user == null) {
|
||||
return ResponseEntity.badRequest().body("User not found: " + request.username());
|
||||
}
|
||||
|
||||
creditService.createUserCreditConfig(user, request.monthlyCredits(), request.isActive());
|
||||
return ResponseEntity.ok().body("User credit configuration created successfully");
|
||||
} catch (Exception e) {
|
||||
log.error("Error creating user credit config", e);
|
||||
return ResponseEntity.badRequest().body("Error: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Operation(
|
||||
summary = "Update role default credit limits",
|
||||
description = "Update the default credit limit for a specific role (admin only)")
|
||||
@PreAuthorize("@roleBasedAuthorizationService.canManageAllUsers()")
|
||||
@PutMapping("/config/role/{roleName}")
|
||||
public ResponseEntity<?> updateRoleDefault(
|
||||
@Parameter(description = "Role name to update") @PathVariable String roleName,
|
||||
@RequestBody UpdateCreditLimitRequest request) {
|
||||
try {
|
||||
creditService.createOrUpdateRoleDefault(roleName, request.monthlyCredits());
|
||||
return ResponseEntity.ok().body("Role default updated successfully");
|
||||
} catch (Exception e) {
|
||||
log.error("Error updating role default", e);
|
||||
return ResponseEntity.badRequest().body("Error: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Operation(
|
||||
summary = "Get credit system status",
|
||||
description = "Returns basic information about the credit system configuration")
|
||||
@GetMapping("/status")
|
||||
public ResponseEntity<?> getCreditSystemStatus() {
|
||||
return ResponseEntity.ok()
|
||||
.body(
|
||||
"API Credit System is active. Use /api/v1/credits/my-usage to check your credit balance.");
|
||||
}
|
||||
}
|
||||
-119
@@ -1,119 +0,0 @@
|
||||
package stirling.software.proprietary.model;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.Instant;
|
||||
import java.time.YearMonth;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.hibernate.annotations.CreationTimestamp;
|
||||
import org.hibernate.annotations.UpdateTimestamp;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
|
||||
import lombok.*;
|
||||
|
||||
import stirling.software.proprietary.model.converter.YearMonthStringConverter;
|
||||
|
||||
@Entity
|
||||
@Table(
|
||||
name = "anonymous_api_credit_usage",
|
||||
uniqueConstraints = {
|
||||
@UniqueConstraint(
|
||||
name = "uq_anon_credit_fingerprint_month",
|
||||
columnNames = {"fingerprint", "month"})
|
||||
},
|
||||
indexes = {
|
||||
@Index(name = "idx_anon_credit_fingerprint", columnList = "fingerprint"),
|
||||
@Index(name = "idx_anon_credit_month", columnList = "month"),
|
||||
@Index(name = "idx_anon_credit_ip", columnList = "ip_address")
|
||||
})
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@Getter
|
||||
@Setter
|
||||
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
|
||||
@ToString(onlyExplicitlyIncluded = true)
|
||||
public class AnonymousCreditUsage implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Column(name = "id")
|
||||
@EqualsAndHashCode.Include
|
||||
@ToString.Include
|
||||
private Long id;
|
||||
|
||||
@NotNull
|
||||
@Column(name = "fingerprint", nullable = false, length = 128)
|
||||
@ToString.Include
|
||||
private String fingerprint;
|
||||
|
||||
@NotNull
|
||||
@Column(name = "month", nullable = false, length = 7)
|
||||
@Convert(converter = YearMonthStringConverter.class)
|
||||
private YearMonth month;
|
||||
|
||||
@NotNull
|
||||
@Column(name = "credits_consumed", nullable = false)
|
||||
@Builder.Default
|
||||
private Integer creditsConsumed = 0;
|
||||
|
||||
@NotNull
|
||||
@Column(name = "credits_allocated", nullable = false)
|
||||
@Builder.Default
|
||||
private Integer creditsAllocated = 0;
|
||||
|
||||
@Column(name = "ip_address", length = 45)
|
||||
private String ipAddress;
|
||||
|
||||
@Column(name = "user_agent", length = 512)
|
||||
private String userAgent;
|
||||
|
||||
@ElementCollection(fetch = FetchType.EAGER)
|
||||
@CollectionTable(
|
||||
name = "anonymous_api_related_fingerprints",
|
||||
joinColumns = @JoinColumn(name = "usage_id"))
|
||||
@Column(name = "related_fingerprint")
|
||||
@Builder.Default
|
||||
private Set<String> relatedFingerprints = new HashSet<>();
|
||||
|
||||
@Column(name = "abuse_score")
|
||||
@Builder.Default
|
||||
private Integer abuseScore = 0;
|
||||
|
||||
@Column(name = "is_blocked")
|
||||
@Builder.Default
|
||||
private Boolean isBlocked = false;
|
||||
|
||||
@Column(name = "last_access")
|
||||
private Instant lastAccess;
|
||||
|
||||
@CreationTimestamp
|
||||
@Column(name = "created_at", updatable = false, nullable = false)
|
||||
private Instant createdAt;
|
||||
|
||||
@UpdateTimestamp
|
||||
@Column(name = "updated_at", nullable = false)
|
||||
private Instant updatedAt;
|
||||
|
||||
@Version
|
||||
@Column(name = "version")
|
||||
private Long version;
|
||||
|
||||
@PreUpdate
|
||||
public void preUpdate() {
|
||||
this.lastAccess = Instant.now();
|
||||
}
|
||||
|
||||
public int getRemainingCredits() {
|
||||
return Math.max(0, creditsAllocated - creditsConsumed);
|
||||
}
|
||||
|
||||
public boolean hasCreditsRemaining(int creditCost) {
|
||||
return getRemainingCredits() >= creditCost;
|
||||
}
|
||||
}
|
||||
-132
@@ -1,132 +0,0 @@
|
||||
package stirling.software.proprietary.model;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.Instant;
|
||||
|
||||
import org.hibernate.annotations.CreationTimestamp;
|
||||
import org.hibernate.annotations.UpdateTimestamp;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
|
||||
import lombok.*;
|
||||
|
||||
import stirling.software.proprietary.security.model.User;
|
||||
|
||||
@Entity
|
||||
@Table(
|
||||
name = "api_credit_configs",
|
||||
uniqueConstraints = {
|
||||
@UniqueConstraint(
|
||||
name = "uq_user_credit_cfg",
|
||||
columnNames = {"scope_type", "user_id"}),
|
||||
@UniqueConstraint(
|
||||
name = "uq_org_credit_cfg",
|
||||
columnNames = {"scope_type", "org_id"}),
|
||||
@UniqueConstraint(
|
||||
name = "uq_role_credit_cfg",
|
||||
columnNames = {"scope_type", "role_name"})
|
||||
},
|
||||
indexes = {
|
||||
@Index(name = "idx_credit_cfg_user", columnList = "user_id"),
|
||||
@Index(name = "idx_credit_cfg_org", columnList = "org_id"),
|
||||
@Index(name = "idx_credit_cfg_role", columnList = "role_name"),
|
||||
@Index(name = "idx_credit_cfg_scope", columnList = "scope_type")
|
||||
})
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@Getter
|
||||
@Setter
|
||||
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
|
||||
@ToString(onlyExplicitlyIncluded = true)
|
||||
public class ApiCreditConfig implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public enum ScopeType {
|
||||
USER,
|
||||
ORGANIZATION,
|
||||
ROLE_DEFAULT
|
||||
}
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Column(name = "id")
|
||||
@EqualsAndHashCode.Include
|
||||
@ToString.Include
|
||||
private Long id;
|
||||
|
||||
@NotNull
|
||||
@Column(name = "scope_type", nullable = false, length = 20)
|
||||
@Enumerated(EnumType.STRING)
|
||||
private ScopeType scopeType;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "user_id", nullable = true)
|
||||
private User user;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "org_id", nullable = true)
|
||||
private Organization organization;
|
||||
|
||||
@Column(name = "role_name", nullable = true, length = 50)
|
||||
private String roleName;
|
||||
|
||||
@NotNull
|
||||
@Min(0)
|
||||
@Column(name = "monthly_credits", nullable = false)
|
||||
private Integer monthlyCredits;
|
||||
|
||||
@NotNull
|
||||
@Builder.Default
|
||||
@Column(name = "is_pooled", nullable = false)
|
||||
private Boolean isPooled = false;
|
||||
|
||||
@NotNull
|
||||
@Builder.Default
|
||||
@Column(name = "is_active", nullable = false)
|
||||
private Boolean isActive = true;
|
||||
|
||||
@CreationTimestamp
|
||||
@Column(name = "created_at", updatable = false, nullable = false)
|
||||
private Instant createdAt;
|
||||
|
||||
@UpdateTimestamp
|
||||
@Column(name = "updated_at", nullable = false)
|
||||
private Instant updatedAt;
|
||||
|
||||
@Version
|
||||
@Column(name = "version")
|
||||
private Long version;
|
||||
|
||||
@PrePersist
|
||||
@PreUpdate
|
||||
protected void validateScope() {
|
||||
int nonNullCount = 0;
|
||||
if (user != null) nonNullCount++;
|
||||
if (organization != null) nonNullCount++;
|
||||
if (roleName != null) nonNullCount++;
|
||||
|
||||
if (nonNullCount != 1) {
|
||||
throw new IllegalStateException(
|
||||
"Exactly one of user, organization, or roleName must be set");
|
||||
}
|
||||
|
||||
if (user != null && scopeType != ScopeType.USER) {
|
||||
throw new IllegalStateException("ScopeType must be USER when user is set");
|
||||
}
|
||||
if (organization != null && scopeType != ScopeType.ORGANIZATION) {
|
||||
throw new IllegalStateException(
|
||||
"ScopeType must be ORGANIZATION when organization is set");
|
||||
}
|
||||
if (roleName != null && scopeType != ScopeType.ROLE_DEFAULT) {
|
||||
throw new IllegalStateException("ScopeType must be ROLE_DEFAULT when roleName is set");
|
||||
}
|
||||
|
||||
if (Boolean.TRUE.equals(isPooled) && scopeType != ScopeType.ORGANIZATION) {
|
||||
throw new IllegalStateException("isPooled can only be true for ORGANIZATION scope");
|
||||
}
|
||||
}
|
||||
}
|
||||
-127
@@ -1,127 +0,0 @@
|
||||
package stirling.software.proprietary.model;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.Instant;
|
||||
import java.time.YearMonth;
|
||||
import java.time.ZoneOffset;
|
||||
|
||||
import org.hibernate.annotations.CreationTimestamp;
|
||||
import org.hibernate.annotations.UpdateTimestamp;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
|
||||
import lombok.*;
|
||||
|
||||
import stirling.software.proprietary.security.model.User;
|
||||
|
||||
@Entity
|
||||
@Table(
|
||||
name = "api_credit_usage",
|
||||
uniqueConstraints = {
|
||||
@UniqueConstraint(
|
||||
name = "uq_credit_user_month",
|
||||
columnNames = {"user_id", "month_key"}),
|
||||
@UniqueConstraint(
|
||||
name = "uq_credit_org_month",
|
||||
columnNames = {"org_id", "month_key"})
|
||||
},
|
||||
indexes = {
|
||||
@Index(name = "idx_credit_usage_user_month", columnList = "user_id, month_key"),
|
||||
@Index(name = "idx_credit_usage_org_month", columnList = "org_id, month_key"),
|
||||
@Index(name = "idx_credit_usage_month", columnList = "month_key")
|
||||
})
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@Getter
|
||||
@Setter
|
||||
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
|
||||
@ToString(onlyExplicitlyIncluded = true)
|
||||
public class ApiCreditUsage implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Column(name = "id")
|
||||
@EqualsAndHashCode.Include
|
||||
@ToString.Include
|
||||
private Long id;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "user_id", nullable = true)
|
||||
private User user;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "org_id", nullable = true)
|
||||
private Organization organization;
|
||||
|
||||
@NotNull
|
||||
@Column(name = "month_key", nullable = false, length = 7)
|
||||
@ToString.Include
|
||||
private YearMonth monthKey;
|
||||
|
||||
@NotNull
|
||||
@Min(0)
|
||||
@Builder.Default
|
||||
@Column(name = "credits_consumed", nullable = false)
|
||||
private Integer creditsConsumed = 0;
|
||||
|
||||
@NotNull
|
||||
@Min(0)
|
||||
@Builder.Default
|
||||
@Column(name = "credits_allocated", nullable = false)
|
||||
private Integer creditsAllocated = 0;
|
||||
|
||||
@CreationTimestamp
|
||||
@Column(name = "created_at", updatable = false, nullable = false)
|
||||
private Instant createdAt;
|
||||
|
||||
@UpdateTimestamp
|
||||
@Column(name = "updated_at", nullable = false)
|
||||
private Instant updatedAt;
|
||||
|
||||
@Version
|
||||
@Column(name = "version")
|
||||
private Long version;
|
||||
|
||||
@PrePersist
|
||||
@PreUpdate
|
||||
protected void validateScope() {
|
||||
if ((user == null && organization == null) || (user != null && organization != null)) {
|
||||
throw new IllegalStateException("Exactly one of user or organization must be set");
|
||||
}
|
||||
}
|
||||
|
||||
public static YearMonth getCurrentMonth() {
|
||||
return YearMonth.now(ZoneOffset.UTC);
|
||||
}
|
||||
|
||||
public static ApiCreditUsage forUser(User user, int creditsAllocated) {
|
||||
return ApiCreditUsage.builder()
|
||||
.user(user)
|
||||
.monthKey(getCurrentMonth())
|
||||
.creditsConsumed(0)
|
||||
.creditsAllocated(creditsAllocated)
|
||||
.build();
|
||||
}
|
||||
|
||||
public static ApiCreditUsage forOrganization(Organization org, int creditsAllocated) {
|
||||
return ApiCreditUsage.builder()
|
||||
.organization(org)
|
||||
.monthKey(getCurrentMonth())
|
||||
.creditsConsumed(0)
|
||||
.creditsAllocated(creditsAllocated)
|
||||
.build();
|
||||
}
|
||||
|
||||
public int getRemainingCredits() {
|
||||
return Math.max(0, creditsAllocated - creditsConsumed);
|
||||
}
|
||||
|
||||
public boolean hasCreditsRemaining(int creditCost) {
|
||||
return getRemainingCredits() >= creditCost;
|
||||
}
|
||||
}
|
||||
-83
@@ -1,83 +0,0 @@
|
||||
package stirling.software.proprietary.model;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import stirling.software.proprietary.security.model.User;
|
||||
|
||||
/** Holds context information for credit-based API requests */
|
||||
public class CreditRequestContext {
|
||||
|
||||
private final String requestId;
|
||||
private final User user;
|
||||
private final String ipAddress;
|
||||
private final String userAgent;
|
||||
private final int creditCost;
|
||||
private final String endpoint;
|
||||
private boolean creditsPreChecked = false;
|
||||
private HttpServletResponse httpResponse;
|
||||
|
||||
public CreditRequestContext(
|
||||
String requestId,
|
||||
User user,
|
||||
String ipAddress,
|
||||
String userAgent,
|
||||
int creditCost,
|
||||
String endpoint) {
|
||||
this.requestId = requestId;
|
||||
this.user = user;
|
||||
this.ipAddress = ipAddress;
|
||||
this.userAgent = userAgent;
|
||||
this.creditCost = creditCost;
|
||||
this.endpoint = endpoint;
|
||||
}
|
||||
|
||||
public String getRequestId() {
|
||||
return requestId;
|
||||
}
|
||||
|
||||
public User getUser() {
|
||||
return user;
|
||||
}
|
||||
|
||||
public String getIpAddress() {
|
||||
return ipAddress;
|
||||
}
|
||||
|
||||
public String getUserAgent() {
|
||||
return userAgent;
|
||||
}
|
||||
|
||||
public int getCreditCost() {
|
||||
return creditCost;
|
||||
}
|
||||
|
||||
public String getEndpoint() {
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
public boolean isCreditsPreChecked() {
|
||||
return creditsPreChecked;
|
||||
}
|
||||
|
||||
public void setCreditsPreChecked(boolean preChecked) {
|
||||
this.creditsPreChecked = preChecked;
|
||||
}
|
||||
|
||||
public boolean isAnonymous() {
|
||||
return user == null;
|
||||
}
|
||||
|
||||
public HttpServletResponse getHttpResponse() {
|
||||
return httpResponse;
|
||||
}
|
||||
|
||||
public void setHttpResponse(HttpServletResponse httpResponse) {
|
||||
this.httpResponse = httpResponse;
|
||||
}
|
||||
|
||||
/** Generate a unique identifier for this request */
|
||||
public static String generateRequestId() {
|
||||
return UUID.randomUUID().toString();
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
package stirling.software.proprietary.model;
|
||||
|
||||
/** Classification of API call failures for credit charging purposes */
|
||||
public enum FailureType {
|
||||
/**
|
||||
* Client-side errors that don't consume credits and don't count toward failure limit Examples:
|
||||
* 400 Bad Request, 401 Unauthorized, 403 Forbidden, 422 Unprocessable Entity
|
||||
*/
|
||||
CLIENT_ERROR,
|
||||
|
||||
/**
|
||||
* Processing errors that occur after validation passes - these count toward consecutive
|
||||
* failures Examples: 500 Internal Server Error, processing exceptions, timeout during PDF
|
||||
* manipulation
|
||||
*/
|
||||
PROCESSING_ERROR,
|
||||
|
||||
/** Successful processing - resets failure counter and consumes credits */
|
||||
SUCCESS
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
package stirling.software.proprietary.model;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
|
||||
import lombok.*;
|
||||
|
||||
@Entity
|
||||
@Table(name = "organizations")
|
||||
@NoArgsConstructor
|
||||
@Getter
|
||||
@Setter
|
||||
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
|
||||
@ToString(onlyExplicitlyIncluded = true)
|
||||
public class Organization implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Column(name = "org_id")
|
||||
private Long id;
|
||||
|
||||
@Column(name = "name", unique = true, nullable = false)
|
||||
private String name;
|
||||
|
||||
@Column(name = "description")
|
||||
private String description;
|
||||
|
||||
@OneToMany(mappedBy = "organization", cascade = CascadeType.ALL, orphanRemoval = true)
|
||||
private Set<Team> teams = new HashSet<>();
|
||||
|
||||
public void addTeam(Team team) {
|
||||
teams.add(team);
|
||||
team.setOrganization(this);
|
||||
}
|
||||
|
||||
public void removeTeam(Team team) {
|
||||
teams.remove(team);
|
||||
team.setOrganization(null);
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ import lombok.*;
|
||||
import stirling.software.proprietary.security.model.User;
|
||||
|
||||
@Entity
|
||||
@Table(name = "teams", uniqueConstraints = @UniqueConstraint(columnNames = {"name", "org_id"}))
|
||||
@Table(name = "teams")
|
||||
@NoArgsConstructor
|
||||
@Getter
|
||||
@Setter
|
||||
@@ -26,15 +26,9 @@ public class Team implements Serializable {
|
||||
@Column(name = "team_id")
|
||||
private Long id;
|
||||
|
||||
@Column(name = "name", nullable = false)
|
||||
@Column(name = "name", unique = true, nullable = false)
|
||||
private String name;
|
||||
|
||||
@ManyToOne(fetch = FetchType.EAGER)
|
||||
@JoinColumn(
|
||||
name = "org_id",
|
||||
nullable = true) // Nullable for backward compatibility during migration
|
||||
private Organization organization;
|
||||
|
||||
@OneToMany(mappedBy = "team", cascade = CascadeType.ALL, orphanRemoval = true)
|
||||
private Set<User> users = new HashSet<>();
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user