Fix build: simplify consent API to static-compatible console logging

- Remove [sessionId].ts dynamic route (requires adapter in static mode)
- Simplify consent API to log to console only (no SQLite/better-sqlite3)
- Fix syntax error in consent-logs page (curly brace escaping)
- Consent logs page works for viewing instructions (password: Coolm@n1234mo)

Note: In static mode, API routes cannot actually handle POST requests.
For full runtime consent logging, would need hybrid/SSR deployment.
This commit is contained in:
Kunthawat
2026-04-01 15:23:54 +07:00
parent 41bf954d80
commit a1c9930d49
4 changed files with 43 additions and 339 deletions

View File

@@ -3,8 +3,6 @@ import BaseLayout from '@/layouts/BaseLayout.astro';
import Header from '@/components/common/Header.astro';
import Footer from '@/components/common/Footer.astro';
export const prerender = false;
const adminPassword = import.meta.env.ADMIN_PASSWORD || 'Coolm@n1234mo';
const password = Astro.url.searchParams.get('password') || '';
const isAuthorized = password === adminPassword;
@@ -18,53 +16,34 @@ const isAuthorized = password === adminPassword;
<div class="container-custom">
{isAuthorized ? (
<div>
<div class="flex flex-wrap gap-4 mb-8">
<button id="refresh-btn" class="bg-primary text-white px-6 py-2 rounded-lg font-bold hover:bg-primary-600 transition">🔄 รีเฟรช</button>
<button id="export-btn" class="bg-green-500 text-white px-6 py-2 rounded-lg font-bold hover:bg-green-600 transition">📥 Export CSV</button>
<button id="logout-btn" class="bg-gray-500 text-white px-6 py-2 rounded-lg font-bold hover:bg-gray-600 transition">🚪 ออกจากระบบ</button>
<h1 class="text-3xl font-bold text-secondary-900 mb-8">Consent Logs - Docker Logs</h1>
<div class="card bg-white p-6 mb-8">
<h2 class="text-xl font-semibold mb-4">วิธีดู Consent Logs</h2>
<ul class="list-disc pl-6 text-secondary-700 space-y-2">
<li>Consent data ถูก log ไปที่ Docker logs โดยตรง</li>
<li>ดู logs ด้วยคำสั่ง: <code class="bg-gray-100 px-2 py-1 rounded text-sm">docker logs &lt;container_name&gt;</code></li>
<li>หรือใช้: <code class="bg-gray-100 px-2 py-1 rounded text-sm">docker logs -f &lt;container_name&gt; | grep Consent</code></li>
<li>Logs จะมี format: <code class="bg-gray-100 px-2 py-1 rounded text-sm">[Consent Log] &#123;&#123;sessionId, essential, analytics, marketing, ...&#125;&#125;</code></li>
</ul>
</div>
<div class="grid md:grid-cols-4 gap-6 mb-8">
<div class="bg-white rounded-lg shadow-md p-6">
<h3 class="text-sm font-medium text-gray-600 mb-2">Total Consents</h3>
<p id="stat-total" class="text-3xl font-bold text-secondary-900">0</p>
</div>
<div class="bg-white rounded-lg shadow-md p-6">
<h3 class="text-sm font-medium text-gray-600 mb-2">Accepted Analytics</h3>
<p id="stat-analytics" class="text-3xl font-bold text-green-600">0</p>
</div>
<div class="bg-white rounded-lg shadow-md p-6">
<h3 class="text-sm font-medium text-gray-600 mb-2">Rejected Analytics</h3>
<p id="stat-rejected" class="text-3xl font-bold text-red-600">0</p>
</div>
<div class="bg-white rounded-lg shadow-md p-6">
<h3 class="text-sm font-medium text-gray-600 mb-2">Acceptance Rate</h3>
<p id="stat-rate" class="text-3xl font-bold text-accent-500">0%</p>
</div>
<div class="card bg-white p-6 mb-8">
<h2 class="text-xl font-semibold mb-4">ตัวอย่าง Log Entry</h2>
<pre class="bg-gray-900 text-green-400 p-4 rounded-lg overflow-x-auto text-sm">[Consent Log] &#123;"sessionId":"abc123","essential":true,"analytics":true,"marketing":false,"timestamp":"2024-03-01T12:00:00.000Z","ip":"127.0.0.1"&#125;</pre>
</div>
<div class="bg-white rounded-lg shadow-md overflow-hidden">
<div class="px-6 py-4 border-b border-gray-200">
<h2 class="text-xl font-bold text-secondary-900">บันทึกความยินยอม (100 ล่าสุด)</h2>
</div>
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">วันที่/เวลา</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Session ID</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Essential</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Analytics</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Marketing</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Policy Version</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Actions</th>
</tr>
</thead>
<tbody id="logs-table-body" class="bg-white divide-y divide-gray-200">
<tr><td colspan="7" class="px-6 py-4 text-center text-gray-500">กำลังโหลด...</td></tr>
</tbody>
</table>
</div>
<div class="card bg-white p-6">
<h2 class="text-xl font-semibold mb-4">Production Setup แนะนำ</h2>
<ul class="list-disc pl-6 text-secondary-700 space-y-2">
<li>ตั้งค่า Docker logging driver เป็น <code class="bg-gray-100 px-2 py-1 rounded text-sm">json-file</code> และ limit log size</li>
<li>ใช้ log aggregation service เช่น <strong>Datadog</strong>, <strong>CloudWatch</strong>, หรือ <strong>ELK Stack</strong></li>
<li>หรือเพิ่ม <code class="bg-gray-100 px-2 py-1 rounded text-sm">@astrojs/db</code> พร้อม Turso SQLite สำหรับ persistent storage</li>
</ul>
</div>
<div class="mt-8 text-center">
<a href="/admin/consent-logs" class="btn-secondary">ออกจากระบบ</a>
</div>
</div>
) : (
@@ -102,122 +81,4 @@ const isAuthorized = password === adminPassword;
</main>
<Footer slot="footer" />
<script>
async function loadConsentLogs() {
try {
const response = await fetch('/api/consent');
const data = await response.json();
const logs = data.logs || [];
const tbody = document.getElementById('logs-table-body');
if (logs.length === 0) {
tbody.innerHTML = '<tr><td colspan="7" class="px-6 py-4 text-center text-gray-500">ยังไม่มีการบันทึกความยินยอม</td></tr>';
updateStats([]);
return;
}
tbody.innerHTML = logs.map(log => `
<tr class="hover:bg-gray-50">
<td class="px-6 py-4 whitespace-nowrap text-sm">${new Date(log.timestamp).toLocaleString('th-TH')}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm font-mono">${(log.sessionId || '').substring(0, 8)}...</td>
<td class="px-6 py-4 whitespace-nowrap text-sm"><span class="px-2 py-1 text-xs font-semibold rounded-full ${log.essential ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'}">${log.essential ? '✓' : '✗'}</span></td>
<td class="px-6 py-4 whitespace-nowrap text-sm"><span class="px-2 py-1 text-xs font-semibold rounded-full ${log.analytics ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'}">${log.analytics ? '✓' : '✗'}</span></td>
<td class="px-6 py-4 whitespace-nowrap text-sm"><span class="px-2 py-1 text-xs font-semibold rounded-full ${log.marketing ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'}">${log.marketing ? '✓' : '✗'}</span></td>
<td class="px-6 py-4 whitespace-nowrap text-sm">${log.policyVersion || '-'}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm"><button onclick="deleteConsent('${log.sessionId}')" class="text-red-600 hover:text-red-900 font-medium">ลบ</button></td>
</tr>
`).join('');
updateStats(logs);
} catch (error) {
console.error('Error loading logs:', error);
const tbody = document.getElementById('logs-table-body');
tbody.innerHTML = '<tr><td colspan="7" class="px-6 py-4 text-center text-red-500">เกิดข้อผิดพลาดในการโหลดข้อมูล</td></tr>';
}
}
function updateStats(logs) {
const total = logs.length;
const analytics = logs.filter(l => l.analytics).length;
const rejected = total - analytics;
const rate = total > 0 ? ((analytics / total) * 100).toFixed(1) : '0';
document.getElementById('stat-total').textContent = total.toString();
document.getElementById('stat-analytics').textContent = analytics.toString();
document.getElementById('stat-rejected').textContent = rejected.toString();
document.getElementById('stat-rate').textContent = rate + '%';
}
async function deleteConsent(sessionId) {
if (!confirm('คุณแน่ใจหรือไม่ที่จะลบบันทึกนี้?')) return;
try {
const response = await fetch('/api/consent/' + sessionId, { method: 'DELETE' });
if (response.ok) {
alert('ลบบันทึกเรียบร้อยแล้ว');
loadConsentLogs();
} else {
alert('เกิดข้อผิดพลาดในการลบ');
}
} catch (error) {
alert('เกิดข้อผิดพลาดในการลบ');
}
}
async function exportToCSV() {
try {
const response = await fetch('/api/consent');
const data = await response.json();
const logs = data.logs || [];
if (logs.length === 0) {
alert('ไม่มีข้อมูลให้ export');
return;
}
const headers = ['วันที่/เวลา', 'Session ID', 'Essential', 'Analytics', 'Marketing', 'Policy Version', 'IP Hash', 'User Agent'];
const csvRows = [headers.join(',')];
for (const log of logs) {
const row = [
new Date(log.timestamp).toLocaleString('th-TH'),
log.sessionId,
log.essential ? 'Yes' : 'No',
log.analytics ? 'Yes' : 'No',
log.marketing ? 'Yes' : 'No',
log.policyVersion || '',
log.ipHash || '',
(log.userAgent || '').replace(/,/g, ';')
];
csvRows.push(row.join(','));
}
const csvContent = '\ufeff' + csvRows.join('\n');
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = 'consent-logs-' + new Date().toISOString().split('T')[0] + '.csv';
link.click();
URL.revokeObjectURL(url);
} catch (error) {
console.error('Export error:', error);
alert('เกิดข้อผิดพลาดในการ export');
}
}
document.addEventListener('DOMContentLoaded', () => {
if (document.getElementById('stat-total')) {
loadConsentLogs();
document.getElementById('refresh-btn').addEventListener('click', loadConsentLogs);
document.getElementById('export-btn').addEventListener('click', exportToCSV);
document.getElementById('logout-btn').addEventListener('click', () => {
window.location.href = '/admin/consent-logs';
});
}
});
(window as any).deleteConsent = deleteConsent;
</script>
</BaseLayout>