From 80cba55459c699ef6db187fd28cd563ecfe3864c Mon Sep 17 00:00:00 2001 From: Ludy Date: Wed, 21 Jan 2026 22:58:29 +0100 Subject: [PATCH] refactor: remove legacy Thymeleaf web UI controllers and templates (#5406) # Description of Changes This pull request completes the removal of Thymeleaf template engine support and documentation from the Stirling-PDF codebase and developer guides, reflecting the project's full migration to a React-based frontend. It removes all references to Thymeleaf in code, configuration, and documentation, and updates guides to focus exclusively on the React SPA architecture. This streamlines the codebase and clarifies the development workflow for contributors. **Codebase cleanup: Thymeleaf removal** - Removed all commented-out Thymeleaf dependencies and related configuration beans from `build.gradle` and `AppConfig.java` (`app/common/build.gradle`, `app/common/src/main/java/stirling/software/common/configuration/AppConfig.java`) [[1]](diffhunk://#diff-2a1a21726f33b05d16451237c68d6df91a5f4a58419d839715f3f1538a9a14aeL32) [[2]](diffhunk://#diff-70792df9a0ab5675ded888c9eb8e2815c780d7b39f4bda8cf2da51d1b336899aL67-L76). - Fully commented out (as a precursor to future deletion) the `FileFallbackTemplateResolver` and `InputStreamTemplateResource` classes, which were only used for Thymeleaf template resolution (`app/common/src/main/java/stirling/software/common/configuration/FileFallbackTemplateResolver.java`, `app/common/src/main/java/stirling/software/common/model/InputStreamTemplateResource.java`) [[1]](diffhunk://#diff-e2bc7614074316b972355cb7dda47b98f75b00eb6b2ca4f143a680ab2803dcd8L1-L49) [[2]](diffhunk://#diff-ab10ee12d8de8fb77759e931170373d388bde04bad6d0e42a0ab674355ef7ef3L1-L40). **Documentation updates: React-only focus** - Removed all instructions and references to migrating or developing with Thymeleaf templates in `DeveloperGuide.md` and `ADDING_TOOLS.md`, including detailed Thymeleaf usage examples, migration steps, and translation key usage in templates [[1]](diffhunk://#diff-ccd22fcbec8148152c8c77b85fbfe2633a6707b5ad50c2ef88fa87e2c47ea88fL5-R5) [[2]](diffhunk://#diff-ccd22fcbec8148152c8c77b85fbfe2633a6707b5ad50c2ef88fa87e2c47ea88fL41-L43) [[3]](diffhunk://#diff-ccd22fcbec8148152c8c77b85fbfe2633a6707b5ad50c2ef88fa87e2c47ea88fL103-L105) [[4]](diffhunk://#diff-ccd22fcbec8148152c8c77b85fbfe2633a6707b5ad50c2ef88fa87e2c47ea88fL157) [[5]](diffhunk://#diff-ccd22fcbec8148152c8c77b85fbfe2633a6707b5ad50c2ef88fa87e2c47ea88fL312) [[6]](diffhunk://#diff-ccd22fcbec8148152c8c77b85fbfe2633a6707b5ad50c2ef88fa87e2c47ea88fL404-R396) [[7]](diffhunk://#diff-ccd22fcbec8148152c8c77b85fbfe2633a6707b5ad50c2ef88fa87e2c47ea88fL451-L505) [[8]](diffhunk://#diff-ccd22fcbec8148152c8c77b85fbfe2633a6707b5ad50c2ef88fa87e2c47ea88fL530-R467) [[9]](diffhunk://#diff-ccd22fcbec8148152c8c77b85fbfe2633a6707b5ad50c2ef88fa87e2c47ea88fL585-L669) [[10]](diffhunk://#diff-ccd22fcbec8148152c8c77b85fbfe2633a6707b5ad50c2ef88fa87e2c47ea88fL699-L709) [[11]](diffhunk://#diff-e2f8148ea620602b7761e8ee24afeac1c577476630528e210fe0b22e950016ddL3-R3) [[12]](diffhunk://#diff-e2f8148ea620602b7761e8ee24afeac1c577476630528e210fe0b22e950016ddL267-R267). - Updated architecture descriptions in `CLAUDE.md` to reflect that the frontend is now exclusively a React SPA and that Thymeleaf templates have been fully replaced [[1]](diffhunk://#diff-6ebdb617a8104a7756d0cf36578ab01103dc9f07e4dc6feb751296b9c402faf7L131-R132) [[2]](diffhunk://#diff-6ebdb617a8104a7756d0cf36578ab01103dc9f07e4dc6feb751296b9c402faf7L143-L144). **Labeler configuration update** - Removed labeler rules for files related to the old Thymeleaf-based web controllers and UI directories, as these are now obsolete (`.github/labeler-config-srvaroa.yml`). These changes ensure the codebase and documentation are consistent with the new React-only frontend approach, reducing maintenance overhead and potential confusion for contributors. --- ## 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) ### Translations (if applicable) - [ ] I ran [`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md) ### 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. --- .github/labeler-config-srvaroa.yml | 3 - ADDING_TOOLS.md | 16 +- CLAUDE.md | 6 +- DeveloperGuide.md | 165 +-- app/common/build.gradle | 1 - .../common/configuration/AppConfig.java | 10 - .../FileFallbackTemplateResolver.java | 49 - .../model/InputStreamTemplateResource.java | 40 - .../InputStreamTemplateResourceTest.java | 88 -- .../web/ConverterWebController.java | 221 ---- .../controller/web/GeneralWebController.java | 352 ------ .../controller/web/HomeWebController.java | 98 -- .../controller/web/OtherWebController.java | 231 ---- .../controller/web/SecurityWebController.java | 98 -- .../src/main/resources/application.properties | 1 - .../src/main/resources/templates/about.html | 21 - .../src/main/resources/templates/account.html | 479 --------- .../resources/templates/adminSettings.html | 458 -------- .../resources/templates/auto-split-pdf.html | 62 -- .../resources/templates/change-creds.html | 88 -- .../templates/convert/cbr-to-pdf.html | 44 - .../templates/convert/cbz-to-pdf.html | 44 - .../templates/convert/ebook-to-pdf.html | 82 -- .../templates/convert/eml-to-pdf.html | 97 -- .../templates/convert/file-to-pdf.html | 60 -- .../templates/convert/html-to-pdf.html | 43 - .../templates/convert/img-to-pdf.html | 127 --- .../templates/convert/markdown-to-pdf.html | 39 - .../templates/convert/pdf-to-cbr.html | 45 - .../templates/convert/pdf-to-cbz.html | 42 - .../templates/convert/pdf-to-csv.html | 46 - .../templates/convert/pdf-to-epub.html | 87 -- .../templates/convert/pdf-to-html.html | 33 - .../templates/convert/pdf-to-img.html | 95 -- .../templates/convert/pdf-to-markdown.html | 31 - .../templates/convert/pdf-to-pdfa.html | 86 -- .../convert/pdf-to-presentation.html | 49 - .../templates/convert/pdf-to-text.html | 39 - .../templates/convert/pdf-to-vector.html | 46 - .../templates/convert/pdf-to-video.html | 84 -- .../templates/convert/pdf-to-word.html | 46 - .../templates/convert/pdf-to-xml.html | 33 - .../templates/convert/url-to-pdf.html | 37 - .../templates/convert/vector-to-pdf.html | 41 - .../src/main/resources/templates/crop.html | 48 - .../main/resources/templates/database.html | 73 -- .../templates/edit-table-of-contents.html | 175 --- .../src/main/resources/templates/error.html | 31 - .../resources/templates/extract-page.html | 44 - .../resources/templates/fragments/card.html | 21 - .../resources/templates/fragments/common.html | 496 --------- .../templates/fragments/errorBanner.html | 258 ----- .../fragments/errorBannerPerPage.html | 53 - .../fragments/featureGroupHeader.html | 3 - .../fragments/featureGroupHeaderLegacy.html | 6 - .../resources/templates/fragments/footer.html | 26 - .../templates/fragments/languageEntry.html | 7 - .../templates/fragments/languages.html | 44 - .../templates/fragments/multi-toolAdvert.html | 65 -- .../templates/fragments/navElements.html | 342 ------ .../resources/templates/fragments/navbar.html | 325 ------ .../templates/fragments/navbarEntry.html | 18 - .../fragments/navbarEntryCustom.html | 21 - .../main/resources/templates/home-legacy.html | 534 ---------- .../src/main/resources/templates/home.html | 293 ----- .../main/resources/templates/licenses.html | 42 - .../src/main/resources/templates/login.html | 178 ---- .../main/resources/templates/merge-pdfs.html | 72 -- .../templates/misc/add-attachments.html | 42 - .../resources/templates/misc/add-image.html | 116 -- .../templates/misc/add-page-numbers.html | 170 --- .../templates/misc/adjust-contrast.html | 65 -- .../resources/templates/misc/auto-crop.html | 33 - .../resources/templates/misc/auto-rename.html | 35 - .../templates/misc/change-metadata.html | 98 -- .../resources/templates/misc/compare.html | 336 ------ .../templates/misc/compress-pdf.html | 74 -- .../templates/misc/extract-attachments.html | 52 - .../templates/misc/extract-image-scans.html | 59 -- .../templates/misc/extract-images.html | 43 - .../resources/templates/misc/flatten.html | 51 - .../resources/templates/misc/ocr-pdf.html | 240 ----- .../resources/templates/misc/print-file.html | 35 - .../templates/misc/remove-annotations.html | 68 -- .../templates/misc/remove-blanks.html | 41 - .../main/resources/templates/misc/repair.html | 30 - .../templates/misc/replace-color.html | 105 -- .../templates/misc/scanner-effect.html | 162 --- .../templates/misc/show-javascript.html | 96 -- .../main/resources/templates/misc/stamp.html | 203 ---- .../templates/misc/unlock-pdf-forms.html | 34 - .../templates/multi-page-layout.html | 44 - .../main/resources/templates/multi-tool.html | 231 ---- .../main/resources/templates/overlay-pdf.html | 102 -- .../resources/templates/pdf-organizer.html | 77 -- .../templates/pdf-to-single-page.html | 30 - .../main/resources/templates/pipeline.html | 203 ---- .../main/resources/templates/releases.html | 511 --------- .../resources/templates/remove-image-pdf.html | 35 - .../resources/templates/remove-pages.html | 48 - .../main/resources/templates/rotate-pdf.html | 133 --- .../main/resources/templates/scale-pages.html | 49 - .../templates/security/add-password.html | 88 -- .../templates/security/add-watermark.html | 176 --- .../templates/security/auto-redact.html | 96 -- .../templates/security/cert-sign.html | 118 --- .../security/change-permissions.html | 72 -- .../templates/security/get-info-on-pdf.html | 753 ------------- .../resources/templates/security/redact.html | 698 ------------ .../templates/security/remove-cert-sign.html | 35 - .../templates/security/remove-password.html | 40 - .../templates/security/remove-watermark.html | 39 - .../templates/security/sanitize-pdf.html | 59 -- .../security/validate-signature.html | 265 ----- .../src/main/resources/templates/sign.html | 250 ----- .../templates/split-by-size-or-count.html | 52 - .../templates/split-pdf-by-chapters.html | 72 -- .../templates/split-pdf-by-sections.html | 125 --- .../main/resources/templates/split-pdfs.html | 60 -- .../src/main/resources/templates/usage.html | 114 -- .../main/resources/templates/view-pdf.html | 627 ----------- .../web/ConverterWebControllerTest.java | 231 ---- app/proprietary/build.gradle | 1 - .../controller/AuditDashboardController.java | 351 ------ .../web/AuditDashboardWebController.java | 41 - .../security/config/AccountWebController.java | 478 --------- .../controller/web/DatabaseWebController.java | 45 - .../controller/web/TeamWebController.java | 145 --- .../resources/static/css/audit-dashboard.css | 239 ----- .../resources/static/css/modern-tables.css | 394 ------- .../resources/static/js/audit/dashboard.js | 999 ------------------ .../templates/accounts/team-details.html | 205 ---- .../resources/templates/accounts/teams.html | 158 --- .../resources/templates/audit/dashboard.html | 383 ------- devGuide/DeveloperGuide.md | 157 +-- 135 files changed, 11 insertions(+), 18244 deletions(-) delete mode 100644 app/common/src/main/java/stirling/software/common/configuration/FileFallbackTemplateResolver.java delete mode 100644 app/common/src/main/java/stirling/software/common/model/InputStreamTemplateResource.java delete mode 100644 app/common/src/test/java/stirling/software/common/model/InputStreamTemplateResourceTest.java delete mode 100644 app/core/src/main/java/stirling/software/SPDF/controller/web/ConverterWebController.java delete mode 100644 app/core/src/main/java/stirling/software/SPDF/controller/web/GeneralWebController.java delete mode 100644 app/core/src/main/java/stirling/software/SPDF/controller/web/HomeWebController.java delete mode 100644 app/core/src/main/java/stirling/software/SPDF/controller/web/OtherWebController.java delete mode 100644 app/core/src/main/java/stirling/software/SPDF/controller/web/SecurityWebController.java delete mode 100644 app/core/src/main/resources/templates/about.html delete mode 100644 app/core/src/main/resources/templates/account.html delete mode 100644 app/core/src/main/resources/templates/adminSettings.html delete mode 100644 app/core/src/main/resources/templates/auto-split-pdf.html delete mode 100644 app/core/src/main/resources/templates/change-creds.html delete mode 100644 app/core/src/main/resources/templates/convert/cbr-to-pdf.html delete mode 100644 app/core/src/main/resources/templates/convert/cbz-to-pdf.html delete mode 100644 app/core/src/main/resources/templates/convert/ebook-to-pdf.html delete mode 100644 app/core/src/main/resources/templates/convert/eml-to-pdf.html delete mode 100644 app/core/src/main/resources/templates/convert/file-to-pdf.html delete mode 100644 app/core/src/main/resources/templates/convert/html-to-pdf.html delete mode 100644 app/core/src/main/resources/templates/convert/img-to-pdf.html delete mode 100644 app/core/src/main/resources/templates/convert/markdown-to-pdf.html delete mode 100644 app/core/src/main/resources/templates/convert/pdf-to-cbr.html delete mode 100644 app/core/src/main/resources/templates/convert/pdf-to-cbz.html delete mode 100644 app/core/src/main/resources/templates/convert/pdf-to-csv.html delete mode 100644 app/core/src/main/resources/templates/convert/pdf-to-epub.html delete mode 100644 app/core/src/main/resources/templates/convert/pdf-to-html.html delete mode 100644 app/core/src/main/resources/templates/convert/pdf-to-img.html delete mode 100644 app/core/src/main/resources/templates/convert/pdf-to-markdown.html delete mode 100644 app/core/src/main/resources/templates/convert/pdf-to-pdfa.html delete mode 100644 app/core/src/main/resources/templates/convert/pdf-to-presentation.html delete mode 100644 app/core/src/main/resources/templates/convert/pdf-to-text.html delete mode 100644 app/core/src/main/resources/templates/convert/pdf-to-vector.html delete mode 100644 app/core/src/main/resources/templates/convert/pdf-to-video.html delete mode 100644 app/core/src/main/resources/templates/convert/pdf-to-word.html delete mode 100644 app/core/src/main/resources/templates/convert/pdf-to-xml.html delete mode 100644 app/core/src/main/resources/templates/convert/url-to-pdf.html delete mode 100644 app/core/src/main/resources/templates/convert/vector-to-pdf.html delete mode 100644 app/core/src/main/resources/templates/crop.html delete mode 100644 app/core/src/main/resources/templates/database.html delete mode 100644 app/core/src/main/resources/templates/edit-table-of-contents.html delete mode 100644 app/core/src/main/resources/templates/error.html delete mode 100644 app/core/src/main/resources/templates/extract-page.html delete mode 100644 app/core/src/main/resources/templates/fragments/card.html delete mode 100644 app/core/src/main/resources/templates/fragments/common.html delete mode 100644 app/core/src/main/resources/templates/fragments/errorBanner.html delete mode 100644 app/core/src/main/resources/templates/fragments/errorBannerPerPage.html delete mode 100644 app/core/src/main/resources/templates/fragments/featureGroupHeader.html delete mode 100644 app/core/src/main/resources/templates/fragments/featureGroupHeaderLegacy.html delete mode 100644 app/core/src/main/resources/templates/fragments/footer.html delete mode 100644 app/core/src/main/resources/templates/fragments/languageEntry.html delete mode 100644 app/core/src/main/resources/templates/fragments/languages.html delete mode 100644 app/core/src/main/resources/templates/fragments/multi-toolAdvert.html delete mode 100644 app/core/src/main/resources/templates/fragments/navElements.html delete mode 100644 app/core/src/main/resources/templates/fragments/navbar.html delete mode 100644 app/core/src/main/resources/templates/fragments/navbarEntry.html delete mode 100644 app/core/src/main/resources/templates/fragments/navbarEntryCustom.html delete mode 100644 app/core/src/main/resources/templates/home-legacy.html delete mode 100644 app/core/src/main/resources/templates/home.html delete mode 100644 app/core/src/main/resources/templates/licenses.html delete mode 100644 app/core/src/main/resources/templates/login.html delete mode 100644 app/core/src/main/resources/templates/merge-pdfs.html delete mode 100644 app/core/src/main/resources/templates/misc/add-attachments.html delete mode 100644 app/core/src/main/resources/templates/misc/add-image.html delete mode 100644 app/core/src/main/resources/templates/misc/add-page-numbers.html delete mode 100644 app/core/src/main/resources/templates/misc/adjust-contrast.html delete mode 100644 app/core/src/main/resources/templates/misc/auto-crop.html delete mode 100644 app/core/src/main/resources/templates/misc/auto-rename.html delete mode 100644 app/core/src/main/resources/templates/misc/change-metadata.html delete mode 100644 app/core/src/main/resources/templates/misc/compare.html delete mode 100644 app/core/src/main/resources/templates/misc/compress-pdf.html delete mode 100644 app/core/src/main/resources/templates/misc/extract-attachments.html delete mode 100644 app/core/src/main/resources/templates/misc/extract-image-scans.html delete mode 100644 app/core/src/main/resources/templates/misc/extract-images.html delete mode 100644 app/core/src/main/resources/templates/misc/flatten.html delete mode 100644 app/core/src/main/resources/templates/misc/ocr-pdf.html delete mode 100644 app/core/src/main/resources/templates/misc/print-file.html delete mode 100644 app/core/src/main/resources/templates/misc/remove-annotations.html delete mode 100644 app/core/src/main/resources/templates/misc/remove-blanks.html delete mode 100644 app/core/src/main/resources/templates/misc/repair.html delete mode 100644 app/core/src/main/resources/templates/misc/replace-color.html delete mode 100644 app/core/src/main/resources/templates/misc/scanner-effect.html delete mode 100644 app/core/src/main/resources/templates/misc/show-javascript.html delete mode 100644 app/core/src/main/resources/templates/misc/stamp.html delete mode 100644 app/core/src/main/resources/templates/misc/unlock-pdf-forms.html delete mode 100644 app/core/src/main/resources/templates/multi-page-layout.html delete mode 100644 app/core/src/main/resources/templates/multi-tool.html delete mode 100644 app/core/src/main/resources/templates/overlay-pdf.html delete mode 100644 app/core/src/main/resources/templates/pdf-organizer.html delete mode 100644 app/core/src/main/resources/templates/pdf-to-single-page.html delete mode 100644 app/core/src/main/resources/templates/pipeline.html delete mode 100644 app/core/src/main/resources/templates/releases.html delete mode 100644 app/core/src/main/resources/templates/remove-image-pdf.html delete mode 100644 app/core/src/main/resources/templates/remove-pages.html delete mode 100644 app/core/src/main/resources/templates/rotate-pdf.html delete mode 100644 app/core/src/main/resources/templates/scale-pages.html delete mode 100644 app/core/src/main/resources/templates/security/add-password.html delete mode 100644 app/core/src/main/resources/templates/security/add-watermark.html delete mode 100644 app/core/src/main/resources/templates/security/auto-redact.html delete mode 100644 app/core/src/main/resources/templates/security/cert-sign.html delete mode 100644 app/core/src/main/resources/templates/security/change-permissions.html delete mode 100644 app/core/src/main/resources/templates/security/get-info-on-pdf.html delete mode 100644 app/core/src/main/resources/templates/security/redact.html delete mode 100644 app/core/src/main/resources/templates/security/remove-cert-sign.html delete mode 100644 app/core/src/main/resources/templates/security/remove-password.html delete mode 100644 app/core/src/main/resources/templates/security/remove-watermark.html delete mode 100644 app/core/src/main/resources/templates/security/sanitize-pdf.html delete mode 100644 app/core/src/main/resources/templates/security/validate-signature.html delete mode 100644 app/core/src/main/resources/templates/sign.html delete mode 100644 app/core/src/main/resources/templates/split-by-size-or-count.html delete mode 100644 app/core/src/main/resources/templates/split-pdf-by-chapters.html delete mode 100644 app/core/src/main/resources/templates/split-pdf-by-sections.html delete mode 100644 app/core/src/main/resources/templates/split-pdfs.html delete mode 100644 app/core/src/main/resources/templates/usage.html delete mode 100644 app/core/src/main/resources/templates/view-pdf.html delete mode 100644 app/core/src/test/java/stirling/software/SPDF/controller/web/ConverterWebControllerTest.java delete mode 100644 app/proprietary/src/main/java/stirling/software/proprietary/controller/AuditDashboardController.java delete mode 100644 app/proprietary/src/main/java/stirling/software/proprietary/controller/web/AuditDashboardWebController.java delete mode 100644 app/proprietary/src/main/java/stirling/software/proprietary/security/config/AccountWebController.java delete mode 100644 app/proprietary/src/main/java/stirling/software/proprietary/security/controller/web/DatabaseWebController.java delete mode 100644 app/proprietary/src/main/java/stirling/software/proprietary/security/controller/web/TeamWebController.java delete mode 100644 app/proprietary/src/main/resources/static/css/audit-dashboard.css delete mode 100644 app/proprietary/src/main/resources/static/css/modern-tables.css delete mode 100644 app/proprietary/src/main/resources/static/js/audit/dashboard.js delete mode 100644 app/proprietary/src/main/resources/templates/accounts/team-details.html delete mode 100644 app/proprietary/src/main/resources/templates/accounts/teams.html delete mode 100644 app/proprietary/src/main/resources/templates/audit/dashboard.html diff --git a/.github/labeler-config-srvaroa.yml b/.github/labeler-config-srvaroa.yml index 177a35f06..4117ec478 100644 --- a/.github/labeler-config-srvaroa.yml +++ b/.github/labeler-config-srvaroa.yml @@ -63,9 +63,6 @@ labels: files: - 'app/core/src/main/resources/static/.*' - 'app/proprietary/src/main/resources/static/.*' - - '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/**' - 'frontend/.*' - 'frontend/**/.*' diff --git a/ADDING_TOOLS.md b/ADDING_TOOLS.md index d24641b00..85775d8dd 100644 --- a/ADDING_TOOLS.md +++ b/ADDING_TOOLS.md @@ -1,6 +1,6 @@ # 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. +This guide covers how to add new PDF tools to the React frontend. ## Overview @@ -188,7 +188,7 @@ import { use[ToolName]Tips } from "../components/tooltips/use[ToolName]Tips"; const [ToolName] = (props: BaseToolProps) => { const tips = use[ToolName]Tips(); - + // In your steps array: steps: [ { @@ -257,22 +257,14 @@ Update translation files. **Important: Only update `en-GB` files** - other langu - Add `options.*` keys if your tool has settings with descriptions **Tooltip Writing Guidelines:** -- **Use simple, everyday language** - avoid technical terms like "converts interactive elements" +- **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 `
` 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 +## 6. Testing Your Tool - Verify tool appears in UI with correct icon and description - Test with various file sizes and types - Confirm translations work diff --git a/CLAUDE.md b/CLAUDE.md index d111f8da3..cb3d9048f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -128,8 +128,8 @@ return useToolOperation({ ## Architecture Overview ### Project Structure -- **Backend**: Spring Boot application with Thymeleaf templating -- **Frontend**: React-based SPA in `/frontend` directory (Thymeleaf templates fully replaced) +- **Backend**: Spring Boot application +- **Frontend**: React-based SPA in `/frontend` directory - **File Storage**: IndexedDB for client-side file persistence and thumbnails - **Internationalization**: JSON-based translations (converted from backend .properties) - **PDF Processing**: PDFBox for core PDF operations, LibreOffice for conversions, PDF.js for client-side rendering @@ -140,8 +140,6 @@ return useToolOperation({ - **API Controllers** (`src/main/java/.../controller/api/`): REST endpoints for PDF operations - Organized by function: converters, security, misc, pipeline - Follow pattern: `@RestController` + `@RequestMapping("/api/v1/...")` -- **Web Controllers** (`src/main/java/.../controller/web/`): Serve Thymeleaf templates - - Pattern: `@Controller` + return template names ### Key Components - **SPDFApplication.java**: Main application class with desktop UI and browser launching logic diff --git a/DeveloperGuide.md b/DeveloperGuide.md index 06eef471c..738816b86 100644 --- a/DeveloperGuide.md +++ b/DeveloperGuide.md @@ -2,7 +2,7 @@ ## 1. Introduction -Stirling-PDF is a robust, locally hosted, web-based PDF manipulation tool. **Stirling 2.0** represents a complete frontend rewrite, replacing the legacy Thymeleaf-based UI with a modern React SPA (Single Page Application). +Stirling-PDF is a robust, locally hosted, web-based PDF manipulation tool. **Stirling 2.0** represents a complete frontend rewrite with a modern React SPA (Single Page Application). This guide focuses on developing for Stirling 2.0, including both the React frontend and Spring Boot backend development workflows. @@ -38,9 +38,6 @@ This guide focuses on developing for Stirling 2.0, including both the React fron - PDF file association support - Self-contained JRE bundling with JLink -**Legacy (reference only during development):** -- Thymeleaf templates (being completely replaced in 2.0) - ## 3. Development Environment Setup ### Prerequisites @@ -100,9 +97,6 @@ Stirling 2.0 uses client-side file storage: - **PDF.js**: Handles client-side PDF rendering and processing - **URL Parameters**: Support for deep linking and tool state persistence -### Legacy Code Reference -The existing Thymeleaf templates remain in the codebase during development as reference material but will be completely removed for the 2.0 release. - ### Tauri Desktop App Development Stirling-PDF can be packaged as a cross-platform desktop application using Tauri with PDF file association support and bundled JRE. See [the frontend README](frontend/README.md#tauri) for build instructions. @@ -154,7 +148,6 @@ Stirling-PDF/ │ │ │ ├── css/ │ │ │ ├── js/ │ │ │ └── pdfjs/ -│ │ └── templates/ # Legacy Thymeleaf templates (reference only) │ └── test/ ├── testing/ # Cucumber and integration tests │ └── cucumber/ # Cucumber test files @@ -309,7 +302,6 @@ For quick iterations and development of Java backend, JavaScript, and UI compone - RESTful API endpoints - JavaScript functionality - User interface components and styling -- Thymeleaf templates To run Stirling-PDF locally: @@ -401,7 +393,7 @@ Remember to test your changes thoroughly to ensure they don't break any existing ### React Component Development (Stirling 2.0) -For Stirling 2.0, new features are built as React components instead of Thymeleaf templates: +For Stirling 2.0, new features are built as React components: #### Creating a New Tool Component @@ -448,61 +440,6 @@ For Stirling 2.0, new features are built as React components instead of Thymelea 3. **Register in Tool Picker:** Update the tool picker component to include the new tool with proper routing and URL parameter support. -### Legacy Reference: Overview of Thymeleaf - -Thymeleaf is a server-side Java HTML template engine. It is used in Stirling-PDF to render dynamic web pages. Thymeleaf integrates heavily with Spring Boot. - -### Thymeleaf overview - -In Stirling-PDF, Thymeleaf is used to create HTML templates that are rendered on the server side. These templates are located in the `stirling-pdf/src/main/resources/templates` directory. Thymeleaf templates use a combination of HTML and special Thymeleaf attributes to dynamically generate content. - -Some examples of this are: - -```html - -``` -or -```html - -``` - -Where it uses the `th:block`, `th:` indicating it's a special Thymeleaf element to be used server-side in generating the HTML, and block being the actual element type. -In this case, we are inserting the `navbar` entry within the `fragments/navbar.html` fragment into the `th:block` element. - -They can be more complex, such as: - -```html - -``` - -Which is the same as above but passes the parameters title and header into the fragment `common.html` to be used in its HTML generation. - -Thymeleaf can also be used to loop through objects or pass things from the Java side into the HTML side. - -```java - @GetMapping - public String newFeaturePage(Model model) { - model.addAttribute("exampleData", exampleData); - return "new-feature"; - } -``` - -In the above example, if exampleData is a list of plain java objects of class Person and within it, you had id, name, age, etc. You can reference it like so - -```html - - - - - - - - - -``` - -This would generate n entries of tr for each person in exampleData - ### Adding a New Feature to the Backend (API) 1. **Create a New Controller:** @@ -527,7 +464,7 @@ This would generate n entries of tr for each person in exampleData @GetMapping @Operation(summary = "New Feature", description = "This is a new feature endpoint.") public String newFeature() { - return "NewFeatureResponse"; // This refers to the NewFeatureResponse.html template presenting the user with the generated html from that file when they navigate to /api/v1/new-feature + return "NewFeatureResponse"; } } ``` @@ -582,91 +519,6 @@ This would generate n entries of tr for each person in exampleData } ``` -### Adding a New Feature to the Frontend (UI) - -1. **Create a New Thymeleaf Template:** - - Create a new HTML file in the `stirling-pdf/src/main/resources/templates` directory. - - Use Thymeleaf attributes to dynamically generate content. - - Use `extract-page.html` as a base example for the HTML template, which is useful to ensure importing of the general layout, navbar, and footer. - - ```html - - - - - - - -
-
- -

