Files
eapim-admin/WebContent/jsp/onl/apim/portalnotice/portalNoticeManDetail.jsp
T
Rinjae 6b6e2863cd
eapim-admin CI / build (push) Has been cancelled
공지사항 관리 - 장애 점검 등록 UI 추가
2026-06-16 14:55:39 +09:00

442 lines
15 KiB
Plaintext

<%@ page language="java" contentType="text/html; charset=utf-8"%>
<%@ page import="java.io.*"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring"%>
<%@ include file="/jsp/common/include/localemessage.jsp" %>
<%
response.setHeader("Pragma", "No-cache");
response.setHeader("Cache-Control", "no-cache");
response.setHeader("Expires", "0");
%>
<html>
<head>
<title></title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<jsp:include page="/jsp/common/include/css.jsp"/>
<link rel="stylesheet" type="text/css" href="<c:url value="/css/editor-content.css"/>"/>
<jsp:include page="/jsp/common/include/script.jsp"/>
<script language="javascript" >
var url = '<c:url value="/onl/apim/portalnotice/portalNoticeMan.json" />';
var url_view = '<c:url value="/onl/apim/portalnotice/portalNoticeMan.view" />';
var apiPopupUrl = '<c:url value="/onl/apim/apigroup/apiGroupMan.view" />';
var file_download = '<c:url value="/file/download.do" />';
var isDetail = false;
var fileInfo = null;
// 코드 상수 — PortalNoticeUI 와 동기화
var NOTICE_TYPE_NORMAL = '1';
var NOTICE_TYPE_MAINTENANCE = '2';
var NOTICE_TYPE_INCIDENT = '3';
// 영향 API 목록 (UI 상태)
var affectedApis = [];
function isIncidentType(t) {
return t === NOTICE_TYPE_INCIDENT || t === NOTICE_TYPE_MAINTENANCE;
}
function init() {
var key = "${param.id}";
isDetail = key != "" && key != "null";
if (isDetail) {
$("#title").append(" 수정");
buttonControl(true);
} else {
$("#title").append(" 등록");
$("#btn_modify").html('<i class="material-icons">save</i> <%= localeMessage.getString("button.register") %>');
buttonControl(false);
}
$.ajax({
type : "POST",
url:url,
dataType:"json",
data:{cmd: 'LIST_INIT_COMBO'},
success:function(json){
combo = json;
new makeOptions("CODE","NAME").setObj($("select[name=noticeType]")).setNoValueInclude(false).setData(json.noticeTypeList).rendering();
toggleIncidentFields();
if (key) {
detail(key);
}
},
error:function(e){
alert(e.responseText);
}
});
}
function toggleIncidentFields() {
var t = $('#noticeType').val();
if (isIncidentType(t)) {
$('.incident-row').show();
// INCIDENT 만 상태 영역 표시
if (t === NOTICE_TYPE_INCIDENT) {
$('.incident-state-row').show();
} else {
$('.incident-state-row').hide();
}
} else {
$('.incident-row').hide();
$('.incident-state-row').hide();
}
}
function decodeHTMLEntities(text) {
var textArea = document.createElement('textarea');
textArea.innerHTML = text;
return textArea.value;
}
function formatFileSize(bytes) {
if(bytes == 0) return '0 Bytes';
var k = 1024,
sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'],
i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
function displayAttachFile(file) {
var $attachFiles = $('#attachFiles');
$attachFiles.empty();
if (file) {
var iconPath = '${pageContext.request.contextPath}/images/icon_file.png';
var fileItem = $('<div>').addClass('file-item');
$('<img>').attr('src', iconPath).css({'marginLeft': '15px','marginRight': '5px' ,'height': '1.2em'}).appendTo(fileItem);
var fileNameSpan = $('<span>').addClass('fileName')
.text(file.originalFileName + '.' + file.fileExtension + ' (' + formatFileSize(file.size) + ')')
.css('cursor', 'pointer')
.on('click', function() { if (file.fileId) downloadFile(file.fileId); });
fileNameSpan.appendTo(fileItem);
$('<button>').text('X').addClass('delete-file').css('marginLeft', '15px')
.on('click', function() {
fileInfo = null;
displayAttachFile(null);
$('<input>').attr({type: 'hidden', name: 'fileDeleted', value: 'true'}).appendTo('#ajaxForm');
}).appendTo(fileItem);
fileItem.appendTo($attachFiles);
}
}
function downloadFile(fileId) {
if (fileId) {
window.location.href = file_download + '?fileId=' + fileId + '&fileSn=1';
}
}
function renderAffectedApis() {
var $tb = $('#affectedApiTbody');
$tb.empty();
if (!affectedApis.length) {
$tb.append('<tr><td colspan="3" style="text-align:center;color:#999;">선택된 영향 API가 없습니다.</td></tr>');
return;
}
$.each(affectedApis, function(idx, api) {
var $tr = $('<tr>');
$tr.append('<td style="text-align:center;width:40px;"><input type="checkbox" class="affected-api-check" data-idx="' + idx + '"></td>');
$tr.append('<td>' + (api.apiId || '') + '</td>');
$tr.append('<td>' + (api.apiName || '') + '</td>');
$tb.append($tr);
});
}
function openApiPopup() {
// showModal 헬퍼는 urlAddServiceType + &menuId=getMenuId() + &pop=true 를 자동 부여 →
// AuthorizeInterceptor 가 정상적으로 권한 인식. window.open 직접 호출 시 권한 부족 발생.
// 본 페이지는 summernote 본문 에디터 등 다른 iframe 이 함께 존재할 수 있어
// frames[0] 가정은 위험 → DOM 에서 .ui-dialog 내 iframe 중 src=apiGroupMan 인 것을 식별.
var selectedIds = affectedApis.map(function(a) { return a.apiId; }).join(',');
var url2 = apiPopupUrl + '?cmd=POPUP&selectedApiIds=' + encodeURIComponent(selectedIds);
var args = {};
showModal(url2, args, 1280, 860, function () {
var doc = args.self && args.self.document;
if (!doc) return;
var popupFrame = null;
var iframes = doc.querySelectorAll('iframe');
for (var i = 0; i < iframes.length; i++) {
var src = iframes[i].getAttribute('src') || '';
if (src.indexOf('apiGroupMan') >= 0) {
popupFrame = iframes[i].contentWindow;
break;
}
}
if (!popupFrame) return;
var rv = popupFrame.returnValue;
if (!rv) return;
var rows = Array.isArray(rv) ? rv : [rv];
$.each(rows, function(_, item) {
if (!item || !item.apiId) return;
var exists = affectedApis.some(function(a){ return a.apiId === item.apiId; });
if (!exists) {
affectedApis.push({apiId: item.apiId, apiName: item.apiDesc || item.apiName || ''});
}
});
renderAffectedApis();
});
}
function removeSelectedAffectedApis() {
var idxes = [];
$('.affected-api-check:checked').each(function() {
idxes.push(parseInt($(this).data('idx'), 10));
});
if (!idxes.length) {
alert('삭제할 항목을 선택해주세요.');
return;
}
// 역순 삭제
idxes.sort(function(a,b){return b-a;}).forEach(function(i){ affectedApis.splice(i, 1); });
renderAffectedApis();
}
function detail(key) {
if (!isDetail) return;
$.ajax({
type: "POST", url: url, dataType: "json",
data: {cmd: 'DETAIL', id: key},
success: function (data) {
$("#id").val(key);
$('#noticeType').val(data.noticeType);
$("#noticeSubject").val(data.noticeSubject);
$("#useYn").prop("checked", data.useYn === "Y");
$("#fixYn").prop("checked", data.fixYn === "Y");
var decodedContent = decodeHTMLEntities(data.noticeDetail);
$('#contents').summernote('code', decodedContent);
// 장애/점검 필드
$('#summary').val(data.summary || '');
$('#startedAt').val(data.startedAt || '');
$('#endAt').val(data.endAt || '');
$('#state').val(data.state || 'INVESTIGATING');
$('#previousState').text(data.previousState || '없음');
affectedApis = (data.affectedApis || []).map(function(a){
return {apiId: a.apiId, apiName: a.apiName || ''};
});
renderAffectedApis();
toggleIncidentFields();
if (data.fileInfo) {
fileInfo = {
originalFileName: data.fileInfo.fileDetails[0].originalFileName,
fileExtension: data.fileInfo.fileDetails[0].fileExtension,
fileId: data.fileInfo.fileDetails[0].fileId,
size: data.fileInfo.fileDetails[0].fileSize
};
displayAttachFile(fileInfo);
}
},
error: function (e) { alert(e.responseText); }
});
}
$(document).ready(function () {
var returnUrl = getReturnUrlForReturn();
init();
initSummernote('#contents', { placeholder: '여기에 내용을 입력하세요.', height: 300 });
$('#noticeType').on('change', toggleIncidentFields);
$('#btn_addAffectedApi').click(openApiPopup);
$('#btn_removeAffectedApi').click(removeSelectedAffectedApis);
$('#fileInput').change(function(e) {
var file = e.target.files[0];
if (file) {
fileInfo = {
originalFileName: file.name.split('.').slice(0, -1).join('.'),
fileExtension: file.name.split('.').pop(),
file: file, size: file.size
};
displayAttachFile(fileInfo);
}
this.value = '';
});
$('#addFile').click(function() { $('#fileInput').click(); });
$("#btn_modify").click(function () {
if (!checkRequired("ajaxForm")) return;
var noticeType = $('#noticeType').val();
if (isIncidentType(noticeType)) {
if (!$('#startedAt').val()) {
alert('시작 시각을 입력해 주십시오.');
return;
}
if (noticeType === NOTICE_TYPE_MAINTENANCE && !$('#endAt').val()) {
alert('점검 종료 시각을 입력해 주십시오.');
return;
}
if (affectedApis.length === 0) {
alert('영향 API를 1건 이상 선택해 주십시오.');
return;
}
}
if (confirm("<%= localeMessage.getString("common.checkSave")%>") != true) return;
var formData = new FormData($("#ajaxForm")[0]);
formData.set("useYn", $("#useYn").is(":checked") ? "Y" : "N");
formData.set("fixYn", $("#fixYn").is(":checked") ? "Y" : "N");
formData.set("noticeDetail", $('#contents').summernote('code'));
// 시작/종료 시각이 비어 있으면 전송하지 않음 (서버 파싱 오류 방지)
if (!$('#startedAt').val()) formData.delete('startedAt');
if (!$('#endAt').val()) formData.delete('endAt');
// 영향 API → affectedApis[i].apiId/apiName 으로 전송
$.each(affectedApis, function(i, api) {
formData.set('affectedApis[' + i + '].apiId', api.apiId);
formData.set('affectedApis[' + i + '].apiName', api.apiName || '');
});
formData.append("cmd", isDetail ? "UPDATE" : "INSERT");
if (fileInfo && fileInfo.file) {
formData.set("files", fileInfo.file);
formData.set("fileName", fileInfo.originalFileName + '.' + fileInfo.fileExtension);
}
$.ajax({
type: "POST", url: url, data: formData,
processData: false, contentType: false,
success: function () {
alert("<%= localeMessage.getString("common.saveMsg") %>");
goNav(returnUrl);
},
error: function (e) { alert(e.responseText); }
});
});
$("#btn_delete").click(function () {
if (confirm("<%= localeMessage.getString("common.confirmMsg")%>")) {
var postData = $('#ajaxForm').serializeArray();
postData.push({name: "cmd", value: "DELETE"});
$.ajax({
type: "POST", url: url, data: postData,
success: function () {
alert("<%= localeMessage.getString("common.deleteMsg") %>");
goNav(returnUrl);
},
error: function (e) { alert(e.responseText); }
});
}
});
$("#btn_previous").click(function () { goNav(returnUrl); });
});
</script>
</head>
<body>
<div class="right_box">
<div class="content_top">
<ul class="path">
<li><a href="#">${rmsMenuPath}</a></li>
</ul>
</div>
<div class="content_middle">
<div class="search_wrap">
<button type="button" class="cssbtn" id="btn_modify" level="W" status="DETAIL,NEW"><i class="material-icons">save</i> <%= localeMessage.getString("button.modify") %></button>
<button type="button" class="cssbtn" id="btn_delete" level="W" status="DETAIL"><i class="material-icons">delete</i> <%= localeMessage.getString("button.delete") %></button>
<button type="button" class="cssbtn" id="btn_previous" level="R" status="DETAIL,NEW"><i class="material-icons">arrow_back</i> <%= localeMessage.getString("button.previous") %></button>
</div>
<div class="title" id="title">공지사항</div>
<form id="ajaxForm" enctype="multipart/form-data" accept-charset="UTF-8">
<input type="hidden" name="id" id="id">
<table class="table_row" cellspacing="0">
<colgroup>
<col style="width: 10%"/>
<col style="width: 40%"/>
<col style="width: 10%"/>
<col style="width: 40%"/>
</colgroup>
<tr>
<th>게시유형 <font color="red">*</font></th>
<td colspan="3">
<div class="select-style">
<select name="noticeType" id="noticeType"></select>
</div>
</td>
</tr>
<tr>
<th>제목 <font color="red">*</font></th>
<td colspan="3"><input type="text" id="noticeSubject" name="noticeSubject" style="width:100%" data-required data-warning="제목을 입력하여 주십시오."/></td>
</tr>
<tr>
<th>사용여부</th>
<td>
<input type="checkbox" name="useYn" id="useYn" value="Y" ${portalNoticeUI.useYn eq 'Y' ? 'checked' : ''}> 사용
</td>
<th>상단고정</th>
<td>
<input type="checkbox" name="fixYn" id="fixYn" value="Y" ${portalNoticeUI.fixYn eq 'Y' ? 'checked' : ''}> 고정
</td>
</tr>
<tr>
<th>본문 <font color="red">*</font></th>
<td colspan="3">
<textarea id="contents" name="noticeDetail" style="width:100%;height:300px" data-required data-warning="본문을 입력하여 주십시오."></textarea>
</td>
</tr>
<!-- ───── 장애/점검 전용 영역 ───── -->
<tr class="incident-row" style="display:none;">
<th>시작 <font color="red">*</font></th>
<td>
<input type="datetime-local" id="startedAt" name="startedAt" style="width:90%">
</td>
<th>종료</th>
<td>
<input type="datetime-local" id="endAt" name="endAt" style="width:90%" placeholder="해소시 자동 채움">
</td>
</tr>
<tr class="incident-row incident-state-row" style="display:none;">
<th>상태</th>
<td>
<div class="select-style">
<select id="state" name="state">
<option value="INVESTIGATING">INVESTIGATING - 조사중</option>
<option value="IDENTIFIED">IDENTIFIED - 원인 파악</option>
<option value="MONITORING">MONITORING - 모니터링</option>
<option value="RESOLVED">RESOLVED - 해소</option>
<option value="CANCELED">CANCELED - 취소</option>
</select>
</div>
</td>
<th>이전 상태</th>
<td><span id="previousState">없음</span></td>
</tr>
<tr class="incident-row" style="display:none;">
<th>영향 API 선택</th>
<td colspan="3">
<div style="margin-bottom:5px;">
<button type="button" class="cssbtn smallBtn" id="btn_removeAffectedApi"><i class="material-icons">delete</i> 삭제</button>
<button type="button" class="cssbtn smallBtn" id="btn_addAffectedApi"><i class="material-icons">add</i> 추가</button>
</div>
<table class="table_row" cellspacing="0" style="width:100%;">
<thead>
<tr>
<th style="width:40px;text-align:center;"><input type="checkbox" id="affectedApiAll" onclick="$('.affected-api-check').prop('checked', this.checked);"></th>
<th>API ID</th>
<th>API 명</th>
</tr>
</thead>
<tbody id="affectedApiTbody">
<tr><td colspan="3" style="text-align:center;color:#999;">선택된 영향 API가 없습니다.</td></tr>
</tbody>
</table>
</td>
</tr>
</table>
</form>
</div>
</div>
</body>
</html>