42 Commits

Author SHA1 Message Date
eastargh 316f787276 API상태변환 수정
eapim-admin CI / build (push) Waiting to run
2026-07-21 11:30:01 +09:00
eastargh c3c9f7a2da IIM 연동시 마스킹코드 적용
eapim-admin CI / build (push) Has been cancelled
2026-07-20 16:19:12 +09:00
eastargh 95e31befbb Djb 커스텀 원복
eapim-admin CI / build (push) Has been cancelled
2026-07-20 14:47:59 +09:00
eastargh f1f7576b9d 인터페이스 목록에 상태칼럼 추가
eapim-admin CI / build (push) Has been cancelled
2026-07-20 11:46:07 +09:00
eastargh 545e09891b 어뎁터 없을때 상세조회 되도록 변경
eapim-admin CI / build (push) Has been cancelled
2026-07-20 10:53:33 +09:00
eastargh 70c2ac8376 서버 등록시 중복체크
eapim-admin CI / build (push) Has been cancelled
2026-07-16 14:52:36 +09:00
eastargh 21c493cb0e 통계 화면 레이아웃 조정
eapim-admin CI / build (push) Has been cancelled
2026-07-16 11:11:01 +09:00
eastargh b9c6a0da06 파이차트 글자 겹쳐보이는 버그 수정 (legend 오른쪽으로 이동)
eapim-admin CI / build (push) Has been cancelled
2026-07-16 10:14:37 +09:00
eastargh a0725b57a6 sms->kakao로 통일
eapim-admin CI / build (push) Has been cancelled
2026-07-16 09:27:37 +09:00
eastargh 50bfcacf27 전문레이아웃 변환 매핑 - api선택 팝업 추가
eapim-admin CI / build (push) Has been cancelled
2026-07-15 16:52:34 +09:00
eastargh f400f0322f 유량제어 이력 검색조건 추가
eapim-admin CI / build (push) Has been cancelled
2026-07-14 15:05:57 +09:00
eastargh d0cd8118f3 유량제어관련 메뉴 - 사용여부 검색조건 추가
eapim-admin CI / build (push) Has been cancelled
2026-07-14 11:30:55 +09:00
Rinjae 07e65c055b - JSP: OpenAPI Spec 버튼 상태 수정 - NEW → DETAIL로 변경
eapim-admin CI / build (push) Has been cancelled
- Service: BIGDECIMAL 매핑 추가 - 반환 유형 'number'
- Monitoring: Properties 초기화 및 null 체크 로직 추가
- JS: 샘플 응답 상태 시 안내 메시지 추가
2026-07-14 11:11:24 +09:00
eastargh af8a9cca51 프라퍼티 정리
eapim-admin CI / build (push) Has been cancelled
2026-07-13 17:47:35 +09:00
eastargh 39a09d0f5d 유량제어 url 특수문자 escape 2026-07-13 17:47:08 +09:00
Rinjae 5b940717c4 Merge remote-tracking branch 'origin/master'
eapim-admin CI / build (push) Has been cancelled
2026-07-13 15:30:13 +09:00
Rinjae 8ccf52fd66 - JSP: OpenAPI Spec 모달 처리 개선 - 동적 크기 조절 및 스타일 추가
- Service: GET/DELETE 요청 바디 항목→쿼리 파라미터 매핑 로직 추가
- JS: 요청 정보 진입 시 탭 자동 전환 로직(firstReqTabWithData) 구현
2026-07-13 15:30:08 +09:00
eastargh dedc58a07f 유량제어 추가임계치가 null or 단위선택 않하면 0으로 설정
eapim-admin CI / build (push) Has been cancelled
2026-07-13 15:24:30 +09:00
Rinjae 4530af9dec Merge remote-tracking branch 'origin/master'
eapim-admin CI / build (push) Has been cancelled
# Conflicts:
#	build.gradle
2026-07-13 14:28:49 +09:00
Rinjae cbf3066a56 API 그룹 팝업 JSP 파일 추가 - 예제 코드 및 UI 구조 작성
Summernote 에디터 스타일 CSS 추가 - 텍스트 및 컴포넌트 스타일 정의
OpenAPI POC JS 파일 추가 - API 스펙 빌더 및 스텝별 렌더 구현
2026-07-13 14:25:09 +09:00
eastargh 160e28444a eapim-admin-djb 제거
eapim-admin CI / build (push) Has been cancelled
2026-07-13 13:20:22 +09:00
Rinjae d7dcf9a81c Q&A 문의 공개범위 기능 추가
eapim-admin CI / build (push) Has been cancelled
- 공개범위 설정 기능 개발(ALL/ORG/PRIVATE) - 게시물/댓글 정교화
- 요청 및 DB 처리 확장 - UI, DTO, Service 계층 업데이트
- 감사 로그 및 조회수 처리 로직 추가
2026-07-13 11:32:34 +09:00
Rinjae acfe6b2d03 weblogic.xml 업데이트
eapim-admin CI / build (push) Has been cancelled
- XML 태그 단순화 및 불필요한 속성 제거
- session-descriptor에 cookie-name 추가
2026-07-10 17:48:34 +09:00
eastargh 481e82fec7 Merge branch 'master' of https://git.eactive.synology.me:8090/eapim/djbank/eapim-admin.git
eapim-admin CI / build (push) Has been cancelled
2026-07-10 11:37:43 +09:00
eastargh 1b382f5859 ums 설정 추가 2026-07-10 11:37:33 +09:00
Rinjae 608cb4188e "Jenkinsfile 개선:
eapim-admin CI / build (push) Has been cancelled
- Tomcat 배포 구조 → WebLogic 기반으로 변경
- stage별 agent 명시 및 불필요한 환경변수 제거
- Webhook 알림 로직 추가"
2026-07-09 16:20:54 +09:00
eastargh 534947637d qna 상세 회원이름 표시
eapim-admin CI / build (push) Has been cancelled
2026-07-09 14:55:07 +09:00
eastargh 153c68c6ab 사이트맵 위치 변경
eapim-admin CI / build (push) Has been cancelled
2026-07-09 11:47:23 +09:00
eastargh 80ef41f4fc QnA 문의검토중 상태 추가
eapim-admin CI / build (push) Has been cancelled
2026-07-09 09:26:24 +09:00
Rinjae 8aebabe005 Merge remote-tracking branch 'origin/master'
eapim-admin CI / build (push) Has been cancelled
2026-07-08 18:31:45 +09:00
Rinjae 6d33b69bed 인증 서버 클라이언트 차단 API 추가:
- `ClientBlockApiController` 신규 생성 및 차단 로직 구현
- `ClientManService`에 클라이언트 차단 메서드 추가
- 승인 요청 삭제 로직 개발 환경 제한 문구 수정
2026-07-08 18:31:39 +09:00
eastargh 49ebdce4c3 전문레이아웃 그리드 항목명 순서 변경(김태호이사 요청)
eapim-admin CI / build (push) Has been cancelled
2026-07-08 11:13:43 +09:00
eastargh 979e43e382 - 포함된 인터페이스명 복제 버그 수정
eapim-admin CI / build (push) Has been cancelled
2026-07-08 10:37:03 +09:00
eastargh 53b075967c 스타일시트 정리
eapim-admin CI / build (push) Has been cancelled
2026-07-07 16:53:22 +09:00
eastargh 724117860b 탑메뉴 오버레이 높이 조정
eapim-admin CI / build (push) Has been cancelled
2026-07-07 14:47:02 +09:00
eastargh 0ee52392aa 조회기간 기본설정 변경
eapim-admin CI / build (push) Has been cancelled
2026-07-07 14:09:58 +09:00
eastargh f0af06da88 사이트맵
eapim-admin CI / build (push) Has been cancelled
2026-07-07 13:38:44 +09:00
eastargh b16d03517a 사이트맵 신규 개발
eapim-admin CI / build (push) Has been cancelled
2026-07-07 13:19:33 +09:00
Rinjae edf024f0a3 Merge remote-tracking branch 'origin/master'
eapim-admin CI / build (push) Has been cancelled
2026-07-07 10:35:26 +09:00
Rinjae a1a1f27498 승인 관리: 승인 상태별 행 배경색 강조 로직 추가 2026-07-07 10:34:41 +09:00
eastargh c62f395983 Merge branch 'master' of https://git.eactive.synology.me:8090/eapim/djbank/eapim-admin.git
eapim-admin CI / build (push) Has been cancelled
2026-07-06 16:34:55 +09:00
eastargh c3955cefd8 Scope관리 Api Reload 버그 수정 2026-07-06 16:26:40 +09:00
110 changed files with 6388 additions and 868 deletions
Vendored
+142 -148
View File
@@ -1,180 +1,174 @@
pipeline { pipeline {
agent { label 'djb-vm' } agent none
options { options {
timestamps() timestamps()
disableConcurrentBuilds() disableConcurrentBuilds()
skipDefaultCheckout() // stage별 agent의 자동 SCM checkout 방지 (Deploy 노드는 git 접근 불필요, unstash로만 WAR 수신)
buildDiscarder(logRotator(numToKeepStr: '20', artifactNumToKeepStr: '20')) buildDiscarder(logRotator(numToKeepStr: '20', artifactNumToKeepStr: '20'))
} }
environment { environment {
JAVA_HOME = '/apps/opts/jdk8'
JAVA_HOME_TOMCAT = '/apps/opts/jdk17'
GRADLE_HOME = '/apps/opts/gradle-8.7'
GRADLE_USER_HOME = '/apps/opts/gradle-home'
NODE_HOME = '/apps/opts/node-v24'
PATH = "/apps/opts/jdk8/bin:/apps/opts/gradle-8.7/bin:/apps/opts/node-v24/bin:/apps/opts/bin:${env.PATH}"
GIT_SSH_COMMAND = 'ssh -o StrictHostKeyChecking=accept-new' GIT_SSH_COMMAND = 'ssh -o StrictHostKeyChecking=accept-new'
CATALINA_BASE = '/prod/eapim/adminportal' WEBHOOK_URL = 'http://172.30.1.50:18000/api/hooks/01ba51b01d3c4c98ae8f3183a23a84ed713197857d024b5da4f303458195eb32'
CATALINA_HOME = '/prod/eapim/apache-tomcat-9.0.116'
CATALINA_PID = '/prod/eapim/adminportal/temp/catalina.pid'
DEPLOY_HTTP_PORT = '39120'
} }
stages { stages {
stage('Checkout') { stage('Build (djb-vm)') {
steps { agent { label 'djb-vm' }
checkout scm environment {
JAVA_HOME = '/apps/opts/jdk8'
GRADLE_HOME = '/apps/opts/gradle-8.7'
GRADLE_USER_HOME = '/apps/opts/gradle-home'
NODE_HOME = '/apps/opts/node-v24'
PATH = "/apps/opts/jdk8/bin:/apps/opts/gradle-8.7/bin:/apps/opts/node-v24/bin:/apps/opts/bin:${env.PATH}"
} }
} stages {
stage('Checkout') {
steps { checkout scm }
}
stage('Checkout dependencies') { stage('Checkout dependencies') {
steps { steps {
sh ''' sh '''
set -eu set -eu
cd "$WORKSPACE/.." cd "$WORKSPACE/.."
mkdir -p eapim-online mkdir -p eapim-online
for REPO in elink-online-core elink-online-core-jpa elink-online-transformer elink-online-common elink-online-emsclient; do for REPO in elink-online-core elink-online-core-jpa elink-online-transformer elink-online-common elink-online-emsclient; do
TARGET="eapim-online/$REPO" TARGET="eapim-online/$REPO"
if [ ! -d "$TARGET/.git" ]; then if [ ! -d "$TARGET/.git" ]; then
rm -rf "$TARGET" rm -rf "$TARGET"
git clone --depth=1 --branch master \ git clone --depth=1 --branch master \
"ssh://git@172.30.1.50:2222/djb-eapim/$REPO.git" \ "ssh://git@172.30.1.50:2222/djb-eapim/$REPO.git" \
"$TARGET" "$TARGET"
else else
git -C "$TARGET" fetch --depth=1 origin master git -C "$TARGET" fetch --depth=1 origin master
git -C "$TARGET" reset --hard origin/master git -C "$TARGET" reset --hard origin/master
git -C "$TARGET" clean -fdx git -C "$TARGET" clean -fdx
fi fi
done done
for REPO in elink-portal-common eapim-admin-djb; do for REPO in elink-portal-common eapim-admin-djb; do
if [ ! -d "$REPO/.git" ]; then if [ ! -d "$REPO/.git" ]; then
rm -rf "$REPO" rm -rf "$REPO"
git clone --depth=1 --branch master \ git clone --depth=1 --branch master \
"ssh://git@172.30.1.50:2222/djb-eapim/$REPO.git" \ "ssh://git@172.30.1.50:2222/djb-eapim/$REPO.git" \
"$REPO" "$REPO"
else else
git -C "$REPO" fetch --depth=1 origin master git -C "$REPO" fetch --depth=1 origin master
git -C "$REPO" reset --hard origin/master git -C "$REPO" reset --hard origin/master
git -C "$REPO" clean -fdx git -C "$REPO" clean -fdx
fi fi
done done
''' '''
} }
} }
stage('Verify toolchain') { stage('Verify toolchain') {
steps { steps {
sh ''' sh '''
set -eu set -eu
java -version java -version
gradle --version gradle --version
''' '''
} }
} }
stage('Build WAR') { stage('Build WAR') {
steps { steps { sh 'gradle clean build -x test --no-daemon -Pprofile=weblogic' }
sh 'gradle clean build -x test --no-daemon' post {
} success {
post { sh '''
success { set -eu
sh ''' cd build/libs
set -eu sha256sum eapim-admin.war > eapim-admin.war.sha256
cd build/libs '''
sha1sum eapim-admin.war > eapim-admin.war.sha1 archiveArtifacts artifacts: 'build/libs/eapim-admin.war,build/libs/eapim-admin.war.sha256', fingerprint: true
sha256sum eapim-admin.war > eapim-admin.war.sha256 stash name: 'war', includes: 'build/libs/eapim-admin.war'
md5sum eapim-admin.war > eapim-admin.war.md5 }
''' }
archiveArtifacts artifacts: 'build/libs/eapim-admin.war,build/libs/eapim-admin.war.sha1,build/libs/eapim-admin.war.sha256,build/libs/eapim-admin.war.md5', fingerprint: true
} }
} }
} }
stage('Stop Tomcat') { stage('Deploy (weblogic)') {
steps { agent { label 'weblogic' }
sh ''' environment {
set +e JENKINS_NODE_COOKIE = 'dontKillMe' // background weblogic를 빌드 종료 시 죽이지 않게
systemctl --user stop eapim-admin 2>/dev/null WL_HOME = '/app/eapim/adminportal'
WL_DEPLOY_DIR = '/app/eapim/adminportal'
if [ -f "$CATALINA_PID" ] && kill -0 "$(cat "$CATALINA_PID")" 2>/dev/null; then WL_WAR_NAME = 'eapim-admin.war'
JAVA_HOME="$JAVA_HOME_TOMCAT" "$CATALINA_HOME/bin/shutdown.sh" 30 -force || true WL_HTTP_PORT = '39120'
for i in $(seq 1 30); do WL_NOHUP = '/logs/weblogic/domains/eapimDomain/nohup.emsSvr11.out'
[ -f "$CATALINA_PID" ] && kill -0 "$(cat "$CATALINA_PID")" 2>/dev/null || break
sleep 1
done
fi
rm -f "$CATALINA_PID"
'''
} }
} stages {
stage('Stop WebLogic') {
steps {
sh '"$WL_HOME/stopEms11.sh"' // 동기: 완전 종료까지 블록, 미기동 시에도 안전
}
}
stage('Deploy WAR') {
steps {
unstash 'war'
sh '''
set -eu
cp build/libs/eapim-admin.war "$WL_DEPLOY_DIR/$WL_WAR_NAME"
ls -la "$WL_DEPLOY_DIR"
'''
}
}
stage('Start WebLogic and readiness') {
steps {
sh '''
set -eu
"$WL_HOME/startEms11.sh" # nohup & 로 백그라운드 기동, 즉시 리턴
stage('Deploy monitoring.war') { DEADLINE=$(($(date +%s) + 300))
steps { STATUS=000
sh ''' while [ "$(date +%s)" -lt "$DEADLINE" ]; do
set -eu STATUS=$(curl -sS -o /dev/null -w '%{http_code}' --max-time 3 \
DEPLOY_DIR="$CATALINA_BASE/webapps" "http://localhost:$WL_HTTP_PORT/monitoring/loginForm.do" 2>/dev/null || echo "000")
rm -rf "$DEPLOY_DIR/monitoring" "$DEPLOY_DIR/monitoring.war" case "$STATUS" in
cp build/libs/eapim-admin.war "$DEPLOY_DIR/monitoring.war" 200|302|401) echo "Readiness OK (/monitoring/loginForm.do HTTP $STATUS)"; break ;;
ls -la "$DEPLOY_DIR" esac
''' sleep 3
} done
}
stage('Start Tomcat and readiness') { case "$STATUS" in
steps { 200|302|401) ;;
sh ''' *)
set -eu echo "Readiness failed within 300s, last HTTP=$STATUS"
LOG="$CATALINA_BASE/logs/catalina.$(date +%Y-%m-%d).log" [ -f "$WL_NOHUP" ] && tail -120 "$WL_NOHUP"
BEFORE=0 exit 1
[ -f "$LOG" ] && BEFORE=$(stat -c %s "$LOG") ;;
esac
systemctl --user start eapim-admin '''
}
PROBE_URL="http://localhost:$DEPLOY_HTTP_PORT/monitoring/loginForm.do" }
DEADLINE=$(($(date +%s) + 300))
STATUS=000
while [ "$(date +%s)" -lt "$DEADLINE" ]; do
STATUS=$(curl -sS -o /dev/null -w '%{http_code}' --max-time 3 \
"$PROBE_URL" 2>/dev/null || echo "000")
case "$STATUS" in
200|302|401)
echo "Readiness OK ($PROBE_URL HTTP $STATUS)"
break
;;
esac
if [ -f "$LOG" ] && tail -c +$((BEFORE+1)) "$LOG" | grep -qE "Application listener .* Exception|SEVERE.*startup.Catalina"; then
echo "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 "Readiness probe failed within 300s, last HTTP status: $STATUS"
echo "--- last 300 lines of catalina log ---"
[ -f "$LOG" ] && tail -c +$((BEFORE+1)) "$LOG" | tail -300
echo "--- systemd unit status ---"
systemctl --user status eapim-admin --no-pager || true
echo "--- listening ports ---"
ss -tlnp 2>/dev/null | grep -E ":$DEPLOY_HTTP_PORT\\b" || echo "(port $DEPLOY_HTTP_PORT not listening)"
exit 1
;;
esac
echo "--- key boot log lines ---"
tail -c +$((BEFORE+1)) "$LOG" | grep -E "Server startup|Deployment of web" | head -10 || true
'''
} }
} }
} }
post {
success {
node('weblogic') {
sh '''
set +e
payload=$(printf '{"text":"[%s #%s](%s) SUCCESS"}' "$JOB_NAME" "$BUILD_NUMBER" "$BUILD_URL")
curl -sS --fail --max-time 5 -H 'Content-Type: application/json' -X POST --data "$payload" "$WEBHOOK_URL" >/dev/null || echo "Webhook failed"
'''
}
}
failure {
node('weblogic') {
sh '''
set +e
payload=$(printf '{"text":"[%s #%s](%s) FAILURE"}' "$JOB_NAME" "$BUILD_NUMBER" "$BUILD_URL")
curl -sS --fail --max-time 5 -H 'Content-Type: application/json' -X POST --data "$payload" "$WEBHOOK_URL" >/dev/null || echo "Webhook failed"
'''
}
}
}
} }
+1
View File
@@ -16,3 +16,4 @@ PortalPartnershipManController_피드백/개선요청관리_APIGW_UNMASK,DELETE
PortalPropertyController_포탈 프로퍼티 관리_APIGW_UPDATE PortalPropertyController_포탈 프로퍼티 관리_APIGW_UPDATE
PortalUserTermsManController_약관동의 이력_APIGW_UNMASK PortalUserTermsManController_약관동의 이력_APIGW_UNMASK
MessageRequestManController_메세지발송내역_APIGW_UPDATE_STATUS MessageRequestManController_메세지발송내역_APIGW_UPDATE_STATUS
PortalInquiryManController_Q&A문의관리_APIGW_VISIBILITY
-43
View File
@@ -1,43 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://xmlns.oracle.com/weblogic/weblogic-web-app"
xmlns="http://xmlns.oracle.com/weblogic/weblogic-web-app"
elementFormDefault="qualified">
<xs:element name="weblogic-web-app">
<xs:complexType>
<xs:all>
<xs:element name="context-root" type="xs:string" minOccurs="0"/>
<xs:element name="session-descriptor" minOccurs="0">
<xs:complexType>
<xs:all>
<xs:element name="timeout-secs" type="xs:integer" minOccurs="0"/>
<xs:element name="persistent-store-type" type="xs:string" minOccurs="0"/>
</xs:all>
</xs:complexType>
</xs:element>
<xs:element name="container-descriptor" minOccurs="0">
<xs:complexType>
<xs:all>
<xs:element name="prefer-application-packages" minOccurs="0">
<xs:complexType>
<xs:sequence>
<xs:element name="package-name" type="xs:string" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="prefer-application-resources" minOccurs="0">
<xs:complexType>
<xs:sequence>
<xs:element name="resource-name" type="xs:string" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:all>
</xs:complexType>
</xs:element>
</xs:all>
</xs:complexType>
</xs:element>
</xs:schema>
+2 -4
View File
@@ -1,12 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<weblogic-web-app <weblogic-web-app xmlns="http://xmlns.oracle.com/weblogic/weblogic-web-app">
xmlns="http://xmlns.oracle.com/weblogic/weblogic-web-app"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.oracle.com/weblogic/weblogic-web-app weblogic-web-app.xsd">
<context-root>monitoring</context-root> <context-root>monitoring</context-root>
<session-descriptor> <session-descriptor>
<timeout-secs>1800</timeout-secs> <timeout-secs>1800</timeout-secs>
<cookie-name>JSESSIONID_PORTAL</cookie-name>
<persistent-store-type>replicated_if_clustered</persistent-store-type> <persistent-store-type>replicated_if_clustered</persistent-store-type>
</session-descriptor> </session-descriptor>
+1
View File
@@ -1,6 +1,7 @@
@charset "utf-8"; @charset "utf-8";
.gnb .depth1 > li:hover > a{color:#008cc5;} /* theme */ .gnb .depth1 > li:hover > a{color:#008cc5;} /* theme */
.gnb .sitemap-link:hover{color:#008cc5;} /* theme */
.gnb .depth1 > li > .red_box{background:#008cc5;}/* theme */ .gnb .depth1 > li > .red_box{background:#008cc5;}/* theme */
.gnb .depth2 > li > a:hover{color:#008cc5;}/* theme */ .gnb .depth2 > li > a:hover{color:#008cc5;}/* theme */
+1
View File
@@ -1,6 +1,7 @@
@charset "utf-8"; @charset "utf-8";
.gnb .depth1 > li:hover > a{color:#16a085;} /* theme */ .gnb .depth1 > li:hover > a{color:#16a085;} /* theme */
.gnb .sitemap-link:hover{color:#16a085;} /* theme */
.gnb .depth1 > li > .red_box{background:#16a085;}/* theme */ .gnb .depth1 > li > .red_box{background:#16a085;}/* theme */
.gnb .depth2 > li > a:hover{color:#16a085;}/* theme */ .gnb .depth2 > li > a:hover{color:#16a085;}/* theme */
+1
View File
@@ -1,6 +1,7 @@
@charset "utf-8"; @charset "utf-8";
.gnb .depth1 > li:hover > a{color:#e74c3c;} /* theme */ .gnb .depth1 > li:hover > a{color:#e74c3c;} /* theme */
.gnb .sitemap-link:hover{color:#e74c3c;} /* theme */
.gnb .depth1 > li > .red_box{background:#e74c3c;}/* theme */ .gnb .depth1 > li > .red_box{background:#e74c3c;}/* theme */
.gnb .depth2 > li > a:hover{color:#e74c3c;}/* theme */ .gnb .depth2 > li > a:hover{color:#e74c3c;}/* theme */
+1
View File
@@ -1,6 +1,7 @@
@charset "utf-8"; @charset "utf-8";
.gnb .depth1 > li:hover > a{color:#ff80c0;} /* theme */ .gnb .depth1 > li:hover > a{color:#ff80c0;} /* theme */
.gnb .sitemap-link:hover{color:#ff80c0;} /* theme */
.gnb .depth1 > li > .red_box{background:#ff80c0;}/* theme */ .gnb .depth1 > li > .red_box{background:#ff80c0;}/* theme */
.gnb .depth2 > li > a:hover{color:#ff80c0;}/* theme */ .gnb .depth2 > li > a:hover{color:#ff80c0;}/* theme */
+1
View File
@@ -1,6 +1,7 @@
@charset "utf-8"; @charset "utf-8";
.gnb .depth1 > li:hover > a{color:#be8200;} /* theme */ .gnb .depth1 > li:hover > a{color:#be8200;} /* theme */
.gnb .sitemap-link:hover{color:#be8200;} /* theme */
.gnb .depth1 > li > .red_box{background:#be8200;}/* theme */ .gnb .depth1 > li > .red_box{background:#be8200;}/* theme */
.gnb .depth2 > li > a:hover{color:#be8200;}/* theme */ .gnb .depth2 > li > a:hover{color:#be8200;}/* theme */
+9 -9
View File
@@ -62,8 +62,8 @@ header.sub{position:relative; padding-top:80px;}
100% { color:rgba(255,96,78,0); } 100% { color:rgba(255,96,78,0); }
} */ } */
.gnb_bg{position:absolute; top:80px; left:0; display:none; width:100%; height:382px; box-sizing:border-box; background:white; box-shadow: 0px 5px 5px 0px rgba(0,0,0,0.2);} .gnb_bg{position:absolute; top:80px; left:0; display:none; width:100%; height:282px; box-sizing:border-box; background:white; box-shadow: 0px 5px 5px 0px rgba(0,0,0,0.2);}
.gnb{position:absolute; top:0px; left:230px; display:inline-block; height:80px; overflow-y:hidden;} .gnb{position:absolute; top:0px; left:180px; display:inline-block; height:80px; overflow-y:hidden;}
.gnb:hover{height:auto; overflow-y:visible;} .gnb:hover{height:auto; overflow-y:visible;}
.gnb:hover + .gnb_bg{display:block;} .gnb:hover + .gnb_bg{display:block;}
.gnb h1{float:left; display:block; width:auto; height:100%; line-height:80px;} .gnb h1{float:left; display:block; width:auto; height:100%; line-height:80px;}
@@ -71,15 +71,15 @@ header.sub{position:relative; padding-top:80px;}
.gnb .depth1 > li{position:relative;} .gnb .depth1 > li{position:relative;}
.gnb .depth1 > li > a{position:relative; display:block; box-sizing:border-box; color:#000; font-size:14px; width:160px; height:80px; text-align:center; line-height:80px; letter-spacing:-0.5px; opacity:0.7; transition:all 0.3s;} .gnb .depth1 > li > a{position:relative; display:block; box-sizing:border-box; color:#000; font-size:14px; width:160px; height:80px; text-align:center; line-height:80px; letter-spacing:-0.5px; opacity:0.7; transition:all 0.3s;}
.gnb .depth1 > li > a:after{content:''; position:absolute; top:34px; right:0; display:inline-block; width:1px; height:13px; background:#ddd;} .gnb .depth1 > li > a:after{content:''; position:absolute; top:34px; right:0; display:inline-block; width:1px; height:13px; }
.gnb .depth1 > li:first-child > a:before{content:''; position:absolute; top:34px; left:0; display:inline-block; width:1px; height:13px; background:#ddd;} .gnb .depth1 > li:first-child > a:before{content:''; position:absolute; top:34px; left:0; display:inline-block; width:1px; height:13px; }
/* /*
.gnb .depth1 > li:hover .red_box{transform:scaleX(1);} .gnb .depth1 > li:hover .red_box{transform:scaleX(1);}
.gnb .depth1 > li:hover > a{opacity:1;} .gnb .depth1 > li:hover > a{opacity:1;}
.gnb .depth1 > li > .red_box{display:block; position:absolute; width:100%; height:2px; top:78px; box-sizing:border-box; background:#ed1c24; transform-origin:center center; transform:scaleX(0); transition:all 0.3s;} .gnb .depth1 > li > .red_box{display:block; position:absolute; width:100%; height:2px; top:78px; box-sizing:border-box; background:#ed1c24; transform-origin:center center; transform:scaleX(0); transition:all 0.3s;}
*/ */
.gnb .depth2{position:absolute; left:0; top:80px; width:160px; height:410px; box-sizing:border-box; padding:20px 0; border-right:1px solid #eee; text-align:center; z-index:9;} .gnb .depth2{position:absolute; left:0; top:80px; width:160px; height:280px; box-sizing:border-box; padding:20px 0; border-right:1px solid #eee; text-align:center; z-index:9;}
.gnb .depth2.first{border-left:1px solid #eee;} .gnb .depth1 > li:first-child .depth2{border-left:1px solid #eee;}
.gnb .depth2 > li > a{display:block; color:#000; font-size:13px; line-height:30px;} .gnb .depth2 > li > a{display:block; color:#000; font-size:13px; line-height:30px;}
.gnb .depth2 > li > a:hover{color:#ff0000;} .gnb .depth2 > li > a:hover{color:#ff0000;}
@@ -94,9 +94,9 @@ header.sub{position:relative; padding-top:80px;}
.left_box .depth3 > li:hover > a, .left_box .depth3 > li.on > a{color:#ed1c24;} .left_box .depth3 > li:hover > a, .left_box .depth3 > li.on > a{color:#ed1c24;}
.content_top{position:fixed; top:0; left:0; padding:0 30px; width:100%; height:50px; line-height:50px; background:#eee; border-top:1px solid #ddd; border-left:1px solid #ddd; z-index:100;} .content_top{position:fixed; top:0; left:0; padding:0 30px; width:100%; height:50px; line-height:50px; background:#eee; border-top:1px solid #ddd; border-left:1px solid #ddd; z-index:100;}
.content_top .path{} .content_top .path{margin:0; padding:0;}
.content_top .path > li{display:inline-block;} .content_top .path > li{display:inline-block; margin:0; padding:0;}
.content_top .path > li a{display:block; height:50px; line-height:50px; padding-left:10px; font-size:12px; color:#999;} .content_top .path > li a{display:block; height:50px; line-height:50px; padding-left:10px; font-size:12px; color:#999; text-decoration:none;}
.content_top .path > li a:hover{color:#666;} .content_top .path > li a:hover{color:#666;}
.content_top .path > li a:before{content:'>'; margin-right:10px;} .content_top .path > li a:before{content:'>'; margin-right:10px;}
.content_top .path > li:first-child a:before{content:'';} .content_top .path > li:first-child a:before{content:'';}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,334 @@
/**
* Editor Content Styles (editor-content.css)
*
* Summernote 에디터로 작성된 HTML 콘텐츠의 공통 스타일
* 관리자포탈/개발자포탈 양쪽에서 동일하게 사용
*
* 사용법:
* - 관리자포탈: Summernote .note-editable에 .editor-content 클래스 추가
* - 개발자포탈: 콘텐츠 wrapper에 .editor-content 클래스 추가
*
* @version 1.0.0
* @date 2025-12-30
*/
/* ==========================================================================
CSS Reset + 기본 설정
========================================================================== */
.editor-content {
all: revert;
font-family: 'Noto Sans KR', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif !important;
font-size: 16px !important;
font-weight: 400 !important;
line-height: 1.8 !important;
color: #1A1A2E !important;
word-wrap: break-word;
text-align: left !important;
}
/* ==========================================================================
헤딩 (Headings)
========================================================================== */
.editor-content h1 {
font-size: 20px !important;
font-weight: 700 !important;
margin: 32px 0 16px !important;
line-height: 1.4 !important;
color: #1A1A2E !important;
}
.editor-content h1:first-child {
margin-top: 0 !important;
}
.editor-content h2 {
font-size: 18px !important;
font-weight: 700 !important;
margin: 24px 0 8px !important;
line-height: 1.4 !important;
color: #1A1A2E !important;
}
.editor-content h3 {
font-size: 16px !important;
font-weight: 600 !important;
margin: 16px 0 8px !important;
line-height: 1.4 !important;
color: #1A1A2E !important;
}
.editor-content h4,
.editor-content h5,
.editor-content h6 {
font-size: 16px !important;
font-weight: 600 !important;
margin: 16px 0 8px !important;
line-height: 1.4 !important;
color: #1A1A2E !important;
}
/* ==========================================================================
단락 (Paragraphs)
========================================================================== */
.editor-content p {
margin-bottom: 16px !important;
line-height: 1.8 !important;
font-size: 16px !important;
}
.editor-content p:last-child {
margin-bottom: 0 !important;
}
/* ==========================================================================
리스트 (Lists) - 글로벌 reset 대응
========================================================================== */
.editor-content ul {
list-style-type: disc !important;
padding-left: 32px !important;
margin: 16px 0 !important;
}
.editor-content ol {
list-style-type: decimal !important;
padding-left: 32px !important;
margin: 16px 0 !important;
}
.editor-content li {
margin-bottom: 8px !important;
line-height: 1.8 !important;
font-size: 16px !important;
}
.editor-content li:last-child {
margin-bottom: 0 !important;
}
/* 중첩 리스트 */
.editor-content ul ul {
list-style-type: circle !important;
margin: 8px 0 !important;
}
.editor-content ul ul ul {
list-style-type: square !important;
}
.editor-content ol ol {
list-style-type: lower-alpha !important;
margin: 8px 0 !important;
}
.editor-content ol ol ol {
list-style-type: lower-roman !important;
}
/* ==========================================================================
테이블 (Tables)
========================================================================== */
.editor-content table {
width: 100% !important;
border-collapse: collapse !important;
margin: 24px 0 !important;
border: 1px solid #E2E8F0 !important;
border-radius: 8px !important;
overflow: hidden !important;
font-size: 14px !important;
}
.editor-content th,
.editor-content td {
border: 1px solid #E2E8F0 !important;
padding: 8px 16px !important;
text-align: left !important;
vertical-align: top !important;
}
.editor-content th {
background: #EFF6FF !important;
font-weight: 600 !important;
color: #1A1A2E !important;
}
.editor-content tr:hover {
background: #F8FAFC !important;
}
/* ==========================================================================
링크 (Links)
========================================================================== */
.editor-content a {
color: #0049b4 !important;
text-decoration: underline !important;
transition: color 0.3s ease !important;
}
.editor-content a:hover {
color: #003080 !important;
}
/* ==========================================================================
인용문 (Blockquote)
========================================================================== */
.editor-content blockquote {
border-left: 4px solid #0049b4 !important;
padding: 16px 24px !important;
margin: 24px 0 !important;
background: #F8FAFC !important;
font-style: italic !important;
color: #64748B !important;
border-radius: 0 8px 8px 0 !important;
}
.editor-content blockquote p {
margin-bottom: 0 !important;
}
/* ==========================================================================
이미지 (Images)
========================================================================== */
.editor-content img {
max-width: 100% !important;
height: auto !important;
border-radius: 8px !important;
margin: 16px 0 !important;
display: block !important;
}
/* ==========================================================================
코드 (Code)
========================================================================== */
.editor-content code {
background: #F8FAFC !important;
padding: 2px 6px !important;
border-radius: 4px !important;
font-family: 'Fira Code', 'Courier New', monospace !important;
font-size: 0.9em !important;
color: #0049b4 !important;
}
.editor-content pre {
background: #F8FAFC !important;
border: 1px solid #E2E8F0 !important;
border-radius: 8px !important;
padding: 16px !important;
overflow-x: auto !important;
margin: 16px 0 !important;
}
.editor-content pre code {
background: transparent !important;
padding: 0 !important;
color: #1A1A2E !important;
}
/* ==========================================================================
구분선 (Horizontal Rule)
========================================================================== */
.editor-content hr {
border: none !important;
border-top: 1px solid #E2E8F0 !important;
margin: 32px 0 !important;
}
/* ==========================================================================
텍스트 강조 (Text Emphasis)
========================================================================== */
.editor-content strong,
.editor-content b {
font-weight: 700 !important;
}
.editor-content em,
.editor-content i {
font-style: italic !important;
}
.editor-content u {
text-decoration: underline !important;
}
.editor-content s,
.editor-content strike,
.editor-content del {
text-decoration: line-through !important;
}
.editor-content mark {
background-color: #FEF3C7 !important;
padding: 0 2px !important;
}
.editor-content sub {
vertical-align: sub !important;
font-size: 0.8em !important;
}
.editor-content sup {
vertical-align: super !important;
font-size: 0.8em !important;
}
/* ==========================================================================
반응형 (Responsive)
========================================================================== */
@media (max-width: 768px) {
.editor-content {
font-size: 14px !important;
}
.editor-content h1 {
font-size: 18px !important;
}
.editor-content h2 {
font-size: 16px !important;
}
.editor-content h3,
.editor-content h4,
.editor-content h5,
.editor-content h6 {
font-size: 14px !important;
}
.editor-content p,
.editor-content li {
font-size: 14px !important;
}
.editor-content table {
font-size: 12px !important;
}
.editor-content th,
.editor-content td {
padding: 4px 8px !important;
}
.editor-content ul,
.editor-content ol {
padding-left: 24px !important;
}
.editor-content blockquote {
padding: 8px 16px !important;
}
.editor-content pre {
padding: 8px !important;
}
}
+75
View File
@@ -0,0 +1,75 @@
// OpenAPI 에디터 초기 상태 골격(빈 껍데기).
// - specToData() 가 deepClone 하여 서버 spec 값으로 덮어쓰는 구조 기준값.
// - 실제 데모/샘플 값은 두지 않는다(팝업은 항상 서버 spec 로드 후 렌더).
// - docOptions 만 기능 기본값으로 유지(문서 미리보기 옵션).
// locked:true = 게이트웨이 자동 주입(회색+자물쇠) / locked:false = 사용자 편집.
window.SAMPLE_DATA = (function () {
function f() { return { value: '', locked: false }; }
return {
info: {
title: f(),
version: f(),
summary: f(),
description: f(),
termsOfService: f(),
contact: { name: f(), email: f(), url: f() },
license: { name: f(), url: f() }
},
tags: [],
externalDocs: { description: f(), url: f() },
servers: [],
serverVariables: [],
operation: {
method: f(),
path: f(),
operationId: f(),
summary: f(),
description: f(),
tags: { value: [], locked: false },
deprecated: { value: false, locked: false }
},
// 파라미터 (Path/Query/Header/Cookie)
parameters: [],
// Request Body 스키마
requestBody: {
mediaType: 'application/json',
required: { value: false, locked: false },
schema: []
},
// 응답 스키마 (상태코드 별)
responses: {
'200': { description: f(), schema: [] }
},
// 보안 스킴
securitySchemes: [],
globalSecurity: [],
// 예제
examples: {
request: {},
response: {}
},
// 문서 옵션
docOptions: {
theme: 'light',
lang: 'ko',
includeExamples: true,
tryItOut: true,
defaultExpandDepth: -1,
layout: 'BaseLayout',
tagFilter: [],
sideToc: true
}
};
})();
+103
View File
@@ -0,0 +1,103 @@
/* DJB OpenAPI Editor POC — Tailwind 보강 커스텀 */
html, body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Apple SD Gothic Neo",
"Malgun Gothic", "Noto Sans KR", system-ui, sans-serif;
}
/* === 잠금 행/카드 좌측 띠 === */
.locked-row td:first-child {
position: relative;
}
.locked-row td:first-child::before {
content: '';
position: absolute;
left: 0;
top: 6px;
bottom: 6px;
width: 3px;
background: #cbd5e1;
border-radius: 2px;
}
.locked-card {
position: relative;
background: linear-gradient(to right, #f8fafc 0%, #ffffff 4px) #ffffff;
}
.locked-card::before {
content: '';
position: absolute;
left: 0; top: 8px; bottom: 8px;
width: 3px;
background: #cbd5e1;
border-radius: 0 2px 2px 0;
}
/* === 스키마 테이블 — 가독성 보조 === */
.schema-table th { white-space: nowrap; }
.schema-table tbody tr:hover { background: #fafbfc; }
.schema-table input,
.schema-table select {
font-size: 12.5px;
}
.schema-table input[type="text"] {
padding-top: 4px;
padding-bottom: 4px;
}
/* === 스텝퍼 항목 hover === */
.step-item:hover { background: #f8fafc; }
/* === 미리보기 패널: Swagger UI 크기 미세조정 === */
#preview-swagger .swagger-ui {
font-size: 13px;
}
#preview-swagger .swagger-ui .info { margin: 18px 0; }
#preview-swagger .swagger-ui .info .title { font-size: 22px; }
#preview-swagger .swagger-ui .scheme-container { padding: 12px 0; box-shadow: none; }
#preview-swagger .swagger-ui .opblock { margin: 0 0 12px 0; }
/* === 다크 테마 (POC 시뮬레이션 — Swagger 자체 다크는 미지원이라 컨테이너만) === */
body.theme-dark #preview-swagger { background: #1e293b; }
/* === 토스트 애니메이션 === */
#toast { transition: opacity 0.2s; }
/* === Monaco 컨테이너가 hidden 일 때 layout 깨짐 방지 === */
#preview-monaco {
overflow: hidden;
}
/* === Stepper 6단 grid 셀 좌우 라인 (마지막 셀 제외) === */
#stepper li {
position: relative;
}
#stepper li:not(:last-child)::after {
content: '';
position: absolute;
top: 50%;
right: -8px;
width: 16px;
height: 1px;
background: #e2e8f0;
}
/* === 보안 스킴 카드 — 잠금 표시 === */
.locked-card {
background: #fafbfc;
}
/* === 칩(태그) 미세 조정 === */
.tag-chip { transition: background 0.15s; }
/* === Export menu fade === */
#export-menu { animation: fadeIn 0.12s ease-out; }
@keyframes fadeIn {
from { opacity: 0; transform: translateY(-4px); }
to { opacity: 1; transform: translateY(0); }
}
/* === 1600px 기준 폼 패널 내부 가로 여유 확보 === */
#form-content { max-width: 100%; }
/* === readonly input 위에서 텍스트 커서 안 보이게 === */
input[readonly] { user-select: text; }
@@ -0,0 +1,120 @@
<%@ page language="java" contentType="text/html; charset=utf-8"%>
<%@ page import="java.util.List"%>
<%@ page import="com.eactive.eai.rms.common.acl.sitemap.ui.SitemapNode"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%!
private void printNodeLabel(javax.servlet.jsp.JspWriter out, SitemapNode node) throws java.io.IOException {
String url = node.getMenuUrl();
boolean hasLink = url != null && !url.trim().isEmpty() && !"NAN".equalsIgnoreCase(url.trim());
if (hasLink) {
out.print("<a href=\"javascript:void(0);\" class=\"sitemap-name\" onclick=\"goPage('"
+ escapeJs(url) + "','" + escapeJs(node.getMenuId()) + "');return false;\">"
+ escapeHtml(node.getMenuName()) + "</a>");
} else {
out.print("<span class=\"sitemap-name\">" + escapeHtml(node.getMenuName()) + "</span>");
}
}
private void printSitemapNode(javax.servlet.jsp.JspWriter out, SitemapNode node) throws java.io.IOException {
out.print("<li>");
printNodeLabel(out, node);
List<SitemapNode> children = node.getChildren();
if (children != null && !children.isEmpty()) {
out.print("<ul>");
for (SitemapNode child : children) {
printSitemapNode(out, child);
}
out.print("</ul>");
}
out.print("</li>");
}
private String escapeHtml(String value) {
if (value == null) {
return "";
}
return value.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;").replace("\"", "&quot;");
}
private String escapeJs(String value) {
if (value == null) {
return "";
}
return value.replace("\\", "\\\\").replace("'", "\\'");
}
%>
<%
response.setHeader("Pragma", "No-cache");
response.setHeader("Cache-Control", "no-cache");
response.setHeader("Expires", "0");
List<SitemapNode> sitemapTree = (List<SitemapNode>) request.getAttribute("sitemapTree");
%>
<html>
<head>
<title></title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<jsp:include page="/jsp/common/include/css.jsp"/>
<jsp:include page="/jsp/common/include/script.jsp"/>
<style>
.sitemap-columns { display: flex; flex-wrap: wrap; gap: 40px; }
.sitemap-column { min-width: 180px; }
.sitemap-category { font-size: 1.1em; font-weight: bold; padding-bottom: 6px; margin-bottom: 8px; border-bottom: 2px solid #ccc; }
.sitemap-tree, .sitemap-tree ul { list-style-type: disc; margin: 0; padding-left: 18px; }
.sitemap-tree { padding-left: 0; list-style-type: none; }
.sitemap-tree li { line-height: 1.8; }
a.sitemap-name { text-decoration: none; cursor: pointer; }
a.sitemap-name:hover { text-decoration: underline; }
</style>
<script language="javascript">
function goPage(url, menuId) {
var serviceType = sessionStorage["serviceType"];
var pageUrl = url;
pageUrl += (pageUrl.indexOf("?") > -1 ? "&" : "?") + "menuId=" + menuId;
pageUrl += "&serviceType=" + serviceType;
parent.leftFrame.location.href = '<c:url value="/leftMenu.do"/>?menuId=' + menuId + '&serviceType=' + serviceType;
location.href = pageUrl;
}
</script>
</head>
<body>
<div class="right_box">
<div class="content_top">
<ul class="path">
<li><a href="#">${rmsMenuPath}</a></li>
</ul>
</div><!-- end content_top -->
<div class="content_middle">
<!-- <div class="title">사이트맵</div> -->
<div class="sitemap-columns">
<%
for (SitemapNode category : sitemapTree) {
%>
<div class="sitemap-column">
<div class="sitemap-category">
<%
printNodeLabel(out, category);
%>
</div>
<ul class="sitemap-tree">
<%
List<SitemapNode> children = category.getChildren();
if (children != null) {
for (SitemapNode child : children) {
printSitemapNode(out, child);
}
}
%>
</ul>
</div>
<%
}
%>
</div>
</div><!-- end content_middle -->
</div><!-- end right_box -->
</body>
</html>
+27 -5
View File
@@ -9,6 +9,8 @@
<%@page import="com.eactive.eai.rms.common.util.StringUtils" %> <%@page import="com.eactive.eai.rms.common.util.StringUtils" %>
<%@page import="com.eactive.eai.rms.common.datasource.DataSourceType" %> <%@page import="com.eactive.eai.rms.common.datasource.DataSourceType" %>
<%@page import="com.eactive.eai.rms.common.datasource.DataSourceTypeManager" %> <%@page import="com.eactive.eai.rms.common.datasource.DataSourceTypeManager" %>
<%@page import="com.eactive.eai.common.util.ApplicationContextProvider" %>
<%@page import="com.eactive.eai.rms.data.entity.man.role.RoleMenuAuthService" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %> <%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %>
<%@ include file="/jsp/common/include/localemessage.jsp" %> <%@ include file="/jsp/common/include/localemessage.jsp" %>
@@ -16,6 +18,12 @@
<% <%
String serviceKey = SessionManager.getServiceTypeKey(request); String serviceKey = SessionManager.getServiceTypeKey(request);
String serviceText = DataSourceTypeManager.getDataSourceType(serviceKey).getText(); String serviceText = DataSourceTypeManager.getDataSourceType(serviceKey).getText();
final String SITEMAP_MENU_ID = "0510013";
RoleMenuAuthService roleMenuAuthService = ApplicationContextProvider.getContext().getBean(RoleMenuAuthService.class);
String sitemapMenuAuth = roleMenuAuthService.getRoleList(SessionManager.getUserId(request), SITEMAP_MENU_ID, serviceKey);
boolean showSitemapLink = "R".equals(sitemapMenuAuth) || "W".equals(sitemapMenuAuth);
String roleScreenId = (String) request.getAttribute("roleScreenId"); String roleScreenId = (String) request.getAttribute("roleScreenId");
String mainPage = (String) request.getAttribute("mainPage"); String mainPage = (String) request.getAttribute("mainPage");
String menuId = (String) request.getAttribute("menuId"); String menuId = (String) request.getAttribute("menuId");
@@ -240,6 +248,12 @@
<link rel="stylesheet" type="text/css" media="screen" href="<c:url value="/css/web_ui.css"/>"/> <link rel="stylesheet" type="text/css" media="screen" href="<c:url value="/css/web_ui.css"/>"/>
<link rel="stylesheet" type="text/css" media="screen" href="<c:url value="/css/theme_${themeColor}.css"/>"/> <link rel="stylesheet" type="text/css" media="screen" href="<c:url value="/css/theme_${themeColor}.css"/>"/>
<style>
.topmenu_box .sitemap-link { display: inline-block; width: 80px; height: 80px; line-height: 80px; box-sizing: border-box; font-size: 14px; color: #666; text-align: center; opacity: 0.7; transition: all 0.3s; margin-top: 0px; }
.topmenu_box .sitemap-link:hover { opacity: 1; }
.topmenu_box .logout-link { line-height: 80px; margin-top:0; }
.topmenu_box > ul li:last-child > a:before { display: none; }
</style>
<script language="javascript" src="<c:url value="/js/jquery-1.12.1.min.js"/>"></script> <script language="javascript" src="<c:url value="/js/jquery-1.12.1.min.js"/>"></script>
<script language="javascript" src="<c:url value="/js/prefixfree.min.js"/>"></script> <script language="javascript" src="<c:url value="/js/prefixfree.min.js"/>"></script>
<script language="javascript" src="<c:url value="/js/jquery.cookie.js"/>"></script> <script language="javascript" src="<c:url value="/js/jquery.cookie.js"/>"></script>
@@ -303,11 +317,11 @@
} }
//왼쪽메뉴, 메인 페이지 이동 //왼쪽메뉴, 메인 페이지 이동
function goPage2(page1, page2, page2_id) { function goPage2(page1, page2, page2_id) {
parent.leftFrame.location.href = goNavUrl(page1); parent.leftFrame.location.href = goNavUrl(page1);
parent.mainFrame.location.href = goNavUrl(page2); parent.mainFrame.location.href = goNavUrl(page2);
} }
var sessionAjax = createAjaxRequest(); var sessionAjax = createAjaxRequest();
function getAjaxData() { function getAjaxData() {
@@ -529,6 +543,7 @@
console.log("selectLocaleValue : " + selectLocaleValue); console.log("selectLocaleValue : " + selectLocaleValue);
parent.changeLocale($(this).val()); parent.changeLocale($(this).val());
}); });
}); });
$(function() { $(function() {
@@ -559,13 +574,20 @@
<span style="display:block;font-size:10px;"><%=localeMessage.getString("screen.lastLogin") %> : <%=LastLoginYms%> [ <%=LastLoginIp%> ]</span> <span style="display:block;font-size:10px;"><%=localeMessage.getString("screen.lastLogin") %> : <%=LastLoginYms%> [ <%=LastLoginIp%> ]</span>
<% } %> <% } %>
</a></li> </a></li>
<li> <li style="width:100px; line-height:80px; text-align:center;">
<select id="selectLocale" name="locale" style="margin-top: 20px; width:80px"> <select id="selectLocale" name="locale" style="width:60px; padding: 2px; text-align:center;">
<option value="en">English</option> <option value="en">English</option>
<option value="ko">한글</option> <option value="ko">한글</option>
</select> </select>
</li> </li>
<li onclick="logout()"><a href="#" class=""><img src="<c:url value="/img/icon_logout.png"/>" <% if (showSitemapLink) { %>
<li>
<a href="<c:url value="/common/acl/sitemap/sitemapMan.view"><c:param name="menuId" value="<%=SITEMAP_MENU_ID%>"/><c:param name="serviceType" value="<%=serviceKey%>"/></c:url>" target="mainFrame" class="sitemap-link">
<%= localeMessage.getString("screen.sitemap") %>
</a>
</li>
<% } %>
<li onclick="logout()"><a href="#" class="logout-link"><img src="<c:url value="/img/icon_logout.png"/>"
alt=""/><%= localeMessage.getString("screen.logout") %> alt=""/><%= localeMessage.getString("screen.logout") %>
</a></li> </a></li>
</ul> </ul>
@@ -19,7 +19,6 @@
var url = '<c:url value="/onl/admin/authserver/scopeMan.json" />'; var url = '<c:url value="/onl/admin/authserver/scopeMan.json" />';
var url_view = '<c:url value="/onl/admin/authserver/scopeMan.view" />'; var url_view = '<c:url value="/onl/admin/authserver/scopeMan.view" />';
var api_url_view = '<c:url value="/onl/apim/apigroup/apiGroupMan.view" />'; var api_url_view = '<c:url value="/onl/apim/apigroup/apiGroupMan.view" />';
var mapping_url = '<c:url value="/onl/admin/authserver/apiScopeMan.json" />';
var isDetail = false; var isDetail = false;
function isValid(){ function isValid(){
@@ -65,9 +64,9 @@ function mappingInfo(key){
$.ajax({ $.ajax({
type : "POST", type : "POST",
url: mapping_url, url: url,
dataType: "json", dataType: "json",
data: {cmd: 'LIST', searchScopeId : key}, data: {cmd: 'API_LIST', searchScopeId : key},
success: function(json){ success: function(json){
var mappingData = json.rows; var mappingData = json.rows;
@@ -131,45 +130,6 @@ function init(key, callback){
} }
} }
function saveApiGridData(scopeId, returnUrl) {
var postData = [];
var rows = $("#selectedApiGrid").jqGrid('getRowData');
$.each(rows, function(index, item) {
item.scopeId = scopeId;
});
postData.push({
name: "apiList",
value: JSON.stringify(rows)
});
postData.push({ name: "cmd" , value:"INSERT_APILIST" });
postData.push({ name: "scopeId" , value:scopeId });
$.ajax({
type : "POST",
url:mapping_url,
data: postData,
beforeSend: function() {
$("[id^='btn_']").prop("disabled", true);
$("#btn_modify").text("처리중 . . . . .");
},
success:function(args){
alert("저장 되었습니다.");
goNav(returnUrl);//LIST로 이동
},
error:function(e){
alert(e.responseText);
},
complete: function() {
$("[id^='btn_']").prop("disabled", false);
$("#btn_modify").text("수정");
}
});
}
$(document).ready(function() { $(document).ready(function() {
var returnUrl = getReturnUrlForReturn(); var returnUrl = getReturnUrlForReturn();
var key ="${param.scopeId}"; var key ="${param.scopeId}";
@@ -182,28 +142,39 @@ $(document).ready(function() {
if (!isValid()){ if (!isValid()){
return; return;
} }
var scopeId = $('input[name=scopeId]').val();
var postData = $('#ajaxForm').serializeArray(); var postData = $('#ajaxForm').serializeArray();
var rows = $("#selectedApiGrid").jqGrid('getRowData');
$.each(rows, function(index, item) {
item.scopeId = scopeId;
});
postData.push({ name: "apiList" , value: JSON.stringify(rows) });
if (isDetail){ if (isDetail){
postData.push({ name: "cmd" , value:"UPDATE"}); postData.push({ name: "cmd" , value:"UPDATE"});
}else{ }else{
postData.push({ name: "cmd" , value:"INSERT"}); postData.push({ name: "cmd" , value:"INSERT"});
} }
$.ajax({ $.ajax({
type : "POST", type : "POST",
url:url, url:url,
data:postData, data:postData,
beforeSend: function() {
$("[id^='btn_']").prop("disabled", true);
$("#btn_modify").text("처리중 . . . . .");
},
success:function(args){ success:function(args){
alert("SCOPE 정보가 저장 되었습니다. 이어서 API 리스트를 저장합니다."); alert("저장 되었습니다.");
goNav(returnUrl);//LIST로 이동
if(!isDetail) { // INSERT 일 경우 ScopeId를 업데이트
key = $('input[name=scopeId]').val();
}
saveApiGridData(key, returnUrl); // API LIST 저장.
}, },
error:function(e){ error:function(e){
alert(e.responseText); alert(e.responseText);
},
complete: function() {
$("[id^='btn_']").prop("disabled", false);
$("#btn_modify").text("수정");
} }
}); });
}); });
@@ -25,7 +25,7 @@ $(document).ready(function() {
datatype:"json", datatype:"json",
mtype: 'POST', mtype: 'POST',
url: url, url: url,
postData : { cmd : 'LIST', searchName: $('input[name=searchName]').val()}, postData : { cmd : 'LIST', searchName: $('input[name=searchName]').val(), searchUseYn: $('select[name=searchUseYn]').val()},
colNames:['<%= localeMessage.getString("infAdpConMan.adaNm") %>', colNames:['<%= localeMessage.getString("infAdpConMan.adaNm") %>',
'<%= localeMessage.getString("infAdpConMan.adaDes") %>', '<%= localeMessage.getString("infAdpConMan.adaDes") %>',
'<%= localeMessage.getString("infAdpConMan.thrPerSecond") %>', '<%= localeMessage.getString("infAdpConMan.thrPerSecond") %>',
@@ -62,6 +62,7 @@ $(document).ready(function() {
url2 += '&menuId='+'${param.menuId}'; url2 += '&menuId='+'${param.menuId}';
//검색값 //검색값
url2 += '&searchName='+$("input[name=searchName]").val(); url2 += '&searchName='+$("input[name=searchName]").val();
url2 += '&searchUseYn='+$("select[name=searchUseYn]").val();
//key값 //key값
url2 += '&name='+name; url2 += '&name='+name;
goNav(url2); goNav(url2);
@@ -80,7 +81,7 @@ $(document).ready(function() {
resizeJqGridWidth('grid','content_middle','1000'); resizeJqGridWidth('grid','content_middle','1000');
$("#btn_search").click(function(){ $("#btn_search").click(function(){
$("#grid").setGridParam({ postData: { searchName: $('input[name=searchName]').val()}, page:1 }).trigger("reloadGrid"); $("#grid").setGridParam({ postData: { searchName: $('input[name=searchName]').val(), searchUseYn: $('select[name=searchUseYn]').val()}, page:1 }).trigger("reloadGrid");
}); });
$("#btn_new").click(function(){ $("#btn_new").click(function(){
var url2 = url_view; var url2 = url_view;
@@ -126,6 +127,16 @@ $(document).ready(function() {
<tr> <tr>
<th style="width:180px;"><%= localeMessage.getString("infAdpConMan.adaNm") %></th> <th style="width:180px;"><%= localeMessage.getString("infAdpConMan.adaNm") %></th>
<td><input type="text" name="searchName" value="${param.searchName}"></td> <td><input type="text" name="searchName" value="${param.searchName}"></td>
<th style="width:180px;"><%= localeMessage.getString("infAdpConMan.useYn") %></th>
<td>
<div class="select-style">
<select name="searchUseYn">
<option value="">전체</option>
<option value="1">사용함</option>
<option value="0">사용안함</option>
</select>
</div>
</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@@ -25,7 +25,7 @@ $(document).ready(function() {
datatype:"json", datatype:"json",
mtype: 'POST', mtype: 'POST',
url: url, url: url,
postData : { cmd : 'LIST', searchName: $('input[name=searchName]').val()}, postData : { cmd : 'LIST', searchName: $('input[name=searchName]').val(), searchUseYn: $('select[name=searchUseYn]').val()},
colNames:['클라이언트 ID', colNames:['클라이언트 ID',
'클라이언트명', '클라이언트명',
'<%= localeMessage.getString("infAdpConMan.thrPerSecond") %>', '<%= localeMessage.getString("infAdpConMan.thrPerSecond") %>',
@@ -62,6 +62,7 @@ $(document).ready(function() {
url2 += '&menuId='+'${param.menuId}'; url2 += '&menuId='+'${param.menuId}';
//검색값 //검색값
url2 += '&searchName='+$("input[name=searchName]").val(); url2 += '&searchName='+$("input[name=searchName]").val();
url2 += '&searchUseYn='+$("select[name=searchUseYn]").val();
//key값 //key값
url2 += '&name='+name; url2 += '&name='+name;
goNav(url2); goNav(url2);
@@ -80,7 +81,7 @@ $(document).ready(function() {
resizeJqGridWidth('grid','content_middle','1000'); resizeJqGridWidth('grid','content_middle','1000');
$("#btn_search").click(function(){ $("#btn_search").click(function(){
$("#grid").setGridParam({ postData: { searchName: $('input[name=searchName]').val()}, page:1 }).trigger("reloadGrid"); $("#grid").setGridParam({ postData: { searchName: $('input[name=searchName]').val(), searchUseYn: $('select[name=searchUseYn]').val()}, page:1 }).trigger("reloadGrid");
}); });
$("#btn_new").click(function(){ $("#btn_new").click(function(){
var url2 = url_view; var url2 = url_view;
@@ -90,7 +91,8 @@ $(document).ready(function() {
url2 += '&menuId='+'${param.menuId}'; url2 += '&menuId='+'${param.menuId}';
//검색값 //검색값
url2 += '&searchName='; url2 += '&searchName=';
url2 += '&searchUseYn='+$('select[name=searchUseYn]').val();
goNav(url2); goNav(url2);
}); });
@@ -124,6 +126,16 @@ $(document).ready(function() {
<tr> <tr>
<th style="width:180px;">클라이언트명</th> <th style="width:180px;">클라이언트명</th>
<td><input type="text" name="searchName" value="${param.searchName}"></td> <td><input type="text" name="searchName" value="${param.searchName}"></td>
<th style="width:180px;">사용여부</th>
<td>
<div class="select-style">
<select name="searchUseYn">
<option value="">전체</option>
<option value="1">사용함</option>
<option value="0">사용안함</option>
</select>
</div>
</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@@ -298,6 +298,7 @@ $(document).ready(function() {
url: url, url: url,
data: postData, data: postData,
success: function(args) { success: function(args) {
console.log('mod', args);
showAlert("<%= localeMessage.getString("common.saveMsg") %>", { showAlert("<%= localeMessage.getString("common.saveMsg") %>", {
type: 'success', type: 'success',
title: '저장 완료', title: '저장 완료',
@@ -493,11 +494,6 @@ $(document).ready(function() {
<span class="bucket-summary-value" id="summaryThreshold">0<span class="unit">req</span></span> <span class="bucket-summary-value" id="summaryThreshold">0<span class="unit">req</span></span>
<span class="bucket-summary-sub" id="summaryThresholdCalc"></span> <span class="bucket-summary-sub" id="summaryThresholdCalc"></span>
</div> </div>
<div class="bucket-summary-item">
<span class="bucket-summary-label">적용 API</span>
<span class="bucket-summary-value" id="summaryActiveApi">0<span class="unit">개</span></span>
<span class="bucket-summary-sub" id="summaryActiveApiList"></span>
</div>
</div> </div>
</div> </div>
@@ -47,27 +47,40 @@ function groupNameFormat(cellvalue, options, rowObject) {
return strVal; return strVal;
} }
$(document).ready(function() { $(document).ready(function() {
$("input[name=searchStartYYYYMMDD],input[name=searchEndYYYYMMDD]").inputmask("yyyy-mm-dd",{'autoUnmask':true}); $("input[name=searchStartYYYYMMDD],input[name=searchEndYYYYMMDD]").inputmask("yyyy-mm-dd",{'autoUnmask':true});
$("input[name=searchStartTime],input[name=searchEndTime]").inputmask("hh:mm:ss",{'autoUnmask':true});
$("input[name=searchStartYYYYMMDD]").each(function(){ $("input[name=searchStartYYYYMMDD]").each(function(){
if ($(this).val() == undefined || $(this).val() == null || $(this).val() == ""){ if ($(this).val() == undefined || $(this).val() == null || $(this).val() == ""){
$(this).val(getToday()); $(this).val(getToday());
} }
}); });
$("input[name=searchEndYYYYMMDD]").each(function(){ $("input[name=searchEndYYYYMMDD]").each(function(){
if ($(this).val() == undefined || $(this).val() == null || $(this).val() == ""){ if ($(this).val() == undefined || $(this).val() == null || $(this).val() == ""){
$(this).val(getToday()); $(this).val(getToday());
} }
}); });
$("input[name=searchStartTime]").each(function(){
var gridPostData = getSearchForJqgrid("cmd","LIST"); //jqgrid에서는 object 로 if ($(this).val() == undefined || $(this).val() == null || $(this).val() == ""){
$(this).val("000000");
}
});
$("input[name=searchEndTime]").each(function(){
if ($(this).val() == undefined || $(this).val() == null || $(this).val() == ""){
$(this).val("235959");
}
});
var gridPostData = getSearchForJqgrid("cmd","LIST"); //jqgrid에서는 object 로
var start = $("input[name=searchStartYYYYMMDD]").val().replace(/-/gi,""); var start = $("input[name=searchStartYYYYMMDD]").val().replace(/-/gi,"");
var end = $("input[name=searchEndYYYYMMDD]").val().replace(/-/gi,""); var end = $("input[name=searchEndYYYYMMDD]").val().replace(/-/gi,"");
gridPostData["searchStartDate"] = start; gridPostData["searchStartDate"] = start;
gridPostData["searchEndDate"] = end; gridPostData["searchEndDate"] = end;
@@ -132,18 +145,20 @@ $(document).ready(function() {
resizeJqGridWidth('grid','content_middle','1000'); resizeJqGridWidth('grid','content_middle','1000');
$("#btn_search").click(function(){ $("#btn_search").click(function(){
var postData = getSearchForJqgrid("cmd","LIST"); //jqgrid에서는 object 로 var postData = getSearchForJqgrid("cmd","LIST"); //jqgrid에서는 object 로
var start = $("input[name=searchStartYYYYMMDD]").val().replace(/-/gi,""); var start = $("input[name=searchStartYYYYMMDD]").val().replace(/-/gi,"");
var end = $("input[name=searchEndYYYYMMDD]").val().replace(/-/gi,""); var end = $("input[name=searchEndYYYYMMDD]").val().replace(/-/gi,"");
var startTime = $("input[name=searchStartTime]").val();
if(start > end){ var endTime = $("input[name=searchEndTime]").val();
if((start + startTime) > (end + endTime)){
alert("조회기간을 확인해주세요."); alert("조회기간을 확인해주세요.");
return false; return false;
} }
$("input[name=searchStartDate]").val(start); $("input[name=searchStartDate]").val(start);
$("input[name=searchEndDate]").val(end); $("input[name=searchEndDate]").val(end);
postData["searchStartDate"] = start; postData["searchStartDate"] = start;
postData["searchEndDate"] = end; postData["searchEndDate"] = end;
@@ -197,14 +212,24 @@ $(document).ready(function() {
<tbody> <tbody>
<tr> <tr>
<th style="width:180px;"><%= localeMessage.getString("infConHstMan.date") %></th> <th style="width:180px;"><%= localeMessage.getString("infConHstMan.date") %></th>
<td> <td colspan="5">
<input type="text" name="searchStartYYYYMMDD" id="startDatepicker" readonly="readonly" value="" size="10" style="width:80px; border:1px solid #ebebec;"> <input type="text" name="searchStartYYYYMMDD" id="startDatepicker" readonly="readonly" value="" size="10" style="width:80px; border:1px solid #ebebec;">
~ <input type="text" name="searchStartTime" value="" size="8" style="width:70px; border:1px solid #ebebec;">
~
<input type="text" name="searchEndYYYYMMDD" value="" id="endDatepicker" size="10" readonly="readonly" style="width:80px; border:1px solid #ebebec;"> <input type="text" name="searchEndYYYYMMDD" value="" id="endDatepicker" size="10" readonly="readonly" style="width:80px; border:1px solid #ebebec;">
<input type="text" name="searchEndTime" value="" size="8" style="width:70px; border:1px solid #ebebec;">
<input type="hidden" name="searchStartDate" value="" style="width:0px;"> <input type="hidden" name="searchStartDate" value="" style="width:0px;">
<input type="hidden" name="searchEndDate" value="" style="width:0px;"> <input type="hidden" name="searchEndDate" value="" style="width:0px;">
</td> </td>
</tr> </tr>
<tr>
<th style="width:180px;"><%= localeMessage.getString("infConHstMan.instNm") %></th>
<td><input type="text" name="searchInstanceName" value="${param.searchInstanceName}"></td>
<th style="width:180px;"><%= localeMessage.getString("infConHstMan.eaiSvcCd") %></th>
<td><input type="text" name="searchApiName" value="${param.searchApiName}"></td>
<th style="width:180px;"><%= localeMessage.getString("infConHstMan.adpGrpNm") %></th>
<td><input type="text" name="searchAdapterGroupName" value="${param.searchAdapterGroupName}"></td>
</tr>
</tbody> </tbody>
</table> </table>
</form> </form>
@@ -88,7 +88,7 @@ $(document).ready(function() {
datatype:"json", datatype:"json",
mtype: 'POST', mtype: 'POST',
url: url, url: url,
postData : { cmd : 'LIST', searchGroupName: $('input[name=searchGroupName]').val()}, postData : { cmd : 'LIST', searchGroupName: $('input[name=searchGroupName]').val(), searchUseYn: $('select[name=searchUseYn]').val()},
colNames:['그룹 ID', colNames:['그룹 ID',
'그룹명', '그룹명',
'초당 임계치', '초당 임계치',
@@ -125,6 +125,7 @@ $(document).ready(function() {
url2 += '&menuId='+'${param.menuId}'; url2 += '&menuId='+'${param.menuId}';
//검색값 //검색값
url2 += '&searchGroupName='+$("input[name=searchGroupName]").val(); url2 += '&searchGroupName='+$("input[name=searchGroupName]").val();
url2 += '&searchUseYn='+$("select[name=searchUseYn]").val();
//key값 //key값
url2 += '&groupId='+groupId; url2 += '&groupId='+groupId;
goNav(url2); goNav(url2);
@@ -143,7 +144,7 @@ $(document).ready(function() {
resizeJqGridWidth('grid','content_middle','1000'); resizeJqGridWidth('grid','content_middle','1000');
$("#btn_search").click(function(){ $("#btn_search").click(function(){
$("#grid").setGridParam({ postData: { searchGroupName: $('input[name=searchGroupName]').val()}, page:1 }).trigger("reloadGrid"); $("#grid").setGridParam({ postData: { searchGroupName: $('input[name=searchGroupName]').val(), searchUseYn: $('select[name=searchUseYn]').val()}, page:1 }).trigger("reloadGrid");
}); });
$("#btn_new").click(function(){ $("#btn_new").click(function(){
var url2 = url_view; var url2 = url_view;
@@ -188,6 +189,16 @@ $(document).ready(function() {
<tr> <tr>
<th style="width:180px;">그룹명</th> <th style="width:180px;">그룹명</th>
<td><input type="text" name="searchGroupName" value="${param.searchGroupName}"></td> <td><input type="text" name="searchGroupName" value="${param.searchGroupName}"></td>
<th style="width:180px;">사용여부</th>
<td>
<div class="select-style">
<select name="searchUseYn">
<option value="">전체</option>
<option value="1">사용함</option>
<option value="0">사용안함</option>
</select>
</div>
</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@@ -25,7 +25,7 @@ $(document).ready(function() {
datatype:"json", datatype:"json",
mtype: 'POST', mtype: 'POST',
url: url, url: url,
postData : { cmd : 'LIST', searchName: $('input[name=searchName]').val()}, postData : { cmd : 'LIST', searchName: $('input[name=searchName]').val(), searchUseYn: $('select[name=searchUseYn]').val()},
colNames:['<%= localeMessage.getString("infIfConMan.ifNm") %>', colNames:['<%= localeMessage.getString("infIfConMan.ifNm") %>',
'<%= localeMessage.getString("infIfConMan.ifDesc") %>', '<%= localeMessage.getString("infIfConMan.ifDesc") %>',
'<%= localeMessage.getString("infAdpConMan.thrPerSecond") %>', '<%= localeMessage.getString("infAdpConMan.thrPerSecond") %>',
@@ -62,6 +62,7 @@ $(document).ready(function() {
url2 += '&menuId='+'${param.menuId}'; url2 += '&menuId='+'${param.menuId}';
//검색값 //검색값
url2 += '&searchName='+$("input[name=searchName]").val(); url2 += '&searchName='+$("input[name=searchName]").val();
url2 += '&searchUseYn='+$("select[name=searchUseYn]").val();
//key값 //key값
url2 += '&name='+name; url2 += '&name='+name;
goNav(url2); goNav(url2);
@@ -80,7 +81,7 @@ $(document).ready(function() {
resizeJqGridWidth('grid','content_middle','1000'); resizeJqGridWidth('grid','content_middle','1000');
$("#btn_search").click(function(){ $("#btn_search").click(function(){
$("#grid").setGridParam({ postData: { searchName: $('input[name=searchName]').val()}, page:1 }).trigger("reloadGrid"); $("#grid").setGridParam({ postData: { searchName: $('input[name=searchName]').val(), searchUseYn: $('select[name=searchUseYn]').val()}, page:1 }).trigger("reloadGrid");
}); });
$("#btn_new").click(function(){ $("#btn_new").click(function(){
var url2 = url_view; var url2 = url_view;
@@ -126,6 +127,16 @@ $(document).ready(function() {
<tr> <tr>
<th style="width:180px;"><%= localeMessage.getString("infIfConMan.ifNm") %></th> <th style="width:180px;"><%= localeMessage.getString("infIfConMan.ifNm") %></th>
<td><input type="text" name="searchName" value="${param.searchName}"></td> <td><input type="text" name="searchName" value="${param.searchName}"></td>
<th style="width:180px;"><%= localeMessage.getString("infAdpConMan.useYn") %></th>
<td>
<div class="select-style">
<select name="searchUseYn">
<option value="">전체</option>
<option value="1">사용함</option>
<option value="0">사용안함</option>
</select>
</div>
</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@@ -74,8 +74,8 @@
const itemTypes = ['Field', 'Grid', 'Group', 'Attr', 'Array']; const itemTypes = ['Field', 'Grid', 'Group', 'Attr', 'Array'];
columns = [ // for columnData prop columns = [ // for columnData prop
{ title: '<%= localeMessage.getString("layoutMan.itmeEn") %>', name: 'loutItemName', type: 'text', width:280, resize:true, minWidth:120 },
{ title: '<%= localeMessage.getString("layoutMan.itemDesc") %>', name: 'loutItemDesc', type: 'text', width:280, resize:true, minWidth:120 }, { title: '<%= localeMessage.getString("layoutMan.itemDesc") %>', name: 'loutItemDesc', type: 'text', width:280, resize:true, minWidth:120 },
{ title: '<%= localeMessage.getString("layoutMan.itmeEn") %>', name: 'loutItemName', type: 'text', width:280, resize:true, minWidth:120 },
{ title: '<%=localeMessage.getString("standardLayout.itemLevel")%>', name: 'loutItemDepth', type: 'text', width:50 }, { title: '<%=localeMessage.getString("standardLayout.itemLevel")%>', name: 'loutItemDepth', type: 'text', width:50 },
{ title: '<%= localeMessage.getString("standardLayout.itemType") %>', name: 'loutItemType', type: 'autocomplete', source: itemTypes, width:70 }, { title: '<%= localeMessage.getString("standardLayout.itemType") %>', name: 'loutItemType', type: 'autocomplete', source: itemTypes, width:70 },
{ title: '<%= localeMessage.getString("standardLayout.arraySize") %>', name: 'loutItemOccCnt', type: 'text', width:90 }, { title: '<%= localeMessage.getString("standardLayout.arraySize") %>', name: 'loutItemOccCnt', type: 'text', width:90 },
@@ -0,0 +1,258 @@
<%@ page language="java" contentType="text/html; charset=utf-8"%>
<%@ page import="java.io.*"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ 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=euc-kr">
<jsp:include page="/jsp/common/include/css.jsp"/>
<jsp:include page="/jsp/common/include/script.jsp"/>
<script language="javascript" >
var url = '<c:url value="/onl/transaction/apim/apiInterfaceMan.json"/>';
var url_view = '<c:url value="/onl/transaction/apim/apiInterfaceMan.view"/>';
var selectName = "searchEaiBzwkDstcd"; // selectBox Name
function processSelectedData() {
var grid = $("#grid");
var selectedRowIds = grid.jqGrid('getGridParam', 'selarrrow');
var selectedData = [];
for(var i = 0; i < selectedRowIds.length; i++) {
var rowData = grid.jqGrid('getRowData', selectedRowIds[i]);
selectedData.push({
bizCode: rowData.eaiBzwkDstcd, // CLINET
apiId: rowData.eaiSvcName,
apiDesc: rowData.eaiSvcDesc, // SCOPE-API
APIFULLPATH: rowData.apiFullPath,
BZWKSVCKEYNAME: rowData.bzwksvckeyname
});
}
if(selectedData.length === 0) {
alert("선택된 항목이 없습니다.");
return;
}
alert("선택한 " + selectedData.length + "건의 API를 추가합니다.");
window.returnValue = selectedData.length === 1 ? selectedData[0] : selectedData;
window.close();
}
function updateSelectionInfo(gridId) {
var grid = $(gridId || "#grid");
var selectedRows = grid.jqGrid('getGridParam', 'selarrrow');
$("#selected-count").text("총: " + selectedRows.length + " 건");
}
function init( callback) {
$.ajax({
type : "POST",
url:url,
dataType:"json",
data:{cmd: 'LIST_COMBO'},
success:function(json){
new makeOptions("BIZCODE","BIZNAME").setObj($("select[name=searchEaiBzwkDstcd]")).setNoValueInclude(true).setNoValue("","전체").setData(json.bizList).setFormat(codeName3OptionFormat).rendering();
setSearchable(selectName); // 콤보에 searchable 설정
if (typeof callback === 'function') {
callback();
}
},
error:function(e){
alert(e.responseText);
}
});
}
function detail(){
var postData = getSearchForJqgrid("cmd","LIST"); //jqgrid에서는 object 로
$("#grid").setGridParam({ url:url,postData: postData }).trigger("reloadGrid");
}
function search(){
var postData = getSearchForJqgrid("cmd","LIST"); //jqgrid에서는 object 로
$("#grid").setGridParam({ url:url,postData: postData ,page:1 }).trigger("reloadGrid");
}
function ioFormatter(cellvalue,options,rowObject){
var serviceType = sessionStorage["serviceType"];
if(cellvalue == "I"){
return "타발";
}else if(cellvalue =="O"){
return "당발";
}
}
function adapterNameShortFormatter(cellvalue,options,rowObject){
if(cellvalue == null || cellvalue == '')
return ''
return cellvalue.substring(1, 4);
}
function list(){
detail()
var gridPostData = getSearchForJqgrid("cmd","LIST"); //jqgrid에서는 object 로
// gridPostData.searchApiEnableYn = 'Y';
var urlParams = new URLSearchParams(window.location.search);
var selectedApiIds = urlParams.get("selectedApiIds");
$('#grid').jqGrid({
datatype : "json",
mtype : 'POST',
url : url,
postData : gridPostData,
colNames : [ '업무구분',
'<%= localeMessage.getString("eaiMessage.eaiSvcName")%>',
'<%= localeMessage.getString("eaiMessage.eaiSvcDesc")%>',
'API FULL PATH',
'업무서비스명',
'요청',
'응답',
'작성자',
'가상응답여부',
'SyncAsyncType',
],
colModel : [ { name : 'eaiBzwkDstcd' , align : 'center' , width:'40', sortable:false},
{ name : 'eaiSvcName' , align : 'left' , width:'100'},
{ name : 'eaiSvcDesc' , align : 'left' },
{ name : 'apiFullPath' , align : 'left' , width:'100'},
{ name : 'bzwksvckeyname' , align : 'left' , width:'100', hidden: true},
{ name : 'fromAdapter' , align : 'center' , width:'40', formatter:adapterNameShortFormatter },
{ name : 'toAdapter' , align : 'center' , width:'40', formatter:adapterNameShortFormatter },
{ name : 'author' , align : 'center' , width:'60' },
{ name : 'simYn' , align : 'center' , width:'40' },
{ name : 'syncAsyncType' , align : 'center' , width:'40', hidden: true},
],
jsonReader : {
repeatitems : false
},
pager : $('#pager'),
page : '${param.page}',
rowNum : '${rmsDefaultRowNum}',
autoheight : true,
height : $("#container").height(),
autowidth : true,
viewrecords : true,
multiselect: false,
multiboxonly: false,
rowList : eval('[${rmsDefaultRowList}]'),
loadComplete:function (d){
var $grid = $(this);
if(selectedApiIds) {
const selectedIds = selectedApiIds.split(',');
$.each($grid.getDataIDs(), function(_, id) {
var rowData = $grid.getRowData(id);
if(selectedIds.includes(rowData.eaiSvcName)) {
$grid.jqGrid('setSelection', id, true);
}
});
}
var colModel = $(this).getGridParam("colModel");
for(var i = 0 ; i< colModel.length; i++){
$(this).setColProp(colModel[i].name, {sortable : false});
}
updateSelectionInfo("#grid");
},
onSelectRow: function(rowid, status, e) {
updateSelectionInfo("#grid");
},
onSelectAll: function(rowids, status) {
updateSelectionInfo("#grid");
},
ondblClickRow : function(rowId) {
const rowData = $(this).getRowData(rowId);
const returnValue = {
bizCode: rowData.eaiBzwkDstcd,
apiId: rowData.eaiSvcName,
apiDesc: rowData.eaiSvcDesc
};
//alert("선택한 API를 추가합니다.\nAPI ID: " + rowData.eaiSvcName);
window.returnValue = returnValue;
window.close();
}
});
}
$(document).ready(function() {
init(list);
resizeJqGridWidth('grid','content_middle','1000');
// 업무구분명 선택 시 자동 검색
$("select[name=searchEaiBzwkDstcd]").change(function() {
search();
});
$("#btn_search").click(function(){
search();
});
$("input[name^=search]").keydown(function(key){
if (key.keyCode == 13){
$("#btn_search").click();
}
});
buttonControl();
$("#btn_select").click(function(){
processSelectedData();
});
$("#btn_close").click(function () {
window.close();
});
});
</script>
</head>
<body>
<div>
<div class="content_middle" id="content_middle" style="margin-top: 0px">
<div class="search_wrap">
<button type="button" class="cssbtn" id="btn_search" level="R" ><i class="material-icons">search</i> <%= localeMessage.getString("button.search") %></button>
<button type="button" class="cssbtn" id="btn_select" level="R" ><i class="material-icons">check</i> <%= localeMessage.getString("button.check") %></button>
<button type="button" class="cssbtn" id="btn_close" level="R" status="DETAIL"><i class="material-icons">close</i> <%= localeMessage.getString("button.close") %></button>
</div>
<div class="title" id="title" >${rmsMenuName}</div>
<!-- 선택 정보 표시 영역 추가 -->
<div style="margin-bottom: 5px;">
<span id="selected-count" style="color: #666; font-size: 12px;"></span>
</div>
<table class="search_condition" cellspacing=0;>
<tbody>
<tr>
<th style="width:180px;">업무구분명</th>
<td>
<select name="searchEaiBzwkDstcd" value="${param.searchEaiBzwkDstcd}">
</select>
</td>
<th style="width:180px;"><%= localeMessage.getString("eaiMessage.eaiSvcName")%></th>
<td>
<input type="text" name="searchEaiSvcName" value="${param.searchEaiSvcName}">
</td>
</tr>
<tr>
<th style="width:180px;"><%= localeMessage.getString("eaiMessage.eaiSvcDesc")%></th>
<td>
<input type="text" name="searchEaiSvcDesc" value="${param.searchEaiSvcDesc}">
</td>
<th style="width:180px;">API FULL PATH</th>
<td>
<input type="text" name="searchApiFullPath" value="${param.searchApiFullPath}">
</td>
</tr>
</tbody>
</table>
<table id="grid" ></table>
<div id="pager"></div>
</div><!-- end content_middle -->
</div><!-- end right_box -->
</body>
</html>
@@ -1947,8 +1947,8 @@
var key = ""; var key = "";
var args = new Object(); var args = new Object();
args['eaiSvcName'] = $('input[name=eaiSvcName]').val(); args['eaiSvcName'] = $('input[name=eaiSvcName]').val();
var url='<c:url value="/onl/transaction/extnl/interfaceMan.view"/>'; var url=url_view; //'<c:url value="/onl/transaction/extnl/interfaceMan.view"/>';
url = url + "?cmd=POPUP"; url = url + "?cmd=API_POPUP";
var ret = showModal(url,args,1020,630, function(arg){ var ret = showModal(url,args,1020,630, function(arg){
var args = null; var args = null;
if(arg == null || arg == undefined ) {//chrome if(arg == null || arg == undefined ) {//chrome
@@ -1962,11 +1962,11 @@
var ret = args.returnValue; var ret = args.returnValue;
console.log("ret",ret); console.log("ret",ret);
key = ret['key']; key = ret['apiId'];
$("input[name=eaiSvcName]").val(key); $("input[name=eaiSvcName]").val(key);
$("input[name=eaiSvcDesc]").val(ret['eaiSvcDesc']); $("input[name=eaiSvcDesc]").val(ret['apiDesc']);
bzwkDstCd = ret['eaiBzwkDstCd']; bzwkDstCd = ret['bizCode'];
$("input[name=eaiSvcName]").change(); $("input[name=eaiSvcName]").change();
}); });
@@ -19,7 +19,8 @@
<jsp:include page="/jsp/common/include/css.jsp"/> <jsp:include page="/jsp/common/include/css.jsp"/>
<jsp:include page="/jsp/common/include/script.jsp"/> <jsp:include page="/jsp/common/include/script.jsp"/>
<script language="javascript" > <script language="javascript" >
var url_view = '<c:url value="/onl/admin/rule/transform2/transform2Man.view" />';
var $ = jQuery.noConflict(); var $ = jQuery.noConflict();
var bzwkDstCd = ""; var bzwkDstCd = "";
@@ -81,8 +82,9 @@ $(document).ready(function() {
var key = ""; var key = "";
var args = new Object(); var args = new Object();
args['eaiSvcName'] = $('input[name=eaiSvcName]').val(); args['eaiSvcName'] = $('input[name=eaiSvcName]').val();
var url='<c:url value="/onl/transaction/extnl/interfaceMan.view"/>'; var url=url_view; //'<c:url value="/onl/transaction/extnl/interfaceMan.view"/>';
url = url + "?cmd=POPUP"; url = url + "?cmd=API_POPUP";
console.log('[new]', url);
var ret = showModal(url,args,1020,630, function(arg){ var ret = showModal(url,args,1020,630, function(arg){
var args = null; var args = null;
if(arg == null || arg == undefined ) {//chrome if(arg == null || arg == undefined ) {//chrome
@@ -96,11 +98,11 @@ $(document).ready(function() {
var ret = args.returnValue; var ret = args.returnValue;
console.log("ret",ret); console.log("ret",ret);
key = ret['key']; key = ret['apiId'];
$("input[name=eaiSvcName]").val(key); $("input[name=eaiSvcName]").val(key);
$("input[name=eaiSvcDesc]").val(ret['eaiSvcDesc']); $("input[name=eaiSvcDesc]").val(ret['apiDesc']);
bzwkDstCd = ret['eaiBzwkDstCd']; bzwkDstCd = ret['bizCode'];
$("input[name=eaiSvcName]").change(); $("input[name=eaiSvcName]").change();
//$("select[name=eaiSevrDstcd]").val(ret['eaiSevrDstcd']); //$("select[name=eaiSevrDstcd]").val(ret['eaiSevrDstcd']);
@@ -23,7 +23,7 @@
<script language="javascript" > <script language="javascript" >
var url = '<c:url value="/onl/apim/apigroup/apiGroupMan.json"/>'; var url = '<c:url value="/onl/apim/apigroup/apiGroupMan.json"/>';
var url_view = '<c:url value="/onl/apim/apigroup/apiGroupMan.view"/>'; var url_view = '<c:url value="/onl/apim/apigroup/apiGroupMan.view"/>';
var url_popup_view = '<c:url value="/onl/transaction/apim/apiSpecManPopup.view"/>'; var url_popup_view = '<c:url value="/onl/transaction/apim/apiSpecMan.view"/>';
var isDetail = false; var isDetail = false;
let dialog; let dialog;
@@ -131,6 +131,24 @@
// var reverseNumber = records - ((page - 1) * rowNum + i); // var reverseNumber = records - ((page - 1) * rowNum + i);
$(this).setCell(rows[i], 'rowNum', number); $(this).setCell(rows[i], 'rowNum', number);
} }
// 승인 상태별 행 배경색: 요청됨(#fff3cd) / 진행중(#d1ecf1) 강조
if (d && d.rows) {
for (var j = 0; j < d.rows.length; j++) {
var st = d.rows[j].approvalStatus || {};
var code = st.code || '';
var desc = st.description || '';
var bg = '';
if (code === 'REQUESTED' || desc === '요청됨') {
bg = '#fff3cd';
} else if (code === 'PROCESSING' || desc === '진행중') {
bg = '#d1ecf1';
}
if (bg) {
$('#grid tr#' + $.jgrid.jqID(d.rows[j].id)).css('background-color', bg);
}
}
}
}, },
loadError: function(jqXHR, textStatus, errorThrown) { loadError: function(jqXHR, textStatus, errorThrown) {
var location = '<%=request.getContextPath()%>/'; var location = '<%=request.getContextPath()%>/';
@@ -176,7 +194,7 @@
<div class="title">승인 요청 목록<span class="tooltip">승인 요청을 관리합니다.</span></div> <div class="title">승인 요청 목록<span class="tooltip">승인 요청을 관리합니다.</span></div>
<c:if test="${hardDeleteEnabled}"> <c:if test="${hardDeleteEnabled}">
<div style="background:#fff3cd; border:1px solid #ffc107; padding:8px 15px; margin:5px 0; border-radius:4px; color:#856404;"> <div style="background:#fff3cd; border:1px solid #ffc107; padding:8px 15px; margin:5px 0; border-radius:4px; color:#856404;">
<strong>[주의]</strong> 선택 삭제 시 승인 요청과 승인자 이력이 DB에서 영구 삭제됩니다. <strong>[주의]</strong> 삭제 기능은 <strong>개발(dev) 환경에서만</strong> 동작합니다. 선택 삭제 시 승인 요청과 승인자 이력이 DB에서 영구 삭제됩니다.
</div> </div>
</c:if> </c:if>
<form id="ajaxForm" onsubmit="return false;"> <form id="ajaxForm" onsubmit="return false;">
@@ -22,6 +22,8 @@
function formatInquiryStatus(cellvalue, options, rowObject) { function formatInquiryStatus(cellvalue, options, rowObject) {
if (cellvalue == 'PENDING') { if (cellvalue == 'PENDING') {
return '<span style="color: red;">문의중</span>'; return '<span style="color: red;">문의중</span>';
} else if (cellvalue == 'REVIEWING') {
return '<span >문의검토중</span>'
} else if (cellvalue == 'RESPONDED') { } else if (cellvalue == 'RESPONDED') {
return '<span >답변완료</span>' return '<span >답변완료</span>'
} else if (cellvalue == 'CLOSED') { } else if (cellvalue == 'CLOSED') {
@@ -31,6 +33,17 @@
} }
} }
function formatVisibility(cellvalue, options, rowObject) {
if (cellvalue == 'PRIVATE') {
return '<span style="color:#c0392b; font-weight:bold;">비공개</span>';
} else if (cellvalue == 'ALL') {
return '전체공개';
} else if (cellvalue == 'ORG') {
return '법인공개';
}
return cellvalue;
}
function formatFile(cellvalue, options, rowObject) { function formatFile(cellvalue, options, rowObject) {
var iconPath = '${pageContext.request.contextPath}/images/icon_file.png'; var iconPath = '${pageContext.request.contextPath}/images/icon_file.png';
var icon = rowObject.hasAttachment ? '<img src="' + iconPath + '" alt="File Attached" style="vertical-align: middle; margin-left: 5px; width: 16px; height: 16px;">' : ''; var icon = rowObject.hasAttachment ? '<img src="' + iconPath + '" alt="File Attached" style="vertical-align: middle; margin-left: 5px; width: 16px; height: 16px;">' : '';
@@ -99,13 +112,14 @@
mtype: 'POST', mtype: 'POST',
url: url, url: url,
postData : { cmd : 'LIST' }, postData : { cmd : 'LIST' },
colNames:['No.', 'id', '제목', '법인명', '상태', '작성자', '등록일', '답변자','답변일'], colNames:['No.', 'id', '제목', '법인명', '상태', '공개범위', '작성자', '등록일', '답변자','답변일'],
colModel:[ colModel:[
{ name : 'rowNum' , align:'center', width:50, sortable:false }, { name : 'rowNum' , align:'center', width:50, sortable:false },
{ name : 'id' , align:'center', key:true, hidden:true}, { name : 'id' , align:'center', key:true, hidden:true},
{ name : 'inquirySubject' , align:'left' , width:200 , formatter: formatFile}, { name : 'inquirySubject' , align:'left' , width:200 , formatter: formatFile},
{ name : 'orgName' , align:'center' , width:100 , }, { name : 'orgName' , align:'center' , width:100 , },
{ name : 'inquiryStatus' , align:'center', width:70, formatter: formatInquiryStatus }, { name : 'inquiryStatus' , align:'center', width:70, formatter: formatInquiryStatus },
{ name : 'visibility' , align:'center', width:70, formatter: formatVisibility },
{ name : 'inquirer.userName', align:'center', width:70 }, { name : 'inquirer.userName', align:'center', width:70 },
{ name : 'createdDate' , align:'center', width:100, formatter: timeStampFormat }, { name : 'createdDate' , align:'center', width:100, formatter: timeStampFormat },
{ name : 'responderName' , align:'center', width:70 }, { name : 'responderName' , align:'center', width:70 },
@@ -207,6 +221,7 @@
<select name="searchInquiryStatus"> <select name="searchInquiryStatus">
<option value="">전체</option> <option value="">전체</option>
<option value="PENDING">문의중</option> <option value="PENDING">문의중</option>
<option value="REVIEWING">문의검토중</option>
<option value="RESPONDED">답변완료</option> <option value="RESPONDED">답변완료</option>
<option value="CLOSED">답변종료</option> <option value="CLOSED">답변종료</option>
</select> </select>
@@ -79,12 +79,46 @@
margin-left: 2px; margin-left: 2px;
font-weight: bold; font-weight: bold;
} }
.reason-modal-overlay {
display: none;
position: fixed;
top: 0; left: 0;
width: 100%; height: 100%;
background: rgba(0, 0, 0, 0.4);
z-index: 1000;
}
.reason-modal-box {
position: absolute;
top: 50%; left: 50%;
transform: translate(-50%, -50%);
background: #fff;
border-radius: 6px;
padding: 20px;
width: 400px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
}
.reason-modal-box h3 {
margin: 0 0 12px;
font-size: 16px;
}
.reason-modal-box textarea {
width: 100%;
min-height: 80px;
box-sizing: border-box;
}
.reason-modal-actions {
margin-top: 12px;
text-align: right;
}
</style> </style>
<script language="javascript"> <script language="javascript">
var url = '<c:url value="/onl/apim/portalinquiry/portalInquiryMan.json" />'; var url = '<c:url value="/onl/apim/portalinquiry/portalInquiryMan.json" />';
var url_view = '<c:url value="/onl/apim/portalinquiry/portalInquiryMan.view" />'; var url_view = '<c:url value="/onl/apim/portalinquiry/portalInquiryMan.view" />';
var file_download = '<c:url value="/file/download.do" />'; var file_download = '<c:url value="/file/download.do" />';
var inquiryStatus = 'PENDING'; var inquiryStatus = 'PENDING';
var originalVisibility = 'ORG';
var reasonModalCallback = null;
function decodeHTMLEntities(text) { function decodeHTMLEntities(text) {
var textArea = document.createElement('textarea'); var textArea = document.createElement('textarea');
@@ -108,7 +142,9 @@
inquiryStatus = data.inquiryStatus; inquiryStatus = data.inquiryStatus;
if (data.inquiryStatus == 'PENDING') { if (data.inquiryStatus == 'PENDING') {
$("#inquiryStatus").text('문의중'); $("#inquiryStatus").text('문의중');
} else if (data.inquiryStatus == 'REVIEWING') {
$("#inquiryStatus").text('문의검토중');
} else if (data.inquiryStatus == 'RESPONDED') { } else if (data.inquiryStatus == 'RESPONDED') {
$("#inquiryStatus").text('답변완료'); $("#inquiryStatus").text('답변완료');
} else if (data.inquiryStatus == 'CLOSED') { } else if (data.inquiryStatus == 'CLOSED') {
@@ -122,6 +158,24 @@
var decodedContent = decodeHTMLEntities(data.responseDetail); var decodedContent = decodeHTMLEntities(data.responseDetail);
$("#responseDetail").summernote('code', decodedContent || ''); $("#responseDetail").summernote('code', decodedContent || '');
// 공개범위 / 조회수
$('#visibilitySelect').val(data.visibility || 'ORG');
originalVisibility = data.visibility || 'ORG';
$('#viewCount').text(data.viewCount || 0);
// 조회수 증가 (sessionStorage dedup — 새로고침 시 중복 증가 억제)
var viewedKey = 'inquiry_viewed_' + key;
if (!sessionStorage.getItem(viewedKey)) {
$.ajax({
type: "POST",
url: url,
data: {cmd: 'INCREMENT_VIEW', id: key},
success: function () {
sessionStorage.setItem(viewedKey, 'true');
}
});
}
// 댓글 표시 // 댓글 표시
listComment(key); listComment(key);
@@ -147,8 +201,17 @@
if (!data || data.length === 0) return; if (!data || data.length === 0) return;
$.each(data, function (i, comment) { $.each(data, function (i, comment) {
var date = comment.createdDate ? timeStampFormat(comment.createdDate) : ''; var date = comment.createdDate ? timeStampFormat(comment.createdDate) : '';
var isPrivate = comment.visibility === 'PRIVATE';
var row = $('<div>').addClass('info-row'); var row = $('<div>').addClass('info-row');
$('<div>').addClass('info-label').css('color', '#555').text(comment.writerName || '').appendTo(row); if (isPrivate) {
// 비공개 댓글은 색상으로 구별
row.css({'background-color': '#FFF5F5', 'border-radius': '4px', 'padding': '4px'});
}
var labelDiv = $('<div>').addClass('info-label').css('color', '#555').text(comment.writerName || '');
if (isPrivate) {
$('<span>').text(' 비공개').css({'color': '#c0392b', 'font-size': '0.85em', 'font-weight': 'bold'}).appendTo(labelDiv);
}
labelDiv.appendTo(row);
var content = $('<div>').addClass('info-content'); var content = $('<div>').addClass('info-content');
$('<div>').css('white-space', 'pre-wrap').text(comment.commentDetail).appendTo(content); $('<div>').css('white-space', 'pre-wrap').text(comment.commentDetail).appendTo(content);
var meta = $('<div>').css({'font-size': '0.9em', 'color': '#999', 'margin-top': '2px'}); var meta = $('<div>').css({'font-size': '0.9em', 'color': '#999', 'margin-top': '2px'});
@@ -159,6 +222,19 @@
})(comment.id, comment.inquiryId)) })(comment.id, comment.inquiryId))
.appendTo(meta); .appendTo(meta);
meta.appendTo(content); meta.appendTo(content);
// 댓글 공개범위 변경 컨트롤 (사유 필수)
var visCtrl = $('<div>').css({'margin-top': '4px', 'font-size': '0.85em'});
var sel = $('<select>').append('<option value="ALL">공개</option>').append('<option value="PRIVATE">비공개</option>');
sel.val(comment.visibility || 'ALL');
var reasonInput = $('<input>').attr({'type': 'text', 'placeholder': '변경 사유'}).css({'margin-left': '4px'});
var applyBtn = $('<button>').attr('type', 'button').addClass('cssbtn smallBtn2').css({'margin-left': '4px', 'font-size': '0.85em', 'padding': '1px 6px'})
.text('공개범위변경').on('click', (function(id, inquiryId, selEl, reasonEl) {
return function() { changeCommentVisibility(id, inquiryId, selEl.val(), reasonEl.val()); };
})(comment.id, comment.inquiryId, sel, reasonInput));
visCtrl.append(sel).append(reasonInput).append(applyBtn);
visCtrl.appendTo(content);
content.appendTo(row); content.appendTo(row);
row.appendTo($commentList); row.appendTo($commentList);
}); });
@@ -169,6 +245,84 @@
}); });
} }
function openReasonModal(callback) {
reasonModalCallback = callback;
$('#reasonModalInput').val('');
$('#reasonModal').show();
$('#reasonModalInput').focus();
}
function closeReasonModal() {
$('#reasonModal').hide();
reasonModalCallback = null;
}
// 답변(summernote) 내용이 실제로 입력됐는지 확인
function hasAnswerContent() {
var v = $('#responseDetail').summernote('code');
var text = $('<div>').html(v || '').text().replace(/ /g, ' ').trim();
return text.length > 0;
}
function saveInquiryAnswer(returnUrl) {
var postData = $('#ajaxForm').serializeArray();
postData.push({name: "cmd", value: "INSERT"});
$.ajax({
type: "POST",
url: url,
data: postData,
success: function (json) {
alert("<%= localeMessage.getString("common.saveMsg") %>");
goNav(returnUrl);
},
error: function (e) {
alert(e.responseText);
}
});
}
// 게시물 공개범위 변경(사유 필수). 성공 후 답변 내용이 있으면 이어서 저장.
function changeInquiryVisibility(newVis, reason, returnUrl, thenSaveAnswer) {
$.ajax({
type: "POST",
url: url,
data: {cmd: 'VISIBILITY', serviceType: 'APIGW', targetType: 'INQUIRY', id: $('#id').val(),
visibility: newVis, auditReason: reason},
success: function () {
originalVisibility = newVis;
if (thenSaveAnswer) {
saveInquiryAnswer(returnUrl);
} else {
alert('변경되었습니다.');
goNav(returnUrl);
}
},
error: function (e) {
alert(e.responseText);
}
});
}
function changeCommentVisibility(commentId, inquiryId, visibility, reason) {
if (!reason || !reason.trim()) {
alert('공개범위 변경 사유를 입력하세요.');
return;
}
if (!confirm('댓글 공개범위를 변경하시겠습니까?')) return;
$.ajax({
type: "POST",
url: url,
data: {cmd: 'VISIBILITY', serviceType: 'APIGW', targetType: 'COMMENT', id: commentId, visibility: visibility, auditReason: reason},
success: function () {
alert('변경되었습니다.');
listComment(inquiryId);
},
error: function (e) {
alert(e.responseText);
}
});
}
function deleteComment(commentId, inquiryId) { function deleteComment(commentId, inquiryId) {
if (inquiryStatus == 'CLOSED') { if (inquiryStatus == 'CLOSED') {
alert('답변이 종료되어 삭제할 수 없습니다'); alert('답변이 종료되어 삭제할 수 없습니다');
@@ -208,24 +362,38 @@
alert('답변이 종료되어 수정할 수 없습니다'); alert('답변이 종료되어 수정할 수 없습니다');
return; return;
} }
var newVis = $('#visibilitySelect').val();
var visChanged = (newVis !== originalVisibility);
if (visChanged) {
// 공개범위 변경 감지 → 사유 입력 모달 (답변 내용이 있으면 함께 저장)
var saveAnswerToo = hasAnswerContent();
openReasonModal(function (reason) {
changeInquiryVisibility(newVis, reason, returnUrl, saveAnswerToo);
});
return;
}
// 공개범위 변경 없음 → 기존 답변 저장
if (!checkRequired("ajaxForm")) return; if (!checkRequired("ajaxForm")) return;
if (!confirm("<%= localeMessage.getString("common.checkSave")%>")) return; if (!confirm("<%= localeMessage.getString("common.checkSave")%>")) return;
saveInquiryAnswer(returnUrl);
});
var postData = $('#ajaxForm').serializeArray(); $("#reasonModalOk").click(function () {
postData.push({name: "cmd", value: "INSERT"}); var reason = ($('#reasonModalInput').val() || '').trim();
if (!reason) {
alert('변경 사유를 입력하세요.');
return;
}
var cb = reasonModalCallback;
closeReasonModal();
if (cb) cb(reason);
});
$.ajax({ $("#reasonModalCancel").click(function () {
type: "POST", closeReasonModal();
url: url,
data: postData,
success: function (json) {
alert("<%= localeMessage.getString("common.saveMsg") %>");
goNav(returnUrl);
},
error: function (e) {
alert(e.responseText);
}
});
}); });
$("#btn_comment").click(function () { $("#btn_comment").click(function () {
@@ -308,7 +476,22 @@
<div class="info-label">작성일</div> <div class="info-label">작성일</div>
<div id="createdDate" class="info-content"></div> <div id="createdDate" class="info-content"></div>
</div> </div>
<div class="info-row" id="attachFileRow" style="display: none;"> <div class="info-row">
<div class="info-label">조회수</div>
<div id="viewCount" class="info-content">0</div>
</div>
<div class="info-row">
<div class="info-label">공개범위</div>
<div class="info-content" style="display:flex; gap:8px; align-items:center;">
<select id="visibilitySelect">
<option value="ALL">전체공개</option>
<option value="ORG">법인공개</option>
<option value="PRIVATE">비공개</option>
</select>
<span style="font-size:0.85em; color:#999;">변경 후 상단 [수정] 버튼을 누르면 사유 입력창이 표시됩니다.</span>
</div>
</div>
<div class="info-row" id="attachFileRow" style="display: none;">
<div class="info-label">첨부파일</div> <div class="info-label">첨부파일</div>
<div id="attachFiles" class="info-content"></div> <div id="attachFiles" class="info-content"></div>
</div> </div>
@@ -356,5 +539,17 @@
</form> </form>
</div> </div>
</div> </div>
<!-- 공개범위 변경 사유 입력 모달 -->
<div id="reasonModal" class="reason-modal-overlay">
<div class="reason-modal-box">
<h3>공개범위 변경 사유</h3>
<textarea id="reasonModalInput" placeholder="변경 사유를 입력하세요."></textarea>
<div class="reason-modal-actions">
<button type="button" class="cssbtn" id="reasonModalOk">확인</button>
<button type="button" class="cssbtn" id="reasonModalCancel">취소</button>
</div>
</div>
</div>
</body> </body>
</html> </html>
@@ -86,9 +86,11 @@
editurl: "clientArray", editurl: "clientArray",
colNames: ['<%= localeMessage.getString("propertyDetail.propertyKey") %>', colNames: ['<%= localeMessage.getString("propertyDetail.propertyKey") %>',
'<%= localeMessage.getString("propertyDetail.propertyValue") %>', '<%= localeMessage.getString("propertyDetail.propertyValue") %>',
'설명',
'<%= localeMessage.getString("propertyDetail.delYn") %>'], '<%= localeMessage.getString("propertyDetail.delYn") %>'],
colModel: [{name: 'PRPTYNAME', width: 50, align: 'left', editable: true}, colModel: [{name: 'PRPTYNAME', width: 50, align: 'left', editable: true},
{name: 'PRPTY2VAL', width: 200, align: 'left', editable: true}, {name: 'PRPTY2VAL', width: 150, align: 'left', editable: true},
{name: 'PRPTYDESC', width: 200, align: 'left', editable: true, edittype: 'textarea', editoptions: {rows: 2}},
{ {
name: 'DELETEYN', name: 'DELETEYN',
width: 20, width: 20,
@@ -258,6 +260,7 @@
var data = new Object(); var data = new Object();
data["PRPTYNAME"] = $("input[name=prptyName]").val(); data["PRPTYNAME"] = $("input[name=prptyName]").val();
data["PRPTY2VAL"] = $("input[name=prpty2Val]").val(); data["PRPTY2VAL"] = $("input[name=prpty2Val]").val();
data["PRPTYDESC"] = $("input[name=prptyDesc]").val();
var rows = $("#grid")[0].rows; var rows = $("#grid")[0].rows;
var index = Number($(rows[rows.length - 1]).attr("id")); var index = Number($(rows[rows.length - 1]).attr("id"));
@@ -340,6 +343,11 @@
<td colspan="3"><input type="text" name="prpty2Val"/> <td colspan="3"><input type="text" name="prpty2Val"/>
</td> </td>
</tr> </tr>
<tr>
<th>설명</th>
<td colspan="3"><input type="text" name="prptyDesc"/>
</td>
</tr>
</table> </table>
<!-- grid --> <!-- grid -->
<table id="grid"></table> <table id="grid"></table>
@@ -172,6 +172,9 @@ $(document).ready(function() {
<form id="ajaxForm"> <form id="ajaxForm">
<input type="hidden" name="id"> <input type="hidden" name="id">
<table class="table_row" cellspacing="0"> <table class="table_row" cellspacing="0">
<colgroup>
<col width="200px">
</colgroup>
<tr> <tr>
<th><%= localeMessage.getString("messageTemplate.messageCode") %> <font color="red"> *</font></th> <th><%= localeMessage.getString("messageTemplate.messageCode") %> <font color="red"> *</font></th>
<td><input type="text" name="messageCode" data-required data-warning="메세지 코드는 필수입니다"/></td> <td><input type="text" name="messageCode" data-required data-warning="메세지 코드는 필수입니다"/></td>
@@ -66,22 +66,27 @@
}, },
tooltip: { tooltip: {
trigger: 'item', trigger: 'item',
formatter: '{b}: {c} ({d}%)' formatter: function (params) {
return params.name + ': ' + params.value.toLocaleString() + ' (' + params.percent + '%)';
}
}, },
legend: { legend: {
orient: 'horizontal', orient: 'vertical',
bottom: 10, right: 30,
top: 'middle',
data: ['성공', 'Timeout', '시스템오류'] data: ['성공', 'Timeout', '시스템오류']
}, },
series: [{ series: [{
name: '총건수', name: '총건수',
type: 'pie', type: 'pie',
radius: ['40%', '70%'], radius: ['38%', '65%'],
center: ['50%', '50%'], center: ['44%', '55%'],
avoidLabelOverlap: true, avoidLabelOverlap: true,
label: { label: {
show: true, show: true,
formatter: '{b}: {c}' formatter: function (params) {
return params.name + ': ' + params.value.toLocaleString();
}
}, },
emphasis: { emphasis: {
label: { label: {
@@ -98,18 +103,6 @@
{ value: 0, name: 'Timeout', itemStyle: { color: '#FAC858' } }, { value: 0, name: 'Timeout', itemStyle: { color: '#FAC858' } },
{ value: 0, name: '시스템오류', itemStyle: { color: '#EE6666' } } { value: 0, name: '시스템오류', itemStyle: { color: '#EE6666' } }
] ]
}],
graphic: [{
type: 'text',
left: 'center',
top: 'center',
style: {
text: '0',
textAlign: 'center',
fill: '#333',
fontSize: 24,
fontWeight: 'bold'
}
}] }]
}; };
@@ -119,7 +112,7 @@
// 호출량 차트 // 호출량 차트
var callOption = { var callOption = {
title: { text: '호출량 추이', left: 'center', textStyle: { fontSize: 14 } }, title: { text: '호출량 추이', left: 'center', top: 10, textStyle: { fontSize: 14 } },
tooltip: { tooltip: {
trigger: 'axis', trigger: 'axis',
axisPointer: { type: 'cross', label: { backgroundColor: '#6a7985' } } axisPointer: { type: 'cross', label: { backgroundColor: '#6a7985' } }
@@ -191,13 +184,9 @@
{ value: totalTimeout, name: 'Timeout' }, { value: totalTimeout, name: 'Timeout' },
{ value: totalSystemErr, name: '시스템오류' } { value: totalSystemErr, name: '시스템오류' }
] ]
}],
graphic: [{
style: {
text: grandTotal.toLocaleString()
}
}] }]
}); });
$("#totalDonutCenterText").text(grandTotal.toLocaleString());
// 호출량 차트 업데이트 // 호출량 차트 업데이트
@@ -455,6 +444,9 @@
for (var i = 0; i < colModel.length; i++) { for (var i = 0; i < colModel.length; i++) {
$(this).setColProp(colModel[i].name, { sortable: false }); $(this).setColProp(colModel[i].name, { sortable: false });
} }
// 조회(reloadGrid) 시 컬럼 폭이 고정폭(shrinkToFit:false)으로 남아
// 실제 컨테이너 폭과 어긋나 가로 스크롤이 생기는 것을 방지
$(this).jqGrid('setGridWidth', $('#content_middle').width(), true);
} }
}); });
} }
@@ -501,6 +493,9 @@
for (var i = 0; i < colModel.length; i++) { for (var i = 0; i < colModel.length; i++) {
$(this).setColProp(colModel[i].name, { sortable: false }); $(this).setColProp(colModel[i].name, { sortable: false });
} }
// 조회(reloadGrid) 시 컬럼 폭이 고정폭(shrinkToFit:false)으로 남아
// 실제 컨테이너 폭과 어긋나 가로 스크롤이 생기는 것을 방지
$(this).jqGrid('setGridWidth', $('#content_middle').width(), true);
} }
}); });
} }
@@ -587,10 +582,18 @@
</button> </button>
</div> </div>
<div class="title" id="title">API 일별 통계</div> <div class="title" id="title">API 일별 통계</div>
<table class="search_condition" cellspacing="0"> <table class="search_condition" cellspacing="0" style="table-layout:fixed;">
<colgroup>
<col style="width:150px;">
<col>
<col style="width:150px;">
<col>
<col style="width:150px;">
<col>
</colgroup>
<tbody> <tbody>
<tr> <tr>
<th style="width:100px;">조회기간</th> <th>조회기간</th>
<td colspan="5"> <td colspan="5">
<input type="text" name="searchStartDateTime" value="${param.searchStartDateTime}" style="width:100px;"> <input type="text" name="searchStartDateTime" value="${param.searchStartDateTime}" style="width:100px;">
~ ~
@@ -599,29 +602,29 @@
</td> </td>
</tr> </tr>
<tr> <tr>
<th style="width:100px;">API명</th> <th>API명</th>
<td> <td>
<input type="text" name="searchApiName" value="${param.searchApiName}"> <input type="text" name="searchApiName" value="${param.searchApiName}">
</td> </td>
<th style="width:100px;">업무구분</th> <th>업무구분</th>
<td> <td>
<input type="text" name="searchBizDivCode" value="${param.searchBizDivCode}"> <input type="text" name="searchBizDivCode" value="${param.searchBizDivCode}">
</td> </td>
<th style="width:100px;">인스턴스</th> <th>인스턴스</th>
<td> <td>
<input type="text" name="searchGwInstanceId" value="${param.searchGwInstanceId}"> <input type="text" name="searchGwInstanceId" value="${param.searchGwInstanceId}">
</td> </td>
</tr> </tr>
<tr> <tr>
<th style="width:100px;">클라이언트ID</th> <th>클라이언트ID</th>
<td> <td>
<input type="text" name="searchClientId" value="${param.searchClientId}"> <input type="text" name="searchClientId" value="${param.searchClientId}">
</td> </td>
<th style="width:100px;">Inbound Adapter</th> <th>Inbound Adapter</th>
<td> <td>
<input type="text" name="searchInboundAdapter" value="${param.searchInboundAdapter}"> <input type="text" name="searchInboundAdapter" value="${param.searchInboundAdapter}">
</td> </td>
<th style="width:100px;">Outbound Adapter</th> <th>Outbound Adapter</th>
<td> <td>
<input type="text" name="searchOutboundAdapter" value="${param.searchOutboundAdapter}"> <input type="text" name="searchOutboundAdapter" value="${param.searchOutboundAdapter}">
</td> </td>
@@ -630,7 +633,10 @@
</table> </table>
<!-- 도넛 차트 (상단 50%씩) --> <!-- 도넛 차트 (상단 50%씩) -->
<div class="chart-container"> <div class="chart-container">
<div id="totalDonutChart" class="chart"></div> <div class="chart" style="position:relative; padding:0;">
<div id="totalDonutChart" style="width:100%; height:100%;"></div>
<div id="totalDonutCenterText" style="position:absolute; left:44.5%; top:55%; transform:translate(-50%,-50%); font-size:24px; font-weight:bold; color:#333; pointer-events:none; z-index:10;">0</div>
</div>
<div id="callChart" class="chart"></div> <div id="callChart" class="chart"></div>
</div> </div>
@@ -67,22 +67,27 @@
}, },
tooltip: { tooltip: {
trigger: 'item', trigger: 'item',
formatter: '{b}: {c} ({d}%)' formatter: function (params) {
return params.name + ': ' + params.value.toLocaleString() + ' (' + params.percent + '%)';
}
}, },
legend: { legend: {
orient: 'horizontal', orient: 'vertical',
bottom: 10, right: 30,
top: 'middle',
data: ['성공', 'Timeout', '시스템오류'] data: ['성공', 'Timeout', '시스템오류']
}, },
series: [{ series: [{
name: '총건수', name: '총건수',
type: 'pie', type: 'pie',
radius: ['40%', '70%'], radius: ['38%', '65%'],
center: ['50%', '50%'], center: ['44%', '55%'],
avoidLabelOverlap: true, avoidLabelOverlap: true,
label: { label: {
show: true, show: true,
formatter: '{b}: {c}' formatter: function (params) {
return params.name + ': ' + params.value.toLocaleString();
}
}, },
emphasis: { emphasis: {
label: { label: {
@@ -99,18 +104,6 @@
{ value: 0, name: 'Timeout', itemStyle: { color: '#FAC858' } }, { value: 0, name: 'Timeout', itemStyle: { color: '#FAC858' } },
{ value: 0, name: '시스템오류', itemStyle: { color: '#EE6666' } } { value: 0, name: '시스템오류', itemStyle: { color: '#EE6666' } }
] ]
}],
graphic: [{
type: 'text',
left: 'center',
top: 'center',
style: {
text: '0',
textAlign: 'center',
fill: '#333',
fontSize: 24,
fontWeight: 'bold'
}
}] }]
}; };
@@ -120,7 +113,7 @@
// 호출량 차트 // 호출량 차트
var callOption = { var callOption = {
title: { text: '호출량 추이', left: 'center', textStyle: { fontSize: 14 } }, title: { text: '호출량 추이', left: 'center', top: 10, textStyle: { fontSize: 14 } },
tooltip: { tooltip: {
trigger: 'axis', trigger: 'axis',
axisPointer: { type: 'cross', label: { backgroundColor: '#6a7985' } } axisPointer: { type: 'cross', label: { backgroundColor: '#6a7985' } }
@@ -227,13 +220,9 @@
{ value: totalTimeout, name: 'Timeout' }, { value: totalTimeout, name: 'Timeout' },
{ value: totalSystemErr, name: '시스템오류' } { value: totalSystemErr, name: '시스템오류' }
] ]
}],
graphic: [{
style: {
text: grandTotal.toLocaleString()
}
}] }]
}); });
$("#totalDonutCenterText").text(grandTotal.toLocaleString());
// 호출량 차트 업데이트 // 호출량 차트 업데이트
@@ -457,6 +446,9 @@
for (var i = 0; i < colModel.length; i++) { for (var i = 0; i < colModel.length; i++) {
$(this).setColProp(colModel[i].name, { sortable: false }); $(this).setColProp(colModel[i].name, { sortable: false });
} }
// 조회(reloadGrid) 시 컬럼 폭이 고정폭(shrinkToFit:false)으로 남아
// 실제 컨테이너 폭과 어긋나 가로 스크롤이 생기는 것을 방지
$(this).jqGrid('setGridWidth', $('#content_middle').width(), true);
} }
}); });
} }
@@ -503,6 +495,9 @@
for (var i = 0; i < colModel.length; i++) { for (var i = 0; i < colModel.length; i++) {
$(this).setColProp(colModel[i].name, { sortable: false }); $(this).setColProp(colModel[i].name, { sortable: false });
} }
// 조회(reloadGrid) 시 컬럼 폭이 고정폭(shrinkToFit:false)으로 남아
// 실제 컨테이너 폭과 어긋나 가로 스크롤이 생기는 것을 방지
$(this).jqGrid('setGridWidth', $('#content_middle').width(), true);
} }
}); });
} }
@@ -567,41 +562,47 @@
</button> </button>
</div> </div>
<div class="title" id="title">API 시간별 통계</div> <div class="title" id="title">API 시간별 통계</div>
<table class="search_condition" cellspacing="0"> <table class="search_condition" cellspacing="0" style="table-layout:fixed;">
<colgroup>
<col style="width:150px;">
<col>
<col style="width:150px;">
<col>
<col style="width:150px;">
<col>
</colgroup>
<tbody> <tbody>
<tr> <tr>
<th style="width:100px;">조회일자</th> <th>조회일자</th>
<td colspan="3"> <td colspan="5">
<input type="text" name="searchDate" value="${param.searchDate}" style="width:100px;"> <input type="text" name="searchDate" value="${param.searchDate}" style="width:100px;">
<span style="color:#888; font-size:12px; margin-left:10px;">(선택일 00시 ~ 23시 조회)</span> <span style="color:#888; font-size:12px; margin-left:10px;">(선택일 00시 ~ 23시 조회)</span>
</td> </td>
</tr> </tr>
<tr> <tr>
<th style="width:100px;">API명</th> <th>API명</th>
<td> <td>
<input type="text" name="searchApiName" value="${param.searchApiName}"> <input type="text" name="searchApiName" value="${param.searchApiName}">
</td> </td>
<th style="width:100px;">인스턴스</th> <th>업무구분</th>
<td>
<input type="text" name="searchBizDivCode" value="${param.searchBizDivCode}">
</td>
<th>인스턴스</th>
<td> <td>
<input type="text" name="searchGwInstanceId" value="${param.searchGwInstanceId}"> <input type="text" name="searchGwInstanceId" value="${param.searchGwInstanceId}">
</td> </td>
</tr> </tr>
<tr> <tr>
<th style="width:100px;">업무구분</th> <th>클라이언트ID</th>
<td>
<input type="text" name="searchBizDivCode" value="${param.searchBizDivCode}">
</td>
<th style="width:100px;">클라이언트ID</th>
<td> <td>
<input type="text" name="searchClientId" value="${param.searchClientId}"> <input type="text" name="searchClientId" value="${param.searchClientId}">
</td> </td>
</tr> <th>Inbound Adapter</th>
<tr>
<th style="width:100px;">Inbound Adapter</th>
<td> <td>
<input type="text" name="searchInboundAdapter" value="${param.searchInboundAdapter}"> <input type="text" name="searchInboundAdapter" value="${param.searchInboundAdapter}">
</td> </td>
<th style="width:100px;">Outbound Adapter</th> <th>Outbound Adapter</th>
<td> <td>
<input type="text" name="searchOutboundAdapter" value="${param.searchOutboundAdapter}"> <input type="text" name="searchOutboundAdapter" value="${param.searchOutboundAdapter}">
</td> </td>
@@ -610,7 +611,10 @@
</table> </table>
<!-- 도넛 차트 (상단 50%씩) --> <!-- 도넛 차트 (상단 50%씩) -->
<div class="chart-container"> <div class="chart-container">
<div id="totalDonutChart" class="chart"></div> <div class="chart" style="position:relative; padding:0;">
<div id="totalDonutChart" style="width:100%; height:100%;"></div>
<div id="totalDonutCenterText" style="position:absolute; left:44.5%; top:55%; transform:translate(-50%,-50%); font-size:24px; font-weight:bold; color:#333; pointer-events:none; z-index:10;">0</div>
</div>
<div id="callChart" class="chart"></div> <div id="callChart" class="chart"></div>
</div> </div>
@@ -59,22 +59,27 @@
}, },
tooltip: { tooltip: {
trigger: 'item', trigger: 'item',
formatter: '{b}: {c} ({d}%)' formatter: function (params) {
return params.name + ': ' + params.value.toLocaleString() + ' (' + params.percent + '%)';
}
}, },
legend: { legend: {
orient: 'horizontal', orient: 'vertical',
bottom: 10, right: 30,
top: 'middle',
data: ['성공', 'Timeout', '시스템오류'] data: ['성공', 'Timeout', '시스템오류']
}, },
series: [{ series: [{
name: '총건수', name: '총건수',
type: 'pie', type: 'pie',
radius: ['40%', '70%'], radius: ['38%', '65%'],
center: ['50%', '50%'], center: ['44%', '55%'],
avoidLabelOverlap: true, avoidLabelOverlap: true,
label: { label: {
show: true, show: true,
formatter: '{b}: {c}' formatter: function (params) {
return params.name + ': ' + params.value.toLocaleString();
}
}, },
emphasis: { emphasis: {
label: { label: {
@@ -91,18 +96,6 @@
{ value: 0, name: 'Timeout', itemStyle: { color: '#FAC858' } }, { value: 0, name: 'Timeout', itemStyle: { color: '#FAC858' } },
{ value: 0, name: '시스템오류', itemStyle: { color: '#EE6666' } } { value: 0, name: '시스템오류', itemStyle: { color: '#EE6666' } }
] ]
}],
graphic: [{
type: 'text',
left: 'center',
top: 'center',
style: {
text: '0',
textAlign: 'center',
fill: '#333',
fontSize: 24,
fontWeight: 'bold'
}
}] }]
}; };
@@ -111,8 +104,8 @@
// 호출량 차트 // 호출량 차트
var callOption = { var callOption = {
title: { text: '호출량 추이', left: 'center', textStyle: { fontSize: 14 } }, title: { text: '호출량 추이', left: 'center', top: 10, textStyle: { fontSize: 14 } },
tooltip: { tooltip: {
trigger: 'axis', trigger: 'axis',
axisPointer: { type: 'cross', label: { backgroundColor: '#6a7985' } } axisPointer: { type: 'cross', label: { backgroundColor: '#6a7985' } }
@@ -189,13 +182,9 @@
{ value: totalTimeout, name: 'Timeout' }, { value: totalTimeout, name: 'Timeout' },
{ value: totalSystemErr, name: '시스템오류' } { value: totalSystemErr, name: '시스템오류' }
] ]
}],
graphic: [{
style: {
text: grandTotal.toLocaleString()
}
}] }]
}); });
$("#totalDonutCenterText").text(grandTotal.toLocaleString());
// 호출량 차트 업데이트 // 호출량 차트 업데이트
@@ -470,6 +459,9 @@
for (var i = 0; i < colModel.length; i++) { for (var i = 0; i < colModel.length; i++) {
$(this).setColProp(colModel[i].name, { sortable: false }); $(this).setColProp(colModel[i].name, { sortable: false });
} }
// 조회(reloadGrid) 시 컬럼 폭이 고정폭(shrinkToFit:false)으로 남아
// 실제 컨테이너 폭과 어긋나 가로 스크롤이 생기는 것을 방지
$(this).jqGrid('setGridWidth', $('#content_middle').width(), true);
} }
}); });
} }
@@ -516,6 +508,9 @@
for (var i = 0; i < colModel.length; i++) { for (var i = 0; i < colModel.length; i++) {
$(this).setColProp(colModel[i].name, { sortable: false }); $(this).setColProp(colModel[i].name, { sortable: false });
} }
// 조회(reloadGrid) 시 컬럼 폭이 고정폭(shrinkToFit:false)으로 남아
// 실제 컨테이너 폭과 어긋나 가로 스크롤이 생기는 것을 방지
$(this).jqGrid('setGridWidth', $('#content_middle').width(), true);
} }
}); });
} }
@@ -525,35 +520,26 @@
$("input[name=searchStartDate], input[name=searchEndDate]").inputmask("9999-99-99", { 'autoUnmask': true }); $("input[name=searchStartDate], input[name=searchEndDate]").inputmask("9999-99-99", { 'autoUnmask': true });
$("input[name=searchStartTime], input[name=searchEndTime]").inputmask("99:99", { 'autoUnmask': true }); $("input[name=searchStartTime], input[name=searchEndTime]").inputmask("99:99", { 'autoUnmask': true });
// 기본값 설정 (현재 시간 기준 1시간 전 정각 ~ 현재 정각) // 기본값 설정 (현재 시간(분) 기준 1시간 전 ~ 현재 시간(분))
var now = new Date(); var now = new Date();
var endHour = now.getHours(); var oneHourAgo = new Date(now.getTime() - 60 * 60 * 1000);
var startHour = endHour - 1;
var startDate, endDate;
if (startHour < 0) { function formatDate(d) {
startHour = 23; return d.getFullYear() + '-' +
var yesterday = new Date(now); String(d.getMonth() + 1).padStart(2, '0') + '-' +
yesterday.setDate(yesterday.getDate() - 1); String(d.getDate()).padStart(2, '0');
startDate = yesterday.getFullYear() + '-' + }
String(yesterday.getMonth() + 1).padStart(2, '0') + '-' + function formatTime(d) {
String(yesterday.getDate()).padStart(2, '0'); return String(d.getHours()).padStart(2, '0') + ':' + String(d.getMinutes()).padStart(2, '0');
endDate = now.getFullYear() + '-' +
String(now.getMonth() + 1).padStart(2, '0') + '-' +
String(now.getDate()).padStart(2, '0');
} else {
var today = getToday();
startDate = today;
endDate = today;
} }
if (!$("input[name=searchStartDate]").val()) { if (!$("input[name=searchStartDate]").val()) {
$("input[name=searchStartDate]").val(startDate); $("input[name=searchStartDate]").val(formatDate(oneHourAgo));
$("input[name=searchStartTime]").val(String(startHour).padStart(2, '0') + ':00'); $("input[name=searchStartTime]").val(formatTime(oneHourAgo));
} }
if (!$("input[name=searchEndDate]").val()) { if (!$("input[name=searchEndDate]").val()) {
$("input[name=searchEndDate]").val(endDate); $("input[name=searchEndDate]").val(formatDate(now));
$("input[name=searchEndTime]").val(String(endHour).padStart(2, '0') + ':00'); $("input[name=searchEndTime]").val(formatTime(now));
} }
initCharts(); initCharts();
@@ -628,7 +614,10 @@
</table> </table>
<!-- 도넛 차트 (상단 50%씩) --> <!-- 도넛 차트 (상단 50%씩) -->
<div class="chart-container"> <div class="chart-container">
<div id="totalDonutChart" class="chart"></div> <div class="chart" style="position:relative; padding:0;">
<div id="totalDonutChart" style="width:100%; height:100%;"></div>
<div id="totalDonutCenterText" style="position:absolute; left:44.5%; top:55%; transform:translate(-50%,-50%); font-size:24px; font-weight:bold; color:#333; pointer-events:none; z-index:10;">0</div>
</div>
<div id="callChart" class="chart"></div> <div id="callChart" class="chart"></div>
</div> </div>
@@ -66,22 +66,27 @@
}, },
tooltip: { tooltip: {
trigger: 'item', trigger: 'item',
formatter: '{b}: {c} ({d}%)' formatter: function (params) {
return params.name + ': ' + params.value.toLocaleString() + ' (' + params.percent + '%)';
}
}, },
legend: { legend: {
orient: 'horizontal', orient: 'vertical',
bottom: 10, right: 30,
top: 'middle',
data: ['성공', 'Timeout', '시스템오류'] data: ['성공', 'Timeout', '시스템오류']
}, },
series: [{ series: [{
name: '총건수', name: '총건수',
type: 'pie', type: 'pie',
radius: ['40%', '70%'], radius: ['38%', '65%'],
center: ['50%', '50%'], center: ['44%', '55%'],
avoidLabelOverlap: true, avoidLabelOverlap: true,
label: { label: {
show: true, show: true,
formatter: '{b}: {c}' formatter: function (params) {
return params.name + ': ' + params.value.toLocaleString();
}
}, },
emphasis: { emphasis: {
label: { label: {
@@ -98,18 +103,6 @@
{ value: 0, name: 'Timeout', itemStyle: { color: '#FAC858' } }, { value: 0, name: 'Timeout', itemStyle: { color: '#FAC858' } },
{ value: 0, name: '시스템오류', itemStyle: { color: '#EE6666' } } { value: 0, name: '시스템오류', itemStyle: { color: '#EE6666' } }
] ]
}],
graphic: [{
type: 'text',
left: 'center',
top: 'center',
style: {
text: '0',
textAlign: 'center',
fill: '#333',
fontSize: 24,
fontWeight: 'bold'
}
}] }]
}; };
@@ -119,7 +112,7 @@
// 호출량 차트 // 호출량 차트
var callOption = { var callOption = {
title: { text: '호출량 추이', left: 'center', textStyle: { fontSize: 14 } }, title: { text: '호출량 추이', left: 'center', top: 10, textStyle: { fontSize: 14 } },
tooltip: { tooltip: {
trigger: 'axis', trigger: 'axis',
axisPointer: { type: 'cross', label: { backgroundColor: '#6a7985' } } axisPointer: { type: 'cross', label: { backgroundColor: '#6a7985' } }
@@ -189,13 +182,9 @@
{ value: totalTimeout, name: 'Timeout' }, { value: totalTimeout, name: 'Timeout' },
{ value: totalSystemErr, name: '시스템오류' } { value: totalSystemErr, name: '시스템오류' }
] ]
}],
graphic: [{
style: {
text: grandTotal.toLocaleString()
}
}] }]
}); });
$("#totalDonutCenterText").text(grandTotal.toLocaleString());
@@ -460,6 +449,9 @@
for (var i = 0; i < colModel.length; i++) { for (var i = 0; i < colModel.length; i++) {
$(this).setColProp(colModel[i].name, { sortable: false }); $(this).setColProp(colModel[i].name, { sortable: false });
} }
// 조회(reloadGrid) 시 컬럼 폭이 고정폭(shrinkToFit:false)으로 남아
// 실제 컨테이너 폭과 어긋나 가로 스크롤이 생기는 것을 방지
$(this).jqGrid('setGridWidth', $('#content_middle').width(), true);
} }
}); });
} }
@@ -506,6 +498,9 @@
for (var i = 0; i < colModel.length; i++) { for (var i = 0; i < colModel.length; i++) {
$(this).setColProp(colModel[i].name, { sortable: false }); $(this).setColProp(colModel[i].name, { sortable: false });
} }
// 조회(reloadGrid) 시 컬럼 폭이 고정폭(shrinkToFit:false)으로 남아
// 실제 컨테이너 폭과 어긋나 가로 스크롤이 생기는 것을 방지
$(this).jqGrid('setGridWidth', $('#content_middle').width(), true);
} }
}); });
} }
@@ -578,11 +573,19 @@
</button> </button>
</div> </div>
<div class="title" id="title">API 월별 통계</div> <div class="title" id="title">API 월별 통계</div>
<table class="search_condition" cellspacing="0"> <table class="search_condition" cellspacing="0" style="table-layout:fixed;">
<colgroup>
<col style="width:150px;">
<col>
<col style="width:150px;">
<col>
<col style="width:150px;">
<col>
</colgroup>
<tbody> <tbody>
<tr> <tr>
<th style="width:100px;">조회기간</th> <th>조회기간</th>
<td colspan="3"> <td colspan="5">
<input type="text" name="searchStartDateTime" value="${param.searchStartDateTime}" style="width:100px;" placeholder="YYYY-MM"> <input type="text" name="searchStartDateTime" value="${param.searchStartDateTime}" style="width:100px;" placeholder="YYYY-MM">
~ ~
<input type="text" name="searchEndDateTime" value="${param.searchEndDateTime}" style="width:100px;" placeholder="YYYY-MM"> <input type="text" name="searchEndDateTime" value="${param.searchEndDateTime}" style="width:100px;" placeholder="YYYY-MM">
@@ -590,31 +593,29 @@
</td> </td>
</tr> </tr>
<tr> <tr>
<th style="width:100px;">API명</th> <th>API명</th>
<td> <td>
<input type="text" name="searchApiName" value="${param.searchApiName}"> <input type="text" name="searchApiName" value="${param.searchApiName}">
</td> </td>
<th style="width:100px;">인스턴스</th> <th>업무구분</th>
<td>
<input type="text" name="searchBizDivCode" value="${param.searchBizDivCode}">
</td>
<th>인스턴스</th>
<td> <td>
<input type="text" name="searchGwInstanceId" value="${param.searchGwInstanceId}"> <input type="text" name="searchGwInstanceId" value="${param.searchGwInstanceId}">
</td> </td>
</tr> </tr>
<tr> <tr>
<th style="width:100px;">업무구분</th> <th>클라이언트ID</th>
<td>
<input type="text" name="searchBizDivCode" value="${param.searchBizDivCode}">
</td>
<th style="width:100px;">클라이언트ID</th>
<td> <td>
<input type="text" name="searchClientId" value="${param.searchClientId}"> <input type="text" name="searchClientId" value="${param.searchClientId}">
</td> </td>
</tr> <th>Inbound Adapter</th>
<tr>
<th style="width:100px;">Inbound Adapter</th>
<td> <td>
<input type="text" name="searchInboundAdapter" value="${param.searchInboundAdapter}"> <input type="text" name="searchInboundAdapter" value="${param.searchInboundAdapter}">
</td> </td>
<th style="width:100px;">Outbound Adapter</th> <th>Outbound Adapter</th>
<td> <td>
<input type="text" name="searchOutboundAdapter" value="${param.searchOutboundAdapter}"> <input type="text" name="searchOutboundAdapter" value="${param.searchOutboundAdapter}">
</td> </td>
@@ -623,7 +624,10 @@
</table> </table>
<!-- 도넛 차트 (상단 50%씩) --> <!-- 도넛 차트 (상단 50%씩) -->
<div class="chart-container"> <div class="chart-container">
<div id="totalDonutChart" class="chart"></div> <div class="chart" style="position:relative; padding:0;">
<div id="totalDonutChart" style="width:100%; height:100%;"></div>
<div id="totalDonutCenterText" style="position:absolute; left:44.5%; top:55%; transform:translate(-50%,-50%); font-size:24px; font-weight:bold; color:#333; pointer-events:none; z-index:10;">0</div>
</div>
<div id="callChart" class="chart"></div> <div id="callChart" class="chart"></div>
</div> </div>
@@ -66,22 +66,27 @@
}, },
tooltip: { tooltip: {
trigger: 'item', trigger: 'item',
formatter: '{b}: {c} ({d}%)' formatter: function (params) {
return params.name + ': ' + params.value.toLocaleString() + ' (' + params.percent + '%)';
}
}, },
legend: { legend: {
orient: 'horizontal', orient: 'vertical',
bottom: 10, right: 30,
top: 'middle',
data: ['성공', 'Timeout', '시스템오류'] data: ['성공', 'Timeout', '시스템오류']
}, },
series: [{ series: [{
name: '총건수', name: '총건수',
type: 'pie', type: 'pie',
radius: ['40%', '70%'], radius: ['38%', '65%'],
center: ['50%', '50%'], center: ['44%', '55%'],
avoidLabelOverlap: true, avoidLabelOverlap: true,
label: { label: {
show: true, show: true,
formatter: '{b}: {c}' formatter: function (params) {
return params.name + ': ' + params.value.toLocaleString();
}
}, },
emphasis: { emphasis: {
label: { label: {
@@ -98,18 +103,6 @@
{ value: 0, name: 'Timeout', itemStyle: { color: '#FAC858' } }, { value: 0, name: 'Timeout', itemStyle: { color: '#FAC858' } },
{ value: 0, name: '시스템오류', itemStyle: { color: '#EE6666' } } { value: 0, name: '시스템오류', itemStyle: { color: '#EE6666' } }
] ]
}],
graphic: [{
type: 'text',
left: 'center',
top: 'center',
style: {
text: '0',
textAlign: 'center',
fill: '#333',
fontSize: 24,
fontWeight: 'bold'
}
}] }]
}; };
@@ -119,7 +112,7 @@
// 호출량 차트 // 호출량 차트
var callOption = { var callOption = {
title: { text: '호출량 추이', left: 'center', textStyle: { fontSize: 14 } }, title: { text: '호출량 추이', left: 'center', top: 10, textStyle: { fontSize: 14 } },
tooltip: { tooltip: {
trigger: 'axis', trigger: 'axis',
axisPointer: { type: 'cross', label: { backgroundColor: '#6a7985' } } axisPointer: { type: 'cross', label: { backgroundColor: '#6a7985' } }
@@ -191,13 +184,9 @@
{ value: totalTimeout, name: 'Timeout' }, { value: totalTimeout, name: 'Timeout' },
{ value: totalSystemErr, name: '시스템오류' } { value: totalSystemErr, name: '시스템오류' }
] ]
}],
graphic: [{
style: {
text: grandTotal.toLocaleString()
}
}] }]
}); });
$("#totalDonutCenterText").text(grandTotal.toLocaleString());
@@ -436,6 +425,9 @@
for (var i = 0; i < colModel.length; i++) { for (var i = 0; i < colModel.length; i++) {
$(this).setColProp(colModel[i].name, { sortable: false }); $(this).setColProp(colModel[i].name, { sortable: false });
} }
// 조회(reloadGrid) 시 컬럼 폭이 고정폭(shrinkToFit:false)으로 남아
// 실제 컨테이너 폭과 어긋나 가로 스크롤이 생기는 것을 방지
$(this).jqGrid('setGridWidth', $('#content_middle').width(), true);
} }
}); });
} }
@@ -482,6 +474,9 @@
for (var i = 0; i < colModel.length; i++) { for (var i = 0; i < colModel.length; i++) {
$(this).setColProp(colModel[i].name, { sortable: false }); $(this).setColProp(colModel[i].name, { sortable: false });
} }
// 조회(reloadGrid) 시 컬럼 폭이 고정폭(shrinkToFit:false)으로 남아
// 실제 컨테이너 폭과 어긋나 가로 스크롤이 생기는 것을 방지
$(this).jqGrid('setGridWidth', $('#content_middle').width(), true);
} }
}); });
} }
@@ -549,42 +544,48 @@
</button> </button>
</div> </div>
<div class="title" id="title">API 연간 통계</div> <div class="title" id="title">API 연간 통계</div>
<table class="search_condition" cellspacing="0"> <table class="search_condition" cellspacing="0" style="table-layout:fixed;">
<colgroup>
<col style="width:150px;">
<col>
<col style="width:150px;">
<col>
<col style="width:150px;">
<col>
</colgroup>
<tbody> <tbody>
<tr> <tr>
<th style="width:100px;">조회기간</th> <th>조회기간</th>
<td colspan="3"> <td colspan="5">
<input type="text" name="searchStartDateTime" value="${param.searchStartDateTime}" style="width:100px;" placeholder="YYYY"> <input type="text" name="searchStartDateTime" value="${param.searchStartDateTime}" style="width:100px;" placeholder="YYYY">
~ ~
<input type="text" name="searchEndDateTime" value="${param.searchEndDateTime}" style="width:100px;" placeholder="YYYY"> <input type="text" name="searchEndDateTime" value="${param.searchEndDateTime}" style="width:100px;" placeholder="YYYY">
</td> </td>
</tr> </tr>
<tr> <tr>
<th style="width:100px;">API명</th> <th>API명</th>
<td> <td>
<input type="text" name="searchApiName" value="${param.searchApiName}"> <input type="text" name="searchApiName" value="${param.searchApiName}">
</td> </td>
<th style="width:100px;">인스턴스</th> <th>업무구분</th>
<td>
<input type="text" name="searchBizDivCode" value="${param.searchBizDivCode}">
</td>
<th>인스턴스</th>
<td> <td>
<input type="text" name="searchGwInstanceId" value="${param.searchGwInstanceId}"> <input type="text" name="searchGwInstanceId" value="${param.searchGwInstanceId}">
</td> </td>
</tr> </tr>
<tr> <tr>
<th style="width:100px;">업무구분</th> <th>클라이언트ID</th>
<td>
<input type="text" name="searchBizDivCode" value="${param.searchBizDivCode}">
</td>
<th style="width:100px;">클라이언트ID</th>
<td> <td>
<input type="text" name="searchClientId" value="${param.searchClientId}"> <input type="text" name="searchClientId" value="${param.searchClientId}">
</td> </td>
</tr> <th>Inbound Adapter</th>
<tr>
<th style="width:100px;">Inbound Adapter</th>
<td> <td>
<input type="text" name="searchInboundAdapter" value="${param.searchInboundAdapter}"> <input type="text" name="searchInboundAdapter" value="${param.searchInboundAdapter}">
</td> </td>
<th style="width:100px;">Outbound Adapter</th> <th>Outbound Adapter</th>
<td> <td>
<input type="text" name="searchOutboundAdapter" value="${param.searchOutboundAdapter}"> <input type="text" name="searchOutboundAdapter" value="${param.searchOutboundAdapter}">
</td> </td>
@@ -593,7 +594,10 @@
</table> </table>
<!-- 도넛 차트 (상단 50%씩) --> <!-- 도넛 차트 (상단 50%씩) -->
<div class="chart-container"> <div class="chart-container">
<div id="totalDonutChart" class="chart"></div> <div class="chart" style="position:relative; padding:0;">
<div id="totalDonutChart" style="width:100%; height:100%;"></div>
<div id="totalDonutCenterText" style="position:absolute; left:44.5%; top:55%; transform:translate(-50%,-50%); font-size:24px; font-weight:bold; color:#333; pointer-events:none; z-index:10;">0</div>
</div>
<div id="callChart" class="chart"></div> <div id="callChart" class="chart"></div>
</div> </div>
@@ -76,6 +76,15 @@
return cellvalue.substring(1, 4); return cellvalue.substring(1, 4);
} }
function apiStatusFormatter(cellvalue, options, rowObject) {
switch (cellvalue) {
case 'E': return '장애';
case 'D': return '지연';
case 'C': return '점검';
default: return '정상';
}
}
function list() { function list() {
detail() detail()
var gridPostData = getSearchForJqgrid("cmd", "LIST"); //jqgrid에서는 object 로 var gridPostData = getSearchForJqgrid("cmd", "LIST"); //jqgrid에서는 object 로
@@ -90,6 +99,7 @@
'API FULL PATH', 'API FULL PATH',
'요청', '요청',
'응답', '응답',
'상태',
'작성자', '작성자',
'가상응답여부', '가상응답여부',
'변경일시(*)', '변경일시(*)',
@@ -99,11 +109,12 @@
{name: 'eaiSvcName', align: 'left', width: '100', sortable: true}, {name: 'eaiSvcName', align: 'left', width: '100', sortable: true},
{name: 'eaiSvcDesc', align: 'left', sortable: false}, {name: 'eaiSvcDesc', align: 'left', sortable: false},
{name: 'apiFullPath', align: 'left', sortable: false}, {name: 'apiFullPath', align: 'left', sortable: false},
{name: 'fromAdapter', align: 'center', width: '40', formatter: adapterNameShortFormatter, sortable: false}, {name: 'fromAdapter', align: 'center', width: '30', formatter: adapterNameShortFormatter, sortable: false},
{name: 'toAdapter', align: 'center', width: '40', formatter: adapterNameShortFormatter, sortable: false}, {name: 'toAdapter', align: 'center', width: '30', formatter: adapterNameShortFormatter, sortable: false},
{name: 'author', align: 'center', width: '60', sortable: false}, {name: 'statusCode', align: 'center', width: '30', formatter: apiStatusFormatter, sortable: false},
{name: 'author', align: 'center', width: '40', sortable: false},
{name : 'simYn', align : 'center' , width:'40', hidden: true }, {name : 'simYn', align : 'center' , width:'40', hidden: true },
{name : 'lastModifiedDate', align : 'center' , width:'40', sortable: true}, {name : 'lastModifiedDate', align : 'center' , width:'60', sortable: true},
{name: 'syncAsyncType', align: 'center', width: '40', hidden: true}, {name: 'syncAsyncType', align: 'center', width: '40', hidden: true},
], ],
jsonReader: { jsonReader: {
@@ -77,13 +77,17 @@
display: inline-block; display: inline-block;
vertical-align: middle; vertical-align: middle;
} }
/* iframe 모달(showModal) 콘텐츠 패딩 제거 — iframe 이 다이얼로그 폭을 최대한 사용해 내부 가로스크롤 방지 */
.ui-dialog .ui-dialog-content { padding: 0 !important; }
</style> </style>
<script language="javascript" > <script language="javascript" >
var isDetail = false; var isDetail = false;
var url ='<c:url value="/onl/transaction/apim/apiInterfaceMan.json" />'; var url ='<c:url value="/onl/transaction/apim/apiInterfaceMan.json" />';
const url_excel ='<c:url value="/onl/transaction/apim/apiInterfaceMan.excel" />'; const url_excel ='<c:url value="/onl/transaction/apim/apiInterfaceMan.excel" />';
var url_spec = '<c:url value="/onl/transaction/apim/apiSpecMan.view"/>'; var url_spec = '<c:url value="/onl/transaction/apim/apiSpecMan.view"/>';
var url_openapi_spec = '<c:url value="/onl/transaction/apim/djbApiSpecMan.view"/>';
var returnUrl ; var returnUrl ;
let headerValues = []; let headerValues = [];
let headerNames = []; let headerNames = [];
@@ -993,6 +997,33 @@
showModal(popupUrl, args, 1200, 800); showModal(popupUrl, args, 1200, 800);
} }
// v2: OpenAPI Spec — 기본은 모달(iframe), Shift+클릭은 새 창.
// 컨텍스트는 전부 query string 으로 전달(dialogArguments 미사용).
function showOpenApiSpecPopup(e) {
var eaiSvcName = getFullSvcName();
var eaiSvcDesc = $('#eaiSvcDesc').val();
var apipath = $('#apiFullPath').val(); // METHOD|URL ('|' 포함 → 인코딩 필수)
var contentType = $('#contentType').val();
var baseUrl = url_openapi_spec
+ '?cmd=DETAIL'
+ '&eaiSvcName=' + encodeURIComponent(eaiSvcName)
+ '&apipath=' + encodeURIComponent(apipath)
+ '&eaiSvcDesc=' + encodeURIComponent(eaiSvcDesc)
+ '&contentType=' + encodeURIComponent(contentType);
if (e && e.shiftKey) {
// Shift+클릭: 새 창. window.open 은 serviceType/menuId 자동 부착 안 되므로 직접 붙임.
window.open(urlAddServiceType(baseUrl + '&menuId=' + encodeURIComponent(getMenuId())), '_blank');
} else {
// 기본: 모달 + iframe. showModal 이 serviceType/menuId/pop 을 자동 부착.
// 콘텐츠 고정폭 1600 + 다이얼로그 chrome(패딩/보더 ~40px) 때문에 iframe 내부폭이 1600 미만이면 가로스크롤 발생.
// 뷰포트 내에서 최대한 크게 열어(캡 1720) iframe 내부폭이 1600 이상 확보되게 한다.
var vw = $(window).width(), vh = $(window).height();
var modalW = Math.min(vw - 12, 1720);
var modalH = Math.min(vh - 20, 900);
showModal(baseUrl, { title: 'OpenAPI Spec' }, modalW, modalH);
}
}
$(document).ready(function() { $(document).ready(function() {
makeValidate(); makeValidate();
// var bootstrapButton = $.fn.button.noConflict() // var bootstrapButton = $.fn.button.noConflict()
@@ -1007,9 +1038,14 @@
} }
init(url,key,detail); init(url,key,detail);
// v1: 기존 API 스펙 (모달) — 기능 비교용 복원
$("#btn_api_spec").click(function() { $("#btn_api_spec").click(function() {
showApiSpecPopup(); showApiSpecPopup();
}); });
// v2: 신규 OpenAPI Spec (새 탭)
$("#btn_openapi_spec").click(function(e) {
showOpenApiSpecPopup(e);
});
$("#btn_modify").click(modifyApi); $("#btn_modify").click(modifyApi);
$("#btn_delete").click(function(){ $("#btn_delete").click(function(){
@@ -1508,7 +1544,10 @@
<button type="button" class="cssbtn" id="btn_json_export" level="R" status="DETAIL,NEW"><i class="material-icons">download</i> <%=localeMessage.getString("button.exportJson")%></button></button> <button type="button" class="cssbtn" id="btn_json_export" level="R" status="DETAIL,NEW"><i class="material-icons">download</i> <%=localeMessage.getString("button.exportJson")%></button></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_delete" level="W" status="DETAIL"><i class="material-icons">delete</i> <%= localeMessage.getString("button.delete") %></button>
<button type="button" class="cssbtn" id="btn_modify" level="R" status="DETAIL,NEW"><i class="material-icons">save</i> <%= localeMessage.getString("button.modify") %></button> <button type="button" class="cssbtn" id="btn_modify" level="R" status="DETAIL,NEW"><i class="material-icons">save</i> <%= localeMessage.getString("button.modify") %></button>
<button type="button" class="cssbtn" id="btn_api_spec" level="R" status="DETAIL,NEW"><i class="material-icons">description</i> API 스펙</button> <!-- v1: 기존 API 스펙 (모달) — 기능 비교용 복원 -->
<button type="button" class="cssbtn" id="btn_api_spec" level="R" status="DETAIL"><i class="material-icons">description</i> API 스펙(삭제예정)</button>
<!-- v2: 신규 OpenAPI Spec (새 탭) -->
<button type="button" class="cssbtn" id="btn_openapi_spec" level="R" status="DETAIL"><i class="material-icons">api</i> OpenAPI Spec</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> <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>
<div class="title" id="title" style="font-size:1.4em">${rmsMenuName}</div> <div class="title" id="title" style="font-size:1.4em">${rmsMenuName}</div>
@@ -23,7 +23,7 @@
</style> </style>
<script language="javascript"> <script language="javascript">
var url = '<c:url value="/onl/transaction/apim/apiSpecMan.json" />'; var url = '<c:url value="/onl/transaction/apim/apiSpecMan.json" />';
var url_org ='<c:url value="/onl/transaction/apim/apiSpecManPopup.view" />'; var url_org ='<c:url value="/onl/transaction/apim/apiSpecMan.view" />';
var isDetail = false; var isDetail = false;
var sampleRequestEditor, sampleResponseEditor, testbedSpecEditor; var sampleRequestEditor, sampleResponseEditor, testbedSpecEditor;
@@ -0,0 +1,132 @@
<%@ page language="java" contentType="text/html; charset=utf-8"%>
<%@ page import="java.io.*"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ 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>API 그룹 선택</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<jsp:include page="/jsp/common/include/css_custom.jsp"/>
<jsp:include page="/jsp/common/include/css.jsp"/>
<jsp:include page="/jsp/common/include/script.jsp"/>
<script language="javascript">
var url = '<c:url value="/onl/apim/apigroup/apiGroupMan.json" />';
function search() {
var postData = getSearchForJqgrid("cmd", "LIST");
$("#grid").setGridParam({url: url, postData: postData, page: 1}).trigger("reloadGrid");
}
$(document).ready(function() {
var urlParams = new URLSearchParams(window.location.search);
var selectedGroupIds = urlParams.get("selectedGroupIds");
if (selectedGroupIds) {
selectedGroupIds = selectedGroupIds.split(','); // 콤마로 구분된 ID들을 배열로 변환
} else {
selectedGroupIds = [];
}
$('#grid').jqGrid({
datatype: "json",
mtype: 'POST',
url: url,
postData: getSearchForJqgrid("cmd", "LIST"),
colNames: ['API 그룹 ID', 'API 그룹 명', '설명'],
colModel: [
{name: 'id', hidden: true},
{name: 'groupName', align: 'center', width: "180"},
{name: 'groupDesc', align: 'left', width: "200"}
],
jsonReader: {
repeatitems: false
},
pager: $('#pager'),
page: '${param.page}',
rowNum: '${rmsDefaultRowNum}',
autoheight: true,
height: 'auto',
width: 410,
autowidth: false,
viewrecords: true,
shrinkToFit: false,
multiselect: true,
multiboxonly: false,
loadComplete: function(data) {
var $grid = $(this);
$.each($grid.getDataIDs(), function(_, id) {
var rowData = $grid.getRowData(id);
if ($.inArray(rowData.id, selectedGroupIds) !== -1) {
$grid.jqGrid('setSelection', id, true);
}
});
}
});
$("#btn_search").click(search);
$("#btn_save").click(function() {
var selectedRows = $('#grid').getGridParam('selarrrow');
var selectedGroups = [];
if (selectedRows.length > 0) {
selectedGroups = selectedRows.map(function(rowid) {
var rowData = $('#grid').getRowData(rowid);
return {
id: rowData.id,
groupName: rowData.groupName
};
});
}
// 부모 창의 selectApiGroup 함수 호출
if (window.parent && typeof window.parent.selectApiGroup === "function") {
window.parent.selectApiGroup(selectedGroups);
window.close();
}
});
$("#btn_close").click(function () {
window.close();
});
// 검색어 입력 후 엔터 키 처리
$("input[name=searchGroupName]").keydown(function(key) {
if (key.keyCode == 13) {
search();
}
});
});
</script>
</head>
<body>
<div class="popup_box">
<div class="search_wrap">
<button type="button" class="cssbtn" id="btn_save" level="W">
<i class="material-icons">save</i> <%= localeMessage.getString("button.save") %>
</button>
<button type="button" class="cssbtn" id="btn_search" level="R">
<i class="material-icons">search</i> <%= localeMessage.getString("button.search") %>
</button>
<button type="button" class="cssbtn" id="btn_close" level="R" status="DETAIL"><i
class="material-icons">close</i> <%= localeMessage.getString("button.close") %>
</button>
</div>
<div class="title"></div>
<table class="search_condition" cellspacing="0">
<tr>
<th style="width:80px;">API 그룹 명</th>
<td>
<input type="text" name="searchGroupName" value="${param.searchGroupName}">
</td>
</tr>
</table>
<table id="grid"></table>
<div id="pager"></div>
</div>
</body>
</html>
@@ -0,0 +1,173 @@
<%@ page language="java" contentType="text/html; charset=utf-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%
response.setHeader("Pragma", "No-cache");
response.setHeader("Cache-Control", "no-cache");
response.setHeader("Expires", "0");
%>
<!doctype html>
<html lang="ko">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=1600" />
<title>OpenAPI Spec 에디터</title>
<%-- Tailwind (벤더된 Play CDN JS, 오프라인) --%>
<script src="<c:url value='/plugins/openapi/tailwind.js'/>"></script>
<script>
tailwind.config = { theme: { extend: { colors: { brand: {
50:'#eff6ff', 100:'#dbeafe', 500:'#3b82f6', 600:'#2563eb', 700:'#1d4ed8'
} } } } };
</script>
<%-- Swagger UI 5 (벤더) --%>
<link rel="stylesheet" href="<c:url value='/plugins/swaggerUI/swagger-ui.css'/>" />
<script src="<c:url value='/plugins/swaggerUI/swagger-ui-bundle.js'/>"></script>
<script src="<c:url value='/plugins/swaggerUI/swagger-ui-standalone-preset.js'/>"></script>
<script src="<c:url value='/plugins/swaggerUI/djb-swagger-i18n.js'/>" charset="UTF-8"></script>
<%-- js-yaml (벤더) --%>
<script src="<c:url value='/plugins/openapi/js-yaml.min.js'/>"></script>
<%-- CodeMirror (YAML 편집기 — Monaco 대체, admin addon) --%>
<link rel="stylesheet" href="<c:url value='/addon/codemirror/lib/codemirror.css'/>" />
<script src="<c:url value='/addon/codemirror/lib/codemirror.js'/>"></script>
<script src="<c:url value='/addon/codemirror/mode/yaml/yaml.js'/>"></script>
<script src="<c:url value='/addon/codemirror/mode/javascript/javascript.js'/>"></script>
<%-- jQuery + Summernote(lite, 무-Bootstrap) — 설명 리치 에디터 --%>
<link rel="stylesheet" href="<c:url value='/addon/summernote/summernote-lite.css'/>" />
<%-- 개발자포탈과 동일한 에디터 콘텐츠 스타일(.editor-content, Noto Sans KR 16px) --%>
<link rel="stylesheet" href="<c:url value='/js/djb/apispec/editor-content.css'/>" />
<script src="<c:url value='/js/jquery-1.12.1.min.js'/>"></script>
<script src="<c:url value='/addon/summernote/summernote-lite.min.js'/>"></script>
<script src="<c:url value='/addon/summernote/lang/summernote-ko-KR.js'/>"></script>
<link rel="stylesheet" href="<c:url value='/js/djb/apispec/styles.css'/>" />
<style>
/* 페이지 자체 세로 스크롤 제거 — 뷰포트 고정, 내부 패널만 스크롤 */
html, body { height: 100%; margin: 0; overflow-x: auto; overflow-y: hidden; }
#djb-wrap { height: 100vh; display: flex; flex-direction: column; }
#djb-wrap > header, #djb-wrap > nav { flex: 0 0 auto; }
#body { flex: 1 1 auto; min-height: 0 !important; overflow: hidden; }
#form-panel { display: flex; flex-direction: column; min-height: 0; overflow: hidden; }
#form-content { flex: 1 1 auto; min-height: 0; overflow-y: auto; }
#preview-panel { display: flex; flex-direction: column; min-height: 0; overflow: hidden; }
#preview-swagger, #preview-monaco { flex: 1 1 auto; min-height: 0; height: auto !important; overflow: auto; }
#preview-monaco .CodeMirror { height: 100%; }
/* 패널 내부 sticky 는 flex 행으로 (스크롤 컨테이너는 form-content / preview) */
#form-panel > .sticky, #preview-panel > .sticky { position: static !important; }
</style>
<script>
window.DJB_CTX = {
eaiSvcName: "${param.eaiSvcName}",
apiName: "${param.eaiSvcDesc}",
detailUrl: "<c:url value='/onl/transaction/apim/djbApiSpecMan.json'/>?cmd=DETAIL_SPEC&serviceType=APIGW",
saveUrl: "<c:url value='/onl/transaction/apim/djbApiSpecMan.json'/>?cmd=SAVE&serviceType=APIGW",
swaggerBase: "<c:url value='/plugins/swaggerUI/'/>",
gwAddress: "${gwAddress}",
orgPopupUrl: "<c:url value='/onl/transaction/apim/apiSpecMan.view'/>",
groupPopupUrl: "<c:url value='/onl/transaction/apim/djbApiSpecMan.view'/>?cmd=GROUP_POPUP",
jsonUrl: "<c:url value='/onl/transaction/apim/djbApiSpecMan.json'/>"
};
</script>
</head>
<body class="bg-slate-50 text-slate-800 antialiased" style="min-width:1600px;">
<div id="djb-wrap" class="mx-auto" style="width:1600px;">
<!-- ===== Header ===== -->
<header class="h-14 flex items-center justify-between px-6 bg-white border-b border-slate-200 sticky top-0 z-30">
<div class="flex items-center gap-3">
<div class="font-semibold text-slate-900">OpenAPI Spec 에디터</div>
<span class="text-slate-300">|</span>
<span class="text-sm text-slate-500">인터페이스:</span>
<span class="text-sm font-medium text-slate-800" id="hdr-project-name">${param.eaiSvcName}</span>
<span class="ml-2 px-2 py-0.5 text-[11px] rounded-full bg-slate-100 text-slate-600 border border-slate-200" id="hdr-version">v1.0.0</span>
<span id="hdr-source" class="hidden ml-1 px-2 py-0.5 text-[11px] rounded-full bg-emerald-50 text-emerald-700 border border-emerald-200"></span>
</div>
<div class="flex items-center gap-2">
<button id="btn-newwin" class="hidden px-3 py-1.5 text-sm rounded border border-slate-300 bg-white hover:bg-slate-50">새창으로 띄우기</button>
<button id="btn-regen" class="px-3 py-1.5 text-sm rounded border border-slate-300 bg-white hover:bg-slate-50">자동 생성(초기화)</button>
<button id="btn-save" class="px-3 py-1.5 text-sm rounded bg-emerald-600 text-white hover:bg-emerald-700">저장</button>
<div class="relative">
<button id="btn-export" class="px-3 py-1.5 text-sm rounded bg-brand-600 text-white hover:bg-brand-700">내보내기 ▾</button>
<div id="export-menu" class="hidden absolute right-0 mt-1 w-44 bg-white border border-slate-200 rounded shadow-lg z-40">
<button data-export="yaml" class="block w-full text-left px-3 py-2 text-sm hover:bg-slate-50">YAML 다운로드</button>
<button data-export="json" class="block w-full text-left px-3 py-2 text-sm hover:bg-slate-50">JSON 다운로드</button>
</div>
</div>
<button id="btn-close" class="px-3 py-1.5 text-sm rounded border border-slate-300 bg-white hover:bg-slate-50">닫기</button>
</div>
</header>
<!-- ===== Stepper ===== -->
<nav class="bg-white border-b border-slate-200 sticky z-20" style="top:56px;">
<div class="h-1 bg-slate-100">
<div id="stepper-progress" class="h-1 bg-brand-500 transition-all duration-300" style="width:16.66%"></div>
</div>
<ol id="stepper" class="grid grid-cols-5 px-2"></ol>
</nav>
<!-- ===== Body ===== -->
<main id="body" class="flex" style="min-height: calc(100vh - 56px - 72px);">
<section id="form-panel" class="bg-white border-r border-slate-200 transition-all duration-300" style="width:960px;">
<div class="flex items-center justify-between px-6 py-3 border-b border-slate-100 sticky bg-white z-10" style="top:128px;">
<div class="flex items-baseline gap-3">
<span class="text-xs uppercase tracking-wide text-slate-400">Step <span id="form-step-num">1</span> / 5</span>
<h2 class="text-base font-semibold text-slate-800" id="form-step-title">기본정보</h2>
</div>
<button id="btn-toggle-preview-from-form" class="hidden text-xs px-2 py-1 rounded border border-slate-200 text-slate-600 hover:bg-slate-50">▶ 미리보기 다시 열기</button>
</div>
<div id="form-content" class="px-6 py-5"></div>
<div class="sticky bottom-0 bg-white border-t border-slate-200 px-6 py-3 flex items-center justify-between">
<button id="btn-prev" class="px-4 py-2 text-sm rounded border border-slate-300 bg-white hover:bg-slate-50 disabled:opacity-40 disabled:cursor-not-allowed">◀ 이전</button>
<div class="text-xs text-slate-500" id="footer-hint">필수 항목을 채우고 다음 단계로 이동하세요</div>
<button id="btn-next" class="px-4 py-2 text-sm rounded bg-brand-600 text-white hover:bg-brand-700">다음 ▶</button>
</div>
</section>
<aside id="preview-panel" class="bg-slate-50 transition-all duration-300" style="width:640px;">
<div class="flex items-center justify-between px-4 py-2 border-b border-slate-200 bg-white sticky z-10" style="top:128px;">
<div class="flex items-center gap-1">
<button data-ptab="swagger" class="preview-tab px-3 py-1.5 text-sm rounded-t border-b-2 border-brand-600 text-brand-700 font-medium">Swagger UI</button>
<button data-ptab="yaml-ro" class="preview-tab px-3 py-1.5 text-sm rounded-t border-b-2 border-transparent text-slate-600 hover:text-slate-800">YAML 보기</button>
<button data-ptab="yaml-edit" class="preview-tab px-3 py-1.5 text-sm rounded-t border-b-2 border-transparent text-slate-600 hover:text-slate-800">스펙 편집</button>
</div>
<button id="btn-toggle-preview" class="text-xs px-2 py-1 rounded border border-slate-200 text-slate-600 hover:bg-slate-100">패널 숨기기 ▶</button>
</div>
<div id="yaml-edit-bar" class="hidden flex items-center justify-between bg-amber-50 border-b border-amber-200 px-4 py-2 text-xs">
<span class="text-amber-800">YAML 을 직접 편집한 뒤 [Form 에 적용] 을 눌러 양식에 동기화하세요.</span>
<div class="flex items-center gap-2">
<button id="btn-yaml-revert" class="px-2 py-1 rounded border border-amber-300 bg-white hover:bg-amber-100">변경 취소</button>
<button id="btn-yaml-apply" class="px-2 py-1 rounded bg-amber-600 text-white hover:bg-amber-700">Form 에 적용</button>
</div>
</div>
<div id="preview-swagger" class="overflow-auto" style="height: calc(100vh - 56px - 72px - 41px);"></div>
<div id="preview-monaco" class="hidden" style="height: calc(100vh - 56px - 72px - 41px);"></div>
</aside>
</main>
</div>
<div id="toast" class="fixed bottom-5 left-1/2 -translate-x-1/2 hidden px-4 py-2 rounded bg-slate-900 text-white text-sm shadow-lg z-50"></div>
<!-- Security scheme modal -->
<div id="sec-modal" class="hidden fixed inset-0 z-50 bg-slate-900/40 flex items-center justify-center">
<div class="bg-white rounded-lg shadow-xl w-[640px] max-h-[80vh] overflow-auto">
<div class="flex items-center justify-between px-5 py-3 border-b border-slate-200">
<h3 class="font-semibold text-slate-800">보안 스킴 추가</h3>
<button class="text-slate-400 hover:text-slate-700" data-sec-close>✕</button>
</div>
<div class="p-5" id="sec-modal-body"></div>
<div class="px-5 py-3 border-t border-slate-200 flex justify-end gap-2 bg-slate-50">
<button class="px-3 py-1.5 text-sm rounded border border-slate-300 bg-white" data-sec-close>취소</button>
<button id="btn-sec-save" class="px-3 py-1.5 text-sm rounded bg-brand-600 text-white">추가</button>
</div>
</div>
</div>
<script src="<c:url value='/js/djb/apispec/sample-data.js'/>"></script>
<script src="<c:url value='/js/djb/apispec/app.js'/>"></script>
</body>
</html>
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,122 @@
/*!
* djb-swagger-i18n.js
* Swagger UI 영문 라벨을 한글로 치환하는 i18n 패치 (방법 B - DOM patch)
* 배포 경로 권장: static/plugins/swaggerUI/djb-swagger-i18n.js
* index.html 의 swagger-initializer.js 직전 또는 직후에 <script> 로 로드
*
* ?lang=en 쿼리스트링이 있으면 패치 비활성화 (원문 보기)
* 매핑 카탈로그는 01-i18n-mapping/i18n-strings.csv 와 동기 유지
*/
(function (window) {
'use strict';
// ── 비활성화 옵션 ──
if (/[?&]lang=en\b/.test(window.location.search)) return;
// ── 매핑 (key 는 디버깅용, 실제 비교는 EN 문자열 일치) ──
var I18N_MAP = {
// P0 — OAuth2 모달 본문
'auth.modal.title': { en: 'Available authorizations', ko: '사용 가능한 인증' },
'auth.scopes.description': {
en: 'Scopes are used to grant an application different levels of access to data on behalf of the end user. Each API may declare one or more scopes.',
ko: '스코프는 최종 사용자를 대신하여 애플리케이션이 데이터에 접근할 수 있는 권한 수준을 부여합니다. 각 API는 하나 이상의 스코프를 선언할 수 있습니다.'
},
'auth.scopes.swagger_grant': {
en: 'API requires the following scopes. Select which ones you want to grant to Swagger UI.',
ko: '이 API 는 아래 스코프가 필요합니다. Swagger UI 에 부여할 스코프를 선택하세요.'
},
'auth.flow.clientCredentials': { en: 'OAuth2 client credentials flow', ko: 'OAuth2 클라이언트 자격증명 흐름' },
'auth.flow.implicit': { en: 'OAuth2 implicit flow', ko: 'OAuth2 암묵적 흐름' },
'auth.flow.password': { en: 'OAuth2 password flow', ko: 'OAuth2 비밀번호 흐름' },
'auth.flow.authorizationCode': { en: 'OAuth2 authorization code flow', ko: 'OAuth2 인가 코드 흐름' },
'auth.field.tokenUrl': { en: 'Token URL:', ko: '토큰 URL:' },
'auth.field.flow': { en: 'Flow:', ko: '흐름:' },
'auth.field.clientId': { en: 'client_id:', ko: '클라이언트 ID (client_id):' },
'auth.field.clientSecret': { en: 'client_secret:',ko: '클라이언트 시크릿 (client_secret):' },
'auth.field.scopes': { en: 'Scopes:', ko: '스코프:' },
'auth.action.selectAll': { en: 'select all', ko: '모두 선택' },
'auth.action.selectNone': { en: 'select none', ko: '선택 해제' },
'auth.action.authorize': { en: 'Authorize', ko: '인증' },
'auth.action.close': { en: 'Close', ko: '닫기' },
'auth.action.logout': { en: 'Logout', ko: '로그아웃' },
'auth.modal.authorized': { en: 'authorized', ko: '인증됨' },
// P1 — 기타 인증 흐름
'auth.scheme.apiKey': { en: 'API key', ko: 'API 키' },
'auth.field.name': { en: 'name:', ko: '이름:' },
'auth.field.in': { en: 'in:', ko: '위치:' },
'auth.field.value': { en: 'Value:', ko: '값:' },
'basic.field.username': { en: 'Username:',ko: '사용자 ID:' },
'basic.field.password': { en: 'Password:',ko: '비밀번호:' }
// P2 (오퍼레이션/응답) 는 필요 시 추가
};
// 빠른 lookup 을 위해 EN → KO 인덱스 사전 생성
var EN_TO_KO = Object.create(null);
Object.keys(I18N_MAP).forEach(function (k) {
var e = I18N_MAP[k];
EN_TO_KO[e.en] = e.ko;
});
// ── 치환 대상 노드 식별 + 치환 ──
function translateNode(node) {
if (!node) return;
if (node.nodeType === Node.TEXT_NODE) {
var raw = node.nodeValue;
if (!raw) return;
var trimmed = raw.trim();
if (!trimmed) return;
// 1) 완전 일치 (가장 안전)
if (EN_TO_KO[trimmed]) {
node.nodeValue = raw.replace(trimmed, EN_TO_KO[trimmed]);
return;
}
// 2) 라벨 colon 패턴: "Token URL:" 같이 끝이 ":" 인 짧은 라벨
// (불필요 — 1번 완전 일치로 대부분 커버)
return;
}
if (node.nodeType !== Node.ELEMENT_NODE) return;
if (node.tagName === 'SCRIPT' || node.tagName === 'STYLE') return;
// input placeholder / button title 도 일부 케이스에 대비
if (node.tagName === 'INPUT' && node.placeholder && EN_TO_KO[node.placeholder.trim()]) {
node.placeholder = EN_TO_KO[node.placeholder.trim()];
}
var i, child = node.childNodes, len = child.length;
for (i = 0; i < len; i++) translateNode(child[i]);
}
function translateAll() {
var root = document.getElementById('swagger-ui') || document.body;
translateNode(root);
}
// ── MutationObserver — SwaggerUI 가 동적으로 DOM 을 다시 그릴 때마다 재적용 ──
var observer = new MutationObserver(function (mutations) {
for (var i = 0; i < mutations.length; i++) {
var m = mutations[i];
if (m.addedNodes && m.addedNodes.length) {
for (var j = 0; j < m.addedNodes.length; j++) translateNode(m.addedNodes[j]);
}
if (m.type === 'characterData') {
translateNode(m.target);
}
}
});
function start() {
translateAll();
observer.observe(document.body, {
childList: true,
subtree: true,
characterData: true
});
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', start);
} else {
start();
}
})(window);
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+6 -11
View File
@@ -4,7 +4,6 @@ plugins {
id 'eclipse-wtp' id 'eclipse-wtp'
id 'idea' id 'idea'
id 'war' id 'war'
id 'com.diffplug.eclipse.apt' version '3.41.1'
} }
group 'com.eactive' group 'com.eactive'
@@ -49,10 +48,6 @@ compileJava {
options.encoding = 'UTF-8' options.encoding = 'UTF-8'
options.compilerArgs = ['-parameters'] options.compilerArgs = ['-parameters']
options.generatedSourceOutputDirectory = project.file(generatedJavaDir) options.generatedSourceOutputDirectory = project.file(generatedJavaDir)
aptOptions {
processorArgs = [ 'querydsl.generatedAnnotationClass' : 'com.querydsl.core.annotations.Generated' ]
}
} }
war { war {
@@ -89,7 +84,7 @@ dependencies {
implementation project(':elink-online-emsclient') implementation project(':elink-online-emsclient')
implementation project(':elink-portal-common') implementation project(':elink-portal-common')
//implementation project(':kjb-safedb') //implementation project(':kjb-safedb')
implementation project(":eapim-admin-djb") //implementation project(":eapim-admin-djb")
/* Custom Libs (damo-manager.jar 는 Tomcat lib 가 런타임 제공 → compileOnly, WAR 미포함) */ /* Custom Libs (damo-manager.jar 는 Tomcat lib 가 런타임 제공 → compileOnly, WAR 미포함) */
implementation fileTree(dir: 'libs', include: ['*.jar'], exclude: ['damo-manager.jar']) implementation fileTree(dir: 'libs', include: ['*.jar'], exclude: ['damo-manager.jar'])
@@ -257,11 +252,11 @@ eclipse {
} }
synchronizationTasks settingEclipseEncoding, initDirs synchronizationTasks settingEclipseEncoding, initDirs
jdt { jdt {
apt { // apt {
// generated 된 패스 경로를 .setting/org.eclipse.jdt.apt.core.prefs 의 org.eclipse.jdt.apt.genSrcDir에 적용한다. // // generated 된 패스 경로를 .setting/org.eclipse.jdt.apt.core.prefs 의 org.eclipse.jdt.apt.genSrcDir에 적용한다.
// project > properties > Java Compiler > Annoation Processing 화면에서 확인 가능하다. // // project > properties > Java Compiler > Annoation Processing 화면에서 확인 가능하다.
genSrcDir = file(generatedJavaDir) // genSrcDir = file(generatedJavaDir)
} // }
} }
project { project {
if (!natures.contains('org.eclipse.buildship.core.gradleprojectnature')) { if (!natures.contains('org.eclipse.buildship.core.gradleprojectnature')) {
+2 -2
View File
@@ -87,8 +87,8 @@ dependencies {
implementation project(":eapim-admin-djb") implementation project(":eapim-admin-djb")
/* Custom Libs (damo-manager.jar 는 Tomcat lib 가 런타임 제공 → compileOnly, WAR 미포함) */ /* Custom Libs (damo-manager.jar 는 Tomcat lib 가 런타임 제공 → compileOnly, WAR 미포함) */
implementation fileTree(dir: 'libs', include: ['*.jar'], exclude: ['damo-manager.jar']) implementation fileTree(dir: 'libs', include: ['*.jar'], exclude: ['damo-manager.jar'])
compileOnly files('libs/damo-manager.jar') compileOnly files('libs/damo-manager.jar')
//implementation "org.dom4j:dom4j:2.1.3" //implementation "org.dom4j:dom4j:2.1.3"
//implementation "com.rabbitmq:amqp-client:3.6.6" //implementation "com.rabbitmq:amqp-client:3.6.6"
+2 -2
View File
@@ -7,7 +7,7 @@ include 'elink-online-common'
include 'elink-online-emsclient' include 'elink-online-emsclient'
include 'elink-portal-common' include 'elink-portal-common'
//include 'kjb-safedb' //include 'kjb-safedb'
include 'eapim-admin-djb' //include 'eapim-admin-djb'
project (':elink-online-core-jpa').projectDir = new File(settingsDir, "../eapim-online/elink-online-core-jpa") project (':elink-online-core-jpa').projectDir = new File(settingsDir, "../eapim-online/elink-online-core-jpa")
project (':elink-online-core').projectDir = new File(settingsDir, "../eapim-online/elink-online-core") project (':elink-online-core').projectDir = new File(settingsDir, "../eapim-online/elink-online-core")
@@ -16,4 +16,4 @@ project (':elink-online-common').projectDir = new File(settingsDir, "../eapim-on
project (':elink-online-emsclient').projectDir = new File(settingsDir, "../eapim-online/elink-online-emsclient") project (':elink-online-emsclient').projectDir = new File(settingsDir, "../eapim-online/elink-online-emsclient")
project (':elink-portal-common').projectDir = new File(settingsDir, "../elink-portal-common") project (':elink-portal-common').projectDir = new File(settingsDir, "../elink-portal-common")
//project (':kjb-safedb').projectDir = new File(settingsDir, "../kjb-safedb") //project (':kjb-safedb').projectDir = new File(settingsDir, "../kjb-safedb")
project (':eapim-admin-djb').projectDir = new File(settingsDir, "../eapim-admin-djb") //project (':eapim-admin-djb').projectDir = new File(settingsDir, "../eapim-admin-djb")
@@ -0,0 +1,23 @@
package com.eactive.eai.rms.common.acl.sitemap;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import com.eactive.eai.rms.common.base.BaseAnnotationController;
@Controller
@RequiredArgsConstructor
public class SitemapController extends BaseAnnotationController {
private final SitemapService sitemapService;
@GetMapping(value = "/common/acl/sitemap/sitemapMan.view")
public String view(@RequestParam(value = "serviceType", required = false) String serviceType, Model model) {
model.addAttribute("sitemapTree", sitemapService.getSitemapTree(serviceType));
return "/common/acl/sitemap/sitemapMan";
}
}
@@ -0,0 +1,11 @@
package com.eactive.eai.rms.common.acl.sitemap;
import java.util.List;
import com.eactive.eai.rms.common.acl.sitemap.ui.SitemapNode;
public interface SitemapService {
public List<SitemapNode> getSitemapTree(String serviceType);
}
@@ -0,0 +1,96 @@
package com.eactive.eai.rms.common.acl.sitemap;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.eactive.eai.rms.common.acl.sitemap.ui.SitemapNode;
import com.eactive.eai.rms.common.base.BaseService;
import com.eactive.eai.rms.data.entity.man.menu.Menu;
import com.eactive.eai.rms.data.entity.man.menu.MenuService;
import com.eactive.eai.rms.data.entity.man.role.RoleMenuAuthService;
@Service("sitemapService")
@Transactional(transactionManager = "transactionManagerForEMS")
public class SitemapServiceImpl extends BaseService implements SitemapService {
@Autowired
private MenuService menuService;
@Autowired
private RoleMenuAuthService roleMenuAuthService;
public List<SitemapNode> getSitemapTree(String serviceType) {
Menu root = menuService.getById("ROOT_DEV");
Menu langRoot = findLangRoot(root);
List<SitemapNode> tree = new ArrayList<>();
if (langRoot == null) {
return tree;
}
Set<String> serviceTypeMenuIds = StringUtils.isBlank(serviceType)
? null
: new HashSet<>(roleMenuAuthService.findMenuIdsByServiceType(serviceType));
for (Menu menu : sortedChildren(langRoot)) {
if (isVisible(menu) && matchesServiceType(menu, serviceTypeMenuIds)) {
tree.add(buildNode(menu, serviceTypeMenuIds));
}
}
return tree;
}
// 엔티티의 childMenus는 menuId 기준으로 정렬되어 있어, 실제 메뉴(운영화면 GNB)와 동일하게 sortOrder 기준으로 재정렬한다.
private List<Menu> sortedChildren(Menu menu) {
List<Menu> children = new ArrayList<>(menu.getChildMenus());
children.sort(Comparator.comparing(Menu::getSortOrder, Comparator.nullsLast(Comparator.naturalOrder())));
return children;
}
// MenuService.getMenuList()의 GNB 렌더링과 동일하게 "ROOT"(한글)/"ROOT_EN"(영문) 이름의 언어별 루트를 찾는다.
private Menu findLangRoot(Menu root) {
String lang = LocaleContextHolder.getLocale().getLanguage();
String langRootName = (StringUtils.isBlank(lang) || "ko".equalsIgnoreCase(lang))
? "ROOT"
: "ROOT_" + lang.toUpperCase();
return root.getChildMenus().stream()
.filter(m -> langRootName.equalsIgnoreCase(m.getMenuName()))
.findFirst()
.orElse(null);
}
private SitemapNode buildNode(Menu menu, Set<String> serviceTypeMenuIds) {
SitemapNode node = new SitemapNode(menu);
for (Menu child : sortedChildren(menu)) {
if (isVisible(child) && matchesServiceType(child, serviceTypeMenuIds)) {
node.getChildren().add(buildNode(child, serviceTypeMenuIds));
}
}
return node;
}
private boolean isVisible(Menu menu) {
return "Y".equalsIgnoreCase(menu.getDisplayYn()) && "Y".equalsIgnoreCase(menu.getUseYn());
}
// serviceType 필터가 없으면 통과. 있으면 자신이나 하위메뉴 중 하나라도 해당 serviceType 권한이 있어야 통과(상위메뉴 누락 방지).
private boolean matchesServiceType(Menu menu, Set<String> serviceTypeMenuIds) {
if (serviceTypeMenuIds == null) {
return true;
}
if (serviceTypeMenuIds.contains(menu.getMenuId())) {
return true;
}
return menu.getChildMenus().stream().anyMatch(child -> matchesServiceType(child, serviceTypeMenuIds));
}
}
@@ -0,0 +1,29 @@
package com.eactive.eai.rms.common.acl.sitemap.ui;
import java.util.ArrayList;
import java.util.List;
import com.eactive.eai.rms.data.entity.man.menu.Menu;
import lombok.Data;
@Data
public class SitemapNode {
private String menuId;
private String menuName;
private String menuUrl;
private List<SitemapNode> children = new ArrayList<>();
public SitemapNode(Menu menu) {
this.menuId = menu.getMenuId();
this.menuName = menu.getMenuName();
this.menuUrl = menu.getMenuUrl();
}
public SitemapNode() {
}
}
@@ -21,8 +21,8 @@ import com.eactive.eai.rms.common.util.ContainerUtil;
import com.eactive.eai.rms.data.entity.man.monitoringProperty.MonitoringPropertyId; import com.eactive.eai.rms.data.entity.man.monitoringProperty.MonitoringPropertyId;
import com.eactive.eai.rms.data.entity.man.monitoringProperty.service.MonitoringPropertyGroupService; import com.eactive.eai.rms.data.entity.man.monitoringProperty.service.MonitoringPropertyGroupService;
import com.eactive.eai.rms.data.entity.man.monitoringProperty.service.MonitoringPropertyService; import com.eactive.eai.rms.data.entity.man.monitoringProperty.service.MonitoringPropertyService;
import com.eactive.ext.kjb.common.KjbPropertyHolder; import com.eactive.eai.rms.ext.djb.common.DjbPropertyHolder;
import com.eactive.ext.kjb.util.KjbPropertyInjector; import com.eactive.eai.rms.ext.djb.common.DjbPropertyInjector;
@Service("monitoringContext") @Service("monitoringContext")
@Transactional(transactionManager = "transactionManagerForEMS") @Transactional(transactionManager = "transactionManagerForEMS")
@@ -36,7 +36,7 @@ class MonitoringContextImpl implements MonitoringContext {
@Autowired @Autowired
private MonitoringContextDAO monitoringContextDAO; private MonitoringContextDAO monitoringContextDAO;
private Properties properties = null; private Properties properties = new Properties();
private String instancePropertyGroupName; private String instancePropertyGroupName;
@@ -45,7 +45,7 @@ class MonitoringContextImpl implements MonitoringContext {
public void refresh() { public void refresh() {
init(); init();
KjbPropertyHolder.reload(); DjbPropertyHolder.reload();
} }
public void refresh(String propName) { public void refresh(String propName) {
@@ -73,7 +73,10 @@ class MonitoringContextImpl implements MonitoringContext {
String hostIp = InetAddress.getLocalHost().getHostAddress(); String hostIp = InetAddress.getLocalHost().getHostAddress();
this.instancePropertyGroupName = String this.instancePropertyGroupName = String
.format(MonitoringPropertyGroupService.MONITORING_PROPERTY_KEY_FORMAT, hostIp); .format(MonitoringPropertyGroupService.MONITORING_PROPERTY_KEY_FORMAT, hostIp);
this.properties = monitoringContextDAO.getProperties(instancePropertyGroupName); Properties loaded = monitoringContextDAO.getProperties(instancePropertyGroupName);
if (loaded != null) {
this.properties = loaded;
}
String eaiAgentId = System.getProperty(EAI_AGENTID); String eaiAgentId = System.getProperty(EAI_AGENTID);
if (StringUtils.isBlank(eaiAgentId)) { if (StringUtils.isBlank(eaiAgentId)) {
@@ -85,9 +88,9 @@ class MonitoringContextImpl implements MonitoringContext {
setPortMapper(monitoringContextDAO.getPortMap()); setPortMapper(monitoringContextDAO.getPortMap());
// KjbPropertyHolder 초기화 // DjbPropertyHolder 초기화
KjbPropertyInjector injector = new KjbPropertyInjector(monitoringPropertyService, "Monitoring"); DjbPropertyInjector injector = new DjbPropertyInjector(monitoringPropertyService, "Monitoring");
KjbPropertyHolder.initialize(injector::inject); DjbPropertyHolder.initialize(injector::inject);
logger.info("SSO AgentId : " + System.getProperty(EAI_AGENTID)); logger.info("SSO AgentId : " + System.getProperty(EAI_AGENTID));
@@ -32,7 +32,6 @@ import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.View; import org.springframework.web.servlet.View;
import com.eactive.eai.common.seed.Seed; import com.eactive.eai.common.seed.Seed;
import com.eactive.eai.rms.ext.kjb.smsauth.SmsAuthService;
import com.eactive.ext.djb.DamoManager; import com.eactive.ext.djb.DamoManager;
import com.eactive.eai.data.converter.LocalDateTimeFormatters; import com.eactive.eai.data.converter.LocalDateTimeFormatters;
import com.eactive.eai.rms.common.context.MonitoringContext; import com.eactive.eai.rms.common.context.MonitoringContext;
@@ -51,6 +50,7 @@ import com.eactive.eai.rms.data.entity.man.user.UserLoginHistoryService;
import com.eactive.eai.rms.data.entity.man.user.UserRole; import com.eactive.eai.rms.data.entity.man.user.UserRole;
import com.eactive.eai.rms.data.entity.man.user.UserRoleService; import com.eactive.eai.rms.data.entity.man.user.UserRoleService;
import com.eactive.eai.rms.data.entity.man.user.UserServiceTypeService; import com.eactive.eai.rms.data.entity.man.user.UserServiceTypeService;
import com.eactive.eai.rms.ext.djb.smsauth.SmsAuthService;
@Controller @Controller
public class MainController implements InterceptorSkipController { public class MainController implements InterceptorSkipController {
@@ -51,4 +51,17 @@ public class RoleMenuAuthService extends AbstractEMSDataSerivce<RoleMenuAuth, Ro
.fetch(); .fetch();
} }
public List<String> findMenuIdsByServiceType(String serviceType) {
QRoleMenuAuth qRoleMenuAuth = QRoleMenuAuth.roleMenuAuth;
return getJPAQueryFactory()
.select(qRoleMenuAuth.id.menuId)
.distinct()
.from(qRoleMenuAuth)
.where(qRoleMenuAuth.id.serviceType.eq(serviceType))
.setHint(QueryHints.CACHEABLE, true)
.fetch();
}
} }
@@ -2,6 +2,7 @@ package com.eactive.eai.rms.data.entity.onl.apim.apigroup;
import com.eactive.eai.data.entity.onl.apim.apigroup.ApiGroup; import com.eactive.eai.data.entity.onl.apim.apigroup.ApiGroup;
import com.eactive.eai.data.entity.onl.apim.apigroup.ApiGroupApi; import com.eactive.eai.data.entity.onl.apim.apigroup.ApiGroupApi;
import com.eactive.eai.data.entity.onl.apim.apigroup.ApiGroupApiId;
import com.eactive.eai.data.entity.onl.apim.apigroup.QApiGroup; import com.eactive.eai.data.entity.onl.apim.apigroup.QApiGroup;
import com.eactive.eai.data.entity.onl.apim.apigroup.QApiGroupApi; import com.eactive.eai.data.entity.onl.apim.apigroup.QApiGroupApi;
import com.eactive.eai.data.jpa.AbstractDataService; import com.eactive.eai.data.jpa.AbstractDataService;
@@ -75,4 +76,37 @@ public class ApiGroupService extends AbstractDataService<ApiGroup, String, ApiGr
.where(qApiGroup.id.in(apiGroupIds)) .where(qApiGroup.id.in(apiGroupIds))
.fetch(); .fetch();
} }
/** API 를 지정한 그룹 목록에만 소속시킨다(기존 소속 제거 후 재설정). */
public void setApiGroupsForApi(String apiId, List<String> groupIds) {
deleteApiGroupApiByApiId(apiId);
if (groupIds == null) {
return;
}
for (String gid : groupIds) {
if (StringUtils.isBlank(gid)) {
continue;
}
ApiGroup group = repository.findById(gid).orElse(null);
if (group == null) {
continue;
}
Hibernate.initialize(group.getApiGroupApiList());
List<ApiGroupApi> list = group.getApiGroupApiList();
boolean exists = false;
for (ApiGroupApi a : list) {
if (a.getId() != null && apiId.equals(a.getId().getApiId())) {
exists = true;
break;
}
}
if (!exists) {
ApiGroupApi a = new ApiGroupApi();
a.setId(new ApiGroupApiId(gid, apiId));
a.setDisplayOrder(list.size() + 1);
list.add(a);
repository.save(group);
}
}
}
} }
@@ -14,6 +14,8 @@ import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDate;
@Service @Service
@Transactional(transactionManager = "transactionManagerForEMS") @Transactional(transactionManager = "transactionManagerForEMS")
public class MessageRequestService public class MessageRequestService
@@ -53,6 +55,16 @@ public class MessageRequestService
return repository.findAll(predicate, fixed); return repository.findAll(predicate, fixed);
} }
/** 특정 일자에 SMS/KAKAO 로 발송 요청된 건수 (UMS 일련번호 채번용). */
public long countSmsKakaoRequestsOnDate(LocalDate date) {
QMessageRequest q = QMessageRequest.messageRequest;
BooleanBuilder predicate = new BooleanBuilder();
predicate.and(q.messageType.in("SMS", "KAKAO"));
predicate.and(q.requestDate.goe(date.atStartOfDay()));
predicate.and(q.requestDate.loe(date.atTime(23, 59, 59)));
return repository.count(predicate);
}
/** PENDING 상태인 건만 FAILED 로 전환한다. */ /** PENDING 상태인 건만 FAILED 로 전환한다. */
public void updateStatusToFailedIfPending(String id) { public void updateStatusToFailedIfPending(String id) {
MessageRequest entity = getById(id); MessageRequest entity = getById(id);
@@ -22,6 +22,7 @@ import com.eactive.eai.data.entity.onl.message.QServiceMessageEntity;
import com.eactive.eai.data.entity.onl.routing.QRouting; import com.eactive.eai.data.entity.onl.routing.QRouting;
import com.eactive.eai.data.entity.onl.stdmessage.QStandardMessageInfo; import com.eactive.eai.data.entity.onl.stdmessage.QStandardMessageInfo;
import com.eactive.eai.data.jpa.AbstractDataService; import com.eactive.eai.data.jpa.AbstractDataService;
import com.eactive.eai.rms.data.entity.onl.djb.apistatus.QApiStatus;
import com.eactive.eai.rms.data.entity.onl.authserver.QScopeInfo; import com.eactive.eai.rms.data.entity.onl.authserver.QScopeInfo;
import com.eactive.eai.rms.data.entity.onl.bzwkdstcd.UserBusinessService; import com.eactive.eai.rms.data.entity.onl.bzwkdstcd.UserBusinessService;
import com.querydsl.core.BooleanBuilder; import com.querydsl.core.BooleanBuilder;
@@ -45,26 +46,28 @@ public class EAIMessageService extends AbstractDataService<EAIMessageEntity, Str
QServiceMessageEntity qServiceMessageEntity = QServiceMessageEntity.serviceMessageEntity; QServiceMessageEntity qServiceMessageEntity = QServiceMessageEntity.serviceMessageEntity;
QRouting qRouting = QRouting.routing; QRouting qRouting = QRouting.routing;
QStandardMessageInfo qStdInfo = QStandardMessageInfo.standardMessageInfo; QStandardMessageInfo qStdInfo = QStandardMessageInfo.standardMessageInfo;
QApiStatus qApiStatus = QApiStatus.apiStatus;
JPAQuery<Tuple> jpaQuery = createBaseQuery(eaiMessageUISearch); JPAQuery<Tuple> jpaQuery = createBaseQuery(eaiMessageUISearch);
long totalCount = jpaQuery.select(QEAIMessageEntity.eAIMessageEntity.count()).fetchOne(); long totalCount = jpaQuery.select(QEAIMessageEntity.eAIMessageEntity.count()).fetchOne();
// 정렬 조건 설정 // 정렬 조건 설정
OrderSpecifier<?> orderSpecifier = getOrderSpecifier(qeaiMessageEntity, sortname, sortorder); OrderSpecifier<?> orderSpecifier = getOrderSpecifier(qeaiMessageEntity, sortname, sortorder);
List<Tuple> eaiMessages = jpaQuery List<Tuple> eaiMessages = jpaQuery
.select(qeaiMessageEntity .select(qeaiMessageEntity
, qServiceMessageEntity.psvintfacdsticname , qServiceMessageEntity.psvintfacdsticname
, qServiceMessageEntity.psvsysadptrbzwkgroupname , qServiceMessageEntity.psvsysadptrbzwkgroupname
, qRouting.nonmotivrouturiname , qRouting.nonmotivrouturiname
, qStdInfo.apifullpath , qStdInfo.apifullpath
, qStdInfo.bzwksvckeyname , qStdInfo.bzwksvckeyname
, qApiStatus.statusCode
) )
.limit(pageable.getPageSize()) .limit(pageable.getPageSize())
.offset(pageable.getOffset()) .offset(pageable.getOffset())
.orderBy(orderSpecifier) .orderBy(orderSpecifier)
.fetch(); .fetch();
return new PageImpl<>(eaiMessages, pageable, totalCount); return new PageImpl<>(eaiMessages, pageable, totalCount);
@@ -105,6 +108,7 @@ public class EAIMessageService extends AbstractDataService<EAIMessageEntity, Str
QServiceMessageEntity qServiceMessageEntity = QServiceMessageEntity.serviceMessageEntity; QServiceMessageEntity qServiceMessageEntity = QServiceMessageEntity.serviceMessageEntity;
QRouting qRouting = QRouting.routing; QRouting qRouting = QRouting.routing;
QStandardMessageInfo qStdInfo = QStandardMessageInfo.standardMessageInfo; QStandardMessageInfo qStdInfo = QStandardMessageInfo.standardMessageInfo;
QApiStatus qApiStatus = QApiStatus.apiStatus;
BooleanBuilder predicate = createSelectListPredicate(eaiMessageUISearch); BooleanBuilder predicate = createSelectListPredicate(eaiMessageUISearch);
@@ -114,6 +118,7 @@ public class EAIMessageService extends AbstractDataService<EAIMessageEntity, Str
, qRouting.nonmotivrouturiname , qRouting.nonmotivrouturiname
, qStdInfo.apifullpath , qStdInfo.apifullpath
, qStdInfo.bzwksvckeyname , qStdInfo.bzwksvckeyname
, qApiStatus.statusCode
) )
.from(qeaiMessageEntity) .from(qeaiMessageEntity)
.join(qRouting) .join(qRouting)
@@ -122,6 +127,8 @@ public class EAIMessageService extends AbstractDataService<EAIMessageEntity, Str
.on(qServiceMessageEntity.id.svcprcssno.eq(1)) .on(qServiceMessageEntity.id.svcprcssno.eq(1))
.leftJoin(qStdInfo) .leftJoin(qStdInfo)
.on(qeaiMessageEntity.eaisvcname.eq(qStdInfo.eaisvcname)) .on(qeaiMessageEntity.eaisvcname.eq(qStdInfo.eaisvcname))
.leftJoin(qApiStatus)
.on(qeaiMessageEntity.eaisvcname.eq(qApiStatus.eaisvcname))
.where(predicate); .where(predicate);
} }
@@ -1,5 +1,6 @@
package com.eactive.eai.rms.data.entity.onl.inflow; package com.eactive.eai.rms.data.entity.onl.inflow;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
@@ -63,11 +64,19 @@ public class InflowControlLogService
public Iterable<InflowControlLog> findAll(InflowControlHistoryManUISearch uiSearch) { public Iterable<InflowControlLog> findAll(InflowControlHistoryManUISearch uiSearch) {
QInflowControlLog qInflowConfrolLog = QInflowControlLog.inflowControlLog; QInflowControlLog qInflowConfrolLog = QInflowControlLog.inflowControlLog;
String startTime = StringUtils.defaultIfEmpty(uiSearch.getSearchStartTime(), "000000");
String endTime = StringUtils.defaultIfEmpty(uiSearch.getSearchEndTime(), "235959");
BooleanBuilder booleanBuilder = new BooleanBuilder(); BooleanBuilder booleanBuilder = new BooleanBuilder();
booleanBuilder booleanBuilder
.and(qInflowConfrolLog.id.msgdpstyms .and(qInflowConfrolLog.id.msgdpstyms
.between(uiSearch.getSearchStartDate() + "000000000", .between(uiSearch.getSearchStartDate() + startTime + "000",
uiSearch.getSearchEndDate() + "235959999")); uiSearch.getSearchEndDate() + endTime + "999"));
if (!StringUtils.isEmpty(uiSearch.getSearchInstanceName()))
booleanBuilder.and(qInflowConfrolLog.eaisevrinstncname.contains(uiSearch.getSearchInstanceName()));
if (!StringUtils.isEmpty(uiSearch.getSearchApiName()))
booleanBuilder.and(qInflowConfrolLog.eaisvcname.contains(uiSearch.getSearchApiName()));
if (!StringUtils.isEmpty(uiSearch.getSearchAdapterGroupName()))
booleanBuilder.and(qInflowConfrolLog.adptrbzwkgroupname.contains(uiSearch.getSearchAdapterGroupName()));
return super.repository.findAll(booleanBuilder); return super.repository.findAll(booleanBuilder);
} }
} }
@@ -27,6 +27,8 @@ import com.eactive.eai.rms.onl.manage.inflow.history.InflowControlHistoryManUISe
import com.eactive.eai.rms.onl.manage.inflow.inflow.InflowControlManMapper; import com.eactive.eai.rms.onl.manage.inflow.inflow.InflowControlManMapper;
import com.querydsl.core.BooleanBuilder; import com.querydsl.core.BooleanBuilder;
import com.querydsl.core.Tuple; import com.querydsl.core.Tuple;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.core.types.dsl.StringPath;
import com.querydsl.jpa.impl.JPAQuery; import com.querydsl.jpa.impl.JPAQuery;
import com.querydsl.jpa.impl.JPAQueryFactory; import com.querydsl.jpa.impl.JPAQueryFactory;
@@ -46,7 +48,12 @@ public class InflowControlService extends AbstractDataService<InflowControl, Inf
@Autowired @Autowired
InflowControlManMapper mapper; InflowControlManMapper mapper;
public Page<InflowControlServiceDto> findAllForAdapter(Pageable pageable, String searchName) { // 유량제어 미설정(useyn null)은 "사용안함" 검색 시 함께 조회되도록 처리
private static BooleanExpression useYnCondition(StringPath useYnPath, String searchUseYn) {
return "0".equals(searchUseYn) ? useYnPath.eq(searchUseYn).or(useYnPath.isNull()) : useYnPath.eq(searchUseYn);
}
public Page<InflowControlServiceDto> findAllForAdapter(Pageable pageable, String searchName, String searchUseYn) {
JPAQueryFactory jpaQueryFactory = new JPAQueryFactory(entityManager); JPAQueryFactory jpaQueryFactory = new JPAQueryFactory(entityManager);
QAdapterGroup qAdapterGroup = QAdapterGroup.adapterGroup; QAdapterGroup qAdapterGroup = QAdapterGroup.adapterGroup;
QInflowControl qInflowControl = QInflowControl.inflowControl; QInflowControl qInflowControl = QInflowControl.inflowControl;
@@ -54,6 +61,8 @@ public class InflowControlService extends AbstractDataService<InflowControl, Inf
BooleanBuilder boolBuilder = new BooleanBuilder(); BooleanBuilder boolBuilder = new BooleanBuilder();
if (!StringUtils.isEmpty(searchName)) if (!StringUtils.isEmpty(searchName))
boolBuilder.and(qAdapterGroup.adptrbzwkgroupname.contains(searchName)); boolBuilder.and(qAdapterGroup.adptrbzwkgroupname.contains(searchName));
if (!StringUtils.isEmpty(searchUseYn))
boolBuilder.and(useYnCondition(qInflowControl.useyn, searchUseYn));
BooleanBuilder adapterWherePredicate = new BooleanBuilder(qAdapterGroup.adptriodstcd BooleanBuilder adapterWherePredicate = new BooleanBuilder(qAdapterGroup.adptriodstcd
.eq("I") .eq("I")
@@ -84,6 +93,10 @@ public class InflowControlService extends AbstractDataService<InflowControl, Inf
long totalCount = jpaQueryFactory long totalCount = jpaQueryFactory
.select(qAdapterGroup.adptrbzwkgroupname.count()) .select(qAdapterGroup.adptrbzwkgroupname.count())
.from(qAdapterGroup) .from(qAdapterGroup)
.leftJoin(qInflowControl)
.on(qAdapterGroup.adptrbzwkgroupname
.eq(qInflowControl.id.name)
.and(qInflowControl.id.type.eq(ADAPTER_TYPE_INFLOW)))
.where(boolBuilder) .where(boolBuilder)
.fetchOne(); .fetchOne();
@@ -121,17 +134,22 @@ public class InflowControlService extends AbstractDataService<InflowControl, Inf
return toDto(qAdapterGroup, qInflowControl, tuple); return toDto(qAdapterGroup, qInflowControl, tuple);
} }
public Page<InflowControlServiceDto> findAllForInterface(Pageable pageable, String searchName) { public Page<InflowControlServiceDto> findAllForInterface(Pageable pageable, String searchName, String searchUseYn) {
JPAQueryFactory jpaQueryFactory = new JPAQueryFactory(entityManager); JPAQueryFactory jpaQueryFactory = new JPAQueryFactory(entityManager);
QInflowControl qInflowControl = QInflowControl.inflowControl; QInflowControl qInflowControl = QInflowControl.inflowControl;
QEAIMessageEntity qEAIMessageEntity = QEAIMessageEntity.eAIMessageEntity; QEAIMessageEntity qEAIMessageEntity = QEAIMessageEntity.eAIMessageEntity;
BooleanBuilder boolBuilder = new BooleanBuilder();
boolBuilder.and(qEAIMessageEntity.eaisvcname.contains(searchName));
if (!StringUtils.isEmpty(searchUseYn))
boolBuilder.and(useYnCondition(qInflowControl.useyn, searchUseYn));
List<Tuple> tupleList = jpaQueryFactory List<Tuple> tupleList = jpaQueryFactory
.select(qInflowControl, qEAIMessageEntity.eaisvcname, qEAIMessageEntity.eaisvcdesc) .select(qInflowControl, qEAIMessageEntity.eaisvcname, qEAIMessageEntity.eaisvcdesc)
.from(qEAIMessageEntity) .from(qEAIMessageEntity)
.leftJoin(qInflowControl) .leftJoin(qInflowControl)
.on(qInflowControl.id.type.eq(INTERFACE_TYPE_INFLOW).and(qInflowControl.id.name.eq(qEAIMessageEntity.eaisvcname))) .on(qInflowControl.id.type.eq(INTERFACE_TYPE_INFLOW).and(qInflowControl.id.name.eq(qEAIMessageEntity.eaisvcname)))
.where(qEAIMessageEntity.eaisvcname.contains(searchName)) .where(boolBuilder)
.orderBy(qEAIMessageEntity.eaisvcname.asc()) .orderBy(qEAIMessageEntity.eaisvcname.asc())
.offset(pageable.getOffset()) .offset(pageable.getOffset())
.limit(pageable.getPageSize()) .limit(pageable.getPageSize())
@@ -145,7 +163,9 @@ public class InflowControlService extends AbstractDataService<InflowControl, Inf
long totalCount = jpaQueryFactory long totalCount = jpaQueryFactory
.select(qEAIMessageEntity.eaisvcname.count()) .select(qEAIMessageEntity.eaisvcname.count())
.from(qEAIMessageEntity) .from(qEAIMessageEntity)
.where(qEAIMessageEntity.eaisvcname.contains(searchName)) .leftJoin(qInflowControl)
.on(qInflowControl.id.type.eq(INTERFACE_TYPE_INFLOW).and(qInflowControl.id.name.eq(qEAIMessageEntity.eaisvcname)))
.where(boolBuilder)
.fetchOne(); .fetchOne();
return new PageImpl<>(dtoList, pageable, totalCount); return new PageImpl<>(dtoList, pageable, totalCount);
} }
@@ -199,19 +219,24 @@ public class InflowControlService extends AbstractDataService<InflowControl, Inf
return toDto(qClientEntity, qInflowControl, tuple); return toDto(qClientEntity, qInflowControl, tuple);
} }
public Page<InflowControlServiceDto> findAllForClient(Pageable pageable, String searchName) { public Page<InflowControlServiceDto> findAllForClient(Pageable pageable, String searchName, String searchUseYn) {
JPAQueryFactory jpaQueryFactory = new JPAQueryFactory(entityManager); JPAQueryFactory jpaQueryFactory = new JPAQueryFactory(entityManager);
QInflowControl qInflowControl = QInflowControl.inflowControl; QInflowControl qInflowControl = QInflowControl.inflowControl;
QClientEntity qClientEntity = QClientEntity.clientEntity; QClientEntity qClientEntity = QClientEntity.clientEntity;
BooleanBuilder boolBuilder = new BooleanBuilder();
boolBuilder.and(qClientEntity.clientname.contains(searchName));
if (!StringUtils.isEmpty(searchUseYn))
boolBuilder.and(useYnCondition(qInflowControl.useyn, searchUseYn));
List<Tuple> tupleList = jpaQueryFactory List<Tuple> tupleList = jpaQueryFactory
.select(qInflowControl, qClientEntity.clientid, qClientEntity.clientname) .select(qInflowControl, qClientEntity.clientid, qClientEntity.clientname)
.from(qClientEntity) .from(qClientEntity)
.leftJoin(qInflowControl) .leftJoin(qInflowControl)
.on(qInflowControl.id.type.eq(CLIENT_TYPE_INFLOW) .on(qInflowControl.id.type.eq(CLIENT_TYPE_INFLOW)
.and(qInflowControl.id.name.eq(qClientEntity.clientid))) .and(qInflowControl.id.name.eq(qClientEntity.clientid)))
.where(qClientEntity.clientname.contains(searchName)) .where(boolBuilder)
.orderBy(qClientEntity.clientname.asc()) .orderBy(qClientEntity.clientname.asc())
.offset(pageable.getOffset()) .offset(pageable.getOffset())
.limit(pageable.getPageSize()) .limit(pageable.getPageSize())
@@ -225,7 +250,10 @@ public class InflowControlService extends AbstractDataService<InflowControl, Inf
long totalCount = jpaQueryFactory long totalCount = jpaQueryFactory
.select(qClientEntity.clientname.count()) .select(qClientEntity.clientname.count())
.from(qClientEntity) .from(qClientEntity)
.where(qClientEntity.clientname.contains(searchName)) .leftJoin(qInflowControl)
.on(qInflowControl.id.type.eq(CLIENT_TYPE_INFLOW)
.and(qInflowControl.id.name.eq(qClientEntity.clientid)))
.where(boolBuilder)
.fetchOne(); .fetchOne();
return new PageImpl<>(dtoList, pageable, totalCount); return new PageImpl<>(dtoList, pageable, totalCount);
} }
@@ -266,12 +294,21 @@ public class InflowControlService extends AbstractDataService<InflowControl, Inf
QInflowControlLog qInflowConfrolLog = QInflowControlLog.inflowControlLog; QInflowControlLog qInflowConfrolLog = QInflowControlLog.inflowControlLog;
QInflowControlGroup qinflowControlGroup = QInflowControlGroup.inflowControlGroup; QInflowControlGroup qinflowControlGroup = QInflowControlGroup.inflowControlGroup;
String startTime = StringUtils.defaultIfEmpty(uiSearch.getSearchStartTime(), "000000");
String endTime = StringUtils.defaultIfEmpty(uiSearch.getSearchEndTime(), "235959");
BooleanBuilder predicate = new BooleanBuilder(); BooleanBuilder predicate = new BooleanBuilder();
predicate predicate
.and(qInflowConfrolLog.id.msgdpstyms .and(qInflowConfrolLog.id.msgdpstyms
.between(uiSearch.getSearchStartDate() + "000000000", .between(uiSearch.getSearchStartDate() + startTime + "000",
uiSearch.getSearchEndDate() + "235959999")); uiSearch.getSearchEndDate() + endTime + "999"));
if (!StringUtils.isEmpty(uiSearch.getSearchInstanceName()))
predicate.and(qInflowConfrolLog.eaisevrinstncname.contains(uiSearch.getSearchInstanceName()));
if (!StringUtils.isEmpty(uiSearch.getSearchApiName()))
predicate.and(qInflowConfrolLog.eaisvcname.contains(uiSearch.getSearchApiName()));
if (!StringUtils.isEmpty(uiSearch.getSearchAdapterGroupName()))
predicate.and(qInflowConfrolLog.adptrbzwkgroupname.contains(uiSearch.getSearchAdapterGroupName()));
return getJPAQueryFactory() return getJPAQueryFactory()
.select( .select(
qInflowConfrolLog, qInflowConfrolLog,
@@ -46,7 +46,7 @@ public interface ApiStatusRepository extends BaseRepository<ApiStatus, String> {
+ " , (SELECT CASE WHEN TOTAL = 0 THEN 'X'" + " , (SELECT CASE WHEN TOTAL = 0 THEN 'X'"
+ " WHEN (TOTAL - SUCCESS) * 100 / TOTAL > :errorRate THEN 'Y'" + " WHEN (TOTAL - SUCCESS) * 100 / TOTAL > :errorRate THEN 'Y'"
+ " ELSE 'N' END" + " ELSE 'N' END"
+ " FROM (SELECT SUM(SUCCESS_CNT + TIMEOUT_CNT + SYSTEM_ERR_CNT + BIZ_ERR_CNT) AS TOTAL" + " FROM (SELECT NVL(SUM(SUCCESS_CNT + TIMEOUT_CNT + SYSTEM_ERR_CNT + BIZ_ERR_CNT),0) AS TOTAL"
+ " , SUM(SUCCESS_CNT) AS SUCCESS" + " , SUM(SUCCESS_CNT) AS SUCCESS"
+ " FROM API_STATS_MINUTE" + " FROM API_STATS_MINUTE"
+ " WHERE A.EAISVCNAME = API_NAME" + " WHERE A.EAISVCNAME = API_NAME"
@@ -56,7 +56,7 @@ public interface ApiStatusRepository extends BaseRepository<ApiStatus, String> {
+ " , (SELECT CASE WHEN TOTAL = 0 THEN 'X'" + " , (SELECT CASE WHEN TOTAL = 0 THEN 'X'"
+ " WHEN RESP_SUM / TOTAL > :delayAvgRespTime THEN 'Y'" + " WHEN RESP_SUM / TOTAL > :delayAvgRespTime THEN 'Y'"
+ " ELSE 'N' END" + " ELSE 'N' END"
+ " FROM (SELECT SUM(SUCCESS_CNT + TIMEOUT_CNT + SYSTEM_ERR_CNT + BIZ_ERR_CNT) AS TOTAL" + " FROM (SELECT NVL(SUM(SUCCESS_CNT + TIMEOUT_CNT + SYSTEM_ERR_CNT + BIZ_ERR_CNT),0) AS TOTAL"
+ " , SUM(SUCCESS_CNT) AS SUCCESS" + " , SUM(SUCCESS_CNT) AS SUCCESS"
+ " , SUM((SUCCESS_CNT + TIMEOUT_CNT + SYSTEM_ERR_CNT + BIZ_ERR_CNT) * AVG_RESP_TIME) AS RESP_SUM" + " , SUM((SUCCESS_CNT + TIMEOUT_CNT + SYSTEM_ERR_CNT + BIZ_ERR_CNT) * AVG_RESP_TIME) AS RESP_SUM"
+ " FROM API_STATS_MINUTE" + " FROM API_STATS_MINUTE"
@@ -0,0 +1,115 @@
package com.eactive.eai.rms.ext.djb.common;
import lombok.Data;
import org.hibernate.validator.constraints.NotEmpty;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.Pattern;
@Data
public class DjbProperty {
// SMS 2차 인증
@DjbPropertyValue(
key = "djb.sms_auth.enabled",
defaultValue = "true",
description = "SMS 2차 인증 사용 여부. true 시 휴대폰번호 없는 사용자는 ID/PW 로그인 불가")
private boolean smsAuthEnabled = false;
@DjbPropertyValue(
key = "djb.sms_auth.mode",
defaultValue = "real",
description = "인증번호 생성 모드: real(랜덤), hhmm00(현재시각+00), fixed(고정값)")
@Pattern(regexp = "^(real|hhmm00|fixed)$", message = "djb.sms_auth.mode는 real, hhmm00, fixed 중 하나여야 합니다.")
private String smsAuthMode = "real";
@DjbPropertyValue(
key = "djb.sms_auth.fixed_value",
defaultValue = "654321",
description = "mode가 fixed일 때 사용할 고정 인증번호 (6자리 숫자)")
@Pattern(regexp = "^\\d{6}$", message = "djb.sms_auth.fixed_value는 6자리 숫자여야 합니다.")
private String smsAuthFixedValue = "654321";
@DjbPropertyValue(
key = "djb.api.allow_ip_list",
description = "API 접근 허용 IP 목록 (콤마(,)로 구분된 IP들, 예: 192.168.0.1, 192.168.1.*,127.*.*.*",
defaultValue = "10.15.106.*, 10.14.8.*, 10.15.8.*, 192.168.240.*, 127.0.0.1, 192.168.132.*, 192.168.131.*"
)
private String apiAllowIpList = "10.15.106.*, 10.14.8.*, 10.15.8.*, 192.168.240.*, 127.0.0.1, 192.168.132.*, 192.168.131.*";
public String toJsonString(boolean isPretty) {
return isPretty
? new GsonBuilder().setPrettyPrinting().create().toJson(this)
: new Gson().toJson(this);
}
// 기본 프로퍼티 목록을 콘솔로 출력 (가이드용)
public static void main(String[] args) {
System.out.println("| Key | Default Value | Description |");
System.out.println("|-----|---------------|-------------|");
for (java.lang.reflect.Field field : DjbProperty.class.getDeclaredFields()) {
DjbPropertyValue annotation = field.getAnnotation(DjbPropertyValue.class);
if (annotation != null) {
String key = annotation.key();
String defaultValue = annotation.defaultValue();
String description = annotation.description();
System.out.printf("| %s | %s | %s |%n", key, defaultValue, description);
}
}
}
}
@@ -0,0 +1,92 @@
package com.eactive.eai.rms.ext.djb.common;
import lombok.extern.slf4j.Slf4j;
import java.util.function.Function;
/**
* DjbProperty 싱글톤 홀더.
*
* <p>모든 곳에서 {@code DjbPropertyHolder.get()}으로 현재 DjbProperty에 접근합니다.
* eapim-admin에서 초기화 시 {@code initialize()}를 호출하고,
* MonitoringContext.refresh() 시 {@code reload()}를 호출하여 값을 갱신합니다.</p>
*
* <p>사용 예시:</p>
* <pre>
* // 값 접근
* String url = DjbPropertyHolder.get().getUmsHostUrl();
*
* // 초기화 (eapim-admin에서)
* DjbPropertyInjector injector = new DjbPropertyInjector(service, "Monitoring");
* DjbPropertyHolder.initialize(injector::inject);
*
* // reload (MonitoringContext.refresh() 시)
* DjbPropertyHolder.reload();
*
* // 테스트용
* DjbPropertyHolder.setForTest(mockProperty);
* </pre>
*/
@Slf4j
public class DjbPropertyHolder {
private static volatile DjbProperty instance = new DjbProperty();
private static volatile Function<DjbProperty, DjbProperty> injector;
private DjbPropertyHolder() {
// 유틸리티 클래스
}
/**
* 현재 DjbProperty 인스턴스를 반환합니다.
*/
public static DjbProperty get() {
return instance;
}
/**
* 초기화 함수를 등록하고 최초 로딩을 수행합니다.
* eapim-admin 부팅 시 MonitoringContextImpl.init()에서 호출됩니다.
*
* @param injectorFn DjbProperty를 DB에서 로딩하는 함수 (DjbPropertyInjector::inject)
*/
public static void initialize(Function<DjbProperty, DjbProperty> injectorFn) {
injector = injectorFn;
reload();
log.info("DjbPropertyHolder 초기화 완료");
}
/**
* DB에서 프로퍼티를 다시 로딩합니다.
* MonitoringContext.refresh() 호출 시 함께 호출됩니다.
*/
public static void reload() {
if (injector != null) {
try {
instance = injector.apply(new DjbProperty());
log.info("DjbProperty reload 완료");
} catch (Exception e) {
log.error("DjbProperty reload 실패: {}", e.getMessage(), e);
}
} else {
log.debug("DjbPropertyHolder: injector가 설정되지 않음 (테스트 환경일 수 있음)");
}
}
/**
* 테스트용 - 직접 인스턴스를 설정합니다.
*/
public static void setForTest(DjbProperty testProperty) {
instance = testProperty;
log.debug("DjbProperty 테스트 인스턴스 설정");
}
/**
* 테스트용 - 기본값으로 초기화합니다.
*/
public static void resetForTest() {
instance = new DjbProperty();
injector = null;
log.debug("DjbPropertyHolder 테스트 초기화");
}
}
@@ -1,10 +1,8 @@
package com.eactive.ext.kjb.util; package com.eactive.eai.rms.ext.djb.common;
import com.eactive.eai.rms.data.entity.man.monitoringProperty.MonitoringProperty; import com.eactive.eai.rms.data.entity.man.monitoringProperty.MonitoringProperty;
import com.eactive.eai.rms.data.entity.man.monitoringProperty.MonitoringPropertyId; import com.eactive.eai.rms.data.entity.man.monitoringProperty.MonitoringPropertyId;
import com.eactive.eai.rms.data.entity.man.monitoringProperty.service.MonitoringPropertyService; import com.eactive.eai.rms.data.entity.man.monitoringProperty.service.MonitoringPropertyService;
import com.eactive.ext.kjb.common.KjbProperty;
import com.eactive.ext.kjb.common.KjbPropertyValue;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@@ -12,52 +10,49 @@ import java.lang.reflect.Field;
import java.util.Optional; import java.util.Optional;
@Slf4j @Slf4j
public class KjbPropertyInjector { public class DjbPropertyInjector {
private final MonitoringPropertyService monitoringPropertyService; private final MonitoringPropertyService monitoringPropertyService;
private final String groupId; private final String groupId;
public KjbPropertyInjector(MonitoringPropertyService monitoringPropertyService, String groupId) { public DjbPropertyInjector(MonitoringPropertyService monitoringPropertyService, String groupId) {
this.monitoringPropertyService = monitoringPropertyService; this.monitoringPropertyService = monitoringPropertyService;
this.groupId = groupId; this.groupId = groupId;
} }
/** /**
* KjbProperty의 URL 값들이 설정되어 있는지 확인 * DjbProperty의 URL 값들이 설정되어 있는지 확인
* @param property 검사할 KjbProperty 객체 * @param property 검사할 DjbProperty 객체
* @return URL 값들이 모두 설정되어 있으면 true * @return URL 값들이 모두 설정되어 있으면 true
*/ */
public boolean isUrlConfigured(KjbProperty property) { public boolean isUrlConfigured(DjbProperty property) {
if (property == null) return false; if (property == null) return false;
// 주요 URL 값들 하나라도 설정되어 있으면 true return StringUtils.isNotEmpty(property.getApiAllowIpList());
return StringUtils.isNotEmpty(property.getEaiBatchUrl())
|| StringUtils.isNotEmpty(property.getUmsHostUrl())
|| StringUtils.isNotEmpty(property.getObpUrl());
} }
/** /**
* URL 값이 비어있을 경우 DB에서 재로딩 시도 * URL 값이 비어있을 경우 DB에서 재로딩 시도
* @param property 기존 KjbProperty 객체 * @param property 기존 DjbProperty 객체
* @return 재로딩된 KjbProperty 객체 (재로딩 실패 기존 객체 반환) * @return 재로딩된 DjbProperty 객체 (재로딩 실패 기존 객체 반환)
*/ */
public KjbProperty reloadIfUrlEmpty(KjbProperty property) { public DjbProperty reloadIfUrlEmpty(DjbProperty property) {
if (property == null) { if (property == null) {
return inject(new KjbProperty()); return inject(new DjbProperty());
} }
if (!isUrlConfigured(property)) { if (!isUrlConfigured(property)) {
log.info("KjbProperty URL 값이 비어있어 DB에서 재로딩 시도"); log.info("DjbProperty URL 값이 비어있어 DB에서 재로딩 시도");
try { try {
KjbProperty reloaded = inject(new KjbProperty()); DjbProperty reloaded = inject(new DjbProperty());
if (isUrlConfigured(reloaded)) { if (isUrlConfigured(reloaded)) {
log.info("KjbProperty 재로딩 성공"); log.info("DjbProperty 재로딩 성공");
return reloaded; return reloaded;
} else { } else {
log.warn("KjbProperty 재로딩 후에도 URL 값이 비어있음"); log.warn("DjbProperty 재로딩 후에도 URL 값이 비어있음");
} }
} catch (Exception e) { } catch (Exception e) {
log.error("KjbProperty 재로딩 실패: {}", e.getMessage(), e); log.error("DjbProperty 재로딩 실패: {}", e.getMessage(), e);
} }
} }
@@ -68,7 +63,7 @@ public class KjbPropertyInjector {
Class<?> clazz = property.getClass(); Class<?> clazz = property.getClass();
for (Field field : clazz.getDeclaredFields()) { for (Field field : clazz.getDeclaredFields()) {
KjbPropertyValue anno = field.getAnnotation(KjbPropertyValue.class); DjbPropertyValue anno = field.getAnnotation(DjbPropertyValue.class);
if (anno != null) { if (anno != null) {
String key = anno.key(); String key = anno.key();
@@ -0,0 +1,14 @@
package com.eactive.eai.rms.ext.djb.common;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface DjbPropertyValue {
String key();
String defaultValue() default "";
String description() default "";
}
@@ -1,4 +1,4 @@
package com.eactive.eai.rms.ext.kjb.smsauth; package com.eactive.eai.rms.ext.djb.smsauth;
import com.eactive.apim.portal.user.entity.UserInfo; import com.eactive.apim.portal.user.entity.UserInfo;
import com.eactive.eai.rms.common.interceptor.InterceptorSkipController; import com.eactive.eai.rms.common.interceptor.InterceptorSkipController;
@@ -1,4 +1,4 @@
package com.eactive.eai.rms.ext.kjb.smsauth; package com.eactive.eai.rms.ext.djb.smsauth;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
@@ -1,4 +1,4 @@
package com.eactive.eai.rms.ext.kjb.smsauth; package com.eactive.eai.rms.ext.djb.smsauth;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.time.LocalDateTime; import java.time.LocalDateTime;
@@ -13,11 +13,9 @@ import org.springframework.stereotype.Service;
import com.eactive.apim.portal.template.entity.MessageCode; import com.eactive.apim.portal.template.entity.MessageCode;
import com.eactive.apim.portal.user.entity.UserInfo; import com.eactive.apim.portal.user.entity.UserInfo;
import com.eactive.eai.rms.common.login.LoginVo; import com.eactive.eai.rms.common.login.LoginVo;
import com.eactive.eai.rms.ext.djb.common.DjbProperty;
import com.eactive.eai.rms.ext.djb.common.DjbPropertyHolder;
import com.eactive.eai.rms.ext.djb.ums.UmsManager; import com.eactive.eai.rms.ext.djb.ums.UmsManager;
import com.eactive.ext.kjb.common.KjbProperty;
import com.eactive.ext.kjb.common.KjbPropertyHolder;
import com.eactive.ext.kjb.common.KjbUmsException;
import com.eactive.ext.kjb.ums.KjbUmsService;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@@ -46,13 +44,11 @@ public class SmsAuthService {
private final UmsManager ums; private final UmsManager ums;
private static KjbProperty getProperty() { private static DjbProperty getProperty() {
return KjbPropertyHolder.get(); return DjbPropertyHolder.get();
} }
private KjbUmsService getUmsService() {
return KjbUmsService.getInstance();
}
/** /**
* SMS 인증이 필요한지 확인 * SMS 인증이 필요한지 확인
@@ -86,7 +82,7 @@ public class SmsAuthService {
* 인증번호 생성 * 인증번호 생성
*/ */
public String generateAuthCode() { public String generateAuthCode() {
KjbProperty prop = getProperty(); DjbProperty prop = getProperty();
String mode = prop.getSmsAuthMode(); String mode = prop.getSmsAuthMode();
if (MODE_FIXED.equalsIgnoreCase(mode)) { if (MODE_FIXED.equalsIgnoreCase(mode)) {
@@ -113,7 +109,7 @@ public class SmsAuthService {
HashMap<String,Object> params = new HashMap<String, Object>(); HashMap<String,Object> params = new HashMap<String, Object>();
params.put("authNumber", authCode); params.put("authNumber", authCode);
params.put("userName", userInfo.getUsername()); params.put("userName", userInfo.getUsername());
log.debug("\n\n\nDDDDDDDDD" + userInfo.toString());
ums.send(userInfo, MessageCode.ADMIN_VERIFICATION_MOBILEPHONE, params); ums.send(userInfo, MessageCode.ADMIN_VERIFICATION_MOBILEPHONE, params);
return true; return true;
} catch (Exception e) { } catch (Exception e) {
@@ -8,6 +8,7 @@ import java.net.URL;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
@@ -18,8 +19,10 @@ import org.springframework.stereotype.Service;
import com.eactive.apim.portal.common.util.PhoneNumberUtil; import com.eactive.apim.portal.common.util.PhoneNumberUtil;
import com.eactive.apim.portal.portalproperty.service.PortalPropertyService; import com.eactive.apim.portal.portalproperty.service.PortalPropertyService;
import com.eactive.apim.portal.template.entity.MessageRequest; import com.eactive.apim.portal.template.entity.MessageRequest;
import com.eactive.eai.common.util.StringUtil;
import com.eactive.eai.common.util.SystemUtil; import com.eactive.eai.common.util.SystemUtil;
import com.eactive.eai.rms.data.entity.man.monitoringProperty.service.MonitoringPropertyService; import com.eactive.eai.rms.data.entity.man.monitoringProperty.service.MonitoringPropertyService;
import com.eactive.eai.rms.data.entity.onl.apim.messagerequest.MessageRequestService;
import com.eactive.eai.rms.ext.djb.ums.vo.EmailDataVO; import com.eactive.eai.rms.ext.djb.ums.vo.EmailDataVO;
import com.eactive.eai.rms.ext.djb.ums.vo.SmsDataVO; import com.eactive.eai.rms.ext.djb.ums.vo.SmsDataVO;
import com.eactive.eai.rms.ext.djb.ums.vo.StdHeadVO; import com.eactive.eai.rms.ext.djb.ums.vo.StdHeadVO;
@@ -39,12 +42,15 @@ public class UmsService {
private static final String SYS_DVCD = "API"; //시스템코드 private static final String SYS_DVCD = "API"; //시스템코드
private static final String BZWK_DVCD = "99"; //업무구분코드 private static final String BZWK_DVCD = "99"; //업무구분코드
private static final String SUB_BZWK_CD = "9019"; //세부구분코드 private static final String SUB_BZWK_CD = "9019"; //세부구분코드
private static final String TEAM_DVCD = "00"; //팀구분코드 TODO: 변경해야함
private final MonitoringPropertyService propertyService; private final MonitoringPropertyService propertyService;
private final MessageRequestService messageRequestService;
public UmsService(MonitoringPropertyService propertyService) { public UmsService(MonitoringPropertyService propertyService, MessageRequestService messageRequestService) {
this.propertyService = propertyService; this.propertyService = propertyService;
this.messageRequestService = messageRequestService;
} }
public boolean send(MessageRequest messageRequest) throws Exception { public boolean send(MessageRequest messageRequest) throws Exception {
@@ -168,17 +174,16 @@ public class UmsService {
// 스윙챗 알림 데이터 // 스윙챗 알림 데이터
SwingDataVO swiDataVO = new SwingDataVO(); SwingDataVO swiDataVO = new SwingDataVO();
swiDataVO.setNotiCode("CNO_FEP_HMP_0002"); swiDataVO.setNotiCode("CNO_FEP_APIM_0002"); //0001:긴급, 0002:중요
swiDataVO.setNotiTitle("알림"); swiDataVO.setNotiTitle("알림");
swiDataVO.setNotiContent(messageRequest.getMessage()); swiDataVO.setNotiContent(messageRequest.getMessage());
// 스윙챗 알림 데이터 // 스윙챗 알림 데이터
List<SwingReceiverDataVO> swiReDataVOList = new ArrayList<SwingReceiverDataVO>(); List<SwingReceiverDataVO> swiReDataVOList = new ArrayList<SwingReceiverDataVO>();
SwingReceiverDataVO receiver = new SwingReceiverDataVO(); SwingReceiverDataVO receiver = new SwingReceiverDataVO();
receiver.setType(""); receiver.setType("PSN");
receiver.setCompanyCode("CB"); receiver.setCompanyCode("CB");
receiver.setCode(messageRequest.getMessengerId()); receiver.setCode(messageRequest.getMessengerId());
receiver.setPrdNm("");
swiReDataVOList.add(receiver); swiReDataVOList.add(receiver);
swiDataVO.setReceiverInfo(swiReDataVOList); swiDataVO.setReceiverInfo(swiReDataVOList);
@@ -200,22 +205,27 @@ public class UmsService {
// Sms 데이터 // Sms 데이터
SmsDataVO dataVO = new SmsDataVO(); SmsDataVO dataVO = new SmsDataVO();
dataVO.setSnd_dman_dt(jsonData[0]); dataVO.setSnd_dman_dt(jsonData[0]);
dataVO.setSnd_dman_id(""); dataVO.setSnd_dman_id(this.getSndDmanId(messageRequest.getMessageType()));
dataVO.setSnd_dvcd("EAI"); dataVO.setSnd_dvcd("EAI");
dataVO.setMsg_titl(""); dataVO.setMsg_titl(StringUtil.chunkString(messageRequest.getSubject(), 40, false)); // MS949 40byte 초과시 절단
dataVO.setMsg_ctnt(messageRequest.getMessage()); dataVO.setMsg_ctnt(messageRequest.getMessage());
dataVO.setRecvr_no(PhoneNumberUtil.digitsOnly(messageRequest.getPhone())); dataVO.setRecvr_no(PhoneNumberUtil.digitsOnly(messageRequest.getPhone()));
dataVO.setSndn_no("15880079"); //발신번호 dataVO.setSndn_no("15880079"); //발신번호
dataVO.setSnd_empno(""); //요청자 dataVO.setSnd_empno("APIM"); //요청자
dataVO.setSnd_emp_dprm_cd(""); //요청부서코드 dataVO.setSnd_emp_dprm_cd("132"); //요청부서코드
if ("SMS".equals(messageRequest.getMessageType())) { if ("SMS".equals(messageRequest.getMessageType())) {
dataVO.setLtrs_snd_tycd("1"); // 1:SMS 2:LMS/MMS dataVO.setLtrs_snd_tycd("1"); // 1:SMS 2:LMS/MMS
dataVO.setMsg_snd_tycd("0000"); // 알림톡 메세지 종류 0000:SMS, 1000:LMS, 1008:알림톡, .... dataVO.setMsg_snd_tycd("0000"); // 알림톡 메세지 종류 0000:SMS, 1000:LMS, 1008:알림톡, ....
} else if ("KAKAO".equals(messageRequest.getMessageType())) { } else if ("KAKAO".equals(messageRequest.getMessageType())) {
dataVO.setLtrs_snd_tycd("2"); // 1:SMS 2:LMS/MMS dataVO.setLtrs_snd_tycd("2"); // 1:SMS 2:LMS/MMS
dataVO.setMsg_snd_tycd("1008"); // 알림톡 메세지 종류 0000:SMS, 1000:LMS, 1008:알림톡, .... dataVO.setMsg_snd_tycd("1008"); // 알림톡 메세지 종류 0000:SMS, 1000:LMS, 1008:알림톡, ....
dataVO.setAlmtk_snd_prfl_key(""); // 플러스친구아이디 //ADMIN으로 시작하면 제주은행 임직원, 나머지는 제주은행 고객용
dataVO.setAlmtk_tmplt_cd(""); // 템플릿코드 if (messageRequest.getMessageCode().name().startsWith("ADMIN")) {
dataVO.setAlmtk_snd_prfl_key(propertyService.getPropertyValue("Monitoring", "djb.ums.sms.almtk_snd_prtl_key.intra", "")); // 제주은행 임직원 플러스친구아이디
} else {
dataVO.setAlmtk_snd_prfl_key(propertyService.getPropertyValue("Monitoring", "djb.ums.sms.almtk_snd_prtl_key.public", "")); // 제주은행 플러스친구아이디
}
dataVO.setAlmtk_tmplt_cd(BZWK_DVCD + TEAM_DVCD + SUB_BZWK_CD + "001"); // 템플릿코드
dataVO.setAlmtk_err_ltrs_snd_yn("Y"); // 오류자문자발송여부 dataVO.setAlmtk_err_ltrs_snd_yn("Y"); // 오류자문자발송여부
dataVO.setAlmtk_btn_trnm_tycd("2"); // 카카오버튼전송방식 1-format string 2-JSON 3-XML dataVO.setAlmtk_btn_trnm_tycd("2"); // 카카오버튼전송방식 1-format string 2-JSON 3-XML
} }
@@ -243,26 +253,26 @@ public class UmsService {
// 이메일 데이터 // 이메일 데이터
EmailDataVO dataVO = new EmailDataVO(); EmailDataVO dataVO = new EmailDataVO();
dataVO.setUms_evnt_id(""); // UMS이벤트ID(필수) dataVO.setUms_evnt_id(""); // TODO: UMS이벤트ID(필수)
dataVO.setSnd_dman_dt(jsonData[0]); dataVO.setSnd_dman_dt(jsonData[0]);
dataVO.setSnd_dman_id(""); dataVO.setSnd_dman_id("");
dataVO.setSnd_dman_sno("1"); dataVO.setSnd_dman_sno("1");
dataVO.setEmail_titl(messageRequest.getSubject()); // 메일제목 dataVO.setEmail_titl(messageRequest.getSubject()); // 메일제목
dataVO.setCstno(""); // 고객번호(필수) dataVO.setCstno(""); // TODO: 고객번호(필수)
dataVO.setSendr_emaddr(""); // 발신자 이메일 dataVO.setSendr_emaddr(""); // TODO: 발신자 이메일
dataVO.setSendr_nm(""); // 발신자 이름 dataVO.setSendr_nm(""); // TODO: 발신자 이름
dataVO.setSndbk_emaddr(""); // 리턴 이메일 dataVO.setSndbk_emaddr(""); // TODO: 리턴 이메일
dataVO.setRecvr_emaddr(messageRequest.getEmail()); // 수신자 이메일(필수) dataVO.setRecvr_emaddr(messageRequest.getEmail()); // 수신자 이메일(필수)
dataVO.setRecvr_nm(messageRequest.getUsername()); // 수신자명(필수) dataVO.setRecvr_nm(messageRequest.getUsername()); // 수신자명(필수)
dataVO.setTmplt_mpng_info(messageRequest.getMessage()); // 탬플릿매핑정보 dataVO.setTmplt_mpng_info(messageRequest.getMessage()); // 탬플릿매핑정보
dataVO.setTmplt_mpng_rptt_info(""); // 탬플릿매핑반복정보 dataVO.setTmplt_mpng_rptt_info(""); // 탬플릿매핑반복정보
dataVO.setSnd_empno(""); // 발신자ID(필수) dataVO.setSnd_empno("APIM"); // 발신자ID(필수)
dataVO.setSnd_emp_dprm_cd(""); // 발신자부서코드(필수) dataVO.setSnd_emp_dprm_cd("132"); // 발신자부서코드(필수)
dataVO.setBzwk_cd(BZWK_DVCD); // 업무구분코드 dataVO.setBzwk_cd(BZWK_DVCD); // 업무구분코드
dataVO.setSub_bzwk_cd(SUB_BZWK_CD); // 업무세부코드 dataVO.setSub_bzwk_cd(SUB_BZWK_CD); // 업무세부코드
dataVO.setSys_dvcd(SYS_DVCD); dataVO.setSys_dvcd(SYS_DVCD);
dataVO.setTx_dt(jsonData[0]); //거래일자 dataVO.setTx_dt(jsonData[0]); // 거래일자
dataVO.setTx_tktm(jsonData[1].substring(0,6)); //거래시간 dataVO.setTx_tktm(jsonData[1].substring(0,6)); // 거래시간
List<EmailDataVO> dataList = new ArrayList<EmailDataVO>(); List<EmailDataVO> dataList = new ArrayList<EmailDataVO>();
dataList.add(dataVO); dataList.add(dataVO);
@@ -334,4 +344,24 @@ public class UmsService {
} }
return buffer.toString(); return buffer.toString();
} }
private String getSndDmanId(String messageType) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
String yyyymmdd = sdf.format(new Date());
String uuid = SYS_DVCD + BZWK_DVCD + SUB_BZWK_CD + yyyymmdd + "O" ;
if ("SMS".equals(messageType)) {
uuid += "S";
} else if ("LMS".equals(messageType)) {
uuid += "L";
} else if ("KAKAO".equals(messageType)) {
uuid += "K";
}
long seq = messageRequestService.countSmsKakaoRequestsOnDate(LocalDate.now()) + 1;
uuid += String.format("%014d", seq);
return uuid;
}
} }
@@ -14,9 +14,9 @@ import com.eactive.eai.rms.data.entity.man.user.UserRoleId;
import com.eactive.eai.rms.data.entity.man.user.UserRoleService; import com.eactive.eai.rms.data.entity.man.user.UserRoleService;
import com.eactive.eai.rms.data.entity.man.user.UserServiceTypeService; import com.eactive.eai.rms.data.entity.man.user.UserServiceTypeService;
import com.eactive.eai.rms.onl.apim.masking.MaskingUtils; import com.eactive.eai.rms.onl.apim.masking.MaskingUtils;
import com.eactive.ext.kjb.common.KjbProperty; //import com.eactive.ext.kjb.common.KjbProperty;
import com.eactive.ext.kjb.common.KjbPropertyHolder; //import com.eactive.ext.kjb.common.KjbPropertyHolder;
import com.eactive.ext.kjb.sso.KjbSsoModule; //import com.eactive.ext.kjb.sso.KjbSsoModule;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
@@ -52,108 +52,108 @@ public class SsoController implements InterceptorSkipController {
this.mainController = mainController; this.mainController = mainController;
} }
private KjbSsoModule getSsoModule() { // private KjbSsoModule getSsoModule() {
return KjbSsoModule.getInstance(); // return KjbSsoModule.getInstance();
} // }
//
// private KjbProperty getProperty() {
// return KjbPropertyHolder.get();
// }
private KjbProperty getProperty() { // @RequestMapping(value = "/sso/login.do")
return KjbPropertyHolder.get(); // public String login(HttpServletRequest req, HttpServletResponse res,
} // @RequestParam(name = "UURL", required = false) String uurl) throws Exception {
// HttpSession session = req.getSession();
@RequestMapping(value = "/sso/login.do") // String ssoId = getSsoModule().getSsoId(req);
public String login(HttpServletRequest req, HttpServletResponse res, //
@RequestParam(name = "UURL", required = false) String uurl) throws Exception { // log.debug("ssoId : {}", ssoId);
HttpSession session = req.getSession(); // log.debug("uurl : {}", uurl);
String ssoId = getSsoModule().getSsoId(req); //
// // 디버깅용 쿠키 목록 출력
log.debug("ssoId : {}", ssoId); // if (log.isDebugEnabled()) {
log.debug("uurl : {}", uurl); // Cookie[] cookies = req.getCookies();
// if (cookies == null || cookies.length == 0) {
// 디버깅용 쿠키 목록 출력 // log.debug("No cookies");
if (log.isDebugEnabled()) { // } else {
Cookie[] cookies = req.getCookies(); // for (Cookie c : cookies) {
if (cookies == null || cookies.length == 0) { // log.debug("Cookie[{}] : Domain={}, Path={}",
log.debug("No cookies"); // c.getName(), c.getDomain(), c.getPath());
} else { // }
for (Cookie c : cookies) { // }
log.debug("Cookie[{}] : Domain={}, Path={}", // }
c.getName(), c.getDomain(), c.getPath()); //
} // if (StringUtils.isEmpty(uurl)) {
} // uurl = getSsoModule().getAscpUrl();
} // log.debug("UURL is empty, set to ASCP URL");
// log.debug("uurl : {}", uurl);
if (StringUtils.isEmpty(uurl)) { // }
uurl = getSsoModule().getAscpUrl(); //
log.debug("UURL is empty, set to ASCP URL"); // if (ssoId == null) {
log.debug("uurl : {}", uurl); // getSsoModule().goLoginPage(res, uurl);
} // return null;
// } else {
if (ssoId == null) { // // EAM 세션 검증
getSsoModule().goLoginPage(res, uurl); // String retCode = getSsoModule().getEamSessionCheck(req, res);
return null; // log.debug("retCode : {}", retCode);
} else { //
// EAM 세션 검증 // log.debug("Property - sso.ignore_validation = {}", getProperty().isSsoIgnoreValidation());
String retCode = getSsoModule().getEamSessionCheck(req, res); // if (!getProperty().isSsoIgnoreValidation() && !"0".equalsIgnoreCase(retCode)) {
log.debug("retCode : {}", retCode); // // SSO 검증 실패 시 에러 메시지 설정 및 로그인 페이지로 이동
// log.warn("SSO 검증 실패 - retCode: {}, ssoId: {}", retCode, ssoId);
log.debug("Property - sso.ignore_validation = {}", getProperty().isSsoIgnoreValidation()); // session.setAttribute("resultMsg", "SSO 로그인을 실패 하였습니다. (오류코드: " + retCode + ") 다른 로그인 방식을 시도 하세요");
if (!getProperty().isSsoIgnoreValidation() && !"0".equalsIgnoreCase(retCode)) { // return "redirect:/loginForm.do";
// SSO 검증 실패 시 에러 메시지 설정 및 로그인 페이지로 이동 // }
log.warn("SSO 검증 실패 - retCode: {}, ssoId: {}", retCode, ssoId); //
session.setAttribute("resultMsg", "SSO 로그인을 실패 하였습니다. (오류코드: " + retCode + ") 다른 로그인 방식을 시도 하세요"); // if (StringUtils.isEmpty(ssoId)) {
return "redirect:/loginForm.do"; // session.setAttribute("resultMsg", "SSO 인증 정보를 획득하지 못했습니다.");
} // return "redirect:/loginForm.do";
// }
if (StringUtils.isEmpty(ssoId)) { //
session.setAttribute("resultMsg", "SSO 인증 정보를 획득하지 못했습니다."); // if (getProperty().isSsoSyncInfo()) {
return "redirect:/loginForm.do"; // // 기본 정보
} // Properties info = getSsoModule().getUserInfos(ssoId);
// // 확장 정보
if (getProperty().isSsoSyncInfo()) { // Properties extendedInfo = getSsoModule().getUserExFields(ssoId);
// 기본 정보 //
Properties info = getSsoModule().getUserInfos(ssoId); // // 필수 필드 검증
// 확장 정보 // try {
Properties extendedInfo = getSsoModule().getUserExFields(ssoId); // validateRequiredFields(info, extendedInfo);
// } catch (IllegalStateException e) {
// 필수 필드 검증 // log.warn("SSO 필수 필드 검증 실패: {}", e.getMessage());
try { // session.setAttribute("resultMsg", e.getMessage());
validateRequiredFields(info, extendedInfo); // return "redirect:/loginForm.do";
} catch (IllegalStateException e) { // }
log.warn("SSO 필수 필드 검증 실패: {}", e.getMessage()); //
session.setAttribute("resultMsg", e.getMessage()); // // 사용자 정보 로깅 (개인정보 마스킹 처리)
return "redirect:/loginForm.do"; // String userId = info.getProperty("USERID");
} // String name = info.getProperty("NAME");
// String email = info.getProperty("EMAIL");
// 사용자 정보 로깅 (개인정보 마스킹 처리) // String cellPhoneNo = extendedInfo != null ? extendedInfo.getProperty("CELLPHONENO") : null;
String userId = info.getProperty("USERID"); //
String name = info.getProperty("NAME"); // log.info("SSO 로그인 시도 - userId: {}", userId);
String email = info.getProperty("EMAIL"); // log.debug("SSO 사용자 정보 - userId: {}, name: {}, phone: {}, email: {}",
String cellPhoneNo = extendedInfo != null ? extendedInfo.getProperty("CELLPHONENO") : null; // userId,
// MaskingUtils.maskName(name),
log.info("SSO 로그인 시도 - userId: {}", userId); // MaskingUtils.maskPhoneNumber(cellPhoneNo),
log.debug("SSO 사용자 정보 - userId: {}, name: {}, phone: {}, email: {}", // MaskingUtils.maskEmailId(email));
userId, //
MaskingUtils.maskName(name), // // 사용자 생성 또는 업데이트
MaskingUtils.maskPhoneNumber(cellPhoneNo), // UserInfo userInfo = findOrCreateUser(info, extendedInfo);
MaskingUtils.maskEmailId(email)); //
// // 로그인 세션 설정 (MainController 위임)
// 사용자 생성 또는 업데이트 // log.info("SSO 로그인 성공 - userId: {}", userId);
UserInfo userInfo = findOrCreateUser(info, extendedInfo); // return mainController.setupSsoLoginSession(req, userInfo);
// } else {
// 로그인 세션 설정 (MainController 위임) // // 동기화 비활성화 모드: 기존 사용자는 업데이트 없이, 신규 사용자는 최소 정보로 생성
log.info("SSO 로그인 성공 - userId: {}", userId); // log.info("SSO 동기화 비활성화 모드로 로그인 처리 - ssoId: {}", ssoId);
return mainController.setupSsoLoginSession(req, userInfo); //
} else { // UserInfo userInfo = findOrCreateUserWithoutSync(ssoId);
// 동기화 비활성화 모드: 기존 사용자는 업데이트 없이, 신규 사용자는 최소 정보로 생성 //
log.info("SSO 동기화 비활성화 모드로 로그인 처리 - ssoId: {}", ssoId); // log.info("SSO 로그인 성공 (동기화 비활성화 모드) - userId: {}", ssoId);
// return mainController.setupSsoLoginSession(req, userInfo);
UserInfo userInfo = findOrCreateUserWithoutSync(ssoId); // }
// }
log.info("SSO 로그인 성공 (동기화 비활성화 모드) - userId: {}", ssoId); // }
return mainController.setupSsoLoginSession(req, userInfo);
}
}
}
/** /**
* 필수 필드 검증 * 필수 필드 검증
@@ -26,6 +26,7 @@ import com.eactive.eai.rms.onl.apim.portalorg.PortalOrgManService;
import com.eactive.eai.rms.onl.apim.portaluser.PortalUserManService; import com.eactive.eai.rms.onl.apim.portaluser.PortalUserManService;
import com.eactive.eai.rms.onl.apim.portaluser.PortalUserUI; import com.eactive.eai.rms.onl.apim.portaluser.PortalUserUI;
import com.eactive.eai.rms.common.acl.user.ui.UserUI; import com.eactive.eai.rms.common.acl.user.ui.UserUI;
import com.eactive.eai.common.util.SystemUtil;
import com.eactive.eai.rms.onl.common.exception.BizException; import com.eactive.eai.rms.onl.common.exception.BizException;
import com.eactive.eai.rms.onl.transaction.apim.ApiInterfaceService; import com.eactive.eai.rms.onl.transaction.apim.ApiInterfaceService;
import com.eactive.eai.rms.onl.transaction.apim.mapping.ApiSpecUIMapper; import com.eactive.eai.rms.onl.transaction.apim.mapping.ApiSpecUIMapper;
@@ -359,6 +360,10 @@ public class PortalApprovalManService extends BaseService {
} }
public boolean isHardDeleteEnabled() { public boolean isHardDeleteEnabled() {
// 승인 요청 완전삭제는 dev(개발) 환경에서만 허용
if (!SystemUtil.isDevServer()) {
return false;
}
Map<String, String> properties = portalPropertyService.getPortalPropertiesAsMap(PORTAL_PROPERTY_GROUP); Map<String, String> properties = portalPropertyService.getPortalPropertiesAsMap(PORTAL_PROPERTY_GROUP);
return Boolean.parseBoolean(properties.getOrDefault(HARD_DELETE_FLAG_KEY, "false")); return Boolean.parseBoolean(properties.getOrDefault(HARD_DELETE_FLAG_KEY, "false"));
} }
@@ -0,0 +1,82 @@
package com.eactive.eai.rms.onl.apim.authserver;
import com.eactive.eai.agent.command.CommonCommand;
import com.eactive.eai.rms.common.datasource.DataSourceContextHolder;
import com.eactive.eai.rms.common.datasource.DataSourceType;
import com.eactive.eai.rms.common.datasource.DataSourceTypeManager;
import com.eactive.eai.rms.onl.apim.common.BaseRestController;
import com.eactive.eai.rms.onl.common.service.AgentUtilService;
import com.eactive.eai.rms.onl.manage.authserver.client.ClientManService;
import java.util.HashMap;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* 포털 등 내부 시스템이 호출하는 인증서버(GW) 클라이언트 제어용 내부 API.
*
* <p>{@link BaseRestController} 를 상속하여 ① 세션 인증을 skip(InterceptorSkipController)하고
* ② {@code IpWhitelistInterceptor} 를 통해 {@code kjb.api.allow_ip_list} 기준 IP 화이트리스트
* 인증을 적용받는다. (호출 측 포털 서버 IP를 반드시 허용목록에 등록해야 함)</p>
*
* @see com.eactive.eai.rms.common.interceptor.IpWhitelistInterceptor
*/
@Controller
public class ClientBlockApiController extends BaseRestController {
private static final Logger logger = LoggerFactory.getLogger(ClientBlockApiController.class);
private final ClientManService clientManService;
@Qualifier("onlAgentUtilService")
@Autowired
private AgentUtilService agentUtilService;
@Autowired
public ClientBlockApiController(ClientManService clientManService) {
this.clientManService = clientManService;
}
/**
* clientId 의 GW 인증 클라이언트를 차단(TSEAIAU01.appstatus = '0')한 뒤,
* 승인 경로와 동일하게 GW 인증서버로 RELOAD_CLIENT 명령을 broadcast 하여 캐시를 갱신한다.
*
* @param clientId 차단할 클라이언트 ID
* @return 처리 결과 JSON ({@code {"success":true,"clientId":...}} 또는 {@code {"success":false,"msg":...}})
*/
@RequestMapping(value = "/onl/admin/authserver/clientBlock.json", method = RequestMethod.POST, produces = JSON_CONTENT_TYPE)
@ResponseBody
public String blockClient(@RequestParam String clientId) {
Map<String, Object> result = new HashMap<>();
try {
// TSEAIAU01(ClientEntity)은 APIGW 데이터소스에 있으므로 라우팅을 먼저 설정한다.
DataSourceType type = DataSourceTypeManager.getDataSourceType("APIGW");
DataSourceContextHolder.setDataSourceType(type);
// 상태 차단 (appstatus = '0')
clientManService.blockClient(clientId);
// GW 인증서버 캐시 리로드 (승인 경로 PortalAppApprovalListener 와 동일)
CommonCommand.builder()
.name(CommonCommand.RELOAD_CLIENT_COMMAND)
.args(clientId)
.build().broadcast(agentUtilService);
logger.warn("GW client 차단(appstatus=0)+리로드 완료 - clientId={}", clientId);
result.put("success", true);
result.put("clientId", clientId);
} catch (Exception e) {
logger.error("GW client 차단 실패 - clientId={}", clientId, e);
result.put("success", false);
result.put("msg", e.getMessage());
}
return toJson(result);
}
}
@@ -10,7 +10,7 @@ import com.eactive.eai.rms.common.util.CommonUtil;
import com.eactive.eai.rms.data.entity.onl.apim.obp.ObpGwMetricJpaDAO; import com.eactive.eai.rms.data.entity.onl.apim.obp.ObpGwMetricJpaDAO;
import com.eactive.eai.rms.data.entity.onl.apim.obp.ObpGwMetricService; import com.eactive.eai.rms.data.entity.onl.apim.obp.ObpGwMetricService;
import com.eactive.eai.rms.data.entity.onl.apim.obp.ObpGwMetricVO; import com.eactive.eai.rms.data.entity.onl.apim.obp.ObpGwMetricVO;
import com.eactive.ext.kjb.common.KjbPropertyHolder; //import com.eactive.ext.kjb.common.KjbPropertyHolder;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.dao.DataIntegrityViolationException; import org.springframework.dao.DataIntegrityViolationException;
@@ -328,7 +328,7 @@ public class ObpGwMetricManService extends BaseService {
DataSourceTypeManager.getDataSourceType(DataSourceTypeManager.MONITORING)); DataSourceTypeManager.getDataSourceType(DataSourceTypeManager.MONITORING));
// KjbProperty에서 pageSize 읽기 // KjbProperty에서 pageSize 읽기
Integer pageSize = KjbPropertyHolder.get().getApiGwMetricPageSize(); Integer pageSize = null; //KjbPropertyHolder.get().getApiGwMetricPageSize();
if (pageSize == null) { if (pageSize == null) {
pageSize = 10000; // 기본값 (null 안전 처리) pageSize = 10000; // 기본값 (null 안전 처리)
} }
@@ -37,4 +37,7 @@ public class PortalInquiryCommentUI {
@DateTimeFormat(pattern = "yyyyMMddHHmmss") @DateTimeFormat(pattern = "yyyyMMddHHmmss")
private LocalDateTime lastModifiedDate; private LocalDateTime lastModifiedDate;
/** 공개범위(ALL=공개/PRIVATE=비공개). */
private String visibility;
} }
@@ -92,4 +92,17 @@ public class PortalInquiryManController extends BaseAnnotationController {
return ResponseEntity.ok().build(); return ResponseEntity.ok().build();
} }
// 공개범위 변경 — 감사 로그는 AuditLogInterceptor 가 자동 기록(auditpoints.dat 등록, auditReason 파라미터)
@PostMapping(value = "/onl/apim/portalinquiry/portalInquiryMan.json", params = "cmd=VISIBILITY")
public ResponseEntity<String> updateVisibility(String targetType, String id, String visibility, String auditReason) {
portalInquiryManService.updateVisibility(targetType, id, visibility, auditReason);
return ResponseEntity.ok().build();
}
@PostMapping(value = "/onl/apim/portalinquiry/portalInquiryMan.json", params = "cmd=INCREMENT_VIEW")
public ResponseEntity<Void> incrementView(String id) {
portalInquiryManService.incrementViewCount(id);
return ResponseEntity.ok().build();
}
} }
@@ -7,6 +7,7 @@ import com.eactive.apim.portal.portaluser.entity.PortalUserEnums;
import com.eactive.apim.portal.portaluser.repository.PortalUserRepository; import com.eactive.apim.portal.portaluser.repository.PortalUserRepository;
import com.eactive.apim.portal.qna.entity.Inquiry; import com.eactive.apim.portal.qna.entity.Inquiry;
import com.eactive.apim.portal.qna.entity.InquiryComment; import com.eactive.apim.portal.qna.entity.InquiryComment;
import com.eactive.apim.portal.qna.entity.VisibilityScope;
import com.eactive.apim.portal.user.entity.UserInfo; import com.eactive.apim.portal.user.entity.UserInfo;
import com.eactive.eai.rms.common.base.BaseService; import com.eactive.eai.rms.common.base.BaseService;
import com.eactive.eai.rms.common.exception.IntegrationException; import com.eactive.eai.rms.common.exception.IntegrationException;
@@ -41,8 +42,10 @@ public class PortalInquiryManService extends BaseService {
private final FileService fileService; private final FileService fileService;
public static final String PENDING = "PENDING"; public static final String PENDING = "PENDING";
public static final String REVIEWING = "REVIEWING";
public static final String RESPONDED = "RESPONDED"; public static final String RESPONDED = "RESPONDED";
public static final String CLOSED = "CLOSED"; public static final String CLOSED = "CLOSED";
public static final String TARGET_COMMENT = "COMMENT";
public PortalInquiryManService(PortalInquiryService portalInquiryService, public PortalInquiryManService(PortalInquiryService portalInquiryService,
@@ -116,12 +119,19 @@ public class PortalInquiryManService extends BaseService {
public PortalInquiryUI selectDetail(String id) { public PortalInquiryUI selectDetail(String id) {
Inquiry inquiry = portalInquiryService.getById(id); Inquiry inquiry = portalInquiryService.getById(id);
// 답변 대기중 상태에서 상세 조회 시 검토중 상태로 변경
if (PENDING.equals(inquiry.getInquiryStatus())) {
inquiry.setInquiryStatus(REVIEWING);
portalInquiryService.save(inquiry);
}
PortalInquiryUI portalInquiryUI = portalInquiryUIMapper.toVo(inquiry); PortalInquiryUI portalInquiryUI = portalInquiryUIMapper.toVo(inquiry);
// 문의자 정보 마스킹처리 _ 이름, 이메일, 로그인id, 핸드폰번호 // 문의자 정보 마스킹처리 _ 이름, 이메일, 로그인id, 핸드폰번호
if (portalInquiryUI.getInquirer() != null) { //if (portalInquiryUI.getInquirer() != null) {
maskPortalInquiryUI(portalInquiryUI); // maskPortalInquiryUI(portalInquiryUI);
} //}
if (StringUtils.isNotBlank(inquiry.getAttachFile())) { if (StringUtils.isNotBlank(inquiry.getAttachFile())) {
try { try {
@@ -159,7 +169,7 @@ public class PortalInquiryManService extends BaseService {
Inquiry inquiry = portalInquiryService.getById(portalInquiryUI.getId()); Inquiry inquiry = portalInquiryService.getById(portalInquiryUI.getId());
validateNotClosed(inquiry); validateNotClosed(inquiry);
// 답변 대기중 상태일 경우만 답변완료로 상태 변경 // 답변 대기중 상태일 경우만 답변완료로 상태 변경
if(inquiry.getInquiryStatus().equals(PENDING)) { if(inquiry.getInquiryStatus().equals(PENDING) || inquiry.getInquiryStatus().equals(REVIEWING)) {
inquiry.setInquiryStatus(RESPONDED); inquiry.setInquiryStatus(RESPONDED);
} }
@@ -227,4 +237,41 @@ public class PortalInquiryManService extends BaseService {
} }
} }
/** 조회수 증가 (JSP sessionStorage dedup 통과 시 호출). 관리자는 공개범위 무관 전체 접근. */
public void incrementViewCount(String id) {
Inquiry inquiry = portalInquiryService.getById(id);
inquiry.increaseViewCount();
portalInquiryService.save(inquiry);
}
/**
* 게시물/댓글 공개범위 변경. 사유(reason) 필수.
* 변경 이력은 eapim-admin 공통 감사 로그(AuditLogInterceptor)가 자동 기록한다
* (auditpoints.dat 의 PortalInquiryManController_..._VISIBILITY, serviceType=APIGW, auditReason 파라미터).
*
* @param targetType INQUIRY 또는 COMMENT
* @param id 대상 게시물/댓글 ID
* @param newVisibility 변경할 공개범위(ALL/ORG/PRIVATE)
* @param reason 변경 사유(필수)
*/
public void updateVisibility(String targetType, String id, String newVisibility, String reason) {
if (StringUtils.isBlank(reason)) {
throw new IntegrationException("공개범위 변경 사유를 입력해주세요.");
}
VisibilityScope newScope = VisibilityScope.fromString(newVisibility, null);
if (newScope == null) {
throw new IntegrationException("유효하지 않은 공개범위입니다.");
}
if (TARGET_COMMENT.equalsIgnoreCase(targetType)) {
InquiryComment comment = portalInquiryCommentService.getById(id);
comment.setVisibility(newScope);
portalInquiryCommentService.save(comment);
} else {
Inquiry inquiry = portalInquiryService.getById(id);
inquiry.setVisibility(newScope);
portalInquiryService.save(inquiry);
}
}
} }
@@ -57,4 +57,10 @@ public class PortalInquiryUI {
private String orgName; private String orgName;
private int commentCount; private int commentCount;
/** 공개범위(ALL/ORG/PRIVATE). */
private String visibility;
/** 조회수. */
private long viewCount;
} }
@@ -14,6 +14,7 @@ public interface PortalPropertyMapper extends GenericMapper<PropertyUI, PortalPr
@Mapping(source = "prptyGroupName", target = "id.propertyGroupName") @Mapping(source = "prptyGroupName", target = "id.propertyGroupName")
@Mapping(source = "prptyName", target = "id.propertyName") @Mapping(source = "prptyName", target = "id.propertyName")
@Mapping(source = "prpty2Val", target = "propertyValue") @Mapping(source = "prpty2Val", target = "propertyValue")
@Mapping(source = "prptyDesc", target = "propertyDesc")
@Override @Override
PortalProperty toEntity(PropertyUI vo); PortalProperty toEntity(PropertyUI vo);
@@ -21,6 +22,7 @@ public interface PortalPropertyMapper extends GenericMapper<PropertyUI, PortalPr
@Mapping(source ="id.propertyGroupName", target = "prptyGroupName") @Mapping(source ="id.propertyGroupName", target = "prptyGroupName")
@Mapping(source ="id.propertyName", target = "prptyName") @Mapping(source ="id.propertyName", target = "prptyName")
@Mapping(source ="propertyValue", target = "prpty2Val") @Mapping(source ="propertyValue", target = "prpty2Val")
@Mapping(source ="propertyDesc", target = "prptyDesc")
@InheritInverseConfiguration @InheritInverseConfiguration
@Override @Override
PropertyUI toVo(PortalProperty entity); PropertyUI toVo(PortalProperty entity);
@@ -1,11 +1,13 @@
package com.eactive.eai.rms.onl.apim.service; package com.eactive.eai.rms.onl.apim.service;
import com.eactive.eai.util.IpUtil;
import com.eactive.ext.kjb.common.KjbPropertyHolder;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import com.eactive.eai.rms.ext.djb.common.DjbPropertyHolder;
import com.eactive.eai.util.IpUtil;
import lombok.extern.slf4j.Slf4j;
/** /**
* IP 화이트리스트 인증 서비스 * IP 화이트리스트 인증 서비스
* *
@@ -113,7 +115,7 @@ public class IpAuthenticationService {
*/ */
private String loadWhitelistFromDb() { private String loadWhitelistFromDb() {
try { try {
String whitelist = KjbPropertyHolder.get().getApiAllowIpList(); String whitelist = DjbPropertyHolder.get().getApiAllowIpList();
if (StringUtils.isBlank(whitelist)) { if (StringUtils.isBlank(whitelist)) {
log.warn("IP whitelist property is empty"); log.warn("IP whitelist property is empty");
return ""; return "";
@@ -103,6 +103,18 @@ public class ClientManService extends OnlBaseService {
clientEntityService.deleteById(clientId); clientEntityService.deleteById(clientId);
} }
/**
* 인증 클라이언트를 차단합니다. (appstatus = '0')
* 삭제하지 않고 상태만 차단으로 변경합니다.
*
* @param clientId 차단할 클라이언트 ID
*/
public void blockClient(String clientId) {
ClientEntity clientEntity = clientEntityService.getById(clientId);
clientEntity.setAppstatus("0"); // 0: 차단, 1: 정상
clientEntityService.save(clientEntity);
}
public List<String> findAll() { public List<String> findAll() {
List<ClientEntity> clientList = clientEntityService.findAll(); List<ClientEntity> clientList = clientEntityService.findAll();
@@ -1,5 +1,7 @@
package com.eactive.eai.rms.onl.manage.authserver.scope; package com.eactive.eai.rms.onl.manage.authserver.scope;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
@@ -8,21 +10,29 @@ import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import com.eactive.eai.agent.authserver.ReloadApiScopeCommand; import com.eactive.eai.agent.authserver.ReloadApiScopeCommand;
import com.eactive.eai.agent.command.CommonCommand; import com.eactive.eai.agent.command.CommonCommand;
import com.eactive.eai.rms.common.base.OnlBaseAnnotationController; import com.eactive.eai.rms.common.base.OnlBaseAnnotationController;
import com.eactive.eai.rms.common.vo.GridResponse; import com.eactive.eai.rms.common.vo.GridResponse;
import com.eactive.eai.rms.data.entity.onl.authserver.ScopeSearch; import com.eactive.eai.rms.data.entity.onl.authserver.ScopeSearch;
import com.eactive.eai.rms.data.entity.onl.eaimsg.ApiScopeSearch;
import com.eactive.eai.rms.onl.common.exception.BizException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
@Controller @Controller
public class ScopeController extends OnlBaseAnnotationController { public class ScopeController extends OnlBaseAnnotationController {
private ScopeManService scopeManService; private ScopeManService scopeManService;
private ApiScopeManService apiScopeManService;
@Autowired @Autowired
public ScopeController(ScopeManService scopeManService) { public ScopeController(ScopeManService scopeManService, ApiScopeManService apiScopeManService) {
this.scopeManService = scopeManService; this.scopeManService = scopeManService;
this.apiScopeManService = apiScopeManService;
} }
@GetMapping(value = "/onl/admin/authserver/scopeMan.view") @GetMapping(value = "/onl/admin/authserver/scopeMan.view")
@@ -41,6 +51,12 @@ public class ScopeController extends OnlBaseAnnotationController {
return ResponseEntity.ok(new GridResponse<>(page)); return ResponseEntity.ok(new GridResponse<>(page));
} }
@PostMapping(value = "/onl/admin/authserver/scopeMan.json", params = "cmd=API_LIST")
public ResponseEntity<GridResponse<ApiScopeUI>> selectApiList(Pageable pageable, ApiScopeSearch apiScopeSearch) {
Page<ApiScopeUI> page = apiScopeManService.selectApiScopeRelationsList(pageable, apiScopeSearch);
return ResponseEntity.ok(new GridResponse<>(page));
}
@PostMapping(value = "/onl/admin/authserver/scopeMan.json", params = "cmd=DETAIL") @PostMapping(value = "/onl/admin/authserver/scopeMan.json", params = "cmd=DETAIL")
public ResponseEntity<ScopeUI> selectDetail(String scopeId) { public ResponseEntity<ScopeUI> selectDetail(String scopeId) {
ScopeUI scopeUI = scopeManService.selectDetail(scopeId); ScopeUI scopeUI = scopeManService.selectDetail(scopeId);
@@ -48,14 +64,16 @@ public class ScopeController extends OnlBaseAnnotationController {
} }
@PostMapping(value = "/onl/admin/authserver/scopeMan.json", params = "cmd=INSERT") @PostMapping(value = "/onl/admin/authserver/scopeMan.json", params = "cmd=INSERT")
public ResponseEntity<Void> insert(ScopeUI scopeUI) { public ResponseEntity<Void> insert(ScopeUI scopeUI, @RequestParam(value = "apiList", required = false) String apiListJson) {
scopeManService.insert(scopeUI); scopeManService.insert(scopeUI);
saveApiScopeRelations(scopeUI.getScopeId(), apiListJson);
return ResponseEntity.ok().build(); return ResponseEntity.ok().build();
} }
@PostMapping(value = "/onl/admin/authserver/scopeMan.json", params = "cmd=UPDATE") @PostMapping(value = "/onl/admin/authserver/scopeMan.json", params = "cmd=UPDATE")
public ResponseEntity<Void> update(ScopeUI scopeUI) { public ResponseEntity<Void> update(ScopeUI scopeUI, @RequestParam(value = "apiList", required = false) String apiListJson) {
scopeManService.update(scopeUI); scopeManService.update(scopeUI);
saveApiScopeRelations(scopeUI.getScopeId(), apiListJson);
return ResponseEntity.ok().build(); return ResponseEntity.ok().build();
} }
@@ -69,8 +87,29 @@ public class ScopeController extends OnlBaseAnnotationController {
.args(new String[] {ReloadApiScopeCommand.COMMAND_TYPE_SCOPE, "", scopeId}) .args(new String[] {ReloadApiScopeCommand.COMMAND_TYPE_SCOPE, "", scopeId})
.build() .build()
.broadcast(agentUtilService); .broadcast(agentUtilService);
return ResponseEntity.ok().build(); return ResponseEntity.ok().build();
} }
private void saveApiScopeRelations(String scopeId, String apiListJson) {
if (apiListJson == null || apiListJson.isEmpty()) {
return;
}
List<ApiScopeUI> apiScopeList;
try {
apiScopeList = new ObjectMapper().readValue(apiListJson, new TypeReference<List<ApiScopeUI>>() {});
} catch (Exception e) {
throw new BizException("데이터 변환 중 오류가 발생했습니다.");
}
scopeManService.updateApiScopeRelations(apiScopeList, scopeId);
CommonCommand.builder()
.name(CommonCommand.RELOAD_API_SCOPE_COMMAND)
.args(new String[] {ReloadApiScopeCommand.COMMAND_TYPE_SCOPE, "", scopeId})
.build()
.broadcast(agentUtilService);
}
} }
@@ -1,5 +1,7 @@
package com.eactive.eai.rms.onl.manage.authserver.scope; package com.eactive.eai.rms.onl.manage.authserver.scope;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
@@ -21,17 +23,20 @@ public class ScopeManService extends OnlBaseService {
private ScopeInfoService scopeInfoService; private ScopeInfoService scopeInfoService;
private ScopeEntityService scopeEntityService; private ScopeEntityService scopeEntityService;
private ScopeUIMapper scopeUIMapper; private ScopeUIMapper scopeUIMapper;
private ApiScopeUIMapper apiScopeUIMapper;
private LocaleMessage localeMessage; private LocaleMessage localeMessage;
@Autowired @Autowired
public ScopeManService(ScopeInfoService scopeInfoService, ScopeEntityService scopeEntityService, public ScopeManService(ScopeInfoService scopeInfoService, ScopeEntityService scopeEntityService,
ScopeUIMapper scopeUIMapper, LocaleMessage localeMessage ) { ScopeUIMapper scopeUIMapper, ApiScopeUIMapper apiScopeUIMapper, LocaleMessage localeMessage ) {
this.scopeInfoService = scopeInfoService; this.scopeInfoService = scopeInfoService;
this.scopeEntityService = scopeEntityService; this.scopeEntityService = scopeEntityService;
this.scopeUIMapper = scopeUIMapper; this.scopeUIMapper = scopeUIMapper;
this.apiScopeUIMapper = apiScopeUIMapper;
this.localeMessage = localeMessage; this.localeMessage = localeMessage;
} }
@@ -65,5 +70,23 @@ public class ScopeManService extends OnlBaseService {
public void deleteApiScopeRelationsByScopeId(String scopeId) { public void deleteApiScopeRelationsByScopeId(String scopeId) {
scopeEntityService.deleteByIdScopeId(scopeId); scopeEntityService.deleteByIdScopeId(scopeId);
} }
public void updateApiScopeRelations(List<ApiScopeUI> apiScopeList, String scopeId) {
if (scopeId == null || scopeId.isEmpty()) {
throw new BizException("ScopeId 가 존재하지 않습니다.");
}
// ScopeId 기준 전체 삭제 후 LIST 기준으로 재저장.
scopeEntityService.deleteByIdScopeId(scopeId);
if (apiScopeList == null || apiScopeList.isEmpty()) {
return;
}
for (ApiScopeUI ui : apiScopeList) {
ui.setScopeId(scopeId);
scopeEntityService.save(apiScopeUIMapper.toEntity(ui));
}
}
} }
@@ -37,10 +37,12 @@ public class InboundErrorInfoManService extends BaseService {
InboundErrorInfoUI ui = inboundErrorInfoUIMapper.toVo(inboundErrorInfoService.getById(eaiSvcSerno)); InboundErrorInfoUI ui = inboundErrorInfoUIMapper.toVo(inboundErrorInfoService.getById(eaiSvcSerno));
adapterGroupService.findById(ui.getAdptrBzwkGroupName()).ifPresent(a -> { if (ui.getAdptrBzwkGroupName() != null) {
ui.setAdptrMsgDstcd(a.getAdptrmsgdstcd()); adapterGroupService.findById(ui.getAdptrBzwkGroupName()).ifPresent(a -> {
ui.setAdptrMsgPtrncd(a.getAdptrmsgptrncd()); ui.setAdptrMsgDstcd(a.getAdptrmsgdstcd());
}); ui.setAdptrMsgPtrncd(a.getAdptrmsgptrncd());
});
}
return ui; return ui;
} }
@@ -15,4 +15,7 @@ public class PropertyUI {
@JsonProperty("PRPTY2VAL") @JsonProperty("PRPTY2VAL")
private String prpty2Val; private String prpty2Val;
@JsonProperty("PRPTYDESC")
private String prptyDesc;
} }
@@ -17,6 +17,7 @@ import com.eactive.eai.data.entity.onl.server.EAIServer;
import com.eactive.eai.rms.common.base.OnlBaseService; import com.eactive.eai.rms.common.base.OnlBaseService;
import com.eactive.eai.rms.data.entity.onl.adapter.AdapterGroupService; import com.eactive.eai.rms.data.entity.onl.adapter.AdapterGroupService;
import com.eactive.eai.rms.data.entity.onl.server.EAIServerService; import com.eactive.eai.rms.data.entity.onl.server.EAIServerService;
import com.eactive.eai.rms.onl.common.exception.BizException;
@Service @Service
@Transactional @Transactional
@@ -78,6 +79,11 @@ public class ServerManService extends OnlBaseService {
} }
public void insert(EAIServerUI eaiServerUI) { public void insert(EAIServerUI eaiServerUI) {
String eaiSevrInstncName = eaiServerUI.getEaiSevrInstncName();
if (eaiServerService.existsById(eaiSevrInstncName)) {
throw new BizException("서버 인스턴스명[" + eaiSevrInstncName + "]은 이미 등록되어 있습니다.");
}
EAIServer eaiServer = eaiServerUIMapper.toEntity(eaiServerUI); EAIServer eaiServer = eaiServerUIMapper.toEntity(eaiServerUI);
eaiServerService.save(eaiServer); eaiServerService.save(eaiServer);
} }
@@ -59,8 +59,8 @@ public class InflowGroupControlManController extends OnlBaseAnnotationController
*/ */
@RequestMapping(value = "/onl/admin/inflow/inflowGroupControlMan.json", params = "cmd=LIST") @RequestMapping(value = "/onl/admin/inflow/inflowGroupControlMan.json", params = "cmd=LIST")
public ResponseEntity<GridResponse<InflowGroupControlManUI>> selectList(HttpServletRequest request, public ResponseEntity<GridResponse<InflowGroupControlManUI>> selectList(HttpServletRequest request,
Pageable pageVo, String searchGroupName) { Pageable pageVo, String searchGroupName, String searchUseYn) {
Page<InflowGroupControlManUI> uiPage = service.selectGroupList(pageVo, searchGroupName); Page<InflowGroupControlManUI> uiPage = service.selectGroupList(pageVo, searchGroupName, searchUseYn);
return ResponseEntity.ok(new GridResponse<>(uiPage)); return ResponseEntity.ok(new GridResponse<>(uiPage));
} }
@@ -61,7 +61,7 @@ public class InflowGroupControlManService extends BaseService {
/** /**
* 그룹 목록 조회 * 그룹 목록 조회
*/ */
public Page<InflowGroupControlManUI> selectGroupList(Pageable pageable, String searchGroupName) { public Page<InflowGroupControlManUI> selectGroupList(Pageable pageable, String searchGroupName, String searchUseYn) {
JPAQueryFactory jpaQueryFactory = new JPAQueryFactory(entityManager); JPAQueryFactory jpaQueryFactory = new JPAQueryFactory(entityManager);
QInflowControlGroup qGroup = QInflowControlGroup.inflowControlGroup; QInflowControlGroup qGroup = QInflowControlGroup.inflowControlGroup;
@@ -69,6 +69,10 @@ public class InflowGroupControlManService extends BaseService {
if (!StringUtils.isEmpty(searchGroupName)) { if (!StringUtils.isEmpty(searchGroupName)) {
boolBuilder.and(qGroup.groupName.contains(searchGroupName)); boolBuilder.and(qGroup.groupName.contains(searchGroupName));
} }
if (!StringUtils.isEmpty(searchUseYn)) {
// 유량제어 미설정(useYn null)은 "사용안함" 검색 시 함께 조회되도록 처리
boolBuilder.and("0".equals(searchUseYn) ? qGroup.useYn.eq(searchUseYn).or(qGroup.useYn.isNull()) : qGroup.useYn.eq(searchUseYn));
}
List<InflowControlGroup> groupList = jpaQueryFactory List<InflowControlGroup> groupList = jpaQueryFactory
.selectFrom(qGroup) .selectFrom(qGroup)
@@ -24,4 +24,29 @@ public class InflowControlHistoryManUISearch {
* 유량제어일 끝(20231221) * 유량제어일 끝(20231221)
*/ */
private String searchEndDate; private String searchEndDate;
/**
* 유량제어일 시작 시간(000000)
*/
private String searchStartTime;
/**
* 유량제어일 끝 시간(235959)
*/
private String searchEndTime;
/**
* 인스턴스명
*/
private String searchInstanceName;
/**
* API 명
*/
private String searchApiName;
/**
* 어댑터그룹명
*/
private String searchAdapterGroupName;
} }
@@ -42,8 +42,8 @@ public class InflowAdapterControlManController extends OnlBaseAnnotationControll
@RequestMapping(value = "/onl/admin/inflow/inflowAdapterControlMan.json", params = "cmd=LIST") @RequestMapping(value = "/onl/admin/inflow/inflowAdapterControlMan.json", params = "cmd=LIST")
public ResponseEntity<GridResponse<InflowControlManUI>> selectList(HttpServletRequest request, Pageable pageVo, public ResponseEntity<GridResponse<InflowControlManUI>> selectList(HttpServletRequest request, Pageable pageVo,
String searchName) { String searchName, String searchUseYn) {
Page<InflowControlManUI> uiPage = service.selectAdapterList(pageVo, searchName); Page<InflowControlManUI> uiPage = service.selectAdapterList(pageVo, searchName, searchUseYn);
return ResponseEntity.ok(new GridResponse<>(uiPage)); return ResponseEntity.ok(new GridResponse<>(uiPage));
} }
@@ -19,7 +19,9 @@ import com.eactive.eai.agent.command.CommonCommand;
import com.eactive.eai.rms.common.base.OnlBaseAnnotationController; import com.eactive.eai.rms.common.base.OnlBaseAnnotationController;
import com.eactive.eai.rms.common.combo.ComboService; import com.eactive.eai.rms.common.combo.ComboService;
import com.eactive.eai.rms.common.combo.ComboVo; import com.eactive.eai.rms.common.combo.ComboVo;
import com.eactive.eai.rms.common.util.CommonUtil;
import com.eactive.eai.rms.common.vo.GridResponse; import com.eactive.eai.rms.common.vo.GridResponse;
import com.eactive.eai.rms.common.vo.SimpleResponse;
@Controller @Controller
public class InflowClientControlManController extends OnlBaseAnnotationController { public class InflowClientControlManController extends OnlBaseAnnotationController {
@@ -42,8 +44,8 @@ public class InflowClientControlManController extends OnlBaseAnnotationControlle
@RequestMapping(value = "/onl/admin/inflow/inflowClientControlMan.json", params = "cmd=LIST") @RequestMapping(value = "/onl/admin/inflow/inflowClientControlMan.json", params = "cmd=LIST")
public ResponseEntity<GridResponse<InflowControlManUI>> selectList(HttpServletRequest request, Pageable pageVo, public ResponseEntity<GridResponse<InflowControlManUI>> selectList(HttpServletRequest request, Pageable pageVo,
String searchName) { String searchName, String searchUseYn) {
Page<InflowControlManUI> uiPage = service.selectClientList(pageVo, searchName); Page<InflowControlManUI> uiPage = service.selectClientList(pageVo, searchName, searchUseYn);
return ResponseEntity.ok(new GridResponse<>(uiPage)); return ResponseEntity.ok(new GridResponse<>(uiPage));
} }
@@ -55,22 +57,22 @@ public class InflowClientControlManController extends OnlBaseAnnotationControlle
} }
@RequestMapping(value = "/onl/admin/inflow/inflowClientControlMan.json", params = "cmd=UPDATE") @RequestMapping(value = "/onl/admin/inflow/inflowClientControlMan.json", params = "cmd=UPDATE")
public String save(HttpServletRequest request, HttpServletResponse response, InflowControlManUI ui) public ResponseEntity<SimpleResponse> save(HttpServletRequest request, HttpServletResponse response, InflowControlManUI ui)
throws Exception { throws Exception {
service.mergeClient(ui); service.mergeClient(ui);
CommonCommand command = new CommonCommand("com.eactive.eai.agent.inflow.ReloadInflowClientControlCommand", CommonCommand command = new CommonCommand("com.eactive.eai.agent.inflow.ReloadInflowClientControlCommand", ui.getName());
ui.getName()); HashMap<String, Object> returnMap = agentUtilService.broadcast(command);
agentUtilService.broadcast(command); SimpleResponse simpleResponse = SimpleResponse.of(CommonUtil.getMapString(returnMap, true));
return null; return ResponseEntity.ok(simpleResponse);
} }
@RequestMapping(value = "/onl/admin/inflow/inflowClientControlMan.json", params = "cmd=DELETE") @RequestMapping(value = "/onl/admin/inflow/inflowClientControlMan.json", params = "cmd=DELETE")
public String delete(HttpServletRequest request, HttpServletResponse response, String name) throws Exception { public ResponseEntity<SimpleResponse> delete(HttpServletRequest request, HttpServletResponse response, String name) throws Exception {
service.deleteClient(name); service.deleteClient(name);
CommonCommand command = new CommonCommand("com.eactive.eai.agent.inflow.RemoveInflowClientControlCommand", CommonCommand command = new CommonCommand("com.eactive.eai.agent.inflow.RemoveInflowClientControlCommand", name);
name); HashMap<String, Object> returnMap = agentUtilService.broadcast(command);
agentUtilService.broadcast(command); SimpleResponse simpleResponse = SimpleResponse.of(CommonUtil.getMapString(returnMap, true));
return null; return ResponseEntity.ok(simpleResponse);
} }
@RequestMapping(value = "/onl/admin/inflow/inflowClientControlMan.json", params = "cmd=LIST_INIT_COMBO") @RequestMapping(value = "/onl/admin/inflow/inflowClientControlMan.json", params = "cmd=LIST_INIT_COMBO")
@@ -1,5 +1,6 @@
package com.eactive.eai.rms.onl.manage.inflow.inflow; package com.eactive.eai.rms.onl.manage.inflow.inflow;
import java.net.URI;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@@ -16,6 +17,7 @@ import org.springframework.data.domain.Pageable;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
import com.eactive.eai.data.entity.onl.inflow.InflowControl; import com.eactive.eai.data.entity.onl.inflow.InflowControl;
import com.eactive.eai.data.entity.onl.inflow.InflowControlId; import com.eactive.eai.data.entity.onl.inflow.InflowControlId;
@@ -50,8 +52,8 @@ public class InflowControlManService extends BaseService {
private RestTemplate restTemplate = new RestTemplate(); private RestTemplate restTemplate = new RestTemplate();
// 어댑터 유량제어 // 어댑터 유량제어
public Page<InflowControlManUI> selectAdapterList(Pageable pageable, String searchName) { public Page<InflowControlManUI> selectAdapterList(Pageable pageable, String searchName, String searchUseYn) {
Page<InflowControlServiceDto> dtoList = service.findAllForAdapter(pageable, searchName); Page<InflowControlServiceDto> dtoList = service.findAllForAdapter(pageable, searchName, searchUseYn);
return dtoList.map(mapper::toVo); return dtoList.map(mapper::toVo);
} }
@@ -72,6 +74,9 @@ public class InflowControlManService extends BaseService {
} else { } else {
entity = mapper.toEntity(ui); entity = mapper.toEntity(ui);
} }
if (entity.getThreshold() == null || StringUtils.isEmpty(entity.getThresholdtimeunit())) {
entity.setThreshold(0);
}
service.save(entity); service.save(entity);
} }
@@ -81,8 +86,8 @@ public class InflowControlManService extends BaseService {
} }
// 인터페이스 유량제어 // 인터페이스 유량제어
public Page<InflowControlManUI> selectInterfaceList(Pageable pageable, String searchName) { public Page<InflowControlManUI> selectInterfaceList(Pageable pageable, String searchName, String searchUseYn) {
Page<InflowControlServiceDto> dtoList = service.findAllForInterface(pageable, searchName); Page<InflowControlServiceDto> dtoList = service.findAllForInterface(pageable, searchName, searchUseYn);
return dtoList.map(mapper::toVo); return dtoList.map(mapper::toVo);
} }
@@ -102,6 +107,9 @@ public class InflowControlManService extends BaseService {
} else { } else {
entity = mapper.toEntity(ui); entity = mapper.toEntity(ui);
} }
if (entity.getThreshold() == null || StringUtils.isEmpty(entity.getThresholdtimeunit())) {
entity.setThreshold(0);
}
service.save(entity); service.save(entity);
} }
@@ -112,8 +120,8 @@ public class InflowControlManService extends BaseService {
// 클라이언트 유량제어 // 클라이언트 유량제어
public Page<InflowControlManUI> selectClientList(Pageable pageable, String searchName) { public Page<InflowControlManUI> selectClientList(Pageable pageable, String searchName, String searchUseYn) {
Page<InflowControlServiceDto> dtoList = service.findAllForClient(pageable, searchName); Page<InflowControlServiceDto> dtoList = service.findAllForClient(pageable, searchName, searchUseYn);
return dtoList.map(mapper::toVo); return dtoList.map(mapper::toVo);
} }
@@ -133,6 +141,9 @@ public class InflowControlManService extends BaseService {
} else { } else {
entity = mapper.toEntity(ui); entity = mapper.toEntity(ui);
} }
if (entity.getThreshold() == null || StringUtils.isEmpty(entity.getThresholdtimeunit())) {
entity.setThreshold(0);
}
service.save(entity); service.save(entity);
} }
@@ -172,9 +183,12 @@ public class InflowControlManService extends BaseService {
serverStatus.put("serverPort", serverPort); serverStatus.put("serverPort", serverPort);
try { try {
String url = String.format("http://%s:%s/manage/inflow/%s/%s/bucket-status", URI uri = UriComponentsBuilder.fromHttpUrl(String.format("http://%s:%s/manage/inflow", serverIp, serverPort))
serverIp, serverPort, target, targetId); .pathSegment(target, targetId, "bucket-status")
ResponseEntity<Map> response = restTemplate.getForEntity(url, Map.class); .build()
.encode()
.toUri();
ResponseEntity<Map> response = restTemplate.getForEntity(uri, Map.class);
if (response.getBody() != null && Boolean.TRUE.equals(response.getBody().get("success"))) { if (response.getBody() != null && Boolean.TRUE.equals(response.getBody().get("success"))) {
serverStatus.put("status", "online"); serverStatus.put("status", "online");
@@ -42,8 +42,8 @@ public class InflowInterfaceControlManController extends OnlBaseAnnotationContro
@RequestMapping(value = "/onl/admin/inflow/inflowInterfaceControlMan.json", params = "cmd=LIST") @RequestMapping(value = "/onl/admin/inflow/inflowInterfaceControlMan.json", params = "cmd=LIST")
public ResponseEntity<GridResponse<InflowControlManUI>> selectList(HttpServletRequest request, Pageable pageVo, public ResponseEntity<GridResponse<InflowControlManUI>> selectList(HttpServletRequest request, Pageable pageVo,
String searchName) { String searchName, String searchUseYn) {
Page<InflowControlManUI> uiPage = service.selectInterfaceList(pageVo, searchName); Page<InflowControlManUI> uiPage = service.selectInterfaceList(pageVo, searchName, searchUseYn);
return ResponseEntity.ok(new GridResponse<>(uiPage)); return ResponseEntity.ok(new GridResponse<>(uiPage));
} }
@@ -127,6 +127,7 @@ public class LayoutDao extends SqlMapClientTemplateDao {
entity.setLoutitemminoccurnoitm((Integer) param.get("loutItemMinOccurNoitm")); entity.setLoutitemminoccurnoitm((Integer) param.get("loutItemMinOccurNoitm"));
entity.setLoutitembascval((String) param.get("loutItemBascVal")); entity.setLoutitembascval((String) param.get("loutItemBascVal"));
entity.setLoutitemmskyn((String) param.get("loutItemMskYn")); entity.setLoutitemmskyn((String) param.get("loutItemMskYn"));
entity.setLoutitemmaskoffset((Integer) param.get("loutItemMaskOffset"));
entity.setLoutitemmasklength((Integer) param.get("loutItemMaskLength")); entity.setLoutitemmasklength((Integer) param.get("loutItemMaskLength"));
entity.setLoutdptcnt((Integer) param.get("lyoutDptCnt")); entity.setLoutdptcnt((Integer) param.get("lyoutDptCnt"));
entity.setDecptlencnt((Integer) param.get("decptLenCnt")); entity.setDecptlencnt((Integer) param.get("decptLenCnt"));
@@ -39,8 +39,11 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.View; import org.springframework.web.servlet.View;
import org.xml.sax.InputSource; import org.xml.sax.InputSource;
import com.eactive.apim.portal.monitoringcode.entity.MonitoringCode;
import com.eactive.apim.portal.monitoringcode.entity.MonitoringCodeId;
import com.eactive.eai.agent.command.CommonCommand; import com.eactive.eai.agent.command.CommonCommand;
import com.eactive.eai.common.util.UUIDGenerator; import com.eactive.eai.common.util.UUIDGenerator;
import com.eactive.eai.rms.data.entity.man.monitoringCode.service.MonitoringCodeService;
import com.eactive.eai.rms.common.base.OnlBaseAnnotationController; import com.eactive.eai.rms.common.base.OnlBaseAnnotationController;
import com.eactive.eai.rms.common.context.MonitoringContext; import com.eactive.eai.rms.common.context.MonitoringContext;
import com.eactive.eai.rms.common.datasource.DataSourceContextHolder; import com.eactive.eai.rms.common.datasource.DataSourceContextHolder;
@@ -66,16 +69,19 @@ public class LayoutSyncController extends OnlBaseAnnotationController implements
@Autowired @Autowired
@Qualifier("layoutSyncService") @Qualifier("layoutSyncService")
private LayoutSyncService service; private LayoutSyncService service;
@Autowired @Autowired
@Qualifier("monitoringContext") @Qualifier("monitoringContext")
private MonitoringContext monitoringContext; private MonitoringContext monitoringContext;
@Autowired @Autowired
private KJBTransformService transformService; private KJBTransformService transformService;
@Autowired @Autowired
private KJBInterfaceService interfaceService; private KJBInterfaceService interfaceService;
@Autowired
private MonitoringCodeService monitoringCodeService;
/** /**
@@ -350,14 +356,28 @@ public class LayoutSyncController extends OnlBaseAnnotationController implements
if( _i.get("mask") != null && _i.get("mask").equals("Y")) if( _i.get("mask") != null && _i.get("mask").equals("Y"))
{ {
item.put("loutItemMskYn", "Y"); item.put("loutItemMskYn", "Y");
item.put("loutItemMskCode", _i.get("mask2"));
// DJB : mask2에 마스킹 코드가 있으면 offset에 mask2 입력 length=0으로, 기존 마스킹은 length만 입력
// 엔진에서는 length가 0이면 masking code로, 0보다 크면 기존 마스킹으로 적용
String mask2 = (String) _i.get("mask2");
if (mask2 == null || mask2.equals("")) {
item.put("loutitemMaskOffset", 0);
item.put("loutItemMaskLength",Integer.parseInt((String) _i.get("maskLength")));
} else {
String maskingCode = monitoringCodeService.findById(new MonitoringCodeId("MASKING_CODE", mask2))
.map(MonitoringCode::getCodeName)
.orElse("99");
item.put("loutItemMskCode", maskingCode);
item.put("loutItemMaskOffset", Integer.parseInt(maskingCode));
item.put("loutItemMaskLength",0);
}
} }
else else
{ {
item.put("loutItemMskYn",_i.get("mask")); // [Y/N/ ] item.put("loutItemMskYn",_i.get("mask")); // [Y/N/ ]
} }
if( "Y".equals(item.get("loutItemMskYn")) ) item.put("loutItemMaskLength",Integer.parseInt((String) _i.get("maskLength")));
item.put("lyoutDptCnt",Integer.parseInt((String) _i.get("depth")) + 1 ); // 통합인터페이스 전달되는 Depth + 1을 해야 item.put("lyoutDptCnt",Integer.parseInt((String) _i.get("depth")) + 1 ); // 통합인터페이스 전달되는 Depth + 1을 해야
item.put("decptLenCnt","".equals(_i.get("scale")) ? 0 : Integer.parseInt((String) _i.get("scale"))); // 소수점길이 META에서 "" 이면 "" 으로 표현함 item.put("decptLenCnt","".equals(_i.get("scale")) ? 0 : Integer.parseInt((String) _i.get("scale"))); // 소수점길이 META에서 "" 이면 "" 으로 표현함
item.put("loutItemMskTarget", _i.get("maskTarget")); item.put("loutItemMskTarget", _i.get("maskTarget"));
@@ -683,9 +703,7 @@ public class LayoutSyncController extends OnlBaseAnnotationController implements
param.put("recvData", ""); param.put("recvData", "");
} }
//syncLayout(param, recvTime, json); syncLayout(param, recvTime, json);
syncLayoutDjb(param, recvTime, json);
// results.getChild("Result").setText("S".equals(param.get("prcssRslt"))?"True":"False"); // results.getChild("Result").setText("S".equals(param.get("prcssRslt"))?"True":"False");
// results.getChild("ResultMessage").setText("S".equals(param.get("prcssRslt"))?"성공":param.get("prcssRsltCmnt")); // results.getChild("ResultMessage").setText("S".equals(param.get("prcssRslt"))?"성공":param.get("prcssRsltCmnt"));
@@ -902,11 +920,7 @@ public class LayoutSyncController extends OnlBaseAnnotationController implements
private void syncLayout(HashMap<String, String> param, JSONObject body, String command, JSONObject layout) private void syncLayout(HashMap<String, String> param, JSONObject body, String command, JSONObject layout)
throws Exception, BizException, ParseException { throws Exception, BizException, ParseException {
String id = (String) layout.get("id"); String layoutName = (String) layout.get("id");
String layoutName = (String) layout.get("layoutName");
if (StringUtils.isEmpty(layoutName)) {
layoutName = id;
}
String useSystem = (String) layout.get("usesystem"); String useSystem = (String) layout.get("usesystem");
param.put("logPrcssSeqno", UUIDGenerator.getUUID()); param.put("logPrcssSeqno", UUIDGenerator.getUUID());
@@ -941,7 +955,7 @@ public class LayoutSyncController extends OnlBaseAnnotationController implements
break; break;
} }
if(!id.matches(fattern)) { if(!layoutName.matches(fattern)) {
throw new BizException("잘못된 전문레이아웃명 입니다.\n명명규칙을 확인하세요.\n"+fattern); throw new BizException("잘못된 전문레이아웃명 입니다.\n명명규칙을 확인하세요.\n"+fattern);
} }
@@ -961,8 +975,7 @@ public class LayoutSyncController extends OnlBaseAnnotationController implements
// Layout Format (TR07) // Layout Format (TR07)
vo.put("loutName",layoutName); vo.put("loutName",layoutName);
vo.put("loutPtrnName",msgType); // ASCII, EBCDIC, XML vo.put("loutPtrnName",msgType); // ASCII, EBCDIC, XML
vo.put("loutDesc",layout.get("desc")); vo.put("loutDesc",layout.get("desc"));
vo.put("useYn", "1");
String date = (String) layout.get("date"); String date = (String) layout.get("date");
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@@ -1190,63 +1203,5 @@ public class LayoutSyncController extends OnlBaseAnnotationController implements
} }
return null ; return null ;
} }
private void syncLayoutDjb(HashMap<String, String> param, String recvTime, JSONObject json)
throws Exception, BizException, ParseException {
JSONObject header = (JSONObject) ((JSONObject) json.get("Message")).get("Header");
JSONObject body = (JSONObject) ((JSONObject) json.get("Message")).get("Body");
String serviceName = (String) header.get("ServiceName");
String command = (String) header.get("Command");
param.put("serviceName", serviceName);
param.put("command", command);
param.put("recvAmndHMS", recvTime);
Object layoutObject = body.get("Layout");
JSONArray layoutArray = null;
if (layoutObject instanceof JSONArray) {
String interfaceId = (String) body.get("interfaceCode");
if (interfaceId.startsWith("ES")) {
interfaceId = interfaceId.replaceFirst("ES", "AS");
}
layoutArray = (JSONArray) layoutObject;
for (int i = 0; i < layoutArray.size(); i++) {
JSONObject layout = (JSONObject)layoutArray.get(i);
String usesystem = (String)layout.get("usesystem");
if ("EAI".equals(usesystem)) {
String id = (String)layout.get("id");
String inOutType = "OPA".equals(id.substring(5,8)) ? "S1" : "S2";
String group = (String)layout.get("group");
String ioType = (String)layout.get("ioType");
if ("I".equals(ioType)) {
layout.put("layoutName", group + "_" + interfaceId + inOutType + "_SRCS");
syncLayout(param, body, command, layout);
layout.put("layoutName", group + "_" + interfaceId + inOutType + "_TGTS");
syncLayout(param, body, command, layout);
} else {
layout.put("layoutName", group + "_" + interfaceId + inOutType + "_SRCR");
syncLayout(param, body, command, layout);
layout.put("layoutName", group + "_" + interfaceId + inOutType + "_TGTR");
syncLayout(param, body, command, layout);
}
}
}
} else {
throw new BizException("단독 레이아웃 연동은 지원하지 않습니다.");
}
}
} }
@@ -101,6 +101,11 @@ public class Transform2Controller extends BaseController {
public String popupCalendarView() { public String popupCalendarView() {
return "/onl/admin/rule/transform2/transform2ManPopupCalendar"; return "/onl/admin/rule/transform2/transform2ManPopupCalendar";
} }
@GetMapping(value = "/onl/admin/rule/transform2/transform2Man.view", params = "cmd=API_POPUP")
public String apiPopupView() {
return "/onl/admin/rule/transform2/transform2ManApiPopup";
}
@PostMapping(value = "/onl/admin/rule/transform2/transform2Man.json", params = "cmd=LIST_INIT_COMBO") @PostMapping(value = "/onl/admin/rule/transform2/transform2Man.json", params = "cmd=LIST_INIT_COMBO")
public ResponseEntity<Map<String, Object>> initCombo(HttpServletRequest request, String eaiSvcName, String cnvsnName) { public ResponseEntity<Map<String, Object>> initCombo(HttpServletRequest request, String eaiSvcName, String cnvsnName) {
@@ -8,6 +8,7 @@ import com.eactive.eai.data.entity.onl.message.ServiceMessageEntityId;
import com.eactive.eai.data.entity.onl.messagekey.MessageKey; import com.eactive.eai.data.entity.onl.messagekey.MessageKey;
import com.eactive.eai.data.entity.onl.stdmessage.QStandardMessageInfo; import com.eactive.eai.data.entity.onl.stdmessage.QStandardMessageInfo;
import com.eactive.eai.data.entity.onl.stdmessage.StandardMessageInfo; import com.eactive.eai.data.entity.onl.stdmessage.StandardMessageInfo;
import com.eactive.eai.rms.data.entity.onl.djb.apistatus.QApiStatus;
import com.eactive.eai.rms.common.base.OnlBaseService; import com.eactive.eai.rms.common.base.OnlBaseService;
import com.eactive.eai.rms.common.combo.ComboService; import com.eactive.eai.rms.common.combo.ComboService;
import com.eactive.eai.rms.common.combo.ComboVo; import com.eactive.eai.rms.common.combo.ComboVo;
@@ -140,6 +141,7 @@ public class ApiInterfaceService extends OnlBaseService {
String apiFullPath = tuple.get(QStandardMessageInfo.standardMessageInfo.apifullpath); String apiFullPath = tuple.get(QStandardMessageInfo.standardMessageInfo.apifullpath);
String bzwksvckeyname = tuple.get(QStandardMessageInfo.standardMessageInfo.bzwksvckeyname); String bzwksvckeyname = tuple.get(QStandardMessageInfo.standardMessageInfo.bzwksvckeyname);
String statusCode = tuple.get(QApiStatus.apiStatus.statusCode);
ApiInterfaceUI apiInterfaceUI; ApiInterfaceUI apiInterfaceUI;
if (eaiMessageEntity.getServiceMessages() != null && eaiMessageEntity.getServiceMessages().size() > 0) { if (eaiMessageEntity.getServiceMessages() != null && eaiMessageEntity.getServiceMessages().size() > 0) {
@@ -147,10 +149,11 @@ public class ApiInterfaceService extends OnlBaseService {
} else { } else {
apiInterfaceUI = apiInterfaceUIMapper.toVo(eaiMessageEntity); apiInterfaceUI = apiInterfaceUIMapper.toVo(eaiMessageEntity);
} }
apiInterfaceUI.setApiFullPath(apiFullPath); apiInterfaceUI.setApiFullPath(apiFullPath);
apiInterfaceUI.setBzwksvckeyname(bzwksvckeyname); apiInterfaceUI.setBzwksvckeyname(bzwksvckeyname);
apiInterfaceUI.setStatusCode(statusCode);
return apiInterfaceUI; return apiInterfaceUI;
}); });
} }
@@ -1117,13 +1120,14 @@ public class ApiInterfaceService extends OnlBaseService {
String replacementBetweenUnderscores = newApiInterfaceId; String replacementBetweenUnderscores = newApiInterfaceId;
// 정규 표현식 패턴: 번째 언더바 전의 단어와 번째 언더바 사이의 단어를 찾기 위한 패턴 // 정규 표현식 패턴: 번째 언더바 전의 단어와 번째 언더바 사이의 단어를 찾기 위한 패턴
String regex = "(^[^_]+)|(?<=_)\\w+(?=_)"; // (서비스ID 등에 '-' 포함될 있으므로 \w+ 대신 [^_]+ 사용 - transform 치환 로직과 동일)
String regex = "(^[^_]+)|(?<=_)[^_]+(?=_)";
Pattern pattern = Pattern.compile(regex); Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(loutName); Matcher matcher = pattern.matcher(loutName);
// 번째 매칭된 부분(언더바 전의 단어) 번째 매칭된 부분(언더바 사이의 단어) 대체 // 번째 매칭된 부분(언더바 전의 단어) 번째 매칭된 부분(언더바 사이의 단어) 대체
String updatedString = matcher.replaceFirst(replacementBeforeUnderscore); // 번째 매칭된 부분 대체 String updatedString = matcher.replaceFirst(replacementBeforeUnderscore); // 번째 매칭된 부분 대체
updatedString = updatedString.replaceFirst("(?<=_)\\w+(?=_)", replacementBetweenUnderscores); // 번째 매칭된 부분 대체 updatedString = updatedString.replaceFirst("(?<=_)[^_]+(?=_)", replacementBetweenUnderscores); // 번째 매칭된 부분 대체
layoutUI.setLoutName(updatedString); layoutUI.setLoutName(updatedString);

Some files were not shown because too many files have changed in this diff Show More