Skip to main content

ACME_DASHBOARD_HTML

Constant ACME_DASHBOARD_HTML 

Source
pub const ACME_DASHBOARD_HTML: &str = r#"<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>ACME/TLS Status - Rush Sync Server</title>
<link rel="icon" href="/.rss/favicon.svg" type="image/svg+xml">
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;background:#0a0a0f;color:#e4e4ef;min-height:100vh}
.container{max-width:900px;margin:0 auto;padding:24px}
.header{display:flex;justify-content:space-between;align-items:center;margin-bottom:24px}
.header h1{font-size:24px;font-weight:700;letter-spacing:-0.5px}
.header h1 span{color:#6c63ff}
.nav-links{display:flex;gap:12px}
.nav-links a{color:#6c63ff;text-decoration:none;font-size:14px}
.cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:16px;margin-bottom:24px}
.card{background:#14141f;border:1px solid #2a2a3a;border-radius:12px;padding:20px}
.card .lbl{font-size:12px;color:#8888a0;text-transform:uppercase;letter-spacing:0.5px;margin-bottom:8px}
.card .val{font-size:28px;font-weight:700}
.card .val.green{color:#00d4aa}
.card .val.red{color:#ff4466}
.card .val.yellow{color:#ffaa00}
.card .val.blue{color:#00a8ff}
.card .val.purple{color:#6c63ff}
.card .sub{font-size:11px;color:#8888a0;margin-top:4px}
.section{background:#14141f;border:1px solid #2a2a3a;border-radius:12px;padding:20px;margin-bottom:16px}
.section h2{font-size:15px;margin-bottom:16px;font-weight:600;color:#c0c0d0}
.info-row{display:flex;justify-content:space-between;padding:10px 0;border-bottom:1px solid #1a1a2a;font-size:13px}
.info-row:last-child{border-bottom:none}
.info-row .label{color:#8888a0}
.info-row .value{font-weight:500;color:#e4e4ef}
.info-row .value.success{color:#00d4aa}
.info-row .value.error{color:#ff4466}
.info-row .value.warn{color:#ffaa00}
.info-row .value code{background:#1a1a2a;padding:2px 6px;border-radius:4px;font-size:12px}
.status-banner{padding:16px 20px;border-radius:12px;margin-bottom:24px;display:flex;align-items:center;gap:12px;font-weight:600}
.status-banner .dot{width:12px;height:12px;border-radius:50%;flex-shrink:0}
.status-banner.success{background:#00d4aa15;border:1px solid #00d4aa40;color:#00d4aa}
.status-banner.success .dot{background:#00d4aa;box-shadow:0 0 8px #00d4aa80}
.status-banner.failed{background:#ff446615;border:1px solid #ff446640;color:#ff4466}
.status-banner.failed .dot{background:#ff4466;box-shadow:0 0 8px #ff446680}
.status-banner.provisioning{background:#ffaa0015;border:1px solid #ffaa0040;color:#ffaa00}
.status-banner.provisioning .dot{background:#ffaa00;box-shadow:0 0 8px #ffaa0080;animation:pulse 1.5s infinite}
.status-banner.idle{background:#6c63ff15;border:1px solid #6c63ff40;color:#6c63ff}
.status-banner.idle .dot{background:#6c63ff}
.status-banner.not_configured{background:#55555515;border:1px solid #55555540;color:#888}
.status-banner.not_configured .dot{background:#555}
@keyframes pulse{0%,100%{opacity:1}50%{opacity:0.4}}
.san-list{display:flex;flex-wrap:wrap;gap:6px}
.san-tag{background:#1a1a2a;border:1px solid #2a2a3a;border-radius:6px;padding:4px 10px;font-size:12px;font-family:monospace}
.error-box{background:#ff446610;border:1px solid #ff446630;border-radius:8px;padding:12px 16px;font-size:13px;color:#ff8899;font-family:monospace;word-break:break-all}
.footer{text-align:center;font-size:11px;color:#555;padding:16px}
</style>
</head>
<body>
<div class="container">
<div class="header"><h1>ACME/TLS <span>Status</span></h1><div class="nav-links"><a href="/.rss/">&larr; Dashboard</a><a href="/api/analytics/dashboard">Analytics</a><a href="/api/acme/status">JSON</a></div></div>
<div id="banner"></div>
<div class="cards" id="cards"></div>
<div class="section" id="cert-section"><h2>Certificate</h2><div id="cert-info"></div></div>
<div class="section" id="sans-section"><h2>Subject Alternative Names</h2><div id="sans-list"></div></div>
<div class="section" id="detail-section"><h2>Details</h2><div id="details"></div></div>
<div id="error-section" style="display:none" class="section"><h2>Last Error</h2><div id="error-box"></div></div>
<div class="footer" id="foot">Loading...</div>
</div>
<script>
var D=__ACME_DATA__;
function render(){
var s=D.status||'not_configured';
var labels={'success':'Certificate Active','failed':'Provisioning Failed','provisioning':'Provisioning in Progress...','idle':'Idle','not_configured':'Not Configured'};
document.getElementById('banner').innerHTML='<div class="status-banner '+s+'"><span class="dot"></span>'+esc(labels[s]||s)+'</div>';
if(s==='not_configured'){document.getElementById('cards').innerHTML='<div class="card"><div class="lbl">Status</div><div class="val purple">N/A</div><div class="sub">ACME not configured</div></div>';document.getElementById('foot').textContent='ACME/Let\'s Encrypt is not enabled';return}
var cert=D.certificate||{};
document.getElementById('cards').innerHTML='<div class="card"><div class="lbl">Status</div><div class="val '+(s==='success'?'green':s==='failed'?'red':s==='provisioning'?'yellow':'blue')+'">'+esc(s.charAt(0).toUpperCase()+s.slice(1))+'</div></div>'+'<div class="card"><div class="lbl">Certificate</div><div class="val '+(cert.exists?'green':'red')+'">'+(cert.exists?'Valid':'Missing')+'</div>'+(cert.age_days!=null?'<div class="sub">'+cert.age_days+' days old</div>':'')+'</div>'+'<div class="card"><div class="lbl">Renewal</div><div class="val '+((cert.days_until_renewal!=null&&cert.days_until_renewal>14)?'green':(cert.days_until_renewal!=null&&cert.days_until_renewal>0)?'yellow':'red')+'">'+(cert.days_until_renewal!=null?cert.days_until_renewal+' days':'N/A')+'</div><div class="sub">until renewal</div></div>'+'<div class="card"><div class="lbl">Attempts</div><div class="val purple">'+(D.attempt_count||0)+'</div><div class="sub">provisioning attempts</div></div>';
var ci='';
ci+='<div class="info-row"><span class="label">Domain</span><span class="value"><code>'+esc(D.domain||'')+'</code></span></div>';
ci+='<div class="info-row"><span class="label">Subdomains</span><span class="value">'+(D.subdomains&&D.subdomains.length?D.subdomains.map(function(s){return '<code>'+esc(s)+'</code>'}).join(' '):'<em>none</em>')+'</span></div>';
ci+='<div class="info-row"><span class="label">Certificate Exists</span><span class="value '+(cert.exists?'success':'error')+'">'+(cert.exists?'Yes':'No')+'</span></div>';
if(cert.age_days!=null)ci+='<div class="info-row"><span class="label">Certificate Age</span><span class="value">'+cert.age_days+' days</span></div>';
if(cert.days_until_renewal!=null)ci+='<div class="info-row"><span class="label">Days Until Renewal</span><span class="value '+(cert.days_until_renewal>14?'success':'warn')+'">'+cert.days_until_renewal+' days</span></div>';
document.getElementById('cert-info').innerHTML=ci;
var sans=cert.sans||[];
if(sans.length>0){document.getElementById('sans-section').style.display='';document.getElementById('sans-list').innerHTML='<div class="san-list">'+sans.map(function(s){return '<span class="san-tag">'+esc(s)+'</span>'}).join('')+'</div>'}else{document.getElementById('sans-section').style.display='none'}
var di='';
di+='<div class="info-row"><span class="label">Last Attempt</span><span class="value">'+(D.last_attempt?fmtTime(D.last_attempt):'Never')+'</span></div>';
di+='<div class="info-row"><span class="label">Last Success</span><span class="value '+(D.last_success?'success':'')+'">'+(D.last_success?fmtTime(D.last_success):'Never')+'</span></div>';
di+='<div class="info-row"><span class="label">Next Renewal Check</span><span class="value">'+(D.next_renewal_check?fmtTime(D.next_renewal_check):'Not scheduled')+'</span></div>';
di+='<div class="info-row"><span class="label">Attempt Count</span><span class="value">'+(D.attempt_count||0)+'</span></div>';
document.getElementById('details').innerHTML=di;
if(D.last_error){document.getElementById('error-section').style.display='';document.getElementById('error-box').innerHTML='<div class="error-box">'+esc(D.last_error)+'</div>'}else{document.getElementById('error-section').style.display='none'}
document.getElementById('foot').textContent='Last updated: '+new Date().toLocaleTimeString()+' \u00b7 Auto-refresh in 15s'}
function fmtTime(s){try{var d=new Date(s);return d.toLocaleString()}catch(e){return s}}
function esc(s){var d=document.createElement('div');d.textContent=s;return d.innerHTML}
render();setTimeout(function(){location.reload()},15000);
</script>
</body></html>"#;
Expand description

ACME Status Dashboard HTML template. The placeholder __ACME_DATA__ is replaced with the current ACME status JSON at render time.