c61f10560f
- 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>
116 lines
4.5 KiB
Plaintext
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>
|
|
` }) %>
|