zioinfo-mail/itsm/static/change-password.js
DESKTOP-TKLFCPR\ython e228faabf5 feat(itsm): G-1~G-12 확장 기능 + 하네스/봇/설치스크립트 구현
G-1: 메신저 Webhook Relay + _send_to_room 실제 httpx 호출 구현
G-2: POST /api/tasks/bulk SR 대량작업 엔드포인트 (최대 100건)
G-3: 라이선스 만료 알림 스케줄러 (매일 09:00 KST)
G-4: 체험판 upgrade_banner 필드 + license.py 배너 로직
G-5: core/auto_rca.py + incidents/problem auto-rca 엔드포인트
G-6: core/deploy_impact.py + vibe impact-analysis 엔드포인트
G-7: core/ticket_classifier.py + SR 생성 시 AI 분류 + ai-suggestion API
G-8: VulnPatchRecord 모델 + vuln_scan 패치추적 4개 엔드포인트
G-9: core/jira_sync.py + gateway Jira/Confluence 연동 엔드포인트
G-10: core/push_notify.py + routers/push.py + PushSubscription 모델
G-11: approvals 다중승인 (위임/서명/기한초과/마감연장)
G-12: alembic.ini + migrations/ + cicd/migrate_to_postgres.sh

하네스: guardia-orchestrator 확장기능 Phase 반영
봇명령어: /sr /status /license /bulk 슬래시 명령어 추가
설치스크립트: setup/ (Ubuntu, CentOS, RHEL, Windows) --test 옵션 포함

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 18:18:52 +09:00

147 lines
5.2 KiB
JavaScript

/* ─── 인증 확인 ──────────────────────────────────── */
const token = localStorage.getItem("guardia_token");
const userInfo = JSON.parse(localStorage.getItem("guardia_userinfo") || "{}");
if (!token) {
window.location.replace("/login");
}
/* ─── UI 초기화 ──────────────────────────────────── */
document.getElementById("user-subtitle").textContent =
userInfo.display_name ? `${userInfo.display_name}` : "GUARDiA ITSM";
// must_change_pw가 false면 배너 숨기기
if (!userInfo.must_change_pw) {
document.getElementById("must-change-banner").classList.add("hidden");
document.getElementById("skip-link").style.display = "none";
}
// 건너뛰기 링크 (must_change_pw가 false인 경우에만 허용)
document.getElementById("skip-link").addEventListener("click", e => {
e.preventDefault();
if (!userInfo.must_change_pw) {
window.location.replace("/");
}
});
/* ─── 비밀번호 보기/숨기기 ─────────────────────── */
function togglePw(inputId, btn) {
const input = document.getElementById(inputId);
if (input.type === "password") {
input.type = "text";
btn.textContent = "🙈";
} else {
input.type = "password";
btn.textContent = "👁";
}
}
/* ─── 강도 체크 ──────────────────────────────────── */
function checkStrength(pw) {
const bar = document.getElementById("strength-bar");
const label = document.getElementById("strength-label");
if (!pw) { bar.style.width = "0"; label.textContent = ""; return; }
let score = 0;
if (pw.length >= 4) score++;
if (pw.length >= 8) score++;
if (/[A-Z]/.test(pw) || /[a-z]/.test(pw)) score++;
if (/[0-9]/.test(pw)) score++;
if (/[^A-Za-z0-9]/.test(pw)) score++;
const levels = [
{ w: "20%", bg: "#f85149", txt: "매우 약함" },
{ w: "40%", bg: "#f85149", txt: "약함" },
{ w: "60%", bg: "#e3b341", txt: "보통" },
{ w: "80%", bg: "#2bac76", txt: "강함" },
{ w: "100%", bg: "#1d9bd1", txt: "매우 강함" },
];
const lv = levels[Math.min(score - 1, 4)] || levels[0];
bar.style.width = lv.w;
bar.style.background = lv.bg;
label.textContent = lv.txt;
label.style.color = lv.bg;
}
/* ─── 일치 확인 ──────────────────────────────────── */
function checkMatch() {
const newPw = document.getElementById("new-pw").value;
const confirmPw = document.getElementById("confirm-pw").value;
const msgEl = document.getElementById("match-msg");
if (!confirmPw) { msgEl.textContent = ""; return; }
if (newPw === confirmPw) {
msgEl.textContent = "✓ 비밀번호가 일치합니다";
msgEl.className = "pw-match-msg ok";
} else {
msgEl.textContent = "✗ 비밀번호가 일치하지 않습니다";
msgEl.className = "pw-match-msg fail";
}
}
/* ─── 폼 제출 ────────────────────────────────────── */
document.getElementById("cp-form").addEventListener("submit", async e => {
e.preventDefault();
const curPw = document.getElementById("cur-pw").value;
const newPw = document.getElementById("new-pw").value;
const confirmPw = document.getElementById("confirm-pw").value;
const errEl = document.getElementById("cp-error");
errEl.style.display = "none";
if (newPw !== confirmPw) {
showError("새 비밀번호가 일치하지 않습니다");
return;
}
if (newPw.length < 4) {
showError("새 비밀번호는 4자 이상이어야 합니다");
return;
}
setLoading(true);
try {
const res = await fetch("/api/auth/change-password", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${token}`,
},
body: JSON.stringify({ current_password: curPw, new_password: newPw }),
});
const data = await res.json();
if (!res.ok) {
showError(data.detail || "변경에 실패했습니다");
setLoading(false);
return;
}
/* 성공 — userinfo 업데이트 후 메인으로 */
const updated = { ...userInfo, must_change_pw: false };
localStorage.setItem("guardia_userinfo", JSON.stringify(updated));
/* 잠깐 성공 메시지 표시 */
const btn = document.getElementById("cp-btn");
btn.style.background = "#2bac76";
document.getElementById("cp-btn-text").textContent = "✓ 변경 완료! 이동 중…";
setTimeout(() => window.location.replace("/"), 1200);
} catch {
showError("서버에 연결할 수 없습니다");
setLoading(false);
}
});
function setLoading(on) {
document.getElementById("cp-btn").disabled = on;
document.getElementById("cp-btn-text").textContent = on ? "변경 중…" : "비밀번호 변경";
document.getElementById("cp-spinner").style.display = on ? "inline-block" : "none";
}
function showError(msg) {
const errEl = document.getElementById("cp-error");
errEl.textContent = msg;
errEl.style.display = "block";
}