Files
crawlmanager/views/admin/adsense.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

116 lines
4.5 KiB
Plaintext

<%- include('layout', { page: 'adsense', pageTitle: 'AdSense 관리', body: `
<div class="card">
<div class="card-header">
<h2>AdSense 설정 목록</h2>
<button class="btn btn-primary" onclick="openModal()">+ AdSense 추가</button>
</div>
<table>
<thead><tr><th>ID</th><th>이름</th><th>Client ID</th><th>상단 슬롯</th><th>중간 슬롯</th><th>하단 슬롯</th><th>상태</th><th>액션</th></tr></thead>
<tbody id="ads-tbody"></tbody>
</table>
</div>
<div class="modal-overlay" id="adsModal">
<div class="modal">
<h3 id="ads-modal-title">AdSense 추가</h3>
<input type="hidden" id="ads-edit-id">
<div class="form-group">
<label>이름 (구분용)</label>
<input id="ads-name" placeholder="예: 메인사이트 애드센스">
</div>
<div class="form-group">
<label>Client ID (ca-pub-XXXX)</label>
<input id="ads-client" placeholder="ca-pub-1234567890123456">
</div>
<div class="form-row">
<div class="form-group">
<label>상단 광고 슬롯 ID</label>
<input id="ads-slot-top" placeholder="1234567890">
</div>
<div class="form-group">
<label>중간 광고 슬롯 ID</label>
<input id="ads-slot-mid" placeholder="1234567891">
</div>
</div>
<div class="form-group">
<label>하단 광고 슬롯 ID</label>
<input id="ads-slot-bot" placeholder="1234567892">
</div>
<div class="flex" style="justify-content:flex-end;gap:.5rem;margin-top:1rem">
<button class="btn btn-outline" onclick="closeModal()">취소</button>
<button class="btn btn-primary" onclick="saveAds()">저장</button>
</div>
</div>
</div>
<script>
let adsList = [];
async function loadAds() {
adsList = await api('GET', '/api/adsense');
document.getElementById('ads-tbody').innerHTML = adsList.map(a => {
const slots = a.slots || {};
return '<tr><td>' + a.id + '</td><td><strong>' + a.name + '</strong></td><td class="text-muted">' + a.client_id + '</td>' +
'<td>' + (slots.top || '-') + '</td><td>' + (slots.middle || '-') + '</td><td>' + (slots.bottom || '-') + '</td>' +
'<td><span class="badge badge-' + (a.is_active ? 'success">활성' : 'danger">비활성') + '</span></td>' +
'<td class="flex"><button class="btn btn-outline btn-sm" onclick="editAds(' + a.id + ')">수정</button>' +
'<button class="btn btn-danger btn-sm" onclick="deleteAds(' + a.id + ')">삭제</button></td></tr>';
}).join('') || '<tr><td colspan="8" class="text-muted" style="text-align:center;padding:2rem">AdSense 설정을 추가하세요</td></tr>';
}
function openModal() {
document.getElementById('ads-modal-title').textContent = 'AdSense 추가';
document.getElementById('ads-edit-id').value = '';
['ads-name','ads-client','ads-slot-top','ads-slot-mid','ads-slot-bot'].forEach(id => document.getElementById(id).value = '');
document.getElementById('adsModal').classList.add('active');
}
function editAds(id) {
const a = adsList.find(x => x.id === id);
if (!a) return;
document.getElementById('ads-modal-title').textContent = 'AdSense 수정';
document.getElementById('ads-edit-id').value = a.id;
document.getElementById('ads-name').value = a.name;
document.getElementById('ads-client').value = a.client_id;
document.getElementById('ads-slot-top').value = a.slots?.top || '';
document.getElementById('ads-slot-mid').value = a.slots?.middle || '';
document.getElementById('ads-slot-bot').value = a.slots?.bottom || '';
document.getElementById('adsModal').classList.add('active');
}
function closeModal() { document.getElementById('adsModal').classList.remove('active'); }
async function saveAds() {
const data = {
name: document.getElementById('ads-name').value,
client_id: document.getElementById('ads-client').value,
slots: {
top: document.getElementById('ads-slot-top').value,
middle: document.getElementById('ads-slot-mid').value,
bottom: document.getElementById('ads-slot-bot').value,
},
is_active: true,
};
if (!data.name || !data.client_id) { toast('이름과 Client ID는 필수입니다', 'error'); return; }
const editId = document.getElementById('ads-edit-id').value;
if (editId) {
await api('PUT', '/api/adsense/' + editId, data);
toast('수정 완료');
} else {
await api('POST', '/api/adsense', data);
toast('추가 완료');
}
closeModal(); loadAds();
}
async function deleteAds(id) {
if (!confirm('삭제하시겠습니까?')) return;
await api('DELETE', '/api/adsense/' + id);
toast('삭제 완료'); loadAds();
}
loadAds();
</script>
` }) %>