mirror of
https://github.com/arsvendg/Stirling-PDF.git
synced 2026-07-16 11:23:10 +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 indexHtmlExists = false;
|
||||||
private boolean useExternalIndexHtml = false;
|
private boolean useExternalIndexHtml = false;
|
||||||
private boolean loggedMissingIndex = false;
|
private boolean loggedMissingIndex = false;
|
||||||
|
private String cachedSaasLandingHtml;
|
||||||
|
private boolean saasLandingExists = false;
|
||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
public void init() {
|
public void init() {
|
||||||
@@ -49,6 +51,20 @@ public class ReactRoutingController {
|
|||||||
// Always initialize callback HTML (used for OAuth desktop flow)
|
// Always initialize callback HTML (used for OAuth desktop flow)
|
||||||
this.cachedCallbackHtml = buildCallbackHtml();
|
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/)
|
// Check for external index.html first (customFiles/static/)
|
||||||
Path externalIndexPath = Paths.get(InstallationPathConfig.getStaticPath(), "index.html");
|
Path externalIndexPath = Paths.get(InstallationPathConfig.getStaticPath(), "index.html");
|
||||||
log.debug("Checking for custom index.html at: {}", externalIndexPath);
|
log.debug("Checking for custom index.html at: {}", externalIndexPath);
|
||||||
@@ -132,6 +148,18 @@ public class ReactRoutingController {
|
|||||||
@GetMapping(
|
@GetMapping(
|
||||||
value = {"/", "/index.html"},
|
value = {"/", "/index.html"},
|
||||||
produces = MediaType.TEXT_HTML_VALUE)
|
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) {
|
public ResponseEntity<String> serveIndexHtml(HttpServletRequest request) {
|
||||||
try {
|
try {
|
||||||
if (indexHtmlExists && cachedIndexHtml != null) {
|
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