diff --git a/common/src/main/java/stirling/software/common/service/TempFileCleanupService.java b/common/src/main/java/stirling/software/common/service/TempFileCleanupService.java index d53c4ea84..4eba52f29 100644 --- a/common/src/main/java/stirling/software/common/service/TempFileCleanupService.java +++ b/common/src/main/java/stirling/software/common/service/TempFileCleanupService.java @@ -72,12 +72,12 @@ public class TempFileCleanupService { fileName -> fileName.contains("jetty") || fileName.startsWith("jetty-") - || fileName.equals("proc") - || fileName.equals("sys") - || fileName.equals("dev") - || fileName.equals("hsperfdata_stirlingpdfuser") + || "proc".equals(fileName) + || "sys".equals(fileName) + || "dev".equals(fileName) + || "hsperfdata_stirlingpdfuser".equals(fileName) || fileName.startsWith("hsperfdata_") - || fileName.equals(".pdfbox.cache"); + || ".pdfbox.cache".equals(fileName); @PostConstruct public void init() { @@ -308,7 +308,7 @@ public class TempFileCleanupService { } java.util.List subdirectories = new java.util.ArrayList<>(); - + try (Stream pathStream = Files.list(directory)) { pathStream.forEach( path -> { @@ -347,7 +347,7 @@ public class TempFileCleanupService { } }); } - + for (Path subdirectory : subdirectories) { try { cleanupDirectoryStreaming( diff --git a/common/src/main/java/stirling/software/common/util/TempFileRegistry.java b/common/src/main/java/stirling/software/common/util/TempFileRegistry.java index 1e55c6b15..323b3bff3 100644 --- a/common/src/main/java/stirling/software/common/util/TempFileRegistry.java +++ b/common/src/main/java/stirling/software/common/util/TempFileRegistry.java @@ -9,7 +9,6 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.ConcurrentSkipListSet; import java.util.stream.Collectors; import org.springframework.stereotype.Component; @@ -24,11 +23,10 @@ import lombok.extern.slf4j.Slf4j; @Component public class TempFileRegistry { - private final ConcurrentMap registeredFiles = new ConcurrentHashMap<>(); - private final Set thirdPartyTempFiles = - Collections.newSetFromMap(new ConcurrentHashMap<>()); - private final Set tempDirectories = - Collections.newSetFromMap(new ConcurrentHashMap<>()); + private final ConcurrentMap registeredFiles = new ConcurrentHashMap<>(); + private final Set thirdPartyTempFiles = + Collections.newSetFromMap(new ConcurrentHashMap<>()); + private final Set tempDirectories = Collections.newSetFromMap(new ConcurrentHashMap<>()); /** * Register a temporary file with the registry. diff --git a/frontend/src/components/shared/AppConfigModal.tsx b/frontend/src/components/shared/AppConfigModal.tsx new file mode 100644 index 000000000..a73e3ff73 --- /dev/null +++ b/frontend/src/components/shared/AppConfigModal.tsx @@ -0,0 +1,140 @@ +import React from 'react'; +import { Modal, Button, Stack, Text, Code, ScrollArea, Group, Badge, Alert, Loader } from '@mantine/core'; +import { useAppConfig } from '../../hooks/useAppConfig'; + +interface AppConfigModalProps { + opened: boolean; + onClose: () => void; +} + +const AppConfigModal: React.FC = ({ opened, onClose }) => { + const { config, loading, error, refetch } = useAppConfig(); + + const renderConfigSection = (title: string, data: any) => { + if (!data || typeof data !== 'object') return null; + + return ( + + {title} + + {Object.entries(data).map(([key, value]) => ( + + + {key}: + + {typeof value === 'boolean' ? ( + + {value ? 'true' : 'false'} + + ) : typeof value === 'object' ? ( + {JSON.stringify(value, null, 2)} + ) : ( + String(value) || 'null' + )} + + ))} + + + ); + }; + + const basicConfig = config ? { + appName: config.appName, + appNameNavbar: config.appNameNavbar, + baseUrl: config.baseUrl, + contextPath: config.contextPath, + serverPort: config.serverPort, + } : null; + + const securityConfig = config ? { + enableLogin: config.enableLogin, + } : null; + + const systemConfig = config ? { + enableAlphaFunctionality: config.enableAlphaFunctionality, + enableAnalytics: config.enableAnalytics, + } : null; + + const premiumConfig = config ? { + premiumEnabled: config.premiumEnabled, + premiumKey: config.premiumKey ? '***hidden***' : null, + runningProOrHigher: config.runningProOrHigher, + runningEE: config.runningEE, + license: config.license, + } : null; + + const integrationConfig = config ? { + GoogleDriveEnabled: config.GoogleDriveEnabled, + SSOAutoLogin: config.SSOAutoLogin, + } : null; + + const legalConfig = config ? { + termsAndConditions: config.termsAndConditions, + privacyPolicy: config.privacyPolicy, + cookiePolicy: config.cookiePolicy, + impressum: config.impressum, + accessibilityStatement: config.accessibilityStatement, + } : null; + + return ( + + + + + This modal shows the current application configuration for testing purposes only. + + + + + {loading && ( + + + Loading configuration... + + )} + + {error && ( + + {error} + + )} + + {config && ( + + + {renderConfigSection('Basic Configuration', basicConfig)} + {renderConfigSection('Security Configuration', securityConfig)} + {renderConfigSection('System Configuration', systemConfig)} + {renderConfigSection('Premium/Enterprise Configuration', premiumConfig)} + {renderConfigSection('Integration Configuration', integrationConfig)} + {renderConfigSection('Legal Configuration', legalConfig)} + + {config.error && ( + + {config.error} + + )} + + + Raw Configuration + + {JSON.stringify(config, null, 2)} + + + + + )} + + + ); +}; + +export default AppConfigModal; \ No newline at end of file diff --git a/frontend/src/components/shared/QuickAccessBar.tsx b/frontend/src/components/shared/QuickAccessBar.tsx index 9c904cb66..45f3b28c7 100644 --- a/frontend/src/components/shared/QuickAccessBar.tsx +++ b/frontend/src/components/shared/QuickAccessBar.tsx @@ -1,12 +1,11 @@ -import React from "react"; +import React, { useState } from "react"; import { ActionIcon, Stack, Tooltip } from "@mantine/core"; -import AddToPhotosIcon from "@mui/icons-material/AddToPhotos"; -import ContentCutIcon from "@mui/icons-material/ContentCut"; -import ZoomInMapIcon from "@mui/icons-material/ZoomInMap"; import MenuBookIcon from "@mui/icons-material/MenuBook"; import AppsIcon from "@mui/icons-material/Apps"; +import SettingsIcon from "@mui/icons-material/Settings"; import { useRainbowThemeContext } from "./RainbowThemeProvider"; import rainbowStyles from '../../styles/rainbow.module.css'; +import AppConfigModal from './AppConfigModal'; interface QuickAccessBarProps { onToolsClick: () => void; @@ -26,6 +25,7 @@ const QuickAccessBar = ({ readerMode, }: QuickAccessBarProps) => { const { isRainbowMode } = useRainbowThemeContext(); + const [configModalOpen, setConfigModalOpen] = useState(false); return (
+ + {/* Config Modal Button (for testing) */} +
+ setConfigModalOpen(true)} + > + + + Config +
+ + setConfigModalOpen(false)} + />
); }; diff --git a/frontend/src/hooks/useAppConfig.ts b/frontend/src/hooks/useAppConfig.ts new file mode 100644 index 000000000..899038f51 --- /dev/null +++ b/frontend/src/hooks/useAppConfig.ts @@ -0,0 +1,77 @@ +import { useState, useEffect } from 'react'; + +export interface AppConfig { + baseUrl?: string; + contextPath?: string; + serverPort?: number; + appName?: string; + appNameNavbar?: string; + homeDescription?: string; + languages?: string[]; + enableLogin?: boolean; + enableAlphaFunctionality?: boolean; + enableAnalytics?: boolean; + premiumEnabled?: boolean; + premiumKey?: string; + termsAndConditions?: string; + privacyPolicy?: string; + cookiePolicy?: string; + impressum?: string; + accessibilityStatement?: string; + runningProOrHigher?: boolean; + runningEE?: boolean; + license?: string; + GoogleDriveEnabled?: boolean; + SSOAutoLogin?: boolean; + error?: string; +} + +interface UseAppConfigReturn { + config: AppConfig | null; + loading: boolean; + error: string | null; + refetch: () => Promise; +} + +/** + * Custom hook to fetch and manage application configuration + */ +export function useAppConfig(): UseAppConfigReturn { + const [config, setConfig] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + const fetchConfig = async () => { + try { + setLoading(true); + setError(null); + + const response = await fetch('/api/v1/config/app-config'); + + if (!response.ok) { + throw new Error(`Failed to fetch config: ${response.status} ${response.statusText}`); + } + + const data: AppConfig = await response.json(); + setConfig(data); + } catch (err) { + const errorMessage = err instanceof Error ? err.message : 'Unknown error occurred'; + setError(errorMessage); + console.error('Failed to fetch app config:', err); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + fetchConfig(); + }, []); + + return { + config, + loading, + error, + refetch: fetchConfig, + }; +} + diff --git a/frontend/src/hooks/useEndpointConfig.ts b/frontend/src/hooks/useEndpointConfig.ts new file mode 100644 index 000000000..13f13764b --- /dev/null +++ b/frontend/src/hooks/useEndpointConfig.ts @@ -0,0 +1,117 @@ +import { useState, useEffect } from 'react'; + +/** + * Hook to check if a specific endpoint is enabled + */ +export function useEndpointEnabled(endpoint: string): { + enabled: boolean | null; + loading: boolean; + error: string | null; + refetch: () => Promise; +} { + const [enabled, setEnabled] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + const fetchEndpointStatus = async () => { + if (!endpoint) { + setEnabled(null); + setLoading(false); + return; + } + + try { + setLoading(true); + setError(null); + + const response = await fetch(`/api/v1/config/endpoint-enabled?endpoint=${encodeURIComponent(endpoint)}`); + + if (!response.ok) { + throw new Error(`Failed to check endpoint: ${response.status} ${response.statusText}`); + } + + const isEnabled: boolean = await response.json(); + setEnabled(isEnabled); + } catch (err) { + const errorMessage = err instanceof Error ? err.message : 'Unknown error occurred'; + setError(errorMessage); + console.error(`Failed to check endpoint ${endpoint}:`, err); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + fetchEndpointStatus(); + }, [endpoint]); + + return { + enabled, + loading, + error, + refetch: fetchEndpointStatus, + }; +} + +/** + * Hook to check multiple endpoints at once using batch API + * Returns a map of endpoint -> enabled status + */ +export function useMultipleEndpointsEnabled(endpoints: string[]): { + endpointStatus: Record; + loading: boolean; + error: string | null; + refetch: () => Promise; +} { + const [endpointStatus, setEndpointStatus] = useState>({}); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + const fetchAllEndpointStatuses = async () => { + if (!endpoints || endpoints.length === 0) { + setEndpointStatus({}); + setLoading(false); + return; + } + + try { + setLoading(true); + setError(null); + + // Use batch API for efficiency + const endpointsParam = endpoints.join(','); + const response = await fetch(`/api/v1/config/endpoints-enabled?endpoints=${encodeURIComponent(endpointsParam)}`); + + if (!response.ok) { + throw new Error(`Failed to check endpoints: ${response.status} ${response.statusText}`); + } + + const statusMap: Record = await response.json(); + setEndpointStatus(statusMap); + } catch (err) { + const errorMessage = err instanceof Error ? err.message : 'Unknown error occurred'; + setError(errorMessage); + console.error('Failed to check multiple endpoints:', err); + + // Fallback: assume all endpoints are disabled on error + const fallbackStatus = endpoints.reduce((acc, endpoint) => { + acc[endpoint] = false; + return acc; + }, {} as Record); + setEndpointStatus(fallbackStatus); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + fetchAllEndpointStatuses(); + }, [endpoints.join(',')]); // Re-run when endpoints array changes + + return { + endpointStatus, + loading, + error, + refetch: fetchAllEndpointStatuses, + }; +} \ No newline at end of file diff --git a/frontend/src/pages/HomePage.tsx b/frontend/src/pages/HomePage.tsx index 515676bd8..a6b5277f6 100644 --- a/frontend/src/pages/HomePage.tsx +++ b/frontend/src/pages/HomePage.tsx @@ -24,6 +24,7 @@ import CompressPdfPanel from "../tools/Compress"; import MergePdfPanel from "../tools/Merge"; import ToolRenderer from "../components/tools/ToolRenderer"; import QuickAccessBar from "../components/shared/QuickAccessBar"; +import { useMultipleEndpointsEnabled } from "../hooks/useEndpointConfig"; type ToolRegistryEntry = { icon: React.ReactNode; @@ -43,6 +44,13 @@ const baseToolRegistry = { merge: { icon: , component: MergePdfPanel, view: "fileManager" }, }; +// Tool endpoint mappings +const toolEndpoints: Record = { + split: ["split-pages", "split-pdf-by-sections", "split-by-size-or-count", "split-pdf-by-chapters"], + compress: ["compress-pdf"], + merge: ["merge-pdfs"], +}; + export default function HomePage() { const { t } = useTranslation(); const [searchParams] = useSearchParams(); @@ -69,6 +77,10 @@ export default function HomePage() { // URL parameter management const { toolParams, updateParams } = useToolParams(selectedToolKey, currentView); + // Get all unique endpoints for batch checking + const allEndpoints = Array.from(new Set(Object.values(toolEndpoints).flat())); + const { endpointStatus, loading: endpointsLoading } = useMultipleEndpointsEnabled(allEndpoints); + // Persist active files across reloads useEffect(() => { // Save active files to localStorage (just metadata) @@ -110,12 +122,41 @@ export default function HomePage() { restoreActiveFiles(); }, []); - const toolRegistry: ToolRegistry = { - split: { ...baseToolRegistry.split, name: t("home.split.title", "Split PDF") }, - compress: { ...baseToolRegistry.compress, name: t("home.compressPdfs.title", "Compress PDF") }, - merge: { ...baseToolRegistry.merge, name: t("home.merge.title", "Merge PDFs") }, + // Helper function to check if a tool is available + const isToolAvailable = (toolKey: string): boolean => { + if (endpointsLoading) return true; // Show tools while loading + const endpoints = toolEndpoints[toolKey] || []; + // Tool is available if at least one of its endpoints is enabled + return endpoints.some(endpoint => endpointStatus[endpoint] === true); }; + // Filter tool registry to only show available tools + const availableToolRegistry: ToolRegistry = {}; + Object.keys(baseToolRegistry).forEach(toolKey => { + if (isToolAvailable(toolKey)) { + availableToolRegistry[toolKey] = { + ...baseToolRegistry[toolKey as keyof typeof baseToolRegistry], + name: t(`home.${toolKey}.title`, toolKey.charAt(0).toUpperCase() + toolKey.slice(1)) + }; + } + }); + + const toolRegistry = availableToolRegistry; + + // Handle case where selected tool becomes unavailable + useEffect(() => { + if (!endpointsLoading && selectedToolKey && !toolRegistry[selectedToolKey]) { + // If current tool is not available, select the first available tool + const firstAvailableTool = Object.keys(toolRegistry)[0]; + if (firstAvailableTool) { + setSelectedToolKey(firstAvailableTool); + if (toolRegistry[firstAvailableTool]?.view) { + setCurrentView(toolRegistry[firstAvailableTool].view); + } + } + } + }, [endpointsLoading, selectedToolKey, toolRegistry]); + // Handle tool selection const handleToolSelect = useCallback( (id: string) => { diff --git a/frontend/src/tools/Compress.tsx b/frontend/src/tools/Compress.tsx index 8f8f4257b..70ca4e00d 100644 --- a/frontend/src/tools/Compress.tsx +++ b/frontend/src/tools/Compress.tsx @@ -1,9 +1,9 @@ import React, { useState } from "react"; -import { useSearchParams } from "react-router-dom"; import { useTranslation } from "react-i18next"; -import { Stack, Slider, Group, Text, Button, Checkbox, TextInput, Paper } from "@mantine/core"; +import { Stack, Slider, Group, Text, Button, Checkbox, TextInput, Loader, Alert } from "@mantine/core"; import { FileWithUrl } from "../types/file"; import { fileStorage } from "../services/fileStorage"; +import { useEndpointEnabled } from "../hooks/useEndpointConfig"; export interface CompressProps { files?: FileWithUrl[]; @@ -36,6 +36,7 @@ const CompressPdfPanel: React.FC = ({ const [selected, setSelected] = useState(files.map(() => false)); const [localLoading, setLocalLoading] = useState(false); + const { enabled: endpointEnabled, loading: endpointLoading } = useEndpointEnabled("compress-pdf"); const { compressionLevel, @@ -99,6 +100,24 @@ const CompressPdfPanel: React.FC = ({ } }; + if (endpointLoading) { + return ( + + + {t("loading", "Loading...")} + + ); + } + + if (endpointEnabled === false) { + return ( + + + {t("endpointDisabled", "This feature is currently disabled.")} + + + ); + } return ( diff --git a/frontend/src/tools/Merge.tsx b/frontend/src/tools/Merge.tsx index e9f528739..2e33ad046 100644 --- a/frontend/src/tools/Merge.tsx +++ b/frontend/src/tools/Merge.tsx @@ -4,6 +4,7 @@ import { useSearchParams } from "react-router-dom"; import { useTranslation } from "react-i18next"; import { FileWithUrl } from "../types/file"; import { fileStorage } from "../services/fileStorage"; +import { useEndpointEnabled } from "../hooks/useEndpointConfig"; export interface MergePdfPanelProps { files: FileWithUrl[]; @@ -22,11 +23,11 @@ const MergePdfPanel: React.FC = ({ updateParams, }) => { const { t } = useTranslation(); - const [searchParams] = useSearchParams(); const [selectedFiles, setSelectedFiles] = useState([]); const [downloadUrl, setLocalDownloadUrl] = useState(null); const [isLoading, setIsLoading] = useState(false); const [errorMessage, setErrorMessage] = useState(null); + const { enabled: endpointEnabled, loading: endpointLoading } = useEndpointEnabled("merge-pdfs"); useEffect(() => { setSelectedFiles(files.map(() => true)); @@ -92,6 +93,25 @@ const MergePdfPanel: React.FC = ({ const { order, removeDuplicates } = params; + if (endpointLoading) { + return ( + + + {t("loading", "Loading...")} + + ); + } + + if (endpointEnabled === false) { + return ( + + + {t("endpointDisabled", "This feature is currently disabled.")} + + + ); + } + return ( {t("merge.header")} diff --git a/frontend/src/tools/Split.tsx b/frontend/src/tools/Split.tsx index 2e3dbbe24..1d0ffb343 100644 --- a/frontend/src/tools/Split.tsx +++ b/frontend/src/tools/Split.tsx @@ -7,13 +7,16 @@ import { Checkbox, Notification, Stack, - Paper, + Loader, + Alert, + Text, } from "@mantine/core"; import { useSearchParams } from "react-router-dom"; import { useTranslation } from "react-i18next"; import DownloadIcon from "@mui/icons-material/Download"; import { FileWithUrl } from "../types/file"; import { fileStorage } from "../services/fileStorage"; +import { useEndpointEnabled } from "../hooks/useEndpointConfig"; export interface SplitPdfPanelProps { file: { file: FileWithUrl; url: string } | null; @@ -48,6 +51,23 @@ const SplitPdfPanel: React.FC = ({ const [isLoading, setIsLoading] = useState(false); const [errorMessage, setErrorMessage] = useState(null); + // Map mode to endpoint name for checking + const getEndpointName = (mode: string) => { + switch (mode) { + case "byPages": + return "split-pages"; + case "bySections": + return "split-pdf-by-sections"; + case "bySizeOrCount": + return "split-by-size-or-count"; + case "byChapters": + return "split-pdf-by-chapters"; + default: + return "split-pages"; + } + }; + + const { mode, pages, @@ -62,6 +82,7 @@ const SplitPdfPanel: React.FC = ({ } = params; + const { enabled: endpointEnabled, loading: endpointLoading } = useEndpointEnabled(getEndpointName(mode)); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!file) { @@ -142,6 +163,25 @@ const SplitPdfPanel: React.FC = ({ } }; + if (endpointLoading) { + return ( + + + {t("loading", "Loading...")} + + ); + } + + if (endpointEnabled === false) { + return ( + + + {t("endpointDisabled", "This feature is currently disabled.")} + + + ); + } + return (
diff --git a/stirling-pdf/src/main/java/stirling/software/SPDF/controller/api/misc/ConfigController.java b/stirling-pdf/src/main/java/stirling/software/SPDF/controller/api/misc/ConfigController.java new file mode 100644 index 000000000..6b69e4b2e --- /dev/null +++ b/stirling-pdf/src/main/java/stirling/software/SPDF/controller/api/misc/ConfigController.java @@ -0,0 +1,129 @@ +package stirling.software.SPDF.controller.api.misc; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.context.ApplicationContext; +import org.springframework.http.ResponseEntity; +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.RestController; + +import io.swagger.v3.oas.annotations.Hidden; +import io.swagger.v3.oas.annotations.tags.Tag; + +import lombok.RequiredArgsConstructor; + +import stirling.software.SPDF.config.EndpointConfiguration; +import stirling.software.common.configuration.AppConfig; +import stirling.software.common.model.ApplicationProperties; + +@RestController +@Tag(name = "Config", description = "Configuration APIs") +@RequestMapping("/api/v1/config") +@RequiredArgsConstructor +@Hidden +public class ConfigController { + + private final ApplicationProperties applicationProperties; + private final ApplicationContext applicationContext; + private final EndpointConfiguration endpointConfiguration; + + @GetMapping("/app-config") + public ResponseEntity> getAppConfig() { + Map configData = new HashMap<>(); + + try { + // Get AppConfig bean + AppConfig appConfig = applicationContext.getBean(AppConfig.class); + + // Extract key configuration values from AppConfig + configData.put("baseUrl", appConfig.getBaseUrl()); + configData.put("contextPath", appConfig.getContextPath()); + configData.put("serverPort", appConfig.getServerPort()); + + // Extract values from ApplicationProperties + configData.put("appName", applicationProperties.getUi().getAppName()); + configData.put("appNameNavbar", applicationProperties.getUi().getAppNameNavbar()); + configData.put("homeDescription", applicationProperties.getUi().getHomeDescription()); + configData.put("languages", applicationProperties.getUi().getLanguages()); + + // Security settings + configData.put("enableLogin", applicationProperties.getSecurity().getEnableLogin()); + + // System settings + configData.put( + "enableAlphaFunctionality", + applicationProperties.getSystem().getEnableAlphaFunctionality()); + configData.put( + "enableAnalytics", applicationProperties.getSystem().getEnableAnalytics()); + + // Premium/Enterprise settings + configData.put("premiumEnabled", applicationProperties.getPremium().isEnabled()); + + // Legal settings + configData.put( + "termsAndConditions", applicationProperties.getLegal().getTermsAndConditions()); + configData.put("privacyPolicy", applicationProperties.getLegal().getPrivacyPolicy()); + configData.put("cookiePolicy", applicationProperties.getLegal().getCookiePolicy()); + configData.put("impressum", applicationProperties.getLegal().getImpressum()); + configData.put( + "accessibilityStatement", + applicationProperties.getLegal().getAccessibilityStatement()); + + // Try to get EEAppConfig values if available + try { + if (applicationContext.containsBean("runningProOrHigher")) { + configData.put( + "runningProOrHigher", + applicationContext.getBean("runningProOrHigher", Boolean.class)); + } + if (applicationContext.containsBean("runningEE")) { + configData.put( + "runningEE", applicationContext.getBean("runningEE", Boolean.class)); + } + if (applicationContext.containsBean("license")) { + configData.put("license", applicationContext.getBean("license", String.class)); + } + if (applicationContext.containsBean("GoogleDriveEnabled")) { + configData.put( + "GoogleDriveEnabled", + applicationContext.getBean("GoogleDriveEnabled", Boolean.class)); + } + if (applicationContext.containsBean("SSOAutoLogin")) { + configData.put( + "SSOAutoLogin", + applicationContext.getBean("SSOAutoLogin", Boolean.class)); + } + } catch (Exception e) { + // EE features not available, continue without them + } + + return ResponseEntity.ok(configData); + + } catch (Exception e) { + // Return basic config if there are any issues + configData.put("error", "Unable to retrieve full configuration"); + return ResponseEntity.ok(configData); + } + } + + @GetMapping("/endpoint-enabled") + public ResponseEntity isEndpointEnabled(@RequestParam String endpoint) { + boolean enabled = endpointConfiguration.isEndpointEnabled(endpoint); + return ResponseEntity.ok(enabled); + } + + @GetMapping("/endpoints-enabled") + public ResponseEntity> areEndpointsEnabled( + @RequestParam String endpoints) { + Map result = new HashMap<>(); + String[] endpointArray = endpoints.split(","); + for (String endpoint : endpointArray) { + String trimmedEndpoint = endpoint.trim(); + result.put(trimmedEndpoint, endpointConfiguration.isEndpointEnabled(trimmedEndpoint)); + } + return ResponseEntity.ok(result); + } +} diff --git a/stirling-pdf/src/main/java/stirling/software/SPDF/controller/web/HomeWebController.java b/stirling-pdf/src/main/java/stirling/software/SPDF/controller/web/HomeWebController.java index 65ebc0969..2b36f95af 100644 --- a/stirling-pdf/src/main/java/stirling/software/SPDF/controller/web/HomeWebController.java +++ b/stirling-pdf/src/main/java/stirling/software/SPDF/controller/web/HomeWebController.java @@ -91,4 +91,4 @@ public class HomeWebController { return "User-agent: Googlebot\nDisallow: /\n\nUser-agent: *\nDisallow: /"; } } -} \ No newline at end of file +}