mirror of
https://github.com/arsvendg/Stirling-PDF.git
synced 2026-07-14 10:34:06 +02:00
SaaS-aware API landing page (#6585)
# Description of Changes OLD (and still current in selfhosted) <img width="610" height="869" alt="image" src="https://github.com/user-attachments/assets/f8019298-b4ee-4a68-b928-a9746b64ac1c" /> New (in SaaS mode) <img width="635" height="876" alt="image" src="https://github.com/user-attachments/assets/6ee4946f-1d7b-42ec-a6f7-75e85739e348" /> --- ## Checklist ### General - [ ] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [ ] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md) (if applicable) - [ ] I have performed a self-review of my own code - [ ] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### Translations (if applicable) - [ ] I ran [`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [ ] I have run `task check` to verify linters, typechecks, and tests pass - [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing) for more details.
This commit is contained in:
+28
@@ -41,6 +41,8 @@ public class ReactRoutingController {
|
||||
private boolean indexHtmlExists = false;
|
||||
private boolean useExternalIndexHtml = false;
|
||||
private boolean loggedMissingIndex = false;
|
||||
private String cachedSaasLandingHtml;
|
||||
private boolean saasLandingExists = false;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
@@ -49,6 +51,20 @@ public class ReactRoutingController {
|
||||
// Always initialize callback HTML (used for OAuth desktop flow)
|
||||
this.cachedCallbackHtml = buildCallbackHtml();
|
||||
|
||||
// SaaS landing page: only present on the classpath when the :saas module is bundled
|
||||
// (app/saas/src/main/resources/static/saas-landing.html). When present it replaces the
|
||||
// root page so the SaaS API host shows its own landing instead of the OSS API-only page.
|
||||
ClassPathResource saasLanding = new ClassPathResource("static/saas-landing.html");
|
||||
if (saasLanding.exists()) {
|
||||
try (InputStream in = saasLanding.getInputStream()) {
|
||||
this.cachedSaasLandingHtml = new String(in.readAllBytes(), StandardCharsets.UTF_8);
|
||||
this.saasLandingExists = true;
|
||||
log.info("SaaS landing page detected; serving it at '/' and '/index.html'");
|
||||
} catch (Exception ex) {
|
||||
log.warn("Failed to read saas-landing.html; falling back to index.html", ex);
|
||||
}
|
||||
}
|
||||
|
||||
// Check for external index.html first (customFiles/static/)
|
||||
Path externalIndexPath = Paths.get(InstallationPathConfig.getStaticPath(), "index.html");
|
||||
log.debug("Checking for custom index.html at: {}", externalIndexPath);
|
||||
@@ -132,6 +148,18 @@ public class ReactRoutingController {
|
||||
@GetMapping(
|
||||
value = {"/", "/index.html"},
|
||||
produces = MediaType.TEXT_HTML_VALUE)
|
||||
public ResponseEntity<String> serveRootPage(HttpServletRequest request) {
|
||||
// Swap ONLY the root page for SaaS. SPA entry points that delegate to serveIndexHtml
|
||||
// (/auth/callback, /share/{token}, forwarded routes) keep serving the normal shell.
|
||||
if (saasLandingExists && cachedSaasLandingHtml != null) {
|
||||
return ResponseEntity.ok()
|
||||
.cacheControl(CacheControl.noCache().mustRevalidate())
|
||||
.contentType(MediaType.TEXT_HTML)
|
||||
.body(cachedSaasLandingHtml);
|
||||
}
|
||||
return serveIndexHtml(request);
|
||||
}
|
||||
|
||||
public ResponseEntity<String> serveIndexHtml(HttpServletRequest request) {
|
||||
try {
|
||||
if (indexHtmlExists && cachedIndexHtml != null) {
|
||||
|
||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 7.2 KiB |
@@ -0,0 +1,250 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="description" content="Stirling PDF Cloud API">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Stirling PDF - Cloud API</title>
|
||||
|
||||
<!-- Icons -->
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="favicon-16x16.png">
|
||||
<link rel="shortcut icon" href="favicon.ico">
|
||||
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
background-color: #f3f4f6;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 1.5rem 1.5rem 0;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.auth-card {
|
||||
width: min(45rem, 96vw);
|
||||
background-color: #ffffff;
|
||||
border-radius: 1.25rem;
|
||||
box-shadow: 0 1.25rem 3.75rem rgba(0, 0, 0, 0.12);
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.auth-content {
|
||||
max-width: 26.25rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.login-header {
|
||||
margin-bottom: 1.5rem;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.login-header-logos {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
|
||||
.login-logo-modern {
|
||||
height: 3rem;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.login-title {
|
||||
font-size: 2rem;
|
||||
font-weight: 800;
|
||||
color: #111827;
|
||||
margin: 0 0 0.375rem;
|
||||
}
|
||||
|
||||
.login-subtitle {
|
||||
color: #6b7280;
|
||||
font-size: 0.875rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.section {
|
||||
margin: 1.5rem 0 0.75rem;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
color: #374151;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.section-text {
|
||||
font-size: 0.875rem;
|
||||
color: #6b7280;
|
||||
line-height: 1.5;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.section-list {
|
||||
font-size: 0.875rem;
|
||||
color: #6b7280;
|
||||
line-height: 1.6;
|
||||
margin-left: 1.25rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.section-list li {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.section-list a,
|
||||
.section-text a {
|
||||
color: #AF3434;
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.section-list a:hover,
|
||||
.section-text a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.cta-button {
|
||||
width: 100%;
|
||||
padding: 0.75rem 1rem;
|
||||
background-color: #AF3434;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 0.625rem;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
transition: background-color 0.2s;
|
||||
margin: 0.75rem 0;
|
||||
}
|
||||
|
||||
.cta-button:hover {
|
||||
background-color: #9a2e2e;
|
||||
}
|
||||
|
||||
.cta-button.secondary {
|
||||
background-color: #ffffff;
|
||||
color: #AF3434;
|
||||
border: 1px solid #AF3434;
|
||||
}
|
||||
|
||||
.cta-button.secondary:hover {
|
||||
background-color: #fdf2f2;
|
||||
}
|
||||
|
||||
.divider {
|
||||
margin: 1.5rem 0;
|
||||
border: 0;
|
||||
border-top: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.footer {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
padding: 1rem;
|
||||
font-size: 0.875rem;
|
||||
color: #6b7280;
|
||||
background-color: transparent;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.footer a {
|
||||
color: #AF3434;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.footer a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="auth-card">
|
||||
<div class="auth-content">
|
||||
<div class="login-header">
|
||||
<div class="login-header-logos">
|
||||
<img src="modern-logo.svg" alt="Stirling PDF" class="login-logo-modern">
|
||||
</div>
|
||||
<h1 class="login-title">Stirling PDF API</h1>
|
||||
<p class="login-subtitle">REST API for PDF manipulation, in the cloud</p>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<p class="section-text">
|
||||
You've reached the <strong>Stirling PDF Cloud API</strong>. This endpoint serves the REST API that powers our apps and your integrations.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<a href="swagger-ui/index.html" class="cta-button">Open API Documentation</a>
|
||||
<a href="https://docs.stirlingpdf.com" target="_blank" class="cta-button secondary">Read the Developer Docs</a>
|
||||
|
||||
<hr class="divider">
|
||||
|
||||
<div class="section">
|
||||
<h2 class="section-title">Just looking to edit a PDF?</h2>
|
||||
<p class="section-text">
|
||||
This page is the API endpoint - it doesn't have an editor. If you want the full interface:
|
||||
</p>
|
||||
<ul class="section-list">
|
||||
<li><a href="https://stirling.com/app" target="_blank">Open Stirling PDF in your browser</a> - the hosted web app</li>
|
||||
<li><a href="https://stirling.com/download" target="_blank">Download the desktop app</a> - macOS, Windows, and Linux</li>
|
||||
<li><a href="https://stirling.com/pricing" target="_blank">Pricing & plans</a> - free tier, paid plans, and enterprise</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<hr class="divider">
|
||||
|
||||
<div class="section">
|
||||
<h2 class="section-title">Want to run your own?</h2>
|
||||
<p class="section-text">
|
||||
Stirling PDF is open source. You can self-host the same toolkit on your own hardware or run it inside your network:
|
||||
</p>
|
||||
<ul class="section-list">
|
||||
<li><a href="https://docs.stirlingpdf.com/Installation/Versions" target="_blank">Installation guide</a> - Docker, JAR, Windows, Mac, and Linux</li>
|
||||
<li><a href="https://github.com/Stirling-Tools/Stirling-PDF" target="_blank">GitHub repository</a> - source code and releases</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<hr class="divider">
|
||||
|
||||
<div class="section">
|
||||
<h2 class="section-title">Support</h2>
|
||||
<ul class="section-list">
|
||||
<li><a href="https://docs.stirlingpdf.com" target="_blank">Documentation</a> - guides, API reference, integration tutorials</li>
|
||||
<li><a href="https://discord.gg/Cn8pWhQRxZ" target="_blank">Discord community</a> - chat with other developers and users</li>
|
||||
<li><a href="https://stirling.com" target="_blank">stirling.com</a> - company website</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<span>Powered by </span>
|
||||
<a href="https://stirlingpdf.com">Stirling PDF</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user