Files
2026-07-13 12:28:19 +08:00

272 lines
14 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AWS Serverless Architecture</title>
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/html2canvas@1.4.1/dist/html2canvas.min.js" integrity="sha384-ZZ1pncU3bQe8y31yfZdMFdSpttDoPmOZg2wguVK9almUodir1PghgT0eY7Mrty8H" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/jspdf@2.5.2/dist/jspdf.umd.min.js" integrity="sha384-en/ztfPSRkGfME4KIm05joYXynqzUgbsG5nMrj/xEFAHXkeZfO3yMK8QQ+mP7p1/" crossorigin="anonymous"></script>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'JetBrains Mono', monospace;
background: #020617;
min-height: 100vh;
padding: 2rem;
color: white;
}
.container { max-width: 1200px; margin: 0 auto; }
.header { margin-bottom: 2rem; }
.header-row { display: flex; align-items: center; gap: 1rem; margin-bottom: 0.5rem; }
.pulse-dot {
width: 12px; height: 12px; background: #fbbf24;
border-radius: 50%; animation: pulse 2s infinite;
}
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } }
h1 { font-size: 1.5rem; font-weight: 700; letter-spacing: -0.025em; }
.subtitle { color: #94a3b8; font-size: 0.875rem; margin-left: 1.75rem; }
.diagram-container {
background: rgba(15, 23, 42, 0.5);
border-radius: 1rem; border: 1px solid #1e293b;
padding: 1.5rem; overflow-x: auto;
}
svg { width: 100%; min-width: 900px; display: block; }
.cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 1rem; margin-top: 2rem; }
.card { background: rgba(15, 23, 42, 0.5); border-radius: 0.75rem; border: 1px solid #1e293b; padding: 1.25rem; }
.card-header { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.75rem; }
.card-dot { width: 8px; height: 8px; border-radius: 50%; }
.card-dot.amber { background: #fbbf24; }
.card-dot.emerald { background: #34d399; }
.card-dot.violet { background: #a78bfa; }
.card h3 { font-size: 0.875rem; font-weight: 600; }
.card ul { list-style: none; color: #94a3b8; font-size: 0.75rem; }
.card li { margin-bottom: 0.375rem; }
.footer { text-align: center; margin-top: 1.5rem; color: #475569; font-size: 0.75rem; }
.toolbar { display: flex; gap: 0.5rem; margin-left: auto; flex-shrink: 0; align-items: center; }
.toolbar-toggle {
background: transparent; border: none; color: #475569; cursor: pointer;
font-size: 1.25rem; line-height: 1; padding: 0.25rem 0.5rem;
border-radius: 0.375rem; transition: color 0.2s, background 0.2s;
}
.toolbar-toggle:hover { color: #94a3b8; background: rgba(30, 41, 59, 0.5); }
.toolbar-actions { display: none; gap: 0.5rem; }
.toolbar.expanded .toolbar-actions { display: flex; }
.toolbar-actions button {
background: rgba(30, 41, 59, 0.8); border: 1px solid #334155; color: #94a3b8;
padding: 0.375rem 0.75rem; border-radius: 0.375rem; font-family: inherit;
font-size: 0.75rem; cursor: pointer; transition: all 0.2s; white-space: nowrap;
}
.toolbar-actions button:hover { background: rgba(51, 65, 85, 0.8); color: white; border-color: #475569; }
@media print {
body { background: #020617; padding: 1rem; }
.toolbar { display: none !important; }
}
</style>
</head>
<body>
<div class="container" id="report-container">
<div class="header">
<div class="header-row">
<div class="pulse-dot"></div>
<h1>AWS Serverless Architecture</h1>
<div class="toolbar">
<div class="toolbar-actions">
<button onclick="copyAsImage(this)">📋 Copy</button>
<button onclick="downloadPNG(this)">🖼️ PNG</button>
<button onclick="downloadPDF(this)">📄 PDF</button>
</div>
<button class="toolbar-toggle" onclick="this.parentElement.classList.toggle('expanded')" title="Export options" aria-label="Export options"></button>
</div>
</div>
<p class="subtitle">Lambda + API Gateway + DynamoDB</p>
</div>
<div class="diagram-container">
<svg viewBox="0 0 950 480">
<defs>
<marker id="arrowhead" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#64748b" />
</marker>
<pattern id="grid" width="40" height="40" patternUnits="userSpaceOnUse">
<path d="M 40 0 L 0 0 0 40" fill="none" stroke="#1e293b" stroke-width="0.5"/>
</pattern>
</defs>
<rect width="100%" height="100%" fill="url(#grid)" />
<!-- AWS Region -->
<rect x="140" y="30" width="780" height="420" rx="12" fill="rgba(251, 191, 36, 0.05)" stroke="#fbbf24" stroke-width="1" stroke-dasharray="8,4"/>
<text x="155" y="52" fill="#fbbf24" font-size="10" font-weight="600">AWS Region: us-east-1</text>
<!-- Arrows -->
<line x1="120" y1="200" x2="188" y2="200" stroke="#64748b" stroke-width="1.5" marker-end="url(#arrowhead)"/>
<line x1="310" y1="200" x2="378" y2="200" stroke="#64748b" stroke-width="1.5" marker-end="url(#arrowhead)"/>
<line x1="500" y1="200" x2="568" y2="200" stroke="#64748b" stroke-width="1.5" marker-end="url(#arrowhead)"/>
<line x1="690" y1="200" x2="758" y2="200" stroke="#64748b" stroke-width="1.5" marker-end="url(#arrowhead)"/>
<!-- Lambda to S3 -->
<line x1="630" y1="240" x2="630" y2="318" stroke="#64748b" stroke-width="1.5" marker-end="url(#arrowhead)"/>
<!-- Lambda to SQS -->
<line x1="630" y1="160" x2="630" y2="102" stroke="#64748b" stroke-width="1.5" marker-end="url(#arrowhead)"/>
<!-- Users -->
<rect x="20" y="160" width="100" height="80" rx="6" fill="#0f172a"/>
<rect x="20" y="160" width="100" height="80" rx="6" fill="rgba(30, 41, 59, 0.5)" stroke="#94a3b8" stroke-width="1.5"/>
<text x="70" y="195" fill="white" font-size="12" font-weight="600" text-anchor="middle">Clients</text>
<text x="70" y="215" fill="#94a3b8" font-size="9" text-anchor="middle">Web/Mobile</text>
<!-- CloudFront -->
<rect x="190" y="160" width="120" height="80" rx="6" fill="#0f172a"/>
<rect x="190" y="160" width="120" height="80" rx="6" fill="rgba(120, 53, 15, 0.3)" stroke="#fbbf24" stroke-width="1.5"/>
<text x="250" y="195" fill="white" font-size="11" font-weight="600" text-anchor="middle">CloudFront</text>
<text x="250" y="215" fill="#94a3b8" font-size="9" text-anchor="middle">CDN</text>
<!-- API Gateway -->
<rect x="380" y="150" width="120" height="100" rx="6" fill="#0f172a"/>
<rect x="380" y="150" width="120" height="100" rx="6" fill="rgba(120, 53, 15, 0.3)" stroke="#fbbf24" stroke-width="1.5"/>
<text x="440" y="185" fill="white" font-size="11" font-weight="600" text-anchor="middle">API Gateway</text>
<text x="440" y="205" fill="#94a3b8" font-size="9" text-anchor="middle">REST API</text>
<text x="440" y="220" fill="#94a3b8" font-size="9" text-anchor="middle">WebSocket</text>
<text x="440" y="240" fill="#fbbf24" font-size="8" text-anchor="middle">HTTPS</text>
<!-- Lambda -->
<rect x="570" y="140" width="120" height="120" rx="6" fill="#0f172a"/>
<rect x="570" y="140" width="120" height="120" rx="6" fill="rgba(6, 78, 59, 0.4)" stroke="#34d399" stroke-width="1.5"/>
<text x="630" y="180" fill="white" font-size="11" font-weight="600" text-anchor="middle">Lambda</text>
<text x="630" y="200" fill="#94a3b8" font-size="9" text-anchor="middle">Functions</text>
<text x="630" y="215" fill="#94a3b8" font-size="9" text-anchor="middle">Node.js/Python</text>
<text x="630" y="248" fill="#34d399" font-size="8" text-anchor="middle">Auto-scaling</text>
<!-- DynamoDB -->
<rect x="760" y="150" width="130" height="100" rx="6" fill="#0f172a"/>
<rect x="760" y="150" width="130" height="100" rx="6" fill="rgba(76, 29, 149, 0.4)" stroke="#a78bfa" stroke-width="1.5"/>
<text x="825" y="185" fill="white" font-size="11" font-weight="600" text-anchor="middle">DynamoDB</text>
<text x="825" y="205" fill="#94a3b8" font-size="9" text-anchor="middle">NoSQL Database</text>
<text x="825" y="220" fill="#94a3b8" font-size="9" text-anchor="middle">On-demand</text>
<text x="825" y="240" fill="#a78bfa" font-size="8" text-anchor="middle">Single-digit ms</text>
<!-- S3 -->
<rect x="570" y="320" width="120" height="90" rx="6" fill="#0f172a"/>
<rect x="570" y="320" width="120" height="90" rx="6" fill="rgba(120, 53, 15, 0.3)" stroke="#fbbf24" stroke-width="1.5"/>
<text x="630" y="355" fill="white" font-size="11" font-weight="600" text-anchor="middle">S3</text>
<text x="630" y="375" fill="#94a3b8" font-size="9" text-anchor="middle">Object Storage</text>
<text x="630" y="400" fill="#fbbf24" font-size="8" text-anchor="middle">Static Assets</text>
<!-- SQS -->
<rect x="570" y="60" width="120" height="40" rx="6" fill="#0f172a"/>
<rect x="570" y="60" width="120" height="40" rx="6" fill="rgba(120, 53, 15, 0.3)" stroke="#fbbf24" stroke-width="1.5"/>
<text x="630" y="85" fill="white" font-size="11" font-weight="600" text-anchor="middle">SQS</text>
<!-- Legend -->
<text x="170" y="420" fill="white" font-size="10" font-weight="600">Legend</text>
<rect x="170" y="432" width="16" height="10" rx="2" fill="rgba(120, 53, 15, 0.3)" stroke="#fbbf24" stroke-width="1"/>
<text x="192" y="440" fill="#94a3b8" font-size="8">AWS Service</text>
<rect x="270" y="432" width="16" height="10" rx="2" fill="rgba(6, 78, 59, 0.4)" stroke="#34d399" stroke-width="1"/>
<text x="292" y="440" fill="#94a3b8" font-size="8">Compute</text>
<rect x="360" y="432" width="16" height="10" rx="2" fill="rgba(76, 29, 149, 0.4)" stroke="#a78bfa" stroke-width="1"/>
<text x="382" y="440" fill="#94a3b8" font-size="8">Database</text>
</svg>
</div>
<div class="cards">
<div class="card">
<div class="card-header">
<div class="card-dot amber"></div>
<h3>Infrastructure</h3>
</div>
<ul>
<li>• CloudFront CDN distribution</li>
<li>• API Gateway REST endpoints</li>
<li>• S3 static asset hosting</li>
<li>• SQS message queues</li>
</ul>
</div>
<div class="card">
<div class="card-header">
<div class="card-dot emerald"></div>
<h3>Compute</h3>
</div>
<ul>
<li>• Lambda functions (Node.js)</li>
<li>• Auto-scaling to zero</li>
<li>• Pay-per-invocation</li>
<li>• 15 min max execution</li>
</ul>
</div>
<div class="card">
<div class="card-header">
<div class="card-dot violet"></div>
<h3>Data</h3>
</div>
<ul>
<li>• DynamoDB tables</li>
<li>• On-demand capacity</li>
<li>• Global secondary indexes</li>
<li>• Point-in-time recovery</li>
</ul>
</div>
</div>
<p class="footer">AWS Serverless Architecture • Event-driven design</p>
</div>
<script>
async function copyAsImage(btn) {
const orig = btn.textContent;
try {
const el = document.getElementById('report-container');
const r = el.getBoundingClientRect();
const pad = 32;
const canvas = await html2canvas(document.body, { backgroundColor: '#020617', scale: 2, useCORS: true, ignoreElements: (e) => e.classList && e.classList.contains('toolbar'), x: r.left + window.scrollX - pad, y: r.top + window.scrollY - pad, width: r.width + pad * 2, height: r.height + pad * 2 });
const blob = await new Promise(r => canvas.toBlob(r, 'image/png'));
await navigator.clipboard.write([new ClipboardItem({ 'image/png': blob })]);
btn.textContent = '✓ Copied!';
} catch (e) {
btn.textContent = '✗ Failed';
}
setTimeout(() => btn.textContent = orig, 2000);
}
async function downloadPNG(btn) {
const orig = btn.textContent;
btn.textContent = '⏳ ...';
try {
const el = document.getElementById('report-container');
const r = el.getBoundingClientRect();
const pad = 32;
const canvas = await html2canvas(document.body, { backgroundColor: '#020617', scale: 2, useCORS: true, ignoreElements: (e) => e.classList && e.classList.contains('toolbar'), x: r.left + window.scrollX - pad, y: r.top + window.scrollY - pad, width: r.width + pad * 2, height: r.height + pad * 2 });
const link = document.createElement('a');
link.download = 'aws-serverless.png';
link.href = canvas.toDataURL('image/png');
link.click();
btn.textContent = '✓ Done!';
} catch (e) {
btn.textContent = '✗ Failed';
}
setTimeout(() => btn.textContent = orig, 2000);
}
async function downloadPDF(btn) {
const orig = btn.textContent;
btn.textContent = '⏳ ...';
try {
const el = document.getElementById('report-container');
const r = el.getBoundingClientRect();
const pad = 32;
const canvas = await html2canvas(document.body, { backgroundColor: '#020617', scale: 2, useCORS: true, ignoreElements: (e) => e.classList && e.classList.contains('toolbar'), x: r.left + window.scrollX - pad, y: r.top + window.scrollY - pad, width: r.width + pad * 2, height: r.height + pad * 2 });
const imgData = canvas.toDataURL('image/png');
const { jsPDF } = window.jspdf;
const orientation = canvas.width > canvas.height ? 'landscape' : 'portrait';
const pdf = new jsPDF({ orientation, unit: 'px', format: [canvas.width, canvas.height], hotfixes: ['px_scaling'] });
pdf.addImage(imgData, 'PNG', 0, 0, canvas.width, canvas.height);
pdf.save('aws-serverless.pdf');
btn.textContent = '✓ Done!';
} catch (e) {
btn.textContent = '✗ Failed';
}
setTimeout(() => btn.textContent = orig, 2000);
}
</script>
</body>
</html>