diff --git a/WebContent/jsp/onl/apim/portalnotice/portalNoticeMan.jsp b/WebContent/jsp/onl/apim/portalnotice/portalNoticeMan.jsp
index a53fa07..088273f 100644
--- a/WebContent/jsp/onl/apim/portalnotice/portalNoticeMan.jsp
+++ b/WebContent/jsp/onl/apim/portalnotice/portalNoticeMan.jsp
@@ -18,17 +18,43 @@
var url = '';
var url_view = '';
var combo;
-
+
+ // 코드 상수 — PortalNoticeUI 와 동기화
+ var NOTICE_TYPE_INCIDENT = '3';
+
+ // IncidentState 라벨 — 상세 화면과 동일하게 유지
+ var STATE_LABELS = {
+ 'INVESTIGATING': '조사중',
+ 'IDENTIFIED': '원인 파악',
+ 'MONITORING': '모니터링',
+ 'RESOLVED': '해소',
+ 'CANCELED': '취소'
+ };
+
+ function formatIncidentState(cellvalue, options, rowObject) {
+ if (!cellvalue) return '';
+ var label = STATE_LABELS[cellvalue] || cellvalue;
+ var closed = (cellvalue === 'RESOLVED' || cellvalue === 'CANCELED');
+ return closed
+ ? '' + label + ''
+ : '' + label + '';
+ }
+
function formatNoticeType(cellvalue, options, rowObject) {
- if (!combo || !combo.noticeTypeList) {
- return cellvalue;
- }
- for (var i = 0; i < combo.noticeTypeList.length; i++) {
- if (combo.noticeTypeList[i].CODE == cellvalue) {
- return combo.noticeTypeList[i].NAME;
+ var name = cellvalue;
+ if (combo && combo.noticeTypeList) {
+ for (var i = 0; i < combo.noticeTypeList.length; i++) {
+ if (combo.noticeTypeList[i].CODE == cellvalue) {
+ name = combo.noticeTypeList[i].NAME;
+ break;
+ }
}
}
- return cellvalue;
+ // 장애인데 종료 시각이 없으면 아직 진행 중이다
+ if (cellvalue == NOTICE_TYPE_INCIDENT && rowObject && !rowObject.endAt) {
+ return '' + name + '-진행중';
+ }
+ return name;
}
function formatuseYn(cellvalue, options, rowObject) {
@@ -115,14 +141,15 @@
searchNoticeSubject: $('input[name=searchNoticeSubject]').val(),
searchNoticeDetail: $('input[name=searchNoticeDetail]').val()
},
- colNames:['id', 'No.', '게시유형', '제목', '고정여부', '사용', '마지막 수정일', '등록일', '등록자', '조회수'],
+ colNames:['id', 'No.', '게시유형', '제목', '고정여부', '사용', '장애상태', '마지막 수정일', '등록일', '등록자', '조회수'],
colModel:[
{ name : 'id' , align:'center', key:true, hidden:true},
{ name : 'rowNum' , align:'center', width:60 },
- { name : 'noticeType' , align:'center', width:60, formatter: formatNoticeType },
+ { name : 'noticeType' , align:'center', width:100, formatter: formatNoticeType },
{ name : 'noticeSubject' , align:'left' , width:300, formatter: formatFile },
{ name : 'fixYn' , align:'center', width:60, formatter: formatFixYn },
{ name : 'useYn' , align:'center', width:60, formatter: formatuseYn },
+ { name : 'state' , align:'center', width:90, sortable:false, formatter: formatIncidentState },
{ name : 'lastModifiedDate', align:'center', width:120 , formatter: timeStampFormat},
{ name : 'createdDate' , align:'center', width:120, formatter: timeStampFormat },
{ name : 'inquirerName' , align:'center', width:100 },
diff --git a/WebContent/jsp/onl/apim/portalnotice/portalNoticeManDetail.jsp b/WebContent/jsp/onl/apim/portalnotice/portalNoticeManDetail.jsp
index 34ca6be..6bee1a8 100644
--- a/WebContent/jsp/onl/apim/portalnotice/portalNoticeManDetail.jsp
+++ b/WebContent/jsp/onl/apim/portalnotice/portalNoticeManDetail.jsp
@@ -32,10 +32,35 @@
// 영향 API 목록 (UI 상태)
var affectedApis = [];
+ // 장애 타임라인 (UI 상태)
+ var currentIncidentId = null;
+ var currentIncidentState = null;
+
+ // 종결 상태 — 재전이 불가 (재발은 새 장애로 등록)
+ var TERMINAL_STATES = ['RESOLVED', 'CANCELED'];
+
+ // IncidentState 라벨 — 상태 콤보 문구와 동일하게 유지
+ var STATE_LABELS = {
+ 'INVESTIGATING': '조사중',
+ 'IDENTIFIED': '원인 파악',
+ 'MONITORING': '모니터링',
+ 'RESOLVED': '해소',
+ 'CANCELED': '취소'
+ };
+
function isIncidentType(t) {
return t === NOTICE_TYPE_INCIDENT || t === NOTICE_TYPE_MAINTENANCE;
}
+ function stateLabel(code) {
+ if (!code) return '';
+ return (STATE_LABELS[code] || code) + ' (' + code + ')';
+ }
+
+ function escapeHtml(text) {
+ return $('
').text(text == null ? '' : text).html();
+ }
+
function init() {
var key = "${param.id}";
isDetail = key != "" && key != "null";
@@ -76,12 +101,28 @@
// INCIDENT 만 상태 영역 표시
if (t === NOTICE_TYPE_INCIDENT) {
$('.incident-state-row').show();
+ $('.incident-endat-hint').show();
} else {
$('.incident-state-row').hide();
+ $('.incident-endat-hint').hide();
}
+ // 장애는 종결(해소·취소) 후에만 종료 시각을 손볼 수 있다. 진행 중에는 상태를 따른다.
+ var closed = $.inArray(currentIncidentState, TERMINAL_STATES) >= 0;
+ $('#endAt').prop('readonly', t === NOTICE_TYPE_INCIDENT && !closed);
+ $('.incident-endat-hint').text(closed
+ ? '※ 종결된 장애입니다. 실제 복구 시각으로 수정할 수 있습니다.'
+ : '※ 타임라인에서 해소·취소로 전이하면 자동 입력됩니다.');
} else {
$('.incident-row').hide();
$('.incident-state-row').hide();
+ $('.incident-endat-hint').hide();
+ $('#endAt').prop('readonly', false);
+ }
+ // 타임라인은 등록된 장애(수정 모드)에서만 다룬다
+ if (t === NOTICE_TYPE_INCIDENT && isDetail && currentIncidentId) {
+ $('#timelineSection').show();
+ } else {
+ $('#timelineSection').hide();
}
}
@@ -192,6 +233,150 @@
renderAffectedApis();
}
+ // ───────────────────── 장애 처리 타임라인 ─────────────────────
+
+ /** 현재 상태 기준으로 전이 가능한 선택지만 남긴다. 실제 차단은 서버가 한다. */
+ function applyStateChangeGuard() {
+ var terminal = $.inArray(currentIncidentState, TERMINAL_STATES) >= 0;
+
+ $('#timelineStateChange').prop('disabled', terminal);
+ if (terminal) {
+ $('#timelineStateChange').prop('checked', false);
+ $('#timelineNextState').prop('disabled', true).val('');
+ }
+ $('#timelineClosedHint').toggle(terminal);
+ $('#timelineResolveHint').toggle(!terminal);
+
+ // 현재와 동일한 상태로는 전이할 수 없다
+ $('#timelineNextState option').each(function() {
+ var v = $(this).val();
+ $(this).prop('disabled', v !== '' && v === currentIncidentState);
+ });
+ if ($('#timelineNextState').val() === currentIncidentState) {
+ $('#timelineNextState').val('');
+ }
+ }
+
+ function loadTimeline() {
+ if (!currentIncidentId) {
+ renderTimeline([]);
+ return;
+ }
+ $.ajax({
+ type: "POST", url: url, dataType: "json",
+ data: {cmd: 'TIMELINE_LIST', incidentId: currentIncidentId},
+ success: function (rows) { renderTimeline(rows || []); },
+ error: function (e) { alert(e.responseText); }
+ });
+ }
+
+ function renderTimeline(rows) {
+ var $tb = $('#timelineTbody');
+ $tb.empty();
+ if (!rows.length) {
+ $tb.append('
| 등록된 타임라인이 없습니다. |
');
+ return;
+ }
+ $.each(rows, function(_, row) {
+ var author = row.authorType === 'SYSTEM' ? 'System' : 'Admin';
+ var visible = row.visibleYn === 'Y';
+ var $tr = $('
');
+ $tr.append('| ' + escapeHtml(row.eventAt) + ' | ');
+ $tr.append('' + author + ' | ');
+ $tr.append('' + (row.stateAfter ? escapeHtml(stateLabel(row.stateAfter)) : '-') + ' | ');
+ $tr.append('' + escapeHtml(row.body).replace(/\n/g, ' ') + ' | ');
+
+ // 현재 상태(라벨) + 눌렀을 때 바뀔 상태(버튼) 를 나눠 표기 — 버튼 하나면 의미가 모호하다
+ var $visibleTd = $('');
+ $('')
+ .text(visible ? '공개중' : '비공개')
+ .css({color: visible ? '#1B8C4A' : '#999', 'margin-right': '5px'})
+ .appendTo($visibleTd);
+ $(' |
-
- | 상태 |
-
-
-
+
+ ※ 타임라인에서 해소·취소로 전이하면 자동 입력됩니다.
|
+
+
+
+ | 상태 |
+ |
이전 상태 |
없음 |
@@ -435,6 +637,61 @@
+
+
+
+
타임라인
+
+
+
+
+
+
+
+
+
+
+
+ | 시간 |
+ 작성자 |
+ 상태 |
+ 메세지 |
+ 개발자포탈 공개 |
+ 삭제 |
+
+
+
+ | 등록된 타임라인이 없습니다. |
+
+
+
+
+
+
+
+
+
※ 해소(RESOLVED)·취소(CANCELED)로 전이하면 종료 시각이 자동 입력됩니다.
+
+ ※ 종결된 장애입니다. 상태 변경 불가 — 재발 시 새 장애로 등록하십시오.
+
+
+
+
+
+ send 게시
+
+
+