guardia-itsm/static/customer.js
2026-05-30 23:02:43 +09:00

145 lines
5.4 KiB
JavaScript

/* GUARDiA 고객 포털 JS */
let currentSrId = null;
let selectedStar = 0;
let pollTimer = null;
const STATUS_LABEL = {
RECEIVED: "접수됨", PARSED: "분석 중", PENDING_APPROVAL: "승인 대기",
APPROVED: "승인됨", IN_PROGRESS: "처리 중",
PENDING_PM_VALIDATION: "PM 검증", COMPLETED: "처리 완료",
FAILED_ROLLBACK: "처리 실패", REJECTED: "반려",
};
const ACTION_LABEL = {
CMDB_CHECK: "🔍 자산 확인", SSH_CONNECT: "🔗 서버 접속",
SSH_EXEC: "⚡ 명령 실행", SOURCE_MOD: "📦 파일 배포",
HEALTH_CHECK: "💓 헬스 체크", RESULT: "📋 결과 기록",
COMPLETE: "✅ 완료 처리",
};
/* ── 폼 제출 ── */
document.getElementById("sr-form").addEventListener("submit", async e => {
e.preventDefault();
const btn = document.getElementById("submit-btn");
const label = document.getElementById("submit-label");
const spinner = document.getElementById("submit-spinner");
btn.disabled = true; label.textContent = "등록 중…"; spinner.classList.remove("hidden");
const fd = new FormData(e.target);
const payload = Object.fromEntries(fd.entries());
if (!payload.description) delete payload.description;
if (!payload.target_server) delete payload.target_server;
try {
const res = await fetch("/api/tasks", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
});
if (!res.ok) throw new Error((await res.json()).detail || "오류 발생");
const sr = await res.json();
currentSrId = sr.sr_id;
document.getElementById("result-sr-id").textContent = sr.sr_id;
document.getElementById("section-form").classList.add("hidden");
document.getElementById("section-result").classList.remove("hidden");
await refreshStatus();
pollTimer = setInterval(refreshStatus, 5000); // 5초마다 갱신
} catch (err) {
alert("요청 등록 실패: " + err.message);
btn.disabled = false; label.textContent = "요청 등록하기"; spinner.classList.add("hidden");
}
});
/* ── 상태 폴링 ── */
async function refreshStatus() {
if (!currentSrId) return;
const [srRes, workRes, ratingRes] = await Promise.all([
fetch(`/api/tasks/${currentSrId}`).then(r => r.ok ? r.json() : null),
fetch(`/api/work/${currentSrId}`).then(r => r.ok ? r.json() : []),
fetch(`/api/rating/${currentSrId}`).then(r => r.ok ? r.json() : null).catch(() => null),
]);
if (!srRes) return;
/* 상태 배지 */
document.getElementById("status-badge-row").innerHTML =
`<span class="s-badge s-${srRes.status}">${STATUS_LABEL[srRes.status] || srRes.status}</span>`;
/* 작업 타임라인 */
const tl = document.getElementById("work-timeline");
if (workRes.length) {
tl.innerHTML = workRes.map(w => `
<div class="tl-item ${w.is_success ? "ok" : "err"}">
<div class="tl-action">${ACTION_LABEL[w.action_type] || w.action_type}</div>
<div class="tl-detail">${esc(w.content || "")}${w.result ? " → " + esc(w.result.slice(0, 80)) : ""}</div>
</div>`).join("");
} else {
tl.innerHTML = `<div class="tl-loading">처리 대기 중입니다…</div>`;
}
/* 완료 시: 별점 섹션 표시 */
if (srRes.status === "COMPLETED") {
clearInterval(pollTimer);
if (!ratingRes) {
document.getElementById("rating-section").classList.remove("hidden");
} else {
showRatingDone(ratingRes.stars);
}
}
}
/* ── 별점 선택 ── */
function setStar(n) {
selectedStar = n;
document.querySelectorAll(".star-btn").forEach((btn, i) => {
btn.classList.toggle("active", i < n);
});
document.getElementById("btn-rate").disabled = false;
}
/* ── 별점 제출 ── */
async function submitRating() {
if (!selectedStar || !currentSrId) return;
const comment = document.getElementById("rating-comment").value.trim();
const customer = document.getElementById("f-name")?.value ||
document.getElementById("result-sr-id")?.textContent || "고객";
const res = await fetch(`/api/rating/${currentSrId}`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ customer, stars: selectedStar, comment: comment || null }),
});
if (res.ok) {
showRatingDone(selectedStar);
}
}
function showRatingDone(stars) {
document.getElementById("rating-section").classList.remove("hidden");
document.querySelector(".stars-row").classList.add("hidden");
document.querySelector(".rating-comment-input").classList.add("hidden");
document.getElementById("btn-rate").classList.add("hidden");
const done = document.getElementById("rating-done");
done.classList.remove("hidden");
done.textContent = `${"★".repeat(stars)}${"☆".repeat(5 - stars)} — 감사합니다! 소중한 의견이 반영됩니다 🙏`;
}
/* ── 초기화 ── */
function resetForm() {
clearInterval(pollTimer);
currentSrId = null; selectedStar = 0;
document.getElementById("sr-form").reset();
document.getElementById("section-form").classList.remove("hidden");
document.getElementById("section-result").classList.add("hidden");
document.getElementById("rating-section").classList.add("hidden");
document.getElementById("rating-done").classList.add("hidden");
document.getElementById("work-timeline").innerHTML = "<div class='tl-loading'>처리 정보를 불러오는 중…</div>";
}
function esc(s) {
return String(s ?? "").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
}