Compare commits

..
98 Commits
Author SHA1 Message Date
Anthony Stirling 1a8e47898e init france 2025-09-19 12:43:31 +01:00
Anthony Stirling 34316ec6c9 revert US 2025-09-19 12:42:38 +01:00
Anthony Stirling d7e590f24e update some langs 2025-09-19 08:54:48 +01:00
Anthony Stirling 49a10a3865 Change fallback base URL to 'https://stirling.com' 2025-09-17 08:34:55 +01:00
ConnorYohandConnor Yoh 190178a471 Feature/v2/filehistory (#4370)
File History

---------

Co-authored-by: Connor Yoh <connor@stirlingpdf.com>
2025-09-16 15:08:11 +01:00
ConnorYohandConnor Yoh 8e8b417f5e V2 Tool - Auto split (#4446)
integrated auto split, with flattened split tool

---------

Co-authored-by: Connor Yoh <connor@stirlingpdf.com>
2025-09-16 13:08:54 +01:00
ConnorYohandConnor Yoh a57373b968 V2 Flatten split options to remove layers of drop downs (#4439)
Co-authored-by: Connor Yoh <connor@stirlingpdf.com>
2025-09-15 16:11:29 +00:00
James Brunton 7dad484aa7 Improve type info on param hooks (#4438)
# Description of Changes
Changes it so that callers of `useBaseTool` know what actual type the
parameters hook that they passed in returned, so they can actually make
use of any extra methods that that params hook has.
2025-09-15 14:28:18 +01:00
James Brunton cfdb6eaa1e Add Adjust Page Scale tool to V2 (#4429)
# Description of Changes
Add Adjust Page Scale tool to V2
2025-09-12 17:25:22 +01:00
James Brunton 8a367aab54 Change tips icon to i circle (#4430)
# Description of Changes

## Before

<img width="102" height="35" alt="image"
src="https://github.com/user-attachments/assets/fcb85906-85b6-41e1-9162-4084c0e684ec"
/>

## After

<img width="103" height="45" alt="image"
src="https://github.com/user-attachments/assets/241d61d8-d3c4-4dbf-a6af-4fda0867734d"
/>
2025-09-10 18:19:05 +01:00
James Brunton f3fd85d777 Add Merge UI to V2 (#4235)
# Description of Changes
Add UI for Merge into V2.
2025-09-10 13:06:23 +00:00
James Brunton 9d723eae69 Add auto-redact to V2 (#4417)
# Description of Changes
Adds auto-redact tool to V2, with manual-redact in the UI but explicitly
disabled.

Also creates a shared component for the large buttons we're using in a
couple different tools and uses consistently.
2025-09-10 14:03:11 +01:00
James Brunton 494ef801a2 Improve npm scripts (#4424)
# Description of Changes
Change NPM scripts so they call each other (single source of truth) and
add a command to run type checking, linting and tests (to give
confidence CI will pass).
2025-09-09 16:18:09 +01:00
LudyandConnorYoh e8af4f6b35 Set i18n to load only current language (#4359)
This pull request introduces a minor configuration change to the i18n
setup in the frontend. The change improves language loading behavior by
ensuring only the current language is loaded, which can help optimize
performance and prevent unnecessary resource usage.

* Added the `load: 'currentOnly'` option to the i18n initialization in
`frontend/src/i18n.ts`, so only the current language is loaded.

Co-authored-by: ConnorYoh <40631091+ConnorYoh@users.noreply.github.com>
2025-09-08 10:05:49 +01:00
stirlingbot[bot]andConnorYoh c25985e49e Update Frontend 3rd Party Licenses (#4319)
Auto-generated by stirlingbot[bot]

This PR updates the frontend license report based on changes to
package.json dependencies.

Signed-off-by: stirlingbot[bot] <stirlingbot[bot]@users.noreply.github.com>
Co-authored-by: stirlingbot[bot] <195170888+stirlingbot[bot]@users.noreply.github.com>
Co-authored-by: ConnorYoh <40631091+ConnorYoh@users.noreply.github.com>
2025-09-08 08:58:22 +00:00
James Brunton 316be5eac5 Fix types of onParameterChange methods (#4415)
# Description of Changes
Fix types of onParameterChange methods
2025-09-08 09:55:30 +01:00
11d23a2d43 V2 Auto rename (#4244)
# Description of Changes

This pull request introduces the new "Auto Rename PDF" tool to the
frontend, enabling users to automatically rename PDF files based on
their content. The implementation includes UI components, parameter
handling, operation logic, localization, and enhancements to the file
response utilities to support backend-provided filenames. Below are the
most important changes grouped by theme:

**Feature: Auto Rename PDF Tool**

- Added the main `AutoRename` tool component (`AutoRename.tsx`) and
registered it in the tool registry, enabling selection and execution of
the auto-rename operation in the UI.
[[1]](diffhunk://#diff-3647ca39d46d109d122d4cd6cbfe981beb4189d05b1b446e5c46824eb98a4a88R1-R80)
[[2]](diffhunk://#diff-0a3e636736c137356dd9354ff3cacbd302ebda40147545e13c62d073525d1969R17)
[[3]](diffhunk://#diff-0a3e636736c137356dd9354ff3cacbd302ebda40147545e13c62d073525d1969L359-R366)
[[4]](diffhunk://#diff-29427b8d06a23772c56645fc4b72af2980c813605abc162e3d47c2e39d026d06L25-R26)
- Implemented the settings panel (`AutoRenameSettings.tsx`) and
parameter management hook (`useAutoRenameParameters.ts`), allowing users
to configure options such as using the first text as a fallback for the
filename.
[[1]](diffhunk://#diff-b2f9474c8e5a7a42df00a12ffd2d31a785895fe1096e8ca515e6af5633a4d648R1-R27)
[[2]](diffhunk://#diff-8798a1ef451233bf3a1bf8825c12c5b434ad1a17a1beb1ca21fd972fdaceb50cR1-R19)
- Created the operation hook (`useAutoRenameOperation.ts`) to handle API
requests, error handling, and result processing for the auto-rename
feature.

**Localization**

- Added English (US and GB) translations for the new tool, including UI
labels, descriptions, error messages, and settings.
[[1]](diffhunk://#diff-e4d543afa388d9eb8a423e45dfebb91641e3558d00848d70b285ebb91c40b249R1048-R1066)
[[2]](diffhunk://#diff-14c707e28788a3a84ed5293ff6689be73d4bca00e155beaf090f9b37c978babbR1321-R1339)

**File Response Handling Enhancements**

- Updated the file response processor and related hooks to support
preserving backend-provided filenames via the `Content-Disposition`
header, ensuring files are renamed according to backend results.
[[1]](diffhunk://#diff-97ea1c842d4b269c566a3085d8555ded7f9b462d9ce8dc73706bec79fe3973e0R11)
[[2]](diffhunk://#diff-97ea1c842d4b269c566a3085d8555ded7f9b462d9ce8dc73706bec79fe3973e0L49-R51)
[[3]](diffhunk://#diff-d44da7f96721d9829f3c20bf9c7ac5b9e156b647d2c75d76e861c8c09abc5191R52-R58)
[[4]](diffhunk://#diff-d44da7f96721d9829f3c20bf9c7ac5b9e156b647d2c75d76e861c8c09abc5191L175-R183)
[[5]](diffhunk://#diff-fa8af80f4d87370d58e3a5b79df675d201f0c3aa753eda89cec03ff027c4213dL13-R21)
[[6]](diffhunk://#diff-efa525dbdeceaeb5701aa3d2303bf1d533541f65a92d985f94f33b8e87b036d1R2-R37)

These changes collectively deliver a new advanced tool for users to
automatically rename PDFs, with robust parameter handling, user
interface integration, and proper handling of filenames as determined by
backend logic.
---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing)
for more details.

---------

Co-authored-by: Connor Yoh <connor@stirlingpdf.com>
Co-authored-by: ConnorYoh <40631091+ConnorYoh@users.noreply.github.com>
Co-authored-by: Reece Browne <74901996+reecebrowne@users.noreply.github.com>
2025-09-05 17:12:52 +01:00
Anthony StirlingandConnorYoh 1898df0df9 Urls for tools for scrapers and open in new tab support (#4364)
# Description of Changes

<!--
Please provide a summary of the changes, including:

- What was changed
- Why the change was made
- Any challenges encountered

Closes #(issue_number)
-->

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing)
for more details.

---------

Co-authored-by: ConnorYoh <40631091+ConnorYoh@users.noreply.github.com>
2025-09-05 11:35:17 +00:00
da359d329d V2 flatten (#4358)
# Description of Changes

<!--
Please provide a summary of the changes, including:

- What was changed
- Why the change was made
- Any challenges encountered

Closes #(issue_number)
-->

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing)
for more details.

---------

Co-authored-by: James Brunton <jbrunton96@gmail.com>
Co-authored-by: ConnorYoh <40631091+ConnorYoh@users.noreply.github.com>
2025-09-05 11:25:30 +00:00
James Brunton bd13f6bf57 Enable ESLint no-unused-vars rule (#4367)
# Description of Changes
Enable ESLint [no-unused-vars
rule](https://typescript-eslint.io/rules/no-unused-vars/)
2025-09-05 11:16:17 +00:00
87c63efcec Feature/v2/filewithid implementation (#4369)
Added Filewithid type
Updated code where file was being used to use filewithid
Updated places we identified files by name or composite keys to use UUID
Updated places we should have been using quickkey
Updated pageeditor issue where we parsed pagenumber from pageid instead
of using pagenumber directly

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: James Brunton <jbrunton96@gmail.com>
2025-09-05 11:33:03 +01:00
James Brunton 5caec41d96 Enable ESLint no-empty-object-type rule (#4354)
# Description of Changes
Enable ESLint [no-empty-object-type
rule](https://typescript-eslint.io/rules/no-empty-object-type/)
2025-09-05 10:15:36 +00:00
James Brunton d558bb5fac Enable ESLint ban-ts-comment rule (#4350)
# Description of Changes
Enable ESLint [ban-ts-comment
rule](https://typescript-eslint.io/rules/ban-ts-comment/)
2025-09-05 10:02:00 +00:00
James Brunton cd1fc682ab Enable ESLint no-case-declarations rule (#4348)
# Description of Changes
Enable ESLint [no-case-declarations
rule](https://eslint.org/docs/latest/rules/no-case-declarations)
2025-09-05 09:58:14 +00:00
James Brunton b9cf7e7820 Enable ESLint no-empty-pattern rule (#4343)
# Description of Changes
Enable ESLint [no-empty-pattern
rule](https://eslint.org/docs/latest/rules/no-empty-pattern)
2025-09-05 10:55:03 +01:00
James Brunton 94e8f603ff Enable ESLint no-unused-expressions rule (#4363)
# Description of Changes
Enable ESLint [no-unused-expressions
rule](https://typescript-eslint.io/rules/no-unused-expressions/)
2025-09-04 15:12:38 +00:00
James Brunton 74609e54fe Enable ESLint prefer-const rule (#4349)
# Description of Changes
Enable ESLint [prefer-const
rule](https://eslint.org/docs/latest/rules/prefer-const)
2025-09-04 15:09:29 +00:00
James Brunton 003285506f Enable ESLint no-useless-escape rule (#4344)
# Description of Changes
Enable ESLint [no-useless-escape
rule](https://eslint.org/docs/latest/rules/no-useless-escape)
2025-09-04 15:04:49 +00:00
James Brunton 6d3b08d9b6 Enable ESLint no-empty rule (#4342)
# Description of Changes
Enable ESLint [no-empty
rule](https://eslint.org/docs/latest/rules/no-empty)
2025-09-04 15:59:31 +01:00
James Brunton 295e682e03 Add linting to frontend (#4341)
# Description of Changes
There's no current linter running over our TypeScript code, which means
we've got a bunch of dead code and other code smells around with nothing
notifying us. This PR adds ESLint with the typescript-eslint plugin and
enables the recommended settings as a starting point for us.

I've disabled all of the failing rules for the scope of this PR, just to
get linting running without causing a massive diff. I'll follow up with
future PRs that enable the failing rules one by one.

Also updates our version of TypeScript, which introduces a new type
error in the code (which I've had to fix)
2025-09-04 14:08:28 +01:00
ConnorYohandConnor Yoh 1a3e8e7ecf Undo Button -> review state. Result files added to recents (#4337)
Produced PDFs go into recent files
Undo button added to review state
Undo causes undoConsume which replaces result files with source files.
Removes result files from recent files too

---------

Co-authored-by: Connor Yoh <connor@stirlingpdf.com>
2025-09-02 15:09:05 +01:00
ConnorYohandConnor Yoh 96aa43860b Bugfix/V2/pdfa_extension (#4336)
Changes fallback pdfa extension pdf when a filename isnt returned for
convert.
Also fixes ui bug with footer and pop ups

---------

Co-authored-by: Connor Yoh <connor@stirlingpdf.com>
2025-09-01 12:23:27 +01:00
James Brunton 6dbaff5342 Fix Split to be a single file interface (#4327)
# Description of Changes
Split was previously incorrectly marked as a multi-file interface, which
meant that if you fed 2 files into it, it'd just process the first and
discard the second.

This PR changes it to a single-file interface, and implements a custom
response handler because Split returns Zip files instead of PDFs, so the
response you get when running Split now is the union of all of the split
input files in the workbench (or them all zipped if you download it).
2025-08-29 17:23:44 +01:00
ConnorYohandConnor Yoh 2f2f966ee9 Turn off logs from scarf (#4326)
Co-authored-by: Connor Yoh <connor@stirlingpdf.com>
2025-08-29 17:23:18 +01:00
James Brunton 3b28b398d9 Automatically select added files (#4325)
# Description of Changes
When adding files, the user probably wants to use them straight away in
the next tool that they use, so automatically select any added files (in
addition to whatever they previously had selected).
2025-08-29 16:39:19 +01:00
eecc410b77 Basic footer structure and Cookie Consent (#4320)
* Added footer with blank links to be filled 
* Cookie consent to match V1 
* Made scrolling work on tool search results
* Made scrolling the same on tool search, tool picker and workbench 
* Cleaned up height variables, view height only used at workbench level 
<img width="1525" height="1270"
alt="{F3C1B15F-A4BE-4DF0-A5A8-92D2A3B14443}"
src="https://github.com/user-attachments/assets/0c23fe35-9973-45c0-85af-0002c5ff58d2"
/>
<img width="1511" height="1262"
alt="{4DDD51C0-4BC5-4E9F-A4F2-E5F49AF5F5FD}"
src="https://github.com/user-attachments/assets/2596d980-0312-4cd7-ad34-9fd3a8d1869e"
/>

---------

Co-authored-by: Connor Yoh <connor@stirlingpdf.com>
Co-authored-by: James Brunton <jbrunton96@gmail.com>
2025-08-29 13:01:46 +00:00
ConnorYohandConnor Yoh 62c929b89b Top Controls was throwing an error (#4324)
<img width="337" height="76"
alt="{4B3307AF-6162-4648-BC06-D5E8967E5D1B}"
src="https://github.com/user-attachments/assets/6401da20-34a5-47f6-8d09-ce53211164d2"
/>

Co-authored-by: Connor Yoh <connor@stirlingpdf.com>
2025-08-29 13:58:06 +01:00
James Brunton 7c9769f5b9 Change to Viewer mode when Read tool selected (#4317)
# Description of Changes
Change it so that when you select the Read tool, the Viewer is
automatically selected.
2025-08-29 13:28:15 +01:00
James Brunton cf771075f4 Reduce required gap between files so more fit on the screen (#4323)
# Description of Changes
Change gap between files in Active Files so more files fit on the screen
at once
2025-08-29 10:30:19 +01:00
ConnorYohandConnor Yoh a7d5c80188 Posthog, scarf and url navigation overhaul (#4318)
Added post hog project - always enabled
Added scarf pixel - Always enabled 
Reworked Url navigation 
Forward and back now works without reloading page

---------

Co-authored-by: Connor Yoh <connor@stirlingpdf.com>
2025-08-28 15:42:33 +01:00
James Brunton 5b20f11e20 Add test that the translation JSON is valid (#4315)
# Description of Changes
Adds a vitest to check that the translation JSON files exist and are
actually valid JSON.
2025-08-28 11:03:43 +01:00
James Brunton 442b373ff4 V2 reduce tool boilerplate (#4313)
# Description of Changes
Reduce boilerplate in tool frontends by creating a base frontend hook
for the simple tools to use.

I've done all the simple tools here. It'd be nice to add in some of the
more complex tools as well in the future if we can figure out how.
2025-08-28 09:59:38 +00:00
James Brunton e142af2863 V2 Make FileId type opaque and use consistently throughout project (#4307)
# Description of Changes
The `FileId` type in V2 currently is just defined to be a string. This
makes it really easy to accidentally pass strings into things accepting
file IDs (such as file names). This PR makes the `FileId` type [an
opaque
type](https://www.geeksforgeeks.org/typescript/opaque-types-in-typescript/),
so it is compatible with things accepting strings (arguably not ideal
for this...) but strings are not compatible with it without explicit
conversion.

The PR also includes changes to use `FileId` consistently throughout the
project (everywhere I could find uses of `fileId: string`), so that we
have the maximum benefit from the type safety.

> [!note]
> I've marked quite a few things as `FIX ME` where we're passing names
in as IDs. If that is intended behaviour, I'm happy to remove the fix me
and insert a cast instead, but they probably need comments explaining
why we're using a file name as an ID.
2025-08-28 09:56:07 +00:00
581bafbd37 quick text changes (#4301)
# Description of Changes

<!--
Please provide a summary of the changes, including:

- What was changed
- Why the change was made
- Any challenges encountered

Closes #(issue_number)
-->

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing)
for more details.

---------

Co-authored-by: James Brunton <jbrunton96@gmail.com>
Co-authored-by: ConnorYoh <40631091+ConnorYoh@users.noreply.github.com>
2025-08-28 10:50:49 +01:00
James Brunton 2cac8e8edf Redesgin ToolOperationConfig so types are known for single/multiple/custom tools (#4221)
# Description of Changes
Redesigns `ToolOperationConfig` so that the types of the functions are
always known depending on whether the tool runs on single files,
multiple files, or uses custom behaviour
2025-08-27 14:51:52 +01:00
James Brunton 0002043266 Fix missing key React error in quick access bar (#4300)
# Description of Changes
React is currently throwing an error becasue of a missing key in V2.
This adds a key so it stops complaining.
2025-08-27 10:57:54 +01:00
James Brunton 00f0194b32 Re-remove rainbow mode after mistaken revert (#4311)
# Description of Changes
Re-remove rainbow mode after mistaken revert
2025-08-27 09:32:39 +01:00
ConnorYohandConnor Yoh 1c8b20b6e9 Split suggested fix (#4310)
Co-authored-by: Connor Yoh <connor@stirlingpdf.com>
2025-08-26 17:39:55 +01:00
ConnorYoh 1eee5c9a11 Remove config button (#4309) 2025-08-26 16:29:43 +00:00
Reece BrowneandAnthony Stirling 68d59fd377 Feature/v2/page editor selection persistance (#4306)
Fixed page editor selection persistance
Fixed delete sleected
Fixed display issue on some tools on reset settings call

---------

Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
2025-08-26 17:26:30 +01:00
47ccb6a6ed improvement/v2/automate/tweaks (#4293)
- [x] Cleanup Automation output name garbage			
- [x] Remove Cross button on first two tools			
- [x] Automation creation name title to make clearer to the user
- [x] Colours for dark mode on automation tool settings are bad 	
- [x] Fix tool names not using correct translated ones 
- [x] suggested Automation Password needs adding to description 
- [x] Allow different filetypes in automation
- [x] Custom Icons for automation
- [x] split Tool wasn't working with merge to single pdf

---------

Co-authored-by: Connor Yoh <connor@stirlingpdf.com>
Co-authored-by: James Brunton <jbrunton96@gmail.com>
2025-08-26 16:59:03 +01:00
EthanHealy01andJames Brunton 3d26b054f1 V2 styling requests (#4288)
some of the requested changes warrant discussion so I haven't applied
all of them here. Making a draft PR for now, we can discuss the rest
later

# Description of Changes

See Matts todo list, the crossed out items are what were addressed in
this PR

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing)
for more details.

---------

Co-authored-by: James Brunton <jbrunton96@gmail.com>
2025-08-26 16:31:20 +01:00
James Brunton 9360a36c31 V2: Disable rainbow mode for demo (#4303)
# Description of Changes
The rainbow mode easter egg doesn't work very well at the moment and
isn't needed for the demo (and is too easy to accidentally enable). This
PR disables it completely by adding a global const to short-circuit the
activation code.
2025-08-26 14:56:37 +00:00
James Brunton d64c56a897 Fix invalid translation file in V2 (#4298)
# Description of Changes
GB translations are currently invalid JSON. This fixes that.
2025-08-26 15:53:49 +01:00
Reece BrowneandAnthony Stirling ca423f9646 Feature/v2/pageeditor improved (#4289)
# Description of Changes

<!--
Please provide a summary of the changes, including:

Rewrite of page editor to make it work properly.  
Added page breaks
Added merged file support
Added "insert file" support
Slight Ux improvements

Closes #(issue_number)
-->

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing)
for more details.

---------

Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
2025-08-26 15:30:58 +01:00
Anthony Stirling 9b8091a630 Revert "npm login" (#4299)
Reverts Stirling-Tools/Stirling-PDF#4296
2025-08-26 14:06:29 +01:00
Anthony Stirlinganda 4aac124e76 npm login (#4296)
# Description of Changes

<!--
Please provide a summary of the changes, including:

- What was changed
- Why the change was made
- Any challenges encountered

Closes #(issue_number)
-->

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing)
for more details.

---------

Co-authored-by: a <a>
2025-08-26 13:46:15 +01:00
James Brunton 6d7f76353e Add tooltips to advanced section in OCR and add missing translations (#4295)
# Description of Changes
Add tooltips to advanced section in OCR and add missing translations
2025-08-26 12:38:47 +01:00
EthanHealy01 16e028a8ef Feature/v2/file manager UI improvements (#4286)
# Description of Changes

<img width="1171" height="416" alt="Screenshot 2025-08-26 at 1 33 14 AM"
src="https://github.com/user-attachments/assets/7fd132fb-93ae-4849-b7b0-78182707cf14"
/>


---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing)
for more details.
2025-08-26 12:35:09 +01:00
Anthony StirlingandConnorYoh 95b3e22229 Automation tolltip + new operations + copy to saved (#4292)
# Description of Changes

<!--
Please provide a summary of the changes, including:

- What was changed
- Why the change was made
- Any challenges encountered

Closes #(issue_number)
-->

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing)
for more details.

---------

Co-authored-by: ConnorYoh <40631091+ConnorYoh@users.noreply.github.com>
2025-08-26 11:19:15 +01:00
ConnorYohandConnor Yoh 4b70ef1298 Moved scroll out to tool panel and works better with zoom (#4282)
Co-authored-by: Connor Yoh <connor@stirlingpdf.com>
2025-08-26 09:12:15 +00:00
ConnorYohandConnor Yoh 1cc803545a Dynamic upload/add files button for toolstep (#4284)
Co-authored-by: Connor Yoh <connor@stirlingpdf.com>
2025-08-26 10:09:49 +01:00
ConnorYohandConnor Yoh fe9d2367d5 AutomateFixes (#4281)
can edit automations
drop down styles
drop down bug fixes

---------

Co-authored-by: Connor Yoh <connor@stirlingpdf.com>
2025-08-26 09:44:30 +01:00
ConnorYohandConnor Yoh 42d7664e25 Preview file (#4260)
Preview file works

Co-authored-by: Connor Yoh <connor@stirlingpdf.com>
2025-08-26 09:26:26 +01:00
ConnorYohandConnor Yoh bbd658d3b8 Default View to file editor not page editor (#4258)
Co-authored-by: Connor Yoh <connor@stirlingpdf.com>
2025-08-26 09:25:33 +01:00
stirlingbot[bot] ed61c71db7 Update Frontend 3rd Party Licenses (#4254)
Auto-generated by stirlingbot[bot]

This PR updates the frontend license report based on changes to
package.json dependencies.

Signed-off-by: stirlingbot[bot] <stirlingbot[bot]@users.noreply.github.com>
Co-authored-by: stirlingbot[bot] <195170888+stirlingbot[bot]@users.noreply.github.com>
2025-08-25 16:10:51 +01:00
Anthony Stirlinganda 73deece29e V2 Replace Google Fonts icons with locally bundled Iconify icons (#4283)
# Description of Changes

This PR refactors the frontend icon system to remove reliance on
@mui/icons-material and the Google Material Symbols webfont.

🔄 Changes

Introduced a new LocalIcon component powered by Iconify.
Added scripts/generate-icons.js to:
Scan the codebase for used icons.
Extract only required Material Symbols from
@iconify-json/material-symbols.
Generate a minimized JSON bundle and TypeScript types.
Updated .gitignore to exclude generated icon files.
Replaced all <span className="material-symbols-rounded"> and MUI icon
imports with <LocalIcon> usage.
Removed material-symbols CSS import and related font dependency.
Updated tsconfig.json to support JSON imports.
Added prebuild/predev hooks to auto-generate the icons.

 Benefits

No more 5MB+ Google webfont download → reduces initial page load size.
Smaller install footprint → no giant @mui/icons-material dependency.
Only ships the icons we actually use, cutting bundle size further.
Type-safe icons via auto-generated MaterialSymbolIcon union type.

Note most MUI not included in this update since they are low priority
due to small SVG sizing (don't grab whole bundle)


---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing)
for more details.

---------

Co-authored-by: a <a>
2025-08-25 16:07:55 +01:00
EthanHealy01 55ebf9ebd0 Bug/v2/all tools section headers gap (#4275)
# Description of Changes

<img width="701" height="574" alt="Screenshot 2025-08-24 at 2 42 22 PM"
src="https://github.com/user-attachments/assets/a5b74eca-176d-4e34-b1da-c5d024a04896"
/>

to: 

<img width="731" height="542" alt="Screenshot 2025-08-24 at 2 42 41 PM"
src="https://github.com/user-attachments/assets/4e6b0632-5581-4e20-ba62-81ba2fafcb4a"
/>


---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing)
for more details.
2025-08-25 15:46:44 +01:00
e6f4cfb318 Automate/v2/suggested (#4257)
Suggested pipelines now work

---------

Co-authored-by: Connor Yoh <connor@stirlingpdf.com>
Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
2025-08-25 13:10:13 +01:00
895bcebc7b Feature/v2/right rail (#4255)
# Description of Changes

<!--
Please provide a summary of the changes, including:

- What was changed
- Why the change was made
- Any challenges encountered

Closes #(issue_number)
-->

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing)
for more details.

---------

Co-authored-by: ConnorYoh <40631091+ConnorYoh@users.noreply.github.com>
Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
2025-08-25 12:53:33 +01:00
EthanHealy01 61f5221c58 Feature/v2/landing page upload buttons (#4259)
# Description of Changes

<img width="659" height="453" alt="Screenshot 2025-08-22 at 5 28 53 PM"
src="https://github.com/user-attachments/assets/1dd0f63c-0b08-435c-bb0e-00bd85be7f24"
/>
<img width="968" height="766" alt="Screenshot 2025-08-22 at 5 30 00 PM"
src="https://github.com/user-attachments/assets/86614256-1375-4e6e-b5bb-ae9cb6708135"
/>

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing)
for more details.
2025-08-25 12:43:20 +01:00
888bac9408 Added optional title for tool workflow (#4256)
- Added optional title for tool workflow - Not added to any tool. Just
there for when we need it
- Added add files button to files step
- renamed Local files button in filemanager to Upload Files
-

---------

Co-authored-by: Connor Yoh <connor@stirlingpdf.com>
Co-authored-by: James Brunton <jbrunton96@gmail.com>
2025-08-22 17:12:14 +01:00
ConnorYohandConnor Yoh 23d86deae7 Feature/v2/automate (#4248)
* automate feature
* Moved all providers to app level to simplify homepage 
* Circular dependency fixes
* You will see that now toolRegistry gets a tool config and a tool
settings object. These enable automate to run the tools using as much
static code as possible.

---------

Co-authored-by: Connor Yoh <connor@stirlingpdf.com>
2025-08-22 14:40:27 +01:00
James Brunton 7d9c0b0298 V2 Fix subcategory names in All Tools (and search) pane (#4252)
# Description of Changes
Because we used string typing for IDs and names, it was really easy to
make mistakes where variables named like `subcategory` would be stored
as an ID in one file, but then read assuming it's a name in another
file. This PR changes the code to consistently use enum cases when
referring to IDs of categories, subcategories, and tools (at least in as
many places as I can find them, ~I had to add a `ToolId` enum for this
work~ I originally added a `ToolId` type for this work, but it caused
too many issues when merging with #4222 so I've pulled it back out for
now).

Making that change made it obvious where we were inconsistently passing
IDs and reading them as names etc. allowing me to fix rendering issues
in the All Tools pane, where the subcategory IDs were being rendered
directly (instead of being translated) or where IDs were being
translated into names, but were then being re-translated, causing
warnings in the log.
2025-08-22 13:53:06 +01:00
Reece BrowneandReece Browne 949ffa01ad Feature/v2/file handling improvements (#4222)
# Description of Changes

A new universal file context rather than the splintered ones for the
main views, tools and manager we had before (manager still has its own
but its better integreated with the core context)
File context has been split it into a handful of different files
managing various file related issues separately to reduce the monolith -
FileReducer.ts - State management
  fileActions.ts - File operations
  fileSelectors.ts - Data access patterns
  lifecycle.ts - Resource cleanup and memory management
  fileHooks.ts - React hooks interface
  contexts.ts - Context providers
Improved thumbnail generation
Improved indexxedb handling
Stopped handling files as blobs were not necessary to improve
performance
A new library handling drag and drop
https://github.com/atlassian/pragmatic-drag-and-drop (Out of scope yes
but I broke the old one with the new filecontext and it needed doing so
it was a might as well)
A new library handling virtualisation on page editor
@tanstack/react-virtual, as above.
Quickly ripped out the last remnants of the old URL params stuff and
replaced with the beginnings of what will later become the new URL
navigation system (for now it just restores the tool name in url
behavior)
Fixed selected file not regestered when opening a tool
Fixed png thumbnails
Closes #(issue_number)

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing)
for more details.

---------

Co-authored-by: Reece Browne <you@example.com>
2025-08-21 17:30:26 +01:00
James Brunton a33e51351b V2 hide incomplete tools (#4250)
# Description of Changes
Filters out tools which don't exist in All Tools list. Also fixes a
bunch of translation warnings in the log.
2025-08-21 09:26:17 +01:00
James Brunton a6706fcb0c V2 reduce boilerplate in param hooks (#4246)
# Description of Changes
Extend the base params in all tools param hooks, reducing boilerplate
code.
2025-08-21 07:48:25 +00:00
ConnorYohandConnor Yoh d06cbcaa91 V2 Files Selected indicator in toolstep (#4241)
Added back our files selected indicator in tools step

---------

Co-authored-by: Connor Yoh <connor@stirlingpdf.com>
2025-08-20 15:54:30 +00:00
ConnorYohandConnor Yoh cd2b82d614 Feature/v2/filemanagerimprovements (#4243)
- Select all/deselect all
- Delete Selected
- Download  Selected
- Recent file delete -> menu button with drop down for delete and
download
- Shift click selection added
<img width="1220" height="751"
alt="{330DF96D-7040-4CCB-B089-523F370E3185}"
src="https://github.com/user-attachments/assets/976e42cc-2124-4e62-83a8-25f184e8da3b"
/>
<img width="1160" height="749"
alt="{2D2F4876-7D35-45C3-B0CD-3127EEEEF7B5}"
src="https://github.com/user-attachments/assets/6879a174-a135-41f4-a876-984e7c2f96e2"
/>

---------

Co-authored-by: Connor Yoh <connor@stirlingpdf.com>
2025-08-20 16:51:55 +01:00
Anthony Stirling eada9e43ec tooltip (#4242)
# Description of Changes

<!--
Please provide a summary of the changes, including:

- What was changed
- Why the change was made
- Any challenges encountered

Closes #(issue_number)
-->

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing)
for more details.
2025-08-20 14:12:12 +01:00
Anthony Stirling d1cb3f0b30 V2 PDF to large page (#4236)
# Description of Changes

<!--
Please provide a summary of the changes, including:

- What was changed
- Why the change was made
- Any challenges encountered

Closes #(issue_number)
-->

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing)
for more details.
2025-08-20 11:38:41 +01:00
Anthony Stirling 857b2b5c53 V2 Unlock forms (#4237)
# Description of Changes

<!--
Please provide a summary of the changes, including:

- What was changed
- Why the change was made
- Any challenges encountered

Closes #(issue_number)
-->

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing)
for more details.
2025-08-20 11:34:28 +01:00
Anthony Stirling d74c3f4598 V2 remove cert (#4239)
# Description of Changes

<!--
Please provide a summary of the changes, including:

- What was changed
- Why the change was made
- Any challenges encountered

Closes #(issue_number)
-->

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing)
for more details.
2025-08-20 11:30:57 +01:00
Anthony Stirling f1cb8ce5b3 V2 Repair tool (#4234)
# Description of Changes

<!--
Please provide a summary of the changes, including:

- What was changed
- Why the change was made
- Any challenges encountered

Closes #(issue_number)
-->

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing)
for more details.
2025-08-19 18:00:50 +01:00
EthanHealy01 8f32082145 Feature/v2/all tools sidebar (#4151)
# Description of Changes

- Added the all tools sidebar
- Added a TextFit component that shrinks text to fit containers
- Added a TopToolIcon on the nav, that animates down to give users
feedback on what tool is selected
- Added the baseToolRegistry, to replace the old pattern of listing
tools, allowing us to clean up the ToolRegistry code
- Fixed Mantine light/dark theme race condition 
- General styling tweaks

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing)
for more details.
2025-08-19 13:31:09 +01:00
ConnorYohandConnor Yoh c1b7911518 Feature/v2/watermark (#4215)
Add watermark feature

Auto scroll on review

---------

Co-authored-by: Connor Yoh <connor@stirlingpdf.com>
2025-08-19 10:31:44 +01:00
James Brunton acbebd67a3 Add Remove Password UI into V2 (#4214)
# Description of Changes
- Add UI for Remove Password tool
- Fix more translation warnings that were being thrown in the console
- Add an encrypted PDF thumbnail and refactor thumbnail generation code
2025-08-18 15:26:29 +01:00
4c17c520d7 V2 results flow (#4196)
Better tool flow for reusability
Pinning 
Styling of tool flow
consumption of files after tooling

---------

Co-authored-by: Connor Yoh <connor@stirlingpdf.com>
Co-authored-by: James Brunton <jbrunton96@gmail.com>
2025-08-15 14:43:30 +01:00
1468df3e21 JWT Auth into V2 (#4187)
Merging JWT feature from main into V2

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing)
for more details.

---------

Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: stirlingbot[bot] <stirlingbot[bot]@users.noreply.github.com>
Co-authored-by: Ludy <Ludy87@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: EthanHealy01 <80844253+EthanHealy01@users.noreply.github.com>
Co-authored-by: Ethan <ethan@MacBook-Pro.local>
Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
Co-authored-by: stirlingbot[bot] <195170888+stirlingbot[bot]@users.noreply.github.com>
Co-authored-by: albanobattistella <34811668+albanobattistella@users.noreply.github.com>
2025-08-15 14:13:45 +01:00
7e60eb40b4 Bug/v2/reduce console pollution (#4212)
# Description of Changes

<!--

Fixes the issues with I18 that were causing lots of console errors.
Fixes mime type error

Closes #(issue_number)
-->

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing)
for more details.

---------

Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: stirlingbot[bot] <stirlingbot[bot]@users.noreply.github.com>
Co-authored-by: Ludy <Ludy87@users.noreply.github.com>
Co-authored-by: Dario Ghunney Ware <dariogware@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: EthanHealy01 <80844253+EthanHealy01@users.noreply.github.com>
Co-authored-by: Ethan <ethan@MacBook-Pro.local>
Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
Co-authored-by: stirlingbot[bot] <195170888+stirlingbot[bot]@users.noreply.github.com>
Co-authored-by: albanobattistella <34811668+albanobattistella@users.noreply.github.com>
2025-08-15 13:15:21 +01:00
Anthony Stirling 129e4d00e9 Seo v2 add home (#4211)
# Description of Changes

<!--
Please provide a summary of the changes, including:

- What was changed
- Why the change was made
- Any challenges encountered

Closes #(issue_number)
-->

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing)
for more details.
2025-08-14 16:36:24 +01:00
Anthony Stirling 8149cb157c SEO init (#4197)
# Description of Changes
This pull request introduces dynamic document meta tag management and
improves URL routing and tool metadata handling in the frontend. The
most significant changes are the addition of a custom hook to update
document meta tags (including OpenGraph tags) based on the selected
tool, enhancements to the tool selection context for better URL
synchronization, and enrichment of the `Tool` type and tool registry
with more metadata.

**Dynamic document meta management:**

* Added a new `useDocumentMeta` hook that updates the page's `<title>`,
description, and OpenGraph meta tags based on the currently selected
tool, and restores the original values on cleanup.
(`frontend/src/hooks/useDocumentMeta.ts`)
* Integrated `useDocumentMeta` into `HomePageContent` so the document's
meta tags dynamically reflect the selected tool's metadata, improving
SEO and social sharing. (`frontend/src/pages/HomePage.tsx`)
[[1]](diffhunk://#diff-85c26b21681286c20e97a26a4912f0b91812776c9d4d0c54aa541fded2565c7eR2-R8)
[[2]](diffhunk://#diff-85c26b21681286c20e97a26a4912f0b91812776c9d4d0c54aa541fded2565c7eR17)
[[3]](diffhunk://#diff-85c26b21681286c20e97a26a4912f0b91812776c9d4d0c54aa541fded2565c7eR28-R37)

**Tool metadata and context improvements:**

* Enhanced the `Tool` type and tool registry to include `title` and
`description` fields, which are now translated and used throughout the
UI and meta tags. (`frontend/src/types/tool.ts`,
`frontend/src/hooks/useToolManagement.tsx`)
[[1]](diffhunk://#diff-0b557df7bd27ac90cd2f925ddd8ef8096ea2decfaee9a5c12a94dc7a03c64bfaR46)
[[2]](diffhunk://#diff-57f8a6b3e75ecaec10ad445b01afe8fccc376af6f8ad4d693c68cf98e8863273L116-R118)
* Updated the `ToolWorkflowContext` to use the new `Tool` type for
`selectedTool`, replacing the previous `ToolConfiguration` type.
(`frontend/src/contexts/ToolWorkflowContext.tsx`)
[[1]](diffhunk://#diff-9b36e2c06dddbcfba6cb66fd0b303b7860f88ca8b562bb2534af1ab50390d385L6-R8)
[[2]](diffhunk://#diff-9b36e2c06dddbcfba6cb66fd0b303b7860f88ca8b562bb2534af1ab50390d385L72-R72)

**URL routing and synchronization:**

* Implemented logic in `ToolWorkflowContext` to synchronize the selected
tool with the browser URL, initialize tool selection from the URL on
load, and handle browser navigation (back/forward) for tool selection.
(`frontend/src/contexts/ToolWorkflowContext.tsx`)
---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing)
for more details.
2025-08-14 15:52:38 +01:00
James Brunton 22db7bec76 Improve titles, headers and tooltips in Add Password and Change Permissions (#4210)
# Description of Changes
Improve titles, headers and tooltips in Add Password and Change
Permissions
2025-08-14 15:31:53 +01:00
James BruntonandJames ecf30d1028 Implement 'Add Password' and 'Change Permissions' tools in V2 (#4195)
# Description of Changes
Implement Add Password and Change Permissions tools in V2 (both in one
because Change Permissions is a fake endpoint which just calls Add
Password behind the scenes).

---------

Co-authored-by: James <james@crosscourtanalytics.com>
2025-08-14 14:27:23 +01:00
Anthony Stirling 0ea4410dd3 Update manifest.json (#4203)
# Description of Changes

<!--
Please provide a summary of the changes, including:

- What was changed
- Why the change was made
- Any challenges encountered

Closes #(issue_number)
-->

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing)
for more details.
2025-08-13 18:29:25 +01:00
8eeb4c148c Add Sanitize UI (#4123)
# Description of Changes

Implementation of Sanitize UI for V2.

Also removes parameter validation from standard tool hooks because the
logic would have to be duplicated between parameter handling and
operation hooks, and the nicer workflow is for the tools to reject using
the Go button if the validation fails, rather than the operation hook
checking it, since that can't appear in the UI.

Co-authored-by: James <james@crosscourtanalytics.com>
Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
Co-authored-by: ConnorYoh <40631091+ConnorYoh@users.noreply.github.com>
2025-08-12 16:05:59 +01:00
James BruntonandJames adf6feea27 Neaten garbage collection code (#4171)
# Description of Changes
Slight tweaks to type checking code merged in #4126.

Co-authored-by: James <james@crosscourtanalytics.com>
2025-08-12 08:56:31 +00:00
Anthony Stirling 299e0b2475 cleanurl fix 2025-08-12 09:51:13 +01:00
631 changed files with 47369 additions and 15222 deletions
+6 -3
View File
@@ -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
View File
@@ -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]
+5 -1
View File
@@ -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:
+1
View File
@@ -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"
+2 -1
View File
@@ -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 }}
+1 -1
View File
@@ -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]'
+2 -2
View File
@@ -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"
+1 -1
View File
@@ -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
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+2 -1
View File
@@ -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:
+2 -2
View File
@@ -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
+1 -1
View File
@@ -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
+8 -8
View File
@@ -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
+1 -1
View File
@@ -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
+2 -2
View File
@@ -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
+4 -4
View File
@@ -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 }}
+2 -2
View File
@@ -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
+2 -2
View File
@@ -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:
+2 -2
View File
@@ -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
+1 -1
View File
@@ -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
+3 -3
View File
@@ -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
+1
View File
@@ -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
]
}
+4 -1
View File
@@ -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
View File
@@ -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
+1
View File
@@ -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,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 "autojob" 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 timeouts. 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&nbsp;1. Retries are executed
* with exponential backoff.
*
* <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 (1100) used by the scheduler to prioritise / throttle jobs. Values
* below 1 are clamped to&nbsp;1, values above 100 to&nbsp;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;
}
@@ -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');
}
+12
View File
@@ -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 {
@@ -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);
}
}
}
}
@@ -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
}
@@ -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) {
@@ -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();
@@ -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 {
@@ -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.");
}
}
@@ -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;
}
}
@@ -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");
}
}
}
@@ -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;
}
}
@@ -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