mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2026-02-17 13:52:14 +01:00
cleanups
This commit is contained in:
@@ -3504,7 +3504,7 @@
|
||||
"saved": "Settings saved successfully",
|
||||
"saveSuccess": "Settings saved successfully",
|
||||
"save": "Save Changes",
|
||||
"restartRequired": "Settings changes require a server restart to take effect.",
|
||||
"restartRequired": "Restart Required",
|
||||
"restart": {
|
||||
"title": "Restart Required",
|
||||
"message": "Settings have been saved successfully. A server restart is required for the changes to take effect.",
|
||||
|
||||
@@ -259,32 +259,34 @@ export default function AdminAdvancedSection() {
|
||||
<Text fw={600} size="sm" mb="xs">{t('admin.settings.advanced.processing', 'Processing')}</Text>
|
||||
|
||||
<div>
|
||||
<Group gap="xs" align="flex-end">
|
||||
<NumberInput
|
||||
label={t('admin.settings.advanced.maxDPI', 'Maximum DPI')}
|
||||
description={t('admin.settings.advanced.maxDPI.description', 'Maximum DPI for image processing (0 = unlimited)')}
|
||||
value={settings.maxDPI || 0}
|
||||
onChange={(value) => setSettings({ ...settings, maxDPI: Number(value) })}
|
||||
min={0}
|
||||
max={3000}
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
<PendingBadge show={isFieldPending('maxDPI')} />
|
||||
</Group>
|
||||
<NumberInput
|
||||
label={
|
||||
<Group gap="xs">
|
||||
<span>{t('admin.settings.advanced.maxDPI', 'Maximum DPI')}</span>
|
||||
<PendingBadge show={isFieldPending('maxDPI')} />
|
||||
</Group>
|
||||
}
|
||||
description={t('admin.settings.advanced.maxDPI.description', 'Maximum DPI for image processing (0 = unlimited)')}
|
||||
value={settings.maxDPI || 0}
|
||||
onChange={(value) => setSettings({ ...settings, maxDPI: Number(value) })}
|
||||
min={0}
|
||||
max={3000}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Group gap="xs" align="flex-end">
|
||||
<TextInput
|
||||
label={t('admin.settings.advanced.tessdataDir', 'Tessdata Directory')}
|
||||
description={t('admin.settings.advanced.tessdataDir.description', 'Path to the directory containing Tessdata files for OCR')}
|
||||
value={settings.tessdataDir || ''}
|
||||
onChange={(e) => setSettings({ ...settings, tessdataDir: e.target.value })}
|
||||
placeholder="/usr/share/tessdata"
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
<PendingBadge show={isFieldPending('tessdataDir')} />
|
||||
</Group>
|
||||
<TextInput
|
||||
label={
|
||||
<Group gap="xs">
|
||||
<span>{t('admin.settings.advanced.tessdataDir', 'Tessdata Directory')}</span>
|
||||
<PendingBadge show={isFieldPending('tessdataDir')} />
|
||||
</Group>
|
||||
}
|
||||
description={t('admin.settings.advanced.tessdataDir.description', 'Path to the directory containing Tessdata files for OCR')}
|
||||
value={settings.tessdataDir || ''}
|
||||
onChange={(e) => setSettings({ ...settings, tessdataDir: e.target.value })}
|
||||
placeholder="/usr/share/tessdata"
|
||||
/>
|
||||
</div>
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
@@ -128,17 +128,25 @@ export default function AdminDatabaseSection() {
|
||||
{t('admin.settings.database.enableCustom.description', 'Use your own custom database configuration instead of the default embedded database')}
|
||||
</Text>
|
||||
</div>
|
||||
<Switch
|
||||
checked={settings.enableCustomDatabase || false}
|
||||
onChange={(e) => setSettings({ ...settings, enableCustomDatabase: e.target.checked })}
|
||||
/>
|
||||
<Group gap="xs">
|
||||
<Switch
|
||||
checked={settings.enableCustomDatabase || false}
|
||||
onChange={(e) => setSettings({ ...settings, enableCustomDatabase: e.target.checked })}
|
||||
/>
|
||||
<PendingBadge show={isFieldPending('enableCustomDatabase')} />
|
||||
</Group>
|
||||
</div>
|
||||
|
||||
{settings.enableCustomDatabase && (
|
||||
<>
|
||||
<div>
|
||||
<TextInput
|
||||
label={t('admin.settings.database.customUrl', 'Custom Database URL')}
|
||||
label={
|
||||
<Group gap="xs">
|
||||
<span>{t('admin.settings.database.customUrl', 'Custom Database URL')}</span>
|
||||
<PendingBadge show={isFieldPending('customDatabaseUrl')} />
|
||||
</Group>
|
||||
}
|
||||
description={t('admin.settings.database.customUrl.description', 'Full JDBC connection string (e.g., jdbc:postgresql://localhost:5432/postgres). If provided, individual connection settings below are not used.')}
|
||||
value={settings.customDatabaseUrl || ''}
|
||||
onChange={(e) => setSettings({ ...settings, customDatabaseUrl: e.target.value })}
|
||||
@@ -148,7 +156,12 @@ export default function AdminDatabaseSection() {
|
||||
|
||||
<div>
|
||||
<Select
|
||||
label={t('admin.settings.database.type', 'Database Type')}
|
||||
label={
|
||||
<Group gap="xs">
|
||||
<span>{t('admin.settings.database.type', 'Database Type')}</span>
|
||||
<PendingBadge show={isFieldPending('type')} />
|
||||
</Group>
|
||||
}
|
||||
description={t('admin.settings.database.type.description', 'Type of database (not used if custom URL is provided)')}
|
||||
value={settings.type || 'postgresql'}
|
||||
onChange={(value) => setSettings({ ...settings, type: value || 'postgresql' })}
|
||||
@@ -163,7 +176,12 @@ export default function AdminDatabaseSection() {
|
||||
|
||||
<div>
|
||||
<TextInput
|
||||
label={t('admin.settings.database.hostName', 'Host Name')}
|
||||
label={
|
||||
<Group gap="xs">
|
||||
<span>{t('admin.settings.database.hostName', 'Host Name')}</span>
|
||||
<PendingBadge show={isFieldPending('hostName')} />
|
||||
</Group>
|
||||
}
|
||||
description={t('admin.settings.database.hostName.description', 'Database server hostname (not used if custom URL is provided)')}
|
||||
value={settings.hostName || ''}
|
||||
onChange={(e) => setSettings({ ...settings, hostName: e.target.value })}
|
||||
@@ -173,7 +191,12 @@ export default function AdminDatabaseSection() {
|
||||
|
||||
<div>
|
||||
<NumberInput
|
||||
label={t('admin.settings.database.port', 'Port')}
|
||||
label={
|
||||
<Group gap="xs">
|
||||
<span>{t('admin.settings.database.port', 'Port')}</span>
|
||||
<PendingBadge show={isFieldPending('port')} />
|
||||
</Group>
|
||||
}
|
||||
description={t('admin.settings.database.port.description', 'Database server port (not used if custom URL is provided)')}
|
||||
value={settings.port || 5432}
|
||||
onChange={(value) => setSettings({ ...settings, port: Number(value) })}
|
||||
@@ -184,7 +207,12 @@ export default function AdminDatabaseSection() {
|
||||
|
||||
<div>
|
||||
<TextInput
|
||||
label={t('admin.settings.database.name', 'Database Name')}
|
||||
label={
|
||||
<Group gap="xs">
|
||||
<span>{t('admin.settings.database.name', 'Database Name')}</span>
|
||||
<PendingBadge show={isFieldPending('name')} />
|
||||
</Group>
|
||||
}
|
||||
description={t('admin.settings.database.name.description', 'Name of the database (not used if custom URL is provided)')}
|
||||
value={settings.name || ''}
|
||||
onChange={(e) => setSettings({ ...settings, name: e.target.value })}
|
||||
@@ -194,7 +222,12 @@ export default function AdminDatabaseSection() {
|
||||
|
||||
<div>
|
||||
<TextInput
|
||||
label={t('admin.settings.database.username', 'Username')}
|
||||
label={
|
||||
<Group gap="xs">
|
||||
<span>{t('admin.settings.database.username', 'Username')}</span>
|
||||
<PendingBadge show={isFieldPending('username')} />
|
||||
</Group>
|
||||
}
|
||||
description={t('admin.settings.database.username.description', 'Database authentication username')}
|
||||
value={settings.username || ''}
|
||||
onChange={(e) => setSettings({ ...settings, username: e.target.value })}
|
||||
@@ -204,7 +237,12 @@ export default function AdminDatabaseSection() {
|
||||
|
||||
<div>
|
||||
<PasswordInput
|
||||
label={t('admin.settings.database.password', 'Password')}
|
||||
label={
|
||||
<Group gap="xs">
|
||||
<span>{t('admin.settings.database.password', 'Password')}</span>
|
||||
<PendingBadge show={isFieldPending('password')} />
|
||||
</Group>
|
||||
}
|
||||
description={t('admin.settings.database.password.description', 'Database authentication password')}
|
||||
value={settings.password || ''}
|
||||
onChange={(e) => setSettings({ ...settings, password: e.target.value })}
|
||||
|
||||
@@ -114,39 +114,41 @@ export default function AdminEndpointsSection() {
|
||||
<Text fw={600} size="sm" mb="xs">{t('admin.settings.endpoints.management', 'Endpoint Management')}</Text>
|
||||
|
||||
<div>
|
||||
<Group gap="xs" align="flex-start">
|
||||
<MultiSelect
|
||||
label={t('admin.settings.endpoints.toRemove', 'Disabled Endpoints')}
|
||||
description={t('admin.settings.endpoints.toRemove.description', 'Select individual endpoints to disable')}
|
||||
value={settings.toRemove || []}
|
||||
onChange={(value) => setSettings({ ...settings, toRemove: value })}
|
||||
data={commonEndpoints.map(endpoint => ({ value: endpoint, label: endpoint }))}
|
||||
searchable
|
||||
clearable
|
||||
placeholder="Select endpoints to disable"
|
||||
comboboxProps={{ zIndex: 1400 }}
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
<PendingBadge show={isFieldPending('toRemove')} />
|
||||
</Group>
|
||||
<MultiSelect
|
||||
label={
|
||||
<Group gap="xs">
|
||||
<span>{t('admin.settings.endpoints.toRemove', 'Disabled Endpoints')}</span>
|
||||
<PendingBadge show={isFieldPending('toRemove')} />
|
||||
</Group>
|
||||
}
|
||||
description={t('admin.settings.endpoints.toRemove.description', 'Select individual endpoints to disable')}
|
||||
value={settings.toRemove || []}
|
||||
onChange={(value) => setSettings({ ...settings, toRemove: value })}
|
||||
data={commonEndpoints.map(endpoint => ({ value: endpoint, label: endpoint }))}
|
||||
searchable
|
||||
clearable
|
||||
placeholder="Select endpoints to disable"
|
||||
comboboxProps={{ zIndex: 1400 }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Group gap="xs" align="flex-start">
|
||||
<MultiSelect
|
||||
label={t('admin.settings.endpoints.groupsToRemove', 'Disabled Endpoint Groups')}
|
||||
description={t('admin.settings.endpoints.groupsToRemove.description', 'Select endpoint groups to disable')}
|
||||
value={settings.groupsToRemove || []}
|
||||
onChange={(value) => setSettings({ ...settings, groupsToRemove: value })}
|
||||
data={commonGroups.map(group => ({ value: group, label: group }))}
|
||||
searchable
|
||||
clearable
|
||||
placeholder="Select groups to disable"
|
||||
comboboxProps={{ zIndex: 1400 }}
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
<PendingBadge show={isFieldPending('groupsToRemove')} />
|
||||
</Group>
|
||||
<MultiSelect
|
||||
label={
|
||||
<Group gap="xs">
|
||||
<span>{t('admin.settings.endpoints.groupsToRemove', 'Disabled Endpoint Groups')}</span>
|
||||
<PendingBadge show={isFieldPending('groupsToRemove')} />
|
||||
</Group>
|
||||
}
|
||||
description={t('admin.settings.endpoints.groupsToRemove.description', 'Select endpoint groups to disable')}
|
||||
value={settings.groupsToRemove || []}
|
||||
onChange={(value) => setSettings({ ...settings, groupsToRemove: value })}
|
||||
data={commonGroups.map(group => ({ value: group, label: group }))}
|
||||
searchable
|
||||
clearable
|
||||
placeholder="Select groups to disable"
|
||||
comboboxProps={{ zIndex: 1400 }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Paper bg="var(--mantine-color-blue-light)" p="sm" radius="sm">
|
||||
|
||||
@@ -134,38 +134,40 @@ export default function AdminFeaturesSection() {
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Group gap="xs" align="flex-end">
|
||||
<TextInput
|
||||
label={t('admin.settings.features.serverCertificate.organizationName', 'Organization Name')}
|
||||
description={t('admin.settings.features.serverCertificate.organizationName.description', 'Organization name for generated certificates')}
|
||||
value={settings.serverCertificate?.organizationName || 'Stirling-PDF'}
|
||||
onChange={(e) => setSettings({
|
||||
...settings,
|
||||
serverCertificate: { ...settings.serverCertificate, organizationName: e.target.value }
|
||||
})}
|
||||
placeholder="Stirling-PDF"
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
<PendingBadge show={isFieldPending('serverCertificate.organizationName')} />
|
||||
</Group>
|
||||
<TextInput
|
||||
label={
|
||||
<Group gap="xs">
|
||||
<span>{t('admin.settings.features.serverCertificate.organizationName', 'Organization Name')}</span>
|
||||
<PendingBadge show={isFieldPending('serverCertificate.organizationName')} />
|
||||
</Group>
|
||||
}
|
||||
description={t('admin.settings.features.serverCertificate.organizationName.description', 'Organization name for generated certificates')}
|
||||
value={settings.serverCertificate?.organizationName || 'Stirling-PDF'}
|
||||
onChange={(e) => setSettings({
|
||||
...settings,
|
||||
serverCertificate: { ...settings.serverCertificate, organizationName: e.target.value }
|
||||
})}
|
||||
placeholder="Stirling-PDF"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Group gap="xs" align="flex-end">
|
||||
<NumberInput
|
||||
label={t('admin.settings.features.serverCertificate.validity', 'Certificate Validity (days)')}
|
||||
description={t('admin.settings.features.serverCertificate.validity.description', 'Number of days the certificate will be valid')}
|
||||
value={settings.serverCertificate?.validity ?? 365}
|
||||
onChange={(value) => setSettings({
|
||||
...settings,
|
||||
serverCertificate: { ...settings.serverCertificate, validity: Number(value) }
|
||||
})}
|
||||
min={1}
|
||||
max={3650}
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
<PendingBadge show={isFieldPending('serverCertificate.validity')} />
|
||||
</Group>
|
||||
<NumberInput
|
||||
label={
|
||||
<Group gap="xs">
|
||||
<span>{t('admin.settings.features.serverCertificate.validity', 'Certificate Validity (days)')}</span>
|
||||
<PendingBadge show={isFieldPending('serverCertificate.validity')} />
|
||||
</Group>
|
||||
}
|
||||
description={t('admin.settings.features.serverCertificate.validity.description', 'Number of days the certificate will be valid')}
|
||||
value={settings.serverCertificate?.validity ?? 365}
|
||||
onChange={(value) => setSettings({
|
||||
...settings,
|
||||
serverCertificate: { ...settings.serverCertificate, validity: Number(value) }
|
||||
})}
|
||||
min={1}
|
||||
max={3650}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
|
||||
@@ -178,7 +178,12 @@ export default function AdminGeneralSection() {
|
||||
|
||||
<div>
|
||||
<TextInput
|
||||
label={t('admin.settings.general.appNameNavbar', 'Navbar Brand')}
|
||||
label={
|
||||
<Group gap="xs">
|
||||
<span>{t('admin.settings.general.appNameNavbar', 'Navbar Brand')}</span>
|
||||
<PendingBadge show={isFieldPending('ui.appNameNavbar')} />
|
||||
</Group>
|
||||
}
|
||||
description={t('admin.settings.general.appNameNavbar.description', 'The name displayed in the navigation bar')}
|
||||
value={settings.ui.appNameNavbar || ''}
|
||||
onChange={(e) => setSettings({ ...settings, ui: { ...settings.ui, appNameNavbar: e.target.value } })}
|
||||
@@ -188,7 +193,12 @@ export default function AdminGeneralSection() {
|
||||
|
||||
<div>
|
||||
<MultiSelect
|
||||
label={t('admin.settings.general.languages', 'Available Languages')}
|
||||
label={
|
||||
<Group gap="xs">
|
||||
<span>{t('admin.settings.general.languages', 'Available Languages')}</span>
|
||||
<PendingBadge show={isFieldPending('ui.languages')} />
|
||||
</Group>
|
||||
}
|
||||
description={t('admin.settings.general.languages.description', 'Limit which languages are available (empty = all languages)')}
|
||||
value={settings.ui.languages || []}
|
||||
onChange={(value) => setSettings({ ...settings, ui: { ...settings.ui, languages: value } })}
|
||||
@@ -213,7 +223,12 @@ export default function AdminGeneralSection() {
|
||||
|
||||
<div>
|
||||
<TextInput
|
||||
label={t('admin.settings.general.defaultLocale', 'Default Locale')}
|
||||
label={
|
||||
<Group gap="xs">
|
||||
<span>{t('admin.settings.general.defaultLocale', 'Default Locale')}</span>
|
||||
<PendingBadge show={isFieldPending('system.defaultLocale')} />
|
||||
</Group>
|
||||
}
|
||||
description={t('admin.settings.general.defaultLocale.description', 'The default language for new users (e.g., en_US, es_ES)')}
|
||||
value={settings.system.defaultLocale || ''}
|
||||
onChange={(e) => setSettings({ ...settings, system: { ...settings.system, defaultLocale: e.target.value } })}
|
||||
@@ -223,7 +238,12 @@ export default function AdminGeneralSection() {
|
||||
|
||||
<div>
|
||||
<TextInput
|
||||
label={t('admin.settings.general.fileUploadLimit', 'File Upload Limit')}
|
||||
label={
|
||||
<Group gap="xs">
|
||||
<span>{t('admin.settings.general.fileUploadLimit', 'File Upload Limit')}</span>
|
||||
<PendingBadge show={isFieldPending('system.fileUploadLimit')} />
|
||||
</Group>
|
||||
}
|
||||
description={t('admin.settings.general.fileUploadLimit.description', 'Maximum file upload size (e.g., 100MB, 1GB)')}
|
||||
value={settings.system.fileUploadLimit || ''}
|
||||
onChange={(e) => setSettings({ ...settings, system: { ...settings.system, fileUploadLimit: e.target.value } })}
|
||||
@@ -238,10 +258,13 @@ export default function AdminGeneralSection() {
|
||||
{t('admin.settings.general.showUpdate.description', 'Display notifications when a new version is available')}
|
||||
</Text>
|
||||
</div>
|
||||
<Switch
|
||||
checked={settings.system.showUpdate || false}
|
||||
onChange={(e) => setSettings({ ...settings, system: { ...settings.system, showUpdate: e.target.checked } })}
|
||||
/>
|
||||
<Group gap="xs">
|
||||
<Switch
|
||||
checked={settings.system.showUpdate || false}
|
||||
onChange={(e) => setSettings({ ...settings, system: { ...settings.system, showUpdate: e.target.checked } })}
|
||||
/>
|
||||
<PendingBadge show={isFieldPending('system.showUpdate')} />
|
||||
</Group>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
@@ -251,10 +274,13 @@ export default function AdminGeneralSection() {
|
||||
{t('admin.settings.general.showUpdateOnlyAdmin.description', 'Restrict update notifications to admin users only')}
|
||||
</Text>
|
||||
</div>
|
||||
<Switch
|
||||
checked={settings.system.showUpdateOnlyAdmin || false}
|
||||
onChange={(e) => setSettings({ ...settings, system: { ...settings.system, showUpdateOnlyAdmin: e.target.checked } })}
|
||||
/>
|
||||
<Group gap="xs">
|
||||
<Switch
|
||||
checked={settings.system.showUpdateOnlyAdmin || false}
|
||||
onChange={(e) => setSettings({ ...settings, system: { ...settings.system, showUpdateOnlyAdmin: e.target.checked } })}
|
||||
/>
|
||||
<PendingBadge show={isFieldPending('system.showUpdateOnlyAdmin')} />
|
||||
</Group>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
@@ -264,10 +290,13 @@ export default function AdminGeneralSection() {
|
||||
{t('admin.settings.general.customHTMLFiles.description', 'Allow serving custom HTML files from the customFiles directory')}
|
||||
</Text>
|
||||
</div>
|
||||
<Switch
|
||||
checked={settings.system.customHTMLFiles || false}
|
||||
onChange={(e) => setSettings({ ...settings, system: { ...settings.system, customHTMLFiles: e.target.checked } })}
|
||||
/>
|
||||
<Group gap="xs">
|
||||
<Switch
|
||||
checked={settings.system.customHTMLFiles || false}
|
||||
onChange={(e) => setSettings({ ...settings, system: { ...settings.system, customHTMLFiles: e.target.checked } })}
|
||||
/>
|
||||
<PendingBadge show={isFieldPending('system.customHTMLFiles')} />
|
||||
</Group>
|
||||
</div>
|
||||
</Stack>
|
||||
</Paper>
|
||||
@@ -287,21 +316,29 @@ export default function AdminGeneralSection() {
|
||||
{t('admin.settings.general.customMetadata.autoUpdate.description', 'Automatically update PDF metadata on all processed documents')}
|
||||
</Text>
|
||||
</div>
|
||||
<Switch
|
||||
checked={settings.customMetadata?.autoUpdateMetadata || false}
|
||||
onChange={(e) => setSettings({
|
||||
...settings,
|
||||
customMetadata: {
|
||||
...settings.customMetadata,
|
||||
autoUpdateMetadata: e.target.checked
|
||||
}
|
||||
})}
|
||||
/>
|
||||
<Group gap="xs">
|
||||
<Switch
|
||||
checked={settings.customMetadata?.autoUpdateMetadata || false}
|
||||
onChange={(e) => setSettings({
|
||||
...settings,
|
||||
customMetadata: {
|
||||
...settings.customMetadata,
|
||||
autoUpdateMetadata: e.target.checked
|
||||
}
|
||||
})}
|
||||
/>
|
||||
<PendingBadge show={isFieldPending('customMetadata.autoUpdateMetadata')} />
|
||||
</Group>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<TextInput
|
||||
label={t('admin.settings.general.customMetadata.author', 'Default Author')}
|
||||
label={
|
||||
<Group gap="xs">
|
||||
<span>{t('admin.settings.general.customMetadata.author', 'Default Author')}</span>
|
||||
<PendingBadge show={isFieldPending('customMetadata.author')} />
|
||||
</Group>
|
||||
}
|
||||
description={t('admin.settings.general.customMetadata.author.description', 'Default author for PDF metadata (e.g., username)')}
|
||||
value={settings.customMetadata?.author || ''}
|
||||
onChange={(e) => setSettings({
|
||||
@@ -317,7 +354,12 @@ export default function AdminGeneralSection() {
|
||||
|
||||
<div>
|
||||
<TextInput
|
||||
label={t('admin.settings.general.customMetadata.creator', 'Default Creator')}
|
||||
label={
|
||||
<Group gap="xs">
|
||||
<span>{t('admin.settings.general.customMetadata.creator', 'Default Creator')}</span>
|
||||
<PendingBadge show={isFieldPending('customMetadata.creator')} />
|
||||
</Group>
|
||||
}
|
||||
description={t('admin.settings.general.customMetadata.creator.description', 'Default creator for PDF metadata')}
|
||||
value={settings.customMetadata?.creator || ''}
|
||||
onChange={(e) => setSettings({
|
||||
@@ -333,7 +375,12 @@ export default function AdminGeneralSection() {
|
||||
|
||||
<div>
|
||||
<TextInput
|
||||
label={t('admin.settings.general.customMetadata.producer', 'Default Producer')}
|
||||
label={
|
||||
<Group gap="xs">
|
||||
<span>{t('admin.settings.general.customMetadata.producer', 'Default Producer')}</span>
|
||||
<PendingBadge show={isFieldPending('customMetadata.producer')} />
|
||||
</Group>
|
||||
}
|
||||
description={t('admin.settings.general.customMetadata.producer.description', 'Default producer for PDF metadata')}
|
||||
value={settings.customMetadata?.producer || ''}
|
||||
onChange={(e) => setSettings({
|
||||
@@ -363,7 +410,12 @@ export default function AdminGeneralSection() {
|
||||
|
||||
<div>
|
||||
<TextInput
|
||||
label={t('admin.settings.general.customPaths.pipeline.watchedFoldersDir', 'Watched Folders Directory')}
|
||||
label={
|
||||
<Group gap="xs">
|
||||
<span>{t('admin.settings.general.customPaths.pipeline.watchedFoldersDir', 'Watched Folders Directory')}</span>
|
||||
<PendingBadge show={isFieldPending('customPaths.pipeline.watchedFoldersDir')} />
|
||||
</Group>
|
||||
}
|
||||
description={t('admin.settings.general.customPaths.pipeline.watchedFoldersDir.description', 'Directory where pipeline monitors for incoming PDFs (leave empty for default: /pipeline/watchedFolders)')}
|
||||
value={settings.customPaths?.pipeline?.watchedFoldersDir || ''}
|
||||
onChange={(e) => setSettings({
|
||||
@@ -382,7 +434,12 @@ export default function AdminGeneralSection() {
|
||||
|
||||
<div>
|
||||
<TextInput
|
||||
label={t('admin.settings.general.customPaths.pipeline.finishedFoldersDir', 'Finished Folders Directory')}
|
||||
label={
|
||||
<Group gap="xs">
|
||||
<span>{t('admin.settings.general.customPaths.pipeline.finishedFoldersDir', 'Finished Folders Directory')}</span>
|
||||
<PendingBadge show={isFieldPending('customPaths.pipeline.finishedFoldersDir')} />
|
||||
</Group>
|
||||
}
|
||||
description={t('admin.settings.general.customPaths.pipeline.finishedFoldersDir.description', 'Directory where processed PDFs are outputted (leave empty for default: /pipeline/finishedFolders)')}
|
||||
value={settings.customPaths?.pipeline?.finishedFoldersDir || ''}
|
||||
onChange={(e) => setSettings({
|
||||
@@ -403,7 +460,12 @@ export default function AdminGeneralSection() {
|
||||
|
||||
<div>
|
||||
<TextInput
|
||||
label={t('admin.settings.general.customPaths.operations.weasyprint', 'WeasyPrint Executable')}
|
||||
label={
|
||||
<Group gap="xs">
|
||||
<span>{t('admin.settings.general.customPaths.operations.weasyprint', 'WeasyPrint Executable')}</span>
|
||||
<PendingBadge show={isFieldPending('customPaths.operations.weasyprint')} />
|
||||
</Group>
|
||||
}
|
||||
description={t('admin.settings.general.customPaths.operations.weasyprint.description', 'Path to WeasyPrint executable for HTML to PDF conversion (leave empty for default: /opt/venv/bin/weasyprint)')}
|
||||
value={settings.customPaths?.operations?.weasyprint || ''}
|
||||
onChange={(e) => setSettings({
|
||||
@@ -422,7 +484,12 @@ export default function AdminGeneralSection() {
|
||||
|
||||
<div>
|
||||
<TextInput
|
||||
label={t('admin.settings.general.customPaths.operations.unoconvert', 'Unoconvert Executable')}
|
||||
label={
|
||||
<Group gap="xs">
|
||||
<span>{t('admin.settings.general.customPaths.operations.unoconvert', 'Unoconvert Executable')}</span>
|
||||
<PendingBadge show={isFieldPending('customPaths.operations.unoconvert')} />
|
||||
</Group>
|
||||
}
|
||||
description={t('admin.settings.general.customPaths.operations.unoconvert.description', 'Path to LibreOffice unoconvert for document conversions (leave empty for default: /opt/venv/bin/unoconvert)')}
|
||||
value={settings.customPaths?.operations?.unoconvert || ''}
|
||||
onChange={(e) => setSettings({
|
||||
|
||||
@@ -84,73 +84,78 @@ export default function AdminLegalSection() {
|
||||
<Paper withBorder p="md" radius="md">
|
||||
<Stack gap="md">
|
||||
<div>
|
||||
<Group gap="xs" align="flex-end">
|
||||
<TextInput
|
||||
label={t('admin.settings.legal.termsAndConditions', 'Terms and Conditions')}
|
||||
description={t('admin.settings.legal.termsAndConditions.description', 'URL or filename to terms and conditions')}
|
||||
value={settings.termsAndConditions || ''}
|
||||
onChange={(e) => setSettings({ ...settings, termsAndConditions: e.target.value })}
|
||||
placeholder="https://example.com/terms"
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
<PendingBadge show={isFieldPending('termsAndConditions')} />
|
||||
</Group>
|
||||
<TextInput
|
||||
label={
|
||||
<Group gap="xs">
|
||||
<span>{t('admin.settings.legal.termsAndConditions', 'Terms and Conditions')}</span>
|
||||
<PendingBadge show={isFieldPending('termsAndConditions')} />
|
||||
</Group>
|
||||
}
|
||||
description={t('admin.settings.legal.termsAndConditions.description', 'URL or filename to terms and conditions')}
|
||||
value={settings.termsAndConditions || ''}
|
||||
onChange={(e) => setSettings({ ...settings, termsAndConditions: e.target.value })}
|
||||
placeholder="https://example.com/terms"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Group gap="xs" align="flex-end">
|
||||
<TextInput
|
||||
label={t('admin.settings.legal.privacyPolicy', 'Privacy Policy')}
|
||||
description={t('admin.settings.legal.privacyPolicy.description', 'URL or filename to privacy policy')}
|
||||
value={settings.privacyPolicy || ''}
|
||||
onChange={(e) => setSettings({ ...settings, privacyPolicy: e.target.value })}
|
||||
placeholder="https://example.com/privacy"
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
<PendingBadge show={isFieldPending('privacyPolicy')} />
|
||||
</Group>
|
||||
<TextInput
|
||||
label={
|
||||
<Group gap="xs">
|
||||
<span>{t('admin.settings.legal.privacyPolicy', 'Privacy Policy')}</span>
|
||||
<PendingBadge show={isFieldPending('privacyPolicy')} />
|
||||
</Group>
|
||||
}
|
||||
description={t('admin.settings.legal.privacyPolicy.description', 'URL or filename to privacy policy')}
|
||||
value={settings.privacyPolicy || ''}
|
||||
onChange={(e) => setSettings({ ...settings, privacyPolicy: e.target.value })}
|
||||
placeholder="https://example.com/privacy"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Group gap="xs" align="flex-end">
|
||||
<TextInput
|
||||
label={t('admin.settings.legal.accessibilityStatement', 'Accessibility Statement')}
|
||||
description={t('admin.settings.legal.accessibilityStatement.description', 'URL or filename to accessibility statement')}
|
||||
value={settings.accessibilityStatement || ''}
|
||||
onChange={(e) => setSettings({ ...settings, accessibilityStatement: e.target.value })}
|
||||
placeholder="https://example.com/accessibility"
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
<PendingBadge show={isFieldPending('accessibilityStatement')} />
|
||||
</Group>
|
||||
<TextInput
|
||||
label={
|
||||
<Group gap="xs">
|
||||
<span>{t('admin.settings.legal.accessibilityStatement', 'Accessibility Statement')}</span>
|
||||
<PendingBadge show={isFieldPending('accessibilityStatement')} />
|
||||
</Group>
|
||||
}
|
||||
description={t('admin.settings.legal.accessibilityStatement.description', 'URL or filename to accessibility statement')}
|
||||
value={settings.accessibilityStatement || ''}
|
||||
onChange={(e) => setSettings({ ...settings, accessibilityStatement: e.target.value })}
|
||||
placeholder="https://example.com/accessibility"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Group gap="xs" align="flex-end">
|
||||
<TextInput
|
||||
label={t('admin.settings.legal.cookiePolicy', 'Cookie Policy')}
|
||||
description={t('admin.settings.legal.cookiePolicy.description', 'URL or filename to cookie policy')}
|
||||
value={settings.cookiePolicy || ''}
|
||||
onChange={(e) => setSettings({ ...settings, cookiePolicy: e.target.value })}
|
||||
placeholder="https://example.com/cookies"
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
<PendingBadge show={isFieldPending('cookiePolicy')} />
|
||||
</Group>
|
||||
<TextInput
|
||||
label={
|
||||
<Group gap="xs">
|
||||
<span>{t('admin.settings.legal.cookiePolicy', 'Cookie Policy')}</span>
|
||||
<PendingBadge show={isFieldPending('cookiePolicy')} />
|
||||
</Group>
|
||||
}
|
||||
description={t('admin.settings.legal.cookiePolicy.description', 'URL or filename to cookie policy')}
|
||||
value={settings.cookiePolicy || ''}
|
||||
onChange={(e) => setSettings({ ...settings, cookiePolicy: e.target.value })}
|
||||
placeholder="https://example.com/cookies"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Group gap="xs" align="flex-end">
|
||||
<TextInput
|
||||
label={t('admin.settings.legal.impressum', 'Impressum')}
|
||||
description={t('admin.settings.legal.impressum.description', 'URL or filename to impressum (required in some jurisdictions)')}
|
||||
value={settings.impressum || ''}
|
||||
onChange={(e) => setSettings({ ...settings, impressum: e.target.value })}
|
||||
placeholder="https://example.com/impressum"
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
<PendingBadge show={isFieldPending('impressum')} />
|
||||
</Group>
|
||||
<TextInput
|
||||
label={
|
||||
<Group gap="xs">
|
||||
<span>{t('admin.settings.legal.impressum', 'Impressum')}</span>
|
||||
<PendingBadge show={isFieldPending('impressum')} />
|
||||
</Group>
|
||||
}
|
||||
description={t('admin.settings.legal.impressum.description', 'URL or filename to impressum (required in some jurisdictions)')}
|
||||
value={settings.impressum || ''}
|
||||
onChange={(e) => setSettings({ ...settings, impressum: e.target.value })}
|
||||
placeholder="https://example.com/impressum"
|
||||
/>
|
||||
</div>
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
@@ -85,72 +85,77 @@ export default function AdminMailSection() {
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Group gap="xs" align="flex-end">
|
||||
<TextInput
|
||||
label={t('admin.settings.mail.host', 'SMTP Host')}
|
||||
description={t('admin.settings.mail.host.description', 'SMTP server hostname')}
|
||||
value={settings.host || ''}
|
||||
onChange={(e) => setSettings({ ...settings, host: e.target.value })}
|
||||
placeholder="smtp.example.com"
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
<PendingBadge show={isFieldPending('host')} />
|
||||
</Group>
|
||||
<TextInput
|
||||
label={
|
||||
<Group gap="xs">
|
||||
<span>{t('admin.settings.mail.host', 'SMTP Host')}</span>
|
||||
<PendingBadge show={isFieldPending('host')} />
|
||||
</Group>
|
||||
}
|
||||
description={t('admin.settings.mail.host.description', 'SMTP server hostname')}
|
||||
value={settings.host || ''}
|
||||
onChange={(e) => setSettings({ ...settings, host: e.target.value })}
|
||||
placeholder="smtp.example.com"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Group gap="xs" align="flex-end">
|
||||
<NumberInput
|
||||
label={t('admin.settings.mail.port', 'SMTP Port')}
|
||||
description={t('admin.settings.mail.port.description', 'SMTP server port (typically 587 for TLS, 465 for SSL)')}
|
||||
value={settings.port || 587}
|
||||
onChange={(value) => setSettings({ ...settings, port: Number(value) })}
|
||||
min={1}
|
||||
max={65535}
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
<PendingBadge show={isFieldPending('port')} />
|
||||
</Group>
|
||||
<NumberInput
|
||||
label={
|
||||
<Group gap="xs">
|
||||
<span>{t('admin.settings.mail.port', 'SMTP Port')}</span>
|
||||
<PendingBadge show={isFieldPending('port')} />
|
||||
</Group>
|
||||
}
|
||||
description={t('admin.settings.mail.port.description', 'SMTP server port (typically 587 for TLS, 465 for SSL)')}
|
||||
value={settings.port || 587}
|
||||
onChange={(value) => setSettings({ ...settings, port: Number(value) })}
|
||||
min={1}
|
||||
max={65535}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Group gap="xs" align="flex-end">
|
||||
<TextInput
|
||||
label={t('admin.settings.mail.username', 'SMTP Username')}
|
||||
description={t('admin.settings.mail.username.description', 'SMTP authentication username')}
|
||||
value={settings.username || ''}
|
||||
onChange={(e) => setSettings({ ...settings, username: e.target.value })}
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
<PendingBadge show={isFieldPending('username')} />
|
||||
</Group>
|
||||
<TextInput
|
||||
label={
|
||||
<Group gap="xs">
|
||||
<span>{t('admin.settings.mail.username', 'SMTP Username')}</span>
|
||||
<PendingBadge show={isFieldPending('username')} />
|
||||
</Group>
|
||||
}
|
||||
description={t('admin.settings.mail.username.description', 'SMTP authentication username')}
|
||||
value={settings.username || ''}
|
||||
onChange={(e) => setSettings({ ...settings, username: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Group gap="xs" align="flex-end">
|
||||
<PasswordInput
|
||||
label={t('admin.settings.mail.password', 'SMTP Password')}
|
||||
description={t('admin.settings.mail.password.description', 'SMTP authentication password')}
|
||||
value={settings.password || ''}
|
||||
onChange={(e) => setSettings({ ...settings, password: e.target.value })}
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
<PendingBadge show={isFieldPending('password')} />
|
||||
</Group>
|
||||
<PasswordInput
|
||||
label={
|
||||
<Group gap="xs">
|
||||
<span>{t('admin.settings.mail.password', 'SMTP Password')}</span>
|
||||
<PendingBadge show={isFieldPending('password')} />
|
||||
</Group>
|
||||
}
|
||||
description={t('admin.settings.mail.password.description', 'SMTP authentication password')}
|
||||
value={settings.password || ''}
|
||||
onChange={(e) => setSettings({ ...settings, password: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Group gap="xs" align="flex-end">
|
||||
<TextInput
|
||||
label={t('admin.settings.mail.from', 'From Address')}
|
||||
description={t('admin.settings.mail.from.description', 'Email address to use as sender')}
|
||||
value={settings.from || ''}
|
||||
onChange={(e) => setSettings({ ...settings, from: e.target.value })}
|
||||
placeholder="noreply@example.com"
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
<PendingBadge show={isFieldPending('from')} />
|
||||
</Group>
|
||||
<TextInput
|
||||
label={
|
||||
<Group gap="xs">
|
||||
<span>{t('admin.settings.mail.from', 'From Address')}</span>
|
||||
<PendingBadge show={isFieldPending('from')} />
|
||||
</Group>
|
||||
}
|
||||
description={t('admin.settings.mail.from.description', 'Email address to use as sender')}
|
||||
value={settings.from || ''}
|
||||
onChange={(e) => setSettings({ ...settings, from: e.target.value })}
|
||||
placeholder="noreply@example.com"
|
||||
/>
|
||||
</div>
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
@@ -87,17 +87,18 @@ export default function AdminPremiumSection() {
|
||||
<Text fw={600} size="sm" mb="xs">{t('admin.settings.premium.license', 'License Configuration')}</Text>
|
||||
|
||||
<div>
|
||||
<Group gap="xs" align="flex-end">
|
||||
<TextInput
|
||||
label={t('admin.settings.premium.key', 'License Key')}
|
||||
description={t('admin.settings.premium.key.description', 'Enter your premium or enterprise license key')}
|
||||
value={settings.key || ''}
|
||||
onChange={(e) => setSettings({ ...settings, key: e.target.value })}
|
||||
placeholder="00000000-0000-0000-0000-000000000000"
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
<PendingBadge show={isFieldPending('key')} />
|
||||
</Group>
|
||||
<TextInput
|
||||
label={
|
||||
<Group gap="xs">
|
||||
<span>{t('admin.settings.premium.key', 'License Key')}</span>
|
||||
<PendingBadge show={isFieldPending('key')} />
|
||||
</Group>
|
||||
}
|
||||
description={t('admin.settings.premium.key.description', 'Enter your premium or enterprise license key')}
|
||||
value={settings.key || ''}
|
||||
onChange={(e) => setSettings({ ...settings, key: e.target.value })}
|
||||
placeholder="00000000-0000-0000-0000-000000000000"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
|
||||
Reference in New Issue
Block a user