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.