-
-
-
-
- upload - -
- -
- -
- - -
- - - -
-
-
-
- -
- - - ``` - -2. **Create a New Controller for the UI:** - - Create a new Java class in the `stirling-pdf/src/main/java/stirling/software/SPDF/controller/ui` directory. - - Annotate the class with `@Controller` and `@RequestMapping` to define the UI endpoint. - - ```java - package stirling.software.SPDF.controller.ui; - - import org.springframework.beans.factory.annotation.Autowired; - import org.springframework.stereotype.Controller; - import org.springframework.ui.Model; - import org.springframework.web.bind.annotation.GetMapping; - import org.springframework.web.bind.annotation.RequestMapping; - import stirling.software.SPDF.service.NewFeatureService; - - @Controller - @RequestMapping("/new-feature") - public class NewFeatureUIController { - - @Autowired - private NewFeatureService newFeatureService; - - @GetMapping - public String newFeaturePage(Model model) { - model.addAttribute("newFeatureData", newFeatureService.getNewFeatureData()); - return "new-feature"; - } - } - ``` - -3. **Update the Navigation Bar:** - - Add a link to the new feature page in the navigation bar. - - Update the `stirling-pdf/src/main/resources/templates/fragments/navbar.html` file. - - ```html - - ``` - ## Adding New Translations to Existing Language Files in Stirling-PDF When adding a new feature or modifying existing ones in Stirling-PDF, you'll need to add new translation entries to the existing language files. Here's a step-by-step guide: @@ -696,15 +548,4 @@ pdfSplitter.input.pages=Enter page numbers to split Add these entries to the default GB language file and any others you wish, translating the values as appropriate for each language. -### 3. Use Translations in Thymeleaf Templates - -In your Thymeleaf templates, use the `#{key}` syntax to reference the new translations: - -```html -

PDF Splitter

-

Split your PDF into multiple documents

