Merge remote-tracking branch 'origin/main' into team

This commit is contained in:
Anthony Stirling 2025-05-14 12:20:55 +01:00
commit 255c99b3de
13 changed files with 266 additions and 385 deletions

View File

@ -24,4 +24,4 @@ jobs:
- name: "Checkout Repository" - name: "Checkout Repository"
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: "Dependency Review" - name: "Dependency Review"
uses: actions/dependency-review-action@ce3cf9537a52e8119d91fd484ab5b8a807627bf8 # v4.6.0 uses: actions/dependency-review-action@38ecb5b593bf0eb19e335c03f97670f792489a8b # v4.7.0

View File

@ -541,7 +541,7 @@ This would generate n entries of tr for each person in exampleData
```html ```html
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" th:href="@{/new-feature}">New Feature</a> <a class="nav-link" th:href="@{'/new-feature'}">New Feature</a>
</li> </li>
``` ```

View File

@ -112,7 +112,7 @@ Visit our comprehensive documentation at [docs.stirlingpdf.com](https://docs.sti
## Supported Languages ## Supported Languages
Stirling-PDF currently supports 39 languages! Stirling-PDF currently supports 40 languages!
| Language | Progress | | Language | Progress |
| -------------------------------------------- | -------------------------------------- | | -------------------------------------------- | -------------------------------------- |
@ -156,7 +156,7 @@ Stirling-PDF currently supports 39 languages!
| Turkish (Türkçe) (tr_TR) | ![97%](https://geps.dev/progress/97) | | Turkish (Türkçe) (tr_TR) | ![97%](https://geps.dev/progress/97) |
| Ukrainian (Українська) (uk_UA) | ![96%](https://geps.dev/progress/96) | | Ukrainian (Українська) (uk_UA) | ![96%](https://geps.dev/progress/96) |
| Vietnamese (Tiếng Việt) (vi_VN) | ![73%](https://geps.dev/progress/73) | | Vietnamese (Tiếng Việt) (vi_VN) | ![73%](https://geps.dev/progress/73) |
| Malayalam (മലയാളം) (ml_ML) | ![99%](https://geps.dev/progress/99) |
## Stirling PDF Enterprise ## Stirling PDF Enterprise

View File

@ -479,7 +479,7 @@ dependencies {
testImplementation "org.springframework.boot:spring-boot-starter-test:$springBootVersion" testImplementation "org.springframework.boot:spring-boot-starter-test:$springBootVersion"
// Batik // Batik
implementation "org.apache.xmlgraphics:batik-all:1.18" implementation "org.apache.xmlgraphics:batik-all:1.19"
// TwelveMonkeys // TwelveMonkeys
runtimeOnly "com.twelvemonkeys.imageio:imageio-batik:$imageioVersion" runtimeOnly "com.twelvemonkeys.imageio:imageio-batik:$imageioVersion"
@ -527,7 +527,7 @@ dependencies {
implementation "org.bouncycastle:bcprov-jdk18on:$bouncycastleVersion" implementation "org.bouncycastle:bcprov-jdk18on:$bouncycastleVersion"
implementation "org.bouncycastle:bcpkix-jdk18on:$bouncycastleVersion" implementation "org.bouncycastle:bcpkix-jdk18on:$bouncycastleVersion"
implementation "org.springframework.boot:spring-boot-starter-actuator:$springBootVersion" implementation "org.springframework.boot:spring-boot-starter-actuator:$springBootVersion"
implementation "io.micrometer:micrometer-core:1.14.6" implementation "io.micrometer:micrometer-core:1.14.7"
implementation group: "com.google.zxing", name: "core", version: "3.5.3" implementation group: "com.google.zxing", name: "core", version: "3.5.3"
// https://mvnrepository.com/artifact/org.commonmark/commonmark // https://mvnrepository.com/artifact/org.commonmark/commonmark
implementation "org.commonmark:commonmark:0.24.0" implementation "org.commonmark:commonmark:0.24.0"

View File

@ -33,7 +33,7 @@ public class EEAppConfig {
public boolean runningProOrHigher() { public boolean runningProOrHigher() {
return licenseKeyChecker.getPremiumLicenseEnabledResult() != License.NORMAL; return licenseKeyChecker.getPremiumLicenseEnabledResult() != License.NORMAL;
} }
@Bean(name = "license") @Bean(name = "license")
public String licenseType() { public String licenseType() {
return licenseKeyChecker.getPremiumLicenseEnabledResult().name(); return licenseKeyChecker.getPremiumLicenseEnabledResult().name();

View File

@ -36,7 +36,7 @@ public class AppConfig {
private final ApplicationProperties applicationProperties; private final ApplicationProperties applicationProperties;
private final Environment env; private final Environment env;
@Bean @Bean
@ConditionalOnProperty(name = "system.customHTMLFiles", havingValue = "true") @ConditionalOnProperty(name = "system.customHTMLFiles", havingValue = "true")
public SpringTemplateEngine templateEngine(ResourceLoader resourceLoader) { public SpringTemplateEngine templateEngine(ResourceLoader resourceLoader) {
@ -197,12 +197,12 @@ public class AppConfig {
public String uuid() { public String uuid() {
return applicationProperties.getAutomaticallyGenerated().getUUID(); return applicationProperties.getAutomaticallyGenerated().getUUID();
} }
@Bean(name = "disablePixel") @Bean(name = "disablePixel")
public boolean disablePixel() { public boolean disablePixel() {
return Boolean.getBoolean(env.getProperty("DISABLE_PIXEL")); return Boolean.getBoolean(env.getProperty("DISABLE_PIXEL"));
} }
@Bean(name = "machineType") @Bean(name = "machineType")
public String determineMachineType() { public String determineMachineType() {
try { try {

View File

@ -73,7 +73,7 @@ public class InitialSetup {
// Initialize Terms and Conditions // Initialize Terms and Conditions
String termsUrl = applicationProperties.getLegal().getTermsAndConditions(); String termsUrl = applicationProperties.getLegal().getTermsAndConditions();
if (StringUtils.isEmpty(termsUrl)) { if (StringUtils.isEmpty(termsUrl)) {
String defaultTermsUrl = "https://www.stirlingpdf.com/terms-and-conditions"; String defaultTermsUrl = "https://www.stirlingpdf.com/terms";
GeneralUtils.saveKeyToSettings("legal.termsAndConditions", defaultTermsUrl); GeneralUtils.saveKeyToSettings("legal.termsAndConditions", defaultTermsUrl);
applicationProperties.getLegal().setTermsAndConditions(defaultTermsUrl); applicationProperties.getLegal().setTermsAndConditions(defaultTermsUrl);
} }

View File

@ -15,7 +15,7 @@ public class MetricsConfig {
return new MeterFilter() { return new MeterFilter() {
@Override @Override
public MeterFilterReply accept(Meter.Id id) { public MeterFilterReply accept(Meter.Id id) {
if (id.getName().equals("http.requests")) { if ("http.requests".equals(id.getName())) {
return MeterFilterReply.NEUTRAL; return MeterFilterReply.NEUTRAL;
} }
return MeterFilterReply.DENY; return MeterFilterReply.DENY;

View File

@ -5,7 +5,9 @@ import org.springframework.context.annotation.Configuration;
import io.swagger.v3.oas.models.Components; import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.Info; import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.License;
import io.swagger.v3.oas.models.security.SecurityRequirement; import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme; import io.swagger.v3.oas.models.security.SecurityScheme;
@ -31,14 +33,25 @@ public class OpenApiConfig {
// default version if all else fails // default version if all else fails
version = "1.0.0"; version = "1.0.0";
} }
Info info =
new Info()
.title(DEFAULT_TITLE)
.version(version)
.license(
new License()
.name("MIT")
.url(
"https://raw.githubusercontent.com/Stirling-Tools/Stirling-PDF/refs/heads/main/LICENSE")
.identifier("MIT"))
.termsOfService("https://www.stirlingpdf.com/terms")
.contact(
new Contact()
.name("Stirling Software")
.url("https://www.stirlingpdf.com")
.email("contact@stirlingpdf.com"))
.description(DEFAULT_DESCRIPTION);
if (!applicationProperties.getSecurity().getEnableLogin()) { if (!applicationProperties.getSecurity().getEnableLogin()) {
return new OpenAPI() return new OpenAPI().components(new Components()).info(info);
.components(new Components())
.info(
new Info()
.title(DEFAULT_TITLE)
.version(version)
.description(DEFAULT_DESCRIPTION));
} else { } else {
SecurityScheme apiKeyScheme = SecurityScheme apiKeyScheme =
new SecurityScheme() new SecurityScheme()
@ -47,11 +60,7 @@ public class OpenApiConfig {
.name("X-API-KEY"); .name("X-API-KEY");
return new OpenAPI() return new OpenAPI()
.components(new Components().addSecuritySchemes("apiKey", apiKeyScheme)) .components(new Components().addSecuritySchemes("apiKey", apiKeyScheme))
.info( .info(info)
new Info()
.title(DEFAULT_TITLE)
.version(version)
.description(DEFAULT_DESCRIPTION))
.addSecurityItem(new SecurityRequirement().addList("apiKey")); .addSecurityItem(new SecurityRequirement().addList("apiKey"));
} }
} }

View File

@ -77,7 +77,7 @@ premium:
appId: '' appId: ''
mail: mail:
enabled: true # set to 'true' to enable sending emails enabled: false # set to 'true' to enable sending emails
host: smtp.example.com # SMTP server hostname host: smtp.example.com # SMTP server hostname
port: 587 # SMTP server port port: 587 # SMTP server port
username: '' # SMTP server username username: '' # SMTP server username
@ -85,7 +85,7 @@ mail:
from: '' # sender email address from: '' # sender email address
legal: legal:
termsAndConditions: https://www.stirlingpdf.com/terms-and-conditions # URL to the terms and conditions of your application (e.g. https://example.com/terms). Empty string to disable or filename to load from local file in static folder termsAndConditions: https://www.stirlingpdf.com/terms # URL to the terms and conditions of your application (e.g. https://example.com/terms). Empty string to disable or filename to load from local file in static folder
privacyPolicy: https://www.stirlingpdf.com/privacy-policy # URL to the privacy policy of your application (e.g. https://example.com/privacy). Empty string to disable or filename to load from local file in static folder privacyPolicy: https://www.stirlingpdf.com/privacy-policy # URL to the privacy policy of your application (e.g. https://example.com/privacy). Empty string to disable or filename to load from local file in static folder
accessibilityStatement: '' # URL to the accessibility statement of your application (e.g. https://example.com/accessibility). Empty string to disable or filename to load from local file in static folder accessibilityStatement: '' # URL to the accessibility statement of your application (e.g. https://example.com/accessibility). Empty string to disable or filename to load from local file in static folder
cookiePolicy: '' # URL to the cookie policy of your application (e.g. https://example.com/cookie). Empty string to disable or filename to load from local file in static folder cookiePolicy: '' # URL to the cookie policy of your application (e.g. https://example.com/cookie). Empty string to disable or filename to load from local file in static folder
@ -113,11 +113,11 @@ system:
name: postgres # set the name of your database. Should match the name of the database you create name: postgres # set the name of your database. Should match the name of the database you create
customPaths: customPaths:
pipeline: pipeline:
watchedFoldersDir: '' #Defaults to /pipeline/watchedFolders watchedFoldersDir: '' # Defaults to /pipeline/watchedFolders
finishedFoldersDir: '' #Defaults to /pipeline/finishedFolders finishedFoldersDir: '' # Defaults to /pipeline/finishedFolders
operations: operations:
weasyprint: '' #Defaults to /opt/venv/bin/weasyprint weasyprint: '' # Defaults to /opt/venv/bin/weasyprint
unoconvert: '' #Defaults to /opt/venv/bin/unoconvert unoconvert: '' # Defaults to /opt/venv/bin/unoconvert
fileUploadLimit: '' # Defaults to "". No limit when string is empty. Set a number, between 0 and 999, followed by one of the following strings to set a limit. "KB", "MB", "GB". fileUploadLimit: '' # Defaults to "". No limit when string is empty. Set a number, between 0 and 999, followed by one of the following strings to set a limit. "KB", "MB", "GB".
ui: ui:

View File

@ -1,19 +1,18 @@
<!DOCTYPE html> <!DOCTYPE html>
<html th:lang="${#locale.language}" th:dir="#{language.direction}" th:data-language="${#locale.toString()}" xmlns:th="https://www.thymeleaf.org"> <html th:lang="${#locale.language}" th:dir="#{language.direction}" th:data-language="${#locale.toString()}" xmlns:th="https://www.thymeleaf.org">
<head> <head>
<th:block th:insert="~{fragments/common :: head(title=#{adminUserSettings.title}, header=#{adminUserSettings.header})}"></th:block> <th:block th:insert="~{fragments/common :: head(title=#{adminUserSettings.title}, header=#{adminUserSettings.header})}"></th:block>
<link rel="stylesheet" th:href="@{/css/modern-tables.css}">
<style> <style>
.active-user { .active-user {
color: var(--md-sys-color-tertiary); color: green;
font-weight: 600; text-shadow: 0 0 5px green;
} }
.text-overflow { .text-overflow {
max-width: 100px; max-width: 100px;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow:ellipsis;
} }
</style> </style>
</head> </head>
@ -23,354 +22,215 @@
<div id="page-container"> <div id="page-container">
<div id="content-wrap"> <div id="content-wrap">
<th:block th:insert="~{fragments/navbar.html :: navbar}"></th:block> <th:block th:insert="~{fragments/navbar.html :: navbar}"></th:block>
<br><br>
<div class="data-container"> <div class="container">
<div class="data-panel"> <div class="row justify-content-center">
<div class="data-header"> <div class="col-md-9 bg-card">
<h1 class="data-title"> <div class="tool-header">
<span class="data-icon"> <span class="material-symbols-rounded tool-header-icon organize">manage_accounts</span>
<span class="material-symbols-rounded">manage_accounts</span> <span class="tool-header-text" th:text="#{adminUserSettings.header}">Admin User Control Settings</span>
</span> </div>
<span th:text="#{adminUserSettings.header}">Admin User Control Settings</span>
</h1> <!-- User Settings Title -->
</div> <div style="background: var(--md-sys-color-outline-variant);padding: .8rem; margin: 10px 0; border-radius: 2rem; text-align: center;">
<a href="#"
<div class="data-body"> th:data-bs-toggle="${@runningProOrHigher && totalUsers >= maxPaidUsers} ? null : 'modal'"
<!-- User Stats Banner --> th:data-bs-target="${@runningProOrHigher && totalUsers >= maxPaidUsers} ? null : '#addUserModal'"
<div class="data-panel data-mb-3" style="background-color: var(--md-sys-color-primary-container);"> th:class="${@runningProOrHigher && totalUsers >= maxPaidUsers} ? 'btn btn-danger' : 'btn btn-outline-success'"
<div class="data-body" style="padding: 1.25rem;"> th:title="${@runningProOrHigher && totalUsers >= maxPaidUsers} ? #{adminUserSettings.maxUsersReached} : #{adminUserSettings.addUser}">
<div style="display: flex; flex-wrap: wrap; justify-content: space-around; align-items: center; gap: 1.5rem;"> <span class="material-symbols-rounded">person_add</span>
<div style="display: flex; align-items: center; gap: 0.75rem;"> <span th:text="#{adminUserSettings.addUser}">Add New User</span>
<span class="material-symbols-rounded" style="font-size: 2.25rem; color: var(--md-sys-color-primary);"> </a>
group
</span> <a href="#"
<div> data-bs-toggle="modal"
<div style="color: var(--md-sys-color-primary); font-size: 0.875rem; font-weight: 500;" th:text="#{adminUserSettings.totalUsers}">Total Users</div> data-bs-target="#changeUserRoleModal"
<div style="color: var(--md-sys-color-primary); font-size: 1.5rem; font-weight: 700;"> class="btn btn-outline-success"
<span th:text="${totalUsers}"></span> th:title="#{adminUserSettings.changeUserRole}">
<span th:if="${@runningProOrHigher}" th:text="'/' + ${maxPaidUsers}" style="font-size: 1rem;"></span> <span class="material-symbols-rounded">edit</span>
</div> <span th:text="#{adminUserSettings.changeUserRole}">Change User's Role</span>
</div> </a>
</div>
<a th:href="@{'/usage'}" th:if="${@runningEE}"
<div style="display: flex; align-items: center; gap: 0.75rem;"> class="btn btn-outline-success"
<span class="material-symbols-rounded" style="font-size: 2.25rem; color: var(--md-sys-color-primary);"> th:title="#{adminUserSettings.usage}">
check_circle <span class="material-symbols-rounded">analytics</span>
</span> <span th:text="#{adminUserSettings.usage}">Usage Statistics</span>
<div> </a>
<div style="color: var(--md-sys-color-primary); font-size: 0.875rem; font-weight: 500;" th:text="#{adminUserSettings.activeUsers}">Active Users</div>
<div style="color: var(--md-sys-color-primary); font-size: 1.5rem; font-weight: 700;" th:text="${activeUsers}"></div> <div class="my-4">
</div> <strong style="margin-left: 20px;" th:text="#{adminUserSettings.totalUsers}">Total Users:</strong>
</div> <span th:text="${totalUsers}"></span>
<span th:if="${@runningProOrHigher}" th:text="'/'+${maxPaidUsers}"></span>
<div style="display: flex; align-items: center; gap: 0.75rem;">
<span class="material-symbols-rounded" style="font-size: 2.25rem; color: var(--md-sys-color-primary);"> <strong style="margin-left: 20px;" th:text="#{adminUserSettings.activeUsers}">Active Users:</strong>
person_off <span th:text="${activeUsers}"></span>
</span>
<div> <strong style="margin-left: 20px;" th:text="#{adminUserSettings.disabledUsers}">Disabled Users:</strong>
<div style="color: var(--md-sys-color-primary); font-size: 0.875rem; font-weight: 500;" th:text="#{adminUserSettings.disabledUsers}">Disabled Users</div> <span th:text="${disabledUsers}"></span>
<div style="color: var(--md-sys-color-primary); font-size: 1.5rem; font-weight: 700;" th:text="${disabledUsers}"></div> </div>
</div> </div>
</div>
</div>
<div th:if="${addMessage}" class="p-3" style="background: var(--md-sys-color-outline-variant);border-radius: 2rem; text-align: center;">
<div class="alert alert-danger mb-auto">
<span th:text="#{${addMessage}}">Default message if not found</span>
</div> </div>
</div> </div>
<div th:if="${changeMessage}" class="p-3" style="background: var(--md-sys-color-outline-variant);border-radius: 2rem; text-align: center;">
<!-- Alert Messages --> <div class="alert alert-danger mb-auto">
<div th:if="${addMessage}" class="alert alert-danger data-mb-3"> <span th:text="#{${changeMessage}}">Default message if not found</span>
<span th:text="#{${addMessage}}">Default message if not found</span> </div>
</div> </div>
<div th:if="${deleteMessage}" class="alert alert-danger">
<div th:if="${changeMessage}" class="alert alert-danger data-mb-3">
<span th:text="#{${changeMessage}}">Default message if not found</span>
</div>
<div th:if="${deleteMessage}" class="alert alert-danger data-mb-3">
<span th:text="#{${deleteMessage}}">Default message if not found</span> <span th:text="#{${deleteMessage}}">Default message if not found</span>
</div> </div>
<div class="bg-card mt-3 mb-3 table-responsive">
<!-- Admin Actions --> <table class="table table-striped table-hover">
<div class="data-section-title">User Management</div>
<div class="data-actions data-mb-3">
<button
th:data-bs-toggle="${@runningProOrHigher && totalUsers >= maxPaidUsers} ? null : 'modal'"
th:data-bs-target="${@runningProOrHigher && totalUsers >= maxPaidUsers} ? null : '#addUserModal'"
th:class="${@runningProOrHigher && totalUsers >= maxPaidUsers} ? 'data-btn data-btn-danger' : 'data-btn data-btn-primary'"
th:title="${@runningProOrHigher && totalUsers >= maxPaidUsers} ? #{adminUserSettings.maxUsersReached} : #{adminUserSettings.addUser}">
<span class="material-symbols-rounded">person_add</span>
<span th:text="#{adminUserSettings.addUser}">Add New User</span>
</button>
<a href="/teams" class="data-btn data-btn-secondary" th:title="#{adminUserSettings.teams}">
<span class="material-symbols-rounded">group</span>
<span th:text="#{adminUserSettings.teams}">Manage Teams</span>
</a>
<button
data-bs-toggle="modal"
data-bs-target="#changeUserRoleModal"
class="data-btn data-btn-secondary"
th:title="#{adminUserSettings.changeUserRole}">
<span class="material-symbols-rounded">edit</span>
<span th:text="#{adminUserSettings.changeUserRole}">Change User's Role</span>
</button>
<a href="/usage" th:if="${@runningEE}" class="data-btn data-btn-secondary" th:title="#{adminUserSettings.usage}">
<span class="material-symbols-rounded">analytics</span>
<span th:text="#{adminUserSettings.usage}">Usage Statistics</span>
</a>
</div>
<!-- Users Table -->
<div class="table-responsive">
<table class="data-table">
<thead> <thead>
<tr> <tr>
<th scope="col">#</th> <th scope="col">#</th>
<th scope="col" th:title="#{username}" th:text="#{username}">Username</th> <th scope="col" th:title="#{username}" th:text="#{username}">Username</th>
<th scope="col" th:title="#{adminUserSettings.team}" th:text="#{adminUserSettings.team}">Team</th>
<th scope="col" th:title="#{adminUserSettings.roles}" th:text="#{adminUserSettings.roles}">Roles</th> <th scope="col" th:title="#{adminUserSettings.roles}" th:text="#{adminUserSettings.roles}">Roles</th>
<th scope="col" th:title="#{adminUserSettings.authenticated}" class="text-overflow" th:text="#{adminUserSettings.authenticated}">Authenticated</th> <th scope="col" th:title="#{adminUserSettings.authenticated}" class="text-overflow" th:text="#{adminUserSettings.authenticated}">Authenticated</th>
<th scope="col" th:title="#{adminUserSettings.lastRequest}" class="text-overflow" th:text="#{adminUserSettings.lastRequest}">Last Request</th> <th scope="col" th:title="#{adminUserSettings.lastRequest}" class="text-overflow" th:text="#{adminUserSettings.lastRequest}">Last Request</th>
<th scope="col" th:title="#{adminUserSettings.actions}" th:text="#{adminUserSettings.actions}">Actions</th> <th scope="col" th:title="#{adminUserSettings.actions}" th:text="#{adminUserSettings.actions}" colspan="2">Actions</th>
<!-- <th scope="col"></th> -->
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr th:each="user : ${users}"> <tr th:each="user : ${users}">
<td th:text="${user.id}"></td> <th scope="row" style="align-content: center;" th:text="${user.id}"></th>
<td th:text="${user.username}" th:classappend="${userSessions[user.username] ? 'active-user' : ''}"></td> <td style="align-content: center;" th:text="${user.username}" th:classappend="${userSessions[user.username] ? 'active-user' : ''}"></td>
<td th:text="${user.team != null ? user.team.name : '—'}"></td> <td style="align-content: center;" th:text="#{${user.roleName}}"></td>
<td> <td style="align-content: center;" th:text="${user.authenticationType}"></td>
<span class="data-badge" style="background-color: var(--md-sys-color-secondary-container); color: var(--md-sys-color-secondary); padding: 0.25rem 0.5rem; border-radius: 1rem; font-size: 0.875rem; display: inline-flex; align-items: center; gap: 0.25rem;"> <td style="align-content: center;" th:text="${userLastRequest[user.username] != null ? #dates.format(userLastRequest[user.username], 'yyyy-MM-dd HH:mm:ss') : 'N/A'}"></td>
<span class="material-symbols-rounded" style="font-size: 1rem;">shield</span> <td style="align-content: center;">
<span th:text="#{${user.roleName}}">Role</span> <form th:if="${user.username != currentUsername}" th:action="@{'/api/v1/user/admin/deleteUser/' + ${user.username}}" method="post" onsubmit="return confirmDeleteUser()">
</span> <button type="submit" th:title="#{adminUserSettings.deleteUser}" class="btn btn-info btn-sm"><span class="material-symbols-rounded">person_remove</span></button>
</form>
<a th:if="${user.username == currentUsername}" th:title="#{adminUserSettings.editOwnProfil}" th:href="@{'/account'}" class="btn btn-outline-success btn-sm"><span class="material-symbols-rounded">edit</span></a>
</td> </td>
<td th:text="${user.authenticationType}"></td> <td style="align-content: center;">
<td th:text="${userLastRequest[user.username] != null ? #dates.format(userLastRequest[user.username], 'yyyy-MM-dd HH:mm:ss') : 'N/A'}"></td> <form th:action="@{'/api/v1/user/admin/changeUserEnabled/' + ${user.username}}" method="post" onsubmit="return confirmChangeUserStatus()">
<td> <input type="hidden" name="enabled" th:value="!${user.enabled}" />
<div class="data-action-cell"> <button type="submit" th:if="${user.enabled}" th:title="#{adminUserSettings.enabledUser}" class="btn btn-success btn-sm">
<form th:if="${user.username != currentUsername}" th:action="@{'/api/v1/user/admin/deleteUser/' + ${user.username}}" method="post" onsubmit="return confirmDeleteUser()" style="display: inline;"> <span class="material-symbols-rounded">person</span>
<button type="submit" th:title="#{adminUserSettings.deleteUser}" class="data-icon-btn data-icon-btn-danger"> </button>
<span class="material-symbols-rounded">person_remove</span> <button type="submit" th:unless="${user.enabled}" th:title="#{adminUserSettings.disabledUser}" class="btn btn-danger btn-sm">
</button> <span class="material-symbols-rounded">person_off</span>
</form> </button>
</form>
<a th:if="${user.username == currentUsername}" th:title="#{adminUserSettings.editOwnProfil}" th:href="@{'/account'}" class="data-icon-btn data-icon-btn-primary">
<span class="material-symbols-rounded">edit</span>
</a>
<form th:action="@{'/api/v1/user/admin/changeUserEnabled/' + ${user.username}}" method="post" onsubmit="return confirmChangeUserStatus()" style="display: inline;">
<input type="hidden" name="enabled" th:value="!${user.enabled}" />
<button type="submit" th:if="${user.enabled}" th:title="#{adminUserSettings.enabledUser}" class="data-icon-btn data-icon-btn-primary">
<span class="material-symbols-rounded">person</span>
</button>
<button type="submit" th:unless="${user.enabled}" th:title="#{adminUserSettings.disabledUser}" class="data-icon-btn data-icon-btn-danger">
<span class="material-symbols-rounded">person_off</span>
</button>
</form>
</div>
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
</div> </div>
<p th:if="${!@runningProOrHigher}" th:text="#{enterpriseEdition.ssoAdvert}"></p>
<p th:if="${!@runningProOrHigher}" class="data-mt-3" th:text="#{enterpriseEdition.ssoAdvert}"></p>
<script th:inline="javascript">
const delete_confirm_text = /*[[#{adminUserSettings.confirmDeleteUser}]]*/ 'Should the user be deleted?';
const change_confirm_text = /*[[#{adminUserSettings.confirmChangeUserStatus}]]*/ 'Should the user be disabled/enabled?';
function confirmDeleteUser() {
return confirm(delete_confirm_text);
}
function confirmChangeUserStatus() {
return confirm(change_confirm_text);
}
</script>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<!-- Change User Role Modal --> <!-- change User role Modal start -->
<div class="modal fade" id="changeUserRoleModal" tabindex="-1" aria-labelledby="changeUserRoleModalLabel" aria-hidden="true"> <div class="modal fade" id="changeUserRoleModal" tabindex="-1" aria-labelledby="changeUserRoleModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered"> <div class="modal-dialog modal-dialog-centered" role="document">
<form th:action="@{'/api/v1/user/admin/changeRole'}" method="post" class="modal-content data-modal"> <div class="modal-content">
<div class="data-modal-header"> <div class="modal-header">
<h5 class="data-modal-title"> <h2 th:text="#{adminUserSettings.changeUserRole}">Change User's Role</h2>
<span class="data-icon"> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close">
<span class="material-symbols-rounded">edit</span>
</span>
<span th:text="#{adminUserSettings.changeUserRole}">Change User's Role</span>
</h5>
<button type="button" class="data-btn-close" data-bs-dismiss="modal" aria-label="Close">
<span class="material-symbols-rounded">close</span> <span class="material-symbols-rounded">close</span>
</button> </button>
</div> </div>
<div class="data-modal-body"> <div class="modal-body">
<div class="data-mb-2"> <button class="btn btn-outline-info" data-toggle="tooltip" data-placement="auto" th:title="#{downgradeCurrentUserLongMessage}" th:text="#{help}">Help</button>
<button class="data-btn data-btn-secondary" data-toggle="tooltip" data-placement="auto" th:title="#{downgradeCurrentUserLongMessage}" style="padding: 0.25rem 0.5rem;"> <form th:action="@{'/api/v1/user/admin/changeRole'}" method="post">
<span class="material-symbols-rounded">help</span> <div class="mb-3">
<span th:text="#{help}">Help</span> <label for="username" th:text="#{username}">Username</label>
</button> <select name="username" class="form-control" required>
</div> <option value="" disabled selected th:text="#{selectFillter}">-- Select --</option>
<option th:each="user : ${users}" th:if="${user.username != currentUsername}" th:value="${user.username}" th:text="${user.username}">Username</option>
<div class="data-form-group"> </select>
<label for="username" class="data-form-label" th:text="#{username}">Username</label> </div>
<select name="username" id="username" class="data-form-control" required> <div class="mb-3">
<option value="" disabled selected th:text="#{selectFillter}">-- Select --</option> <label for="role" th:text="#{adminUserSettings.role}">Role</label>
<option th:each="user : ${users}" th:if="${user.username != currentUsername}" th:value="${user.username}" th:text="${user.username}">Username</option> <select name="role" class="form-control" required>
</select> <option value="" disabled selected th:text="#{selectFillter}">-- Select --</option>
</div> <option th:each="roleDetail : ${roleDetails}" th:value="${roleDetail.key}" th:text="#{${roleDetail.value}}">Role</option>
</select>
<div class="data-form-group"> </div>
<label for="role" class="data-form-label" th:text="#{adminUserSettings.role}">Role</label>
<select name="role" id="role" class="data-form-control" required> <!-- Add other fields as required -->
<option value="" disabled selected th:text="#{selectFillter}">-- Select --</option> <button type="submit" class="btn btn-primary" th:text="#{adminUserSettings.submit}">Save User</button>
<option th:each="roleDetail : ${roleDetails}" th:value="${roleDetail.key}" th:text="#{${roleDetail.value}}">Role</option> </form>
</select>
</div>
<div class="data-form-group">
<label for="team" class="data-form-label" th:text="#{adminUserSettings.team}">Team</label>
<select name="teamId" id="team" class="data-form-control" required>
<option value="" th:text="#{selectFillter}">-- Select --</option>
<option th:each="team : ${teams}" th:value="${team.id}" th:text="${team.name}"></option>
</select>
</div>
<div class="data-form-actions">
<button type="button" class="data-btn data-btn-secondary" data-bs-dismiss="modal">
<span class="material-symbols-rounded">close</span>
<span th:text="#{cancel}">Cancel</span>
</button>
<button type="submit" class="data-btn data-btn-primary">
<span class="material-symbols-rounded">check</span>
<span th:text="#{adminUserSettings.submit}">Save User</span>
</button>
</div>
</div> </div>
</form> <div class="modal-footer"></div>
</div>
</div> </div>
</div> </div>
<!-- change User role Modal end -->
<!-- Add User Modal --> <!-- Add User Modal start -->
<div class="modal fade" id="addUserModal" tabindex="-1" aria-labelledby="addUserModalLabel" aria-hidden="true"> <div class="modal fade" id="addUserModal" tabindex="-1" aria-labelledby="addUserModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered"> <div class="modal-dialog modal-dialog-centered" role="document">
<form id="formsaveuser" th:action="@{'/api/v1/user/admin/saveUser'}" method="post" class="modal-content data-modal"> <div class="modal-content">
<div class="data-modal-header"> <div class="modal-header">
<h5 class="data-modal-title"> <h5 class="modal-title" id="addUserModalLabel" th:text="#{adminUserSettings.addUser}">Add New User</h5>
<span class="data-icon"> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close">
<span class="material-symbols-rounded">person_add</span>
</span>
<span th:text="#{adminUserSettings.addUser}">Add New User</span>
</h5>
<button type="button" class="data-btn-close" data-bs-dismiss="modal" aria-label="Close">
<span class="material-symbols-rounded">close</span> <span class="material-symbols-rounded">close</span>
</button> </button>
</div> </div>
<div class="data-modal-body"> <div class="modal-body">
<div class="data-mb-2"> <button class="btn btn-outline-info" data-toggle="tooltip" data-placement="auto" th:title="#{adminUserSettings.usernameInfo}" th:text="#{help}">Help</button>
<button class="data-btn data-btn-secondary" data-toggle="tooltip" data-placement="auto" th:title="#{adminUserSettings.usernameInfo}" style="padding: 0.25rem 0.5rem;"> <form id="formsaveuser" th:action="@{'/api/v1/user/admin/saveUser'}" method="post">
<span class="material-symbols-rounded">help</span> <div class="mb-3">
<span th:text="#{help}">Help</span> <label for="username" th:text="#{username}">Username</label>
</button> <input type="text" class="form-control" name="username" id="username" th:title="#{adminUserSettings.usernameInfo}" required>
</div> <span id="usernameError" style="display: none;" th:text="#{invalidUsernameMessage}">Invalid username!</span>
</div>
<div class="data-form-group"> <div class="mb-3" id="passwordContainer">
<label for="username" class="data-form-label" th:text="#{username}">Username</label> <label for="password" th:text="#{password}">Password</label>
<input type="text" class="data-form-control" name="username" id="username" th:title="#{adminUserSettings.usernameInfo}" required> <input type="password" class="form-control" name="password" id="password" required>
<span id="usernameError" style="display: none; color: var(--md-sys-color-error);" th:text="#{invalidUsernameMessage}">Invalid username!</span> </div>
</div> <div class="mb-3">
<label for="role" th:text="#{adminUserSettings.role}">Role</label>
<div class="data-form-group" id="passwordContainer"> <select name="role" class="form-control" id="role" required>
<label for="password" class="data-form-label" th:text="#{password}">Password</label> <option value="" disabled selected th:text="#{selectFillter}">-- Select --</option>
<input type="password" class="data-form-control" name="password" id="password" required> <option th:each="roleDetail : ${roleDetails}" th:value="${roleDetail.key}" th:text="#{${roleDetail.value}}">Role</option>
</div> </select>
</div>
<div class="data-form-group"> <div class="mb-3">
<label for="role" class="data-form-label" th:text="#{adminUserSettings.role}">Role</label> <label for="authType">Authentication Type</label>
<select name="role" class="data-form-control" id="role" required> <select id="authType" name="authType" class="form-control" required>
<option value="" disabled selected th:text="#{selectFillter}">-- Select --</option> <option value="web" selected>WEB</option>
<option th:each="roleDetail : ${roleDetails}" th:value="${roleDetail.key}" th:text="#{${roleDetail.value}}">Role</option> <option value="sso">SSO</option>
</select> </select>
</div> </div>
<div class="form-check mb-3" id="checkboxContainer">
<div class="data-form-group">
<label for="team" class="data-form-label" th:text="#{adminUserSettings.team}">Team</label>
<select name="teamId" class="data-form-control" required>
<option value="" th:text="#{selectFillter}">-- Select --</option>
<option th:each="team : ${teams}" th:value="${team.id}" th:text="${team.name}"></option>
</select>
</div>
<div class="data-form-group">
<label for="authType" class="data-form-label">Authentication Type</label>
<select id="authType" name="authType" class="data-form-control" required>
<option value="web" selected>WEB</option>
<option value="sso">SSO</option>
</select>
</div>
<div class="data-form-group" id="checkboxContainer">
<div class="form-check">
<input type="checkbox" class="form-check-input" id="forceChange" name="forceChange"> <input type="checkbox" class="form-check-input" id="forceChange" name="forceChange">
<label class="form-check-label" for="forceChange" th:text="#{adminUserSettings.forceChange}">Force user to change username/password on login</label> <label class="form-check-label" for="forceChange" th:text="#{adminUserSettings.forceChange}">Force user to change username/password on login</label>
</div> </div>
</div> <button type="submit" class="btn btn-primary" th:text="#{adminUserSettings.submit}">Save User</button>
</form>
<div class="data-form-actions">
<button type="button" class="data-btn data-btn-secondary" data-bs-dismiss="modal">
<span class="material-symbols-rounded">close</span>
<span th:text="#{cancel}">Cancel</span>
</button>
<button type="submit" class="data-btn data-btn-primary">
<span class="material-symbols-rounded">check</span>
<span th:text="#{adminUserSettings.submit}">Save User</span>
</button>
</div>
</div> </div>
</form> <div class="modal-footer"></div>
</div> </div>
</div>
<!-- Add Team Modal -->
<div class="modal fade" id="addTeamModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<form th:action="@{'/api/v1/team/create'}" method="post" class="modal-content data-modal">
<div class="data-modal-header">
<h5 class="data-modal-title">
<span class="data-icon">
<span class="material-symbols-rounded">group_add</span>
</span>
<span th:text="#{adminUserSettings.createTeam}">Create Team</span>
</h5>
<button type="button" class="data-btn-close" data-bs-dismiss="modal" aria-label="Close">
<span class="material-symbols-rounded">close</span>
</button>
</div>
<div class="data-modal-body">
<div class="data-form-group">
<label for="teamName" class="data-form-label" th:text="#{adminUserSettings.teamName}">Team Name</label>
<input type="text" name="name" id="teamName" class="data-form-control" required />
</div>
<div class="data-form-actions">
<button type="button" class="data-btn data-btn-secondary" data-bs-dismiss="modal">
<span class="material-symbols-rounded">close</span>
<span th:text="#{cancel}">Cancel</span>
</button>
<button type="submit" class="data-btn data-btn-primary">
<span class="material-symbols-rounded">check</span>
<span th:text="#{adminUserSettings.submit}">Create</span>
</button>
</div>
</div>
</form>
</div> </div>
</div> </div>
<!-- Add User Modal end -->
<script th:inline="javascript"> <script th:inline="javascript">
const delete_confirm_text = /*[[#{adminUserSettings.confirmDeleteUser}]]*/ 'Should the user be deleted?';
const change_confirm_text = /*[[#{adminUserSettings.confirmChangeUserStatus}]]*/ 'Should the user be disabled/enabled?';
function confirmDeleteUser() {
return confirm(delete_confirm_text);
}
function confirmChangeUserStatus() {
return confirm(change_confirm_text);
}
jQuery.validator.addMethod("usernamePattern", function(value, element) { jQuery.validator.addMethod("usernamePattern", function(value, element) {
// Regular expression for user name: Min. 3 characters, max. 50 characters // Regular expression for user name: Min. 3 characters, max. 50 characters
const regexUsername = /^[a-zA-Z0-9](?!.*[-@._+]{2,})([a-zA-Z0-9@._+-]{1,48})[a-zA-Z0-9]$/; const regexUsername = /^[a-zA-Z0-9](?!.*[-@._+]{2,})([a-zA-Z0-9@._+-]{1,48})[a-zA-Z0-9]$/;
@ -381,7 +241,6 @@
// Check if the field is optional or meets the requirements // Check if the field is optional or meets the requirements
return this.optional(element) || regexUsername.test(value) || regexEmail.test(value); return this.optional(element) || regexUsername.test(value) || regexEmail.test(value);
}, /*[[#{invalidUsernameMessage}]]*/ "Invalid username format"); }, /*[[#{invalidUsernameMessage}]]*/ "Invalid username format");
$(document).ready(function() { $(document).ready(function() {
$('[data-toggle="tooltip"]').tooltip(); $('[data-toggle="tooltip"]').tooltip();
@ -402,9 +261,9 @@
} }
}, },
messages: { messages: {
username: { username: {
usernamePattern: /*[[#{invalidUsernameMessage}]]*/ "Invalid username format" usernamePattern: /*[[#{invalidUsernameMessage}]]*/ "Invalid username format"
}, },
}, },
errorPlacement: function(error, element) { errorPlacement: function(error, element) {
if (element.attr("name") === "username") { if (element.attr("name") === "username") {
@ -421,40 +280,40 @@
}); });
$('#username').on('input', function() { $('#username').on('input', function() {
var usernameInput = $(this); var usernameInput = $(this);
var isValid = usernameInput[0].checkValidity(); var isValid = usernameInput[0].checkValidity();
var errorSpan = $('#usernameError'); var errorSpan = $('#usernameError');
if (isValid) { if (isValid) {
usernameInput.removeClass('invalid').addClass('valid'); usernameInput.removeClass('invalid').addClass('valid');
errorSpan.hide(); errorSpan.hide();
} else { } else {
usernameInput.removeClass('valid').addClass('invalid'); usernameInput.removeClass('valid').addClass('invalid');
errorSpan.show(); errorSpan.show();
} }
}); });
$('#authType').on('change', function() { $('#authType').on('change', function() {
var authType = $(this).val(); var authType = $(this).val();
var passwordField = $('#password'); var passwordField = $('#password');
var passwordFieldContainer = $('#passwordContainer'); var passwordFieldContainer = $('#passwordContainer');
var checkboxContainer = $('#checkboxContainer'); var checkboxContainer = $('#checkboxContainer');
if (authType === 'sso') { if (authType === 'sso') {
passwordField.removeAttr('required'); passwordField.removeAttr('required');
passwordField.prop('disabled', true).val(''); passwordField.prop('disabled', true).val('');
passwordFieldContainer.slideUp('fast'); passwordFieldContainer.slideUp('fast');
checkboxContainer.slideUp('fast'); checkboxContainer.slideUp('fast');
} else { } else {
passwordField.prop('disabled', false); passwordField.prop('disabled', false);
passwordField.attr('required', 'required'); passwordField.attr('required', 'required');
passwordFieldContainer.slideDown('fast'); passwordFieldContainer.slideDown('fast');
checkboxContainer.slideDown('fast'); checkboxContainer.slideDown('fast');
} }
}); });
}); });
</script> </script>
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block> <th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
</div> </div>
</body> </body>
</html> </html>

View File

@ -19,7 +19,7 @@
<link rel="stylesheet" th:href="@{'/pdfjs-legacy/css/viewer-redact.css'}"> <link rel="stylesheet" th:href="@{'/pdfjs-legacy/css/viewer-redact.css'}">
<script th:src="@{'/pdfjs-legacy/js/viewer.mjs'}" type="module"></script> <script th:src="@{'/pdfjs-legacy/js/viewer.mjs'}" type="module"></script>
<script src='./js/redact.js' type="module"></script> <script th:src="@{'/js/redact.js'}" type="module"></script>
<link rel="stylesheet" th:href="@{'/css/redact.css'}"> <link rel="stylesheet" th:href="@{'/css/redact.css'}">
</head> </head>
@ -695,4 +695,4 @@
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block> <th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
</body> </body>
</html> </html>

View File

@ -11,7 +11,6 @@
# If you want to override with environment parameter follow parameter naming SECURITY_INITIALLOGIN_USERNAME # # If you want to override with environment parameter follow parameter naming SECURITY_INITIALLOGIN_USERNAME #
############################################################################################################# #############################################################################################################
security: security:
enableLogin: false # set to 'true' to enable login enableLogin: false # set to 'true' to enable login
csrfDisabled: false # set to 'true' to disable CSRF protection (not recommended for production) csrfDisabled: false # set to 'true' to disable CSRF protection (not recommended for production)
@ -62,18 +61,32 @@ security:
privateKey: classpath:saml-private-key.key # Your private key. Generated from your keypair privateKey: classpath:saml-private-key.key # Your private key. Generated from your keypair
spCert: classpath:saml-public-cert.crt # Your signing certificate. Generated from your keypair spCert: classpath:saml-public-cert.crt # Your signing certificate. Generated from your keypair
enterpriseEdition: premium:
enabled: false # set to 'true' to enable enterprise edition
key: 00000000-0000-0000-0000-000000000000 key: 00000000-0000-0000-0000-000000000000
SSOAutoLogin: false # Enable to auto login to first provided SSO enabled: false # Enable license key checks for pro/enterprise features
CustomMetadata: proFeatures:
autoUpdateMetadata: false # set to 'true' to automatically update metadata with below values SSOAutoLogin: false
author: username # supports text such as 'John Doe' or types such as username to autopopulate with user's username CustomMetadata:
creator: Stirling-PDF # supports text such as 'Company-PDF' autoUpdateMetadata: false # set to 'true' to automatically update metadata with below values
producer: Stirling-PDF # supports text such as 'Company-PDF' author: username # supports text such as 'John Doe' or types such as username to autopopulate with user's username
creator: Stirling-PDF # supports text such as 'Company-PDF'
producer: Stirling-PDF # supports text such as 'Company-PDF'
googleDrive:
enabled: false
clientId: ''
apiKey: ''
appId: ''
mail:
enabled: false # set to 'true' to enable sending emails
host: smtp.example.com # SMTP server hostname
port: 587 # SMTP server port
username: '' # SMTP server username
password: '' # SMTP server password
from: '' # sender email address
legal: legal:
termsAndConditions: https://www.stirlingpdf.com/terms-and-conditions # URL to the terms and conditions of your application (e.g. https://example.com/terms). Empty string to disable or filename to load from local file in static folder termsAndConditions: https://www.stirlingpdf.com/terms # URL to the terms and conditions of your application (e.g. https://example.com/terms). Empty string to disable or filename to load from local file in static folder
privacyPolicy: https://www.stirlingpdf.com/privacy-policy # URL to the privacy policy of your application (e.g. https://example.com/privacy). Empty string to disable or filename to load from local file in static folder privacyPolicy: https://www.stirlingpdf.com/privacy-policy # URL to the privacy policy of your application (e.g. https://example.com/privacy). Empty string to disable or filename to load from local file in static folder
accessibilityStatement: '' # URL to the accessibility statement of your application (e.g. https://example.com/accessibility). Empty string to disable or filename to load from local file in static folder accessibilityStatement: '' # URL to the accessibility statement of your application (e.g. https://example.com/accessibility). Empty string to disable or filename to load from local file in static folder
cookiePolicy: '' # URL to the cookie policy of your application (e.g. https://example.com/cookie). Empty string to disable or filename to load from local file in static folder cookiePolicy: '' # URL to the cookie policy of your application (e.g. https://example.com/cookie). Empty string to disable or filename to load from local file in static folder
@ -88,6 +101,7 @@ system:
customHTMLFiles: false # enable to have files placed in /customFiles/templates override the existing template HTML files customHTMLFiles: false # enable to have files placed in /customFiles/templates override the existing template HTML files
tessdataDir: /usr/share/tessdata # path to the directory containing the Tessdata files. This setting is relevant for Windows systems. For Windows users, this path should be adjusted to point to the appropriate directory where the Tessdata files are stored. tessdataDir: /usr/share/tessdata # path to the directory containing the Tessdata files. This setting is relevant for Windows systems. For Windows users, this path should be adjusted to point to the appropriate directory where the Tessdata files are stored.
enableAnalytics: true # set to 'true' to enable analytics, set to 'false' to disable analytics; for enterprise users, this is set to true enableAnalytics: true # set to 'true' to enable analytics, set to 'false' to disable analytics; for enterprise users, this is set to true
enableUrlToPDF: false # Set to 'true' to enable URL to PDF, INTERNAL ONLY, known security issues, should not be used externally
disableSanitize: false # set to true to disable Sanitize HTML; (can lead to injections in HTML) disableSanitize: false # set to true to disable Sanitize HTML; (can lead to injections in HTML)
datasource: datasource:
enableCustomDatabase: false # Enterprise users ONLY, set this property to 'true' if you would like to use your own custom database configuration enableCustomDatabase: false # Enterprise users ONLY, set this property to 'true' if you would like to use your own custom database configuration
@ -100,13 +114,12 @@ system:
name: postgres # set the name of your database. Should match the name of the database you create name: postgres # set the name of your database. Should match the name of the database you create
customPaths: customPaths:
pipeline: pipeline:
watchedFoldersDir: "" #Defaults to /pipeline/watchedFolders watchedFoldersDir: '' # Defaults to /pipeline/watchedFolders
finishedFoldersDir: "" #Defaults to /pipeline/finishedFolders finishedFoldersDir: '' # Defaults to /pipeline/finishedFolders
operations: operations:
weasyprint: "" #Defaults to /opt/venv/bin/weasyprint weasyprint: '' # Defaults to /opt/venv/bin/weasyprint
unoconvert: "" #Defaults to /opt/venv/bin/unoconvert unoconvert: '' # Defaults to /opt/venv/bin/unoconvert
fileUploadLimit: '' # Defaults to "". No limit when string is empty. Set a number, between 0 and 999, followed by one of the following strings to set a limit. "KB", "MB", "GB".
ui: ui:
appName: '' # application's visible name appName: '' # application's visible name