Files
crawlmanager/views/admin/dashboard.ejs
T
chpark c61f10560f init: 크롤링 관리 솔루션 초기 구성
- Express.js 기반 관리자 페이지 (사이트/크롤링/AdSense/도메인 관리)
- PostgreSQL 16 + Docker Compose (Traefik 연동)
- 크롤러: axios + cheerio 기반 HTML 파싱
- 스케줄러: node-cron 기반 자동 크롤링
- 공개 사이트: slug/도메인 기반 DB에서 렌더링 HTML 서빙
- 도메인: admin.startover.co.kr

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 00:44:19 +09:00

57 lines
3.1 KiB
Plaintext

<%- include('layout', { page: 'dashboard', pageTitle: '대시보드', body: `
<div class="stats-grid" id="stats">
<div class="stat-card"><div class="number" id="stat-sites">-</div><div class="label">등록된 사이트</div></div>
<div class="stat-card"><div class="number" id="stat-active">-</div><div class="label">스케줄 활성</div></div>
<div class="stat-card"><div class="number" id="stat-crawls">-</div><div class="label">총 크롤링 횟수</div></div>
<div class="stat-card"><div class="number" id="stat-adsense">-</div><div class="label">AdSense 설정</div></div>
</div>
<div class="card">
<div class="card-header">
<h2>사이트 현황</h2>
<a href="/admin/sites" class="btn btn-primary btn-sm">사이트 관리 &rarr;</a>
</div>
<table>
<thead><tr><th>사이트명</th><th>URL</th><th>스케줄</th><th>마지막 크롤링</th><th>상태</th><th>공개 URL</th></tr></thead>
<tbody id="site-table"></tbody>
</table>
</div>
<div class="card">
<div class="card-header"><h2>최근 로그</h2></div>
<table>
<thead><tr><th>시간</th><th>사이트</th><th>액션</th><th>메시지</th></tr></thead>
<tbody id="log-table"></tbody>
</table>
</div>
<script>
async function loadDashboard() {
const [sites, adsense, logs] = await Promise.all([
api('GET', '/api/sites'),
api('GET', '/api/adsense'),
api('GET', '/api/logs?limit=10'),
]);
document.getElementById('stat-sites').textContent = sites.length;
document.getElementById('stat-active').textContent = sites.filter(s => s.schedule_active).length;
document.getElementById('stat-crawls').textContent = sites.reduce((a, s) => a + parseInt(s.crawl_count || 0), 0);
document.getElementById('stat-adsense').textContent = adsense.length;
document.getElementById('site-table').innerHTML = sites.map(s => {
const schedBadge = s.schedule_active
? '<span class="badge badge-success">' + (s.cron_schedule || 'ON') + '</span>'
: '<span class="badge badge-danger">OFF</span>';
const slug = s.slug ? '<a href="/s/' + s.slug + '" target="_blank" style="color:var(--primary)">/s/' + s.slug + '</a>' : '-';
return '<tr><td><strong>' + s.name + '</strong></td><td class="text-muted" style="max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">' + s.url + '</td><td>' + schedBadge + '</td><td>' + timeAgo(s.last_crawled_at) + '</td><td><span class="badge badge-' + (s.status === 'active' ? 'success' : 'danger') + '">' + s.status + '</span></td><td>' + slug + '</td></tr>';
}).join('') || '<tr><td colspan="6" class="text-muted" style="text-align:center;padding:2rem">등록된 사이트가 없습니다. <a href="/admin/sites" style="color:var(--primary)">사이트를 추가하세요</a></td></tr>';
document.getElementById('log-table').innerHTML = logs.map(l =>
'<tr><td class="text-muted">' + timeAgo(l.created_at) + '</td><td>' + (l.site_name || '-') + '</td><td><span class="badge badge-info">' + l.action + '</span></td><td>' + (l.message || '').substring(0, 80) + '</td></tr>'
).join('') || '<tr><td colspan="4" class="text-muted" style="text-align:center">로그가 없습니다</td></tr>';
}
loadDashboard();
</script>
` }) %>