/* portal/domains/public/domains.js — 도메인 화면 스크립트(목록·연결·상세). */ (() => { const $ = (s, r = document) => r.querySelector(s); const esc = aw.esc; const fmt = aw.fmt; const OWN = { pending: ['warning', '확인 전'], verified: ['ok', '확인됨'] }; const DNS = { pending: ['warning', '미반영'], wrong: ['failed', '잘못된 값'], ok: ['ok', '정상'], customer: ['neutral', '고객 관리'], 'n/a': ['neutral', '해당 없음'] }; const CERT = { none: ['neutral', '없음'], issuing: ['running', '발급 중'], valid: ['ok', '정상'], expiring_soon: ['warning', '만료 임박'], failed: ['failed', '실패'], customer_managed: ['neutral', '고객 관리'] }; const MDNS = { ok: ['ok', '정상'], warn: ['warning', '권장 개선'], error: ['failed', '오류'], unknown: ['neutral', '확인 전'] }; const badge = (m, k) => { const v = m[k] || ['neutral', k || '-']; return `${({ ok: '●', failed: '✘', warning: '!', running: '◌', neutral: '○' })[v[0]]} ${esc(v[1])}`; }; const onErr = (el, e, retry) => aw.state(el, e.status === 403 ? 'forbidden' : e.code === 'service_unreachable' || e.status === 503 ? 'stale' : 'failed', e.message, { retry }); async function list() { const el = $('#list'); try { const r = await aw.api('GET', '/api/domains'); if (!r.domains.length) return aw.state(el, 'empty', '연결된 도메인이 없습니다', { href: '/domains/connect', hrefLabel: '도메인 연결' }); el.innerHTML = `${r.domains.map((d) => ``).join('')}
도메인연결용도소유 확인DNSHTTPS메일 보안마지막 확인
${esc(d.name)}${d.isPrimary ? ' ★' : ''}${d.actionNeeded ? `
! ${esc(d.actionNeeded.text)}` : ''}
${d.site ? `사이트 ${esc(d.site.name)}` : ''}${d.mailService ? `
메일 ${esc(d.mailService.name)}` : ''}${!d.site && !d.mailService ? '없음' : ''}
${esc({ web: '웹', mail: '메일', both: '웹+메일' }[d.purpose])}${badge(OWN, d.ownershipState)}${badge(DNS, d.dnsState)}${badge(CERT, d.certState)}${d.certExpiresAt ? `
만료 ${esc(d.certExpiresAt.slice(0, 10))}` : ''}
${d.mailDnsState ? badge(MDNS, d.mailDnsState) : '-'}${d.lastCheckedAt ? esc(fmt(d.lastCheckedAt)) : '-'}${d.recentFailures ? `
최근 실패 ${d.recentFailures}회` : ''}
`; } catch (e) { onErr(el, e, list); } } async function connect() { const f = $('#connect'); const sel = $('#siteId'); try { const r = await aw.api('GET', '/api/sites'); sel.innerHTML = '' + r.sites.map((s) => ``).join(''); } catch (_) {} f.onsubmit = async (e) => { e.preventDefault(); f.querySelectorAll('.err').forEach((x) => { x.textContent = ''; }); const fd = new FormData(f); try { const r = await aw.api('POST', '/api/domains', { name: fd.get('name'), purpose: fd.get('purpose'), siteId: fd.get('siteId') || null, makePrimary: fd.get('makePrimary') === 'on' }); location.href = `/domains/${r.domain.id}`; } catch (err) { (f.querySelector(`[data-err="${err.detail && err.detail.field || 'form'}"]`) || f.querySelector('[data-err=form]')).innerHTML = esc(err.message) + (err.detail && err.detail.href ? ` 열기` : ''); } }; } async function detail() { const el = $('#detail'); const id = el.dataset.domain; const load = async () => { try { const r = await aw.api('GET', `/api/domains/${id}`); const d = r.domain; const job = d.job; el.innerHTML = `

상태

용도
${esc({ web: '웹', mail: '메일', both: '웹+메일' }[d.purpose])}
연결
${d.site ? `사이트 ${esc(d.site.name)}${d.site.deployMode === 'external' ? ' (외부 배포 — DNS·인증서는 고객 관리)' : ''}` : '사이트 없음'}${d.mailService ? ` · 메일 ${esc(d.mailService.name)}` : ''}
대표 도메인
${d.isPrimary ? '★ 대표' : d.site ? '' : '-'}${d.redirectFrom ? `
이전 주소 ${esc(d.redirectFrom)} 는 이 도메인으로 이동 처리` : ''}
소유 확인
${badge(OWN, d.ownershipState)}
DNS
${badge(DNS, d.dnsState)}${d.dnsDetail && d.dnsDetail.got ? ` 지금 값: ${esc(d.dnsDetail.got.join(', ') || '없음')} / 필요 ${esc(d.dnsDetail.expected)}` : ''}
HTTPS 인증서
${badge(CERT, d.certState)}${d.certExpiresAt ? ` 만료 ${esc(d.certExpiresAt.slice(0, 10))}` : ''}${d.certError ? `
${esc(d.certError)}` : ''}
${d.mailDnsState ? `
메일 보안 레코드
${badge(MDNS, d.mailDnsState)} 메일 화면에서 확인
` : ''}
마지막 외부 확인
${d.externalCheck ? `${d.externalCheck.ok ? '정상' : '실패'} (${d.externalCheck.status || '-'}, TLS ${d.externalCheck.tls && d.externalCheck.tls.valid ? '유효' : '무효/미확인'})` : '-'}
마지막 확인
${d.lastCheckedAt ? esc(fmt(d.lastCheckedAt)) : '-'}

필요한 DNS 값

DNS 를 직접 관리하면 아래 값을 그대로 넣으세요. 미반영(레코드 없음)과 잘못된 값(다른 값)을 구분해 표시합니다.

${r.records.map((x) => ``).join('')}
종류이름값용도상태
${esc(x.type)}${esc(x.name)}${x.value ? `${esc(x.value)}` : `${esc(x.note || '-')}`}${esc(x.purpose)}${esc({ ok: '정상', pending: '미반영', wrong: '잘못된 값', optional: '선택', customer: '고객 관리', unknown: '확인 전', 'n/a': '-' }[x.state] || x.state || '-')}

연결 진행

확인 기록

${d.checks.length ? `${d.checks.map((c) => ``).join('')}
시각종류결과
${esc(fmt(c.checked_at))}${esc(c.kind)}${c.ok ? '✔ 성공' : '✘ 실패'} ${esc(JSON.stringify(c.detail || {})).slice(0, 160)}
` : '

기록 없음

'}
`; if (job) aw.job($('#job'), job, { onAction: async (a, j) => { try { await aw.api('POST', `/api/domains/${id}/jobs/${j.id}/${a === 'go' ? 'resume' : a}`); load(); } catch (e) { aw.toast(e.message); } } }); else $('#job').innerHTML = '

진행 중인 확인 작업이 없습니다. "다시 확인" 으로 시작하세요

'; $('#btnCheck').onclick = async () => { try { await aw.api('POST', `/api/domains/${id}/check`, {}); aw.toast('확인을 시작했습니다'); load(); } catch (e) { aw.toast(e.message); } }; const bp = $('#btnPrimary'); if (bp) bp.onclick = async () => { const redirect = confirm('기존 대표 주소를 새 대표 도메인으로 이동(301) 처리할까요?'); try { await aw.api('POST', `/api/domains/${id}/primary`, { redirectOld: redirect }); aw.toast('대표 도메인으로 지정했습니다'); load(); } catch (e) { aw.toast(e.message + (e.detail ? ` (소유 ${e.detail.ownership}, DNS ${e.detail.dns}, 인증서 ${e.detail.cert})` : '')); } }; $('#btnRelease').onclick = async () => { if (!(await aw.confirmDanger({ title: '도메인 연결 해제', target: d.name, impacts: [d.isPrimary ? '대표 도메인이라 사이트가 "도메인 설정 필요" 상태가 됩니다' : '이 도메인으로의 접속이 끊깁니다', '인증서·DNS 상태 기록이 지워집니다(도메인 자체는 등록기관에 그대로 있음)'], typeToConfirm: d.name }))) return; try { await aw.api('DELETE', `/api/domains/${id}`); location.href = '/domains'; } catch (e) { aw.toast(e.message); } }; return !!(job && ['queued', 'running'].includes(job.status)); } catch (e) { onErr(el, e, load); return false; } }; if (await load()) aw.poll(load, 4000); } const page = document.querySelector('[data-page]')?.dataset.page; ({ 'domains-list': list, 'domains-connect': connect, 'domains-detail': detail }[page] || (() => {}))(); })();