Add styled /status page, update footer/terms links from /health to /status
All checks were successful
Deploy to Production / Deploy to Server (push) Successful in 2m49s

This commit is contained in:
OpenClaw 2026-02-16 19:48:25 +00:00
parent 59cc8f3d0e
commit 6cc30db5c6
11 changed files with 219 additions and 11 deletions

View file

@ -96,9 +96,8 @@ footer .container { display: flex; justify-content: space-between; align-items:
<div class="footer-links">
<a href="/">Home</a>
<a href="/docs">Docs</a>
<a href="/health">API Status</a>
<a href="/status">API Status</a>
<a href="/#change-email" class="open-email-change">Change Email</a>
<a href="mailto:support@docfast.dev">Support</a>
<a href="/impressum">Impressum</a>
<a href="/privacy">Privacy Policy</a>
<a href="/terms">Terms of Service</a>

View file

@ -421,9 +421,8 @@ html, body {
<div class="footer-links">
<a href="/">Home</a>
<a href="/docs">Docs</a>
<a href="/health">API Status</a>
<a href="/status">API Status</a>
<a href="/#change-email" class="open-email-change">Change Email</a>
<a href="mailto:support@docfast.dev">Support</a>
<a href="/impressum">Impressum</a>
<a href="/privacy">Privacy Policy</a>
<a href="/terms">Terms of Service</a>

View file

@ -4,7 +4,7 @@
<div class="footer-links">
<a href="/">Home</a>
<a href="/docs">Docs</a>
<a href="/health">API Status</a>
<a href="/status">API Status</a>
<a href="/#change-email" class="open-email-change">Change Email</a>
<a href="/impressum">Impressum</a>
<a href="/privacy">Privacy Policy</a>

View file

@ -178,9 +178,8 @@ footer .container { display: flex; justify-content: space-between; align-items:
<div class="footer-links">
<a href="/">Home</a>
<a href="/docs">Docs</a>
<a href="/health">API Status</a>
<a href="/status">API Status</a>
<a href="/#change-email" class="open-email-change">Change Email</a>
<a href="mailto:support@docfast.dev">Support</a>
<a href="/impressum">Impressum</a>
<a href="/privacy">Privacy Policy</a>
<a href="/terms">Terms of Service</a>

48
public/src/status.html Normal file
View file

@ -0,0 +1,48 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>System Status — DocFast</title>
<meta name="description" content="Real-time system status for the DocFast PDF API.">
<link rel="canonical" href="https://docfast.dev/status">
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>⚡</text></svg>">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
{{> styles_base}}
<style>
.status-hero { text-align: center; padding: 48px 0 32px; }
.status-indicator { display: inline-flex; align-items: center; gap: 12px; font-size: 1.5rem; font-weight: 700; margin-bottom: 8px; }
.status-dot { width: 16px; height: 16px; border-radius: 50%; display: inline-block; }
.status-dot.ok { background: var(--accent); box-shadow: 0 0 12px var(--accent); }
.status-dot.degraded { background: #f59e0b; box-shadow: 0 0 12px #f59e0b; }
.status-dot.error { background: #ef4444; box-shadow: 0 0 12px #ef4444; }
.status-meta { color: var(--muted); font-size: 0.85rem; margin-top: 8px; }
.status-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin: 32px 0; }
@media (max-width: 600px) { .status-grid { grid-template-columns: 1fr; } }
.status-card { background: var(--card); border: 1px solid var(--border); border-radius: var(--radius); padding: 24px; }
.status-card h3 { font-size: 0.85rem; text-transform: uppercase; letter-spacing: 1px; color: var(--muted); margin-bottom: 16px; }
.status-row { display: flex; justify-content: space-between; align-items: center; padding: 8px 0; border-bottom: 1px solid var(--border); }
.status-row:last-child { border-bottom: none; }
.status-label { color: var(--muted); font-size: 0.9rem; }
.status-value { font-weight: 600; font-size: 0.9rem; }
.status-value.ok { color: var(--accent); }
.status-value.warn { color: #f59e0b; }
.status-value.err { color: #ef4444; }
.status-loading { text-align: center; padding: 60px 0; color: var(--muted); }
</style>
</head>
<body>
{{> nav}}
<main>
<div class="container">
<div id="status-content" class="status-loading">Loading system status…</div>
</div>
</main>
{{> footer}}
<script src="/status.js"></script>
</body>
</html>

View file

@ -93,7 +93,7 @@
<ul>
<li><strong>Target:</strong> 99.5% uptime (best effort, no SLA for free tier)</li>
<li><strong>Maintenance:</strong> Scheduled maintenance with advance notice</li>
<li><strong>Status page:</strong> <a href="/health">https://docfast.dev/health</a></li>
<li><strong>Status page:</strong> <a href="/status">https://docfast.dev/status</a></li>
</ul>
<h3>5.2 Performance</h3>

106
public/status.html Normal file
View file

@ -0,0 +1,106 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>System Status — DocFast</title>
<meta name="description" content="Real-time system status for the DocFast PDF API.">
<link rel="canonical" href="https://docfast.dev/status">
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>⚡</text></svg>">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--bg: #0b0d11; --bg2: #12151c; --fg: #e4e7ed; --muted: #7a8194;
--accent: #34d399; --accent-hover: #5eead4; --accent-glow: rgba(52,211,153,0.12);
--card: #151922; --border: #1e2433;
--radius: 12px; --radius-lg: 16px;
}
body { font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: var(--bg); color: var(--fg); line-height: 1.65; -webkit-font-smoothing: antialiased; }
a { color: var(--accent); text-decoration: none; transition: color 0.2s; }
a:hover { color: var(--accent-hover); }
.container { max-width: 800px; margin: 0 auto; padding: 0 24px; }
nav { padding: 20px 0; border-bottom: 1px solid var(--border); }
nav .container { display: flex; align-items: center; justify-content: space-between; }
.logo { font-size: 1.25rem; font-weight: 700; letter-spacing: -0.5px; color: var(--fg); display: flex; align-items: center; gap: 8px; text-decoration: none; }
.logo span { color: var(--accent); }
.nav-links { display: flex; gap: 28px; align-items: center; }
.nav-links a { color: var(--muted); font-size: 0.9rem; font-weight: 500; }
.nav-links a:hover { color: var(--fg); }
.content { padding: 60px 0; min-height: 60vh; }
.content h1 { font-size: 2rem; font-weight: 800; margin-bottom: 32px; letter-spacing: -1px; }
.content h2 { font-size: 1.3rem; font-weight: 700; margin: 32px 0 16px; color: var(--fg); }
.content h3 { font-size: 1.1rem; font-weight: 600; margin: 24px 0 12px; color: var(--fg); }
.content p, .content li { color: var(--muted); margin-bottom: 12px; }
.content ul, .content ol { padding-left: 24px; }
.content strong { color: var(--fg); }
footer { padding: 32px 0; border-top: 1px solid var(--border); margin-top: 60px; }
footer .container { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 16px; }
.footer-left { color: var(--muted); font-size: 0.85rem; }
.footer-links { display: flex; gap: 20px; flex-wrap: wrap; }
.footer-links a { color: var(--muted); font-size: 0.85rem; }
.footer-links a:hover { color: var(--fg); }
@media (max-width: 768px) {
footer .container { flex-direction: column; text-align: center; }
.nav-links { gap: 16px; }
}
</style>
<style>
.status-hero { text-align: center; padding: 48px 0 32px; }
.status-indicator { display: inline-flex; align-items: center; gap: 12px; font-size: 1.5rem; font-weight: 700; margin-bottom: 8px; }
.status-dot { width: 16px; height: 16px; border-radius: 50%; display: inline-block; }
.status-dot.ok { background: var(--accent); box-shadow: 0 0 12px var(--accent); }
.status-dot.degraded { background: #f59e0b; box-shadow: 0 0 12px #f59e0b; }
.status-dot.error { background: #ef4444; box-shadow: 0 0 12px #ef4444; }
.status-meta { color: var(--muted); font-size: 0.85rem; margin-top: 8px; }
.status-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin: 32px 0; }
@media (max-width: 600px) { .status-grid { grid-template-columns: 1fr; } }
.status-card { background: var(--card); border: 1px solid var(--border); border-radius: var(--radius); padding: 24px; }
.status-card h3 { font-size: 0.85rem; text-transform: uppercase; letter-spacing: 1px; color: var(--muted); margin-bottom: 16px; }
.status-row { display: flex; justify-content: space-between; align-items: center; padding: 8px 0; border-bottom: 1px solid var(--border); }
.status-row:last-child { border-bottom: none; }
.status-label { color: var(--muted); font-size: 0.9rem; }
.status-value { font-weight: 600; font-size: 0.9rem; }
.status-value.ok { color: var(--accent); }
.status-value.warn { color: #f59e0b; }
.status-value.err { color: #ef4444; }
.status-loading { text-align: center; padding: 60px 0; color: var(--muted); }
</style>
</head>
<body>
<nav aria-label="Main navigation">
<div class="container">
<a href="/" class="logo">⚡ Doc<span>Fast</span></a>
<div class="nav-links">
<a href="/#features">Features</a>
<a href="/#pricing">Pricing</a>
<a href="/docs">Docs</a>
</div>
</div>
</nav>
<main>
<div class="container">
<div id="status-content" class="status-loading">Loading system status…</div>
</div>
</main>
<footer aria-label="Footer">
<div class="container">
<div class="footer-left">© 2026 DocFast. Fast PDF generation for developers.</div>
<div class="footer-links">
<a href="/">Home</a>
<a href="/docs">Docs</a>
<a href="/status">API Status</a>
<a href="/#change-email" class="open-email-change">Change Email</a>
<a href="/impressum">Impressum</a>
<a href="/privacy">Privacy Policy</a>
<a href="/terms">Terms of Service</a>
</div>
</div>
</footer>
<script src="/status.js"></script>
</body>
</html>

48
public/status.js Normal file
View file

@ -0,0 +1,48 @@
async function fetchStatus() {
const el = document.getElementById("status-content");
try {
const res = await fetch("/health");
const d = await res.json();
const isOk = d.status === "ok";
const isDegraded = d.status === "degraded";
const dotClass = isOk ? "ok" : isDegraded ? "degraded" : "error";
const label = isOk ? "All Systems Operational" : isDegraded ? "Degraded Performance" : "Service Disruption";
const now = new Date().toLocaleTimeString();
el.innerHTML =
"<div class=\"status-hero\">" +
"<div class=\"status-indicator\"><span class=\"status-dot " + dotClass + "\"></span> " + label + "</div>" +
"<div class=\"status-meta\">Version " + d.version + " · Last checked " + now + " · Auto-refreshes every 30s</div>" +
"</div>" +
"<div class=\"status-grid\">" +
"<div class=\"status-card\">" +
"<h3>🗄️ Database</h3>" +
"<div class=\"status-row\"><span class=\"status-label\">Status</span><span class=\"status-value " + (d.database && d.database.status === "ok" ? "ok" : "err") + "\">" + (d.database && d.database.status === "ok" ? "Connected" : "Error") + "</span></div>" +
"<div class=\"status-row\"><span class=\"status-label\">Engine</span><span class=\"status-value\">" + (d.database ? d.database.version : "Unknown") + "</span></div>" +
"</div>" +
"<div class=\"status-card\">" +
"<h3>🖨️ PDF Engine</h3>" +
"<div class=\"status-row\"><span class=\"status-label\">Status</span><span class=\"status-value " + (d.pool && d.pool.available > 0 ? "ok" : "warn") + "\">" + (d.pool && d.pool.available > 0 ? "Ready" : "Busy") + "</span></div>" +
"<div class=\"status-row\"><span class=\"status-label\">Available</span><span class=\"status-value\">" + (d.pool ? d.pool.available : 0) + " / " + (d.pool ? d.pool.size : 0) + "</span></div>" +
"<div class=\"status-row\"><span class=\"status-label\">Queue</span><span class=\"status-value " + (d.pool && d.pool.queueDepth > 0 ? "warn" : "ok") + "\">" + (d.pool ? d.pool.queueDepth : 0) + " waiting</span></div>" +
"<div class=\"status-row\"><span class=\"status-label\">PDFs Generated</span><span class=\"status-value\">" + (d.pool ? d.pool.pdfCount.toLocaleString() : "0") + "</span></div>" +
"<div class=\"status-row\"><span class=\"status-label\">Uptime</span><span class=\"status-value\">" + formatUptime(d.pool ? d.pool.uptimeSeconds : 0) + "</span></div>" +
"</div>" +
"</div>" +
"<div style=\"text-align:center;margin-top:16px;\"><a href=\"/health\" style=\"font-size:0.8rem;color:var(--muted);\">Raw JSON endpoint →</a></div>";
} catch (e) {
el.innerHTML = "<div class=\"status-hero\"><div class=\"status-indicator\"><span class=\"status-dot error\"></span> Unable to reach API</div><div class=\"status-meta\">The service may be temporarily unavailable. Please try again shortly.</div></div>";
}
}
function formatUptime(s) {
if (!s && s !== 0) return "Unknown";
if (s < 60) return s + "s";
if (s < 3600) return Math.floor(s/60) + "m " + (s%60) + "s";
var h = Math.floor(s/3600);
var m = Math.floor((s%3600)/60);
return h + "h " + m + "m";
}
fetchStatus();
setInterval(fetchStatus, 30000);

View file

@ -138,7 +138,7 @@ footer .container { display: flex; justify-content: space-between; align-items:
<ul>
<li><strong>Target:</strong> 99.5% uptime (best effort, no SLA for free tier)</li>
<li><strong>Maintenance:</strong> Scheduled maintenance with advance notice</li>
<li><strong>Status page:</strong> <a href="/health">https://docfast.dev/health</a></li>
<li><strong>Status page:</strong> <a href="/status">https://docfast.dev/status</a></li>
</ul>
<h3>5.2 Performance</h3>
@ -250,9 +250,8 @@ footer .container { display: flex; justify-content: space-between; align-items:
<div class="footer-links">
<a href="/">Home</a>
<a href="/docs">Docs</a>
<a href="/health">API Status</a>
<a href="/status">API Status</a>
<a href="/#change-email" class="open-email-change">Change Email</a>
<a href="mailto:support@docfast.dev">Support</a>
<a href="/impressum">Impressum</a>
<a href="/privacy">Privacy Policy</a>
<a href="/terms">Terms of Service</a>