/* 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 = `
| 도메인 | 연결 | 용도 | 소유 확인 | DNS | HTTPS | 메일 보안 | 마지막 확인 |
|---|---|---|---|---|---|---|---|
| ${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}회` : ''} |
DNS 를 직접 관리하면 아래 값을 그대로 넣으세요. 미반영(레코드 없음)과 잘못된 값(다른 값)을 구분해 표시합니다.
| 종류 | 이름 | 값 | 용도 | 상태 |
|---|---|---|---|---|
| ${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 || '-')} |
| 시각 | 종류 | 결과 |
|---|---|---|
| ${esc(fmt(c.checked_at))} | ${esc(c.kind)} | ${c.ok ? '✔ 성공' : '✘ 실패'} ${esc(JSON.stringify(c.detail || {})).slice(0, 160)} |
기록 없음
'}진행 중인 확인 작업이 없습니다. "다시 확인" 으로 시작하세요
'; $('#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] || (() => {}))(); })();