145 lines
5.4 KiB
JavaScript
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,"&").replace(/</g,"<").replace(/>/g,">");
|
|
}
|