- - -``` - Remember, never hard-code text in your templates or Java code. Always use translation keys to ensure proper localization. diff --git a/app/common/build.gradle b/app/common/build.gradle index 169f7a503..25ae6707b 100644 --- a/app/common/build.gradle +++ b/app/common/build.gradle @@ -29,7 +29,6 @@ spotless { dependencies { api 'org.springframework.boot:spring-boot-starter-web' api 'org.springframework.boot:spring-boot-starter-aop' - // api 'org.springframework.boot:spring-boot-starter-thymeleaf' // Deprecated - UI moved to React frontend api 'com.googlecode.owasp-java-html-sanitizer:owasp-java-html-sanitizer:20240325.1' api 'com.fathzer:javaluator:3.0.6' api 'com.posthog.java:posthog:1.2.0' diff --git a/app/common/src/main/java/stirling/software/common/configuration/AppConfig.java b/app/common/src/main/java/stirling/software/common/configuration/AppConfig.java index efc42046d..6d5e2507e 100644 --- a/app/common/src/main/java/stirling/software/common/configuration/AppConfig.java +++ b/app/common/src/main/java/stirling/software/common/configuration/AppConfig.java @@ -64,16 +64,6 @@ public class AppConfig { return v2Enabled; } - /* Commented out Thymeleaf template engine bean - to be removed when frontend migration is complete - @Bean - @ConditionalOnProperty(name = "system.customHTMLFiles", havingValue = "true") - public SpringTemplateEngine templateEngine(ResourceLoader resourceLoader) { - SpringTemplateEngine templateEngine = new SpringTemplateEngine(); - templateEngine.addTemplateResolver(new FileFallbackTemplateResolver(resourceLoader)); - return templateEngine; - } - */ - @Bean(name = "loginEnabled") public boolean loginEnabled() { return applicationProperties.getSecurity().isEnableLogin(); diff --git a/app/common/src/main/java/stirling/software/common/configuration/FileFallbackTemplateResolver.java b/app/common/src/main/java/stirling/software/common/configuration/FileFallbackTemplateResolver.java deleted file mode 100644 index 9c5e0a7ae..000000000 --- a/app/common/src/main/java/stirling/software/common/configuration/FileFallbackTemplateResolver.java +++ /dev/null @@ -1,49 +0,0 @@ -package stirling.software.common.configuration; - -/* Commented out entire FileFallbackTemplateResolver class - Thymeleaf dependency removed - * This class will be removed when frontend migration to React is complete - - -@Slf4j -public class FileFallbackTemplateResolver extends AbstractConfigurableTemplateResolver { - - private final ResourceLoader resourceLoader; - - public FileFallbackTemplateResolver(ResourceLoader resourceLoader) { - super(); - this.resourceLoader = resourceLoader; - setSuffix(".html"); - } - - // Note this does not work in local IDE, Prod jar only. - @Override - protected ITemplateResource computeTemplateResource( - IEngineConfiguration configuration, - String ownerTemplate, - String template, - String resourceName, - String characterEncoding, - Map templateResolutionAttributes) { - Resource resource = - resourceLoader.getResource( - "file:" + InstallationPathConfig.getTemplatesPath() + resourceName); - try { - if (resource.exists() && resource.isReadable()) { - return new FileTemplateResource(resource.getFile().getPath(), characterEncoding); - } - } catch (IOException e) { - // Log the exception to help with debugging issues loading external templates - log.warn("Unable to read template '{}' from file system", resourceName, e); - } - - InputStream inputStream = - Thread.currentThread() - .getContextClassLoader() - .getResourceAsStream("templates/" + resourceName); - if (inputStream != null) { - return new InputStreamTemplateResource(inputStream, "UTF-8"); - } - return null; - } -} -*/ diff --git a/app/common/src/main/java/stirling/software/common/model/InputStreamTemplateResource.java b/app/common/src/main/java/stirling/software/common/model/InputStreamTemplateResource.java deleted file mode 100644 index 92d7418ea..000000000 --- a/app/common/src/main/java/stirling/software/common/model/InputStreamTemplateResource.java +++ /dev/null @@ -1,40 +0,0 @@ -package stirling.software.common.model; - -/* Commented out entire InputStreamTemplateResource class - Thymeleaf dependency removed - * This class will be removed when frontend migration to React is complete - - - -@RequiredArgsConstructor -@Getter -public class InputStreamTemplateResource implements ITemplateResource { - private final InputStream inputStream; - private final String characterEncoding; - - @Override - public Reader reader() throws IOException { - return new InputStreamReader(inputStream, characterEncoding); - } - - @Override - public ITemplateResource relative(String relativeLocation) { - // Implement logic for relative resources, if needed - throw new UnsupportedOperationException("Relative resources not supported"); - } - - @Override - public String getDescription() { - return "InputStream resource [Stream]"; - } - - @Override - public String getBaseName() { - return "streamResource"; - } - - @Override - public boolean exists() { - return inputStream != null; - } -} -*/ diff --git a/app/common/src/test/java/stirling/software/common/model/InputStreamTemplateResourceTest.java b/app/common/src/test/java/stirling/software/common/model/InputStreamTemplateResourceTest.java deleted file mode 100644 index 6543c1763..000000000 --- a/app/common/src/test/java/stirling/software/common/model/InputStreamTemplateResourceTest.java +++ /dev/null @@ -1,88 +0,0 @@ -package stirling.software.common.model; - -/* Commented out - InputStreamTemplateResource class removed with Thymeleaf migration - * This test will be removed when frontend migration to React is complete - - -public class InputStreamTemplateResourceTest { - - @Test - void gettersReturnProvidedFields() { - byte[] data = {1, 2, 3}; - InputStream is = new ByteArrayInputStream(data); - String encoding = "UTF-8"; - InputStreamTemplateResource resource = new InputStreamTemplateResource(is, encoding); - - assertSame(is, resource.getInputStream()); - assertEquals(encoding, resource.getCharacterEncoding()); - } - - @Test - void fieldsAreFinal() throws NoSuchFieldException { - Field inputStreamField = InputStreamTemplateResource.class.getDeclaredField("inputStream"); - Field characterEncodingField = - InputStreamTemplateResource.class.getDeclaredField("characterEncoding"); - - assertTrue(Modifier.isFinal(inputStreamField.getModifiers())); - assertTrue(Modifier.isFinal(characterEncodingField.getModifiers())); - } - - @Test - void noSetterMethodsPresent() { - long setterCount = - Arrays.stream(InputStreamTemplateResource.class.getDeclaredMethods()) - .filter(method -> method.getName().startsWith("set")) - .count(); - - assertEquals(0, setterCount, "InputStreamTemplateResource should not have setter methods"); - } - - @Test - void readerReturnsCorrectContent() throws Exception { - String content = "Hello, world!"; - InputStream is = new ByteArrayInputStream(content.getBytes("UTF-8")); - InputStreamTemplateResource resource = new InputStreamTemplateResource(is, "UTF-8"); - - try (Reader reader = resource.reader()) { - char[] buffer = new char[content.length()]; - int read = reader.read(buffer); - assertEquals(content.length(), read); - assertEquals(content, new String(buffer)); - } - } - - @Test - void relativeThrowsUnsupportedOperationException() { - InputStream is = new ByteArrayInputStream(new byte[0]); - InputStreamTemplateResource resource = new InputStreamTemplateResource(is, "UTF-8"); - assertThrows(UnsupportedOperationException.class, () -> resource.relative("other")); - } - - @Test - void getDescriptionReturnsExpectedString() { - InputStream is = new ByteArrayInputStream(new byte[0]); - InputStreamTemplateResource resource = new InputStreamTemplateResource(is, "UTF-8"); - assertEquals("InputStream resource [Stream]", resource.getDescription()); - } - - @Test - void getBaseNameReturnsExpectedString() { - InputStream is = new ByteArrayInputStream(new byte[0]); - InputStreamTemplateResource resource = new InputStreamTemplateResource(is, "UTF-8"); - assertEquals("streamResource", resource.getBaseName()); - } - - @Test - void existsReturnsTrueWhenInputStreamNotNull() { - InputStream is = new ByteArrayInputStream(new byte[0]); - InputStreamTemplateResource resource = new InputStreamTemplateResource(is, "UTF-8"); - assertTrue(resource.exists()); - } - - @Test - void existsReturnsFalseWhenInputStreamIsNull() { - InputStreamTemplateResource resource = new InputStreamTemplateResource(null, "UTF-8"); - assertFalse(resource.exists()); - } -} -*/ diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/web/ConverterWebController.java b/app/core/src/main/java/stirling/software/SPDF/controller/web/ConverterWebController.java deleted file mode 100644 index d5c05ff68..000000000 --- a/app/core/src/main/java/stirling/software/SPDF/controller/web/ConverterWebController.java +++ /dev/null @@ -1,221 +0,0 @@ -package stirling.software.SPDF.controller.web; - -import org.springframework.http.HttpStatus; -import org.springframework.stereotype.Controller; -import org.springframework.ui.Model; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.server.ResponseStatusException; -import org.springframework.web.servlet.ModelAndView; - -import io.swagger.v3.oas.annotations.Hidden; -import io.swagger.v3.oas.annotations.tags.Tag; - -import stirling.software.SPDF.config.EndpointConfiguration; -import stirling.software.common.model.ApplicationProperties; -import stirling.software.common.util.ApplicationContextProvider; -import stirling.software.common.util.CheckProgramInstall; - -@Controller -@Tag(name = "Convert", description = "Convert APIs") -public class ConverterWebController { - - @GetMapping("/img-to-pdf") - @Hidden - public String convertImgToPdfForm(Model model) { - model.addAttribute("currentPage", "img-to-pdf"); - return "convert/img-to-pdf"; - } - - @GetMapping("/cbz-to-pdf") - @Hidden - public String convertCbzToPdfForm(Model model) { - model.addAttribute("currentPage", "cbz-to-pdf"); - return "convert/cbz-to-pdf"; - } - - @GetMapping("/pdf-to-cbz") - @Hidden - public String convertPdfToCbzForm(Model model) { - model.addAttribute("currentPage", "pdf-to-cbz"); - return "convert/pdf-to-cbz"; - } - - @GetMapping("/cbr-to-pdf") - @Hidden - public String convertCbrToPdfForm(Model model) { - model.addAttribute("currentPage", "cbr-to-pdf"); - return "convert/cbr-to-pdf"; - } - - @GetMapping("/ebook-to-pdf") - @Hidden - public String convertEbookToPdfForm(Model model) { - model.addAttribute("currentPage", "ebook-to-pdf"); - return "convert/ebook-to-pdf"; - } - - @GetMapping("/pdf-to-epub") - @Hidden - public String convertPdfToEpubForm(Model model) { - if (!ApplicationContextProvider.getBean(EndpointConfiguration.class) - .isEndpointEnabled("pdf-to-epub")) { - throw new ResponseStatusException(HttpStatus.NOT_FOUND); - } - model.addAttribute("currentPage", "pdf-to-epub"); - return "convert/pdf-to-epub"; - } - - @GetMapping("/pdf-to-cbr") - @Hidden - public String convertPdfToCbrForm(Model model) { - if (!ApplicationContextProvider.getBean(EndpointConfiguration.class) - .isEndpointEnabled("pdf-to-cbr")) { - throw new ResponseStatusException(HttpStatus.NOT_FOUND); - } - model.addAttribute("currentPage", "pdf-to-cbr"); - return "convert/pdf-to-cbr"; - } - - @GetMapping("/html-to-pdf") - @Hidden - public String convertHTMLToPdfForm(Model model) { - model.addAttribute("currentPage", "html-to-pdf"); - return "convert/html-to-pdf"; - } - - @GetMapping("/markdown-to-pdf") - @Hidden - public String convertMarkdownToPdfForm(Model model) { - model.addAttribute("currentPage", "markdown-to-pdf"); - return "convert/markdown-to-pdf"; - } - - @GetMapping("/pdf-to-markdown") - @Hidden - public String convertPdfToMarkdownForm(Model model) { - model.addAttribute("currentPage", "pdf-to-markdown"); - return "convert/pdf-to-markdown"; - } - - @GetMapping("/url-to-pdf") - @Hidden - public String convertURLToPdfForm(Model model) { - model.addAttribute("currentPage", "url-to-pdf"); - return "convert/url-to-pdf"; - } - - @GetMapping("/file-to-pdf") - @Hidden - public String convertToPdfForm(Model model) { - model.addAttribute("currentPage", "file-to-pdf"); - return "convert/file-to-pdf"; - } - - // PDF TO...... - - @GetMapping("/pdf-to-img") - @Hidden - public String pdfToimgForm(Model model) { - boolean isPython = CheckProgramInstall.isPythonAvailable(); - ApplicationProperties properties = - ApplicationContextProvider.getBean(ApplicationProperties.class); - if (properties != null && properties.getSystem() != null) { - model.addAttribute("maxDPI", properties.getSystem().getMaxDPI()); - } else { - model.addAttribute("maxDPI", 500); // Default value if not set - } - model.addAttribute("isPython", isPython); - model.addAttribute("currentPage", "pdf-to-img"); - return "convert/pdf-to-img"; - } - - @GetMapping("/pdf-to-html") - @Hidden - public ModelAndView pdfToHTML() { - ModelAndView modelAndView = new ModelAndView("convert/pdf-to-html"); - modelAndView.addObject("currentPage", "pdf-to-html"); - return modelAndView; - } - - @GetMapping("/pdf-to-presentation") - @Hidden - public ModelAndView pdfToPresentation() { - ModelAndView modelAndView = new ModelAndView("convert/pdf-to-presentation"); - modelAndView.addObject("currentPage", "pdf-to-presentation"); - return modelAndView; - } - - @GetMapping("/pdf-to-text") - @Hidden - public ModelAndView pdfToText() { - ModelAndView modelAndView = new ModelAndView("convert/pdf-to-text"); - modelAndView.addObject("currentPage", "pdf-to-text"); - return modelAndView; - } - - @GetMapping("/pdf-to-word") - @Hidden - public ModelAndView pdfToWord() { - ModelAndView modelAndView = new ModelAndView("convert/pdf-to-word"); - modelAndView.addObject("currentPage", "pdf-to-word"); - return modelAndView; - } - - @GetMapping("/pdf-to-xml") - @Hidden - public ModelAndView pdfToXML() { - ModelAndView modelAndView = new ModelAndView("convert/pdf-to-xml"); - modelAndView.addObject("currentPage", "pdf-to-xml"); - return modelAndView; - } - - @GetMapping("/pdf-to-csv") - @Hidden - public ModelAndView pdfToCSV() { - ModelAndView modelAndView = new ModelAndView("convert/pdf-to-csv"); - modelAndView.addObject("currentPage", "pdf-to-csv"); - return modelAndView; - } - - @GetMapping("/pdf-to-pdfa") - @Hidden - public String pdfToPdfAForm(Model model) { - model.addAttribute("currentPage", "pdf-to-pdfa"); - return "convert/pdf-to-pdfa"; - } - - @GetMapping("/pdf-to-vector") - @Hidden - public String pdfToVectorForm(Model model) { - model.addAttribute("currentPage", "pdf-to-vector"); - return "convert/pdf-to-vector"; - } - - @GetMapping("/vector-to-pdf") - @Hidden - public String vectorToPdfForm(Model model) { - model.addAttribute("currentPage", "vector-to-pdf"); - return "convert/vector-to-pdf"; - } - - @GetMapping("/eml-to-pdf") - @Hidden - public String convertEmlToPdfForm(Model model) { - model.addAttribute("currentPage", "eml-to-pdf"); - return "convert/eml-to-pdf"; - } - - @GetMapping("/pdf-to-video") - @Hidden - public String pdfToVideo(Model model) { - ApplicationProperties properties = - ApplicationContextProvider.getBean(ApplicationProperties.class); - if (properties != null && properties.getSystem() != null) { - model.addAttribute("maxDPI", properties.getSystem().getMaxDPI()); - } else { - model.addAttribute("maxDPI", 500); - } - model.addAttribute("currentPage", "pdf-to-video"); - return "convert/pdf-to-video"; - } -} diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/web/GeneralWebController.java b/app/core/src/main/java/stirling/software/SPDF/controller/web/GeneralWebController.java deleted file mode 100644 index e1796b827..000000000 --- a/app/core/src/main/java/stirling/software/SPDF/controller/web/GeneralWebController.java +++ /dev/null @@ -1,352 +0,0 @@ -package stirling.software.SPDF.controller.web; - -import java.io.File; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.*; -import java.util.stream.Stream; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.core.io.Resource; -import org.springframework.core.io.ResourceLoader; -import org.springframework.ui.Model; - -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; - -import io.swagger.v3.oas.annotations.Hidden; -import io.swagger.v3.oas.annotations.tags.Tag; - -import lombok.Getter; -import lombok.Setter; -import lombok.extern.slf4j.Slf4j; - -import stirling.software.SPDF.model.SignatureFile; -import stirling.software.SPDF.service.SharedSignatureService; -import stirling.software.common.configuration.InstallationPathConfig; -import stirling.software.common.configuration.RuntimePathConfig; -import stirling.software.common.service.UserServiceInterface; -import stirling.software.common.util.ExceptionUtils; -import stirling.software.common.util.GeneralUtils; - -// @Controller // Disabled - Backend-only mode, no Thymeleaf UI -@Tag(name = "General", description = "General APIs") -@Slf4j -public class GeneralWebController { - - private final SharedSignatureService signatureService; - private final UserServiceInterface userService; - private final ResourceLoader resourceLoader; - private final RuntimePathConfig runtimePathConfig; - - public GeneralWebController( - SharedSignatureService signatureService, - @Autowired(required = false) UserServiceInterface userService, - ResourceLoader resourceLoader, - RuntimePathConfig runtimePathConfig) { - this.signatureService = signatureService; - this.userService = userService; - this.resourceLoader = resourceLoader; - this.runtimePathConfig = runtimePathConfig; - } - - @Deprecated - // @GetMapping("/pipeline") - @Hidden - public String pipelineForm(Model model) { - model.addAttribute("currentPage", "pipeline"); - List pipelineConfigs = new ArrayList<>(); - List> pipelineConfigsWithNames = new ArrayList<>(); - if (new File(runtimePathConfig.getPipelineDefaultWebUiConfigs()).exists()) { - try (Stream paths = - Files.walk(Paths.get(runtimePathConfig.getPipelineDefaultWebUiConfigs()))) { - List jsonFiles = - paths.filter(Files::isRegularFile) - .filter(p -> p.toString().endsWith(".json")) - .toList(); - for (Path jsonFile : jsonFiles) { - String content = Files.readString(jsonFile, StandardCharsets.UTF_8); - pipelineConfigs.add(content); - } - for (String config : pipelineConfigs) { - Map jsonContent = - new ObjectMapper() - .readValue(config, new TypeReference>() {}); - String name = (String) jsonContent.get("name"); - if (name == null || name.isEmpty()) { - String filename = - jsonFiles - .get(pipelineConfigs.indexOf(config)) - .getFileName() - .toString(); - name = filename.substring(0, filename.lastIndexOf('.')); - } - Map configWithName = new HashMap<>(); - configWithName.put("json", config); - configWithName.put("name", name); - pipelineConfigsWithNames.add(configWithName); - } - } catch (IOException e) { - log.error("exception", e); - } - } - if (pipelineConfigsWithNames.isEmpty()) { - Map configWithName = new HashMap<>(); - configWithName.put("json", ""); - configWithName.put("name", "No preloaded configs found"); - pipelineConfigsWithNames.add(configWithName); - } - model.addAttribute("pipelineConfigsWithNames", pipelineConfigsWithNames); - model.addAttribute("pipelineConfigs", pipelineConfigs); - return "pipeline"; - } - - @Deprecated - // @GetMapping("/merge-pdfs") - @Hidden - public String mergePdfForm(Model model) { - model.addAttribute("currentPage", "merge-pdfs"); - return "merge-pdfs"; - } - - @Deprecated - // @GetMapping("/split-pdf-by-sections") - @Hidden - public String splitPdfBySections(Model model) { - model.addAttribute("currentPage", "split-pdf-by-sections"); - return "split-pdf-by-sections"; - } - - @Deprecated - // @GetMapping("/split-pdf-by-chapters") - @Hidden - public String splitPdfByChapters(Model model) { - model.addAttribute("currentPage", "split-pdf-by-chapters"); - return "split-pdf-by-chapters"; - } - - @Deprecated - // @GetMapping("/view-pdf") - @Hidden - public String ViewPdfForm2(Model model) { - model.addAttribute("currentPage", "view-pdf"); - return "view-pdf"; - } - - @Deprecated - // @GetMapping("/edit-table-of-contents") - @Hidden - public String editTableOfContents(Model model) { - model.addAttribute("currentPage", "edit-table-of-contents"); - return "edit-table-of-contents"; - } - - @Deprecated - // @GetMapping("/multi-tool") - @Hidden - public String multiToolForm(Model model) { - model.addAttribute("currentPage", "multi-tool"); - return "multi-tool"; - } - - @Deprecated - // @GetMapping("/remove-pages") - @Hidden - public String pageDeleter(Model model) { - model.addAttribute("currentPage", "remove-pages"); - return "remove-pages"; - } - - @Deprecated - // @GetMapping("/pdf-organizer") - @Hidden - public String pageOrganizer(Model model) { - model.addAttribute("currentPage", "pdf-organizer"); - return "pdf-organizer"; - } - - @Deprecated - // @GetMapping("/extract-page") - @Hidden - public String extractPages(Model model) { - model.addAttribute("currentPage", "extract-page"); - return "extract-page"; - } - - @Deprecated - // @GetMapping("/pdf-to-single-page") - @Hidden - public String pdfToSinglePage(Model model) { - model.addAttribute("currentPage", "pdf-to-single-page"); - return "pdf-to-single-page"; - } - - @Deprecated - // @GetMapping("/rotate-pdf") - @Hidden - public String rotatePdfForm(Model model) { - model.addAttribute("currentPage", "rotate-pdf"); - return "rotate-pdf"; - } - - @Deprecated - // @GetMapping("/split-pdfs") - @Hidden - public String splitPdfForm(Model model) { - model.addAttribute("currentPage", "split-pdfs"); - return "split-pdfs"; - } - - @Deprecated - // @GetMapping("/sign") - @Hidden - public String signForm(Model model) { - String username = ""; - if (userService != null) { - username = userService.getCurrentUsername(); - } - // Get signatures from both personal and ALL_USERS folders - List signatures = signatureService.getAvailableSignatures(username); - model.addAttribute("currentPage", "sign"); - model.addAttribute("fonts", getFontNames()); - model.addAttribute("signatures", signatures); - return "sign"; - } - - @Deprecated - // @GetMapping("/multi-page-layout") - @Hidden - public String multiPageLayoutForm(Model model) { - model.addAttribute("currentPage", "multi-page-layout"); - return "multi-page-layout"; - } - - @Deprecated - // @GetMapping("/scale-pages") - @Hidden - public String scalePagesFrom(Model model) { - model.addAttribute("currentPage", "scale-pages"); - return "scale-pages"; - } - - @Deprecated - // @GetMapping("/split-by-size-or-count") - @Hidden - public String splitBySizeOrCount(Model model) { - model.addAttribute("currentPage", "split-by-size-or-count"); - return "split-by-size-or-count"; - } - - @Deprecated - // @GetMapping("/overlay-pdf") - @Hidden - public String overlayPdf(Model model) { - model.addAttribute("currentPage", "overlay-pdf"); - return "overlay-pdf"; - } - - private List getFontNames() { - List fontNames = new ArrayList<>(); - // Extract font names from classpath - fontNames.addAll(getFontNamesFromLocation("classpath:static/fonts/*.woff2")); - // Extract font names from external directory - fontNames.addAll( - getFontNamesFromLocation( - "file:" - + InstallationPathConfig.getStaticPath() - + "fonts" - + File.separator - + "*")); - return fontNames; - } - - private List getFontNamesFromLocation(String locationPattern) { - try { - Resource[] resources = - GeneralUtils.getResourcesFromLocationPattern(locationPattern, resourceLoader); - return Arrays.stream(resources) - .map( - resource -> { - try { - String filename = resource.getFilename(); - if (filename != null) { - int lastDotIndex = filename.lastIndexOf('.'); - if (lastDotIndex != -1) { - String name = filename.substring(0, lastDotIndex); - String extension = filename.substring(lastDotIndex + 1); - return new FontResource(name, extension); - } - } - return null; - } catch (Exception e) { - throw ExceptionUtils.createRuntimeException( - "error.fontLoadingFailed", - "Error processing font file", - e); - } - }) - .filter(Objects::nonNull) - .toList(); - } catch (Exception e) { - throw ExceptionUtils.createRuntimeException( - "error.fontDirectoryReadFailed", "Failed to read font directory", e); - } - } - - public String getFormatFromExtension(String extension) { - return switch (extension) { - case "ttf" -> "truetype"; - case "woff" -> "woff"; - case "woff2" -> "woff2"; - case "eot" -> "embedded-opentype"; - case "svg" -> "svg"; - default -> - // or throw an exception if an unexpected extension is encountered - ""; - }; - } - - @Deprecated - // @GetMapping("/crop") - @Hidden - public String cropForm(Model model) { - model.addAttribute("currentPage", "crop"); - return "crop"; - } - - @Deprecated - // @GetMapping("/auto-split-pdf") - @Hidden - public String autoSPlitPDFForm(Model model) { - model.addAttribute("currentPage", "auto-split-pdf"); - return "auto-split-pdf"; - } - - @Deprecated - // @GetMapping("/remove-image-pdf") - @Hidden - public String removeImagePdfForm(Model model) { - model.addAttribute("currentPage", "remove-image-pdf"); - return "remove-image-pdf"; - } - - @Setter - @Getter - public class FontResource { - - private String name; - - private String extension; - - private String type; - - public FontResource(String name, String extension) { - this.name = name; - this.extension = extension; - this.type = getFormatFromExtension(extension); - } - } -} diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/web/HomeWebController.java b/app/core/src/main/java/stirling/software/SPDF/controller/web/HomeWebController.java deleted file mode 100644 index 62f2f0d8a..000000000 --- a/app/core/src/main/java/stirling/software/SPDF/controller/web/HomeWebController.java +++ /dev/null @@ -1,98 +0,0 @@ -package stirling.software.SPDF.controller.web; - -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; -import java.util.List; -import java.util.Map; - -import org.springframework.core.io.ClassPathResource; -import org.springframework.core.io.Resource; -import org.springframework.http.MediaType; -import org.springframework.ui.Model; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.ResponseBody; - -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; - -import io.swagger.v3.oas.annotations.Hidden; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; - -import stirling.software.SPDF.model.Dependency; -import stirling.software.common.model.ApplicationProperties; - -@Slf4j -// @Controller // Disabled - Backend-only mode, no Thymeleaf UI -@RequiredArgsConstructor -public class HomeWebController { - - private final ApplicationProperties applicationProperties; - - @Deprecated - // @GetMapping("/about") - @Hidden - public String gameForm(Model model) { - model.addAttribute("currentPage", "about"); - return "about"; - } - - @Deprecated - // @GetMapping("/licenses") - @Hidden - public String licensesForm(Model model) { - model.addAttribute("currentPage", "licenses"); - Resource resource = new ClassPathResource("static/3rdPartyLicenses.json"); - try (InputStream is = resource.getInputStream()) { - String json = new String(is.readAllBytes(), StandardCharsets.UTF_8); - ObjectMapper mapper = new ObjectMapper(); - Map> data = mapper.readValue(json, new TypeReference<>() {}); - model.addAttribute("dependencies", data.get("dependencies")); - } catch (IOException e) { - log.error("exception", e); - } - return "licenses"; - } - - @Deprecated - // @GetMapping("/releases") - public String getReleaseNotes(Model model) { - return "releases"; - } - - @Deprecated - // @GetMapping("/") - public String home(Model model) { - model.addAttribute("currentPage", "home"); - String showSurvey = System.getenv("SHOW_SURVEY"); - boolean showSurveyValue = showSurvey == null || "true".equalsIgnoreCase(showSurvey); - model.addAttribute("showSurveyFromDocker", showSurveyValue); - return "home"; - } - - @Deprecated - // @GetMapping("/home") - public String root(Model model) { - return "redirect:/"; - } - - @Deprecated - // @GetMapping("/home-legacy") - public String redirectHomeLegacy() { - return "redirect:/"; - } - - @GetMapping(value = "/robots.txt", produces = MediaType.TEXT_PLAIN_VALUE) - @ResponseBody - @Hidden - public String getRobotsTxt() { - boolean allowGoogle = applicationProperties.getSystem().isGooglevisibility(); - if (allowGoogle) { - return "User-agent: Googlebot\nAllow: /\n\nUser-agent: *\nAllow: /"; - } else { - return "User-agent: Googlebot\nDisallow: /\n\nUser-agent: *\nDisallow: /"; - } - } -} diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/web/OtherWebController.java b/app/core/src/main/java/stirling/software/SPDF/controller/web/OtherWebController.java deleted file mode 100644 index e309fa6ba..000000000 --- a/app/core/src/main/java/stirling/software/SPDF/controller/web/OtherWebController.java +++ /dev/null @@ -1,231 +0,0 @@ -package stirling.software.SPDF.controller.web; - -import java.io.File; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -import org.springframework.ui.Model; -import org.springframework.web.servlet.ModelAndView; - -import io.swagger.v3.oas.annotations.Hidden; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; - -import stirling.software.common.configuration.RuntimePathConfig; -import stirling.software.common.model.ApplicationProperties; -import stirling.software.common.util.CheckProgramInstall; - -// @Controller // Disabled - Backend-only mode, no Thymeleaf UI -@RequiredArgsConstructor -@Slf4j -public class OtherWebController { - - private final ApplicationProperties applicationProperties; - private final RuntimePathConfig runtimePathConfig; - - @Deprecated - // @GetMapping("/compress-pdf") - @Hidden - public String compressPdfForm(Model model) { - model.addAttribute("currentPage", "compress-pdf"); - return "misc/compress-pdf"; - } - - @Deprecated - // @GetMapping("/replace-and-invert-color-pdf") - @Hidden - public String replaceAndInvertColorPdfForm(Model model) { - model.addAttribute("currentPage", "replace-invert-color-pdf"); - return "misc/replace-color"; - } - - @Deprecated - // @GetMapping("/extract-image-scans") - @Hidden - public ModelAndView extractImageScansForm() { - ModelAndView modelAndView = new ModelAndView("misc/extract-image-scans"); - boolean isPython = CheckProgramInstall.isPythonAvailable(); - modelAndView.addObject("isPython", isPython); - modelAndView.addObject("currentPage", "extract-image-scans"); - return modelAndView; - } - - @Deprecated - // @GetMapping("/show-javascript") - @Hidden - public String extractJavascriptForm(Model model) { - model.addAttribute("currentPage", "show-javascript"); - return "misc/show-javascript"; - } - - @Deprecated - // @GetMapping("/stamp") - @Hidden - public String stampForm(Model model) { - model.addAttribute("currentPage", "stamp"); - return "misc/stamp"; - } - - @Deprecated - // @GetMapping("/add-page-numbers") - @Hidden - public String addPageNumbersForm(Model model) { - model.addAttribute("currentPage", "add-page-numbers"); - return "misc/add-page-numbers"; - } - - @Deprecated - // @GetMapping("/scanner-effect") - @Hidden - public String scannerEffectForm(Model model) { - model.addAttribute("currentPage", "scanner-effect"); - return "misc/scanner-effect"; - } - - @Deprecated - // @GetMapping("/extract-images") - @Hidden - public String extractImagesForm(Model model) { - model.addAttribute("currentPage", "extract-images"); - return "misc/extract-images"; - } - - @Deprecated - // @GetMapping("/flatten") - @Hidden - public String flattenForm(Model model) { - model.addAttribute("currentPage", "flatten"); - return "misc/flatten"; - } - - @Deprecated - // @GetMapping("/change-metadata") - @Hidden - public String addWatermarkForm(Model model) { - model.addAttribute("currentPage", "change-metadata"); - return "misc/change-metadata"; - } - - @Deprecated - // @GetMapping("/unlock-pdf-forms") - @Hidden - public String unlockPDFForms(Model model) { - model.addAttribute("currentPage", "unlock-pdf-forms"); - return "misc/unlock-pdf-forms"; - } - - @Deprecated - // @GetMapping("/compare") - @Hidden - public String compareForm(Model model) { - model.addAttribute("currentPage", "compare"); - return "misc/compare"; - } - - @Deprecated - // @GetMapping("/print-file") - @Hidden - public String printFileForm(Model model) { - model.addAttribute("currentPage", "print-file"); - return "misc/print-file"; - } - - public List getAvailableTesseractLanguages() { - String tessdataDir = runtimePathConfig.getTessDataPath(); - File[] files = new File(tessdataDir).listFiles(); - if (files == null) { - return Collections.emptyList(); - } - return Arrays.stream(files) - .filter(file -> file.getName().endsWith(".traineddata")) - .map(file -> file.getName().replace(".traineddata", "")) - .filter(lang -> !"osd".equalsIgnoreCase(lang)) - .sorted() - .toList(); - } - - @Deprecated - // @GetMapping("/ocr-pdf") - @Hidden - public ModelAndView ocrPdfPage() { - ModelAndView modelAndView = new ModelAndView("misc/ocr-pdf"); - List languages = getAvailableTesseractLanguages(); - modelAndView.addObject("languages", languages); - modelAndView.addObject("currentPage", "ocr-pdf"); - return modelAndView; - } - - @Deprecated - // @GetMapping("/add-image") - @Hidden - public String overlayImage(Model model) { - model.addAttribute("currentPage", "add-image"); - return "misc/add-image"; - } - - @Deprecated - // @GetMapping("/adjust-contrast") - @Hidden - public String contrast(Model model) { - model.addAttribute("currentPage", "adjust-contrast"); - return "misc/adjust-contrast"; - } - - @Deprecated - // @GetMapping("/repair") - @Hidden - public String repairForm(Model model) { - model.addAttribute("currentPage", "repair"); - return "misc/repair"; - } - - @Deprecated - // @GetMapping("/remove-blanks") - @Hidden - public String removeBlanksForm(Model model) { - model.addAttribute("currentPage", "remove-blanks"); - return "misc/remove-blanks"; - } - - @Deprecated - // @GetMapping("/remove-annotations") - @Hidden - public String removeAnnotationsForm(Model model) { - model.addAttribute("currentPage", "remove-annotations"); - return "misc/remove-annotations"; - } - - @Deprecated - // @GetMapping("/auto-crop") - @Hidden - public String autoCropForm(Model model) { - model.addAttribute("currentPage", "auto-crop"); - return "misc/auto-crop"; - } - - @Deprecated - // @GetMapping("/auto-rename") - @Hidden - public String autoRenameForm(Model model) { - model.addAttribute("currentPage", "auto-rename"); - return "misc/auto-rename"; - } - - @Deprecated - // @GetMapping("/add-attachments") - @Hidden - public String attachmentsForm(Model model) { - model.addAttribute("currentPage", "add-attachments"); - return "misc/add-attachments"; - } - - @Deprecated - // @GetMapping("/extract-attachments") - @Hidden - public String extractAttachmentsForm(Model model) { - model.addAttribute("currentPage", "extract-attachments"); - return "misc/extract-attachments"; - } -} diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/web/SecurityWebController.java b/app/core/src/main/java/stirling/software/SPDF/controller/web/SecurityWebController.java deleted file mode 100644 index e2a34e140..000000000 --- a/app/core/src/main/java/stirling/software/SPDF/controller/web/SecurityWebController.java +++ /dev/null @@ -1,98 +0,0 @@ -package stirling.software.SPDF.controller.web; - -import org.springframework.ui.Model; - -import io.swagger.v3.oas.annotations.Hidden; -import io.swagger.v3.oas.annotations.tags.Tag; - -// @Controller // Disabled - Backend-only mode, no Thymeleaf UI -@Tag(name = "Security", description = "Security APIs") -public class SecurityWebController { - - @Deprecated - // @GetMapping("/auto-redact") - @Hidden - public String autoRedactForm(Model model) { - model.addAttribute("currentPage", "auto-redact"); - return "security/auto-redact"; - } - - @Deprecated - // @GetMapping("/redact") - public String redactForm(Model model) { - model.addAttribute("currentPage", "redact"); - return "security/redact"; - } - - @Deprecated - // @GetMapping("/add-password") - @Hidden - public String addPasswordForm(Model model) { - model.addAttribute("currentPage", "add-password"); - return "security/add-password"; - } - - @Deprecated - // @GetMapping("/change-permissions") - @Hidden - public String permissionsForm(Model model) { - model.addAttribute("currentPage", "change-permissions"); - return "security/change-permissions"; - } - - @Deprecated - // @GetMapping("/remove-password") - @Hidden - public String removePasswordForm(Model model) { - model.addAttribute("currentPage", "remove-password"); - return "security/remove-password"; - } - - @Deprecated - // @GetMapping("/add-watermark") - @Hidden - public String addWatermarkForm(Model model) { - model.addAttribute("currentPage", "add-watermark"); - return "security/add-watermark"; - } - - @Deprecated - // @GetMapping("/cert-sign") - @Hidden - public String certSignForm(Model model) { - model.addAttribute("currentPage", "cert-sign"); - return "security/cert-sign"; - } - - @Deprecated - // @GetMapping("/validate-signature") - @Hidden - public String certSignVerifyForm(Model model) { - model.addAttribute("currentPage", "validate-signature"); - return "security/validate-signature"; - } - - @Deprecated - // @GetMapping("/remove-cert-sign") - @Hidden - public String certUnSignForm(Model model) { - model.addAttribute("currentPage", "remove-cert-sign"); - return "security/remove-cert-sign"; - } - - @Deprecated - // @GetMapping("/sanitize-pdf") - @Hidden - public String sanitizeForm(Model model) { - model.addAttribute("currentPage", "sanitize-pdf"); - return "security/sanitize-pdf"; - } - - @Deprecated - // @GetMapping("/get-info-on-pdf") - @Hidden - public String getInfo(Model model) { - model.addAttribute("currentPage", "get-info-on-pdf"); - return "security/get-info-on-pdf"; - } -} diff --git a/app/core/src/main/resources/application.properties b/app/core/src/main/resources/application.properties index 738ea825b..3c13f6c96 100644 --- a/app/core/src/main/resources/application.properties +++ b/app/core/src/main/resources/application.properties @@ -33,7 +33,6 @@ server.servlet.context-path=${SYSTEM_ROOTURIPATH:/} spring.devtools.restart.enabled=true spring.devtools.livereload.enabled=true spring.devtools.restart.exclude=stirling.software.proprietary.security/** -# spring.thymeleaf.encoding=UTF-8 # Disabled - React frontend replaces Thymeleaf spring.web.resources.mime-mappings.webmanifest=application/manifest+json spring.mvc.async.request-timeout=${SYSTEM_CONNECTIONTIMEOUTMILLISECONDS:1200000} diff --git a/app/core/src/main/resources/templates/about.html b/app/core/src/main/resources/templates/about.html deleted file mode 100644 index 1ae7ede3c..000000000 --- a/app/core/src/main/resources/templates/about.html +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - -
-
- -

-
-
-
-
-
-
- -
- - \ No newline at end of file diff --git a/app/core/src/main/resources/templates/account.html b/app/core/src/main/resources/templates/account.html deleted file mode 100644 index db48bb3a5..000000000 --- a/app/core/src/main/resources/templates/account.html +++ /dev/null @@ -1,479 +0,0 @@ - - - - - - - - - -
-
- - -
-
-
-

- - settings_account_box - - User Settings -

-
- -
-
- Default message if not found -
- - - - -
-
-
- - admin_panel_settings - -
-

Administrator Tools

-

You have admin privileges. Access system settings and user management.

-
-
- - admin_panel_settings - Admin Settings - -
-
- - - -
Account Management
-
- - -
-
- - -
API Key
-
-
-
- - key - - API Key -
-
-
-
- - - - -
-
-
- - -
Sync browser settings with Account
-
-
-
- - sync - - Settings Comparison -
-
-
-
- - - - - - - - - - - -
PropertyAccount SettingWeb Browser Setting
-
- -
- - -
-
-
-
-
-
-
- - - - - - - - - - - - - - - - - - -
- - \ No newline at end of file diff --git a/app/core/src/main/resources/templates/adminSettings.html b/app/core/src/main/resources/templates/adminSettings.html deleted file mode 100644 index 1c4fca184..000000000 --- a/app/core/src/main/resources/templates/adminSettings.html +++ /dev/null @@ -1,458 +0,0 @@ - - - - - - - - - - -
-
- - -
-
-
-

- - manage_accounts - - Admin User Control Settings -

-
- -
- -
-
-
-
- - group - -
-
Total Users
-
- - -
-
-
- -
- - check_circle - -
-
Active Users
-
-
-
- -
- - person_off - -
-
Disabled Users
-
-
-
-
-
-
- - -
- Default message if not found -
- -
- Default message if not found -
- -
- Default message if not found -
- - -
User Management
-
- - - - group - Manage Teams - - - - - - analytics - Usage Statistics - - - - security - Audit Dashboard - -
- - -
- - - - - - - - - - - - - - - - - - - - - - - -
#UsernameTeamRolesAuthenticatedLast RequestActions
- - shield - Role - - -
-
- -
- - - edit - - -
- - - -
-
-
-
- -

-
-
-
-
- - - - - - - - - - - - -
- - \ No newline at end of file diff --git a/app/core/src/main/resources/templates/auto-split-pdf.html b/app/core/src/main/resources/templates/auto-split-pdf.html deleted file mode 100644 index b52aa0009..000000000 --- a/app/core/src/main/resources/templates/auto-split-pdf.html +++ /dev/null @@ -1,62 +0,0 @@ - - - - - - - - - - -
-
- -

-
-
-
-
- - - - -
- -
-

-
-
-
- - -
-

- -

-
- -

-
    -
  • -
  • -
  • -
  • -
-

-
- - -
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/app/core/src/main/resources/templates/change-creds.html b/app/core/src/main/resources/templates/change-creds.html deleted file mode 100644 index 0ba3ed7b2..000000000 --- a/app/core/src/main/resources/templates/change-creds.html +++ /dev/null @@ -1,88 +0,0 @@ - - - - - - - - -
-
- -

-
-
-
- - -

User Settings

-
- -
- Default message if not found -
-
- -

User!

- - -

Change password

-
-
- - -
-
- - -
-
- - -
-
- - -
-
- -
-
-
-
- -
- - \ No newline at end of file diff --git a/app/core/src/main/resources/templates/convert/cbr-to-pdf.html b/app/core/src/main/resources/templates/convert/cbr-to-pdf.html deleted file mode 100644 index b49c06f81..000000000 --- a/app/core/src/main/resources/templates/convert/cbr-to-pdf.html +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - - -
-
- -

-
-
-
-
- auto_stories - -
-
-
-
- -
- - -
- -
- -
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/app/core/src/main/resources/templates/convert/cbz-to-pdf.html b/app/core/src/main/resources/templates/convert/cbz-to-pdf.html deleted file mode 100644 index c7e547cdb..000000000 --- a/app/core/src/main/resources/templates/convert/cbz-to-pdf.html +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - - -
-
- -

-
-
-
-
- auto_stories - -
-
-
-
- -
- - -
- -
- -
-
-
-
-
- -
- - - diff --git a/app/core/src/main/resources/templates/convert/ebook-to-pdf.html b/app/core/src/main/resources/templates/convert/ebook-to-pdf.html deleted file mode 100644 index 047e5c02d..000000000 --- a/app/core/src/main/resources/templates/convert/ebook-to-pdf.html +++ /dev/null @@ -1,82 +0,0 @@ - - - - - - - - - -
-
- -

-
-
-
-
- menu_book - -
-

- -
- Calibre support is disabled. -
- -
-
-
- -
- - -
- -
- - -
- -
- - -
- -
- - -
- - -
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/app/core/src/main/resources/templates/convert/eml-to-pdf.html b/app/core/src/main/resources/templates/convert/eml-to-pdf.html deleted file mode 100644 index 37e412018..000000000 --- a/app/core/src/main/resources/templates/convert/eml-to-pdf.html +++ /dev/null @@ -1,97 +0,0 @@ - - - - - - - - - -
-
- -
-
-
-
-
- email - -
-

-
-
-
- -
- - -
-
- -
-
- - -
- -
- - -
-
- -
- -
-
-

-
    -
  • -
  • -
  • -
-
-
-
- -
-
- -
-
- -
-
-
-
- -
- - - - diff --git a/app/core/src/main/resources/templates/convert/file-to-pdf.html b/app/core/src/main/resources/templates/convert/file-to-pdf.html deleted file mode 100644 index 4fbc8ead5..000000000 --- a/app/core/src/main/resources/templates/convert/file-to-pdf.html +++ /dev/null @@ -1,60 +0,0 @@ - - - - - - - - - -
-
- -

-
-
-
-
- draft - -
-

-
-
-
- -
-

-

-

Microsoft Word: (DOC, DOCX, DOT, DOTX)

-

Microsoft Excel: (CSV, XLS, XLSX, XLT, XLTX, SLK, DIF)

-

Microsoft PowerPoint: (PPT, PPTX)

-

OpenDocument Formats: (ODT, OTT, ODS, OTS, ODP, OTP, ODG, OTG)

-

Plain Text: (TXT, TEXT, XML)

-

Rich Text Format: (RTF)

-

Images: (BMP, GIF, JPEG, PNG, TIF, PBM, PGM, PPM, RAS, XBM, XPM, SVG, SVM, WMF)

-

HTML: (HTML)

-

Lotus Word Pro: (LWP)

-

StarOffice: (SDA, SDC, SDD, SDW, STC, STD, STI, STW, SXD, SXG, SXI, SXW)

-

Other: (DBF, FODS, VSD, VOR, VOR3, VOR4, UOP, PCT, PS, PDF)

- https://help.libreoffice.org/latest/en-US/text/shared/guide/supported_formats.html -
-
-
- -
-
- -
-
-
-
- -
- - - \ No newline at end of file diff --git a/app/core/src/main/resources/templates/convert/html-to-pdf.html b/app/core/src/main/resources/templates/convert/html-to-pdf.html deleted file mode 100644 index 25f75b0f0..000000000 --- a/app/core/src/main/resources/templates/convert/html-to-pdf.html +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - - -
-
- -

-
-
-
-
- html - -
-
-
-
-
- - -
-
- -
-

-

-
-
-
-
- -
- - - \ No newline at end of file diff --git a/app/core/src/main/resources/templates/convert/img-to-pdf.html b/app/core/src/main/resources/templates/convert/img-to-pdf.html deleted file mode 100644 index c0638c55f..000000000 --- a/app/core/src/main/resources/templates/convert/img-to-pdf.html +++ /dev/null @@ -1,127 +0,0 @@ - - - - - - - - - -
-
- -

-
-
-
-
- picture_as_pdf - -
-
-
-
-
- - -
- -
- - -
-
- - -
- -
- - -
-
- - -
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/app/core/src/main/resources/templates/convert/markdown-to-pdf.html b/app/core/src/main/resources/templates/convert/markdown-to-pdf.html deleted file mode 100644 index f40a0781e..000000000 --- a/app/core/src/main/resources/templates/convert/markdown-to-pdf.html +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - - -
-
- -

-
-
-
-
- markdown - -
-
-
-
- -
-

-

-
-
-
-
- -
- - - \ No newline at end of file diff --git a/app/core/src/main/resources/templates/convert/pdf-to-cbr.html b/app/core/src/main/resources/templates/convert/pdf-to-cbr.html deleted file mode 100644 index 3594bd301..000000000 --- a/app/core/src/main/resources/templates/convert/pdf-to-cbr.html +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - -
-
- -

-
-
-
-
- auto_stories - -
-
-
-
- -
- - -
Higher DPI results in better quality but larger file size.
-
- -
- -
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/app/core/src/main/resources/templates/convert/pdf-to-cbz.html b/app/core/src/main/resources/templates/convert/pdf-to-cbz.html deleted file mode 100644 index fd1023a9c..000000000 --- a/app/core/src/main/resources/templates/convert/pdf-to-cbz.html +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - - -
-
- -

-
-
-
-
- auto_stories - -
-
-
-
-
- - -
-
- -
-
-
-
-
- -
- - - diff --git a/app/core/src/main/resources/templates/convert/pdf-to-csv.html b/app/core/src/main/resources/templates/convert/pdf-to-csv.html deleted file mode 100644 index 3272766a1..000000000 --- a/app/core/src/main/resources/templates/convert/pdf-to-csv.html +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - -
-
- -

-
-
-
-
- csv - -
-
- -
- -
- - -
-
- - -
- -
- - -
-
-
-
- -
- - \ No newline at end of file diff --git a/app/core/src/main/resources/templates/convert/pdf-to-epub.html b/app/core/src/main/resources/templates/convert/pdf-to-epub.html deleted file mode 100644 index b56593956..000000000 --- a/app/core/src/main/resources/templates/convert/pdf-to-epub.html +++ /dev/null @@ -1,87 +0,0 @@ - - - - - - - - - -
-
- -

-
-
-
-
- menu_book - -
-

- -
- Calibre support is disabled. -
- -
-
-
- -
- - -
- -
- - -
- -
- - -
- - -
-
-
-
-
- -
- - - diff --git a/app/core/src/main/resources/templates/convert/pdf-to-html.html b/app/core/src/main/resources/templates/convert/pdf-to-html.html deleted file mode 100644 index 833940c0f..000000000 --- a/app/core/src/main/resources/templates/convert/pdf-to-html.html +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - -
-
- -

-
-
-
-
- html - -
-
-
-
- -
-

-
-
-
-
- -
- - \ No newline at end of file diff --git a/app/core/src/main/resources/templates/convert/pdf-to-img.html b/app/core/src/main/resources/templates/convert/pdf-to-img.html deleted file mode 100644 index 531d9dad9..000000000 --- a/app/core/src/main/resources/templates/convert/pdf-to-img.html +++ /dev/null @@ -1,95 +0,0 @@ - - - - - - - - - - -
-
- -

-
-
-
-
- photo_library - -
-

-

Python is not installed. Required for WebP conversion. -

-
-
-
-
- - -
-
- - -
- -
- - -
-
- - -
-
- - -
-
- - -
- -
-
-
-
-
- -
- - - - \ No newline at end of file diff --git a/app/core/src/main/resources/templates/convert/pdf-to-markdown.html b/app/core/src/main/resources/templates/convert/pdf-to-markdown.html deleted file mode 100644 index 9c5a7caa2..000000000 --- a/app/core/src/main/resources/templates/convert/pdf-to-markdown.html +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - -
-
- -

-
-
-
-
- markdown_copy - -
-
-
- -
-
-
-
-
- -
- - \ No newline at end of file diff --git a/app/core/src/main/resources/templates/convert/pdf-to-pdfa.html b/app/core/src/main/resources/templates/convert/pdf-to-pdfa.html deleted file mode 100644 index 6035e7561..000000000 --- a/app/core/src/main/resources/templates/convert/pdf-to-pdfa.html +++ /dev/null @@ -1,86 +0,0 @@ - - - - - - - - -
-
- -

-
-
-
-
- picture_as_pdf - -
-

-
-
-
- - -
-
- -
- - -

-
-
-
-
- -
- - diff --git a/app/core/src/main/resources/templates/convert/pdf-to-presentation.html b/app/core/src/main/resources/templates/convert/pdf-to-presentation.html deleted file mode 100644 index 56f22db56..000000000 --- a/app/core/src/main/resources/templates/convert/pdf-to-presentation.html +++ /dev/null @@ -1,49 +0,0 @@ - - - - - - - - - - -
-
- -

-
-
-
-
- slideshow - -
-
-
-
-
- - -
-
- -
-

-
-
-
-
- -
- - - \ No newline at end of file diff --git a/app/core/src/main/resources/templates/convert/pdf-to-text.html b/app/core/src/main/resources/templates/convert/pdf-to-text.html deleted file mode 100644 index a98f06f0f..000000000 --- a/app/core/src/main/resources/templates/convert/pdf-to-text.html +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - -
-
- -

-
-
-
-
- text_fields - -
-
-
-
- - -
- -
-

-
-
-
-
- -
- - \ No newline at end of file diff --git a/app/core/src/main/resources/templates/convert/pdf-to-vector.html b/app/core/src/main/resources/templates/convert/pdf-to-vector.html deleted file mode 100644 index b4efe95e6..000000000 --- a/app/core/src/main/resources/templates/convert/pdf-to-vector.html +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - -
-
- -

-
-
-
-
- stroke_full - -
-
-
-
-
- - -
- -
-

-
-
-
-
- -
- - - \ No newline at end of file diff --git a/app/core/src/main/resources/templates/convert/pdf-to-video.html b/app/core/src/main/resources/templates/convert/pdf-to-video.html deleted file mode 100644 index e3307af70..000000000 --- a/app/core/src/main/resources/templates/convert/pdf-to-video.html +++ /dev/null @@ -1,84 +0,0 @@ - - - - - - - - - - -
-
- -

-
-
-
-
- movie - -
-

-
-
-
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
- -
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/app/core/src/main/resources/templates/convert/pdf-to-word.html b/app/core/src/main/resources/templates/convert/pdf-to-word.html deleted file mode 100644 index 0b500cbb6..000000000 --- a/app/core/src/main/resources/templates/convert/pdf-to-word.html +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - -
-
- -

-
-
-
-
- description - -
-
-
-
-
- - -
-
- -
-

-
-
-
-
- -
- - - \ No newline at end of file diff --git a/app/core/src/main/resources/templates/convert/pdf-to-xml.html b/app/core/src/main/resources/templates/convert/pdf-to-xml.html deleted file mode 100644 index 25358a681..000000000 --- a/app/core/src/main/resources/templates/convert/pdf-to-xml.html +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - -
-
- -

-
-
-
-
- code - -
-
-
-
- -
-

-
-
-
-
- -
- - \ No newline at end of file diff --git a/app/core/src/main/resources/templates/convert/url-to-pdf.html b/app/core/src/main/resources/templates/convert/url-to-pdf.html deleted file mode 100644 index 231ad8c3c..000000000 --- a/app/core/src/main/resources/templates/convert/url-to-pdf.html +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - -
-
- -

-
-
-
-
- link - -
-

-
- -
- -
-

-
-
-
-
- -
- - - \ No newline at end of file diff --git a/app/core/src/main/resources/templates/convert/vector-to-pdf.html b/app/core/src/main/resources/templates/convert/vector-to-pdf.html deleted file mode 100644 index d7c112fa9..000000000 --- a/app/core/src/main/resources/templates/convert/vector-to-pdf.html +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - - -
-
- -

-
-
-
-
- picture_as_pdf - -
-
-
-
-
- - -
- -
-

-
-
-
-
- -
- - - \ No newline at end of file diff --git a/app/core/src/main/resources/templates/crop.html b/app/core/src/main/resources/templates/crop.html deleted file mode 100644 index 6487b296d..000000000 --- a/app/core/src/main/resources/templates/crop.html +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - - -
-
- -

-
-
-
-
- crop - -
-
-
- - - - - -
- - - -
- - -
-
- - -
- -
-
- - -
-
- -
- - diff --git a/app/core/src/main/resources/templates/database.html b/app/core/src/main/resources/templates/database.html deleted file mode 100644 index 753a3a352..000000000 --- a/app/core/src/main/resources/templates/database.html +++ /dev/null @@ -1,73 +0,0 @@ - - - - - - - -
-
- -

-
-
-
-
- database - Database Im-/Export -
-

-

-
- - - - - - - - - - - - - - - - - - - - - -
File NameCreation DateFile Size
deletebackupdownload
-
DB-Version
- Create Backup File -
-
-
-
-

-

-
-
-
- -
-
- -
-
-
-
-
- - - -
- - \ No newline at end of file diff --git a/app/core/src/main/resources/templates/edit-table-of-contents.html b/app/core/src/main/resources/templates/edit-table-of-contents.html deleted file mode 100644 index 503ab99f4..000000000 --- a/app/core/src/main/resources/templates/edit-table-of-contents.html +++ /dev/null @@ -1,175 +0,0 @@ - - - - - - - - - - -
-
- -

-
-
-
-
- bookmark_add - -
-
-
-
- -
- - - -
- -
-
-

- -
- -
- -
- -
- -
- - -
- -
- - - -
- - -
- - - -
-
-
- - - -
- -

- -

-
-

-

-

-
- -
- -
- - - -
-
-
-
- -
- - - - - - \ No newline at end of file diff --git a/app/core/src/main/resources/templates/error.html b/app/core/src/main/resources/templates/error.html deleted file mode 100644 index 00f22c27b..000000000 --- a/app/core/src/main/resources/templates/error.html +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - -
-
- -
-
-
-

-

-

-
-

-

-
- - -
- -
-
-
- -
- - \ No newline at end of file diff --git a/app/core/src/main/resources/templates/extract-page.html b/app/core/src/main/resources/templates/extract-page.html deleted file mode 100644 index c01d2c7eb..000000000 --- a/app/core/src/main/resources/templates/extract-page.html +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - - -
-
- -

- -
-
-
-
- upload - -
-
-
-
- -
- - -
- - -
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/app/core/src/main/resources/templates/fragments/card.html b/app/core/src/main/resources/templates/fragments/card.html deleted file mode 100644 index b2ab67eec..000000000 --- a/app/core/src/main/resources/templates/fragments/card.html +++ /dev/null @@ -1,21 +0,0 @@ - diff --git a/app/core/src/main/resources/templates/fragments/common.html b/app/core/src/main/resources/templates/fragments/common.html deleted file mode 100644 index ffb3e01de..000000000 --- a/app/core/src/main/resources/templates/fragments/common.html +++ /dev/null @@ -1,496 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
Lives: 3
-
Score: 0
-
High Score: 0
-
Level: 1
- favicon -
- -
-
- - - - - - -
-
- -
-
- - - -
-
-
-
-
- Maximum file size: - -
-
- - -
diff --git a/app/core/src/main/resources/templates/fragments/errorBanner.html b/app/core/src/main/resources/templates/fragments/errorBanner.html deleted file mode 100644 index bb65a9428..000000000 --- a/app/core/src/main/resources/templates/fragments/errorBanner.html +++ /dev/null @@ -1,258 +0,0 @@ - - -
- - -
\ No newline at end of file diff --git a/app/core/src/main/resources/templates/fragments/errorBannerPerPage.html b/app/core/src/main/resources/templates/fragments/errorBannerPerPage.html deleted file mode 100644 index db3d1586b..000000000 --- a/app/core/src/main/resources/templates/fragments/errorBannerPerPage.html +++ /dev/null @@ -1,53 +0,0 @@ - - - - - - diff --git a/app/core/src/main/resources/templates/fragments/featureGroupHeader.html b/app/core/src/main/resources/templates/fragments/featureGroupHeader.html deleted file mode 100644 index 7cdcb8339..000000000 --- a/app/core/src/main/resources/templates/fragments/featureGroupHeader.html +++ /dev/null @@ -1,3 +0,0 @@ -
- -
\ No newline at end of file diff --git a/app/core/src/main/resources/templates/fragments/featureGroupHeaderLegacy.html b/app/core/src/main/resources/templates/fragments/featureGroupHeaderLegacy.html deleted file mode 100644 index 0a8f7e9b1..000000000 --- a/app/core/src/main/resources/templates/fragments/featureGroupHeaderLegacy.html +++ /dev/null @@ -1,6 +0,0 @@ -
- - - chevron_right - -
\ No newline at end of file diff --git a/app/core/src/main/resources/templates/fragments/footer.html b/app/core/src/main/resources/templates/fragments/footer.html deleted file mode 100644 index 89c3c78b1..000000000 --- a/app/core/src/main/resources/templates/fragments/footer.html +++ /dev/null @@ -1,26 +0,0 @@ - \ No newline at end of file diff --git a/app/core/src/main/resources/templates/fragments/languageEntry.html b/app/core/src/main/resources/templates/fragments/languageEntry.html deleted file mode 100644 index 18a976ba0..000000000 --- a/app/core/src/main/resources/templates/fragments/languageEntry.html +++ /dev/null @@ -1,7 +0,0 @@ - -
- - -
-
diff --git a/app/core/src/main/resources/templates/fragments/languages.html b/app/core/src/main/resources/templates/fragments/languages.html deleted file mode 100644 index 78914ad65..000000000 --- a/app/core/src/main/resources/templates/fragments/languages.html +++ /dev/null @@ -1,44 +0,0 @@ - -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
\ No newline at end of file diff --git a/app/core/src/main/resources/templates/fragments/multi-toolAdvert.html b/app/core/src/main/resources/templates/fragments/multi-toolAdvert.html deleted file mode 100644 index 5b624bd2c..000000000 --- a/app/core/src/main/resources/templates/fragments/multi-toolAdvert.html +++ /dev/null @@ -1,65 +0,0 @@ -
-
-
- - -
-
- - -
diff --git a/app/core/src/main/resources/templates/fragments/navElements.html b/app/core/src/main/resources/templates/fragments/navElements.html deleted file mode 100644 index 84acb22dd..000000000 --- a/app/core/src/main/resources/templates/fragments/navElements.html +++ /dev/null @@ -1,342 +0,0 @@ - -
-
-
- -
- -
-
-
- -
-
-
-
- -
-
-
-
- -
-
- -
-
-
-
- -
- -
-
-
- -
-
-
-
- -
-
\ No newline at end of file diff --git a/app/core/src/main/resources/templates/fragments/navbar.html b/app/core/src/main/resources/templates/fragments/navbar.html deleted file mode 100644 index 2278ed79e..000000000 --- a/app/core/src/main/resources/templates/fragments/navbar.html +++ /dev/null @@ -1,325 +0,0 @@ -
- - - - - - - -
- - - - - - -
\ No newline at end of file diff --git a/app/core/src/main/resources/templates/fragments/navbarEntry.html b/app/core/src/main/resources/templates/fragments/navbarEntry.html deleted file mode 100644 index 42f696bfb..000000000 --- a/app/core/src/main/resources/templates/fragments/navbarEntry.html +++ /dev/null @@ -1,18 +0,0 @@ - - -
- - -
- -
- -
diff --git a/app/core/src/main/resources/templates/fragments/navbarEntryCustom.html b/app/core/src/main/resources/templates/fragments/navbarEntryCustom.html deleted file mode 100644 index c3b2ad848..000000000 --- a/app/core/src/main/resources/templates/fragments/navbarEntryCustom.html +++ /dev/null @@ -1,21 +0,0 @@ - - -
- - - - -
- -
- -
diff --git a/app/core/src/main/resources/templates/home-legacy.html b/app/core/src/main/resources/templates/home-legacy.html deleted file mode 100644 index 33441fe95..000000000 --- a/app/core/src/main/resources/templates/home-legacy.html +++ /dev/null @@ -1,534 +0,0 @@ - - - - - - - - -
-
- - -
-
-

-

-

-
-
-
- - -
-
- - search - - - -
- - star - - - expand_all - - - collapse_all - - -
- -
- - - -
-
-
-
-
-
- - -
-
-
-
-
-
- -
-
-
-
-
-
-
-
- - - - -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
- - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/core/src/main/resources/templates/home.html b/app/core/src/main/resources/templates/home.html deleted file mode 100644 index 9773d54ab..000000000 --- a/app/core/src/main/resources/templates/home.html +++ /dev/null @@ -1,293 +0,0 @@ - - - - - - - - -
- -
-
- - -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
- -
- -
-
- - -
-
-
- - star - -
-
- - visibility - -
- - - update - - -
- -
-
-
-
-
-
-
-
-
-
-
-
- -
- -
- -
-
- -
- -
-
- - - - - - - - - - - - - - - - - diff --git a/app/core/src/main/resources/templates/licenses.html b/app/core/src/main/resources/templates/licenses.html deleted file mode 100644 index 5aec42a45..000000000 --- a/app/core/src/main/resources/templates/licenses.html +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - -
-
- -

-
-
-
-
- license - 3rd Party licenses -
- - - - - - - - - - - - - - - -
ModuleVersionLicense
-
-
-
-
- -
- - \ No newline at end of file diff --git a/app/core/src/main/resources/templates/login.html b/app/core/src/main/resources/templates/login.html deleted file mode 100644 index 39c5e31a0..000000000 --- a/app/core/src/main/resources/templates/login.html +++ /dev/null @@ -1,178 +0,0 @@ - - - - - - - - - - -
-
-
- -
- favicon - -

Stirling-PDF

-
- Login Via SSO -
-
-
-
-
-
OAuth2: Error Message
-
- -
-
OAuth2: Error Message
-
-
-
-
- Default message if not found -
-
-
-
-

Please sign in

-
- - -
-
- - -
- -
- - -
- -
-
- -
-
- -
- - - \ No newline at end of file diff --git a/app/core/src/main/resources/templates/merge-pdfs.html b/app/core/src/main/resources/templates/merge-pdfs.html deleted file mode 100644 index 8878c3c25..000000000 --- a/app/core/src/main/resources/templates/merge-pdfs.html +++ /dev/null @@ -1,72 +0,0 @@ - - - - - - - - - -
-
- -

- -
-
-
-
- add_to_photos - -
-
- -
- -
-
-
-
- - -
-
- - -
-
-
    -
    -
    - - - -
    -
    - -
    -
    - - - - - - -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/app/core/src/main/resources/templates/misc/add-attachments.html b/app/core/src/main/resources/templates/misc/add-attachments.html deleted file mode 100644 index 60bb16c96..000000000 --- a/app/core/src/main/resources/templates/misc/add-attachments.html +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - -
    -
    - -

    -
    -
    -
    -
    - attachment - -
    - -
    - -
    -
    - - -
    -
    - - - -
    -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/app/core/src/main/resources/templates/misc/add-image.html b/app/core/src/main/resources/templates/misc/add-image.html deleted file mode 100644 index ec75b8227..000000000 --- a/app/core/src/main/resources/templates/misc/add-image.html +++ /dev/null @@ -1,116 +0,0 @@ - - - - - - - - - - -
    -
    - -

    -
    -
    -
    -
    - add_photo_alternate - -
    - - -
    -
    - - -
    -
    -
    -
    - -
    - - - - - - - - - -
    - -
    - - -
    -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/app/core/src/main/resources/templates/misc/add-page-numbers.html b/app/core/src/main/resources/templates/misc/add-page-numbers.html deleted file mode 100644 index c85d0404e..000000000 --- a/app/core/src/main/resources/templates/misc/add-page-numbers.html +++ /dev/null @@ -1,170 +0,0 @@ - - - - - - - - - -
    -
    - -

    -
    -
    -
    -
    - 123 - -
    -
    -
    -
    -
    - - -
    -
    - -
    -
    1
    -
    2
    -
    3
    -
    4
    -
    5
    -
    6
    -
    7
    -
    8
    -
    9
    -
    -
    - -
    - - -
    -
    - - -
    -
    - -
    - -
    - -
    -
    - - -
    -
    - - -
    -
    - - -
    - -
    -
    -
    -
    - -
    - -
    - - diff --git a/app/core/src/main/resources/templates/misc/adjust-contrast.html b/app/core/src/main/resources/templates/misc/adjust-contrast.html deleted file mode 100644 index 571dd66b4..000000000 --- a/app/core/src/main/resources/templates/misc/adjust-contrast.html +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - - - -
    -
    - -

    -
    -
    -
    -
    -
    - -
    - -
    -
    -
    - palette - -
    -
    -
    -
    -
    - -
    - -
    -
    - -
    -
    - - -
    -
    -
    -
    - -
    - - \ No newline at end of file diff --git a/app/core/src/main/resources/templates/misc/auto-crop.html b/app/core/src/main/resources/templates/misc/auto-crop.html deleted file mode 100644 index 99b3c3485..000000000 --- a/app/core/src/main/resources/templates/misc/auto-crop.html +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - -
    -
    - -

    -
    -
    -
    -
    - crop - -
    -
    -
    -
    - -
    -

    -
    -
    -
    -
    - -
    - - \ No newline at end of file diff --git a/app/core/src/main/resources/templates/misc/auto-rename.html b/app/core/src/main/resources/templates/misc/auto-rename.html deleted file mode 100644 index 884b68985..000000000 --- a/app/core/src/main/resources/templates/misc/auto-rename.html +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - -
    -
    - -

    -
    -
    -
    -
    - - - - - - -
    -
    -
    -
    - -
    -
    -
    -
    -
    - -
    - - \ No newline at end of file diff --git a/app/core/src/main/resources/templates/misc/change-metadata.html b/app/core/src/main/resources/templates/misc/change-metadata.html deleted file mode 100644 index 265ea298d..000000000 --- a/app/core/src/main/resources/templates/misc/change-metadata.html +++ /dev/null @@ -1,98 +0,0 @@ - - - - - - - -
    -
    - -

    -
    -
    -
    -
    - assignment - -
    -
    -
    -

    -
    - - -
    -
    - - -
    -
    - - -
    - -
    - - -
    - -
    - - -
    - -
    - - -
    - -
    - - -
    - -
    - - -
    - -
    - - -
    - -
    - - -
    - -
    - - -
    - -
    - -
    -
    - - - -
    -
    -
    -
    -
    - -
    - - \ No newline at end of file diff --git a/app/core/src/main/resources/templates/misc/compare.html b/app/core/src/main/resources/templates/misc/compare.html deleted file mode 100644 index f7bab9589..000000000 --- a/app/core/src/main/resources/templates/misc/compare.html +++ /dev/null @@ -1,336 +0,0 @@ - - - - - - - - - -
    -
    - -

    -
    -
    -
    -
    - compare - -
    -
    -
    -
    -
    - -
    -
    -
    - - -
    -
    - - -
    -
    -
    - - - -
    -
    -

    -
    -
    -
    -

    -
    -
    -
    - - -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/app/core/src/main/resources/templates/misc/compress-pdf.html b/app/core/src/main/resources/templates/misc/compress-pdf.html deleted file mode 100644 index b593eb8f3..000000000 --- a/app/core/src/main/resources/templates/misc/compress-pdf.html +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - - - - -
    -
    - -

    -
    -
    -
    -
    - zoom_in_map - -
    -
    -
    -
    -
    -
    -

    -

    - - - -
    - - -
    -
    - - -
    -
    -
    -
    -
    -

    - - -
    -
    - -
    -
    -
    -
    -
    - -
    - - - diff --git a/app/core/src/main/resources/templates/misc/extract-attachments.html b/app/core/src/main/resources/templates/misc/extract-attachments.html deleted file mode 100644 index b1b12ca05..000000000 --- a/app/core/src/main/resources/templates/misc/extract-attachments.html +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - - - - -
    -
    - -

    -
    -
    -
    -
    - folder_zip - -
    - -

    - -
    - -
    -
    - - - -
    -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/app/core/src/main/resources/templates/misc/extract-image-scans.html b/app/core/src/main/resources/templates/misc/extract-image-scans.html deleted file mode 100644 index 9d9670cc6..000000000 --- a/app/core/src/main/resources/templates/misc/extract-image-scans.html +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - - - -
    -
    - -

    -
    -
    -
    -
    - scanner - -
    - -

    Python is not installed. It is required to run.

    - -
    -
    -
    - - - -
    -
    - - - -
    -
    - - - -
    -
    - - - -
    -
    - - - -
    - -
    -
    -
    -
    -
    - -
    - - \ No newline at end of file diff --git a/app/core/src/main/resources/templates/misc/extract-images.html b/app/core/src/main/resources/templates/misc/extract-images.html deleted file mode 100644 index afcc2a789..000000000 --- a/app/core/src/main/resources/templates/misc/extract-images.html +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - -
    -
    - -

    -
    -
    -
    -
    - photo_library - -
    -
    -
    -
    - - -
    -
    - - -
    - -
    -
    -
    -
    -
    - -
    - - \ No newline at end of file diff --git a/app/core/src/main/resources/templates/misc/flatten.html b/app/core/src/main/resources/templates/misc/flatten.html deleted file mode 100644 index 3e31d1cbf..000000000 --- a/app/core/src/main/resources/templates/misc/flatten.html +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - -
    -
    - -

    -
    -
    - -
    -
    - layers_clear - -
    -
    -
    -
    -
    -
    - - -
    -
    - - - -
    -
    - -
    -
    -
    -
    -
    - -
    - - diff --git a/app/core/src/main/resources/templates/misc/ocr-pdf.html b/app/core/src/main/resources/templates/misc/ocr-pdf.html deleted file mode 100644 index 6fa85b1fc..000000000 --- a/app/core/src/main/resources/templates/misc/ocr-pdf.html +++ /dev/null @@ -1,240 +0,0 @@ - - - - - - - - - - -
    -
    - -

    -
    -
    -
    -
    - quick_reference_all - -
    -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    -
    -
    - - -
    -
    - -
    - - -
    -
    -
    - -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    -
    - -
    - -

    - https://docs.stirlingpdf.com/Advanced%20Configuration/OCR -
    -
    -
    -
    - -
    - - \ No newline at end of file diff --git a/app/core/src/main/resources/templates/misc/print-file.html b/app/core/src/main/resources/templates/misc/print-file.html deleted file mode 100644 index c898482dc..000000000 --- a/app/core/src/main/resources/templates/misc/print-file.html +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - -
    -
    - -

    -
    -
    -
    -

    -
    -
    -
    -
    -

    Select Printer

    - - -
    -
    - -
    -
    -
    -
    -
    - -
    - - \ No newline at end of file diff --git a/app/core/src/main/resources/templates/misc/remove-annotations.html b/app/core/src/main/resources/templates/misc/remove-annotations.html deleted file mode 100644 index d1c1f1d00..000000000 --- a/app/core/src/main/resources/templates/misc/remove-annotations.html +++ /dev/null @@ -1,68 +0,0 @@ - - - - - - - -
    -
    - -

    -
    -
    -
    -
    - thread_unread - -
    -
    -
    -
    -
    - -
    -
    -
    -
    -
    - -
    - - - - \ No newline at end of file diff --git a/app/core/src/main/resources/templates/misc/remove-blanks.html b/app/core/src/main/resources/templates/misc/remove-blanks.html deleted file mode 100644 index 433a6ebd3..000000000 --- a/app/core/src/main/resources/templates/misc/remove-blanks.html +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - -
    -
    - -

    -
    -
    -
    -
    - scan_delete - -
    -
    -
    -
    - - - -
    -
    - - - -
    - -
    -
    -
    -
    -
    - -
    - - \ No newline at end of file diff --git a/app/core/src/main/resources/templates/misc/repair.html b/app/core/src/main/resources/templates/misc/repair.html deleted file mode 100644 index eb84a932f..000000000 --- a/app/core/src/main/resources/templates/misc/repair.html +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - -
    -
    - -

    -
    -
    -
    -
    - build - -
    -
    -
    - -
    -
    -
    -
    -
    - -
    - - \ No newline at end of file diff --git a/app/core/src/main/resources/templates/misc/replace-color.html b/app/core/src/main/resources/templates/misc/replace-color.html deleted file mode 100644 index 18b8c9f0c..000000000 --- a/app/core/src/main/resources/templates/misc/replace-color.html +++ /dev/null @@ -1,105 +0,0 @@ - - - - - - - - - -
    -
    - -

    -
    -
    -
    -
    - zoom_in_map - -
    -
    -
    -
    -
    -
    -

    - -
    -
    - - - - - - - -
    -
    -
    -
    -
    - -
    - - - - \ No newline at end of file diff --git a/app/core/src/main/resources/templates/misc/scanner-effect.html b/app/core/src/main/resources/templates/misc/scanner-effect.html deleted file mode 100644 index b2900816f..000000000 --- a/app/core/src/main/resources/templates/misc/scanner-effect.html +++ /dev/null @@ -1,162 +0,0 @@ - - - - - - - - -
    -
    - -

    -
    -
    -
    -
    - scanner - -
    - -
    - -
    -
    -
    - - -
    -
    - - -
    -
    - - -
    - -
    - -
    -
    -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/app/core/src/main/resources/templates/misc/show-javascript.html b/app/core/src/main/resources/templates/misc/show-javascript.html deleted file mode 100644 index e875f5edb..000000000 --- a/app/core/src/main/resources/templates/misc/show-javascript.html +++ /dev/null @@ -1,96 +0,0 @@ - - - - - - - - - - -
    -
    - -

    -
    -
    -
    -
    - javascript - -
    -
    -
    -
    - -
    -
    - -
    - -
    - - - -
    - -
    -
    -
    -
    - -
    - - \ No newline at end of file diff --git a/app/core/src/main/resources/templates/misc/stamp.html b/app/core/src/main/resources/templates/misc/stamp.html deleted file mode 100644 index f3745918a..000000000 --- a/app/core/src/main/resources/templates/misc/stamp.html +++ /dev/null @@ -1,203 +0,0 @@ - - - - - - - - - -
    -
    - -

    -
    -
    -
    -
    - approval - -
    -
    -
    -
    -
    - - -
    - -
    - - -
    - -
    - -
    -
    1
    -
    2
    -
    3
    -
    4
    -
    5
    -
    6
    -
    7
    -
    8
    -
    9
    -
    -
    - - - -
    - - -
    - -
    - - -
    - - - -
    - - -
    - -
    - - -
    - -
    - - -
    - -
    - - -
    - - -
    - - -
    - -
    - - -
    - -
    - -
    - -
    - -
    - - -
    -
    -
    -
    - -
    - -
    - - \ No newline at end of file diff --git a/app/core/src/main/resources/templates/misc/unlock-pdf-forms.html b/app/core/src/main/resources/templates/misc/unlock-pdf-forms.html deleted file mode 100644 index 11fc43515..000000000 --- a/app/core/src/main/resources/templates/misc/unlock-pdf-forms.html +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - -
    -
    - -

    -
    -
    - -
    -
    - preview_off - -
    -
    -
    -
    -
    -
    - -
    -
    -
    -
    -
    - -
    - - diff --git a/app/core/src/main/resources/templates/multi-page-layout.html b/app/core/src/main/resources/templates/multi-page-layout.html deleted file mode 100644 index 0a8f5ebb3..000000000 --- a/app/core/src/main/resources/templates/multi-page-layout.html +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - -
    -
    - -

    -
    -
    -
    -
    - dashboard - -
    -
    -
    -
    - - -
    -
    - - -
    - -
    -
    -
    -
    -
    - -
    - - \ No newline at end of file diff --git a/app/core/src/main/resources/templates/multi-tool.html b/app/core/src/main/resources/templates/multi-tool.html deleted file mode 100644 index a1160d43f..000000000 --- a/app/core/src/main/resources/templates/multi-tool.html +++ /dev/null @@ -1,231 +0,0 @@ - - - - - - - - -
    -
    -
    - -

    -
    -
    - -
    -
    -
    - construction - -
    -
    -
    - -
    -
    -
    -
    -
    - - -
    - -
    -
    -
    -
    -
    -
    - -
    - - - - - - - - - - - - - -
    -
    -
    -
    -
    -
    -
    - -
    - - - - - - \ No newline at end of file diff --git a/app/core/src/main/resources/templates/overlay-pdf.html b/app/core/src/main/resources/templates/overlay-pdf.html deleted file mode 100644 index e92e4cb08..000000000 --- a/app/core/src/main/resources/templates/overlay-pdf.html +++ /dev/null @@ -1,102 +0,0 @@ - - - - - - - - -
    -
    - -

    -
    -
    -
    -
    - layers - -
    -
    -
    -
    - - - -
    - - - - - -
    - -
    -
    -
    -
    - -
    - - \ No newline at end of file diff --git a/app/core/src/main/resources/templates/pdf-organizer.html b/app/core/src/main/resources/templates/pdf-organizer.html deleted file mode 100644 index 8e2937eea..000000000 --- a/app/core/src/main/resources/templates/pdf-organizer.html +++ /dev/null @@ -1,77 +0,0 @@ - - - - - - - - - -
    -
    - -

    - -
    -
    -
    -
    - format_list_bulleted - -
    - -
    -
    -
    -
    - - -
    -
    - - -
    - -
    - -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/app/core/src/main/resources/templates/pdf-to-single-page.html b/app/core/src/main/resources/templates/pdf-to-single-page.html deleted file mode 100644 index 58b51a140..000000000 --- a/app/core/src/main/resources/templates/pdf-to-single-page.html +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - -
    -
    - -

    -
    -
    -
    -
    - looks_one - -
    -
    -
    - -
    -
    -
    -
    -
    - -
    - - \ No newline at end of file diff --git a/app/core/src/main/resources/templates/pipeline.html b/app/core/src/main/resources/templates/pipeline.html deleted file mode 100644 index c1bbe88de..000000000 --- a/app/core/src/main/resources/templates/pipeline.html +++ /dev/null @@ -1,203 +0,0 @@ - - - - - - - - - -
    -
    - -

    -
    -
    -
    -
    - family_history - -
    -
    - - -
    - -
    -
    - -
    -
    -
    -
    -
    - -
    -
    - Pipeline Help -
    - Folder Scanning Help -
    -
    - - - -
    -
    -
    - -
    - - diff --git a/app/core/src/main/resources/templates/releases.html b/app/core/src/main/resources/templates/releases.html deleted file mode 100644 index c8e6373da..000000000 --- a/app/core/src/main/resources/templates/releases.html +++ /dev/null @@ -1,511 +0,0 @@ - - - - - - - -
    -
    - -

    -
    -
    -
    -
    - update - Release Notes -
    - - - - - -
    -
    - Loading... -
    -
    - - - - -
    - -
    -
    -
    -
    -
    - -
    - - - - - - diff --git a/app/core/src/main/resources/templates/remove-image-pdf.html b/app/core/src/main/resources/templates/remove-image-pdf.html deleted file mode 100644 index 5b64081f1..000000000 --- a/app/core/src/main/resources/templates/remove-image-pdf.html +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - - -
    -
    - -

    -
    -
    -
    -
    - remove_selection - -
    -
    -
    - -
    - -
    -
    -
    -
    -
    - -
    - - diff --git a/app/core/src/main/resources/templates/remove-pages.html b/app/core/src/main/resources/templates/remove-pages.html deleted file mode 100644 index ea728d969..000000000 --- a/app/core/src/main/resources/templates/remove-pages.html +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - - - - -
    -
    - -

    - -
    -
    -
    -
    - delete - -
    - -
    -
    -
    -
    - - -
    - -
    -
    -
    -
    -
    - -
    - - - - \ No newline at end of file diff --git a/app/core/src/main/resources/templates/rotate-pdf.html b/app/core/src/main/resources/templates/rotate-pdf.html deleted file mode 100644 index 6baa24268..000000000 --- a/app/core/src/main/resources/templates/rotate-pdf.html +++ /dev/null @@ -1,133 +0,0 @@ - - - - - - - - -
    -
    - -

    - -
    -
    -
    -
    - rotate_right - -
    - -
    -
    -
    - - - -
    -
    -
    -
    -
    - -
    - - - - - \ No newline at end of file diff --git a/app/core/src/main/resources/templates/scale-pages.html b/app/core/src/main/resources/templates/scale-pages.html deleted file mode 100644 index e6f190323..000000000 --- a/app/core/src/main/resources/templates/scale-pages.html +++ /dev/null @@ -1,49 +0,0 @@ - - - - - - - -
    -
    - -

    -
    -
    -
    -
    - fullscreen - -
    -
    -
    -
    - - -
    -
    - - -
    - -
    -
    -
    -
    -
    - -
    - - \ No newline at end of file diff --git a/app/core/src/main/resources/templates/security/add-password.html b/app/core/src/main/resources/templates/security/add-password.html deleted file mode 100644 index 9153b1a13..000000000 --- a/app/core/src/main/resources/templates/security/add-password.html +++ /dev/null @@ -1,88 +0,0 @@ - - - - - - - -
    -
    - -

    -
    -
    -
    -
    - lock - -
    -
    -
    - -
    -
    -
    - - -
    -
    - - -
    - -
    - - -
    -
    - -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    -
    -
    - -
    -
    -
    -
    -
    -
    - -
    - - \ No newline at end of file diff --git a/app/core/src/main/resources/templates/security/add-watermark.html b/app/core/src/main/resources/templates/security/add-watermark.html deleted file mode 100644 index 99cfc62fd..000000000 --- a/app/core/src/main/resources/templates/security/add-watermark.html +++ /dev/null @@ -1,176 +0,0 @@ - - - - - - - -
    -
    - -

    -
    -
    -
    -
    - water_drop - -
    - -
    -
    - -
    -
    - -
    - - -
    -
    - - -
    -
    - - -
    - - - -
    - - -
    -
    - - - -
    - - -
    - - -
    -
    - - -
    -
    - - -
    -
    - -
    - -
    - -
    - - -
    - - -
    -
    - -
    -
    - - -
    -
    -
    -
    - -
    - - diff --git a/app/core/src/main/resources/templates/security/auto-redact.html b/app/core/src/main/resources/templates/security/auto-redact.html deleted file mode 100644 index 56ac35a0e..000000000 --- a/app/core/src/main/resources/templates/security/auto-redact.html +++ /dev/null @@ -1,96 +0,0 @@ - - - - - - - - -
    -
    - -

    -
    -
    -
    -
    - - - - -
    -
    -
    - -
    - -
    - - -
    - -
    - - -
    - - - - - -
    - - -
    - -
    - - -
    - -
    - - -
    - -
    - - -
    - - -
    -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/app/core/src/main/resources/templates/security/cert-sign.html b/app/core/src/main/resources/templates/security/cert-sign.html deleted file mode 100644 index 5c6f1ebc3..000000000 --- a/app/core/src/main/resources/templates/security/cert-sign.html +++ /dev/null @@ -1,118 +0,0 @@ - - - - - - - -
    -
    - -

    -
    -
    -
    -
    - workspace_premium - -
    -
    -
    - -
    -
    - -
    - -
    -
    - - -
    - - - -
    - - -
    -
    - - -
    - -
    - -
    -
    -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/app/core/src/main/resources/templates/security/change-permissions.html b/app/core/src/main/resources/templates/security/change-permissions.html deleted file mode 100644 index 5db85b064..000000000 --- a/app/core/src/main/resources/templates/security/change-permissions.html +++ /dev/null @@ -1,72 +0,0 @@ - - - - - - - -
    -
    - -

    -
    -
    -
    -
    - encrypted - -
    -

    -
    -
    - -
    -
    -
    - -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    -
    -
    - -
    -
    -
    -
    -
    -
    - -
    - - \ No newline at end of file diff --git a/app/core/src/main/resources/templates/security/get-info-on-pdf.html b/app/core/src/main/resources/templates/security/get-info-on-pdf.html deleted file mode 100644 index 0bd73b091..000000000 --- a/app/core/src/main/resources/templates/security/get-info-on-pdf.html +++ /dev/null @@ -1,753 +0,0 @@ - - - - - - - -
    -
    - -

    -
    -
    -
    -
    - info - -
    -
    -
    - - -
    -
    - - - - -
    - -
    - - - -
    - -
    -
    -
    -
    - -
    - - \ No newline at end of file diff --git a/app/core/src/main/resources/templates/security/redact.html b/app/core/src/main/resources/templates/security/redact.html deleted file mode 100644 index 39608d7db..000000000 --- a/app/core/src/main/resources/templates/security/redact.html +++ /dev/null @@ -1,698 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    -
    -
    -
    -
    - - - - -
    -
    -
    -
    -
    - - -
    -
    - - -
    -
    - - -
    - - -
    -
    -
    -
    - -
    -
    -
    - document_scanner - -
    -
    - - -
    -
    - - -
    - - -
    -
    -
    -
    -
    -
    -
    - - - - -
    -
    - -
    -
    -
    - - -
    -
    -
    -
    -
    -
    - - - -
    -
    -
    - -
    - - - - - - - - - - - - -
    -
    -
    -
    -
    - -
    - -
    - -
    - -
    - -
    - - - - - icon -
    -
    -
    - -
    - -
    - - - -
    -
    - - - -
    -
    -
    - - - - -
    -
    - - - - - -
    - -
    - - - - - -
    - - -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - -
    -
    - -
    -
    - -
    - -
    - -
    -
    - -
    -
    - - -
    -
    - -
    - File name: -

    -

    -
    -
    - File size: -

    -

    -
    -
    -
    - Title: -

    -

    -
    -
    - Author: -

    -

    -
    -
    - Subject: -

    -

    -
    -
    - Keywords: -

    -

    -
    -
    - Creation - Date: -

    -

    -
    -
    - Modification - Date: -

    -

    -
    -
    - Creator: -

    -

    -
    -
    -
    - PDF Producer: -

    -

    -
    -
    - PDF Version: -

    -

    -
    -
    - Page Count: -

    -

    -
    -
    - Page Size: -

    -

    -
    -
    -
    - Fast Web View: -

    -

    -
    -
    - -
    -
    - -
    -
    - Choose an - option - - Alt text (alternative text) helps when people can’t see the image or when it doesn’t load. - -
    -
    -
    -
    - - -
    -
    - - Aim for 1-2 sentences that describe the subject, setting, or actions. - -
    -
    -
    - -
    -
    -
    -
    -
    - - -
    -
    - - This is used for ornamental images, like borders or watermarks. - -
    -
    -
    -
    - - -
    -
    -
    - -
    - Preparing document for printing… -
    -
    - - 0% -
    -
    - -
    -
    -
    - -
    -
    - - - - - diff --git a/app/core/src/main/resources/templates/security/remove-cert-sign.html b/app/core/src/main/resources/templates/security/remove-cert-sign.html deleted file mode 100644 index ae0bc3cb4..000000000 --- a/app/core/src/main/resources/templates/security/remove-cert-sign.html +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - -
    -
    - -

    -
    -
    -
    -
    - remove_moderator - -
    -
    -
    - -
    -
    -
    - -
    -
    -
    -
    -
    -
    - -
    - - \ No newline at end of file diff --git a/app/core/src/main/resources/templates/security/remove-password.html b/app/core/src/main/resources/templates/security/remove-password.html deleted file mode 100644 index 96617cdd6..000000000 --- a/app/core/src/main/resources/templates/security/remove-password.html +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - -
    -
    - -

    -
    -
    -
    -
    - lock_open_right - -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    - -
    -
    -
    -
    -
    -
    - -
    - - \ No newline at end of file diff --git a/app/core/src/main/resources/templates/security/remove-watermark.html b/app/core/src/main/resources/templates/security/remove-watermark.html deleted file mode 100644 index a13c41a15..000000000 --- a/app/core/src/main/resources/templates/security/remove-watermark.html +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - -
    -
    - -

    -
    -
    -
    -
    - water_drop - -
    -
    -
    - -
    -
    -
    - - -
    -
    - -
    -
    -
    -
    -
    -
    - -
    - - \ No newline at end of file diff --git a/app/core/src/main/resources/templates/security/sanitize-pdf.html b/app/core/src/main/resources/templates/security/sanitize-pdf.html deleted file mode 100644 index 693d0334f..000000000 --- a/app/core/src/main/resources/templates/security/sanitize-pdf.html +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - - -
    -
    - -

    -
    -
    -
    -
    - sanitizer - -
    -
    -
    -
    -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    -
    - -
    -
    -
    -
    -
    -
    - -
    - - \ No newline at end of file diff --git a/app/core/src/main/resources/templates/security/validate-signature.html b/app/core/src/main/resources/templates/security/validate-signature.html deleted file mode 100644 index af4e92132..000000000 --- a/app/core/src/main/resources/templates/security/validate-signature.html +++ /dev/null @@ -1,265 +0,0 @@ - - - - - - -
    -
    - -

    -
    -
    -
    -
    - verified - -
    -
    -
    - -
    -
    -
    - -
    -
    -
    - -
    -
    - - - -
    -
    -
    -
    - -
    - - - - \ No newline at end of file diff --git a/app/core/src/main/resources/templates/sign.html b/app/core/src/main/resources/templates/sign.html deleted file mode 100644 index d6032db3f..000000000 --- a/app/core/src/main/resources/templates/sign.html +++ /dev/null @@ -1,250 +0,0 @@ - - - - - - - - - - - - - - - -
    -
    - -

    -
    -
    -
    -
    - signature - -
    - - -
    -
    - -
    -
    -
    -
    -
    - -
    -
    - - -
    - -
    - - - - -
    - -
    -
    - - - - -
    -
    -
    -
    -
    - - -
    - -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    -
    -
    - - -
    - -
    -
    -
    -
    -
    -
    - -
    -
    -

    No saved signatures found

    -
    -
    - -
    - - - -
    - -
    - -
    - - -
    -
    - -
    -
    -
    - - -
    - - - - - - - - - -
    -
    - - - - - - -
    - -
    -
    -
    -
    - -
    - - - - - \ No newline at end of file diff --git a/app/core/src/main/resources/templates/split-by-size-or-count.html b/app/core/src/main/resources/templates/split-by-size-or-count.html deleted file mode 100644 index ce7cb2f0b..000000000 --- a/app/core/src/main/resources/templates/split-by-size-or-count.html +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - - - - - -
    -
    - -

    -
    -
    -
    -
    - - - - -
    -
    -
    -
    - - -
    - - -
    - -
    -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/app/core/src/main/resources/templates/split-pdf-by-chapters.html b/app/core/src/main/resources/templates/split-pdf-by-chapters.html deleted file mode 100644 index 0a7220000..000000000 --- a/app/core/src/main/resources/templates/split-pdf-by-chapters.html +++ /dev/null @@ -1,72 +0,0 @@ - - - - - - - - - -
    -
    - -

    -
    -
    -
    -
    - - - - -
    -
    -
    -
    - -
    - - -
    - -
    - - - -
    - -
    - - - -
    - -

    - -

    -
    -

    -

    -

    -

    -
    - -
    - -
    - -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/app/core/src/main/resources/templates/split-pdf-by-sections.html b/app/core/src/main/resources/templates/split-pdf-by-sections.html deleted file mode 100644 index 9283b457d..000000000 --- a/app/core/src/main/resources/templates/split-pdf-by-sections.html +++ /dev/null @@ -1,125 +0,0 @@ - - - - - - - - - -
    -
    - -

    -
    -
    -
    -
    - grid_on - -
    -
    -
    -
    - - -
    -
    - - -
    - - -
    - - -
    -
    - - -
    -
    -
    - -
    - -
    -
    -
    -
    -
    - -
    - - \ No newline at end of file diff --git a/app/core/src/main/resources/templates/split-pdfs.html b/app/core/src/main/resources/templates/split-pdfs.html deleted file mode 100644 index 75dbb21cd..000000000 --- a/app/core/src/main/resources/templates/split-pdfs.html +++ /dev/null @@ -1,60 +0,0 @@ - - - - - - - - -
    -
    - -

    - -
    -
    -
    -
    - cut - -
    -
    -
    -
    - -
    - - -
    - -

    - -

    -
    -

    -

    -

    -

    -

    -

    -

    -

    -
    - -
    - -
    - -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/app/core/src/main/resources/templates/usage.html b/app/core/src/main/resources/templates/usage.html deleted file mode 100644 index d94c08e67..000000000 --- a/app/core/src/main/resources/templates/usage.html +++ /dev/null @@ -1,114 +0,0 @@ - - - - - - - - - - -
    -
    - -

    -
    -
    -
    -
    - analytics - Endpoint Statistics -
    - - -
    -
    - - - - -
    - -
    -
    - - -
    -
    - - -
    -
    - -
    - Total Endpoints: 0 - Total Visits: 0 - Showing: Top 10 - Selected Visits: 0 (0%) -
    -
    - - -
    -
    - -
    -
    - - -
    - - - - - - - - - - - - -
    #EndpointVisitsPercentage
    -
    -
    -
    -
    -
    - - - - - -
    - - \ No newline at end of file diff --git a/app/core/src/main/resources/templates/view-pdf.html b/app/core/src/main/resources/templates/view-pdf.html deleted file mode 100644 index a50b362a2..000000000 --- a/app/core/src/main/resources/templates/view-pdf.html +++ /dev/null @@ -1,627 +0,0 @@ - - - - - - - - - - PDF.js viewer - - - - - - - - - - - - - - -
    -
    -
    -
    -
    - - - - -
    -
    - -
    -
    -
    - - -
    -
    -
    -
    -
    -
    - - - -
    -
    -
    - -
    - - - - - - - - - - - - -
    -
    -
    -
    - -
    - -
    - -
    - -
    - - - - -
    -
    -
    - - Back to Main Page - - - - - - -
    - -
    - - - - -
    - - -
    -
    -
    - -
    - -
    - - - -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - -
    -
    -
    - - -
    -
    - -
    - -
    - -
    -
    - -
    -
    - - -
    -
    - -
    - File name: -

    -

    -
    -
    - File size: -

    -

    -
    -
    -
    - Title: -

    -

    -
    -
    - Author: -

    -

    -
    -
    - Subject: -

    -

    -
    -
    - Keywords: -

    -

    -
    -
    - Creation Date: -

    -

    -
    -
    - Modification - Date: -

    -

    -
    -
    - Creator: -

    -

    -
    -
    -
    - PDF Producer: -

    -

    -
    -
    - PDF Version: -

    -

    -
    -
    - Page Count: -

    -

    -
    -
    - Page Size: -

    -

    -
    -
    -
    - Fast Web View: -

    -

    -
    -
    - -
    -
    - -
    -
    - Choose an - option - - Alt text (alternative text) helps when people can’t see the image or when it doesn’t load. - -
    -
    -
    -
    - - -
    -
    - - Aim for 1-2 sentences that describe the subject, setting, or actions. - -
    -
    -
    - -
    -
    -
    -
    -
    - - -
    -
    - - This is used for ornamental images, like borders or watermarks. - -
    -
    -
    -
    - - -
    -
    -
    - -
    - Preparing document for printing… -
    -
    - - 0% -
    -
    - -
    -
    -
    - -
    -
    - - - - \ No newline at end of file diff --git a/app/core/src/test/java/stirling/software/SPDF/controller/web/ConverterWebControllerTest.java b/app/core/src/test/java/stirling/software/SPDF/controller/web/ConverterWebControllerTest.java deleted file mode 100644 index da73cf83c..000000000 --- a/app/core/src/test/java/stirling/software/SPDF/controller/web/ConverterWebControllerTest.java +++ /dev/null @@ -1,231 +0,0 @@ -package stirling.software.SPDF.controller.web; - -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; - -import java.util.stream.Stream; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.MethodSource; -import org.mockito.MockedStatic; -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; - -import stirling.software.SPDF.config.EndpointConfiguration; -import stirling.software.common.model.ApplicationProperties; -import stirling.software.common.util.ApplicationContextProvider; -import stirling.software.common.util.CheckProgramInstall; - -@ExtendWith(MockitoExtension.class) -class ConverterWebControllerTest { - - private MockMvc mockMvc; - - private ConverterWebController controller; - - @BeforeEach - void setup() { - controller = new ConverterWebController(); - mockMvc = MockMvcBuilders.standaloneSetup(controller).build(); - } - - private static Stream simpleEndpoints() { - return Stream.of( - new Object[] {"/img-to-pdf", "convert/img-to-pdf", "img-to-pdf"}, - new Object[] {"/cbz-to-pdf", "convert/cbz-to-pdf", "cbz-to-pdf"}, - new Object[] {"/pdf-to-cbz", "convert/pdf-to-cbz", "pdf-to-cbz"}, - new Object[] {"/cbr-to-pdf", "convert/cbr-to-pdf", "cbr-to-pdf"}, - new Object[] {"/html-to-pdf", "convert/html-to-pdf", "html-to-pdf"}, - new Object[] {"/markdown-to-pdf", "convert/markdown-to-pdf", "markdown-to-pdf"}, - new Object[] {"/pdf-to-markdown", "convert/pdf-to-markdown", "pdf-to-markdown"}, - new Object[] {"/url-to-pdf", "convert/url-to-pdf", "url-to-pdf"}, - new Object[] {"/file-to-pdf", "convert/file-to-pdf", "file-to-pdf"}, - new Object[] {"/pdf-to-pdfa", "convert/pdf-to-pdfa", "pdf-to-pdfa"}, - new Object[] {"/pdf-to-vector", "convert/pdf-to-vector", "pdf-to-vector"}, - new Object[] {"/vector-to-pdf", "convert/vector-to-pdf", "vector-to-pdf"}, - new Object[] {"/pdf-to-xml", "convert/pdf-to-xml", "pdf-to-xml"}, - new Object[] {"/pdf-to-csv", "convert/pdf-to-csv", "pdf-to-csv"}, - new Object[] {"/pdf-to-html", "convert/pdf-to-html", "pdf-to-html"}, - new Object[] { - "/pdf-to-presentation", "convert/pdf-to-presentation", "pdf-to-presentation" - }, - new Object[] {"/pdf-to-text", "convert/pdf-to-text", "pdf-to-text"}, - new Object[] {"/pdf-to-word", "convert/pdf-to-word", "pdf-to-word"}, - new Object[] {"/eml-to-pdf", "convert/eml-to-pdf", "eml-to-pdf"}); - } - - @ParameterizedTest(name = "[{index}] GET {0}") - @MethodSource("simpleEndpoints") - @DisplayName("Should return correct view and model for simple endpoints") - void shouldReturnCorrectViewForSimpleEndpoints(String path, String viewName, String page) - throws Exception { - mockMvc.perform(get(path)) - .andExpect(status().isOk()) - .andExpect(view().name(viewName)) - .andExpect(model().attribute("currentPage", page)); - } - - @Nested - @DisplayName("PDF to CBR endpoint tests") - class PdfToCbrTests { - - @Test - @DisplayName("Should return 404 when endpoint disabled") - void shouldReturn404WhenDisabled() throws Exception { - try (MockedStatic acp = - org.mockito.Mockito.mockStatic(ApplicationContextProvider.class)) { - EndpointConfiguration endpointConfig = mock(EndpointConfiguration.class); - when(endpointConfig.isEndpointEnabled(eq("pdf-to-cbr"))).thenReturn(false); - acp.when(() -> ApplicationContextProvider.getBean(EndpointConfiguration.class)) - .thenReturn(endpointConfig); - - mockMvc.perform(get("/pdf-to-cbr")).andExpect(status().isNotFound()); - } - } - - @Test - @DisplayName("Should return OK when endpoint enabled") - void shouldReturnOkWhenEnabled() throws Exception { - try (MockedStatic acp = - org.mockito.Mockito.mockStatic(ApplicationContextProvider.class)) { - EndpointConfiguration endpointConfig = mock(EndpointConfiguration.class); - when(endpointConfig.isEndpointEnabled(eq("pdf-to-cbr"))).thenReturn(true); - acp.when(() -> ApplicationContextProvider.getBean(EndpointConfiguration.class)) - .thenReturn(endpointConfig); - - mockMvc.perform(get("/pdf-to-cbr")) - .andExpect(status().isOk()) - .andExpect(view().name("convert/pdf-to-cbr")) - .andExpect(model().attribute("currentPage", "pdf-to-cbr")); - } - } - } - - @Nested - @DisplayName("PDF to EPUB endpoint tests") - class PdfToEpubTests { - - @Test - @DisplayName("Should return 404 when endpoint disabled") - void shouldReturn404WhenDisabled() throws Exception { - try (MockedStatic acp = - org.mockito.Mockito.mockStatic(ApplicationContextProvider.class)) { - EndpointConfiguration endpointConfig = mock(EndpointConfiguration.class); - when(endpointConfig.isEndpointEnabled(eq("pdf-to-epub"))).thenReturn(false); - acp.when(() -> ApplicationContextProvider.getBean(EndpointConfiguration.class)) - .thenReturn(endpointConfig); - - mockMvc.perform(get("/pdf-to-epub")).andExpect(status().isNotFound()); - } - } - - @Test - @DisplayName("Should return OK when endpoint enabled") - void shouldReturnOkWhenEnabled() throws Exception { - try (MockedStatic acp = - org.mockito.Mockito.mockStatic(ApplicationContextProvider.class)) { - EndpointConfiguration endpointConfig = mock(EndpointConfiguration.class); - when(endpointConfig.isEndpointEnabled(eq("pdf-to-epub"))).thenReturn(true); - acp.when(() -> ApplicationContextProvider.getBean(EndpointConfiguration.class)) - .thenReturn(endpointConfig); - - mockMvc.perform(get("/pdf-to-epub")) - .andExpect(status().isOk()) - .andExpect(view().name("convert/pdf-to-epub")) - .andExpect(model().attribute("currentPage", "pdf-to-epub")); - } - } - } - - @Test - @DisplayName("Should handle pdf-to-img with default maxDPI=500") - void shouldHandlePdfToImgWithDefaultMaxDpi() throws Exception { - try (MockedStatic acp = - org.mockito.Mockito.mockStatic(ApplicationContextProvider.class); - MockedStatic cpi = - org.mockito.Mockito.mockStatic(CheckProgramInstall.class)) { - cpi.when(CheckProgramInstall::isPythonAvailable).thenReturn(true); - acp.when(() -> ApplicationContextProvider.getBean(ApplicationProperties.class)) - .thenReturn(null); - - mockMvc.perform(get("/pdf-to-img")) - .andExpect(status().isOk()) - .andExpect(view().name("convert/pdf-to-img")) - .andExpect(model().attribute("isPython", true)) - .andExpect(model().attribute("maxDPI", 500)); - } - } - - @Test - @DisplayName("Should handle pdf-to-video with default maxDPI=500") - void shouldHandlePdfToVideoWithDefaultMaxDpi() throws Exception { - try (MockedStatic acp = - org.mockito.Mockito.mockStatic(ApplicationContextProvider.class)) { - acp.when(() -> ApplicationContextProvider.getBean(ApplicationProperties.class)) - .thenReturn(null); - - mockMvc.perform(get("/pdf-to-video")) - .andExpect(status().isOk()) - .andExpect(view().name("convert/pdf-to-video")) - .andExpect(model().attribute("maxDPI", 500)) - .andExpect(model().attribute("currentPage", "pdf-to-video")); - } - } - - @Test - @DisplayName("Should handle pdf-to-img with configured maxDPI from properties") - void shouldHandlePdfToImgWithConfiguredMaxDpi() throws Exception { - // Covers the 'if' branch (properties and system not null) - try (MockedStatic acp = - org.mockito.Mockito.mockStatic(ApplicationContextProvider.class); - MockedStatic cpi = - org.mockito.Mockito.mockStatic(CheckProgramInstall.class)) { - - ApplicationProperties properties = - org.mockito.Mockito.mock( - ApplicationProperties.class, org.mockito.Mockito.RETURNS_DEEP_STUBS); - when(properties.getSystem().getMaxDPI()).thenReturn(777); - acp.when(() -> ApplicationContextProvider.getBean(ApplicationProperties.class)) - .thenReturn(properties); - cpi.when(CheckProgramInstall::isPythonAvailable).thenReturn(true); - - mockMvc.perform(get("/pdf-to-img")) - .andExpect(status().isOk()) - .andExpect(view().name("convert/pdf-to-img")) - .andExpect(model().attribute("isPython", true)) - .andExpect(model().attribute("maxDPI", 777)) - .andExpect(model().attribute("currentPage", "pdf-to-img")); - } - } - - @Test - @DisplayName("Should handle pdf-to-video with configured maxDPI from properties") - void shouldHandlePdfToVideoWithConfiguredMaxDpi() throws Exception { - // Covers the 'if' branch (properties and system not null) - try (MockedStatic acp = - org.mockito.Mockito.mockStatic(ApplicationContextProvider.class)) { - - ApplicationProperties properties = - org.mockito.Mockito.mock( - ApplicationProperties.class, org.mockito.Mockito.RETURNS_DEEP_STUBS); - when(properties.getSystem().getMaxDPI()).thenReturn(640); - acp.when(() -> ApplicationContextProvider.getBean(ApplicationProperties.class)) - .thenReturn(properties); - - mockMvc.perform(get("/pdf-to-video")) - .andExpect(status().isOk()) - .andExpect(view().name("convert/pdf-to-video")) - .andExpect(model().attribute("maxDPI", 640)) - .andExpect(model().attribute("currentPage", "pdf-to-video")); - } - } -} diff --git a/app/proprietary/build.gradle b/app/proprietary/build.gradle index e9aea411f..f6bb668c1 100644 --- a/app/proprietary/build.gradle +++ b/app/proprietary/build.gradle @@ -55,7 +55,6 @@ dependencies { // https://mvnrepository.com/artifact/com.bucket4j/bucket4j_jdk17 implementation "org.bouncycastle:bcprov-jdk18on:$bouncycastleVersion" - implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5:3.1.3.RELEASE' api 'io.micrometer:micrometer-registry-prometheus' implementation 'com.unboundid.product.scim2:scim2-sdk-client:4.1.0' diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/controller/AuditDashboardController.java b/app/proprietary/src/main/java/stirling/software/proprietary/controller/AuditDashboardController.java deleted file mode 100644 index 6d2f85303..000000000 --- a/app/proprietary/src/main/java/stirling/software/proprietary/controller/AuditDashboardController.java +++ /dev/null @@ -1,351 +0,0 @@ -package stirling.software.proprietary.controller; - -import java.time.Instant; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.ZoneId; -import java.time.format.DateTimeFormatter; -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; - -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; -import org.springframework.format.annotation.DateTimeFormat; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.ui.Model; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.ResponseBody; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; - -import jakarta.servlet.http.HttpServletRequest; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; - -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.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("hasRole('ADMIN')") -@RequiredArgsConstructor -@EnterpriseEndpoint -public class AuditDashboardController { - - private final PersistentAuditEventRepository auditRepository; - private final AuditConfigurationProperties auditConfig; - private final ObjectMapper objectMapper; - - /** Display the audit dashboard. */ - @GetMapping - public String showDashboard(Model model) { - model.addAttribute("auditEnabled", auditConfig.isEnabled()); - model.addAttribute("auditLevel", auditConfig.getAuditLevel()); - model.addAttribute("auditLevelInt", auditConfig.getLevel()); - model.addAttribute("retentionDays", auditConfig.getRetentionDays()); - - // Add audit level enum values for display - model.addAttribute("auditLevels", AuditLevel.values()); - - // Add audit event types for the dropdown - model.addAttribute("auditEventTypes", AuditEventType.values()); - - return "audit/dashboard"; - } - - /** Get audit events data for the dashboard tables. */ - @GetMapping("/data") - @ResponseBody - public Map getAuditData( - @RequestParam(value = "page", defaultValue = "0") int page, - @RequestParam(value = "size", defaultValue = "30") int size, - @RequestParam(value = "type", required = false) String type, - @RequestParam(value = "principal", required = false) String principal, - @RequestParam(value = "startDate", required = false) - @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) - LocalDate startDate, - @RequestParam(value = "endDate", required = false) - @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) - LocalDate endDate, - HttpServletRequest request) { - - Pageable pageable = PageRequest.of(page, size, Sort.by("timestamp").descending()); - Page events; - - String mode; - - if (type != null && principal != null && startDate != null && endDate != null) { - mode = "principal + type + startDate + endDate"; - Instant start = startDate.atStartOfDay(ZoneId.systemDefault()).toInstant(); - Instant end = endDate.plusDays(1).atStartOfDay(ZoneId.systemDefault()).toInstant(); - events = - auditRepository.findByPrincipalAndTypeAndTimestampBetween( - principal, type, start, end, pageable); - } else if (type != null && principal != null) { - mode = "principal + type"; - events = auditRepository.findByPrincipalAndType(principal, type, pageable); - } else if (type != null && startDate != null && endDate != null) { - mode = "type + startDate + endDate"; - Instant start = startDate.atStartOfDay(ZoneId.systemDefault()).toInstant(); - Instant end = endDate.plusDays(1).atStartOfDay(ZoneId.systemDefault()).toInstant(); - events = auditRepository.findByTypeAndTimestampBetween(type, start, end, pageable); - } else if (principal != null && startDate != null && endDate != null) { - mode = "principal + startDate + endDate"; - Instant start = startDate.atStartOfDay(ZoneId.systemDefault()).toInstant(); - Instant end = endDate.plusDays(1).atStartOfDay(ZoneId.systemDefault()).toInstant(); - events = - auditRepository.findByPrincipalAndTimestampBetween( - principal, start, end, pageable); - } else if (startDate != null && endDate != null) { - mode = "startDate + endDate"; - Instant start = startDate.atStartOfDay(ZoneId.systemDefault()).toInstant(); - Instant end = endDate.plusDays(1).atStartOfDay(ZoneId.systemDefault()).toInstant(); - events = auditRepository.findByTimestampBetween(start, end, pageable); - } else if (type != null) { - mode = "type"; - events = auditRepository.findByType(type, pageable); - } else if (principal != null) { - mode = "principal"; - events = auditRepository.findByPrincipal(principal, pageable); - } else { - mode = "all"; - events = auditRepository.findAll(pageable); - } - - // Logging - List content = events.getContent(); - - Map response = new HashMap<>(); - response.put("content", content); - response.put("totalPages", events.getTotalPages()); - response.put("totalElements", events.getTotalElements()); - response.put("currentPage", events.getNumber()); - - return response; - } - - /** Get statistics for charts. */ - @GetMapping("/stats") - @ResponseBody - public Map getAuditStats( - @RequestParam(value = "days", defaultValue = "7") int days) { - - // Get events from the last X days - Instant startDate = Instant.now().minus(java.time.Duration.ofDays(days)); - List events = auditRepository.findByTimestampAfter(startDate); - - // Count events by type - Map eventsByType = - events.stream() - .collect( - Collectors.groupingBy( - PersistentAuditEvent::getType, Collectors.counting())); - - // Count events by principal - Map eventsByPrincipal = - events.stream() - .collect( - Collectors.groupingBy( - PersistentAuditEvent::getPrincipal, Collectors.counting())); - - // Count events by day - Map eventsByDay = - events.stream() - .collect( - Collectors.groupingBy( - e -> - LocalDateTime.ofInstant( - e.getTimestamp(), - ZoneId.systemDefault()) - .format(DateTimeFormatter.ISO_LOCAL_DATE), - Collectors.counting())); - - Map stats = new HashMap<>(); - stats.put("eventsByType", eventsByType); - stats.put("eventsByPrincipal", eventsByPrincipal); - stats.put("eventsByDay", eventsByDay); - stats.put("totalEvents", events.size()); - - return stats; - } - - /** Get all unique event types from the database for filtering. */ - @GetMapping("/types") - @ResponseBody - public List getAuditTypes() { - // Get distinct event types from the database - List dbTypes = auditRepository.findDistinctEventTypes(); - - // Include standard enum types in case they're not in the database yet - List enumTypes = - Arrays.stream(AuditEventType.values()) - .map(AuditEventType::name) - .collect(Collectors.toList()); - - // Combine both sources, remove duplicates, and sort - Set combinedTypes = new HashSet<>(); - combinedTypes.addAll(dbTypes); - combinedTypes.addAll(enumTypes); - - return combinedTypes.stream().sorted().collect(Collectors.toList()); - } - - /** Export audit data as CSV. */ - @GetMapping("/export") - public ResponseEntity exportAuditData( - @RequestParam(value = "type", required = false) String type, - @RequestParam(value = "principal", required = false) String principal, - @RequestParam(value = "startDate", required = false) - @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) - LocalDate startDate, - @RequestParam(value = "endDate", required = false) - @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) - LocalDate endDate) { - - // Get data with same filtering as getAuditData - List events; - - if (type != null && principal != null && startDate != null && endDate != null) { - Instant start = startDate.atStartOfDay(ZoneId.systemDefault()).toInstant(); - Instant end = endDate.plusDays(1).atStartOfDay(ZoneId.systemDefault()).toInstant(); - events = - auditRepository.findAllByPrincipalAndTypeAndTimestampBetweenForExport( - principal, type, start, end); - } else if (type != null && principal != null) { - events = auditRepository.findAllByPrincipalAndTypeForExport(principal, type); - } else if (type != null && startDate != null && endDate != null) { - Instant start = startDate.atStartOfDay(ZoneId.systemDefault()).toInstant(); - Instant end = endDate.plusDays(1).atStartOfDay(ZoneId.systemDefault()).toInstant(); - events = auditRepository.findAllByTypeAndTimestampBetweenForExport(type, start, end); - } else if (principal != null && startDate != null && endDate != null) { - Instant start = startDate.atStartOfDay(ZoneId.systemDefault()).toInstant(); - Instant end = endDate.plusDays(1).atStartOfDay(ZoneId.systemDefault()).toInstant(); - events = - auditRepository.findAllByPrincipalAndTimestampBetweenForExport( - principal, start, end); - } else if (startDate != null && endDate != null) { - Instant start = startDate.atStartOfDay(ZoneId.systemDefault()).toInstant(); - Instant end = endDate.plusDays(1).atStartOfDay(ZoneId.systemDefault()).toInstant(); - events = auditRepository.findAllByTimestampBetweenForExport(start, end); - } else if (type != null) { - events = auditRepository.findByTypeForExport(type); - } else if (principal != null) { - events = auditRepository.findAllByPrincipalForExport(principal); - } else { - events = auditRepository.findAll(); - } - - // Convert to CSV - StringBuilder csv = new StringBuilder(); - csv.append("ID,Principal,Type,Timestamp,Data\n"); - - DateTimeFormatter formatter = DateTimeFormatter.ISO_INSTANT; - - for (PersistentAuditEvent event : events) { - csv.append(event.getId()).append(","); - csv.append(escapeCSV(event.getPrincipal())).append(","); - csv.append(escapeCSV(event.getType())).append(","); - csv.append(formatter.format(event.getTimestamp())).append(","); - csv.append(escapeCSV(event.getData())).append("\n"); - } - - byte[] csvBytes = csv.toString().getBytes(); - - // Set up HTTP headers for download - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.APPLICATION_OCTET_STREAM); - headers.setContentDispositionFormData("attachment", "audit_export.csv"); - - return ResponseEntity.ok().headers(headers).body(csvBytes); - } - - /** Export audit data as JSON. */ - @GetMapping("/export/json") - public ResponseEntity exportAuditDataJson( - @RequestParam(value = "type", required = false) String type, - @RequestParam(value = "principal", required = false) String principal, - @RequestParam(value = "startDate", required = false) - @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) - LocalDate startDate, - @RequestParam(value = "endDate", required = false) - @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) - LocalDate endDate) { - - // Get data with same filtering as getAuditData - List events; - - if (type != null && principal != null && startDate != null && endDate != null) { - Instant start = startDate.atStartOfDay(ZoneId.systemDefault()).toInstant(); - Instant end = endDate.plusDays(1).atStartOfDay(ZoneId.systemDefault()).toInstant(); - events = - auditRepository.findAllByPrincipalAndTypeAndTimestampBetweenForExport( - principal, type, start, end); - } else if (type != null && principal != null) { - events = auditRepository.findAllByPrincipalAndTypeForExport(principal, type); - } else if (type != null && startDate != null && endDate != null) { - Instant start = startDate.atStartOfDay(ZoneId.systemDefault()).toInstant(); - Instant end = endDate.plusDays(1).atStartOfDay(ZoneId.systemDefault()).toInstant(); - events = auditRepository.findAllByTypeAndTimestampBetweenForExport(type, start, end); - } else if (principal != null && startDate != null && endDate != null) { - Instant start = startDate.atStartOfDay(ZoneId.systemDefault()).toInstant(); - Instant end = endDate.plusDays(1).atStartOfDay(ZoneId.systemDefault()).toInstant(); - events = - auditRepository.findAllByPrincipalAndTimestampBetweenForExport( - principal, start, end); - } else if (startDate != null && endDate != null) { - Instant start = startDate.atStartOfDay(ZoneId.systemDefault()).toInstant(); - Instant end = endDate.plusDays(1).atStartOfDay(ZoneId.systemDefault()).toInstant(); - events = auditRepository.findAllByTimestampBetweenForExport(start, end); - } else if (type != null) { - events = auditRepository.findByTypeForExport(type); - } else if (principal != null) { - events = auditRepository.findAllByPrincipalForExport(principal); - } else { - events = auditRepository.findAll(); - } - - // Convert to JSON - try { - byte[] jsonBytes = objectMapper.writeValueAsBytes(events); - - // Set up HTTP headers for download - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.APPLICATION_JSON); - headers.setContentDispositionFormData("attachment", "audit_export.json"); - - return ResponseEntity.ok().headers(headers).body(jsonBytes); - } catch (JsonProcessingException e) { - log.error("Error serializing audit events to JSON", e); - return ResponseEntity.internalServerError().build(); - } - } - - /** Helper method to escape CSV fields. */ - private String escapeCSV(String field) { - if (field == null) { - return ""; - } - // Replace double quotes with two double quotes and wrap in quotes - return "\"" + field.replace("\"", "\"\"") + "\""; - } -} diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/controller/web/AuditDashboardWebController.java b/app/proprietary/src/main/java/stirling/software/proprietary/controller/web/AuditDashboardWebController.java deleted file mode 100644 index e5c80e162..000000000 --- a/app/proprietary/src/main/java/stirling/software/proprietary/controller/web/AuditDashboardWebController.java +++ /dev/null @@ -1,41 +0,0 @@ -package stirling.software.proprietary.controller.web; - -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.stereotype.Controller; -import org.springframework.ui.Model; -import org.springframework.web.bind.annotation.GetMapping; - -import io.swagger.v3.oas.annotations.Hidden; - -import lombok.RequiredArgsConstructor; - -import stirling.software.proprietary.audit.AuditEventType; -import stirling.software.proprietary.audit.AuditLevel; -import stirling.software.proprietary.config.AuditConfigurationProperties; -import stirling.software.proprietary.security.config.EnterpriseEndpoint; - -@Controller -@PreAuthorize("hasRole('ADMIN')") -@RequiredArgsConstructor -@EnterpriseEndpoint -public class AuditDashboardWebController { - private final AuditConfigurationProperties auditConfig; - - /** Display the audit dashboard. */ - @GetMapping("/audit") - @Hidden - public String showDashboard(Model model) { - model.addAttribute("auditEnabled", auditConfig.isEnabled()); - model.addAttribute("auditLevel", auditConfig.getAuditLevel()); - model.addAttribute("auditLevelInt", auditConfig.getLevel()); - model.addAttribute("retentionDays", auditConfig.getRetentionDays()); - - // Add audit level enum values for display - model.addAttribute("auditLevels", AuditLevel.values()); - - // Add audit event types for the dropdown - model.addAttribute("auditEventTypes", AuditEventType.values()); - - return "audit/dashboard"; - } -} diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/config/AccountWebController.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/config/AccountWebController.java deleted file mode 100644 index 17857fc85..000000000 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/config/AccountWebController.java +++ /dev/null @@ -1,478 +0,0 @@ -package stirling.software.proprietary.security.config; - -import static stirling.software.common.util.ProviderUtils.validateProvider; - -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -import org.springframework.security.core.Authentication; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.oauth2.core.user.OAuth2User; -import org.springframework.ui.Model; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; - -import io.swagger.v3.oas.annotations.tags.Tag; - -import jakarta.servlet.http.HttpServletRequest; - -import lombok.extern.slf4j.Slf4j; - -import stirling.software.common.model.ApplicationProperties; -import stirling.software.common.model.ApplicationProperties.Security; -import stirling.software.common.model.ApplicationProperties.Security.OAUTH2; -import stirling.software.common.model.ApplicationProperties.Security.OAUTH2.Client; -import stirling.software.common.model.ApplicationProperties.Security.SAML2; -import stirling.software.common.model.enumeration.Role; -import stirling.software.common.model.oauth2.GitHubProvider; -import stirling.software.common.model.oauth2.GoogleProvider; -import stirling.software.common.model.oauth2.KeycloakProvider; -import stirling.software.proprietary.model.Team; -import stirling.software.proprietary.security.database.repository.UserRepository; -import stirling.software.proprietary.security.model.Authority; -import stirling.software.proprietary.security.model.SessionEntity; -import stirling.software.proprietary.security.model.User; -import stirling.software.proprietary.security.repository.TeamRepository; -import stirling.software.proprietary.security.saml2.CustomSaml2AuthenticatedPrincipal; -import stirling.software.proprietary.security.service.TeamService; -import stirling.software.proprietary.security.session.SessionPersistentRegistry; - -// @Controller // Disabled - Backend-only mode, no Thymeleaf UI -@Slf4j -@Tag(name = "Account Security", description = "Account Security APIs") -public class AccountWebController { - - public static final String OAUTH_2_AUTHORIZATION = "/oauth2/authorization/"; - - private final ApplicationProperties applicationProperties; - private final SessionPersistentRegistry sessionPersistentRegistry; - // Assuming you have a repository for user operations - private final UserRepository userRepository; - private final TeamRepository teamRepository; - - public AccountWebController( - ApplicationProperties applicationProperties, - SessionPersistentRegistry sessionPersistentRegistry, - UserRepository userRepository, - TeamRepository teamRepository) { - this.applicationProperties = applicationProperties; - this.sessionPersistentRegistry = sessionPersistentRegistry; - this.userRepository = userRepository; - this.teamRepository = teamRepository; - } - - // @GetMapping("/login") - public String login(HttpServletRequest request, Model model, Authentication authentication) { - // If the user is already authenticated and it's not a logout scenario, redirect them to the - // home page. - if (authentication != null - && authentication.isAuthenticated() - && request.getParameter("logout") == null) { - return "redirect:/"; - } - - Map providerList = new HashMap<>(); - Security securityProps = applicationProperties.getSecurity(); - OAUTH2 oauth = securityProps.getOauth2(); - - if (oauth != null) { - if (oauth.getEnabled()) { - if (oauth.isSettingsValid()) { - String firstChar = String.valueOf(oauth.getProvider().charAt(0)); - String clientName = - oauth.getProvider().replaceFirst(firstChar, firstChar.toUpperCase()); - providerList.put(OAUTH_2_AUTHORIZATION + oauth.getProvider(), clientName); - } - - Client client = oauth.getClient(); - - if (client != null) { - GoogleProvider google = client.getGoogle(); - - if (validateProvider(google)) { - providerList.put( - OAUTH_2_AUTHORIZATION + google.getName(), google.getClientName()); - } - - GitHubProvider github = client.getGithub(); - - if (validateProvider(github)) { - providerList.put( - OAUTH_2_AUTHORIZATION + github.getName(), github.getClientName()); - } - - KeycloakProvider keycloak = client.getKeycloak(); - - if (validateProvider(keycloak)) { - providerList.put( - OAUTH_2_AUTHORIZATION + keycloak.getName(), - keycloak.getClientName()); - } - } - } - } - - SAML2 saml2 = securityProps.getSaml2(); - - if (securityProps.isSaml2Active() && applicationProperties.getPremium().isEnabled()) { - String samlIdp = saml2.getProvider(); - String saml2AuthenticationPath = "/saml2/authenticate/" + saml2.getRegistrationId(); - - if (applicationProperties.getPremium().getProFeatures().isSsoAutoLogin()) { - return "redirect:" + request.getRequestURL() + saml2AuthenticationPath; - } else { - providerList.put(saml2AuthenticationPath, samlIdp + " (SAML 2)"); - } - } - - // Remove any null keys/values from the providerList - providerList - .entrySet() - .removeIf(entry -> entry.getKey() == null || entry.getValue() == null); - model.addAttribute("providerList", providerList); - model.addAttribute("loginMethod", securityProps.getLoginMethod()); - - boolean altLogin = !providerList.isEmpty() ? securityProps.isAltLogin() : false; - - model.addAttribute("altLogin", altLogin); - model.addAttribute("currentPage", "login"); - String error = request.getParameter("error"); - - if (error != null) { - switch (error) { - case "badCredentials" -> error = "login.invalid"; - case "locked" -> error = "login.locked"; - case "oauth2AuthenticationError" -> error = "userAlreadyExistsOAuthMessage"; - } - - model.addAttribute("error", error); - } - - String errorOAuth = request.getParameter("errorOAuth"); - - if (errorOAuth != null) { - switch (errorOAuth) { - case "oAuth2AutoCreateDisabled" -> errorOAuth = "login.oAuth2AutoCreateDisabled"; - case "invalidUsername" -> errorOAuth = "login.invalid"; - case "userAlreadyExistsWeb" -> errorOAuth = "userAlreadyExistsWebMessage"; - case "oAuth2AuthenticationErrorWeb" -> errorOAuth = "login.oauth2InvalidUserType"; - case "invalid_token_response" -> errorOAuth = "login.oauth2InvalidTokenResponse"; - case "authorization_request_not_found" -> - errorOAuth = "login.oauth2RequestNotFound"; - case "access_denied" -> errorOAuth = "login.oauth2AccessDenied"; - case "invalid_user_info_response" -> - errorOAuth = "login.oauth2InvalidUserInfoResponse"; - case "invalid_request" -> errorOAuth = "login.oauth2invalidRequest"; - case "invalid_id_token" -> errorOAuth = "login.oauth2InvalidIdToken"; - case "oAuth2AdminBlockedUser" -> errorOAuth = "login.oAuth2AdminBlockedUser"; - case "userIsDisabled" -> errorOAuth = "login.userIsDisabled"; - case "invalid_destination" -> errorOAuth = "login.invalid_destination"; - case "relying_party_registration_not_found" -> - errorOAuth = "login.relyingPartyRegistrationNotFound"; - // Valid InResponseTo was not available from the validation context, unable to - // evaluate - case "invalid_in_response_to" -> errorOAuth = "login.invalidInResponseTo"; - case "not_authentication_provider_found" -> - errorOAuth = "login.not_authentication_provider_found"; - } - - model.addAttribute("errorOAuth", errorOAuth); - } - - if (request.getParameter("messageType") != null) { - model.addAttribute("messageType", "changedCredsMessage"); - } - - if (request.getParameter("logout") != null) { - model.addAttribute("logoutMessage", "login.logoutMessage"); - } - - return "login"; - } - - // @EnterpriseEndpoint - // @PreAuthorize("hasRole('ROLE_ADMIN')") - // @GetMapping("/usage") - - public String showUsage() { - return "usage"; - } - - // @PreAuthorize("hasRole('ROLE_ADMIN')") - // @GetMapping("/adminSettings") - public String showAddUserForm( - HttpServletRequest request, Model model, Authentication authentication) { - List allUsers = userRepository.findAllWithTeam(); - Iterator iterator = allUsers.iterator(); - Map roleDetails = Role.getAllRoleDetails(); - // Map to store session information and user activity status - Map userSessions = new HashMap<>(); - Map userLastRequest = new HashMap<>(); - int activeUsers = 0; - int disabledUsers = 0; - while (iterator.hasNext()) { - User user = iterator.next(); - if (user != null) { - boolean shouldRemove = false; - - // Check if user is an INTERNAL_API_USER - for (Authority authority : user.getAuthorities()) { - if (authority.getAuthority().equals(Role.INTERNAL_API_USER.getRoleId())) { - shouldRemove = true; - roleDetails.remove(Role.INTERNAL_API_USER.getRoleId()); - break; - } - } - - // Also check if user is part of the Internal team - if (user.getTeam() != null - && TeamService.INTERNAL_TEAM_NAME.equals(user.getTeam().getName())) { - shouldRemove = true; - } - - // Remove the user if either condition is true - if (shouldRemove) { - iterator.remove(); - continue; - } - // Determine the user's session status and last request time - int maxInactiveInterval = sessionPersistentRegistry.getMaxInactiveInterval(); - boolean hasActiveSession = false; - Instant lastRequest = null; - Optional latestSession = - sessionPersistentRegistry.findLatestSession(user.getUsername()); - if (latestSession.isPresent()) { - SessionEntity sessionEntity = latestSession.get(); - // sessionEntity stores Instant directly - Instant lastAccessedTime = - Optional.ofNullable(sessionEntity.getLastRequest()) - .orElse(Instant.EPOCH); - - Instant now = Instant.now(); - // Calculate session expiration and update session status accordingly - Instant expirationTime = - lastAccessedTime.plus(maxInactiveInterval, ChronoUnit.SECONDS); - if (now.isAfter(expirationTime)) { - sessionPersistentRegistry.expireSession(sessionEntity.getSessionId()); - } else { - hasActiveSession = !sessionEntity.isExpired(); - } - lastRequest = lastAccessedTime; - } else { - // No session, set default last request time - lastRequest = Instant.EPOCH; - } - userSessions.put(user.getUsername(), hasActiveSession); - userLastRequest.put(user.getUsername(), lastRequest); - if (hasActiveSession) { - activeUsers++; - } - if (!user.isEnabled()) { - disabledUsers++; - } - } - } - // Sort users by active status and last request date - List sortedUsers = - allUsers.stream() - .sorted( - (u1, u2) -> { - boolean u1Active = - userSessions.getOrDefault(u1.getUsername(), false); - boolean u2Active = - userSessions.getOrDefault(u2.getUsername(), false); - if (u1Active && !u2Active) { - return -1; - } else if (!u1Active && u2Active) { - return 1; - } else { - Instant u1LastRequest = - userLastRequest.getOrDefault( - u1.getUsername(), Instant.EPOCH); - Instant u2LastRequest = - userLastRequest.getOrDefault( - u2.getUsername(), Instant.EPOCH); - return u2LastRequest.compareTo(u1LastRequest); - } - }) - .toList(); - String messageType = request.getParameter("messageType"); - - String deleteMessage; - if (messageType != null) { - deleteMessage = - switch (messageType) { - case "deleteCurrentUser" -> "deleteCurrentUserMessage"; - case "deleteUsernameExists" -> "deleteUsernameExistsMessage"; - default -> null; - }; - - model.addAttribute("deleteMessage", deleteMessage); - - String addMessage; - addMessage = - switch (messageType) { - case "usernameExists" -> "usernameExistsMessage"; - case "invalidUsername" -> "invalidUsernameMessage"; - case "invalidPassword" -> "invalidPasswordMessage"; - default -> null; - }; - model.addAttribute("addMessage", addMessage); - } - - String changeMessage; - if (messageType != null) { - changeMessage = - switch (messageType) { - case "userNotFound" -> "userNotFoundMessage"; - case "downgradeCurrentUser" -> "downgradeCurrentUserMessage"; - case "disabledCurrentUser" -> "disabledCurrentUserMessage"; - case "cannotMoveInternalUsers" -> "team.cannotMoveInternalUsers"; - case "internalTeamNotAccessible" -> "team.internalTeamNotAccessible"; - case "invalidRole" -> "invalidRoleMessage"; - default -> messageType; - }; - model.addAttribute("changeMessage", changeMessage); - } - - model.addAttribute("users", sortedUsers); - model.addAttribute("currentUsername", authentication.getName()); - model.addAttribute("roleDetails", roleDetails); - model.addAttribute("userSessions", userSessions); - model.addAttribute("userLastRequest", userLastRequest); - model.addAttribute("totalUsers", allUsers.size()); - model.addAttribute("activeUsers", activeUsers); - model.addAttribute("disabledUsers", disabledUsers); - - // Get all teams but filter out the Internal team - List allTeams = - teamRepository.findAll().stream() - .filter( - team -> - !stirling.software.proprietary.security.service.TeamService - .INTERNAL_TEAM_NAME - .equals(team.getName())) - .toList(); - model.addAttribute("teams", allTeams); - - model.addAttribute("maxPaidUsers", applicationProperties.getPremium().getMaxUsers()); - return "adminSettings"; - } - - // @PreAuthorize("!hasAuthority('ROLE_DEMO_USER')") - // @GetMapping("/account") - public String account(HttpServletRequest request, Model model, Authentication authentication) { - if (authentication == null || !authentication.isAuthenticated()) { - return "redirect:/"; - } - if (authentication.isAuthenticated()) { - Object principal = authentication.getPrincipal(); - String username = null; - - // Retrieve username and other attributes and add login attributes to the model - if (principal instanceof UserDetails detailsUser) { - username = detailsUser.getUsername(); - model.addAttribute("oAuth2Login", false); - } - if (principal instanceof OAuth2User oAuth2User) { - username = oAuth2User.getName(); - model.addAttribute("oAuth2Login", true); - } - if (principal instanceof CustomSaml2AuthenticatedPrincipal saml2User) { - username = saml2User.name(); - model.addAttribute("saml2Login", true); - } - if (username != null) { - // Fetch user details from the database - Optional user = userRepository.findByUsernameIgnoreCaseWithSettings(username); - - if (user.isEmpty()) { - return "redirect:/error"; - } - - // Convert settings map to JSON string - ObjectMapper objectMapper = new ObjectMapper(); - String settingsJson; - try { - settingsJson = objectMapper.writeValueAsString(user.get().getSettings()); - } catch (JsonProcessingException e) { - log.error("Error converting settings map", e); - return "redirect:/error"; - } - - String messageType = request.getParameter("messageType"); - if (messageType != null) { - switch (messageType) { - case "notAuthenticated" -> messageType = "notAuthenticatedMessage"; - case "userNotFound" -> messageType = "userNotFoundMessage"; - case "incorrectPassword" -> messageType = "incorrectPasswordMessage"; - case "usernameExists" -> messageType = "usernameExistsMessage"; - case "invalidUsername" -> messageType = "invalidUsernameMessage"; - } - } - - model.addAttribute("username", username); - model.addAttribute("messageType", messageType); - model.addAttribute("role", user.get().getRolesAsString()); - model.addAttribute("settings", settingsJson); - model.addAttribute("changeCredsFlag", user.get().isFirstLogin()); - model.addAttribute("currentPage", "account"); - } - } else { - return "redirect:/"; - } - return "account"; - } - - // @PreAuthorize("!hasAuthority('ROLE_DEMO_USER')") - // @GetMapping("/change-creds") - public String changeCreds( - HttpServletRequest request, Model model, Authentication authentication) { - if (authentication == null || !authentication.isAuthenticated()) { - return "redirect:/"; - } - if (authentication.isAuthenticated()) { - Object principal = authentication.getPrincipal(); - if (principal instanceof UserDetails detailsUser) { - String username = detailsUser.getUsername(); - // Fetch user details from the database - Optional user = userRepository.findByUsernameIgnoreCase(username); - if (user.isEmpty()) { - // Handle error appropriately, example redirection in case of error - return "redirect:/error"; - } - String messageType = request.getParameter("messageType"); - if (messageType != null) { - switch (messageType) { - case "notAuthenticated": - messageType = "notAuthenticatedMessage"; - break; - case "userNotFound": - messageType = "userNotFoundMessage"; - break; - case "incorrectPassword": - messageType = "incorrectPasswordMessage"; - break; - case "usernameExists": - messageType = "usernameExistsMessage"; - break; - default: - break; - } - model.addAttribute("messageType", messageType); - } - - model.addAttribute("username", username); - } - } else { - return "redirect:/"; - } - return "change-creds"; - } -} diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/controller/web/DatabaseWebController.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/controller/web/DatabaseWebController.java deleted file mode 100644 index ee33be0b9..000000000 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/controller/web/DatabaseWebController.java +++ /dev/null @@ -1,45 +0,0 @@ -package stirling.software.proprietary.security.controller.web; - -import java.util.List; - -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.security.core.Authentication; -import org.springframework.ui.Model; - -import io.swagger.v3.oas.annotations.tags.Tag; - -import jakarta.servlet.http.HttpServletRequest; - -import lombok.RequiredArgsConstructor; - -import stirling.software.common.model.FileInfo; -import stirling.software.proprietary.security.service.DatabaseService; - -// @Controller // Disabled - Backend-only mode, no Thymeleaf UI -@Tag(name = "Database Management", description = "Database management and security APIs") -@RequiredArgsConstructor -public class DatabaseWebController { - - private final DatabaseService databaseService; - - @Deprecated - @PreAuthorize("hasRole('ROLE_ADMIN')") - // @GetMapping("/database") - public String database(HttpServletRequest request, Model model, Authentication authentication) { - String error = request.getParameter("error"); - String confirmed = request.getParameter("infoMessage"); - if (error != null) { - model.addAttribute("error", error); - } else if (confirmed != null) { - model.addAttribute("infoMessage", confirmed); - } - List backupList = databaseService.getBackupList(); - model.addAttribute("backupFiles", backupList); - String dbVersion = databaseService.getH2Version(); - model.addAttribute("databaseVersion", dbVersion); - if ("Unknown".equalsIgnoreCase(dbVersion)) { - model.addAttribute("infoMessage", "notSupported"); - } - return "database"; - } -} diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/controller/web/TeamWebController.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/controller/web/TeamWebController.java deleted file mode 100644 index 320d69fd5..000000000 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/controller/web/TeamWebController.java +++ /dev/null @@ -1,145 +0,0 @@ -package stirling.software.proprietary.security.controller.web; - -import java.time.Instant; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.ui.Model; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; - -import jakarta.servlet.http.HttpServletRequest; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; - -import stirling.software.proprietary.model.Team; -import stirling.software.proprietary.model.dto.TeamWithUserCountDTO; -import stirling.software.proprietary.security.database.repository.SessionRepository; -import stirling.software.proprietary.security.database.repository.UserRepository; -import stirling.software.proprietary.security.model.User; -import stirling.software.proprietary.security.repository.TeamRepository; -import stirling.software.proprietary.security.service.TeamService; - -// @Controller // Disabled - Backend-only mode, no Thymeleaf UI -@RequestMapping("/teams") -@RequiredArgsConstructor -@Slf4j -public class TeamWebController { - - private final TeamRepository teamRepository; - private final SessionRepository sessionRepository; - private final UserRepository userRepository; - - @Deprecated - // @GetMapping - @PreAuthorize("hasRole('ROLE_ADMIN')") - public String listTeams(HttpServletRequest request, Model model) { - // Get teams with user counts using a DTO projection - List allTeamsWithCounts = teamRepository.findAllTeamsWithUserCount(); - - // Filter out the Internal team - List teamsWithCounts = - allTeamsWithCounts.stream() - .filter(team -> !TeamService.INTERNAL_TEAM_NAME.equals(team.getName())) - .toList(); - - // Get the latest activity for each team - List teamActivities = sessionRepository.findLatestActivityByTeam(); - - // Convert the query results to a map for easy access in the view - Map teamLastRequest = new HashMap<>(); - for (Object[] result : teamActivities) { - Long teamId = (Long) result[0]; // teamId alias - Instant lastActivity = (Instant) result[1]; // lastActivity alias - teamLastRequest.put(teamId, lastActivity); - } - - String messageType = request.getParameter("messageType"); - if (messageType != null) { - if ("teamCreated".equals(messageType)) { - model.addAttribute("addMessage", "teamCreated"); - } else if ("teamExists".equals(messageType)) { - model.addAttribute("errorMessage", "teamExists"); - } else if ("teamNotFound".equals(messageType)) { - model.addAttribute("errorMessage", "teamNotFound"); - } else if ("teamNameExists".equals(messageType)) { - model.addAttribute("errorMessage", "teamNameExists"); - } else if ("internalTeamNotAccessible".equals(messageType)) { - model.addAttribute("errorMessage", "team.internalTeamNotAccessible"); - } else if ("teamRenamed".equals(messageType)) { - model.addAttribute("changeMessage", "teamRenamed"); - } else if ("teamHasUsers".equals(messageType)) { - model.addAttribute("errorMessage", "teamHasUsers"); - } else if ("teamDeleted".equals(messageType)) { - model.addAttribute("deleteMessage", "teamDeleted"); - } - } - - // Add data to the model - model.addAttribute("teamsWithCounts", teamsWithCounts); - model.addAttribute("teamLastRequest", teamLastRequest); - - return "accounts/teams"; - } - - @Deprecated - // @GetMapping("/{id}") - @PreAuthorize("hasRole('ROLE_ADMIN')") - public String viewTeamDetails( - HttpServletRequest request, @PathVariable("id") Long id, Model model) { - // Get the team - Team team = - teamRepository - .findById(id) - .orElseThrow(() -> new RuntimeException("Team not found")); - - // Prevent access to Internal team - if (TeamService.INTERNAL_TEAM_NAME.equals(team.getName())) { - return "redirect:/teams?error=internalTeamNotAccessible"; - } - - // Get users for this team directly using the direct query - List teamUsers = userRepository.findAllByTeamId(id); - - // Get all users not in this team for the Add User to Team dropdown - // Exclude users that are in the Internal team - List allUsers = userRepository.findAllWithTeam(); - List availableUsers = - allUsers.stream() - .filter( - user -> - (user.getTeam() == null - || !user.getTeam().getId().equals(id)) - && (user.getTeam() == null - || !TeamService.INTERNAL_TEAM_NAME.equals( - user.getTeam().getName()))) - .toList(); - - // Get the latest session for each user in the team - List userSessions = sessionRepository.findLatestSessionByTeamId(id); - - // Create a map of username to last request date - Map userLastRequest = new HashMap<>(); - for (Object[] result : userSessions) { - String username = (String) result[0]; // username alias - Instant lastRequest = (Instant) result[1]; // lastRequest alias - userLastRequest.put(username, lastRequest); - } - - String errorMessage = request.getParameter("error"); - if (errorMessage != null) { - if ("cannotMoveInternalUsers".equals(errorMessage)) { - model.addAttribute("errorMessage", "team.cannotMoveInternalUsers"); - } - } - - model.addAttribute("team", team); - model.addAttribute("teamUsers", teamUsers); - model.addAttribute("availableUsers", availableUsers); - model.addAttribute("userLastRequest", userLastRequest); - return "accounts/team-details"; - } -} diff --git a/app/proprietary/src/main/resources/static/css/audit-dashboard.css b/app/proprietary/src/main/resources/static/css/audit-dashboard.css deleted file mode 100644 index 51531c04e..000000000 --- a/app/proprietary/src/main/resources/static/css/audit-dashboard.css +++ /dev/null @@ -1,239 +0,0 @@ -.dashboard-card { - margin-bottom: 20px; - border-radius: 8px; - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); - background-color: var(--md-sys-color-surface-container); - color: var(--md-sys-color-on-surface); - border: 1px solid var(--md-sys-color-outline-variant); -} - -.card-header { - background-color: var(--md-sys-color-surface-container-high); - color: var(--md-sys-color-on-surface); - border-bottom: 1px solid var(--md-sys-color-outline-variant); -} - -.card-body { - background-color: var(--md-sys-color-surface-container); -} -.stat-card { - text-align: center; - padding: 20px; -} -.stat-number { - font-size: 2rem; - font-weight: bold; -} -.stat-label { - font-size: 1rem; - color: var(--md-sys-color-on-surface-variant); -} -.chart-container { - position: relative; - height: 300px; - width: 100%; -} -.filter-card { - margin-bottom: 20px; - padding: 15px; - background-color: var(--md-sys-color-surface-container-low); - border: 1px solid var(--md-sys-color-outline-variant); - border-radius: 4px; -} -.loading-overlay { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - background-color: var(--md-sys-color-surface-container-high, rgba(229, 232, 241, 0.8)); - display: flex; - justify-content: center; - align-items: center; - z-index: 1000; -} -.level-indicator { - display: inline-block; - padding: 5px 10px; - border-radius: 15px; - color: white; - font-weight: bold; -} -.level-0 { - background-color: var(--md-sys-color-error, #dc3545); /* Red */ -} -.level-1 { - background-color: var(--md-sys-color-secondary, #fd7e14); /* Orange */ -} -.level-2 { - background-color: var(--md-nav-section-color-other, #28a745); /* Green */ -} -.level-3 { - background-color: var(--md-sys-color-tertiary, #17a2b8); /* Teal */ -} -/* Custom data table styling */ -.audit-table { - font-size: 0.9rem; - color: var(--md-sys-color-on-surface); - border-color: var(--md-sys-color-outline-variant); -} - -.audit-table tbody tr { - background-color: var(--md-sys-color-surface-container-low); -} - -.audit-table tbody tr:nth-child(even) { - background-color: var(--md-sys-color-surface-container); -} - -.audit-table tbody tr:hover { - background-color: var(--md-sys-color-surface-container-high); -} -.audit-table th { - background-color: var(--md-sys-color-surface-container-high); - color: var(--md-sys-color-on-surface); - position: sticky; - top: 0; - z-index: 10; - font-weight: bold; -} -.table-responsive { - max-height: 600px; -} -.pagination-container { - display: flex; - justify-content: space-between; - align-items: center; - margin-top: 15px; - padding: 10px 0; - border-top: 1px solid var(--md-sys-color-outline-variant); - color: var(--md-sys-color-on-surface); -} - -.pagination .page-item.active .page-link { - background-color: var(--bs-primary); - border-color: var(--bs-primary); - color: white; -} - -.pagination .page-link { - color: var(--bs-primary); -} - -.pagination .page-link.disabled { - pointer-events: none; - color: var(--bs-secondary); - background-color: var(--bs-light); -} -.json-viewer { - background-color: var(--md-sys-color-surface-container-low); - color: var(--md-sys-color-on-surface); - border-radius: 4px; - padding: 15px; - max-height: 350px; - overflow-y: auto; - font-family: monospace; - font-size: 0.9rem; - white-space: pre-wrap; - border: 1px solid var(--md-sys-color-outline-variant); - margin-top: 10px; -} - -/* Simple, minimal radio styling - no extras */ -.form-check { - padding: 8px 0; -} - -#debug-console { - position: fixed; - bottom: 0; - right: 0; - width: 400px; - height: 200px; - background: var(--md-sys-color-surface-container-highest, rgba(0,0,0,0.8)); - color: var(--md-sys-color-tertiary, #0f0); - font-family: monospace; - font-size: 12px; - z-index: 9999; - overflow-y: auto; - padding: 10px; - border: 1px solid var(--md-sys-color-outline); - display: none; /* Changed to none by default, enable with key command */ -} - -/* Enhanced styling for radio buttons as buttons */ -label.btn-outline-primary { - cursor: pointer; - transition: all 0.2s; - border-color: var(--md-sys-color-primary); - color: var(--md-sys-color-primary); -} - -label.btn-outline-primary.active { - background-color: var(--md-sys-color-primary); - color: var(--md-sys-color-on-primary); - border-color: var(--md-sys-color-primary); -} - -label.btn-outline-primary input[type="radio"] { - cursor: pointer; -} - -/* Modal overrides for dark mode */ -.modal-content { - background-color: var(--md-sys-color-surface-container); - color: var(--md-sys-color-on-surface); - border-color: var(--md-sys-color-outline); -} - -.modal-header { - border-bottom-color: var(--md-sys-color-outline-variant); -} - -.modal-footer { - border-top-color: var(--md-sys-color-outline-variant); -} - -/* Improved modal positioning */ -.modal-dialog-centered { - display: flex; - align-items: center; - min-height: calc(100% - 3.5rem); -} - -.modal { - z-index: 1050; -} - -/* Button overrides for theme consistency */ -.btn-outline-primary { - color: var(--md-sys-color-primary); - border-color: var(--md-sys-color-primary); -} - -.btn-outline-primary:hover { - background-color: var(--md-sys-color-primary); - color: var(--md-sys-color-on-primary); -} - -.btn-outline-secondary { - color: var(--md-sys-color-secondary); - border-color: var(--md-sys-color-secondary); -} - -.btn-outline-secondary:hover { - background-color: var(--md-sys-color-secondary); - color: var(--md-sys-color-on-secondary); -} - -.btn-primary { - background-color: var(--md-sys-color-primary); - color: var(--md-sys-color-on-primary); - border-color: var(--md-sys-color-primary); -} - -.btn-secondary { - background-color: var(--md-sys-color-secondary); - color: var(--md-sys-color-on-secondary); - border-color: var(--md-sys-color-secondary); -} \ No newline at end of file diff --git a/app/proprietary/src/main/resources/static/css/modern-tables.css b/app/proprietary/src/main/resources/static/css/modern-tables.css deleted file mode 100644 index 156c39fed..000000000 --- a/app/proprietary/src/main/resources/static/css/modern-tables.css +++ /dev/null @@ -1,394 +0,0 @@ -/* modern-tables.css - Professional styling for data tables and related elements */ - -/* Main container - Reduced max-width from 1100px to 900px */ -.data-container { - max-width: 900px; - margin: 2rem auto; - background-color: var(--md-sys-color-surface-container-lowest); - border-radius: 1rem; - padding: 0.5rem; - box-shadow: 0 2px 12px rgba(var(--md-sys-color-shadow, 0, 0, 0), 0.05); -} - -/* Panel / Card */ -.data-panel { - background-color: var(--md-sys-color-surface); - border-radius: 0.75rem; - box-shadow: 0 2px 8px rgba(var(--md-sys-color-shadow, 0, 0, 0), 0.08); - overflow: hidden; -} - -/* Header */ -.data-header { - display: flex; - align-items: center; - padding: 1.25rem 1.5rem; - background-color: var(--md-sys-color-surface-variant); - border-bottom: 1px solid var(--md-sys-color-outline-variant); -} - -.data-title { - margin: 0; - font-size: 1.5rem; - font-weight: 600; - display: flex; - align-items: center; - gap: 0.75rem; -} - -.data-icon { - display: flex; - align-items: center; - justify-content: center; - width: 2.5rem; - height: 2.5rem; - background-color: var(--md-sys-color-primary); - color: var(--md-sys-color-on-primary); - border-radius: 0.5rem; - transition: all 0.2s ease; -} - -/* Content area */ -.data-body { - padding: 1.5rem; - background-color: var(--md-sys-color-surface-container-low); - border-radius: 0.5rem; -} - -/* Action buttons container */ -.data-actions { - display: flex; - justify-content: center; - margin: 1rem 0 1.5rem; - gap: 0.75rem; -} - -/* Can add these classes for different alignments */ -.data-actions-start { - justify-content: flex-start; -} - -.data-actions-end { - justify-content: flex-end; -} - -/* Button styling */ -.data-btn { - display: inline-flex; - align-items: center; - gap: 0.5rem; - padding: 0.625rem 1.25rem; - border-radius: 0.5rem; - font-weight: 500; - transition: all 0.2s ease; - border: none; - cursor: pointer; - text-decoration: none; -} - -/* Fixed button colors - normal state has more contrast now */ -.data-btn-primary { - background-color: var(--md-sys-color-primary); - color: var(--md-sys-color-on-primary); -} - -.data-btn-primary:hover { - background-color: var(--md-sys-color-primary-container); - color: var(--md-sys-color-primary); - box-shadow: 0 2px 4px rgba(var(--md-sys-color-shadow, 0, 0, 0), 0.1); -} - -.data-btn-secondary { - background-color: var(--md-sys-color-secondary); - color: var(--md-sys-color-on-secondary); -} - -.data-btn-secondary:hover { - background-color: var(--md-sys-color-secondary-container); - color: var(--md-sys-color-secondary); - box-shadow: 0 2px 4px rgba(var(--md-sys-color-shadow, 0, 0, 0), 0.1); -} - -.data-btn-danger { - background-color: var(--md-sys-color-error); - color: var(--md-sys-color-on-error); -} - -.data-btn-danger:hover { - background-color: var(--md-sys-color-error-container); - color: var(--md-sys-color-error); - box-shadow: 0 2px 4px rgba(var(--md-sys-color-shadow, 0, 0, 0), 0.1); -} - -.data-btn-sm { - padding: 0.375rem 0.75rem; - font-size: 0.875rem; -} - -/* Icon button */ -.data-icon-btn { - display: inline-flex; - align-items: center; - justify-content: center; - width: 2.25rem; - height: 2.25rem; - border-radius: 0.5rem; - border: none; - cursor: pointer; - transition: all 0.2s ease; - background-color: transparent; -} - -/* Fixed icon button colors */ -.data-icon-btn-primary { - background-color: var(--md-sys-color-primary); - color: var(--md-sys-color-on-primary); -} - -.data-icon-btn-primary:hover { - background-color: var(--md-sys-color-primary-container); - color: var(--md-sys-color-primary); - box-shadow: 0 2px 4px rgba(var(--md-sys-color-shadow, 0, 0, 0), 0.1); -} - -.data-icon-btn-danger { - background-color: var(--md-sys-color-error); - color: var(--md-sys-color-on-error); -} - -.data-icon-btn-danger:hover { - background-color: var(--md-sys-color-error-container); - color: var(--md-sys-color-error); - box-shadow: 0 2px 4px rgba(var(--md-sys-color-shadow, 0, 0, 0), 0.1); -} - -/* Table styling */ -.data-table { - width: 100%; - border-collapse: separate; - border-spacing: 0; -} - -.data-table th { - text-align: left; - padding: 1rem; - background-color: var(--md-sys-color-surface-variant); - color: var(--md-sys-color-on-surface-variant); - font-weight: 600; - position: sticky; - top: 0; -} - -.data-table th:first-child { - border-top-left-radius: 0.5rem; -} - -.data-table th:last-child { - border-top-right-radius: 0.5rem; -} - -.data-table td { - padding: 1rem; - border-bottom: 1px solid var(--md-sys-color-outline-variant); -} - -.data-table tr:last-child td { - border-bottom: none; -} - -.data-table tr:hover { - background-color: rgba(var(--md-sys-color-surface-variant-rgb), 0.5); -} - -/* Table action cells */ -.data-action-cell { - display: flex; - align-items: center; - gap: 0.5rem; - justify-content: flex-start; -} - -.data-action-cell-center { - justify-content: center; -} - -.data-action-cell-end { - justify-content: flex-end; -} - -/* Status indicators */ -.data-status { - display: inline-flex; - align-items: center; - gap: 0.375rem; - padding: 0.375rem 0.75rem; - border-radius: 1rem; - font-size: 0.875rem; - font-weight: 500; -} - -.data-status-success { - background-color: var(--md-sys-color-tertiary-container); - color: var(--md-sys-color-tertiary); -} - -.data-status-danger { - background-color: var(--md-sys-color-error-container); - color: var(--md-sys-color-error); -} - -.data-status-warning { - background-color: var(--md-sys-color-secondary-container); - color: var(--md-sys-color-secondary); -} - -.data-status-info { - background-color: var(--md-sys-color-primary-container); - color: var(--md-sys-color-primary); -} - -/* Stats/Info container */ -.data-stats { - display: flex; - gap: 1.5rem; - margin-bottom: 1.5rem; - flex-wrap: wrap; -} - -.data-stat-card { - background-color: var(--md-sys-color-surface-variant); - border-radius: 0.5rem; - padding: 1.25rem; - flex: 1; - min-width: 180px; - box-shadow: 0 2px 8px rgba(var(--md-sys-color-shadow, 0, 0, 0), 0.05); -} - -.data-stat-label { - font-size: 0.875rem; - color: var(--md-sys-color-on-surface-variant); - margin-bottom: 0.5rem; -} - -.data-stat-value { - font-size: 1.75rem; - font-weight: 700; - color: var(--md-sys-color-on-surface); -} - -/* Section title */ -.data-section-title { - font-size: 1.25rem; - font-weight: 600; - margin: 1.5rem 0 1rem; - padding-bottom: 0.5rem; - border-bottom: 1px solid var(--md-sys-color-outline-variant); -} - -/* Empty state styling */ -.data-empty { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - padding: 3rem; - color: var(--md-sys-color-on-surface-variant); -} - -.data-empty-icon { - font-size: 4rem; - margin-bottom: 1rem; - opacity: 0.7; -} - -.data-empty-text { - font-size: 1.125rem; - margin-bottom: 1.5rem; -} - -/* Modal styling */ -.data-modal { - border-radius: 0.75rem; - overflow: hidden; -} - -.data-modal-header { - background-color: var(--md-sys-color-surface-variant); - padding: 1.25rem; - border-bottom: 1px solid var(--md-sys-color-outline-variant); - display: flex; - align-items: center; - justify-content: space-between; -} - -.data-modal-title { - margin: 0; - font-size: 1.25rem; - font-weight: 600; - display: flex; - align-items: center; - gap: 0.75rem; -} - -/* Modal close button styling */ -.data-btn-close { - display: flex; - align-items: center; - justify-content: center; - width: 2rem; - height: 2rem; - border-radius: 50%; - background-color: var(--md-sys-color-surface-variant); - color: var(--md-sys-color-on-surface-variant); - border: none; - cursor: pointer; - transition: all 0.2s ease; - padding: 0; - margin: 0; -} - -.data-btn-close:hover { - background-color: var(--md-sys-color-surface-container-high); - color: var(--md-sys-color-on-surface); -} - -.data-btn-close .material-symbols-rounded { - font-size: 1.25rem; -} - -.data-modal-body { - padding: 1.5rem; -} - -.data-modal-footer { - padding: 1rem 1.5rem; - border-top: 1px solid var(--md-sys-color-outline-variant); - display: flex; - justify-content: flex-end; - gap: 0.75rem; -} - -/* Form elements */ -.data-form-group { - margin-bottom: 1.25rem; -} - -.data-form-label { - display: block; - margin-bottom: 0.5rem; - font-weight: 500; -} - -.data-form-control { - width: 100%; - padding: 0.75rem 1rem; - border-radius: 0.5rem; - border: 1px solid -} - -.text-overflow { - max-width: 100px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} \ No newline at end of file diff --git a/app/proprietary/src/main/resources/static/js/audit/dashboard.js b/app/proprietary/src/main/resources/static/js/audit/dashboard.js deleted file mode 100644 index 35f0ab3d5..000000000 --- a/app/proprietary/src/main/resources/static/js/audit/dashboard.js +++ /dev/null @@ -1,999 +0,0 @@ -// Initialize variables -let currentPage = 0; -let pageSize = 20; -let totalPages = 0; -let typeFilter = ''; -let principalFilter = ''; -let startDateFilter = ''; -let endDateFilter = ''; - -// Charts -let typeChart; -let userChart; -let timeChart; - -// DOM elements - will properly initialize these during page load -let auditTableBody; -let pageSizeSelect; -let typeFilterInput; -let exportTypeFilterInput; -let principalFilterInput; -let startDateFilterInput; -let endDateFilterInput; -let applyFiltersButton; -let resetFiltersButton; - - -// Initialize page -// Theme change listener to redraw charts when theme changes -function setupThemeChangeListener() { - // Watch for theme changes (usually by a class on body or html element) - const observer = new MutationObserver(function(mutations) { - mutations.forEach(function(mutation) { - if (mutation.attributeName === 'data-bs-theme' || mutation.attributeName === 'class') { - // Redraw charts with new theme colors if they exist - if (typeChart && userChart && timeChart) { - // If we have stats data cached, use it - if (window.cachedStatsData) { - renderCharts(window.cachedStatsData); - } - } - } - }); - }); - - // Observe the document element for theme changes - observer.observe(document.documentElement, { attributes: true }); - - // Also observe body for class changes - observer.observe(document.body, { attributes: true }); -} - -document.addEventListener('DOMContentLoaded', function() { - // Initialize DOM references - auditTableBody = document.getElementById('auditTableBody'); - pageSizeSelect = document.getElementById('pageSizeSelect'); - typeFilterInput = document.getElementById('typeFilter'); - exportTypeFilterInput = document.getElementById('exportTypeFilter'); - principalFilterInput = document.getElementById('principalFilter'); - startDateFilterInput = document.getElementById('startDateFilter'); - endDateFilterInput = document.getElementById('endDateFilter'); - applyFiltersButton = document.getElementById('applyFilters'); - resetFiltersButton = document.getElementById('resetFilters'); - - // Load event types for dropdowns - loadEventTypes(); - - // Show a loading message immediately - if (auditTableBody) { - auditTableBody.innerHTML = - '
    ' + window.i18n.loading + ''; - } - - // Make a direct API call first to avoid validation issues - loadAuditData(0, pageSize); - - // Load statistics for dashboard - loadStats(7); - - // Setup theme change listener - setupThemeChangeListener(); - - // Set up event listeners - pageSizeSelect.addEventListener('change', function() { - pageSize = parseInt(this.value); - window.originalPageSize = pageSize; - currentPage = 0; - window.requestedPage = 0; - loadAuditData(0, pageSize); - }); - - applyFiltersButton.addEventListener('click', function() { - typeFilter = typeFilterInput.value.trim(); - principalFilter = principalFilterInput.value.trim(); - startDateFilter = startDateFilterInput.value; - endDateFilter = endDateFilterInput.value; - currentPage = 0; - window.requestedPage = 0; - loadAuditData(0, pageSize); - }); - - resetFiltersButton.addEventListener('click', function() { - // Reset input fields - typeFilterInput.value = ''; - principalFilterInput.value = ''; - startDateFilterInput.value = ''; - endDateFilterInput.value = ''; - - // Reset filter variables - typeFilter = ''; - principalFilter = ''; - startDateFilter = ''; - endDateFilter = ''; - - // Reset page - currentPage = 0; - window.requestedPage = 0; - - // Update UI - document.getElementById('currentPage').textContent = '1'; - - // Load data with reset filters - loadAuditData(0, pageSize); - }); - - // Reset export filters button - document.getElementById('resetExportFilters').addEventListener('click', function() { - exportTypeFilter.value = ''; - exportPrincipalFilter.value = ''; - exportStartDateFilter.value = ''; - exportEndDateFilter.value = ''; - }); - - // Make radio buttons behave like toggle buttons - const radioLabels = document.querySelectorAll('label.btn-outline-primary'); - radioLabels.forEach(label => { - const radio = label.querySelector('input[type="radio"]'); - - if (radio) { - // Highlight the checked radio button's label - if (radio.checked) { - label.classList.add('active'); - } - - // Handle clicking on the label - label.addEventListener('click', function() { - // Remove active class from all labels - radioLabels.forEach(l => l.classList.remove('active')); - - // Add active class to this label - this.classList.add('active'); - - // Check this radio button - radio.checked = true; - }); - } - }); - - // Handle export button - exportButton.onclick = function(e) { - e.preventDefault(); - - // Get selected format with fallback - const selectedRadio = document.querySelector('input[name="exportFormat"]:checked'); - const exportFormat = selectedRadio ? selectedRadio.value : 'csv'; - exportAuditData(exportFormat); - return false; - }; - - // Set up pagination buttons - document.getElementById('page-first').onclick = function() { - if (currentPage > 0) { - goToPage(0); - } - return false; - }; - - document.getElementById('page-prev').onclick = function() { - if (currentPage > 0) { - goToPage(currentPage - 1); - } - return false; - }; - - document.getElementById('page-next').onclick = function() { - if (currentPage < totalPages - 1) { - goToPage(currentPage + 1); - } - return false; - }; - - document.getElementById('page-last').onclick = function() { - if (totalPages > 0 && currentPage < totalPages - 1) { - goToPage(totalPages - 1); - } - return false; - }; - - // Set up tab change events - const tabEls = document.querySelectorAll('button[data-bs-toggle="tab"]'); - tabEls.forEach(tabEl => { - tabEl.addEventListener('shown.bs.tab', function (event) { - const targetId = event.target.getAttribute('data-bs-target'); - if (targetId === '#dashboard') { - // Redraw charts when dashboard tab is shown - if (typeChart) typeChart.update(); - if (userChart) userChart.update(); - if (timeChart) timeChart.update(); - } - }); - }); -}); - -// Load audit data from server -function loadAuditData(targetPage, realPageSize) { - const requestedPage = targetPage !== undefined ? targetPage : window.requestedPage || 0; - realPageSize = realPageSize || pageSize; - - showLoading('table-loading'); - - // Always request page 0 from server, but with increased page size if needed - let url = `/api/v1/audit/data?page=${requestedPage}&size=${realPageSize}`; - - if (typeFilter) url += `&type=${encodeURIComponent(typeFilter)}`; - if (principalFilter) url += `&principal=${encodeURIComponent(principalFilter)}`; - if (startDateFilter) url += `&startDate=${startDateFilter}`; - if (endDateFilter) url += `&endDate=${endDateFilter}`; - - // Update page indicator - if (document.getElementById('page-indicator')) { - document.getElementById('page-indicator').textContent = `Page ${requestedPage + 1} of ?`; - } - - fetchWithCsrf(url) - .then(response => { - return response.json(); - }) - .then(data => { - - - // Calculate the correct slice of data to show for the requested page - let displayContent = data.content; - - // Render the correct slice of data - renderTable(displayContent); - - // Calculate total pages based on the actual total elements - const calculatedTotalPages = Math.ceil(data.totalElements / realPageSize); - totalPages = calculatedTotalPages; - currentPage = requestedPage; // Use our tracked page, not server's - - - // Update UI - document.getElementById('currentPage').textContent = currentPage + 1; - document.getElementById('totalPages').textContent = totalPages; - document.getElementById('totalRecords').textContent = data.totalElements; - if (document.getElementById('page-indicator')) { - document.getElementById('page-indicator').textContent = `Page ${currentPage + 1} of ${totalPages}`; - } - - // Re-enable buttons with correct state - document.getElementById('page-first').disabled = currentPage === 0; - document.getElementById('page-prev').disabled = currentPage === 0; - document.getElementById('page-next').disabled = currentPage >= totalPages - 1; - document.getElementById('page-last').disabled = currentPage >= totalPages - 1; - - hideLoading('table-loading'); - - // Restore original page size for next operations - if (window.originalPageSize && realPageSize !== window.originalPageSize) { - pageSize = window.originalPageSize; - - } - - // Store original page size for recovery - window.originalPageSize = realPageSize; - - // Clear busy flag - window.paginationBusy = false; - - }) - .catch(error => { - - if (auditTableBody) { - auditTableBody.innerHTML = `${window.i18n.errorLoading} ${error.message}`; - } - hideLoading('table-loading'); - - // Re-enable buttons - document.getElementById('page-first').disabled = false; - document.getElementById('page-prev').disabled = false; - document.getElementById('page-next').disabled = false; - document.getElementById('page-last').disabled = false; - - // Clear busy flag - window.paginationBusy = false; - }); -} - -// Load statistics for charts -function loadStats(days) { - showLoading('type-chart-loading'); - showLoading('user-chart-loading'); - showLoading('time-chart-loading'); - - fetchWithCsrf(`/api/v1/audit/stats?days=${days}`) - .then(response => response.json()) - .then(data => { - document.getElementById('total-events').textContent = data.totalEvents; - // Cache stats data for theme changes - window.cachedStatsData = data; - renderCharts(data); - hideLoading('type-chart-loading'); - hideLoading('user-chart-loading'); - hideLoading('time-chart-loading'); - }) - .catch(error => { - console.error('Error loading stats:', error); - hideLoading('type-chart-loading'); - hideLoading('user-chart-loading'); - hideLoading('time-chart-loading'); - }); -} - -// Export audit data -function exportAuditData(format) { - const type = exportTypeFilter.value.trim(); - const principal = exportPrincipalFilter.value.trim(); - const startDate = exportStartDateFilter.value; - const endDate = exportEndDateFilter.value; - - let url = format === 'json' ? '/api/v1/audit/export/json?' : '/api/v1/audit/export/csv?'; - - if (type) url += `&type=${encodeURIComponent(type)}`; - if (principal) url += `&principal=${encodeURIComponent(principal)}`; - if (startDate) url += `&startDate=${startDate}`; - if (endDate) url += `&endDate=${endDate}`; - - // Trigger download - window.location.href = url; -} - -// Render table with audit data -function renderTable(events) { - - if (!events || events.length === 0) { - auditTableBody.innerHTML = '' + window.i18n.noEventsFound + ''; - return; - } - - try { - auditTableBody.innerHTML = ''; - - events.forEach((event, index) => { - try { - const row = document.createElement('tr'); - row.innerHTML = ` - ${event.id || 'N/A'} - ${formatDate(event.timestamp)} - ${escapeHtml(event.principal || 'N/A')} - ${escapeHtml(event.type || 'N/A')} - - `; - - // Store event data for modal - row.dataset.event = JSON.stringify(event); - - // Add click handler for details button - const detailsButton = row.querySelector('.view-details'); - if (detailsButton) { - detailsButton.addEventListener('click', function() { - showEventDetails(event); - }); - } - - auditTableBody.appendChild(row); - } catch (rowError) { - - } - }); - - } catch (e) { - auditTableBody.innerHTML = '' + window.i18n.errorRendering + ' ' + e.message + ''; - } -} - -// Show event details in modal -function showEventDetails(event) { - // Get modal elements by ID with correct hyphenated IDs from HTML - const modalId = document.getElementById('modal-id'); - const modalPrincipal = document.getElementById('modal-principal'); - const modalType = document.getElementById('modal-type'); - const modalTimestamp = document.getElementById('modal-timestamp'); - const modalData = document.getElementById('modal-data'); - const eventDetailsModal = document.getElementById('eventDetailsModal'); - - // Set modal content - if (modalId) modalId.textContent = event.id; - if (modalPrincipal) modalPrincipal.textContent = event.principal; - if (modalType) modalType.textContent = event.type; - if (modalTimestamp) modalTimestamp.textContent = formatDate(event.timestamp); - - // Format JSON data - if (modalData) { - try { - const dataObj = typeof event.data === 'string' ? JSON.parse(event.data) : event.data; - modalData.textContent = JSON.stringify(dataObj, null, 2); - } catch (e) { - modalData.textContent = event.data || 'No data available'; - } - } - - // Show the modal - if (eventDetailsModal) { - const modal = new bootstrap.Modal(eventDetailsModal); - modal.show(); - } -} - -// No need for a dynamic pagination renderer anymore as we're using static buttons - -// Direct pagination approach - server seems to be hard-limited to returning 20 items -function goToPage(page) { - - // Basic validation - totalPages may not be initialized on first load - if (page < 0) { - return; - } - - // Skip validation against totalPages on first load - if (totalPages > 0 && page >= totalPages) { - return; - } - - // Simple guard flag - if (window.paginationBusy) { - return; - } - window.paginationBusy = true; - - try { - - // Store the requested page for later - window.requestedPage = page; - currentPage = page; - - // Update UI immediately for user feedback - document.getElementById('currentPage').textContent = page + 1; - - // Load data with this page - loadAuditData(page, pageSize); - } catch (e) { - window.paginationBusy = false; - } -} - -// Render charts -function renderCharts(data) { - // Get theme colors - const colors = getThemeColors(); - - // Prepare data for charts - const typeLabels = Object.keys(data.eventsByType); - const typeValues = Object.values(data.eventsByType); - - const userLabels = Object.keys(data.eventsByPrincipal); - const userValues = Object.values(data.eventsByPrincipal); - - // Sort days for time chart - const timeLabels = Object.keys(data.eventsByDay).sort(); - const timeValues = timeLabels.map(day => data.eventsByDay[day] || 0); - - // Chart.js global defaults for dark mode compatibility - Chart.defaults.color = colors.text; - Chart.defaults.borderColor = colors.grid; - - // Type chart - if (typeChart) { - typeChart.destroy(); - } - - const typeCtx = document.getElementById('typeChart').getContext('2d'); - typeChart = new Chart(typeCtx, { - type: 'bar', - data: { - labels: typeLabels, - datasets: [{ - label: window.i18n.eventsByType, - data: typeValues, - backgroundColor: colors.chartColors.slice(0, typeLabels.length).map(color => { - // Add transparency to the colors - if (color.startsWith('rgb(')) { - return color.replace('rgb(', 'rgba(').replace(')', ', 0.8)'); - } - return color; - }), - borderColor: colors.chartColors.slice(0, typeLabels.length), - borderWidth: 2, - borderRadius: 4 - }] - }, - options: { - responsive: true, - maintainAspectRatio: false, - plugins: { - legend: { - labels: { - color: colors.text, - font: { - weight: colors.isDarkMode ? 'bold' : 'normal', - size: 14 - }, - usePointStyle: true, - pointStyle: 'rectRounded', - boxWidth: 12, - boxHeight: 12, - } - }, - tooltip: { - titleFont: { - weight: 'bold', - size: 14 - }, - bodyFont: { - size: 13 - }, - backgroundColor: colors.isDarkMode ? 'rgba(40, 44, 52, 0.9)' : 'rgba(255, 255, 255, 0.9)', - titleColor: colors.isDarkMode ? '#ffffff' : '#000000', - bodyColor: colors.isDarkMode ? '#ffffff' : '#000000', - borderColor: colors.isDarkMode ? 'rgba(255, 255, 255, 0.5)' : colors.grid, - borderWidth: 1, - padding: 10, - cornerRadius: 6, - callbacks: { - label: function(context) { - return `${context.dataset.label}: ${context.raw}`; - } - } - } - }, - scales: { - y: { - beginAtZero: true, - ticks: { - color: colors.text, - font: { - weight: colors.isDarkMode ? 'bold' : 'normal', - size: 12 - }, - precision: 0 // Only show whole numbers - }, - grid: { - color: colors.isDarkMode ? 'rgba(255, 255, 255, 0.1)' : colors.grid - }, - title: { - display: true, - text: 'Count', - color: colors.text, - font: { - weight: colors.isDarkMode ? 'bold' : 'normal', - size: 14 - } - } - }, - x: { - ticks: { - color: colors.text, - font: { - weight: colors.isDarkMode ? 'bold' : 'normal', - size: 11 - }, - callback: function(value, index) { - // Get the original label - const label = this.getLabelForValue(value); - // If the label is too long, truncate it - const maxLength = 10; - if (label.length > maxLength) { - return label.substring(0, maxLength) + '...'; - } - return label; - }, - autoSkip: true, - maxRotation: 0, - minRotation: 0 - }, - grid: { - color: colors.isDarkMode ? 'rgba(255, 255, 255, 0.1)' : colors.grid, - display: false // Hide vertical gridlines for cleaner look - }, - title: { - display: true, - text: 'Event Type', - color: colors.text, - font: { - weight: colors.isDarkMode ? 'bold' : 'normal', - size: 14 - }, - padding: {top: 10, bottom: 0} - } - } - } - } - }); - - // User chart - if (userChart) { - userChart.destroy(); - } - - const userCtx = document.getElementById('userChart').getContext('2d'); - userChart = new Chart(userCtx, { - type: 'pie', - data: { - labels: userLabels, - datasets: [{ - label: window.i18n.eventsByUser, - data: userValues, - backgroundColor: colors.chartColors.slice(0, userLabels.length), - borderWidth: 2, - borderColor: colors.isDarkMode ? 'rgba(255, 255, 255, 0.8)' : 'rgba(0, 0, 0, 0.5)' - }] - }, - options: { - responsive: true, - maintainAspectRatio: false, - plugins: { - legend: { - position: 'right', - labels: { - color: colors.text, - font: { - size: colors.isDarkMode ? 14 : 12, - weight: colors.isDarkMode ? 'bold' : 'normal' - }, - padding: 15, - usePointStyle: true, - pointStyle: 'circle', - boxWidth: 10, - boxHeight: 10, - // Add a box around each label for better contrast in dark mode - generateLabels: function(chart) { - const original = Chart.overrides.pie.plugins.legend.labels.generateLabels; - const labels = original.call(this, chart); - - if (colors.isDarkMode) { - labels.forEach(label => { - // Enhance contrast for dark mode - label.fillStyle = label.fillStyle; // Keep original fill - label.strokeStyle = 'rgba(255, 255, 255, 0.8)'; // White border - label.lineWidth = 2; // Thicker border - }); - } - - return labels; - } - } - }, - tooltip: { - titleFont: { - weight: 'bold', - size: 14 - }, - bodyFont: { - size: 13 - }, - backgroundColor: colors.isDarkMode ? 'rgba(40, 44, 52, 0.9)' : 'rgba(255, 255, 255, 0.9)', - titleColor: colors.isDarkMode ? '#ffffff' : '#000000', - bodyColor: colors.isDarkMode ? '#ffffff' : '#000000', - borderColor: colors.isDarkMode ? 'rgba(255, 255, 255, 0.5)' : colors.grid, - borderWidth: 1, - padding: 10, - cornerRadius: 6 - } - } - } - }); - - // Time chart - if (timeChart) { - timeChart.destroy(); - } - - const timeCtx = document.getElementById('timeChart').getContext('2d'); - - // Get first color for line chart with appropriate transparency - let bgColor, borderColor; - if (colors.isDarkMode) { - bgColor = 'rgba(162, 201, 255, 0.3)'; // Light blue with transparency - borderColor = 'rgb(162, 201, 255)'; // Light blue solid - } else { - bgColor = 'rgba(0, 96, 170, 0.2)'; // Dark blue with transparency - borderColor = 'rgb(0, 96, 170)'; // Dark blue solid - } - - timeChart = new Chart(timeCtx, { - type: 'line', - data: { - labels: timeLabels, - datasets: [{ - label: window.i18n.eventsOverTime, - data: timeValues, - backgroundColor: bgColor, - borderColor: borderColor, - borderWidth: 3, - tension: 0.2, - fill: true, - pointBackgroundColor: borderColor, - pointBorderColor: colors.isDarkMode ? '#fff' : '#000', - pointBorderWidth: 2, - pointRadius: 5, - pointHoverRadius: 7 - }] - }, - options: { - responsive: true, - maintainAspectRatio: false, - plugins: { - legend: { - labels: { - color: colors.text, - font: { - weight: colors.isDarkMode ? 'bold' : 'normal', - size: 14 - }, - usePointStyle: true, - pointStyle: 'line', - boxWidth: 50, - boxHeight: 3 - } - }, - tooltip: { - titleFont: { - weight: 'bold', - size: 14 - }, - bodyFont: { - size: 13 - }, - backgroundColor: colors.isDarkMode ? 'rgba(40, 44, 52, 0.9)' : 'rgba(255, 255, 255, 0.9)', - titleColor: colors.isDarkMode ? '#ffffff' : '#000000', - bodyColor: colors.isDarkMode ? '#ffffff' : '#000000', - borderColor: colors.isDarkMode ? 'rgba(255, 255, 255, 0.5)' : colors.grid, - borderWidth: 1, - padding: 10, - cornerRadius: 6, - callbacks: { - label: function(context) { - return `Events: ${context.raw}`; - } - } - } - }, - interaction: { - intersect: false, - mode: 'index' - }, - scales: { - y: { - beginAtZero: true, - ticks: { - color: colors.text, - font: { - weight: colors.isDarkMode ? 'bold' : 'normal', - size: 12 - }, - precision: 0 // Only show whole numbers - }, - grid: { - color: colors.isDarkMode ? 'rgba(255, 255, 255, 0.1)' : colors.grid - }, - title: { - display: true, - text: 'Number of Events', - color: colors.text, - font: { - weight: colors.isDarkMode ? 'bold' : 'normal', - size: 14 - } - } - }, - x: { - ticks: { - color: colors.text, - font: { - weight: colors.isDarkMode ? 'bold' : 'normal', - size: 12 - }, - maxRotation: 45, - minRotation: 45 - }, - grid: { - color: colors.isDarkMode ? 'rgba(255, 255, 255, 0.1)' : colors.grid - }, - title: { - display: true, - text: 'Date', - color: colors.text, - font: { - weight: colors.isDarkMode ? 'bold' : 'normal', - size: 14 - }, - padding: {top: 20} - } - } - } - } - }); -} - -// Helper functions -function formatDate(timestamp) { - const date = new Date(timestamp); - return date.toLocaleString(); -} - -function escapeHtml(text) { - if (!text) return ''; - return text - .toString() - .replace(/&/g, '&') - .replace(//g, '>') - .replace(/\"/g, '"') - .replace(/'/g, '''); -} - -function showLoading(id) { - const loading = document.getElementById(id); - if (loading) loading.style.display = 'flex'; -} - -function hideLoading(id) { - const loading = document.getElementById(id); - if (loading) loading.style.display = 'none'; -} - -// Load event types from the server for filter dropdowns -function loadEventTypes() { - fetchWithCsrf('/api/v1/audit/types') - .then(response => response.json()) - .then(types => { - if (!types || types.length === 0) { - return; - } - - // Populate the type filter dropdowns - const typeFilter = document.getElementById('typeFilter'); - const exportTypeFilter = document.getElementById('exportTypeFilter'); - - // Clear existing options except the first one (All event types) - while (typeFilter.options.length > 1) { - typeFilter.remove(1); - } - - while (exportTypeFilter.options.length > 1) { - exportTypeFilter.remove(1); - } - - // Add new options - types.forEach(type => { - // Main filter dropdown - const option = document.createElement('option'); - option.value = type; - option.textContent = type; - typeFilter.appendChild(option); - - // Export filter dropdown - const exportOption = document.createElement('option'); - exportOption.value = type; - exportOption.textContent = type; - exportTypeFilter.appendChild(exportOption); - }); - }) - .catch(error => { - console.error('Error loading event types:', error); - }); -} - -// Get theme colors for charts -function getThemeColors() { - const isDarkMode = document.documentElement.getAttribute('data-bs-theme') === 'dark'; - - // In dark mode, use higher contrast colors for text - const textColor = isDarkMode ? - 'rgb(255, 255, 255)' : // White for dark mode for maximum contrast - getComputedStyle(document.documentElement).getPropertyValue('--md-sys-color-on-surface').trim(); - - // Use a more visible grid color in dark mode - const gridColor = isDarkMode ? - 'rgba(255, 255, 255, 0.2)' : // Semi-transparent white for dark mode - getComputedStyle(document.documentElement).getPropertyValue('--md-sys-color-outline-variant').trim(); - - // Define bright, high-contrast colors for both dark and light modes - const chartColorsDark = [ - 'rgb(162, 201, 255)', // Light blue - primary - 'rgb(193, 194, 248)', // Light purple - tertiary - 'rgb(255, 180, 171)', // Light red - error - 'rgb(72, 189, 84)', // Green - other - 'rgb(25, 177, 212)', // Cyan - convert - 'rgb(25, 101, 212)', // Blue - sign - 'rgb(255, 120, 146)', // Pink - security - 'rgb(104, 220, 149)', // Light green - convertto - 'rgb(212, 172, 25)', // Yellow - image - 'rgb(245, 84, 84)', // Red - advance - ]; - - const chartColorsLight = [ - 'rgb(0, 96, 170)', // Blue - primary - 'rgb(88, 90, 138)', // Purple - tertiary - 'rgb(186, 26, 26)', // Red - error - 'rgb(72, 189, 84)', // Green - other - 'rgb(25, 177, 212)', // Cyan - convert - 'rgb(25, 101, 212)', // Blue - sign - 'rgb(255, 120, 146)', // Pink - security - 'rgb(104, 220, 149)', // Light green - convertto - 'rgb(212, 172, 25)', // Yellow - image - 'rgb(245, 84, 84)', // Red - advance - ]; - - return { - text: textColor, - grid: gridColor, - backgroundColor: getComputedStyle(document.documentElement).getPropertyValue('--md-sys-color-surface-container').trim(), - chartColors: isDarkMode ? chartColorsDark : chartColorsLight, - isDarkMode: isDarkMode - }; -} - -// Function to generate a palette of colors for charts -function getChartColors(count, opacity = 0.6) { - try { - // Use theme colors first - const themeColors = getThemeColors(); - if (themeColors && themeColors.chartColors && themeColors.chartColors.length > 0) { - const result = []; - for (let i = 0; i < count; i++) { - // Get the raw color and add opacity - let color = themeColors.chartColors[i % themeColors.chartColors.length]; - // If it's rgb() format, convert to rgba() - if (color.startsWith('rgb(')) { - color = color.replace('rgb(', '').replace(')', ''); - result.push(`rgba(${color}, ${opacity})`); - } else { - // Just use the color directly - result.push(color); - } - } - return result; - } - } catch (e) { - console.warn('Error using theme colors, falling back to default colors', e); - } - - // Base colors - a larger palette than the default - const colors = [ - [54, 162, 235], // blue - [255, 99, 132], // red - [75, 192, 192], // teal - [255, 206, 86], // yellow - [153, 102, 255], // purple - [255, 159, 64], // orange - [46, 204, 113], // green - [231, 76, 60], // dark red - [52, 152, 219], // light blue - [155, 89, 182], // violet - [241, 196, 15], // dark yellow - [26, 188, 156], // turquoise - [230, 126, 34], // dark orange - [149, 165, 166], // light gray - [243, 156, 18], // amber - [39, 174, 96], // emerald - [211, 84, 0], // dark orange red - [22, 160, 133], // green sea - [41, 128, 185], // belize hole - [142, 68, 173] // wisteria - ]; - - const result = []; - - // Always use the same format regardless of color source - if (count > colors.length) { - // Generate colors algorithmically for large sets - for (let i = 0; i < count; i++) { - // Generate a color based on position in the hue circle (0-360) - const hue = (i * 360 / count) % 360; - const sat = 70 + Math.random() * 10; // 70-80% - const light = 50 + Math.random() * 10; // 50-60% - - result.push(`hsla(${hue}, ${sat}%, ${light}%, ${opacity})`); - } - } else { - // Use colors from our palette but also return in hsla format for consistency - for (let i = 0; i < count; i++) { - const color = colors[i % colors.length]; - result.push(`rgba(${color[0]}, ${color[1]}, ${color[2]}, ${opacity})`); - } - } - - return result; -} diff --git a/app/proprietary/src/main/resources/templates/accounts/team-details.html b/app/proprietary/src/main/resources/templates/accounts/team-details.html deleted file mode 100644 index bc7d2e533..000000000 --- a/app/proprietary/src/main/resources/templates/accounts/team-details.html +++ /dev/null @@ -1,205 +0,0 @@ - - - - - - - - - - -
    -
    - - -
    -
    -
    -

    - - group - - Team Name -

    -
    - -
    -
    -
    -
    Total Members:
    -
    1
    -
    -
    - - -
    - Default message if not found -
    - - - -
    Members
    - -
    - - - - - - - - - - - - - - - - - - - -
    #UsernameRoleLast RequestStatus
    1usernameRole - 2023-01-01 12:00:00 - - person - Enabled - - - person_off - Disabled - -
    -
    - - -
    - person_off -

    This team has no members yet.

    - -
    - - -
    - -
    -
    -
    -
    -
    - - - - - - - - -
    - - - \ No newline at end of file diff --git a/app/proprietary/src/main/resources/templates/accounts/teams.html b/app/proprietary/src/main/resources/templates/accounts/teams.html deleted file mode 100644 index a1e485d62..000000000 --- a/app/proprietary/src/main/resources/templates/accounts/teams.html +++ /dev/null @@ -1,158 +0,0 @@ - - - - - - - - - - - -
    -
    - - -
    -
    -
    -

    - - groups - - Team Management -

    -
    - -
    - - - - -
    - Default message if not found -
    - -
    - Default message if not found -
    - -
    - Default message if not found -
    - -
    - Default message if not found -
    - - - - - -
    - - - - - - - - - - - - - - - - - - -
    Team NameTotal MembersLast RequestActions
    - -
    - - search View - -
    - - -
    -
    -
    -
    - - - -
    -
    -
    - - - -
    - -
    - - - \ No newline at end of file diff --git a/app/proprietary/src/main/resources/templates/audit/dashboard.html b/app/proprietary/src/main/resources/templates/audit/dashboard.html deleted file mode 100644 index a0a61d69e..000000000 --- a/app/proprietary/src/main/resources/templates/audit/dashboard.html +++ /dev/null @@ -1,383 +0,0 @@ - - - - - - - - - - - - -
    -
    - - -
    -

    Audit Dashboard

    - - -
    -
    -

    Audit System Status

    -
    -
    -
    -
    -
    -
    Status
    -
    - Enabled - Disabled -
    -
    -
    -
    -
    -
    Current Level
    -
    - STANDARD -
    -
    -
    -
    -
    -
    Retention Period
    -
    90 days
    -
    -
    -
    -
    -
    Total Events
    -
    -
    -
    -
    -
    -
    -
    - - - - -
    - -
    -
    -
    -
    -
    -

    Events by Type

    -
    - - - -
    -
    -
    -
    -
    -
    - Loading... -
    -
    - -
    -
    -
    -
    -
    -
    -
    -

    Events by User

    -
    -
    -
    -
    -
    - Loading... -
    -
    - -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Events Over Time

    -
    -
    -
    -
    -
    - Loading... -
    -
    - -
    -
    -
    -
    -
    -
    - - -
    -
    -
    -

    Audit Events

    -
    -
    - -
    -
    -
    -
    - - -
    -
    -
    -
    - - -
    -
    -
    -
    - - -
    -
    -
    -
    - - -
    -
    -
    -
    -
    - - -
    -
    -
    - - -
    -
    -
    - Loading... -
    -
    - - - - - - - - - - - - - -
    IDTimeUserTypeDetails
    -
    - - -
    -
    - Show - - entries - Page 1 of 1 (Total records: 0) -
    - -
    -
    -
    - - - -
    - - -
    -
    -
    -

    Export Audit Data

    -
    -
    - -
    -
    -
    -
    - - -
    -
    -
    -
    - - -
    -
    -
    -
    - - -
    -
    -
    -
    - - -
    -
    -
    -
    -
    -
    Export Format
    -
    - - -
    -
    -
    - - -
    -
    -
    - -
    -
    Export Information
    -

    The export will include all audit events matching the selected filters. For large datasets, the export may take a few moments to generate.

    -

    Exported data will include:

    -
      -
    • Event ID
    • -
    • User
    • -
    • Event Type
    • -
    • Timestamp
    • -
    • Event Data
    • -
    -
    -
    -
    -
    - -
    -
    - - - - - - - - - - -
    -
    - - - - \ No newline at end of file diff --git a/devGuide/DeveloperGuide.md b/devGuide/DeveloperGuide.md index 746e09e24..0fe7216fa 100644 --- a/devGuide/DeveloperGuide.md +++ b/devGuide/DeveloperGuide.md @@ -8,7 +8,7 @@ Stirling-PDF is a robust, locally hosted, web-based PDF manipulation tool. This Stirling-PDF is built using: -- Spring Boot + Thymeleaf +- Spring Boot - PDFBox - LibreOffice - qpdf @@ -94,7 +94,6 @@ Stirling-PDF/ │ │ │ ├── css/ │ │ │ ├── js/ │ │ │ └── pdfjs/ -│ │ └── templates/ │ └── test/ │ └── java/ │ └── stirling/ @@ -242,7 +241,6 @@ For quick iterations and development of Java backend, JavaScript, and UI compone - RESTful API endpoints - JavaScript functionality - User interface components and styling -- Thymeleaf templates To run Stirling-PDF locally: @@ -333,61 +331,6 @@ Remember to test your changes thoroughly to ensure they don't break any existing ## Code examples -### Overview of Thymeleaf - -Thymeleaf is a server-side Java HTML template engine. It is used in Stirling-PDF to render dynamic web pages. Thymeleaf integrates heavily with Spring Boot. - -### Thymeleaf overview - -In Stirling-PDF, Thymeleaf is used to create HTML templates that are rendered on the server side. These templates are located in the `app/core/src/main/resources/templates` directory. Thymeleaf templates use a combination of HTML and special Thymeleaf attributes to dynamically generate content. - -Some examples of this are: - -```html - -``` -or -```html - -``` - -Where it uses the `th:block`, `th:` indicating it's a special Thymeleaf element to be used server-side in generating the HTML, and block being the actual element type. -In this case, we are inserting the `navbar` entry within the `fragments/navbar.html` fragment into the `th:block` element. - -They can be more complex, such as: - -```html - -``` - -Which is the same as above but passes the parameters title and header into the fragment `common.html` to be used in its HTML generation. - -Thymeleaf can also be used to loop through objects or pass things from the Java side into the HTML side. - -```java - @GetMapping - public String newFeaturePage(Model model) { - model.addAttribute("exampleData", exampleData); - return "new-feature"; - } -``` - -In the above example, if exampleData is a list of plain java objects of class Person and within it, you had id, name, age, etc. You can reference it like so - -```html - - - - - - - - - -``` - -This would generate n entries of tr for each person in exampleData - ### Adding a New Feature to the Backend (API) 1. **Create a New Controller:** @@ -412,7 +355,7 @@ This would generate n entries of tr for each person in exampleData @GetMapping @Operation(summary = "New Feature", description = "This is a new feature endpoint.") public String newFeature() { - return "NewFeatureResponse"; // This refers to the NewFeatureResponse.html template presenting the user with the generated html from that file when they navigate to /api/v1/new-feature + return "NewFeatureResponse"; } } ``` @@ -467,91 +410,6 @@ This would generate n entries of tr for each person in exampleData } ``` -### Adding a New Feature to the Frontend (UI) - -1. **Create a New Thymeleaf Template:** - - Create a new HTML file in the `app/core/src/main/resources/templates` directory. - - Use Thymeleaf attributes to dynamically generate content. - - Use `extract-page.html` as a base example for the HTML template, which is useful to ensure importing of the general layout, navbar, and footer. - - ```html - - - - - - - -
    -
    - -

    -
    -
    -
    -
    - upload - -
    -
    -
    - -
    - - -
    - - -
    -
    -
    -
    -
    - -
    - - - ``` - -2. **Create a New Controller for the UI:** - - Create a new Java class in the `app/core/src/main/java/stirling/software/SPDF/controller/ui` directory. - - Annotate the class with `@Controller` and `@RequestMapping` to define the UI endpoint. - - ```java - package stirling.software.SPDF.controller.ui; - - import org.springframework.beans.factory.annotation.Autowired; - import org.springframework.stereotype.Controller; - import org.springframework.ui.Model; - import org.springframework.web.bind.annotation.GetMapping; - import org.springframework.web.bind.annotation.RequestMapping; - import stirling.software.SPDF.service.NewFeatureService; - - @Controller - @RequestMapping("/new-feature") - public class NewFeatureUIController { - - @Autowired - private NewFeatureService newFeatureService; - - @GetMapping - public String newFeaturePage(Model model) { - model.addAttribute("newFeatureData", newFeatureService.getNewFeatureData()); - return "new-feature"; - } - } - ``` - -3. **Update the Navigation Bar:** - - Add a link to the new feature page in the navigation bar. - - Update the `app/core/src/main/resources/templates/fragments/navbar.html` file. - - ```html - - ``` - ## Adding New Translations to Existing Language Files in Stirling-PDF When adding a new feature or modifying existing ones in Stirling-PDF, you'll need to add new translation entries to the existing language files. Here's a step-by-step guide: @@ -581,15 +439,4 @@ pdfSplitter.input.pages=Enter page numbers to split Add these entries to the default GB language file and any others you wish, translating the values as appropriate for each language. -### 3. Use Translations in Thymeleaf Templates - -In your Thymeleaf templates, use the `#{key}` syntax to reference the new translations: - -```html -

    PDF Splitter

    -

    Split your PDF into multiple documents

    - - -``` - Remember, never hard-code text in your templates or Java code. Always use translation keys to ensure proper localization.