Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a0743465ab | |||
| d3e05256b7 | |||
| 4d5572f880 | |||
| e21014f992 |
@@ -0,0 +1,140 @@
|
|||||||
|
name: eapim-admin CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- develop
|
||||||
|
- stage
|
||||||
|
- feats/ci-test
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: [self-hosted, linux, eapim-admin]
|
||||||
|
env:
|
||||||
|
JAVA_HOME: /apps/opts/jdk8
|
||||||
|
JAVA_HOME_TOMCAT: /apps/opts/jdk17
|
||||||
|
CATALINA_BASE: /prod/eapim/adminportal
|
||||||
|
CATALINA_HOME: /prod/eapim/apache-tomcat-9.0.116
|
||||||
|
CATALINA_PID: /prod/eapim/adminportal/temp/catalina.pid
|
||||||
|
DEPLOY_HTTP_PORT: "30120"
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
working-directory: eapim-admin
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout eapim-admin (trigger SHA)
|
||||||
|
uses: http://172.30.1.50:3000/actions/checkout@v4
|
||||||
|
with:
|
||||||
|
path: eapim-admin
|
||||||
|
|
||||||
|
- name: Checkout sibling modules (master)
|
||||||
|
working-directory: ${{ github.workspace }}
|
||||||
|
run: |
|
||||||
|
set -e
|
||||||
|
mkdir -p eapim-online
|
||||||
|
|
||||||
|
# eapim-online/* — 5 modules referenced from settings.gradle
|
||||||
|
for REPO in elink-online-core elink-online-core-jpa elink-online-transformer elink-online-common elink-online-emsclient; do
|
||||||
|
rm -rf "eapim-online/$REPO"
|
||||||
|
git clone --depth=1 --branch master \
|
||||||
|
http://oauth2:${{ secrets.CI_TOKEN }}@172.30.1.50:3000/djb-eapim/$REPO.git \
|
||||||
|
"eapim-online/$REPO"
|
||||||
|
done
|
||||||
|
|
||||||
|
# standalone siblings
|
||||||
|
for REPO in elink-portal-common eapim-admin-djb; do
|
||||||
|
rm -rf "$REPO"
|
||||||
|
git clone --depth=1 --branch master \
|
||||||
|
http://oauth2:${{ secrets.CI_TOKEN }}@172.30.1.50:3000/djb-eapim/$REPO.git \
|
||||||
|
"$REPO"
|
||||||
|
done
|
||||||
|
|
||||||
|
- name: Verify toolchain
|
||||||
|
run: |
|
||||||
|
java -version
|
||||||
|
gradle --version
|
||||||
|
|
||||||
|
- name: Gradle build (no tests)
|
||||||
|
run: gradle clean build -x test --no-daemon
|
||||||
|
|
||||||
|
- name: Upload WAR artifact
|
||||||
|
uses: http://172.30.1.50:3000/actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: eapim-admin-war-${{ github.sha }}
|
||||||
|
path: eapim-admin/build/libs/eapim-admin.war
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
- name: Sync systemd user unit
|
||||||
|
working-directory: ${{ github.workspace }}
|
||||||
|
run: |
|
||||||
|
install -D -m 644 eapim-admin/deploy/systemd/eapim-admin.service \
|
||||||
|
"$HOME/.config/systemd/user/eapim-admin.service"
|
||||||
|
systemctl --user daemon-reload
|
||||||
|
systemctl --user enable eapim-admin 2>/dev/null || true
|
||||||
|
|
||||||
|
- name: Stop tomcat (pre-clean)
|
||||||
|
working-directory: ${{ github.workspace }}
|
||||||
|
run: |
|
||||||
|
# 1) systemd 관리 인스턴스 정지 (이번 워크플로가 띄운 것)
|
||||||
|
systemctl --user stop eapim-admin 2>/dev/null || true
|
||||||
|
|
||||||
|
# 2) systemd 외부 detached 인스턴스 정리
|
||||||
|
if [ -f "$CATALINA_PID" ] && kill -0 "$(cat $CATALINA_PID)" 2>/dev/null; then
|
||||||
|
JAVA_HOME=$JAVA_HOME_TOMCAT "$CATALINA_HOME/bin/shutdown.sh" 30 -force || true
|
||||||
|
for i in $(seq 1 30); do
|
||||||
|
[ -f "$CATALINA_PID" ] && kill -0 "$(cat $CATALINA_PID)" 2>/dev/null || break
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
rm -f "$CATALINA_PID"
|
||||||
|
|
||||||
|
- name: Deploy war as /monitoring
|
||||||
|
working-directory: ${{ github.workspace }}
|
||||||
|
run: |
|
||||||
|
DEPLOY_DIR="$CATALINA_BASE/webapps"
|
||||||
|
rm -rf "$DEPLOY_DIR/monitoring" "$DEPLOY_DIR/monitoring.war"
|
||||||
|
cp eapim-admin/build/libs/eapim-admin.war "$DEPLOY_DIR/monitoring.war"
|
||||||
|
ls -la "$DEPLOY_DIR"
|
||||||
|
|
||||||
|
- name: Start tomcat and readiness probe (timeout 120s)
|
||||||
|
working-directory: ${{ github.workspace }}
|
||||||
|
run: |
|
||||||
|
LOG="$CATALINA_BASE/logs/catalina.$(date +%Y-%m-%d).log"
|
||||||
|
BEFORE=0
|
||||||
|
[ -f "$LOG" ] && BEFORE=$(stat -c %s "$LOG")
|
||||||
|
|
||||||
|
# systemd 가 Tomcat 을 새 cgroup 으로 띄움 — runner step 종료와 무관하게 살아남음
|
||||||
|
systemctl --user start eapim-admin
|
||||||
|
|
||||||
|
DEADLINE=$(($(date +%s) + 120))
|
||||||
|
STATUS=000
|
||||||
|
while [ $(date +%s) -lt $DEADLINE ]; do
|
||||||
|
STATUS=$(curl -sS -o /dev/null -w '%{http_code}' --max-time 3 \
|
||||||
|
"http://localhost:$DEPLOY_HTTP_PORT/monitoring/login.do" 2>/dev/null || echo "000")
|
||||||
|
case "$STATUS" in
|
||||||
|
200|302|401)
|
||||||
|
echo "Readiness OK (/monitoring/login.do HTTP $STATUS)"
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [ -f "$LOG" ] && tail -c +$((BEFORE+1)) "$LOG" | grep -qE "Application listener .* Exception|SEVERE.*startup.Catalina"; then
|
||||||
|
echo "::error::Startup error detected in log"
|
||||||
|
tail -c +$((BEFORE+1)) "$LOG" | tail -80
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
|
||||||
|
case "$STATUS" in
|
||||||
|
200|302|401) ;;
|
||||||
|
*)
|
||||||
|
echo "::error::Readiness probe failed within 120s (last HTTP status: $STATUS)"
|
||||||
|
[ -f "$LOG" ] && tail -c +$((BEFORE+1)) "$LOG" | tail -100
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
echo "--- key boot log lines ---"
|
||||||
|
tail -c +$((BEFORE+1)) "$LOG" | grep -E "Server startup|Deployment of web" | head -10 || true
|
||||||
@@ -115,7 +115,7 @@
|
|||||||
</bean>
|
</bean>
|
||||||
|
|
||||||
<bean id="ioAcceptor" class="com.eactive.eai.rms.onl.server.UdpServer" init-method="init" destroy-method="destory">
|
<bean id="ioAcceptor" class="com.eactive.eai.rms.onl.server.UdpServer" init-method="init" destroy-method="destory">
|
||||||
<property name="port" value="10800" />
|
<property name="port" value="39126" />
|
||||||
<property name="handler" ref="handler"/>
|
<property name="handler" ref="handler"/>
|
||||||
</bean>
|
</bean>
|
||||||
|
|
||||||
|
|||||||
@@ -19,23 +19,10 @@
|
|||||||
<script language="javascript" >
|
<script language="javascript" >
|
||||||
var url = '<c:url value="/onl/apim/portalnotice/portalNoticeMan.json" />';
|
var url = '<c:url value="/onl/apim/portalnotice/portalNoticeMan.json" />';
|
||||||
var url_view = '<c:url value="/onl/apim/portalnotice/portalNoticeMan.view" />';
|
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 file_download = '<c:url value="/file/download.do" />';
|
||||||
var isDetail = false;
|
var isDetail = false;
|
||||||
var fileInfo = null;
|
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() {
|
function init() {
|
||||||
var key = "${param.id}";
|
var key = "${param.id}";
|
||||||
isDetail = key != "" && key != "null";
|
isDetail = key != "" && key != "null";
|
||||||
@@ -49,6 +36,7 @@
|
|||||||
buttonControl(false);
|
buttonControl(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 초기화 로직
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type : "POST",
|
type : "POST",
|
||||||
url:url,
|
url:url,
|
||||||
@@ -57,7 +45,6 @@
|
|||||||
success:function(json){
|
success:function(json){
|
||||||
combo = json;
|
combo = json;
|
||||||
new makeOptions("CODE","NAME").setObj($("select[name=noticeType]")).setNoValueInclude(false).setData(json.noticeTypeList).rendering();
|
new makeOptions("CODE","NAME").setObj($("select[name=noticeType]")).setNoValueInclude(false).setData(json.noticeTypeList).rendering();
|
||||||
toggleIncidentFields();
|
|
||||||
|
|
||||||
if (key) {
|
if (key) {
|
||||||
detail(key);
|
detail(key);
|
||||||
@@ -69,22 +56,6 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
function decodeHTMLEntities(text) {
|
||||||
var textArea = document.createElement('textarea');
|
var textArea = document.createElement('textarea');
|
||||||
textArea.innerHTML = text;
|
textArea.innerHTML = text;
|
||||||
@@ -102,100 +73,57 @@
|
|||||||
function displayAttachFile(file) {
|
function displayAttachFile(file) {
|
||||||
var $attachFiles = $('#attachFiles');
|
var $attachFiles = $('#attachFiles');
|
||||||
$attachFiles.empty();
|
$attachFiles.empty();
|
||||||
|
|
||||||
if (file) {
|
if (file) {
|
||||||
var iconPath = '${pageContext.request.contextPath}/images/icon_file.png';
|
var iconPath = '${pageContext.request.contextPath}/images/icon_file.png';
|
||||||
var fileItem = $('<div>').addClass('file-item');
|
var fileItem = $('<div>').addClass('file-item');
|
||||||
|
|
||||||
$('<img>').attr('src', iconPath).css({'marginLeft': '15px','marginRight': '5px' ,'height': '1.2em'}).appendTo(fileItem);
|
$('<img>').attr('src', iconPath).css({'marginLeft': '15px','marginRight': '5px' ,'height': '1.2em'}).appendTo(fileItem);
|
||||||
var fileNameSpan = $('<span>').addClass('fileName')
|
|
||||||
|
var fileNameSpan = $('<span>')
|
||||||
|
.addClass('fileName')
|
||||||
.text(file.originalFileName + '.' + file.fileExtension + ' (' + formatFileSize(file.size) + ')')
|
.text(file.originalFileName + '.' + file.fileExtension + ' (' + formatFileSize(file.size) + ')')
|
||||||
.css('cursor', 'pointer')
|
.css('cursor', 'pointer')
|
||||||
.on('click', function() { if (file.fileId) downloadFile(file.fileId); });
|
.on('click', function() {
|
||||||
|
if (file.fileId) {
|
||||||
|
downloadFile(file.fileId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
fileNameSpan.appendTo(fileItem);
|
fileNameSpan.appendTo(fileItem);
|
||||||
$('<button>').text('X').addClass('delete-file').css('marginLeft', '15px')
|
$('<button>').text('X').addClass('delete-file').css('marginLeft', '15px')
|
||||||
.on('click', function() {
|
.on('click', function() {
|
||||||
fileInfo = null;
|
fileInfo = null;
|
||||||
displayAttachFile(null);
|
displayAttachFile(null);
|
||||||
$('<input>').attr({type: 'hidden', name: 'fileDeleted', value: 'true'}).appendTo('#ajaxForm');
|
// 폼에 삭제 플래그 추가
|
||||||
}).appendTo(fileItem);
|
$('<input>').attr({
|
||||||
|
type: 'hidden',
|
||||||
|
name: 'fileDeleted',
|
||||||
|
value: 'true'
|
||||||
|
}).appendTo('#ajaxForm');
|
||||||
|
})
|
||||||
|
.appendTo(fileItem);
|
||||||
|
|
||||||
fileItem.appendTo($attachFiles);
|
fileItem.appendTo($attachFiles);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function downloadFile(fileId) {
|
function downloadFile(fileId) {
|
||||||
if (fileId) {
|
if (fileId) {
|
||||||
window.location.href = file_download + '?fileId=' + fileId + '&fileSn=1';
|
var fileSn = 1; // 단일 파일이므로 시리얼 넘버를 1로 고정
|
||||||
|
window.location.href = file_download + '?fileId=' + fileId + '&fileSn=' + fileSn;
|
||||||
|
} else {
|
||||||
|
console.log("File not yet uploaded or doesn't have an ID");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
function detail(key) {
|
||||||
if (!isDetail) return;
|
if (!isDetail) return;
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: "POST", url: url, dataType: "json",
|
type: "POST",
|
||||||
|
url: url,
|
||||||
|
dataType: "json",
|
||||||
data: {cmd: 'DETAIL', id: key},
|
data: {cmd: 'DETAIL', id: key},
|
||||||
success: function (data) {
|
success: function (data) {
|
||||||
$("#id").val(key);
|
$("#id").val(key);
|
||||||
@@ -207,18 +135,7 @@
|
|||||||
var decodedContent = decodeHTMLEntities(data.noticeDetail);
|
var decodedContent = decodeHTMLEntities(data.noticeDetail);
|
||||||
$('#contents').summernote('code', decodedContent);
|
$('#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) {
|
if (data.fileInfo) {
|
||||||
fileInfo = {
|
fileInfo = {
|
||||||
originalFileName: data.fileInfo.fileDetails[0].originalFileName,
|
originalFileName: data.fileInfo.fileDetails[0].originalFileName,
|
||||||
@@ -229,7 +146,9 @@
|
|||||||
displayAttachFile(fileInfo);
|
displayAttachFile(fileInfo);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
error: function (e) { alert(e.responseText); }
|
error: function (e) {
|
||||||
|
alert(e.responseText);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,12 +157,13 @@
|
|||||||
|
|
||||||
init();
|
init();
|
||||||
|
|
||||||
initSummernote('#contents', { placeholder: '여기에 내용을 입력하세요.', height: 300 });
|
|
||||||
|
|
||||||
$('#noticeType').on('change', toggleIncidentFields);
|
// Summernote 에디터 초기화 (이미지 붙여넣기 data-uri 방식, 리사이징 지원)
|
||||||
|
initSummernote('#contents', {
|
||||||
|
placeholder: '여기에 내용을 입력하세요.',
|
||||||
|
height: 300
|
||||||
|
});
|
||||||
|
|
||||||
$('#btn_addAffectedApi').click(openApiPopup);
|
|
||||||
$('#btn_removeAffectedApi').click(removeSelectedAffectedApis);
|
|
||||||
|
|
||||||
$('#fileInput').change(function(e) {
|
$('#fileInput').change(function(e) {
|
||||||
var file = e.target.files[0];
|
var file = e.target.files[0];
|
||||||
@@ -251,66 +171,52 @@
|
|||||||
fileInfo = {
|
fileInfo = {
|
||||||
originalFileName: file.name.split('.').slice(0, -1).join('.'),
|
originalFileName: file.name.split('.').slice(0, -1).join('.'),
|
||||||
fileExtension: file.name.split('.').pop(),
|
fileExtension: file.name.split('.').pop(),
|
||||||
file: file, size: file.size
|
file: file,
|
||||||
|
size: file.size
|
||||||
};
|
};
|
||||||
displayAttachFile(fileInfo);
|
displayAttachFile(fileInfo);
|
||||||
}
|
}
|
||||||
this.value = '';
|
this.value = ''; // 입력 필드 초기화
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#addFile').click(function() {
|
||||||
|
$('#fileInput').click();
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#addFile').click(function() { $('#fileInput').click(); });
|
|
||||||
|
|
||||||
$("#btn_modify").click(function () {
|
$("#btn_modify").click(function () {
|
||||||
if (!checkRequired("ajaxForm")) return;
|
if (!checkRequired("ajaxForm")) return;
|
||||||
|
|
||||||
var noticeType = $('#noticeType').val();
|
if (confirm("<%= localeMessage.getString("common.checkSave")%>") != true)
|
||||||
if (isIncidentType(noticeType)) {
|
|
||||||
if (!$('#startedAt').val()) {
|
|
||||||
alert('시작 시각을 입력해 주십시오.');
|
|
||||||
return;
|
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]);
|
var formData = new FormData($("#ajaxForm")[0]);
|
||||||
|
|
||||||
formData.set("useYn", $("#useYn").is(":checked") ? "Y" : "N");
|
formData.set("useYn", $("#useYn").is(":checked") ? "Y" : "N");
|
||||||
formData.set("fixYn", $("#fixYn").is(":checked") ? "Y" : "N");
|
formData.set("fixYn", $("#fixYn").is(":checked") ? "Y" : "N");
|
||||||
formData.set("noticeDetail", $('#contents').summernote('code'));
|
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");
|
formData.append("cmd", isDetail ? "UPDATE" : "INSERT");
|
||||||
|
|
||||||
|
// 파일 처리
|
||||||
if (fileInfo && fileInfo.file) {
|
if (fileInfo && fileInfo.file) {
|
||||||
formData.set("files", fileInfo.file);
|
formData.set("files", fileInfo.file);
|
||||||
formData.set("fileName", fileInfo.originalFileName + '.' + fileInfo.fileExtension);
|
formData.set("fileName", fileInfo.originalFileName + '.' + fileInfo.fileExtension);
|
||||||
}
|
}
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: "POST", url: url, data: formData,
|
type: "POST",
|
||||||
processData: false, contentType: false,
|
url: url,
|
||||||
success: function () {
|
data: formData,
|
||||||
|
processData: false,
|
||||||
|
contentType: false,
|
||||||
|
success: function (json) {
|
||||||
alert("<%= localeMessage.getString("common.saveMsg") %>");
|
alert("<%= localeMessage.getString("common.saveMsg") %>");
|
||||||
goNav(returnUrl);
|
goNav(returnUrl);
|
||||||
},
|
},
|
||||||
error: function (e) { alert(e.responseText); }
|
error: function (e) {
|
||||||
|
alert(e.responseText);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -318,18 +224,25 @@
|
|||||||
if (confirm("<%= localeMessage.getString("common.confirmMsg")%>")) {
|
if (confirm("<%= localeMessage.getString("common.confirmMsg")%>")) {
|
||||||
var postData = $('#ajaxForm').serializeArray();
|
var postData = $('#ajaxForm').serializeArray();
|
||||||
postData.push({name: "cmd", value: "DELETE"});
|
postData.push({name: "cmd", value: "DELETE"});
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: "POST", url: url, data: postData,
|
type: "POST",
|
||||||
success: function () {
|
url: url,
|
||||||
|
data: postData,
|
||||||
|
success: function (args) {
|
||||||
alert("<%= localeMessage.getString("common.deleteMsg") %>");
|
alert("<%= localeMessage.getString("common.deleteMsg") %>");
|
||||||
goNav(returnUrl);
|
goNav(returnUrl);
|
||||||
},
|
},
|
||||||
error: function (e) { alert(e.responseText); }
|
error: function (e) {
|
||||||
|
alert(e.responseText);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$("#btn_previous").click(function () { goNav(returnUrl); });
|
$("#btn_previous").click(function () {
|
||||||
|
goNav(returnUrl);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
@@ -339,7 +252,7 @@
|
|||||||
<ul class="path">
|
<ul class="path">
|
||||||
<li><a href="#">${rmsMenuPath}</a></li>
|
<li><a href="#">${rmsMenuPath}</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div><!-- end content_top -->
|
||||||
<div class="content_middle">
|
<div class="content_middle">
|
||||||
<div class="search_wrap">
|
<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_modify" level="W" status="DETAIL,NEW"><i class="material-icons">save</i> <%= localeMessage.getString("button.modify") %></button>
|
||||||
@@ -357,7 +270,7 @@
|
|||||||
<col style="width: 40%"/>
|
<col style="width: 40%"/>
|
||||||
</colgroup>
|
</colgroup>
|
||||||
<tr>
|
<tr>
|
||||||
<th>게시유형 <font color="red">*</font></th>
|
<th>게시유형</th>
|
||||||
<td colspan="3">
|
<td colspan="3">
|
||||||
<div class="select-style" >
|
<div class="select-style" >
|
||||||
<select name="noticeType" id="noticeType"></select>
|
<select name="noticeType" id="noticeType"></select>
|
||||||
@@ -373,7 +286,7 @@
|
|||||||
<td>
|
<td>
|
||||||
<input type="checkbox" name="useYn" id="useYn" value="Y" ${portalNoticeUI.useYn eq 'Y' ? 'checked' : ''}> 사용
|
<input type="checkbox" name="useYn" id="useYn" value="Y" ${portalNoticeUI.useYn eq 'Y' ? 'checked' : ''}> 사용
|
||||||
</td>
|
</td>
|
||||||
<th>상단고정</th>
|
<th>고정여부</th>
|
||||||
<td>
|
<td>
|
||||||
<input type="checkbox" name="fixYn" id="fixYn" value="Y" ${portalNoticeUI.fixYn eq 'Y' ? 'checked' : ''}> 고정
|
<input type="checkbox" name="fixYn" id="fixYn" value="Y" ${portalNoticeUI.fixYn eq 'Y' ? 'checked' : ''}> 고정
|
||||||
</td>
|
</td>
|
||||||
@@ -384,55 +297,16 @@
|
|||||||
<textarea id="contents" name="noticeDetail" style="width:100%;height:300px" data-required data-warning="본문을 입력하여 주십시오."></textarea>
|
<textarea id="contents" name="noticeDetail" style="width:100%;height:300px" data-required data-warning="본문을 입력하여 주십시오."></textarea>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<!-- <tr>
|
||||||
<!-- ───── 장애/점검 전용 영역 ───── -->
|
<th>첨부파일</th>
|
||||||
<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">
|
<td colspan="3">
|
||||||
<div style="margin-bottom:5px;">
|
<div style="margin: 5px 0px; display: inline-block;">
|
||||||
<button type="button" class="cssbtn smallBtn" id="btn_removeAffectedApi"><i class="material-icons">delete</i> 삭제</button>
|
<input type="file" id="fileInput" name="files" style="display:none;" />
|
||||||
<button type="button" class="cssbtn smallBtn" id="btn_addAffectedApi"><i class="material-icons">add</i> 추가</button>
|
<button type="button" id="addFile" class="cssbtn smallBtn">파일 선택</button>
|
||||||
</div>
|
</div>
|
||||||
<table class="table_row" cellspacing="0" style="width:100%;">
|
<div id="attachFiles" style="margin: 5px 0px; display: inline-block;"></div>
|
||||||
<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>
|
</td>
|
||||||
</tr>
|
</tr> -->
|
||||||
</table>
|
</table>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
# DJBank EAPIM Admin — user-scope systemd unit
|
||||||
|
#
|
||||||
|
# Install (one-time, on the host as `djb`):
|
||||||
|
# sudo loginctl enable-linger djb # allow services to outlive ssh logout
|
||||||
|
# mkdir -p ~/.config/systemd/user
|
||||||
|
# # workflow copies this file in on each deploy; for first boot you can also do:
|
||||||
|
# # cp /prod/.../deploy/systemd/eapim-admin.service ~/.config/systemd/user/
|
||||||
|
# systemctl --user daemon-reload
|
||||||
|
# systemctl --user enable eapim-admin
|
||||||
|
# systemctl --user start eapim-admin
|
||||||
|
#
|
||||||
|
# Why this exists:
|
||||||
|
# - `catalina.sh start` + `nohup &` does NOT escape the Gitea Actions runner
|
||||||
|
# process group. When the workflow step finishes, the runner sends SIGTERM
|
||||||
|
# to the process tree and Tomcat dies seconds after boot.
|
||||||
|
# - Running Tomcat as a systemd user service detaches lifecycle from the
|
||||||
|
# runner entirely. The workflow just calls `systemctl --user restart` and
|
||||||
|
# systemd owns the process.
|
||||||
|
|
||||||
|
[Unit]
|
||||||
|
Description=DJBank EAPIM Admin (Tomcat 9)
|
||||||
|
After=network-online.target
|
||||||
|
Wants=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
|
||||||
|
Environment=JAVA_HOME=/apps/opts/jdk17
|
||||||
|
Environment=CATALINA_BASE=/prod/eapim/adminportal
|
||||||
|
Environment=CATALINA_HOME=/prod/eapim/apache-tomcat-9.0.116
|
||||||
|
Environment=CATALINA_PID=/prod/eapim/adminportal/temp/catalina.pid
|
||||||
|
|
||||||
|
# `catalina.sh run` keeps Tomcat in the foreground so systemd can supervise it.
|
||||||
|
ExecStart=/prod/eapim/apache-tomcat-9.0.116/bin/catalina.sh run
|
||||||
|
|
||||||
|
# Graceful shutdown via Tomcat's shutdown port, 30s window.
|
||||||
|
ExecStop=/prod/eapim/apache-tomcat-9.0.116/bin/catalina.sh stop 30
|
||||||
|
|
||||||
|
# Tomcat returns 143 on SIGTERM during normal stop — treat as success.
|
||||||
|
SuccessExitStatus=143
|
||||||
|
TimeoutStartSec=180
|
||||||
|
TimeoutStopSec=60
|
||||||
|
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=10
|
||||||
|
|
||||||
|
# Memory / fd limits — adjust as needed.
|
||||||
|
LimitNOFILE=65535
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=default.target
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
package com.eactive.eai.rms.onl.apim.portalnotice;
|
|
||||||
|
|
||||||
import lombok.Data;
|
|
||||||
import lombok.NoArgsConstructor;
|
|
||||||
|
|
||||||
@Data
|
|
||||||
@NoArgsConstructor
|
|
||||||
public class IncidentAffectedApiUI {
|
|
||||||
private String apiId;
|
|
||||||
private String apiName;
|
|
||||||
}
|
|
||||||
+1
-136
@@ -1,13 +1,5 @@
|
|||||||
package com.eactive.eai.rms.onl.apim.portalnotice;
|
package com.eactive.eai.rms.onl.apim.portalnotice;
|
||||||
|
|
||||||
import com.eactive.apim.portal.djb.apistatus.incident.entity.DjbApistatusIncident;
|
|
||||||
import com.eactive.apim.portal.djb.apistatus.incident.entity.DjbApistatusIncidentApi;
|
|
||||||
import com.eactive.apim.portal.djb.apistatus.incident.entity.DjbApistatusIncidentApiId;
|
|
||||||
import com.eactive.apim.portal.djb.apistatus.incident.entity.IncidentKind;
|
|
||||||
import com.eactive.apim.portal.djb.apistatus.incident.entity.IncidentState;
|
|
||||||
import com.eactive.apim.portal.djb.apistatus.incident.repository.DjbApistatusIncidentApiRepository;
|
|
||||||
import com.eactive.apim.portal.djb.apistatus.incident.repository.DjbApistatusIncidentRepository;
|
|
||||||
import com.eactive.apim.portal.djb.apistatus.incident.repository.DjbApistatusIncidentTimelineRepository;
|
|
||||||
import com.eactive.apim.portal.file.entity.FileDetail;
|
import com.eactive.apim.portal.file.entity.FileDetail;
|
||||||
import com.eactive.apim.portal.file.entity.FileInfo;
|
import com.eactive.apim.portal.file.entity.FileInfo;
|
||||||
import com.eactive.apim.portal.file.service.FileService;
|
import com.eactive.apim.portal.file.service.FileService;
|
||||||
@@ -31,12 +23,7 @@ import java.io.IOException;
|
|||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@Transactional(transactionManager = "transactionManagerForEMS")
|
@Transactional(transactionManager = "transactionManagerForEMS")
|
||||||
@@ -45,9 +32,6 @@ public class PortalNoticeManService extends BaseService {
|
|||||||
private final PortalNoticeService portalNoticeService;
|
private final PortalNoticeService portalNoticeService;
|
||||||
private final PortalNoticeUIMapper portalNoticeUIMapper;
|
private final PortalNoticeUIMapper portalNoticeUIMapper;
|
||||||
private final FileService fileService;
|
private final FileService fileService;
|
||||||
private final DjbApistatusIncidentRepository incidentRepository;
|
|
||||||
private final DjbApistatusIncidentApiRepository incidentApiRepository;
|
|
||||||
private final DjbApistatusIncidentTimelineRepository incidentTimelineRepository;
|
|
||||||
|
|
||||||
private String decodeString(String value) {
|
private String decodeString(String value) {
|
||||||
if (ContainerUtil.get() == ContainerUtil.TOMCAT) {
|
if (ContainerUtil.get() == ContainerUtil.TOMCAT) {
|
||||||
@@ -64,27 +48,10 @@ public class PortalNoticeManService extends BaseService {
|
|||||||
@Autowired
|
@Autowired
|
||||||
public PortalNoticeManService(PortalNoticeService portalNoticeService,
|
public PortalNoticeManService(PortalNoticeService portalNoticeService,
|
||||||
PortalNoticeUIMapper portalNoticeUIMapper,
|
PortalNoticeUIMapper portalNoticeUIMapper,
|
||||||
FileService fileService,
|
FileService fileService){
|
||||||
DjbApistatusIncidentRepository incidentRepository,
|
|
||||||
DjbApistatusIncidentApiRepository incidentApiRepository,
|
|
||||||
DjbApistatusIncidentTimelineRepository incidentTimelineRepository){
|
|
||||||
this.portalNoticeService = portalNoticeService;
|
this.portalNoticeService = portalNoticeService;
|
||||||
this.portalNoticeUIMapper = portalNoticeUIMapper;
|
this.portalNoticeUIMapper = portalNoticeUIMapper;
|
||||||
this.fileService = fileService;
|
this.fileService = fileService;
|
||||||
this.incidentRepository = incidentRepository;
|
|
||||||
this.incidentApiRepository = incidentApiRepository;
|
|
||||||
this.incidentTimelineRepository = incidentTimelineRepository;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean isIncidentKind(String noticeType) {
|
|
||||||
return PortalNoticeUI.NOTICE_TYPE_INCIDENT.equals(noticeType)
|
|
||||||
|| PortalNoticeUI.NOTICE_TYPE_MAINTENANCE.equals(noticeType);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static IncidentKind toKind(String noticeType) {
|
|
||||||
if (PortalNoticeUI.NOTICE_TYPE_INCIDENT.equals(noticeType)) return IncidentKind.INCIDENT;
|
|
||||||
if (PortalNoticeUI.NOTICE_TYPE_MAINTENANCE.equals(noticeType)) return IncidentKind.MAINTENANCE;
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -122,39 +89,9 @@ public class PortalNoticeManService extends BaseService {
|
|||||||
|
|
||||||
PortalNoticeUI ui = portalNoticeUIMapper.toVo(portalNotice);
|
PortalNoticeUI ui = portalNoticeUIMapper.toVo(portalNotice);
|
||||||
setFileInfo(portalNotice, ui);
|
setFileInfo(portalNotice, ui);
|
||||||
populateIncident(portalNotice, ui);
|
|
||||||
return ui;
|
return ui;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void populateIncident(PortalNotice portalNotice, PortalNoticeUI ui) {
|
|
||||||
if (!isIncidentKind(portalNotice.getNoticeType())) {
|
|
||||||
ui.setAffectedApis(Collections.emptyList());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Optional<DjbApistatusIncident> incidentOpt = incidentRepository.findByNoticeId(portalNotice.getId());
|
|
||||||
if (!incidentOpt.isPresent()) {
|
|
||||||
ui.setAffectedApis(Collections.emptyList());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
DjbApistatusIncident incident = incidentOpt.get();
|
|
||||||
ui.setIncidentId(incident.getIncidentId());
|
|
||||||
ui.setSummary(incident.getSummary());
|
|
||||||
ui.setStartedAt(incident.getStartedAt());
|
|
||||||
ui.setEndAt(incident.getEndAt());
|
|
||||||
ui.setState(incident.getState() == null ? null : incident.getState().name());
|
|
||||||
ui.setPreviousState(incident.getPreviousState() == null ? null : incident.getPreviousState().name());
|
|
||||||
|
|
||||||
List<IncidentAffectedApiUI> apis = incidentApiRepository
|
|
||||||
.findByIncidentIdOrderByApiId(incident.getIncidentId()).stream()
|
|
||||||
.map(api -> {
|
|
||||||
IncidentAffectedApiUI vo = new IncidentAffectedApiUI();
|
|
||||||
vo.setApiId(api.getApiId());
|
|
||||||
vo.setApiName(api.getApiName());
|
|
||||||
return vo;
|
|
||||||
}).collect(Collectors.toList());
|
|
||||||
ui.setAffectedApis(apis);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void insert(PortalNoticeUI portalNoticeUI) throws IOException {
|
public void insert(PortalNoticeUI portalNoticeUI) throws IOException {
|
||||||
//portalNoticeUI.setNoticeSubject(decodeString(portalNoticeUI.getNoticeSubject()));
|
//portalNoticeUI.setNoticeSubject(decodeString(portalNoticeUI.getNoticeSubject()));
|
||||||
//portalNoticeUI.setNoticeDetail(decodeString(StringEscapeUtils.unescapeHtml(portalNoticeUI.getNoticeDetail())));
|
//portalNoticeUI.setNoticeDetail(decodeString(StringEscapeUtils.unescapeHtml(portalNoticeUI.getNoticeDetail())));
|
||||||
@@ -169,61 +106,6 @@ public class PortalNoticeManService extends BaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
portalNoticeService.save(portalNotice);
|
portalNoticeService.save(portalNotice);
|
||||||
|
|
||||||
if (isIncidentKind(portalNoticeUI.getNoticeType())) {
|
|
||||||
persistIncident(portalNoticeUI, portalNotice, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void persistIncident(PortalNoticeUI portalNoticeUI, PortalNotice portalNotice, boolean isCreate) {
|
|
||||||
IncidentKind kind = toKind(portalNoticeUI.getNoticeType());
|
|
||||||
|
|
||||||
DjbApistatusIncident incident = incidentRepository.findByNoticeId(portalNotice.getId())
|
|
||||||
.orElseGet(DjbApistatusIncident::new);
|
|
||||||
|
|
||||||
incident.setKind(kind);
|
|
||||||
incident.setTitle(portalNotice.getNoticeSubject());
|
|
||||||
incident.setSummary(portalNoticeUI.getSummary());
|
|
||||||
incident.setStartedAt(portalNoticeUI.getStartedAt() != null
|
|
||||||
? portalNoticeUI.getStartedAt() : LocalDateTime.now());
|
|
||||||
incident.setEndAt(portalNoticeUI.getEndAt());
|
|
||||||
incident.setDetectedBy("MANUAL");
|
|
||||||
incident.setNoticeId(portalNotice.getId());
|
|
||||||
incident.setDraftYn("N");
|
|
||||||
incident.setFixYn(StringUtils.defaultIfBlank(portalNotice.getFixYn(), "N"));
|
|
||||||
|
|
||||||
if (kind == IncidentKind.INCIDENT) {
|
|
||||||
IncidentState newState = StringUtils.isNotBlank(portalNoticeUI.getState())
|
|
||||||
? IncidentState.valueOf(portalNoticeUI.getState())
|
|
||||||
: IncidentState.INVESTIGATING;
|
|
||||||
if (!isCreate && incident.getState() != newState) {
|
|
||||||
incident.setPreviousState(incident.getState());
|
|
||||||
}
|
|
||||||
incident.setState(newState);
|
|
||||||
} else {
|
|
||||||
incident.setState(null);
|
|
||||||
incident.setPreviousState(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
DjbApistatusIncident saved = incidentRepository.save(incident);
|
|
||||||
|
|
||||||
// 영향 API 동기화 — 단순 전체 삭제 후 재삽입
|
|
||||||
incidentApiRepository.deleteByIncidentId(saved.getIncidentId());
|
|
||||||
List<IncidentAffectedApiUI> affected = portalNoticeUI.getAffectedApis();
|
|
||||||
if (affected != null && !affected.isEmpty()) {
|
|
||||||
List<DjbApistatusIncidentApi> rows = new ArrayList<>();
|
|
||||||
for (IncidentAffectedApiUI src : affected) {
|
|
||||||
if (StringUtils.isBlank(src.getApiId())) continue;
|
|
||||||
DjbApistatusIncidentApi api = new DjbApistatusIncidentApi();
|
|
||||||
api.setIncidentId(saved.getIncidentId());
|
|
||||||
api.setApiId(src.getApiId());
|
|
||||||
api.setApiName(src.getApiName());
|
|
||||||
rows.add(api);
|
|
||||||
}
|
|
||||||
if (!rows.isEmpty()) {
|
|
||||||
incidentApiRepository.saveAll(rows);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleFileUpload(PortalNoticeUI portalNoticeUI, PortalNotice portalNotice) throws IOException {
|
private void handleFileUpload(PortalNoticeUI portalNoticeUI, PortalNotice portalNotice) throws IOException {
|
||||||
@@ -247,7 +129,6 @@ public class PortalNoticeManService extends BaseService {
|
|||||||
portalNoticeUI.setNoticeDetail(StringEscapeUtils.unescapeHtml(portalNoticeUI.getNoticeDetail()));
|
portalNoticeUI.setNoticeDetail(StringEscapeUtils.unescapeHtml(portalNoticeUI.getNoticeDetail()));
|
||||||
|
|
||||||
PortalNotice portalNotice = portalNoticeService.getById(portalNoticeUI.getId());
|
PortalNotice portalNotice = portalNoticeService.getById(portalNoticeUI.getId());
|
||||||
String previousNoticeType = portalNotice.getNoticeType();
|
|
||||||
|
|
||||||
if (portalNoticeUI.getFiles() != null && !portalNoticeUI.getFiles().isEmpty()) {
|
if (portalNoticeUI.getFiles() != null && !portalNoticeUI.getFiles().isEmpty()) {
|
||||||
handleFileUpload(portalNoticeUI, portalNotice);
|
handleFileUpload(portalNoticeUI, portalNotice);
|
||||||
@@ -258,27 +139,11 @@ public class PortalNoticeManService extends BaseService {
|
|||||||
|
|
||||||
portalNoticeUIMapper.updateToEntity(portalNoticeUI, portalNotice);
|
portalNoticeUIMapper.updateToEntity(portalNoticeUI, portalNotice);
|
||||||
portalNoticeService.save(portalNotice);
|
portalNoticeService.save(portalNotice);
|
||||||
|
|
||||||
if (isIncidentKind(portalNotice.getNoticeType())) {
|
|
||||||
persistIncident(portalNoticeUI, portalNotice, false);
|
|
||||||
} else if (isIncidentKind(previousNoticeType)) {
|
|
||||||
// 일반 공지로 유형 변경 → 기존 incident 정리
|
|
||||||
deleteIncidentIfExists(portalNotice.getId());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void deleteIncidentIfExists(String noticeId) {
|
|
||||||
incidentRepository.findByNoticeId(noticeId).ifPresent(incident -> {
|
|
||||||
incidentApiRepository.deleteByIncidentId(incident.getIncidentId());
|
|
||||||
incidentTimelineRepository.deleteByIncidentId(incident.getIncidentId());
|
|
||||||
incidentRepository.delete(incident);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void delete(String id) {
|
public void delete(String id) {
|
||||||
PortalNotice portalNotice = portalNoticeService.getById(id);
|
PortalNotice portalNotice = portalNoticeService.getById(id);
|
||||||
Optional.ofNullable(portalNotice.getFileId()).ifPresent(fileService::deleteFile);
|
Optional.ofNullable(portalNotice.getFileId()).ifPresent(fileService::deleteFile);
|
||||||
deleteIncidentIfExists(id);
|
|
||||||
portalNoticeService.deleteById(id);
|
portalNoticeService.deleteById(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,16 +8,11 @@ import org.springframework.format.annotation.DateTimeFormat;
|
|||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
public class PortalNoticeUI {
|
public class PortalNoticeUI {
|
||||||
|
|
||||||
public static final String NOTICE_TYPE_NORMAL = "1";
|
|
||||||
public static final String NOTICE_TYPE_MAINTENANCE = "2";
|
|
||||||
public static final String NOTICE_TYPE_INCIDENT = "3";
|
|
||||||
|
|
||||||
private String id;
|
private String id;
|
||||||
|
|
||||||
private String noticeSubject;
|
private String noticeSubject;
|
||||||
@@ -58,23 +53,4 @@ public class PortalNoticeUI {
|
|||||||
private boolean hasAttachment; // 첨부파일 여부
|
private boolean hasAttachment; // 첨부파일 여부
|
||||||
|
|
||||||
private boolean fileDeleted; // 파일 삭제 여부
|
private boolean fileDeleted; // 파일 삭제 여부
|
||||||
|
|
||||||
// ───── 장애/점검(djb_apistatus_incident) 연동 필드 ─────
|
|
||||||
private Long incidentId;
|
|
||||||
|
|
||||||
private String summary;
|
|
||||||
|
|
||||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm")
|
|
||||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm")
|
|
||||||
private LocalDateTime startedAt;
|
|
||||||
|
|
||||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm")
|
|
||||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm")
|
|
||||||
private LocalDateTime endAt;
|
|
||||||
|
|
||||||
private String state; // INCIDENT 한정 (INVESTIGATING/IDENTIFIED/MONITORING/RESOLVED/CANCELED)
|
|
||||||
|
|
||||||
private String previousState; // 직전 상태
|
|
||||||
|
|
||||||
private List<IncidentAffectedApiUI> affectedApis;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,9 +3,7 @@ package com.eactive.eai.rms.onl.apim.portalnotice;
|
|||||||
import com.eactive.apim.portal.portalNotice.entity.PortalNotice;
|
import com.eactive.apim.portal.portalNotice.entity.PortalNotice;
|
||||||
import org.mapstruct.*;
|
import org.mapstruct.*;
|
||||||
|
|
||||||
@Mapper(componentModel = "spring",
|
@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
|
||||||
unmappedTargetPolicy = ReportingPolicy.IGNORE,
|
|
||||||
unmappedSourcePolicy = ReportingPolicy.IGNORE)
|
|
||||||
public interface PortalNoticeUIMapper {
|
public interface PortalNoticeUIMapper {
|
||||||
|
|
||||||
PortalNoticeUI toVo(PortalNotice entity);
|
PortalNoticeUI toVo(PortalNotice entity);
|
||||||
|
|||||||
@@ -5,13 +5,20 @@
|
|||||||
# \uC790\uB3D9 \uD56B\uC2A4\uC651 \uD65C\uC131\uD654
|
# \uC790\uB3D9 \uD56B\uC2A4\uC651 \uD65C\uC131\uD654
|
||||||
autoHotswap=true
|
autoHotswap=true
|
||||||
|
|
||||||
# \uB85C\uAE45 \uB808\uBCA8 (TRACE, DEBUG, INFO, WARNING, ERROR)
|
# \uB85C\uADF8 \uB808\uBCA8 (TRACE, DEBUG, INFO, WARNING, ERROR)
|
||||||
LOGGER.level=INFO
|
LOGGER=INFO
|
||||||
|
|
||||||
|
# \uB85C\uADF8 \uD30C\uC77C \uACBD\uB85C (\uC9C0\uC815 \uC2DC \uCF58\uC194 \uB300\uC2E0 \uD30C\uC77C\uB85C \uCD9C\uB825)
|
||||||
|
LOGFILE=/Users/rinjae/eactive/djb-eapim/djb-eapim/eapim-admin/logs/hotswap-agent.log
|
||||||
|
LOGFILE.append=true
|
||||||
|
|
||||||
|
# \uC2DC\uAC01 \uD3EC\uB9F7
|
||||||
|
LOGGER_DATETIME_FORMAT=yyyy-MM-dd HH:mm:ss.SSS
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# \uBA85\uC2DC\uC801 \uD50C\uB7EC\uADF8\uC778 \uBE44\uD65C\uC131\uD654 (\uC27C\uD45C \uAD6C\uBD84)
|
# \uBA85\uC2DC\uC801 \uD50C\uB7EC\uADF8\uC778 \uBE44\uD65C\uC131\uD654 (\uC27C\uD45C \uAD6C\uBD84)
|
||||||
# ============================================================
|
# ============================================================
|
||||||
disabledPlugins=org.hotswap.agent.plugin.ibatis.IBatisPlugin,org.hotswap.agent.plugin.mybatis.MyBatisPlugin
|
disabledPlugins=IBatis,MyBatis
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# Core Plugin \uD65C\uC131\uD654
|
# Core Plugin \uD65C\uC131\uD654
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
<property name="LOG_PATTERN" value="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] \\(%F:%L\\) %-20M -%msg%n" />
|
<property name="LOG_PATTERN" value="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] \\(%F:%L\\) %-20M -%msg%n" />
|
||||||
<property name="PREFIX" value="${inst.Type:-ems}" />
|
<property name="PREFIX" value="${inst.Type:-ems}" />
|
||||||
<property name="LOG_HOME" value="/Log/App/eapim/${inst.Name:-devSvr00}" />
|
<property name="LOG_HOME" value="/logs/eapim/${inst.Name:-emsSvr00}" />
|
||||||
<property name="BACKUP_HOME" value="${LOG_HOME}/backup/" />
|
<property name="BACKUP_HOME" value="${LOG_HOME}/backup/" />
|
||||||
<property name="LOG_LEVEL" value="${LOGBACK_LOG_LEVEL:-DEBUG}" />
|
<property name="LOG_LEVEL" value="${LOGBACK_LOG_LEVEL:-DEBUG}" />
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
<property name="LOG_PATTERN" value="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] \\(%F:%L\\) %-20M -%msg%n" />
|
<property name="LOG_PATTERN" value="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] \\(%F:%L\\) %-20M -%msg%n" />
|
||||||
<property name="PREFIX" value="${inst.Type:-ems}" />
|
<property name="PREFIX" value="${inst.Type:-ems}" />
|
||||||
<property name="LOG_HOME" value="/Log/App/eapim/${inst.Name:-devSvr00}" />
|
<property name="LOG_HOME" value="/logs/eapim/${inst.Name:-devSvr00}" />
|
||||||
<property name="BACKUP_HOME" value="${LOG_HOME}/backup/" />
|
<property name="BACKUP_HOME" value="${LOG_HOME}/backup/" />
|
||||||
<property name="LOG_LEVEL" value="${LOGBACK_LOG_LEVEL:-DEBUG}" />
|
<property name="LOG_LEVEL" value="${LOGBACK_LOG_LEVEL:-DEBUG}" />
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
<property name="LOG_PATTERN" value="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] \\(%F:%L\\) %-20M -%msg%n" />
|
<property name="LOG_PATTERN" value="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] \\(%F:%L\\) %-20M -%msg%n" />
|
||||||
<property name="PREFIX" value="${inst.Type:-ems}" />
|
<property name="PREFIX" value="${inst.Type:-ems}" />
|
||||||
<property name="LOG_HOME" value="/Log/App/eapim/${inst.Name:-emsSvr00}" />
|
<property name="LOG_HOME" value="/logs/eapim/${inst.Name:-emsSvr00}" />
|
||||||
<property name="BACKUP_HOME" value="${LOG_HOME}/backup/" />
|
<property name="BACKUP_HOME" value="${LOG_HOME}/backup/" />
|
||||||
<property name="LOG_LEVEL" value="${LOGBACK_LOG_LEVEL:-INFO}" />
|
<property name="LOG_LEVEL" value="${LOGBACK_LOG_LEVEL:-INFO}" />
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user