Compare commits
232 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e4de8ebc8b | |||
| 52e8c3be39 | |||
| 136626e7c4 | |||
| 0848cd3469 | |||
| 0b6f273132 | |||
| 266644798f | |||
| 2e8b45c69e | |||
| ea3d23424c | |||
| 2a9dfaa093 | |||
| 3b74f9ce38 | |||
| 145ad37499 | |||
| a235b0656c | |||
| da397b21b0 | |||
| e084436c90 | |||
| 15652e158d | |||
| 8e4c2efe88 | |||
| cad51df3cb | |||
| d19a9eb593 | |||
| a05870fd1b | |||
| 47259da0f6 | |||
| 0bdcc440b7 | |||
| 778197c1dd | |||
| 91631ea750 | |||
| a1d7f8f10e | |||
| 1ba140ddea | |||
| 7117420f38 | |||
| 536151a737 | |||
| 1ac84f2f92 | |||
| 60d1dcea96 | |||
| bb3fb5e111 | |||
| 1608d29726 | |||
| d08c17bb13 | |||
| 5fd8334ea8 | |||
| a1d2058bca | |||
| 927ef830ac | |||
| 9cdfad1317 | |||
| bc40362c47 | |||
| 74f7aa5cf9 | |||
| 258bb70a93 | |||
| a2cda26909 | |||
| 598dc5b1ee | |||
| 28ab6863da | |||
| 6e4abf9f27 | |||
| 95ce7f9150 | |||
| 8a7ceacb32 | |||
| 8b22f8e1f8 | |||
| 497b72c52a | |||
| e69e1a1031 | |||
| 9df39e74bd | |||
| 37817b15a9 | |||
| b3f797b726 | |||
| 929ec1f62d | |||
| 720c49295c | |||
| b3b315cd6a | |||
| e0b7ab1129 | |||
| ac90d171d3 | |||
| 0f36718534 | |||
| 314eb87ea4 | |||
| a6e88f2281 | |||
| 38f89fcd7a | |||
| 48073d5d91 | |||
| 79653a2887 | |||
| 97d6489bf7 | |||
| 1bf3a950e3 | |||
| 12e4213fa1 | |||
| e60d5443a3 | |||
| a15fa5ad66 | |||
| 7b8646ab94 | |||
| 26f85b6f11 | |||
| de6f7e5ff2 | |||
| e2206aedc4 | |||
| 2e30239a90 | |||
| e28d6e54c5 | |||
| eb5e289a4e | |||
| ca9c3240bf | |||
| 289f8e2f2f | |||
| a0a1954a58 | |||
| 0149da372e | |||
| 3bb0325bf7 | |||
| 579cb61a75 | |||
| 6c67e3f1d8 | |||
| a19f65a660 | |||
| 0e616bc995 | |||
| 7ac9c05f62 | |||
| 7c9f87db73 | |||
| 8448d99fe8 | |||
| ec9cc19821 | |||
| c2d6eb4c34 | |||
| e364a35b66 | |||
| 760000070d | |||
| 71e433a2a5 | |||
| 5830031f32 | |||
| c7be9760a0 | |||
| ee0b882608 | |||
| 2cdd321c39 | |||
| d1eecb4ee7 | |||
| 91a156c6b0 | |||
| c19c12df70 | |||
| 347cca2d7c | |||
| d8a1ca84e2 | |||
| bf4f953d56 | |||
| 814571eeba | |||
| 13850ba329 | |||
| 04b4d415a9 | |||
| 38902cf6b3 | |||
| 7ebbd7d9e4 | |||
| f59de66d2c | |||
| c179e50047 | |||
| 39f0baf5ef | |||
| 08354837b1 | |||
| a31b991e59 | |||
| 615e56687a | |||
| ba1161be7b | |||
| 7430b03323 | |||
| 5b05489924 | |||
| 3c386b780b | |||
| f928a0b1e4 | |||
| dc9e737aa9 | |||
| db57e95233 | |||
| 643e7fcdf2 | |||
| 36994a57b6 | |||
| 4a087e576b | |||
| a92fc85730 | |||
| 9f22d4fa4d | |||
| 66c01029cd | |||
| 317634b5b7 | |||
| 27fa79ddd9 | |||
| ccc55fd015 | |||
| 3bf8370d0c | |||
| 46bd79bc36 | |||
| cdc1a6f342 | |||
| 42d7fa977e | |||
| cc0ef13d5d | |||
| 6604f7c9e8 | |||
| cd8f826ba5 | |||
| 031796dac4 | |||
| f091b9ab19 | |||
| f47480674d | |||
| cd20aeee2a | |||
| 0365f4342e | |||
| 0576fd4c6e | |||
| 8215b596fe | |||
| fc6b96d7cd | |||
| 5fe247df65 | |||
| d11f7d9f38 | |||
| 889fd1f9f5 | |||
| c0844e8d36 | |||
| db38b8f6e9 | |||
| 8abe0a8585 | |||
| ca8d46c7b8 | |||
| 628ab01c13 | |||
| f10f5ce08e | |||
| 90ba584e4b | |||
| e59f64653f | |||
| 2422c7f01e | |||
| 29025d5f47 | |||
| e91b12f2d7 | |||
| da90a3a002 | |||
| a4614ca7fd | |||
| 2d414dc31b | |||
| 775d69eb53 | |||
| 8f4162569d | |||
| adc5371df8 | |||
| e7297458f6 | |||
| c10bfe8326 | |||
| c224f80cdf | |||
| 6c70d9611b | |||
| 26679cf649 | |||
| 406dd34e28 | |||
| 59dd28c23b | |||
| cd93e34840 | |||
| 2fddffe29d | |||
| 7a73ef370f | |||
| c9be6069ae | |||
| a13c8f154f | |||
| 8c98297634 | |||
| f36bfcc642 | |||
| 2833944fe4 | |||
| 18110641d6 | |||
| 316f787276 | |||
| c3c9f7a2da | |||
| 95e31befbb | |||
| f1f7576b9d | |||
| 545e09891b | |||
| 70c2ac8376 | |||
| 21c493cb0e | |||
| b9c6a0da06 | |||
| a0725b57a6 | |||
| 50bfcacf27 | |||
| f400f0322f | |||
| d0cd8118f3 | |||
| 07e65c055b | |||
| af8a9cca51 | |||
| 39a09d0f5d | |||
| 5b940717c4 | |||
| 8ccf52fd66 | |||
| dedc58a07f | |||
| 4530af9dec | |||
| cbf3066a56 | |||
| 160e28444a | |||
| d7dcf9a81c | |||
| acfe6b2d03 | |||
| 481e82fec7 | |||
| 1b382f5859 | |||
| 608cb4188e | |||
| 534947637d | |||
| 153c68c6ab | |||
| 80ef41f4fc | |||
| 8aebabe005 | |||
| 6d33b69bed | |||
| 49ebdce4c3 | |||
| 979e43e382 | |||
| 53b075967c | |||
| 724117860b | |||
| 0ee52392aa | |||
| f0af06da88 | |||
| b16d03517a | |||
| edf024f0a3 | |||
| a1a1f27498 | |||
| c62f395983 | |||
| c3955cefd8 | |||
| 5c378eac0d | |||
| e51cdbc42a | |||
| 142d402bad | |||
| f23cb02b1c | |||
| eca120812c | |||
| 71b0fc7009 | |||
| 7b12ef2fb5 | |||
| 292e27e66e | |||
| 61d710dd8c | |||
| 8494758fcd | |||
| f2b25d2775 |
Vendored
+157
-148
@@ -1,180 +1,189 @@
|
||||
pipeline {
|
||||
agent { label 'djb-vm' }
|
||||
agent none
|
||||
|
||||
options {
|
||||
timestamps()
|
||||
disableConcurrentBuilds()
|
||||
skipDefaultCheckout() // stage별 agent의 자동 SCM checkout 방지 (Deploy 노드는 git 접근 불필요, unstash로만 WAR 수신)
|
||||
buildDiscarder(logRotator(numToKeepStr: '20', artifactNumToKeepStr: '20'))
|
||||
}
|
||||
|
||||
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'
|
||||
CATALINA_BASE = '/prod/eapim/adminportal'
|
||||
CATALINA_HOME = '/prod/eapim/apache-tomcat-9.0.116'
|
||||
CATALINA_PID = '/prod/eapim/adminportal/temp/catalina.pid'
|
||||
DEPLOY_HTTP_PORT = '39120'
|
||||
WEBHOOK_URL = 'http://172.30.1.50:18000/api/hooks/01ba51b01d3c4c98ae8f3183a23a84ed713197857d024b5da4f303458195eb32'
|
||||
}
|
||||
|
||||
stages {
|
||||
stage('Checkout') {
|
||||
steps {
|
||||
checkout scm
|
||||
stage('Build (djb-vm)') {
|
||||
agent { label 'djb-vm' }
|
||||
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') {
|
||||
steps {
|
||||
sh '''
|
||||
set -eu
|
||||
cd "$WORKSPACE/.."
|
||||
stage('Checkout dependencies') {
|
||||
steps {
|
||||
sh '''
|
||||
set -eu
|
||||
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
|
||||
TARGET="eapim-online/$REPO"
|
||||
if [ ! -d "$TARGET/.git" ]; then
|
||||
rm -rf "$TARGET"
|
||||
git clone --depth=1 --branch master \
|
||||
"ssh://git@172.30.1.50:2222/djb-eapim/$REPO.git" \
|
||||
"$TARGET"
|
||||
else
|
||||
git -C "$TARGET" fetch --depth=1 origin master
|
||||
git -C "$TARGET" reset --hard origin/master
|
||||
git -C "$TARGET" clean -fdx
|
||||
fi
|
||||
done
|
||||
for REPO in elink-online-core elink-online-core-jpa elink-online-transformer elink-online-common elink-online-emsclient; do
|
||||
TARGET="eapim-online/$REPO"
|
||||
if [ ! -d "$TARGET/.git" ]; then
|
||||
rm -rf "$TARGET"
|
||||
git clone --depth=1 --branch master \
|
||||
"ssh://git@172.30.1.50:2222/djb-eapim/$REPO.git" \
|
||||
"$TARGET"
|
||||
else
|
||||
git -C "$TARGET" fetch --depth=1 origin master
|
||||
git -C "$TARGET" reset --hard origin/master
|
||||
git -C "$TARGET" clean -fdx
|
||||
fi
|
||||
done
|
||||
|
||||
for REPO in elink-portal-common eapim-admin-djb; do
|
||||
if [ ! -d "$REPO/.git" ]; then
|
||||
rm -rf "$REPO"
|
||||
git clone --depth=1 --branch master \
|
||||
"ssh://git@172.30.1.50:2222/djb-eapim/$REPO.git" \
|
||||
"$REPO"
|
||||
else
|
||||
git -C "$REPO" fetch --depth=1 origin master
|
||||
git -C "$REPO" reset --hard origin/master
|
||||
git -C "$REPO" clean -fdx
|
||||
fi
|
||||
done
|
||||
'''
|
||||
}
|
||||
}
|
||||
for REPO in elink-portal-common eapim-admin-djb; do
|
||||
if [ ! -d "$REPO/.git" ]; then
|
||||
rm -rf "$REPO"
|
||||
git clone --depth=1 --branch master \
|
||||
"ssh://git@172.30.1.50:2222/djb-eapim/$REPO.git" \
|
||||
"$REPO"
|
||||
else
|
||||
git -C "$REPO" fetch --depth=1 origin master
|
||||
git -C "$REPO" reset --hard origin/master
|
||||
git -C "$REPO" clean -fdx
|
||||
fi
|
||||
done
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
stage('Verify toolchain') {
|
||||
steps {
|
||||
sh '''
|
||||
set -eu
|
||||
java -version
|
||||
gradle --version
|
||||
'''
|
||||
}
|
||||
}
|
||||
stage('Verify toolchain') {
|
||||
steps {
|
||||
sh '''
|
||||
set -eu
|
||||
java -version
|
||||
gradle --version
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
stage('Build WAR') {
|
||||
steps {
|
||||
sh 'gradle clean build -x test --no-daemon'
|
||||
}
|
||||
post {
|
||||
success {
|
||||
sh '''
|
||||
set -eu
|
||||
cd build/libs
|
||||
sha1sum eapim-admin.war > eapim-admin.war.sha1
|
||||
sha256sum eapim-admin.war > eapim-admin.war.sha256
|
||||
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('Build WAR') {
|
||||
steps { sh 'gradle clean build -x test --no-daemon -Pprofile=weblogic' }
|
||||
post {
|
||||
success {
|
||||
sh '''
|
||||
set -eu
|
||||
cd build/libs
|
||||
sha256sum eapim-admin.war > eapim-admin.war.sha256
|
||||
'''
|
||||
archiveArtifacts artifacts: 'build/libs/eapim-admin.war,build/libs/eapim-admin.war.sha256', fingerprint: true
|
||||
stash name: 'war', includes: 'build/libs/eapim-admin.war'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SBOM(CycloneDX) -> xlsx. 산출물 eapim-admin-sbom.xlsx 를 아티팩트로 보관.
|
||||
// 실패해도 배포는 진행하도록 UNSTABLE 로만 표시한다.
|
||||
stage('SBOM') {
|
||||
steps {
|
||||
catchError(buildResult: 'UNSTABLE', stageResult: 'FAILURE') {
|
||||
sh 'gradle sbomXlsx --no-daemon -Pprofile=weblogic'
|
||||
}
|
||||
}
|
||||
post {
|
||||
always {
|
||||
archiveArtifacts allowEmptyArchive: true, artifacts: 'build/reports/sbom/*.xlsx', fingerprint: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('Stop Tomcat') {
|
||||
steps {
|
||||
sh '''
|
||||
set +e
|
||||
systemctl --user stop eapim-admin 2>/dev/null
|
||||
|
||||
if [ -f "$CATALINA_PID" ] && kill -0 "$(cat "$CATALINA_PID")" 2>/dev/null; then
|
||||
JAVA_HOME="$JAVA_HOME_TOMCAT" "$CATALINA_HOME/bin/shutdown.sh" 30 -force || true
|
||||
for i in $(seq 1 30); do
|
||||
[ -f "$CATALINA_PID" ] && kill -0 "$(cat "$CATALINA_PID")" 2>/dev/null || break
|
||||
sleep 1
|
||||
done
|
||||
fi
|
||||
rm -f "$CATALINA_PID"
|
||||
'''
|
||||
stage('Deploy (weblogic)') {
|
||||
agent { label 'weblogic' }
|
||||
environment {
|
||||
JENKINS_NODE_COOKIE = 'dontKillMe' // background weblogic를 빌드 종료 시 죽이지 않게
|
||||
WL_HOME = '/app/eapim/adminportal'
|
||||
WL_DEPLOY_DIR = '/app/eapim/adminportal'
|
||||
WL_WAR_NAME = 'eapim-admin.war'
|
||||
WL_HTTP_PORT = '39120'
|
||||
WL_NOHUP = '/logs/weblogic/domains/eapimDomain/nohup.emsSvr11.out'
|
||||
}
|
||||
}
|
||||
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') {
|
||||
steps {
|
||||
sh '''
|
||||
set -eu
|
||||
DEPLOY_DIR="$CATALINA_BASE/webapps"
|
||||
rm -rf "$DEPLOY_DIR/monitoring" "$DEPLOY_DIR/monitoring.war"
|
||||
cp build/libs/eapim-admin.war "$DEPLOY_DIR/monitoring.war"
|
||||
ls -la "$DEPLOY_DIR"
|
||||
'''
|
||||
}
|
||||
}
|
||||
DEADLINE=$(($(date +%s) + 300))
|
||||
STATUS=000
|
||||
while [ "$(date +%s)" -lt "$DEADLINE" ]; do
|
||||
STATUS=$(curl -sS -o /dev/null -w '%{http_code}' --max-time 3 \
|
||||
"http://localhost:$WL_HTTP_PORT/monitoring/loginForm.do" 2>/dev/null || echo "000")
|
||||
case "$STATUS" in
|
||||
200|302|401) echo "Readiness OK (/monitoring/loginForm.do HTTP $STATUS)"; break ;;
|
||||
esac
|
||||
sleep 3
|
||||
done
|
||||
|
||||
stage('Start Tomcat and readiness') {
|
||||
steps {
|
||||
sh '''
|
||||
set -eu
|
||||
LOG="$CATALINA_BASE/logs/catalina.$(date +%Y-%m-%d).log"
|
||||
BEFORE=0
|
||||
[ -f "$LOG" ] && BEFORE=$(stat -c %s "$LOG")
|
||||
|
||||
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
|
||||
'''
|
||||
case "$STATUS" in
|
||||
200|302|401) ;;
|
||||
*)
|
||||
echo "Readiness failed within 300s, last HTTP=$STATUS"
|
||||
[ -f "$WL_NOHUP" ] && tail -120 "$WL_NOHUP"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
'''
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,7 +1,7 @@
|
||||
UserManController_사용자관리_APIGW_INSERT,UPDATE,DELETE
|
||||
AdapterController_어댑터관리_APIGW_INSERT,UPDATE,DELETE
|
||||
ApiInterfaceController_API관리_APIGW_INSERT,UPDATE,DELETE,CLONE
|
||||
ClientController_Client (키정보)관리_APIGW_INSERT,UPDATE,DELETE
|
||||
ClientController_Client (키정보)관리_APIGW_INSERT,UPDATE,DELETE,SYNC_PORTAL
|
||||
RoleController_Role (역할)관리_APIGW_INSERT,UPDATE,DELETE
|
||||
MenuController_Menu관리_APIGW_INSERT,UPDATE,DELETE
|
||||
PortalApprovalLineManController_승인라인관리_APIGW_INSERT,UPDATE,DELETE
|
||||
@@ -16,3 +16,5 @@ PortalPartnershipManController_피드백/개선요청관리_APIGW_UNMASK,DELETE
|
||||
PortalPropertyController_포탈 프로퍼티 관리_APIGW_UPDATE
|
||||
PortalUserTermsManController_약관동의 이력_APIGW_UNMASK
|
||||
MessageRequestManController_메세지발송내역_APIGW_UPDATE_STATUS
|
||||
PortalInquiryManController_Q&A문의관리_APIGW_VISIBILITY
|
||||
PortalMenuManController_포탈메뉴관리_APIGW_INSERT,UPDATE,DELETE,TRANSACTION_PLACEMENT,INITIALIZE,TRANSACTION_RELOAD
|
||||
|
||||
@@ -15,4 +15,4 @@ eai.tableowner=EMSAPP
|
||||
eai.systemmode=L
|
||||
eai.drmode=N
|
||||
# black / blue / green / orange / yellow
|
||||
theme.color=yellow
|
||||
theme.color=blue
|
||||
|
||||
@@ -110,7 +110,7 @@
|
||||
</mvc:interceptor>
|
||||
<mvc:interceptor>
|
||||
<mvc:mapping path="/_onl/**"/>
|
||||
<bean class="com.eactive.eai.rms.common.interceptor.ApiKeyInterceptor" />
|
||||
<bean class="com.eactive.eai.rms.common.interceptor.InternalApiTokenInterceptor" />
|
||||
</mvc:interceptor>
|
||||
<!-- BaseRestController 엔드포인트 IP 화이트리스트 인증 -->
|
||||
<mvc:interceptor>
|
||||
|
||||
@@ -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>
|
||||
@@ -1,12 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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">
|
||||
<weblogic-web-app xmlns="http://xmlns.oracle.com/weblogic/weblogic-web-app">
|
||||
|
||||
<context-root>monitoring</context-root>
|
||||
<session-descriptor>
|
||||
<timeout-secs>1800</timeout-secs>
|
||||
<timeout-secs>3600</timeout-secs>
|
||||
<cookie-name>JSESSIONID_EMS</cookie-name>
|
||||
<persistent-store-type>replicated_if_clustered</persistent-store-type>
|
||||
</session-descriptor>
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
@charset "utf-8";
|
||||
|
||||
.gnb .depth1 > li:hover > a{color:#008cc5;} /* theme */
|
||||
.gnb .sitemap-link:hover{color:#008cc5;} /* theme */
|
||||
.gnb .depth1 > li > .red_box{background:#008cc5;}/* theme */
|
||||
.gnb .depth2 > li > a:hover{color:#008cc5;}/* theme */
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
@charset "utf-8";
|
||||
|
||||
.gnb .depth1 > li:hover > a{color:#16a085;} /* theme */
|
||||
.gnb .sitemap-link:hover{color:#16a085;} /* theme */
|
||||
.gnb .depth1 > li > .red_box{background:#16a085;}/* theme */
|
||||
.gnb .depth2 > li > a:hover{color:#16a085;}/* theme */
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
@charset "utf-8";
|
||||
|
||||
.gnb .depth1 > li:hover > a{color:#e74c3c;} /* theme */
|
||||
.gnb .sitemap-link:hover{color:#e74c3c;} /* theme */
|
||||
.gnb .depth1 > li > .red_box{background:#e74c3c;}/* theme */
|
||||
.gnb .depth2 > li > a:hover{color:#e74c3c;}/* theme */
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
@charset "utf-8";
|
||||
|
||||
.gnb .depth1 > li:hover > a{color:#ff80c0;} /* theme */
|
||||
.gnb .sitemap-link:hover{color:#ff80c0;} /* theme */
|
||||
.gnb .depth1 > li > .red_box{background:#ff80c0;}/* theme */
|
||||
.gnb .depth2 > li > a:hover{color:#ff80c0;}/* theme */
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
@charset "utf-8";
|
||||
|
||||
.gnb .depth1 > li:hover > a{color:#be8200;} /* theme */
|
||||
.gnb .sitemap-link:hover{color:#be8200;} /* theme */
|
||||
.gnb .depth1 > li > .red_box{background:#be8200;}/* theme */
|
||||
.gnb .depth2 > li > a:hover{color:#be8200;}/* theme */
|
||||
|
||||
|
||||
@@ -62,8 +62,8 @@ header.sub{position:relative; padding-top:80px;}
|
||||
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{position:absolute; top:0px; left:230px; display:inline-block; height:80px; overflow-y:hidden;}
|
||||
.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:180px; display:inline-block; height:80px; overflow-y:hidden;}
|
||||
.gnb:hover{height:auto; overflow-y:visible;}
|
||||
.gnb:hover + .gnb_bg{display:block;}
|
||||
.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 > 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:first-child > a:before{content:''; position:absolute; top:34px; left: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; }
|
||||
/*
|
||||
.gnb .depth1 > li:hover .red_box{transform:scaleX(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 .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.first{border-left:1px solid #eee;}
|
||||
.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 .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: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;}
|
||||
|
||||
.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 > li{display:inline-block;}
|
||||
.content_top .path > li a{display:block; height:50px; line-height:50px; padding-left:10px; font-size:12px; color:#999;}
|
||||
.content_top .path{margin:0; padding:0;}
|
||||
.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; text-decoration:none;}
|
||||
.content_top .path > li a:hover{color:#666;}
|
||||
.content_top .path > li a:before{content:'>'; margin-right:10px;}
|
||||
.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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
};
|
||||
})();
|
||||
@@ -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; }
|
||||
@@ -45,9 +45,13 @@ $(document).ready(function() {
|
||||
}
|
||||
});
|
||||
|
||||
//검색조건 달력
|
||||
$("#startDatepicker").datepicker();
|
||||
$("#endDatepicker").datepicker();
|
||||
|
||||
var gridPostData = getSearchForJqgrid("cmd","LIST"); //jqgrid에서는 object 로
|
||||
gridPostData['searchStartDateTime'] = $("input[name=searchStartYYYYMMDD]").val() + $("input[name=searchStartHHMM]").val();
|
||||
gridPostData['searchEndDateTime'] = $("input[name=searchEndYYYYMMDD]").val() + $("input[name=searchEndHHMM]").val();
|
||||
gridPostData['searchStartDateTime'] = $("input[name=searchStartYYYYMMDD]").val().replace(/-/gi, "") + $("input[name=searchStartHHMM]").val();
|
||||
gridPostData['searchEndDateTime'] = $("input[name=searchEndYYYYMMDD]").val().replace(/-/gi, "") + $("input[name=searchEndHHMM]").val();
|
||||
$('#grid').jqGrid({
|
||||
datatype:"json",
|
||||
mtype: 'POST',
|
||||
@@ -107,8 +111,8 @@ $(document).ready(function() {
|
||||
|
||||
$("#btn_search").click(function(){
|
||||
var postData = getSearchForJqgrid("cmd","LIST"); //jqgrid에서는 object 로
|
||||
postData['searchStartDateTime'] = $("input[name=searchStartYYYYMMDD]").val() + $("input[name=searchStartHHMM]").val();
|
||||
postData['searchEndDateTime'] = $("input[name=searchEndYYYYMMDD]").val() + $("input[name=searchEndHHMM]").val();
|
||||
postData['searchStartDateTime'] = $("input[name=searchStartYYYYMMDD]").val().replace(/-/gi, "") + $("input[name=searchStartHHMM]").val();
|
||||
postData['searchEndDateTime'] = $("input[name=searchEndYYYYMMDD]").val().replace(/-/gi, "") + $("input[name=searchEndHHMM]").val();
|
||||
$("#grid").setGridParam({ postData: postData ,page : "1" }).trigger("reloadGrid");
|
||||
|
||||
});
|
||||
@@ -154,11 +158,11 @@ function getEndTime(){
|
||||
<tr>
|
||||
<th style="width:180px;"><%= localeMessage.getString("auditLogMan.logamndHMS") %></th>
|
||||
<td>
|
||||
<input type="text" name="searchStartYYYYMMDD" value="" size="10" style="width:100px; border:1px solid #ebebec;">
|
||||
<input type="text" name="searchStartHHMM" value="" size="8" style="width:100px; border:1px solid #ebebec;">
|
||||
<input type="text" name="searchStartYYYYMMDD" id="startDatepicker" value="${param.searchStartYYYYMMDD}" readonly="readonly" size="10" style="width:100px; border:1px solid #ebebec;">
|
||||
<input type="text" name="searchStartHHMM" value="${param.searchStartHHMM}" size="8" style="width:100px; border:1px solid #ebebec;">
|
||||
~
|
||||
<input type="text" name="searchEndYYYYMMDD" value="" size="10" style="width:100px; border:1px solid #ebebec;">
|
||||
<input type="text" name="searchEndHHMM" value="" size="8" style="width:100px; border:1px solid #ebebec;">
|
||||
<input type="text" name="searchEndYYYYMMDD" id="endDatepicker" value="${param.searchEndYYYYMMDD}" readonly="readonly" size="10" style="width:100px; border:1px solid #ebebec;">
|
||||
<input type="text" name="searchEndHHMM" value="${param.searchEndHHMM}" size="8" style="width:100px; border:1px solid #ebebec;">
|
||||
<input type="hidden" name="searchStartDateTime" />
|
||||
<input type="hidden" name="searchEndDateTime" />
|
||||
</td>
|
||||
@@ -168,9 +172,9 @@ function getEndTime(){
|
||||
<th style="width:180px;"><%= localeMessage.getString("auditLogMan.systemCode") %></th>
|
||||
<td><div class = "select-style">
|
||||
<select id="searchSystemCode" name="searchSystemCode">
|
||||
<option value=""><%= localeMessage.getString("combo.all")%></option>
|
||||
<option value="" <c:if test="${empty param.searchSystemCode}">selected</c:if>><%= localeMessage.getString("combo.all")%></option>
|
||||
<c:forEach items="${serviceTypeCombos }" var="serviceTypeCombo">
|
||||
<option value="${serviceTypeCombo.code }">${serviceTypeCombo.codename }</option>
|
||||
<option value="${serviceTypeCombo.code }" <c:if test="${serviceTypeCombo.code == param.searchSystemCode}">selected</c:if>>${serviceTypeCombo.codename }</option>
|
||||
</c:forEach>
|
||||
</select>
|
||||
</div>
|
||||
@@ -180,12 +184,13 @@ function getEndTime(){
|
||||
|
||||
<th style="width:180px;"><%= localeMessage.getString("auditLogMan.logTypeText") %></th>
|
||||
<td>
|
||||
<input type="text" value="" name="searchLogTypeText" checked="checked"/>
|
||||
<input type="text" value="${param.searchLogTypeText}" name="searchLogTypeText" checked="checked"/>
|
||||
</td>
|
||||
<th style="width:180px;"><%= localeMessage.getString("auditLogMan.remoteAddress") %></th>
|
||||
<td><input type="text" name="searchRemoteAddress" value="${param.searchRemoteAddress}" style="width:100%"></td>
|
||||
<th style="width:180px;"></th>
|
||||
<th style="width:180px;"><%= localeMessage.getString("auditLogMan.parameters") %></th>
|
||||
<td>
|
||||
<input type="text" name="searchParameters" value="${param.searchParameters}" style="width:100%">
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
@@ -54,10 +54,18 @@ function detail(key){
|
||||
$('#systemCode').val(data.systemCode);
|
||||
$('#logTypeText').val(data.logTypeText);
|
||||
$('#remoteAddress').val(data.remoteAddress);
|
||||
$('#logMsg').val(data.message);
|
||||
$('#logSubMsg').val(data.command);
|
||||
$('#parameters').val(data.parameters);
|
||||
|
||||
// 사유(message)는 값이 있을 때만 노출
|
||||
if (data.message) {
|
||||
$('#logMsg').val(data.message);
|
||||
$('#messageRow').show();
|
||||
} else {
|
||||
$('#logMsg').val('');
|
||||
$('#messageRow').hide();
|
||||
}
|
||||
|
||||
},
|
||||
error:function(e){
|
||||
alert(e.responseText);
|
||||
@@ -121,6 +129,9 @@ $(document).ready(function() {
|
||||
<tr>
|
||||
<th><%= localeMessage.getString("auditLogMan.command") %></th><td><input type="text" id="logSubMsg" name="logSubMsg" readonly="readonly"/></td>
|
||||
</tr>
|
||||
<tr id="messageRow" style="display:none;">
|
||||
<th><%= localeMessage.getString("auditLogMan.message") %></th><td><textarea id="logMsg" name="logMsg" style="width:100%;height:80px" readonly="readonly"></textarea></td>
|
||||
</tr>
|
||||
<tr height="100px">
|
||||
<th><%= localeMessage.getString("auditLogMan.parameters") %></th><td><textarea id="parameters" name="parameters" style="width:100%;height:200px" readonly="readonly"></textarea></td>
|
||||
</tr>
|
||||
|
||||
@@ -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("&", "&").replace("<", "<").replace(">", ">").replace("\"", """);
|
||||
}
|
||||
|
||||
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>
|
||||
@@ -18,6 +18,16 @@
|
||||
<script language="javascript" >
|
||||
var url = '<c:url value="/common/acl/user/userMan.json" />';
|
||||
var url_view = '<c:url value="/common/acl/user/userMan.view" />';
|
||||
|
||||
function userStatusFormat (cellvalue){
|
||||
if ( cellvalue == '1' ){
|
||||
return '<%= localeMessage.getString("code.normal") %>';
|
||||
}else if ( cellvalue == '2' ) {
|
||||
return '<span style="color:red"><%= localeMessage.getString("code.lock") %></span>';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
var gridPostData = getSearchForJqgrid("cmd","LIST"); //jqgrid에서는 object 로
|
||||
$('#grid').jqGrid({
|
||||
@@ -28,16 +38,20 @@ $(document).ready(function() {
|
||||
colNames:['<%= localeMessage.getString("login.userId") %>',
|
||||
'<%= localeMessage.getString("user.name") %>',
|
||||
'<%= localeMessage.getString("user.roleName") %>',
|
||||
'<%= localeMessage.getString("user.status") %>',
|
||||
'<%= localeMessage.getString("user.createOn") %>',
|
||||
'<%= localeMessage.getString("user.lastLoginYms") %>',
|
||||
'<%= localeMessage.getString("user.depart") %>',
|
||||
'<%= localeMessage.getString("user.teamName") %>',
|
||||
'<%= localeMessage.getString("user.position") %>'
|
||||
],
|
||||
colModel:[
|
||||
{ name : 'USERID' , align:'center' ,sortable:false },
|
||||
{ name : 'USERNAME' , align:'center' },
|
||||
{ name : 'ROLEIDNFINAME' , align:'center' },
|
||||
{ name : 'LASTAMNDYMS' , align:'center', formatter: timeStampFormat },
|
||||
{ name : 'USERID' , align:'center', width: 100, sortable:false },
|
||||
{ name : 'USERNAME' , align:'center', width: 100 },
|
||||
{ name : 'ROLEIDNFINAME' , align:'center', width: 300 },
|
||||
{ name : 'STATUS' , align:'center', width: 80, formatter: userStatusFormat },
|
||||
{ name : 'REGDYMS' , align:'center', width: 120, formatter: timeStampFormat },
|
||||
{ name : 'LASTLOGINYMS' , align:'center', width: 120, formatter: timeStampFormat },
|
||||
{ name : 'DVSNNAME' , align:'center', hidden: true },
|
||||
{ name : 'TEAMNAME' , align:'center', hidden: true },
|
||||
{ name : 'JOBTLNAME' , align:'center', hidden: true }
|
||||
|
||||
@@ -88,7 +88,8 @@ function detail(key){
|
||||
$(tag+"[name="+name+"]").val(data[name.toUpperCase()] ? data[name.toUpperCase()].trim():"");
|
||||
});
|
||||
|
||||
$("input[name=LASTAMNDYMS]").inputmask("9999-99-99 99:99:99",{'autoUnmask':true});
|
||||
$("input[name=REGDYMS]").inputmask("9999-99-99 99:99:99",{'autoUnmask':true});
|
||||
$("input[name=LASTLOGINYMS]").inputmask("9999-99-99 99:99:99",{'autoUnmask':true});
|
||||
/* $("input[name=SECONDMENTSTDT]").inputmask("9999-99-99",{'autoUnmask':true});
|
||||
$("input[name=SECONDMENTENDT]").inputmask("9999-99-99",{'autoUnmask':true}); */
|
||||
|
||||
@@ -216,6 +217,7 @@ $(document).ready(function() {
|
||||
dataType:"json",
|
||||
data:{cmd: 'UPDATE_PASSWORD', userId : $("input[name=userId]").val()},
|
||||
success:function(json){
|
||||
$('select[name="status"]').val("1")
|
||||
alert(json.result);
|
||||
},
|
||||
error:function(e){
|
||||
@@ -277,10 +279,10 @@ $(document).ready(function() {
|
||||
<table class="table_row" cellspacing="0">
|
||||
<input type="hidden" name="jobclcd">
|
||||
<tr>
|
||||
<th style="width:180px;"><%= localeMessage.getString("login.userId") %></th><td ><input type="text" name="userId"/> </td>
|
||||
<th style="width:180px;"><%= localeMessage.getString("login.userId") %> <span class=\"required-mark\">*</span></th><td ><input type="text" name="userId"/> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><%= localeMessage.getString("user.name") %> </th><td><input type="text" name="userName" /></td>
|
||||
<th><%= localeMessage.getString("user.name") %> <span class=\"required-mark\">*</span></th><td><input type="text" name="userName" /></td>
|
||||
</tr>
|
||||
<tr class="datailT">
|
||||
<th><%= localeMessage.getString("user.auth") %> </th><td><input type="text" name="roleidnfiname" /></td>
|
||||
@@ -317,7 +319,6 @@ $(document).ready(function() {
|
||||
<td>
|
||||
<div class="select-style">
|
||||
<select name="status">
|
||||
<option value=""><%= localeMessage.getString("code.normal") %></option>
|
||||
<option value="1"><%= localeMessage.getString("code.normal") %></option>
|
||||
<option value="2"><%= localeMessage.getString("code.lock") %></option>
|
||||
</select>
|
||||
@@ -334,7 +335,10 @@ $(document).ready(function() {
|
||||
<th><%= localeMessage.getString("user.fieldAgentName") %> </th><td><input type="text" name="spotprxyname" /></td>
|
||||
</tr> --%>
|
||||
<tr>
|
||||
<th><%= localeMessage.getString("user.createOn") %> </th><td><input type="text" name="LASTAMNDYMS" readonly/></td>
|
||||
<th><%= localeMessage.getString("user.createOn") %> </th><td><input type="text" name="REGDYMS" readonly/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><%= localeMessage.getString("user.lastLoginYms") %> </th><td><input type="text" name="LASTLOGINYMS" readonly/></td>
|
||||
</tr>
|
||||
<%-- <tr>
|
||||
<th><%= localeMessage.getString("user.secondmentbrncd") %> </th><td><input type="text" name="SECONDMENTBRNCD" readonly/></td>
|
||||
|
||||
@@ -31,8 +31,12 @@
|
||||
<script language="javascript" src="<c:url value="/js/custom.js"/>"></script>
|
||||
<script language="javascript" src="<c:url value="/js/common-${locale}.js"/>"></script>
|
||||
<script type="text/javascript">
|
||||
/* 자동 로그아웃: iframe 내 활동을 sessionStorage에 기록 (main.jsp 타이머와 공유) */
|
||||
(function() { var m = function() { sessionStorage.setItem('lastActivity', String(Date.now())); }; ['mousemove','keydown','click','touchstart'].forEach(function(e){ document.addEventListener(e, m, true); }); })();
|
||||
/* 자동 로그아웃: 서버로 실제 요청이 발생한 경우에만 활동으로 기록 (main.jsp 타이머와 sessionStorage 공유) */
|
||||
(function() {
|
||||
var mark = function() { sessionStorage.setItem('lastActivity', String(Date.now())); };
|
||||
mark(); // 화면(프레임) 로드 자체가 서버 요청이므로 활동으로 기록
|
||||
$(document).ajaxSend(mark); // 이후 이 프레임에서 발생하는 모든 AJAX 요청을 활동으로 기록
|
||||
})();
|
||||
</script>
|
||||
<script language="javascript" src="<c:url value="/js/jquery.inputmask.bundle.min.js"/>"></script>
|
||||
<script language="javascript" src="<c:url value="/js/jquery.searchabledropdown-1.0.8.min.js"/>"></script>
|
||||
@@ -531,6 +535,88 @@
|
||||
}
|
||||
}, 200);
|
||||
}
|
||||
|
||||
/**
|
||||
* 사유 입력 모달 - 확인 시 입력한 사유 문자열을 onConfirm(reason)으로 전달
|
||||
* @param {string} message - 안내 메시지
|
||||
* @param {object} options - (title, confirmText, cancelText, placeholder, required, onConfirm, onCancel)
|
||||
* required 기본 true (빈 사유 차단)
|
||||
*/
|
||||
function showReasonPrompt(message, options) {
|
||||
options = options || {};
|
||||
var title = options.title || '사유 입력';
|
||||
var confirmText = options.confirmText || '확인';
|
||||
var cancelText = options.cancelText || '취소';
|
||||
var placeholder = options.placeholder || '사유를 입력하세요.';
|
||||
var required = options.required !== false;
|
||||
var onConfirm = options.onConfirm || function(){};
|
||||
var onCancel = options.onCancel || function(){};
|
||||
|
||||
// 기존 모달 제거
|
||||
$('#commonReasonModal').remove();
|
||||
|
||||
var iconHtml = getAlertIcon('warning');
|
||||
|
||||
var modalHtml =
|
||||
'<div id="commonReasonModal" class="common-alert-overlay">' +
|
||||
'<div class="common-alert-modal alert-warning">' +
|
||||
'<div class="common-alert-header">' +
|
||||
'<span class="common-alert-icon">' + iconHtml + '</span>' +
|
||||
'<span class="common-alert-title">' + title + '</span>' +
|
||||
'</div>' +
|
||||
'<div class="common-alert-body">' +
|
||||
'<p class="common-alert-message">' + message + '</p>' +
|
||||
'<textarea id="commonReasonInput" maxlength="200" placeholder="' + placeholder + '" style="width:100%;height:80px;margin-top:10px;box-sizing:border-box;font-size:13px;padding:8px;border:1px solid #ddd;border-radius:4px;resize:vertical;"></textarea>' +
|
||||
'<p id="commonReasonError" style="display:none;color:#f44336;font-size:12px;margin:6px 0 0;">사유를 입력해 주세요.</p>' +
|
||||
'</div>' +
|
||||
'<div class="common-alert-footer">' +
|
||||
'<button type="button" class="common-confirm-cancel-btn" id="commonReasonCancelBtn">' + cancelText + '</button>' +
|
||||
'<button type="button" class="common-alert-btn" id="commonReasonOkBtn">' + confirmText + '</button>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'</div>';
|
||||
|
||||
$('body').append(modalHtml);
|
||||
|
||||
setTimeout(function() {
|
||||
$('#commonReasonModal').addClass('show');
|
||||
$('#commonReasonInput').focus();
|
||||
}, 10);
|
||||
|
||||
// 확인 버튼
|
||||
$('#commonReasonOkBtn').on('click', function() {
|
||||
var reason = $.trim($('#commonReasonInput').val());
|
||||
if (required && reason === '') {
|
||||
$('#commonReasonError').show();
|
||||
$('#commonReasonInput').focus();
|
||||
return;
|
||||
}
|
||||
closeCommonReason(function() { onConfirm(reason); });
|
||||
});
|
||||
|
||||
// 취소 버튼
|
||||
$('#commonReasonCancelBtn').on('click', function() {
|
||||
closeCommonReason(onCancel);
|
||||
});
|
||||
|
||||
// ESC 키로 취소
|
||||
$(document).on('keydown.commonReason', function(e) {
|
||||
if (e.keyCode === 27) {
|
||||
closeCommonReason(onCancel);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function closeCommonReason(callback) {
|
||||
$('#commonReasonModal').removeClass('show');
|
||||
setTimeout(function() {
|
||||
$('#commonReasonModal').remove();
|
||||
$(document).off('keydown.commonReason');
|
||||
if (typeof callback === 'function') {
|
||||
callback();
|
||||
}
|
||||
}, 200);
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- 공용 Alert 모달 스타일 -->
|
||||
|
||||
@@ -31,6 +31,8 @@ function init(){
|
||||
new makeOptions("CODE","NAME").setObj($("select[name=searchJobInst]")).setNoValueInclude(true).setNoValue("",
|
||||
"<%= localeMessage.getString("combo.all")%>").setData(json.instanceList).rendering();
|
||||
|
||||
$("select[name=searchJobUseYn]").val("Y");
|
||||
|
||||
putSelectFromParam();
|
||||
|
||||
initGrid();
|
||||
|
||||
@@ -7,6 +7,19 @@
|
||||
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
|
||||
<%@taglib uri="http://www.springframework.org/tags" prefix="spring"%>
|
||||
<%@ include file="/jsp/common/include/localemessage.jsp" %>
|
||||
<%
|
||||
// 비밀번호 규칙 안내문구용 모니터링 프로퍼티 조회
|
||||
com.eactive.eai.rms.common.context.MonitoringContext monitoringContext =
|
||||
(com.eactive.eai.rms.common.context.MonitoringContext) WebApplicationContextUtils
|
||||
.getRequiredWebApplicationContext(pageContext.getServletContext())
|
||||
.getBean("monitoringContext");
|
||||
int pwdLengthCheck = monitoringContext.getIntProperty(
|
||||
com.eactive.eai.rms.common.context.MonitoringContext.RMS_PASSWORD_LENGTH_CHECK, 7);
|
||||
int pwdCombiCheck = monitoringContext.getIntProperty(
|
||||
com.eactive.eai.rms.common.context.MonitoringContext.RMS_PASSWORD_COMBI_CHECK, 2);
|
||||
int pwdRepeatCheck = monitoringContext.getIntProperty(
|
||||
com.eactive.eai.rms.common.context.MonitoringContext.RMS_PASSWORD_REPEAT_CHECK, 3);
|
||||
%>
|
||||
<c:set var="themeColor" value="<%=System.getProperty(\"theme.color\")%>" scope="session" />
|
||||
|
||||
<%
|
||||
@@ -65,6 +78,7 @@
|
||||
// SMS 인증 관련 변수
|
||||
var smsAuthTimer = null;
|
||||
var resendTimer = null;
|
||||
var smsAuthPurpose = 'LOGIN'; // 'LOGIN' | 'PASSWORD_CHANGE' - SMS 인증 모달을 어느 흐름에서 띄웠는지 구분
|
||||
|
||||
function fncPreMain() {
|
||||
if ($("input[name=userId]").val() ==null || $("input[name=userId]").val().length == 0 ){
|
||||
@@ -91,15 +105,26 @@
|
||||
data: $('#loginForm').serialize(),
|
||||
dataType: 'json',
|
||||
success: function(response) {
|
||||
if (response.smsAuthRequired) {
|
||||
console.log('res', response)
|
||||
if (response.changePassword) {
|
||||
showChangeInitPassword(response);
|
||||
}
|
||||
else if (response.smsAuthRequired) {
|
||||
// SMS 인증 필요
|
||||
smsAuthPurpose = 'LOGIN';
|
||||
showSmsAuthModal(response);
|
||||
} else if (response.success) {
|
||||
// 직접 로그인 성공
|
||||
window.location.href = response.redirectUrl;
|
||||
} else {
|
||||
// 로그인 실패
|
||||
alert(response.errorMessage || '로그인에 실패했습니다.');
|
||||
if (response.errorMessage == 'NO_PHONE_NUMBER') {
|
||||
alert("휴대폰번호가 등록되어 있지 않아 로그인할 수 없습니다. 관리자에게 문의하세요")
|
||||
} else if (response.errorMessage == 'FAIL_SEND_AUTH') {
|
||||
alert("인증번호 발송에 실패했습니다. 잠시 후 다시 시도해주세요.")
|
||||
} else {
|
||||
// 로그인 실패
|
||||
alert('로그인에 실패했습니다');
|
||||
}
|
||||
}
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
@@ -109,15 +134,32 @@
|
||||
});
|
||||
}
|
||||
|
||||
function showChangeInitPassword(data) {
|
||||
$('input[name="resetUserId"]').val($('input[name="userId"]').val());
|
||||
$('input[name="resetPassword"]').val($('input[name="password"]').val());
|
||||
$('#changeInitPassword').hide();
|
||||
$('#pwdChgModal').modal('show');
|
||||
}
|
||||
|
||||
// SMS 인증 모달 표시
|
||||
function showSmsAuthModal(data) {
|
||||
$('#smsAuthMaskedPhone').text(data.maskedPhone);
|
||||
$('#smsAuthCode').val('');
|
||||
updateSmsTestHint(data.smsTestHint);
|
||||
startExpiryTimer(data.expiresAt);
|
||||
startResendTimer(data.resendableAt);
|
||||
$('#smsAuthModal').modal('show');
|
||||
}
|
||||
|
||||
// fixed 모드(djb.sms_auth.mode=fixed)일 때만 서버가 내려주는 테스트용 인증번호 힌트 표시
|
||||
function updateSmsTestHint(hint) {
|
||||
if (hint) {
|
||||
$('#smsAuthTestHint').text(hint).show();
|
||||
} else {
|
||||
$('#smsAuthTestHint').text('').hide();
|
||||
}
|
||||
}
|
||||
|
||||
// 유효시간 타이머
|
||||
function startExpiryTimer(expiresAt) {
|
||||
clearInterval(smsAuthTimer);
|
||||
@@ -171,6 +213,7 @@
|
||||
dataType: 'json',
|
||||
success: function(data) {
|
||||
if (data.sent) {
|
||||
updateSmsTestHint(data.smsTestHint);
|
||||
startExpiryTimer(data.expiresAt);
|
||||
startResendTimer(data.resendableAt);
|
||||
alert('인증번호가 재발송되었습니다.');
|
||||
@@ -184,7 +227,7 @@
|
||||
});
|
||||
}
|
||||
|
||||
// SMS 인증번호 확인
|
||||
// SMS 인증번호 확인 (로그인 / 비밀번호 변경 공용 - smsAuthPurpose로 분기)
|
||||
function verifySmsAuthCode() {
|
||||
var code = $('#smsAuthCode').val();
|
||||
if (!code || code.length !== 6) {
|
||||
@@ -193,17 +236,25 @@
|
||||
return;
|
||||
}
|
||||
|
||||
var isPasswordChange = (smsAuthPurpose === 'PASSWORD_CHANGE');
|
||||
var verifyUrl = isPasswordChange
|
||||
? '<c:url value="/sms-auth/verify-password-change.json"/>'
|
||||
: '<c:url value="/sms-auth/verify.json"/>';
|
||||
|
||||
$.ajax({
|
||||
url: '<c:url value="/sms-auth/verify.json"/>',
|
||||
url: verifyUrl,
|
||||
type: 'POST',
|
||||
data: { authCode: code },
|
||||
dataType: 'json',
|
||||
success: function(data) {
|
||||
if (data.success) {
|
||||
$('#smsAuthModal').modal('hide');
|
||||
if (isPasswordChange) {
|
||||
alert(data.message);
|
||||
}
|
||||
window.location.href = data.redirectUrl;
|
||||
} else {
|
||||
alert(data.errorMessage || '인증에 실패했습니다.');
|
||||
alert(data.errorMessage || data.message || '인증에 실패했습니다.');
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
@@ -218,44 +269,79 @@
|
||||
clearInterval(resendTimer);
|
||||
$('#smsAuthModal').modal('hide');
|
||||
}
|
||||
function changePwd(){
|
||||
|
||||
function checkPwd(){
|
||||
if ($("input[name=resetUserId]").val() == null || $("input[name=resetUserId]").val().length == 0 ){
|
||||
alert("<%= localeMessage.getString("login.checkid") %>");
|
||||
$("input[name=resetUserId]").trigger("focus");
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
if ($("input[name=resetPassword]").val() == null || $("input[name=resetPassword]").val().trim().length == 0 ){
|
||||
alert("<%= localeMessage.getString("login.checkpwd1") %>");
|
||||
$("input[name=resetPassword]").trigger("focus");
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
if ($("input[name=changePassword]").val() == null || $("input[name=changePassword]").val().trim().length == 0 ){
|
||||
alert("<%= localeMessage.getString("login.checkpwd2") %>");
|
||||
$("input[name=changePassword]").trigger("focus");
|
||||
return ;
|
||||
return false;
|
||||
}
|
||||
if ($("input[name=confirmPassword]").val() == null || $("input[name=confirmPassword]").val().trim().length == 0 ){
|
||||
alert("<%= localeMessage.getString("login.checkpwd3") %>");
|
||||
return ;
|
||||
return false;
|
||||
}
|
||||
if ($("input[name=confirmPassword]").val() != $("input[name=confirmPassword]").val()){
|
||||
if ($("input[name=changePassword]").val() != $("input[name=confirmPassword]").val()){
|
||||
alert("<%= localeMessage.getString("login.checkpwd4") %>");
|
||||
$("input[name=confirmPassword]").trigger("focus");
|
||||
return ;
|
||||
return false;
|
||||
}
|
||||
if ($("input[name=resetUserId]").val() == $("input[name=changePassword]").val()){
|
||||
alert("<%= localeMessage.getString("login.checkpwd5") %>");
|
||||
$("input[name=changePassword]").trigger("focus");
|
||||
return ;
|
||||
return false;
|
||||
}
|
||||
|
||||
//if (idCheck.checked){
|
||||
// setCookie("key",id);
|
||||
//}
|
||||
<%-- $("form").attr("action","<c:url value="/changePassword.do"/>");
|
||||
//$("form").action = "<%=request.getContextPath()%>/changePassword.do";
|
||||
--%>
|
||||
$("form").submit();
|
||||
return true;
|
||||
}
|
||||
|
||||
// 비밀번호 변경 요청 (AJAX) - 서버 룰 검증 실패 시 모달을 닫지 않고 비밀번호 입력값만 초기화
|
||||
function submitChangePassword() {
|
||||
if (!checkPwd()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: '<c:url value="/changePassword.do"/>',
|
||||
type: 'POST',
|
||||
data: $('#modalLoginForm').serialize(),
|
||||
dataType: 'json',
|
||||
success: function(response) {
|
||||
if (response.smsAuthRequired) {
|
||||
// 비밀번호는 아직 반영되지 않음 - SMS 인증 완료 시 최종 반영됨
|
||||
smsAuthPurpose = 'PASSWORD_CHANGE';
|
||||
// 두 모달이 겹치면 이전 모달의 enforceFocus/backdrop 이 남아 입력이 막히므로
|
||||
// #pwdChgModal 이 완전히 닫힌 뒤 SMS 인증 모달을 연다
|
||||
$('#pwdChgModal').one('hidden.bs.modal', function() {
|
||||
showSmsAuthModal(response);
|
||||
});
|
||||
$('#pwdChgModal').modal('hide');
|
||||
return;
|
||||
}
|
||||
alert(response.message);
|
||||
if (response.success) {
|
||||
window.location.href = response.redirectUrl || '<c:url value="/emergency.jsp"/>';
|
||||
} else {
|
||||
$("input[name=changePassword]").val('');
|
||||
$("input[name=confirmPassword]").val('');
|
||||
$("input[name=changePassword]").trigger("focus");
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
alert("<%= localeMessage.getString("login.pwdchangefail") %>");
|
||||
}
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
@@ -271,14 +357,24 @@
|
||||
});
|
||||
$("input[name=changePassword]").keydown(function(event){
|
||||
if ( event.which == 13 ) {
|
||||
changePwd();
|
||||
event.preventDefault();
|
||||
submitChangePassword();
|
||||
}
|
||||
});
|
||||
$("input[name=confirmPassword]").keydown(function(event){
|
||||
if ( event.which == 13 ) {
|
||||
changePwd();
|
||||
event.preventDefault();
|
||||
submitChangePassword();
|
||||
}
|
||||
});
|
||||
// Password Change 링크로 직접 열 때만 초기화 (JS에서 modal('show') 호출 시 relatedTarget 없음)
|
||||
$('#pwdChgModal').on('show.bs.modal', function(event) {
|
||||
if (event.relatedTarget) {
|
||||
$('#modalLoginForm')[0].reset();
|
||||
$('#changeInitPassword').show();
|
||||
}
|
||||
});
|
||||
|
||||
$("select[name=serviceType]").change(function(){
|
||||
fncPreMain();
|
||||
});
|
||||
@@ -286,9 +382,7 @@
|
||||
$("#btn_login").click(function(){
|
||||
fncPreMain();
|
||||
});
|
||||
$("#changePwd").click(function(){
|
||||
changePwd();
|
||||
});
|
||||
|
||||
$("#btn_sso_login").click(function(){
|
||||
fncSsoLogin();
|
||||
});
|
||||
@@ -299,6 +393,19 @@
|
||||
verifySmsAuthCode();
|
||||
}
|
||||
});
|
||||
|
||||
// 인증번호는 숫자만 허용 (타이핑/붙여넣기/드래그 공통, 백스페이스·방향키는 방해하지 않음)
|
||||
$("#smsAuthCode").on("input", function(){
|
||||
var digits = this.value.replace(/[^0-9]/g, '').slice(0, 6);
|
||||
if (this.value !== digits) {
|
||||
this.value = digits;
|
||||
}
|
||||
});
|
||||
|
||||
// SMS 인증 모달이 완전히 열린 뒤 인증번호 입력창에 포커스
|
||||
$('#smsAuthModal').on('shown.bs.modal', function() {
|
||||
$('#smsAuthCode').trigger('focus');
|
||||
});
|
||||
});
|
||||
|
||||
function fncSsoLogin() {
|
||||
@@ -366,7 +473,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 비밀번호 변경 모달 -->
|
||||
<div class="modal fade" id="pwdChgModal" tabindex="-1" role="dialog" aria-labelledby="pwdChgModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
@@ -376,13 +483,15 @@
|
||||
<span aria-hidden="true">X</span>
|
||||
</button>
|
||||
</div>
|
||||
<form name="form" id="modalLoginForm" action="<c:url value="/changePassword.do"/>" method="post">
|
||||
<form id="modalLoginForm" action="<c:url value="/changePassword.do"/>" method="post" onsubmit="return submitChangePassword()" novalidate>
|
||||
<div class="modal-body">
|
||||
<div class="form-group d-flex">
|
||||
<input type="text" name="resetUserId" class="form-control rounded-left" placeholder="User Id" required>
|
||||
</div>
|
||||
<div class="form-group d-flex">
|
||||
<input type="password" name="resetPassword" class="form-control rounded-left" placeholder="<%= localeMessage.getString("login.placeholderCurrentPassword") %>" autocomplete="off" required>
|
||||
<div id="changeInitPassword">
|
||||
<div class="form-group d-flex">
|
||||
<input type="text" name="resetUserId" class="form-control rounded-left" placeholder="User Id" required>
|
||||
</div>
|
||||
<div class="form-group d-flex">
|
||||
<input type="password" name="resetPassword" class="form-control rounded-left" placeholder="<%= localeMessage.getString("login.placeholderCurrentPassword") %>" autocomplete="off" required>
|
||||
</div>
|
||||
</div>
|
||||
<p><%= localeMessage.getString("login.toChangePasswordMessage") %></p>
|
||||
<div class="form-group d-flex">
|
||||
@@ -391,9 +500,13 @@
|
||||
<div class="form-group d-flex">
|
||||
<input type="password" name="confirmPassword" class="form-control rounded-left" placeholder="<%= localeMessage.getString("login.placeholderConfirmationPassword") %>" autocomplete="off" required>
|
||||
</div>
|
||||
<span style="font-size:0.8em; color:red">
|
||||
※ <%= pwdLengthCheck %>글자 이상 & 영문/숫자/특수문자 <%= pwdCombiCheck %>종류 이상<br/>
|
||||
※ 연속된 숫자/문자(예: 123, abc, qwer) 사용 불가<%= pwdRepeatCheck >= 2 ? ", 동일 문자 " + pwdRepeatCheck + "자 이상 반복 불가" : "" %>
|
||||
</span>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<input type="button" id ="changePwd" class="form-control btn btn-primary rounded submit px-3" value="변경">
|
||||
<button type="submit" id="changePwd" class="form-control btn btn-primary rounded submit px-3">변경</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -411,9 +524,9 @@
|
||||
<p>인증번호가 <strong><span id="smsAuthMaskedPhone"></span></strong>로 발송되었습니다.</p>
|
||||
<div class="form-group">
|
||||
<input type="text" id="smsAuthCode" class="form-control"
|
||||
placeholder="인증번호 6자리" maxlength="6"
|
||||
onkeypress="return event.charCode >= 48 && event.charCode <= 57">
|
||||
placeholder="인증번호 6자리" maxlength="6" inputmode="numeric" autocomplete="off">
|
||||
</div>
|
||||
<p id="smsAuthTestHint" class="text-warning" style="display:none;"></p>
|
||||
<p class="text-muted">남은 시간: <span id="smsAuthRemainingTime" class="text-danger font-weight-bold">60</span>초</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
|
||||
@@ -166,10 +166,8 @@
|
||||
location.href = '<%=request.getContextPath()%>/rms/logout.do';
|
||||
}
|
||||
|
||||
/* 상위 프레임(main.jsp) 활동 감지 */
|
||||
['mousemove', 'keydown', 'click', 'touchstart'].forEach(function (e) {
|
||||
document.addEventListener(e, markActivity, true);
|
||||
});
|
||||
/* 상위 프레임(main.jsp)에서 발생하는 AJAX 요청만 활동으로 기록 (마우스 이동/클릭만으로는 갱신되지 않음) */
|
||||
$(document).ajaxSend(markActivity);
|
||||
|
||||
/* 주기적 유휴 시간 체크 */
|
||||
setInterval(function () {
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
<%@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.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 uri="http://www.springframework.org/tags" prefix="spring" %>
|
||||
<%@ include file="/jsp/common/include/localemessage.jsp" %>
|
||||
@@ -16,6 +18,12 @@
|
||||
<%
|
||||
String serviceKey = SessionManager.getServiceTypeKey(request);
|
||||
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 mainPage = (String) request.getAttribute("mainPage");
|
||||
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/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/prefixfree.min.js"/>"></script>
|
||||
<script language="javascript" src="<c:url value="/js/jquery.cookie.js"/>"></script>
|
||||
@@ -529,6 +543,7 @@
|
||||
console.log("selectLocaleValue : " + selectLocaleValue);
|
||||
parent.changeLocale($(this).val());
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
$(function() {
|
||||
@@ -542,6 +557,34 @@
|
||||
|
||||
});
|
||||
|
||||
// 세션(자동로그아웃) 타임아웃 카운트다운 - main.jsp의 유휴시간 판단과 동일한 sessionStorage 값을 공유한다.
|
||||
(function () {
|
||||
var IDLE_MS = ${autoLogoutTimeout} * 60 * 1000;
|
||||
|
||||
function getLastActivity() {
|
||||
var t = sessionStorage.getItem('lastActivity');
|
||||
return t ? parseInt(t, 10) : Date.now();
|
||||
}
|
||||
|
||||
function formatRemain(ms) {
|
||||
if (ms < 0) ms = 0;
|
||||
var totalSec = Math.floor(ms / 1000);
|
||||
var mm = Math.floor(totalSec / 60);
|
||||
var ss = totalSec % 60;
|
||||
return (mm < 10 ? '0' + mm : mm) + ':' + (ss < 10 ? '0' + ss : ss);
|
||||
}
|
||||
|
||||
function updateSessionTimeoutTimer() {
|
||||
var remain = IDLE_MS - (Date.now() - getLastActivity());
|
||||
$('#sessionTimeoutTimer').text('세션 만료까지 ' + formatRemain(remain));
|
||||
}
|
||||
|
||||
$(function () {
|
||||
updateSessionTimeoutTimer();
|
||||
setInterval(updateSessionTimeoutTimer, 1000);
|
||||
});
|
||||
})();
|
||||
|
||||
</script>
|
||||
</head>
|
||||
<body onload="menuRender();">
|
||||
@@ -551,21 +594,29 @@
|
||||
</h1><!-- /monitoring/images/top_logo.png -->
|
||||
<div class="topmenu_box">
|
||||
<ul>
|
||||
<li style="width:240px;" id="newDashboardShow">
|
||||
<a style="width:240px;"
|
||||
href="#"><span><%=SessionManager.getUserName(request) %>(<%=SessionManager.getUserId(request) %>-<%=System.getProperty("inst.Name") %>)</span>
|
||||
<li style="width:240px;">
|
||||
<a style="width:240px; cursor: default"
|
||||
href="#"><span><%=SessionManager.getUserName(request) %>(<%=SessionManager.getUserId(request) %>)</span>
|
||||
<span onClick="javascript:openColorPopup();"><%=localeMessage.getString("screen.customer") %></span>
|
||||
<% if (!"".equals(LastLoginYms.trim())) { %>
|
||||
<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%></span>
|
||||
<% } %>
|
||||
<span id="sessionTimeoutTimer" style="display:block;font-size:10px;"></span>
|
||||
</a></li>
|
||||
<li>
|
||||
<select id="selectLocale" name="locale" style="margin-top: 20px; width:80px">
|
||||
<li style="width:100px; line-height:80px; text-align:center;">
|
||||
<select id="selectLocale" name="locale" style="width:60px; padding: 2px; text-align:center;">
|
||||
<option value="en">English</option>
|
||||
<option value="ko">한글</option>
|
||||
</select>
|
||||
</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") %>
|
||||
</a></li>
|
||||
</ul>
|
||||
|
||||
@@ -180,7 +180,7 @@
|
||||
viewrecords : true,
|
||||
gridview : true,
|
||||
ondblClickRow : function(rowId) {
|
||||
console.log("rowId", rowId);
|
||||
/* console.log("rowId", rowId);
|
||||
var rowData = $(this)
|
||||
.getRowData(rowId);
|
||||
var key = rowData['EaiSevrInstncName'];
|
||||
@@ -193,7 +193,7 @@
|
||||
url2 += "&searchAdptrBzwkGroupName="
|
||||
+ key2;
|
||||
url2 += '&menuId='+'${param.menuId}';
|
||||
goNav(url2);
|
||||
goNav(url2); */
|
||||
},
|
||||
loadComplete : function(d) {
|
||||
$("button[name*=img]").unbind("click");
|
||||
|
||||
@@ -82,8 +82,10 @@
|
||||
apiDesc: api.eaiSvcDesc
|
||||
};
|
||||
});
|
||||
// addJSONData는 datatype:"local" 그리드의 p.data를 채우지 않아, 헤더 클릭 정렬 시 p.data(빈 배열) 기준으로 다시 그려지며 행이 사라짐.
|
||||
// setGridParam({data:...}).trigger('reloadGrid')로 채워야 정렬이 정상 동작함.
|
||||
$("#selectedApiGrid").jqGrid("clearGridData");
|
||||
$("#selectedApiGrid")[0].addJSONData(gridData);
|
||||
$("#selectedApiGrid").jqGrid('setGridParam', {data: gridData}).trigger('reloadGrid');
|
||||
|
||||
var data = json;
|
||||
$("#btn_gen_id").hide();
|
||||
@@ -133,15 +135,14 @@
|
||||
var selectedApiGrid = $("#selectedApiGrid");
|
||||
selectedApiGrid.jqGrid({
|
||||
datatype: "local",
|
||||
gridview: true, // 성능 향상
|
||||
rownumbers: true, // 가상 인덱스
|
||||
rownumbers: true, // 가상 인덱스
|
||||
rownumWidth: 40,
|
||||
colNames: ['API ID', 'API 이름'],
|
||||
colModel: [
|
||||
{name: 'apiId', index: 'apiId', width: 100, align: 'center'},
|
||||
{name: 'apiDesc', index: 'apiDesc', width: 100, align: 'center'}
|
||||
],
|
||||
rowNum: -1,
|
||||
rowNum: 9999,
|
||||
// rowList: [10, 20, 30],
|
||||
// pager: '#selectedApiGridPager',
|
||||
// sortname: 'apiId',
|
||||
@@ -189,6 +190,8 @@
|
||||
var key = "${param.clientId}";
|
||||
if (key != "" && key != "null") {
|
||||
isDetail = true;
|
||||
} else {
|
||||
$('input[name="grantTypes_"]').prop('checked', true);
|
||||
}
|
||||
init(key, detail);
|
||||
|
||||
@@ -305,7 +308,8 @@
|
||||
dataType: "json",
|
||||
data: {
|
||||
cmd: 'SYNC_PORTAL',
|
||||
clientId: clientId
|
||||
clientId: clientId,
|
||||
serviceType: 'APIGW'
|
||||
},
|
||||
success: function(response) {
|
||||
showSyncResultModal(response);
|
||||
@@ -505,18 +509,17 @@
|
||||
<td><input type="text" name="scope"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><%= localeMessage.getString("clntManDtl.grntTp") %>
|
||||
(<%= localeMessage.getString("clntManDtl.sprt") %> ,) <small>(*)</small></th>
|
||||
<th><%= localeMessage.getString("clntManDtl.grntTp") %> <small>(*)</small></th>
|
||||
<td>
|
||||
<input type="checkbox" name="grantTypes_" value="client_credentials"> client_credentials
|
||||
<%-- <input type="checkbox" name="grantTypes_" value="authorization_code"> authorization_code--%>
|
||||
<%-- <input type="checkbox" name="grantTypes_" value="password"> password--%>
|
||||
<input type="checkbox" name="grantTypes_" value="refresh_token"> refresh_token
|
||||
<!-- <input type="checkbox" name="grantTypes_" value="refresh_token"> refresh_token -->
|
||||
</td>
|
||||
<input type="hidden" name="grantTypes"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<tr style="display: none;">
|
||||
<th>REDIRECT URI</th>
|
||||
<td><input type="text" name="redirectUri"/></td>
|
||||
</tr>
|
||||
@@ -537,7 +540,7 @@
|
||||
</th>
|
||||
<td><input type="text" name="accessTokenValiditySeconds"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<tr style="display: none;">
|
||||
<th><%= localeMessage.getString("clntManDtl.rfrshTknExprtnDt") %>
|
||||
</th>
|
||||
<td><input type="text" name="refreshTokenValiditySeconds"/></td>
|
||||
@@ -554,7 +557,7 @@
|
||||
</th>
|
||||
<td><input type="text" name="allowedIps"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<tr style="display: none;">
|
||||
<th>Security Key</th>
|
||||
<td><textarea name="securityKey" rows="3" style="width:100%;"></textarea></td>
|
||||
</tr>
|
||||
@@ -739,7 +742,7 @@ function showSyncResultModal(response) {
|
||||
if (response.success) {
|
||||
statusClass = "status-success";
|
||||
var createdCount = response.createdApiSpecCount || 0;
|
||||
var createdMsg = createdCount > 0 ? "<br/><span style='color:#007bff;'>* " + createdCount + "개의 API Spec이 자동 생성되었습니다.</span>" : "";
|
||||
var createdMsg = createdCount > 0 ? "<br/><span style='color:#007bff;'>* " + createdCount + "개의 API Spec을 비공개(N)로 자동 생성했습니다. API Spec 관리에서 확인 후 공개하세요.</span>" : "";
|
||||
|
||||
if (response.action === 'INSERTED') {
|
||||
statusMsg = "<strong>반영 완료</strong> - 포탈에 새로운 인증정보가 생성되었습니다." + createdMsg;
|
||||
@@ -769,6 +772,7 @@ function showSyncResultModal(response) {
|
||||
var syncedApis = response.syncedApis || [];
|
||||
var invalidApis = response.invalidApis || [];
|
||||
var createdApiSpecs = response.createdApiSpecs || [];
|
||||
var specGenFailed = response.specGenFailed || [];
|
||||
|
||||
$targetCount.text(targetApis.length);
|
||||
|
||||
@@ -782,8 +786,11 @@ function showSyncResultModal(response) {
|
||||
if (invalidApis.includes(apiId)) {
|
||||
status = "미존재 (APIGW에 없음)";
|
||||
statusClass = "api-invalid";
|
||||
} else if (specGenFailed.includes(apiId)) {
|
||||
status = "반영됨 (Spec 자동생성 실패·빈 항목)";
|
||||
statusClass = "api-created";
|
||||
} else if (createdApiSpecs.includes(apiId)) {
|
||||
status = "반영됨 (Spec 신규생성)";
|
||||
status = "반영됨 (Spec 자동생성·비공개)";
|
||||
statusClass = "api-created";
|
||||
} else if (syncedApis.includes(apiId)) {
|
||||
status = "반영됨";
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
var url = '<c:url value="/onl/admin/authserver/scopeMan.json" />';
|
||||
var url_view = '<c:url value="/onl/admin/authserver/scopeMan.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;
|
||||
function isValid(){
|
||||
@@ -65,9 +64,9 @@ function mappingInfo(key){
|
||||
|
||||
$.ajax({
|
||||
type : "POST",
|
||||
url: mapping_url,
|
||||
url: url,
|
||||
dataType: "json",
|
||||
data: {cmd: 'LIST', searchScopeId : key},
|
||||
data: {cmd: 'API_LIST', searchScopeId : key},
|
||||
success: function(json){
|
||||
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() {
|
||||
var returnUrl = getReturnUrlForReturn();
|
||||
var key ="${param.scopeId}";
|
||||
@@ -182,28 +142,39 @@ $(document).ready(function() {
|
||||
if (!isValid()){
|
||||
return;
|
||||
}
|
||||
var scopeId = $('input[name=scopeId]').val();
|
||||
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){
|
||||
postData.push({ name: "cmd" , value:"UPDATE"});
|
||||
}else{
|
||||
postData.push({ name: "cmd" , value:"INSERT"});
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
type : "POST",
|
||||
url:url,
|
||||
data:postData,
|
||||
beforeSend: function() {
|
||||
$("[id^='btn_']").prop("disabled", true);
|
||||
$("#btn_modify").text("처리중 . . . . .");
|
||||
},
|
||||
success:function(args){
|
||||
alert("SCOPE 정보가 저장 되었습니다. 이어서 API 리스트를 저장합니다.");
|
||||
|
||||
if(!isDetail) { // INSERT 일 경우 ScopeId를 업데이트
|
||||
key = $('input[name=scopeId]').val();
|
||||
}
|
||||
saveApiGridData(key, returnUrl); // API LIST 저장.
|
||||
|
||||
|
||||
alert("저장 되었습니다.");
|
||||
goNav(returnUrl);//LIST로 이동
|
||||
},
|
||||
error:function(e){
|
||||
alert(e.responseText);
|
||||
},
|
||||
complete: function() {
|
||||
$("[id^='btn_']").prop("disabled", false);
|
||||
$("#btn_modify").text("수정");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -25,7 +25,7 @@ $(document).ready(function() {
|
||||
datatype:"json",
|
||||
mtype: 'POST',
|
||||
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") %>',
|
||||
'<%= localeMessage.getString("infAdpConMan.adaDes") %>',
|
||||
'<%= localeMessage.getString("infAdpConMan.thrPerSecond") %>',
|
||||
@@ -62,6 +62,7 @@ $(document).ready(function() {
|
||||
url2 += '&menuId='+'${param.menuId}';
|
||||
//검색값
|
||||
url2 += '&searchName='+$("input[name=searchName]").val();
|
||||
url2 += '&searchUseYn='+$("select[name=searchUseYn]").val();
|
||||
//key값
|
||||
url2 += '&name='+name;
|
||||
goNav(url2);
|
||||
@@ -80,7 +81,7 @@ $(document).ready(function() {
|
||||
resizeJqGridWidth('grid','content_middle','1000');
|
||||
|
||||
$("#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(){
|
||||
var url2 = url_view;
|
||||
@@ -126,6 +127,16 @@ $(document).ready(function() {
|
||||
<tr>
|
||||
<th style="width:180px;"><%= localeMessage.getString("infAdpConMan.adaNm") %></th>
|
||||
<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>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -25,7 +25,7 @@ $(document).ready(function() {
|
||||
datatype:"json",
|
||||
mtype: 'POST',
|
||||
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',
|
||||
'클라이언트명',
|
||||
'<%= localeMessage.getString("infAdpConMan.thrPerSecond") %>',
|
||||
@@ -62,6 +62,7 @@ $(document).ready(function() {
|
||||
url2 += '&menuId='+'${param.menuId}';
|
||||
//검색값
|
||||
url2 += '&searchName='+$("input[name=searchName]").val();
|
||||
url2 += '&searchUseYn='+$("select[name=searchUseYn]").val();
|
||||
//key값
|
||||
url2 += '&name='+name;
|
||||
goNav(url2);
|
||||
@@ -80,7 +81,7 @@ $(document).ready(function() {
|
||||
resizeJqGridWidth('grid','content_middle','1000');
|
||||
|
||||
$("#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(){
|
||||
var url2 = url_view;
|
||||
@@ -90,6 +91,7 @@ $(document).ready(function() {
|
||||
url2 += '&menuId='+'${param.menuId}';
|
||||
//검색값
|
||||
url2 += '&searchName=';
|
||||
url2 += '&searchUseYn='+$('select[name=searchUseYn]').val();
|
||||
|
||||
goNav(url2);
|
||||
});
|
||||
@@ -124,6 +126,16 @@ $(document).ready(function() {
|
||||
<tr>
|
||||
<th style="width:180px;">클라이언트명</th>
|
||||
<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>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -298,6 +298,7 @@ $(document).ready(function() {
|
||||
url: url,
|
||||
data: postData,
|
||||
success: function(args) {
|
||||
console.log('mod', args);
|
||||
showAlert("<%= localeMessage.getString("common.saveMsg") %>", {
|
||||
type: 'success',
|
||||
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-sub" id="summaryThresholdCalc"></span>
|
||||
</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>
|
||||
|
||||
|
||||
@@ -49,6 +49,7 @@ function groupNameFormat(cellvalue, options, rowObject) {
|
||||
|
||||
$(document).ready(function() {
|
||||
$("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(){
|
||||
if ($(this).val() == undefined || $(this).val() == null || $(this).val() == ""){
|
||||
@@ -62,6 +63,18 @@ $(document).ready(function() {
|
||||
}
|
||||
});
|
||||
|
||||
$("input[name=searchStartTime]").each(function(){
|
||||
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 로
|
||||
|
||||
@@ -136,8 +149,10 @@ $(document).ready(function() {
|
||||
|
||||
var start = $("input[name=searchStartYYYYMMDD]").val().replace(/-/gi,"");
|
||||
var end = $("input[name=searchEndYYYYMMDD]").val().replace(/-/gi,"");
|
||||
var startTime = $("input[name=searchStartTime]").val();
|
||||
var endTime = $("input[name=searchEndTime]").val();
|
||||
|
||||
if(start > end){
|
||||
if((start + startTime) > (end + endTime)){
|
||||
alert("조회기간을 확인해주세요.");
|
||||
return false;
|
||||
}
|
||||
@@ -197,14 +212,24 @@ $(document).ready(function() {
|
||||
<tbody>
|
||||
<tr>
|
||||
<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="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="searchEndTime" value="" size="8" style="width:70px; border:1px solid #ebebec;">
|
||||
<input type="hidden" name="searchStartDate" value="" style="width:0px;">
|
||||
<input type="hidden" name="searchEndDate" value="" style="width:0px;">
|
||||
</td>
|
||||
</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>
|
||||
</table>
|
||||
</form>
|
||||
|
||||
@@ -88,7 +88,7 @@ $(document).ready(function() {
|
||||
datatype:"json",
|
||||
mtype: 'POST',
|
||||
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',
|
||||
'그룹명',
|
||||
'초당 임계치',
|
||||
@@ -125,6 +125,7 @@ $(document).ready(function() {
|
||||
url2 += '&menuId='+'${param.menuId}';
|
||||
//검색값
|
||||
url2 += '&searchGroupName='+$("input[name=searchGroupName]").val();
|
||||
url2 += '&searchUseYn='+$("select[name=searchUseYn]").val();
|
||||
//key값
|
||||
url2 += '&groupId='+groupId;
|
||||
goNav(url2);
|
||||
@@ -143,7 +144,7 @@ $(document).ready(function() {
|
||||
resizeJqGridWidth('grid','content_middle','1000');
|
||||
|
||||
$("#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(){
|
||||
var url2 = url_view;
|
||||
@@ -188,6 +189,16 @@ $(document).ready(function() {
|
||||
<tr>
|
||||
<th style="width:180px;">그룹명</th>
|
||||
<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>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -25,7 +25,7 @@ $(document).ready(function() {
|
||||
datatype:"json",
|
||||
mtype: 'POST',
|
||||
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") %>',
|
||||
'<%= localeMessage.getString("infIfConMan.ifDesc") %>',
|
||||
'<%= localeMessage.getString("infAdpConMan.thrPerSecond") %>',
|
||||
@@ -62,6 +62,7 @@ $(document).ready(function() {
|
||||
url2 += '&menuId='+'${param.menuId}';
|
||||
//검색값
|
||||
url2 += '&searchName='+$("input[name=searchName]").val();
|
||||
url2 += '&searchUseYn='+$("select[name=searchUseYn]").val();
|
||||
//key값
|
||||
url2 += '&name='+name;
|
||||
goNav(url2);
|
||||
@@ -80,7 +81,7 @@ $(document).ready(function() {
|
||||
resizeJqGridWidth('grid','content_middle','1000');
|
||||
|
||||
$("#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(){
|
||||
var url2 = url_view;
|
||||
@@ -126,6 +127,16 @@ $(document).ready(function() {
|
||||
<tr>
|
||||
<th style="width:180px;"><%= localeMessage.getString("infIfConMan.ifNm") %></th>
|
||||
<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>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -71,18 +71,18 @@
|
||||
|
||||
$('#grid').children().remove();
|
||||
|
||||
const itemTypes = ['Field', 'Grid', 'Group', 'Attr'];
|
||||
const itemTypes = ['Field', 'Grid', 'Group', 'Attr', 'Array'];
|
||||
|
||||
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("standardLayout.itemLevel")%>', name: 'loutItemDepth', type: 'text', width:50 },
|
||||
{ title: '<%= localeMessage.getString("layoutMan.itmeEn") %>', name: 'loutItemName', type: 'text', width:280, resize:true, minWidth:120 },
|
||||
{ title: '<%=localeMessage.getString("standardLayout.itemLevel")%>', name: 'loutItemDepth', type: 'numeric', width:50 },
|
||||
{ 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.refField") %>', name: 'loutItemOccRef', type: 'text', width:90 },
|
||||
{ title: '<%=localeMessage.getString("layoutMan.dataType")%>', name: 'loutItemDataType', type: 'autocomplete', source: basicDataTypes, width:160 },
|
||||
{ title: '<%=localeMessage.getString("layoutMan.dataLen")%>', name: 'loutItemLength', type: 'numeric', width: 70, align: 'center' },
|
||||
{ title: '<%=localeMessage.getString("layoutMan.decpLen")%>', name: 'loutItemDecimal', type: 'text', width: 70 },
|
||||
{ title: '<%=localeMessage.getString("layoutMan.decpLen")%>', name: 'loutItemDecimal', type: 'numeric', width: 70 },
|
||||
{ title: '<%=localeMessage.getString("layoutMan.defaultVal")%>', name: 'loutItemDefault', type: 'text', width: 70 },
|
||||
{ title: '<%=localeMessage.getString("layoutMan.mask")%>', name: 'loutItemMaskYn', type: 'checkbox', width: 70 },
|
||||
{ title: '<%=localeMessage.getString("layoutMan.moffset")%>', name: 'loutItemMaskOffset', type: 'text', width: 50 },
|
||||
@@ -182,6 +182,41 @@
|
||||
$('#dataTotalLen').text(totalLen);
|
||||
}
|
||||
|
||||
function focusGridCell(colIndex, rowIndex) {
|
||||
var cellName = jexcel.getColumnNameFromId([colIndex, rowIndex]);
|
||||
var cell = grid.getCell(cellName);
|
||||
grid.updateSelection(cell);
|
||||
cell.scrollIntoView({block: 'center', inline: 'center'});
|
||||
grid.openEditor(cell, false);
|
||||
}
|
||||
|
||||
var INTEGER_REGEXP = /^(0|[1-9][0-9]*)$/;
|
||||
|
||||
function validateNumberColumns() {
|
||||
var data = grid.getData();
|
||||
var gridColumns = grid.options.columns;
|
||||
|
||||
for (var col = 0; col < gridColumns.length; col++) {
|
||||
if (gridColumns[col].type !== 'numeric') {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (var row = 0; row < data.length; row++) {
|
||||
var value = data[row][col];
|
||||
if (value == null) {
|
||||
continue;
|
||||
}
|
||||
value = $.trim(String(value));
|
||||
if (value !== '' && !INTEGER_REGEXP.test(value)) {
|
||||
alert("숫자만 입력할 수 있습니다.");
|
||||
focusGridCell(col, row);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function detail(key) {
|
||||
if (!isDetail) {
|
||||
return
|
||||
@@ -321,6 +356,10 @@
|
||||
});
|
||||
|
||||
$("#btn_modify").click(function(){
|
||||
if (!validateNumberColumns()) {
|
||||
return;
|
||||
}
|
||||
|
||||
var postData = $('#ajaxForm').serializeArray();
|
||||
if (isDetail){
|
||||
postData.push({ name: "cmd" , value:"UPDATE"});
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
<%@ page language="java" contentType="text/html; charset=utf-8" %>
|
||||
<%@ page import="java.io.*" %>
|
||||
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
|
||||
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %>
|
||||
<%@ include file="/jsp/common/include/localemessage.jsp" %>
|
||||
<%
|
||||
response.setHeader("Pragma", "No-cache");
|
||||
response.setHeader("Cache-Control", "no-cache");
|
||||
response.setHeader("Expires", "0");
|
||||
%>
|
||||
<html>
|
||||
<head>
|
||||
<title></title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<jsp:include page="/jsp/common/include/css.jsp"/>
|
||||
<jsp:include page="/jsp/common/include/script.jsp"/>
|
||||
<script language="javascript">
|
||||
var url = '<c:url value="/onl/admin/rule/layoutSyncHistoryMan.json" />';
|
||||
var url_view = '<c:url value="/onl/admin/rule/layoutSyncHistoryMan.view" />';
|
||||
|
||||
function formatPrcssRslt(cellvalue, options, rowObject) {
|
||||
if (cellvalue === 'S') return '<span style="color:#1a73e8;font-weight:bold;">성공</span>';
|
||||
if (cellvalue === 'F') return '<span style="color:#d93025;font-weight:bold;">실패</span>';
|
||||
return cellvalue || '';
|
||||
}
|
||||
|
||||
function init() {
|
||||
$("#startDatepicker").datepicker().inputmask("yyyy-mm-dd", {'autoUnmask': true});
|
||||
$("#endDatepicker").datepicker().inputmask("yyyy-mm-dd", {'autoUnmask': true});
|
||||
|
||||
var today = getToday();
|
||||
|
||||
$("input[name=searchStartYYYYMMDD]").val(today.substring(0, 6) + "01");
|
||||
$("input[name=searchEndYYYYMMDD]").val(today);
|
||||
$("input[name=searchStartDate]").val(today.substring(0, 6) + "01");
|
||||
$("input[name=searchEndDate]").val(today);
|
||||
|
||||
// 뒤로가기 등으로 이전 검색조건 파라미터가 있으면 기본값을 덮어씀
|
||||
putSelectFromParam();
|
||||
|
||||
buildGrid();
|
||||
}
|
||||
|
||||
function buildGrid() {
|
||||
$('#grid').jqGrid({
|
||||
datatype: "json",
|
||||
mtype: 'POST',
|
||||
url: url,
|
||||
postData: getSearchForJqgrid("cmd", "LIST"),
|
||||
colNames: ['No', '서비스명', 'Command', '연동구분명', '연동시간', '결과', '결과메세지'],
|
||||
colModel: [
|
||||
{name: 'seqno', align: 'center', width: 50, sortable: false},
|
||||
{name: 'serviceName', align: 'center', width: 100, sortable: false},
|
||||
{name: 'command', align: 'center', width: 80, sortable: false},
|
||||
{name: 'loutName', align: 'left', width: 180, sortable: false},
|
||||
{name: 'recvAmndHms', align: 'center', width: 140, sortable: false},
|
||||
{name: 'prcssRslt', align: 'center', width: 60, sortable: false, formatter: formatPrcssRslt},
|
||||
{name: 'prcssRsltCmnt', align: 'left', width: 300, sortable: false}
|
||||
],
|
||||
jsonReader: {repeatitems: false},
|
||||
pager: $('#pager'),
|
||||
page: '${param.page}',
|
||||
rowNum: '${rmsDefaultRowNum}',
|
||||
autoheight: true,
|
||||
height: $("#container").height(),
|
||||
autowidth: true,
|
||||
viewrecords: true,
|
||||
rowList: eval('[${rmsDefaultRowList}]'),
|
||||
ondblClickRow: function (rowId) {
|
||||
var rowData = $(this).getRowData(rowId);
|
||||
var seqno = rowData['seqno'];
|
||||
var url2 = url_view + '?cmd=DETAIL';
|
||||
url2 += '&page=' + $(this).getGridParam("page");
|
||||
url2 += '&returnUrl=' + getReturnUrl();
|
||||
url2 += '&menuId=' + '${param.menuId}';
|
||||
url2 += '&seqno=' + seqno;
|
||||
url2 += '&' + getSearchUrl();
|
||||
goNav(url2);
|
||||
},
|
||||
loadError: function (jqXHR, textStatus, errorThrown) {
|
||||
var location = '<%=request.getContextPath()%>/';
|
||||
comloadError(jqXHR, textStatus, errorThrown, location);
|
||||
}
|
||||
});
|
||||
resizeJqGridWidth('grid', 'content_middle', '1000');
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
init();
|
||||
|
||||
$("#btn_search").click(function () {
|
||||
var start = $("input[name=searchStartYYYYMMDD]").val().replace(/-/gi, "");
|
||||
var end = $("input[name=searchEndYYYYMMDD]").val().replace(/-/gi, "");
|
||||
|
||||
if (start && end && start > end) {
|
||||
alert("조회기간 종료일은 시작일보다 커야합니다.");
|
||||
$("input[name=searchEndYYYYMMDD]").focus();
|
||||
return false;
|
||||
}
|
||||
$("input[name=searchStartDate]").val(start);
|
||||
$("input[name=searchEndDate]").val(end);
|
||||
|
||||
var postData = getSearchForJqgrid("cmd", "LIST");
|
||||
$("#grid").setGridParam({url: url, postData: postData, page: 1}).trigger("reloadGrid");
|
||||
});
|
||||
|
||||
$("input[name^=search]").keydown(function (key) {
|
||||
if (key.keyCode == 13) {
|
||||
$("#btn_search").click();
|
||||
}
|
||||
});
|
||||
|
||||
buttonControl();
|
||||
});
|
||||
</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" id="content_middle">
|
||||
<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>
|
||||
</div>
|
||||
<div class="title">연동 배포 이력<span class="tooltip">IIM 레이아웃 연동 이력과 eCams 인터페이스 배포 이력을 조회합니다.</span></div>
|
||||
<form id="ajaxForm" onsubmit="return false;">
|
||||
<table class="search_condition" cellspacing=0;>
|
||||
<colgroup>
|
||||
<col style="width:120px;"><col style="width:260px;">
|
||||
<col style="width:120px;"><col style="width:260px;">
|
||||
<col style="width:120px;"><col style="width:260px;">
|
||||
</colgroup>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>연동시간</th>
|
||||
<td>
|
||||
<input type="text" name="searchStartYYYYMMDD" id="startDatepicker" readonly="readonly" value="" size="10" style="width:100px; border:1px solid #ebebec;">
|
||||
~
|
||||
<input type="text" name="searchEndYYYYMMDD" value="" id="endDatepicker" size="10" readonly="readonly" style="width:100px; border:1px solid #ebebec;">
|
||||
<input type="hidden" name="searchStartDate" value="">
|
||||
<input type="hidden" name="searchEndDate" value="">
|
||||
</th>
|
||||
<th>서비스명</th>
|
||||
<td><input type="text" name="searchServiceName" autocomplete="off" style="width:95%;"></td>
|
||||
<th>Command</th>
|
||||
<td><input type="text" name="searchCommand" autocomplete="off" style="width:95%;"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>연동구분명</th>
|
||||
<td><input type="text" name="searchLoutName" autocomplete="off" style="width:95%;"></td>
|
||||
<th>결과</th>
|
||||
<td>
|
||||
<div class="select-style" style="display:inline-block; margin-left:6px;">
|
||||
<select name="searchPrcssRslt">
|
||||
<option value="">전체</option>
|
||||
<option value="S">성공</option>
|
||||
<option value="F">실패</option>
|
||||
</select>
|
||||
</div>
|
||||
</td>
|
||||
<th></th>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</form>
|
||||
<table id="grid"></table>
|
||||
<div id="pager"></div>
|
||||
</div><!-- end content_middle -->
|
||||
</div><!-- end right_box -->
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,141 @@
|
||||
<%@ page language="java" contentType="text/html; charset=utf-8" %>
|
||||
<%@ page import="java.io.*" %>
|
||||
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
|
||||
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %>
|
||||
<%@ include file="/jsp/common/include/localemessage.jsp" %>
|
||||
<%
|
||||
response.setHeader("Pragma", "No-cache");
|
||||
response.setHeader("Cache-Control", "no-cache");
|
||||
response.setHeader("Expires", "0");
|
||||
%>
|
||||
<html>
|
||||
<head>
|
||||
<title></title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<jsp:include page="/jsp/common/include/css.jsp"/>
|
||||
<jsp:include page="/jsp/common/include/script.jsp"/>
|
||||
<style>
|
||||
.succ-y { color:#1a73e8; font-weight:bold; }
|
||||
.succ-n { color:#d93025; font-weight:bold; }
|
||||
.pre-wrap { white-space:pre-wrap; word-break:break-all; height:500px; overflow: auto; line-height:1.5; }
|
||||
.recvdata-toolbar { text-align:right; margin-top: 5px; margin-bottom:6px; }
|
||||
</style>
|
||||
<script language="javascript">
|
||||
var url = '<c:url value="/onl/admin/rule/layoutSyncHistoryMan.json" />';
|
||||
var key = '${param.seqno}';
|
||||
var rawRecvData = '';
|
||||
var isRecvDataPretty = false;
|
||||
|
||||
function detail() {
|
||||
if (!key) return;
|
||||
$.ajax({
|
||||
type: "POST", url: url, dataType: "json",
|
||||
data: {cmd: 'DETAIL', seqno: key},
|
||||
success: function (data) {
|
||||
$('#seqno').text(data.seqno != null ? data.seqno : '');
|
||||
$('#serviceName').text(data.serviceName || '');
|
||||
$('#command').text(data.command || '');
|
||||
$('#loutName').text(data.loutName || '');
|
||||
$('#recvAmndHms').text(data.recvAmndHms || '');
|
||||
$('#prcssRsltCmnt').text(data.prcssRsltCmnt || '');
|
||||
|
||||
rawRecvData = data.recvData || '';
|
||||
isRecvDataPretty = false;
|
||||
$('#recvData').text(rawRecvData);
|
||||
|
||||
if (data.prcssRslt === 'S') {
|
||||
$('#prcssRslt').html('<span class="succ-y">성공</span>');
|
||||
} else if (data.prcssRslt === 'F') {
|
||||
$('#prcssRslt').html('<span class="succ-n">실패</span>');
|
||||
} else {
|
||||
$('#prcssRslt').text(data.prcssRslt || '');
|
||||
}
|
||||
},
|
||||
error: function (e) { alert(e.responseText); }
|
||||
});
|
||||
}
|
||||
|
||||
// JSON 지수표기(예: 1e+21) 값이 vkbeautify 처리 중 깨지지 않도록 문자열로 감쌌다가 되돌린다.
|
||||
function preserveExponential(jsonString) {
|
||||
var protectedJson = jsonString.replace(/:\s*([+-]?\d+\.?\d*e[+-]?\d+)/gi, ': "$1"');
|
||||
var prettyJson = vkbeautify.json(protectedJson);
|
||||
prettyJson = prettyJson.replace(/:\s*"([+-]?\d+\.?\d*e[+-]?\d+)"/gi, ': $1');
|
||||
return prettyJson;
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
var returnUrl = getReturnUrlForReturn();
|
||||
|
||||
buttonControl();
|
||||
detail();
|
||||
|
||||
$('#btn_previous').click(function () { goNav(returnUrl); });
|
||||
|
||||
$('#btn_pretty_recvdata').click(function () {
|
||||
if (!rawRecvData) return;
|
||||
if (isRecvDataPretty) {
|
||||
$('#recvData').text(rawRecvData);
|
||||
isRecvDataPretty = false;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
$('#recvData').text(preserveExponential(rawRecvData));
|
||||
isRecvDataPretty = true;
|
||||
} catch (e) {
|
||||
alert('JSON 형식이 아니거나 정렬할 수 없습니다.');
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="right_box">
|
||||
<div class="content_top">
|
||||
<ul class="path"><li><a href="#">${rmsMenuPath}</a></li></ul>
|
||||
</div>
|
||||
<div class="content_middle">
|
||||
<div class="search_wrap">
|
||||
<button type="button" class="cssbtn" id="btn_previous" level="R" status="DETAIL,NEW"><i class="material-icons">arrow_back</i> <%= localeMessage.getString("button.previous") %></button>
|
||||
</div>
|
||||
<div class="title" id="title">연동 배포 이력 상세</div>
|
||||
<table class="table_row" cellspacing="0">
|
||||
<colgroup>
|
||||
<col style="width:12%"/><col style="width:38%"/>
|
||||
<col style="width:12%"/><col style="width:38%"/>
|
||||
</colgroup>
|
||||
<tr>
|
||||
<th>No</th>
|
||||
<td><span id="seqno"></span></td>
|
||||
<th>Service Name</th>
|
||||
<td><span id="serviceName"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>레이아웃명</th>
|
||||
<td><span id="loutName"></span></td>
|
||||
<th>Command</th>
|
||||
<td><span id="command"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>연동시간</th>
|
||||
<td><span id="recvAmndHms"></span></td>
|
||||
<th>결과</th>
|
||||
<td><span id="prcssRslt"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>결과메세지</th>
|
||||
<td colspan="3"><span id="prcssRsltCmnt"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>수신데이타</th>
|
||||
<td colspan="3">
|
||||
<div class="recvdata-toolbar">
|
||||
<button type="button" class="cssbtn" id="btn_pretty_recvdata"><i class="material-icons">format_align_left</i> 정렬</button>
|
||||
</div>
|
||||
<div id="recvData" class="pre-wrap"></div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -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>
|
||||
|
||||
@@ -45,6 +45,9 @@
|
||||
background-color: #EDD5DD
|
||||
}
|
||||
|
||||
.btn_move { width:32px; min-width: 32px; height: 32px; background-color: #fff; border:1px solid #999; border-radius:4px; margin-bottom: 4px; }
|
||||
|
||||
|
||||
</style>
|
||||
<jsp:include page="/jsp/common/include/script.jsp"/>
|
||||
<script language="javascript" src="<c:url value="/js/jsplumb.js"/>"></script>
|
||||
@@ -54,6 +57,7 @@
|
||||
const LAYOUT_ITEM_TYPE_GROUP = "Group";
|
||||
const LAYOUT_ITEM_TYPE_GRID = "Grid";
|
||||
const LAYOUT_ITEM_TYPE_FIELD = "Field";
|
||||
const LAYOUT_ITEM_TYPE_ARRAY = "Array";
|
||||
|
||||
var isDetail = false;
|
||||
|
||||
@@ -550,6 +554,14 @@
|
||||
return data;
|
||||
}
|
||||
|
||||
// 항목 자신이 Array인 경우, 경로/변환명령 끝에 반복 표시 [*]를 추가
|
||||
function addArrayTag(loutItemType, data) {
|
||||
if (loutItemType == LAYOUT_ITEM_TYPE_ARRAY && !data.endsWith("[*]")) {
|
||||
data = data + "[*]";
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
//변환명에 있는 서비스코드와 if서비스 코드 매칭
|
||||
function validation(){
|
||||
if ($('input[name=cnvsnName]').val() == '') {
|
||||
@@ -879,7 +891,7 @@
|
||||
}
|
||||
});
|
||||
|
||||
$("input[name=auto]").click(function(){
|
||||
$("#auto").click(function(){
|
||||
//debugger;
|
||||
jsPlumbInstance.deleteEveryConnection();
|
||||
|
||||
@@ -889,10 +901,10 @@
|
||||
let selectValue = "";
|
||||
let targetValue = "";
|
||||
for ( var i = 0; i < sourceData.length; i++) {
|
||||
if (sourceData[i]['loutItemType'] != "Field" && sourceData[i]['loutItemType'] != "Attr") continue;
|
||||
if (sourceData[i]['loutItemType'] != "Field" && sourceData[i]['loutItemType'] != "Attr" && sourceData[i]['loutItemType'] != "Array") continue;
|
||||
selectValue = (sourceData[i]['LOUTITEMPATH']).substr(sourceData[i]['loutName'].length+1);
|
||||
for (var j = 0; j <targetData.length; j++) {
|
||||
if (targetData[j]['loutItemType'] != "Field" && targetData[j]['loutItemType'] != "Attr") continue;
|
||||
if (targetData[j]['loutItemType'] != "Field" && targetData[j]['loutItemType'] != "Attr" && targetData[j]['loutItemType'] != "Array") continue;
|
||||
targetValue = (targetData[j]['LOUTITEMPATH']).substr(targetData[j]['loutName'].length+1);
|
||||
if (targetValue == selectValue ) {
|
||||
var data = sourceData[i]['LOUTITEMPATH'];
|
||||
@@ -906,7 +918,7 @@
|
||||
}
|
||||
});
|
||||
|
||||
$("input[name=addAll]").click(function() {
|
||||
$("#addAll").click(function() {
|
||||
console.debug('addAll');
|
||||
var srcRowId = sourceGrid.getGridParam( "selrow" );
|
||||
var tgtRowId = targetGrid.getGridParam( "selrow" );
|
||||
@@ -916,8 +928,8 @@
|
||||
//field, ATTR 여부 판단
|
||||
var sourceRow = sourceGrid.getRowData( srcRowId );
|
||||
var targetRow = targetGrid.getRowData( tgtRowId );
|
||||
if ( (sourceRow["loutItemType"] == 'Field' || sourceRow["loutItemType"] == 'Attr') &&
|
||||
(targetRow["loutItemType"] == 'Field' || targetRow["loutItemType"] == 'Attr') ){
|
||||
if ( (sourceRow["loutItemType"] == 'Field' || sourceRow["loutItemType"] == 'Attr' || sourceRow["loutItemType"] == 'Array') &&
|
||||
(targetRow["loutItemType"] == 'Field' || targetRow["loutItemType"] == 'Attr' || targetRow["loutItemType"] == 'Array') ){
|
||||
;
|
||||
}else{
|
||||
alert('<%=localeMessage.getString("transformManDetail.alert3")%>');
|
||||
@@ -936,15 +948,15 @@
|
||||
var targetValue = "";
|
||||
var j=tgtIndex-1-1;
|
||||
for ( var i = srcIndex-1; i < sourceData.length; i++) {
|
||||
if (sourceData[i]['loutItemType'] != LAYOUT_ITEM_TYPE_FIELD) continue;
|
||||
if (sourceData[i]['loutItemType'] != LAYOUT_ITEM_TYPE_FIELD && sourceData[i]['loutItemType'] != LAYOUT_ITEM_TYPE_ARRAY) continue;
|
||||
j++;
|
||||
while (targetData.length -1 >= j &&targetData[j]["loutItemType"] != LAYOUT_ITEM_TYPE_FIELD){
|
||||
while (targetData.length -1 >= j && targetData[j]["loutItemType"] != LAYOUT_ITEM_TYPE_FIELD && targetData[j]["loutItemType"] != LAYOUT_ITEM_TYPE_ARRAY){
|
||||
j++;
|
||||
}
|
||||
if (targetData.length -1 < j) break;
|
||||
var targetRowId = targetData[j]['id'];
|
||||
targetGrid.jqGrid('setCell',targetRowId,'CNVSNCMDNAME',addGroupTag(srcGroup,sourceData[i]['LOUTITEMPATH']));
|
||||
targetGrid.jqGrid('setCell',targetRowId,'CNVSNRSULTITEMPATHNAME',addGroupTag(tgtGroup,targetData[j]['LOUTITEMPATH']));
|
||||
targetGrid.jqGrid('setCell',targetRowId,'CNVSNCMDNAME',addArrayTag(sourceData[i]['loutItemType'], addGroupTag(srcGroup,sourceData[i]['LOUTITEMPATH'])));
|
||||
targetGrid.jqGrid('setCell',targetRowId,'CNVSNRSULTITEMPATHNAME',addArrayTag(targetData[j]['loutItemType'], addGroupTag(tgtGroup,targetData[j]['LOUTITEMPATH'])));
|
||||
targetGrid.jqGrid('setCell',targetRowId,'CNVSNITEMSERNO',targetData[j]['loutItemSerno']);
|
||||
|
||||
const sourceEndpointElementId = sourceGrid.getEndpointElementId(sourceData[i]['id']);
|
||||
@@ -957,7 +969,7 @@
|
||||
}
|
||||
});
|
||||
|
||||
$("input[name=removeAll]").click(function(){
|
||||
$("#removeAll").click(function(){
|
||||
var tgtRowId = targetGrid.getGridParam( "selrow" );
|
||||
if (tgtRowId == null) {alert("타겟레이아웃을 선택하여 주십시요.");return;}
|
||||
//grid data
|
||||
@@ -977,6 +989,49 @@
|
||||
}
|
||||
});
|
||||
|
||||
$("#add").click(function() {
|
||||
|
||||
var srcRowId = sourceGrid.getGridParam( "selrow" );
|
||||
var tgtRowId = targetGrid.getGridParam( "selrow" );
|
||||
if (srcRowId == null) {alert('<%=localeMessage.getString("transformManDetail.alert1")%>');return;}
|
||||
if (tgtRowId == null) {alert('<%=localeMessage.getString("transformManDetail.alert2")%>');return;}
|
||||
|
||||
//field, ATTR 여부 판단
|
||||
var sourceRow = sourceGrid.getRowData( srcRowId );
|
||||
var targetRow = targetGrid.getRowData( tgtRowId );
|
||||
if ( (sourceRow["loutItemType"] == 'Field' || sourceRow["loutItemType"] == 'Attr' || sourceRow["loutItemType"] == 'Array') &&
|
||||
(targetRow["loutItemType"] == 'Field' || targetRow["loutItemType"] == 'Attr' || targetRow["loutItemType"] == 'Array') ){
|
||||
;
|
||||
}else{
|
||||
alert('<%=localeMessage.getString("transformManDetail.alert3")%>');
|
||||
return ;
|
||||
}
|
||||
|
||||
targetGrid.jqGrid('setCell',tgtRowId,'CNVSNCMDNAME',addArrayTag(sourceRow['loutItemType'], addGroupTag(srcGroup,sourceRow['LOUTITEMPATH'])));
|
||||
targetGrid.jqGrid('setCell',tgtRowId,'CNVSNRSULTITEMPATHNAME',addArrayTag(targetRow['loutItemType'], addGroupTag(tgtGroup,targetRow['LOUTITEMPATH'])));
|
||||
targetGrid.jqGrid('setCell',tgtRowId,'CNVSNITEMSERNO',targetRow['loutItemSerno']);
|
||||
|
||||
try {
|
||||
jsPlumbConnect(srcRowId, tgtRowId);
|
||||
} catch (e) {console.error(e);}
|
||||
});
|
||||
|
||||
$("#remove").click(function(){
|
||||
var tgtRowId = targetGrid.getGridParam( "selrow" );
|
||||
if (tgtRowId == null) {alert("타겟레이아웃을 선택하여 주십시요.");return;}
|
||||
//grid data
|
||||
var targetData = targetGrid.getRowData();
|
||||
|
||||
targetGrid.jqGrid('setCell', tgtRowId, 'CNVSNCMDNAME', null);
|
||||
targetGrid.jqGrid('setCell', tgtRowId, 'CNVSNRSULTITEMPATHNAME', null);
|
||||
targetGrid.jqGrid('setCell', tgtRowId, 'CNVSNITEMSERNO', null);
|
||||
targetGrid.jqGrid('setCell', tgtRowId, 'CNVSNITEMBASCVAL', null);
|
||||
|
||||
const targetEndpointElementId = targetGrid.getEndpointElementId(tgtRowId);
|
||||
jsPlumbInstance.deleteConnectionsForElement(document.getElementById(targetEndpointElementId));
|
||||
|
||||
});
|
||||
|
||||
$("textarea[name=CNVSNRSULTITEMPATHNAME],textarea[name=CNVSNCMDNAME],input[name=CNVSNITEMBASCVAL],input[name=CNVSNITEMSERNO]").blur(function(){
|
||||
var rowId = targetGrid.getGridParam( "selrow" );
|
||||
if (isNull(rowId)) return;
|
||||
@@ -1015,7 +1070,7 @@
|
||||
var targetData = targetGrid.getRowData();
|
||||
var gridData = new Array();
|
||||
for (var i = 0; i <targetData.length; i++) {
|
||||
if ((targetData[i]['loutItemType'] != "Field") && (targetData[i]['loutItemType'] != "Attr")) {
|
||||
if ((targetData[i]['loutItemType'] != "Field") && (targetData[i]['loutItemType'] != "Attr") && (targetData[i]['loutItemType'] != "Array")) {
|
||||
continue;
|
||||
}
|
||||
if (targetData[i]['CNVSNCMDNAME']==null
|
||||
@@ -1158,7 +1213,7 @@
|
||||
function srcformatterFunction(cellvalue,options,rowObject){
|
||||
var rowId = options["rowId"];
|
||||
var loutItemType = rowObject["loutItemType"];
|
||||
if (loutItemType == 'Field' || loutItemType == 'Attr'){
|
||||
if (loutItemType == 'Field' || loutItemType == 'Attr' || loutItemType == 'Array'){
|
||||
return "<div id='div_src_"+rowId+"' name='src_"+rowId+"' style='width:28px;height:17px;background-color: #D12F35; border-color: #ab1717; border-style: solid; border-width: 1px; border-radius: 3px;' />";
|
||||
}else{
|
||||
return "<div id='div_srcgroup_"+rowId+"' name='srcgroup_"+rowId+"' style='width:28px;height:17px;' />";
|
||||
@@ -1170,7 +1225,7 @@
|
||||
function tgtformatterFunction(cellvalue,options,rowObject){
|
||||
var rowId = options["rowId"];
|
||||
var loutItemType = rowObject["loutItemType"];
|
||||
if (loutItemType == 'Field' || loutItemType == 'Attr'){
|
||||
if (loutItemType == 'Field' || loutItemType == 'Attr' || loutItemType == 'Array'){
|
||||
return "<div id='div_tgt_"+rowId+"_base' name='div_tgt_"+rowId+"_base' style='width:30px;height:20px; background-color:gray;' >"
|
||||
+ "<div id='div_tgt_"+rowId+"' name='tgt_"+rowId+"' style='width:28px;height:17px; background-color: #3E7E9C; border-color: #217ca7; border-style: solid; border-width: 1px; border-radius: 3px;' onclick='jqGridOnTargetGridEndpointClick(this)'>"
|
||||
+ "</div></div>";
|
||||
@@ -1669,8 +1724,8 @@
|
||||
return;
|
||||
}
|
||||
|
||||
if ((sourceRow["loutItemType"] != 'Field' && sourceRow["loutItemType"] != 'Attr')
|
||||
|| (targetRow["loutItemType"] != 'Field' && targetRow["loutItemType"] != 'Attr')) {
|
||||
if ((sourceRow["loutItemType"] != 'Field' && sourceRow["loutItemType"] != 'Attr' && sourceRow["loutItemType"] != 'Array')
|
||||
|| (targetRow["loutItemType"] != 'Field' && targetRow["loutItemType"] != 'Attr' && targetRow["loutItemType"] != 'Array')) {
|
||||
alert('<%=localeMessage.getString("transformManDetail.alert3")%>');
|
||||
return ;
|
||||
}
|
||||
@@ -1684,7 +1739,7 @@
|
||||
|
||||
if (targetRow['CNVSNCMDNAME'] == '') {
|
||||
// 변환 명령 값이 한번 설정 되면 변경 되지 않게 함.
|
||||
var cnvsnCmdName = addGroupTag(srcGroup,sourceRow["LOUTITEMPATH"]);
|
||||
var cnvsnCmdName = addArrayTag(sourceRow["loutItemType"], addGroupTag(srcGroup,sourceRow["LOUTITEMPATH"]));
|
||||
// function까지 처리되도록 수정했으나, parameter가 하나인 경우에만 정상처리됨
|
||||
if($('#functionCombo').val() != "") {
|
||||
cnvsnCmdName = $('#functionCombo').val() + "(" + cnvsnCmdName + ")";
|
||||
@@ -1693,7 +1748,7 @@
|
||||
targetGrid.jqGrid('setCell',tgtRowId,'CNVSNCMDNAME', cnvsnCmdName);
|
||||
}
|
||||
|
||||
targetGrid.jqGrid('setCell',tgtRowId,'CNVSNRSULTITEMPATHNAME',addGroupTag(tgtGroup,targetRow["LOUTITEMPATH"]));
|
||||
targetGrid.jqGrid('setCell',tgtRowId,'CNVSNRSULTITEMPATHNAME',addArrayTag(targetRow["loutItemType"], addGroupTag(tgtGroup,targetRow["LOUTITEMPATH"])));
|
||||
targetGrid.jqGrid('setCell',tgtRowId,'CNVSNITEMSERNO',targetRow['loutItemSerno']);
|
||||
targetGrid.jqGrid('setCell',tgtRowId,'CNVSNSRCITEMS', cnvsnSrcItems);
|
||||
targetGrid.jqGrid('setCell',tgtRowId,'ISVALIDMAPPING', true);
|
||||
@@ -1885,8 +1940,8 @@
|
||||
if (loutItemType == LAYOUT_ITEM_TYPE_GRID || loutItemType == LAYOUT_ITEM_TYPE_GROUP) {
|
||||
// group 인 경우
|
||||
items = createGridGroupContextMenuItems(grid, rowId);
|
||||
} else if(loutItemType == LAYOUT_ITEM_TYPE_FIELD) {
|
||||
// field 인 경우
|
||||
} else if(loutItemType == LAYOUT_ITEM_TYPE_FIELD || loutItemType == LAYOUT_ITEM_TYPE_ARRAY) {
|
||||
// field, array 인 경우
|
||||
items = createFieldContextMenuItems(grid, positionElement.attr('id'));
|
||||
} else {
|
||||
return false;
|
||||
@@ -1938,8 +1993,8 @@
|
||||
var key = "";
|
||||
var args = new Object();
|
||||
args['eaiSvcName'] = $('input[name=eaiSvcName]').val();
|
||||
var url='<c:url value="/onl/transaction/extnl/interfaceMan.view"/>';
|
||||
url = url + "?cmd=POPUP";
|
||||
var url=url_view; //'<c:url value="/onl/transaction/extnl/interfaceMan.view"/>';
|
||||
url = url + "?cmd=API_POPUP";
|
||||
var ret = showModal(url,args,1020,630, function(arg){
|
||||
var args = null;
|
||||
if(arg == null || arg == undefined ) {//chrome
|
||||
@@ -1953,11 +2008,11 @@
|
||||
|
||||
var ret = args.returnValue;
|
||||
console.log("ret",ret);
|
||||
key = ret['key'];
|
||||
key = ret['apiId'];
|
||||
|
||||
$("input[name=eaiSvcName]").val(key);
|
||||
$("input[name=eaiSvcDesc]").val(ret['eaiSvcDesc']);
|
||||
bzwkDstCd = ret['eaiBzwkDstCd'];
|
||||
$("input[name=eaiSvcDesc]").val(ret['apiDesc']);
|
||||
bzwkDstCd = ret['bizCode'];
|
||||
|
||||
$("input[name=eaiSvcName]").change();
|
||||
});
|
||||
@@ -1989,7 +2044,7 @@
|
||||
break;
|
||||
}
|
||||
|
||||
if (loutItemType == LAYOUT_ITEM_TYPE_FIELD) {
|
||||
if (loutItemType == LAYOUT_ITEM_TYPE_FIELD || loutItemType == LAYOUT_ITEM_TYPE_ARRAY) {
|
||||
loutItemPath = loutItemPath.replace(new RegExp("^" + prefix), "");
|
||||
console.debug(grid.getId() + " : loutItemPath : " + loutItemPath);
|
||||
rowData['GROUP_LOUTITEMPATH'] = loutItemPath;
|
||||
@@ -2152,12 +2207,12 @@
|
||||
<div id="sourcePage"></div>
|
||||
</div>
|
||||
</td>
|
||||
<td style="width:30%; text-align:center; vertical-align: top;">
|
||||
|
||||
<br /><!-- 이 br 이 없을 때 버튼이 제대로 동작 하지 않는다. -->
|
||||
<input type=button id="removeAll" name="removeAll" class="btn_img btn_removeAll"/><br />
|
||||
<input type=button id="addAll" name="addAll" class="btn_img btn_addAll"/><br />
|
||||
<input type=button id="auto" name="auto" class="btn_img btn_auto" />
|
||||
<td style="width:30%; min-width:70px; text-align:center; vertical-align: top; ">
|
||||
<button type=button id="remove" name="remove" class="cssbtn btn_move" title="remove"><</button>
|
||||
<button type=button id="add" name="add" class="cssbtn btn_move" title="add"> > </button><br />
|
||||
<button type=button id="removeAll" name="removeAll" class="cssbtn btn_move" title="remove all"> << </button>
|
||||
<button type=button id="addAll" name="addAll" class="cssbtn btn_move" title="add all"> >> </button><br />
|
||||
<button type=button id="auto" name="auto" class="cssbtn btn_move" style="width: 70px; " title="auto"> Auto </button>
|
||||
</td>
|
||||
<td style="vertical-align:top;">
|
||||
<table class="table_row" cellspacing="0">
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
<jsp:include page="/jsp/common/include/script.jsp"/>
|
||||
|
||||
<script language="javascript" >
|
||||
var url_view = '<c:url value="/onl/admin/rule/transform2/transform2Man.view" />';
|
||||
var $ = jQuery.noConflict();
|
||||
var bzwkDstCd = "";
|
||||
|
||||
@@ -81,8 +82,9 @@ $(document).ready(function() {
|
||||
var key = "";
|
||||
var args = new Object();
|
||||
args['eaiSvcName'] = $('input[name=eaiSvcName]').val();
|
||||
var url='<c:url value="/onl/transaction/extnl/interfaceMan.view"/>';
|
||||
url = url + "?cmd=POPUP";
|
||||
var url=url_view; //'<c:url value="/onl/transaction/extnl/interfaceMan.view"/>';
|
||||
url = url + "?cmd=API_POPUP";
|
||||
console.log('[new]', url);
|
||||
var ret = showModal(url,args,1020,630, function(arg){
|
||||
var args = null;
|
||||
if(arg == null || arg == undefined ) {//chrome
|
||||
@@ -96,11 +98,11 @@ $(document).ready(function() {
|
||||
|
||||
var ret = args.returnValue;
|
||||
console.log("ret",ret);
|
||||
key = ret['key'];
|
||||
key = ret['apiId'];
|
||||
|
||||
$("input[name=eaiSvcName]").val(key);
|
||||
$("input[name=eaiSvcDesc]").val(ret['eaiSvcDesc']);
|
||||
bzwkDstCd = ret['eaiBzwkDstCd'];
|
||||
$("input[name=eaiSvcDesc]").val(ret['apiDesc']);
|
||||
bzwkDstCd = ret['bizCode'];
|
||||
|
||||
$("input[name=eaiSvcName]").change();
|
||||
//$("select[name=eaiSevrDstcd]").val(ret['eaiSevrDstcd']);
|
||||
|
||||
@@ -4,11 +4,18 @@
|
||||
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
|
||||
<%@ include file="/jsp/common/include/localemessage.jsp" %>
|
||||
<%@ page import="com.eactive.eai.common.util.SystemUtil"%>
|
||||
<%@ page import="com.eactive.eai.rms.common.context.MonitoringContext"%>
|
||||
<%@ page import="com.eactive.eai.rms.common.util.CommonUtil"%>
|
||||
<%
|
||||
response.setHeader("Pragma", "No-cache");
|
||||
response.setHeader("Cache-Control", "no-cache");
|
||||
response.setHeader("Expires", "0");
|
||||
|
||||
MonitoringContext monitoringContext = (MonitoringContext)CommonUtil.getBean(request, "monitoringContext");
|
||||
String deployYnStr = monitoringContext.getStringProperty(MonitoringContext.IOMAP_DEPLOY_YN_BY_RMS, "N");
|
||||
boolean deployYn = "Y".equalsIgnoreCase(deployYnStr);
|
||||
String adminOnlyYnStr = monitoringContext.getStringProperty(MonitoringContext.IOMAP_DEPLOY_YN_BY_ADMIN_ONLY, "Y");
|
||||
boolean adminOnly = "Y".equalsIgnoreCase(adminOnlyYnStr);
|
||||
%>
|
||||
<html>
|
||||
<head>
|
||||
@@ -17,62 +24,48 @@
|
||||
|
||||
<jsp:include page="/jsp/common/include/css.jsp"/>
|
||||
<jsp:include page="/jsp/common/include/script.jsp"/>
|
||||
<script language="javascript" >
|
||||
<script>
|
||||
var url = '<c:url value="/onl/admin/rule/transformMan.json" />';
|
||||
var url_view = '<c:url value="/onl/admin/rule/transformMan.view" />';
|
||||
var url_excel= '<c:url value="/onl/admin/rule/transformMan.excel"/>';
|
||||
var roleString = "<%=SessionManager.getRoleIdString(request)%>";
|
||||
var devSvr = <%=SystemUtil.isDevServer()%>;
|
||||
|
||||
function dateFormater(cellvalue, options, rowObject){
|
||||
if (cellvalue==null|| cellvalue==undefined) return "";
|
||||
if (cellvalue.trim().length < 14) return "";
|
||||
var display = "";
|
||||
display=display+cellvalue.substr(0,4)+"-";
|
||||
display=display+cellvalue.substr(4,2)+"-";
|
||||
display=display+cellvalue.substr(6,2)+" ";
|
||||
|
||||
display=display+cellvalue.substr(8,2)+":";
|
||||
display=display+cellvalue.substr(10,2)+":";
|
||||
display=display+cellvalue.substr(12,2);
|
||||
|
||||
return display;
|
||||
}
|
||||
|
||||
var stageSvr = <%=SystemUtil.isStagingServer()%>;
|
||||
var adminOnly = <%=adminOnly%>;
|
||||
|
||||
$(document).ready(function() {
|
||||
|
||||
var postData = getSearchForJqgrid("cmd","LIST"); //jqgrid에서는 object 로
|
||||
|
||||
$('#grid').jqGrid({
|
||||
datatype:"json",
|
||||
mtype: 'POST',
|
||||
url: url,
|
||||
postData : { cmd : 'LIST'
|
||||
, searchBizCode : $('input[name=searchBizCode]').val()
|
||||
, searchCnvsnName : $('input[name=searchCnvsnName]').val()
|
||||
, searchCnvsnDesc : $('input[name=searchCnvsnDesc]').val()
|
||||
|
||||
},
|
||||
postData : postData,
|
||||
colNames:[
|
||||
'<%=localeMessage.getString("transformMan.eaibzwkdstcd")%>',
|
||||
'<%=localeMessage.getString("transformMan.telCngMappCd")%>',
|
||||
'<%=localeMessage.getString("transformMan.telCngMappDesc")%>',
|
||||
'<%=localeMessage.getString("transformMan.ifSvcCd")%>',
|
||||
'<%=localeMessage.getString("transformMan.ifSvcDesc")%>',
|
||||
'<%=localeMessage.getString("transformMan.eaiSvrDiv")%>',
|
||||
'<%= localeMessage.getString("combo.useYn")%> ',
|
||||
'<%=localeMessage.getString("transformMan.modifyDate")%>',
|
||||
'<%=localeMessage.getString("transformMan.verInfo")%>',
|
||||
'타입',
|
||||
'cnvsnType',
|
||||
'flag'
|
||||
],
|
||||
colModel:[
|
||||
{ name : 'EAIBZWKDSTCD' , align:'center' ,sortable:false , width : "40"},
|
||||
{ name : 'CNVSNNAME' , align:'left' },
|
||||
{ name : 'EAISVCNAME' , align:'left' },
|
||||
{ name : 'EAISVCDESC' , align:'left' },
|
||||
{ name : 'EAIBZWKDSTCD' , align:'center' ,sortable:false , width:"30"},
|
||||
{ name : 'CNVSNNAME' , align:'left' , width:"100" },
|
||||
{ name : 'CNVSNDESC' , align:'left' , width:"100" },
|
||||
{ name : 'EAISVCNAME' , align:'left' , width:"70" },
|
||||
{ name : 'EAISVCDESC' , align:'left' , width:"100"},
|
||||
{ name : 'EAISEVRDSTCD' , hidden : true },
|
||||
{ name : 'USEYN', align:'center', width:30,editoptions:{value:"0:<%= localeMessage.getString("combo.usen")%>;1:<%= localeMessage.getString("combo.usey")%>"}, formatter:"select"},
|
||||
{ name : 'LASTAMNDHMS' , align:'center' , formatter: dateFormater, width : "70" },
|
||||
{ name : 'VERINFO' , align:'center' , width : "70" },
|
||||
{ name : 'CNVSNTYPE' , hidden : true },
|
||||
{ name : 'USEYN', align:'center', width:30 ,editoptions:{value:"0:<%= localeMessage.getString("combo.usen")%>;1:<%= localeMessage.getString("combo.usey")%>"}, formatter:"select"},
|
||||
{ name : 'LASTAMNDHMS' , align:'center' , width : "60" },
|
||||
{ name : 'VERINFO' , align:'center' , width : "30" },
|
||||
{ name : 'CNVSNTYPE' , hidden : true },
|
||||
{ name : 'FLAG' , hidden : true }
|
||||
],
|
||||
jsonReader: {
|
||||
@@ -136,7 +129,8 @@ $(document).ready(function() {
|
||||
//key값
|
||||
url2 += '&cnvsnName='+cnvsnName;
|
||||
url2 += '&eaiSvcName='+eaiSvcName;
|
||||
url2 += '&eaiSvcDesc='+eaiSvcDesc;
|
||||
//url2 += '&eaiSvcDesc='+eaiSvcDesc;
|
||||
url2 += '&eaiSvcDesc='+encodeURIComponent(eaiSvcDesc);
|
||||
url2 += '&cnvsnDesc='+cnvsnDesc;
|
||||
url2 += '&eaiSevrDStcd='+eaiSevrDStcd;
|
||||
url2 += '&useYn='+useYn;
|
||||
@@ -161,7 +155,6 @@ $(document).ready(function() {
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
resizeJqGridWidth('grid','content_middle','1000');
|
||||
|
||||
$("#btn_search").click(function(){
|
||||
@@ -217,7 +210,7 @@ $(document).ready(function() {
|
||||
}
|
||||
return false;
|
||||
}else{
|
||||
if(confirm("초기화 하시겠습니까?")){
|
||||
if(confirm("<%=localeMessage.getString("eaiMessage.confirm2")%>")){
|
||||
var postData = [];
|
||||
postData.push({ name: "cmd" , value:"LIST_INITIALIZE"});
|
||||
postData.push({ name: "chkItems" , value : chkArr});
|
||||
@@ -242,8 +235,13 @@ $(document).ready(function() {
|
||||
});
|
||||
|
||||
$('#btn_cmsave').click(onclickBtnCmSave);
|
||||
$('#btn_clone').click(onclickBtnClone);
|
||||
|
||||
$("#btn_delete").click(function(){
|
||||
if(!confirm("삭제하시겠습니까?")){
|
||||
return;
|
||||
}
|
||||
|
||||
var id = "grid";
|
||||
var cnvsnNameArr = new Array();
|
||||
var eaiSvcNameArr = new Array();
|
||||
@@ -263,7 +261,7 @@ $(document).ready(function() {
|
||||
}
|
||||
|
||||
if(chkcnt == 0){
|
||||
alert("변환매핑을 선택해 주세요");
|
||||
alert("<%= localeMessage.getString("standardLayout.message4") %>");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -303,10 +301,18 @@ $(document).ready(function() {
|
||||
});
|
||||
buttonControl();
|
||||
setBtnHide(roleString, "admin", "btn_initialize");
|
||||
//setBtnControl(roleString, "admin", "btn_cmsave");
|
||||
if(!devSvr){
|
||||
$("#btn_cmsave").hide();
|
||||
|
||||
if(devSvr || stageSvr){
|
||||
if (adminOnly) {
|
||||
setBtnHide(roleString, "admin", "btn_clone");
|
||||
}
|
||||
}else{
|
||||
$("#btn_clone").hide();
|
||||
}
|
||||
//setBtnControl(roleString, "admin", "btn_cmsave");
|
||||
//if(!devSvr){
|
||||
// $("#btn_cmsave").hide();
|
||||
//}
|
||||
});
|
||||
|
||||
function setBtnControl(roleString, condiVal, compoId)
|
||||
@@ -430,6 +436,62 @@ function addCMResource(eaiBzwkDsdCd, obj, cnvsnNames) {
|
||||
});
|
||||
}
|
||||
|
||||
function onclickBtnClone() {
|
||||
var sel = $('#grid').jqGrid('getGridParam', 'selarrrow');
|
||||
if (sel.length == 0) {
|
||||
alert('<%= localeMessage.getString("eaiMessage.alert17")%>');
|
||||
return;
|
||||
}
|
||||
var selectedTransformNames = [];
|
||||
|
||||
$(sel).each(function(index, i) {
|
||||
var cnvsnName = $('#grid').getCell(i, 'CNVSNNAME');
|
||||
selectedTransformNames.push(cnvsnName);
|
||||
});
|
||||
|
||||
var url2 = url_view;
|
||||
url2 += "?cmd=POPUP CALENDAR";
|
||||
var args = new Object();
|
||||
args['selectedCnvsnName'] = selectedTransformNames.join(',');
|
||||
showModal(url2,args,540,270, function(arg){
|
||||
var args = null;
|
||||
if(arg == null || arg == undefined ) {
|
||||
//chrome
|
||||
args = this.dialogArguments;
|
||||
args.returnValue = this.returnValue;
|
||||
} else {
|
||||
//ie
|
||||
args = arg;
|
||||
}
|
||||
if( !args || !args.returnValue ) return;
|
||||
deployTransforms(args.returnValue);
|
||||
});
|
||||
}
|
||||
|
||||
function deployTransforms(ret) {
|
||||
var params = ret;
|
||||
params['cmd'] = 'CLONETOPROD';
|
||||
$.ajax({
|
||||
type : "POST",
|
||||
url:url,
|
||||
dataType:"json",
|
||||
data:params,
|
||||
success:function(json){
|
||||
alert(json.message);
|
||||
list($("#grid").getGridParam("page"));
|
||||
},
|
||||
error:function(xhr,status,error){
|
||||
if (xhr.responseText != null) {
|
||||
try {
|
||||
alert(JSON.parse(xhr.responseText).errorMsg);
|
||||
} catch (e) {
|
||||
alert(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
@@ -441,6 +503,11 @@ function addCMResource(eaiBzwkDsdCd, obj, cnvsnNames) {
|
||||
</div><!-- end content_top -->
|
||||
<div class="content_middle" id="content_middle">
|
||||
<div class="search_wrap">
|
||||
<% if (SystemUtil.isDevServer() && deployYn) { %>
|
||||
<button type="button" class="cssbtn" id="btn_clone" level="W"><i class="material-icons">move_up</i> <%= localeMessage.getString("button.copyStage") %></button>
|
||||
<% } else if (SystemUtil.isStagingServer() && deployYn) { %>
|
||||
<button type="button" class="cssbtn" id="btn_clone" level="W"><i class="material-icons">move_up</i> <%= localeMessage.getString("button.copyProd") %></button>
|
||||
<% } %>
|
||||
<button type="button" class="cssbtn" id="btn_excel" level="W" status="DETAIL"><i class="material-icons">table_view</i> <%= localeMessage.getString("button.excel") %></button>
|
||||
<button type="button" class="cssbtn" id="btn_initialize" level="W" ><i class="material-icons">restart_alt</i> <%= localeMessage.getString("button.reset") %></button>
|
||||
<button type="button" class="cssbtn" id="btn_delete" level="W" status="DETAIL,NEW"><i class="material-icons">delete</i> <%= localeMessage.getString("button.delete") %></button>
|
||||
@@ -469,9 +536,13 @@ function addCMResource(eaiBzwkDsdCd, obj, cnvsnNames) {
|
||||
</tr>
|
||||
<tr>
|
||||
<th style="width:180px;"><%=localeMessage.getString("transformMan.telCngMappDesc")%> </th>
|
||||
<td colspan="3">
|
||||
<td>
|
||||
<input type="text" name="searchCnvsnDesc" value="${param.searchCnvsnDesc}">
|
||||
</td>
|
||||
<th style="width:180px;"><%=localeMessage.getString("transformMan.ifSvcCd")%> </th>
|
||||
<td>
|
||||
<input type="text" name="searchIfSvcCd" value="${param.searchIfSvcCd}">
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -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>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -46,10 +46,10 @@ $(document).ready(function() {
|
||||
, src_layout : src_layout
|
||||
},
|
||||
colNames:[
|
||||
'<%=localeMessage.getString("tranManDtPop.conRsItPtNm")%>',
|
||||
'<%=localeMessage.getString("tranManDtPop.conOrd")%>',
|
||||
'<%=localeMessage.getString("tranManDtPop.ItDft")%>',
|
||||
'<%=localeMessage.getString("tranManDtPop.itSrlNmb")%>',
|
||||
'변환결과 항목경로명',
|
||||
'변환명령',
|
||||
'항목 기본값',
|
||||
'항목 일련번호',
|
||||
'타겟Flag',
|
||||
'소스Flag',
|
||||
'<%=localeMessage.getString("tranManDtPop.errMsg")%>'
|
||||
@@ -104,7 +104,6 @@ $(document).ready(function() {
|
||||
resizeJqGridWidth('grid','title','1000');
|
||||
|
||||
$("#btn_close").click(function(){
|
||||
//debugger;
|
||||
window.close();
|
||||
});
|
||||
$("body").css("overflow-x","hidden");
|
||||
@@ -115,13 +114,12 @@ $(document).ready(function() {
|
||||
<body>
|
||||
<div class="popup_box" id="title">
|
||||
<div class="search_wrap">
|
||||
<%-- <img id="btn_close" src="<c:url value="/img/btn_close.png"/>" level="R"/> --%>
|
||||
<button type="button" class="cssbtn" id="btn_close" level="R"><i class="material-icons">cancel</i> <%= localeMessage.getString("button.close") %></button>
|
||||
<img id="btn_close" src="<c:url value="/img/btn_close.png"/>" level="R"/>
|
||||
</div>
|
||||
<div class="title"><%=localeMessage.getString("tranManDtPop.title")%></div>
|
||||
<div class="title">변환매핑항목 상세</div>
|
||||
<table class="search_condition" cellspacing="0">
|
||||
<tr>
|
||||
<th style="width:180px;"><%=localeMessage.getString("tranManDtPop.conMapCd")%></th>
|
||||
<th style="width:180px;">변환매핑 코드</th>
|
||||
<td>
|
||||
<input type="text" name="cnvsnName" readonly>
|
||||
</td>
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
<jsp:include page="/jsp/common/include/script.jsp"/>
|
||||
|
||||
<script language="javascript" >
|
||||
var url_view = '<c:url value="/onl/admin/rule/transformMan.view" />';
|
||||
var $ = jQuery.noConflict();
|
||||
var bzwkDstCd = "";
|
||||
|
||||
@@ -81,8 +82,9 @@ $(document).ready(function() {
|
||||
var key = "";
|
||||
var args = new Object();
|
||||
args['eaiSvcName'] = $('input[name=eaiSvcName]').val();
|
||||
var url='<c:url value="/onl/transaction/extnl/interfaceMan.view"/>';
|
||||
url = url + "?cmd=POPUP";
|
||||
var url=url_view; //'<c:url value="/onl/transaction/extnl/interfaceMan.view"/>';
|
||||
url = url + "?cmd=API_POPUP";
|
||||
console.log('[new]', url);
|
||||
var ret = showModal(url,args,1020,630, function(arg){
|
||||
var args = null;
|
||||
if(arg == null || arg == undefined ) {//chrome
|
||||
@@ -96,15 +98,15 @@ $(document).ready(function() {
|
||||
|
||||
var ret = args.returnValue;
|
||||
console.log("ret",ret);
|
||||
key = ret['key'];
|
||||
key = ret['apiId'];
|
||||
|
||||
$("input[name=eaiSvcName]").val(key);
|
||||
$("input[name=eaiSvcDesc]").val(ret['eaiSvcDesc']);
|
||||
bzwkDstCd = ret['eaiBzwkDstCd'];
|
||||
$("input[name=eaiSvcDesc]").val(ret['apiDesc']);
|
||||
bzwkDstCd = ret['bizCode'];
|
||||
|
||||
$("input[name=eaiSvcName]").change();
|
||||
$("select[name=eaiSevrDstcd]").val(ret['eaiSevrDstcd']);
|
||||
$("select[name=eaiSevrDstcd]").attr("disabled",true);
|
||||
//$("select[name=eaiSevrDstcd]").val(ret['eaiSevrDstcd']);
|
||||
//$("select[name=eaiSevrDstcd]").attr("disabled",true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -159,25 +161,29 @@ $(document).ready(function() {
|
||||
<form id="ajaxForm">
|
||||
<table class="table_row" cellspacing="0">
|
||||
<tr>
|
||||
<th style="width:25%;"><%=localeMessage.getString("transformMan.ifSvcCd")%> </th>
|
||||
<td colspan="3">
|
||||
<input type="text" name="eaiSvcName" style="width:calc(100% - 60px);" readonly> <%-- <img id="btn_search" src="<c:url value="/img/btn_pop_search.png" />" level="R" class="btn_img" /> --%>
|
||||
<th style="width:14%; min-width:130px;"><%=localeMessage.getString("transformMan.ifSvcCd")%> </th>
|
||||
<td style="width:31%;">
|
||||
<input type="text" name="eaiSvcName" style="width:60%;" readonly> <!-- <img id="btn_search" src="<c:url value="/img/btn_pop_search.png" />" level="R" class="btn_img" /> -->
|
||||
<button type="button" class="cssbtn smallBtn2" id="btn_search" style="vertical-align:middle; font-weight:bold;" level="R"><i class="material-icons">search</i><%= localeMessage.getString("button.search") %></button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><%=localeMessage.getString("transformMan.ifSvcDesc")%></th>
|
||||
<td colspan="3">
|
||||
<th style="width:14%; min-width:130px;"><%=localeMessage.getString("transformMan.ifSvcDesc")%></th>
|
||||
<td style="width:41%;">
|
||||
<input type="text" name="eaiSvcDesc" readOnly>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><%=localeMessage.getString("transformMan.eaiSvrDiv")%></th>
|
||||
<td colspan="3">
|
||||
<td>
|
||||
<div class="select-style">
|
||||
<select name="eaiSevrDstcd" />
|
||||
</div>
|
||||
</td>
|
||||
<th style="width:13%; min-width:130px;"><%=localeMessage.getString("combo.useYn")%></th>
|
||||
<td>
|
||||
<div class="select-style">
|
||||
<select name="useYn" />
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><%=localeMessage.getString("transformMan.cnvsnType")%></th>
|
||||
@@ -186,20 +192,13 @@ $(document).ready(function() {
|
||||
<select name="cnvsnType" />
|
||||
</div>
|
||||
</td>
|
||||
<th><%=localeMessage.getString("combo.useYn")%></th>
|
||||
<td>
|
||||
<div class="select-style">
|
||||
<select name="useYn" />
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
||||
<th><%=localeMessage.getString("transformMan.telCngMappCd")%></th>
|
||||
<td colspan="3"><input type="text" name="cnvsnName" readonly="readonly"/></td>
|
||||
<td><input type="text" name="cnvsnName" readonly="readonly"/></td>
|
||||
</tr>
|
||||
|
||||
<tr height="100px">
|
||||
<th><%=localeMessage.getString("transformMan.cnvsnDesc")%></th><td colspan="3"><textarea name="cnvsnDesc" style="width:100%;height:100px"></textarea></td>
|
||||
<tr>
|
||||
<th><%=localeMessage.getString("transformMan.cnvsnDesc")%></th><td colspan="3"><textarea name="cnvsnDesc" style="width:100%;height:50px"></textarea></td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
|
||||
@@ -47,9 +47,6 @@ function init(callback){
|
||||
dataType:"json",
|
||||
data:{cmd: 'LIST_INIT', codeGroupArr : 'SVC_SAME_TIME_TYPE,<%= tranTypeCode %>'},
|
||||
success:function(json){
|
||||
new makeOptions("CODE","NAME").setObj($("select[name=searchSvcMotivUsedStcd]")).setNoValueInclude(true).setNoValue("", "<%=localeMessage.getString("combo.all")%>").setData(json.list[0].SVC_SAME_TIME_TYPE).rendering();
|
||||
new makeOptions("CODE","NAME").setObj($("select[name=searchPsvintfacdsticName]")).setNoValueInclude(true).setNoValue("", "<%=localeMessage.getString("combo.all")%>").setData(json.list[0].SVC_SAME_TIME_TYPE).rendering();
|
||||
new makeOptions("CODE","NAME").setObj($("select[name=searchFlowControl]")).setNoValueInclude(true).setNoValue("", "<%=localeMessage.getString("combo.all")%>").setData(json.listFlowControl).rendering();
|
||||
new makeOptions("CODE","NAME").setObj($("select[name=searchTranType]")).setNoValueInclude(true).setNoValue("", "<%=localeMessage.getString("combo.all")%>").setData(json.list[0].<%= tranTypeCode %>).rendering();
|
||||
svrInst = json.listInoutType;
|
||||
putSelectFromParam();
|
||||
@@ -89,8 +86,6 @@ $(document).ready(function() {
|
||||
gridPostData["rmsMenuAuth"]= auth;
|
||||
|
||||
init(detail);
|
||||
gridPostData["searchSvcMotivUsedStcd"] = '${param.searchSvcMotivUsedStcd}';
|
||||
gridPostData["searchPsvintfacdsticName"] = '${param.searchPsvintfacdsticName}';
|
||||
|
||||
$('#grid').jqGrid({
|
||||
datatype:"json",
|
||||
@@ -100,10 +95,8 @@ $(document).ready(function() {
|
||||
colNames:['<%= localeMessage.getString("eaiMsg.bizCode")%> ',
|
||||
'<%= localeMessage.getString("eaiMessage.eaiSvcName")%> ',
|
||||
'<%= localeMessage.getString("eaiMessage.eaiSvcDesc")%> ',
|
||||
'<%= localeMessage.getString("eaiMessage.svcMotivUsedStcd")%>',
|
||||
'<%= localeMessage.getString("eaiMessage.psvintfacdsticName")%>',
|
||||
'<%= localeMessage.getString("eaiMessage.queueName")%> ',
|
||||
'<%= localeMessage.getString("eaiMessage.flowCtrlRoutName")%> ',
|
||||
'<%= localeMessage.getString("eaiMessage.gstatSysAdptr")%>',
|
||||
'<%= localeMessage.getString("eaiMessage.psvSysAdptr")%>',
|
||||
'<%= localeMessage.getString("eaiMessage.tranType")%> ',
|
||||
'<%= localeMessage.getString("combo.useYn")%> ',
|
||||
'<%= localeMessage.getString("eaiMessage.sevrDstCd")%>',
|
||||
@@ -113,10 +106,8 @@ $(document).ready(function() {
|
||||
{ name : 'EAIBZWKDSTCD' , align:'center' ,sortable:false, width:20 },
|
||||
{ name : 'EAISVCNAME' , align:'left', width:50 },
|
||||
{ name : 'EAISVCDESC' , align:'left' },
|
||||
{ name : 'SVCMOTIVUSEDSTCD', align:'center', width:50 },
|
||||
{ name : 'PSVINTFACDSTICNAME', align:'center', width:50 },
|
||||
{ name : 'NONMOTIVROUTURINAME' , align:'left', width:90 },
|
||||
{ name : 'FLOWCTRLROUTNAME' , align:'center', width:40 },
|
||||
{ name : 'GSTATSYSADPTRBZWKGROUPNAME', align:'left', width:80 },
|
||||
{ name : 'PSVSYSADPTRBZWKGROUPNAME', align:'left', width:80 },
|
||||
{ name : 'TRANTYPE', align:'center', width:30 },
|
||||
{ name : 'USEYN', align:'center', width:30,editoptions:{value:"0:<%= localeMessage.getString("combo.usen")%>;1:<%= localeMessage.getString("combo.usey")%>"}, formatter:"select"},
|
||||
{ name : 'EAISEVRDSTCD', align:'center', width:30 , formatter:formatter, hidden:true},
|
||||
@@ -426,28 +417,25 @@ function deployInterfaces(ret) {
|
||||
<td>
|
||||
<input type="text" name="searchEaiSvcName" value="${param.searchEaiSvcName}">
|
||||
</td>
|
||||
<th style="width:10%; min-width:120px;"><%= localeMessage.getString("eaiMessage.svcMotivUsedStcd")%></th>
|
||||
<td><div class="select-style"><select name="searchSvcMotivUsedStcd" value="${param.searchSvcMotivUsedStcd}"></select></div></td>
|
||||
<th style="width:10%; min-width:90px;"><%= localeMessage.getString("eaiMessage.tranType")%></th>
|
||||
<td><div class="select-style"><select name="searchTranType" value="${param.searchTranType}"></select></div></td>
|
||||
|
||||
<th style="width:10%; min-width:90px;"><%= localeMessage.getString("eaiMessage.eaiSvcDesc")%></th>
|
||||
<td>
|
||||
<input type="text" name="searchEaiSvcDesc" value="${param.searchEaiSvcDesc}">
|
||||
</td>
|
||||
<th style="width:10%; min-width:90px;"><%= localeMessage.getString("eaiMessage.tranType")%></th>
|
||||
<td><div class="select-style"><select name="searchTranType" value="${param.searchTranType}"></select></div></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><%= localeMessage.getString("eaiMessage.searchBzwkDstCd")%></th>
|
||||
<td>
|
||||
<input type="text" name="searchEaiBzwkDstcd" value="${param.searchEaiBzwkDstcd}">
|
||||
</td>
|
||||
<th><%= localeMessage.getString("eaiMessage.psvintfacdsticName")%></th>
|
||||
<td><div class="select-style"><select name="searchPsvintfacdsticName" value="${param.searchPsvintfacdsticName}"></select></div></td>
|
||||
<th><%= localeMessage.getString("eaiMessage.searchFlowControl")%></th>
|
||||
<td><div class="select-style"><select name="searchFlowControl" value="${param.searchFlowControl}"></select></div></td>
|
||||
<th><%= localeMessage.getString("eaiMessage.queueName")%></th>
|
||||
<th><%= localeMessage.getString("eaiMessage.gstatSysAdptr")%></th>
|
||||
<td>
|
||||
<input type="text" name="searchQueueName" value="${param.searchQueueName}">
|
||||
<input type="text" name="searchGstatSysAdptrBzwkGroupName" value="${param.searchGstatSysAdptrBzwkGroupName}">
|
||||
</td>
|
||||
<th><%= localeMessage.getString("eaiMessage.psvSysAdptr")%></th>
|
||||
<td>
|
||||
<input type="text" name="searchPsvSysAdptrBzwkGroupName" value="${param.searchPsvSysAdptrBzwkGroupName}">
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<script language="javascript" >
|
||||
var url = '<c:url value="/onl/apim/apigroup/apiGroupMan.json"/>';
|
||||
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;
|
||||
let dialog;
|
||||
@@ -415,6 +415,12 @@
|
||||
<input type="text" name="groupName" data-required data-warning="API 그룹 명을 입력하여 주십시오.">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th style="width:150px;">그룹 설명</th>
|
||||
<td colspan="7">
|
||||
<input type="text" name="groupDesc">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th style="width:150px;">공개 여부</th>
|
||||
<td style="width:100px;">
|
||||
@@ -461,14 +467,6 @@
|
||||
<img id="mainIconPreview" src="#" alt="Main Icon Preview" style="display:none;height:20px">
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
|
||||
<%--<tr>
|
||||
<th>컨텐츠 내용</th>
|
||||
<td colspan="5">
|
||||
<textarea name="contentDetail" id="contentDetail" rows="10" cols="50"></textarea>
|
||||
</td>
|
||||
</tr>--%>
|
||||
</table>
|
||||
<div style="margin-top:15px;">
|
||||
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 5px ">
|
||||
|
||||
@@ -72,13 +72,22 @@
|
||||
});
|
||||
|
||||
}
|
||||
function detail(){
|
||||
// 체크박스는 name 을 search* 로 두면 getSearchForJqgrid 가 해제 상태에서도 value 를 담아버린다
|
||||
// (input[type!=radio][name^=search] 를 checked 여부와 무관하게 수집) → 여기서 직접 붙인다
|
||||
function getPopupSearchData() {
|
||||
var postData = getSearchForJqgrid("cmd","LIST"); //jqgrid에서는 object 로
|
||||
postData.searchApiSpecYn = $("#chkApiSpec").is(":checked") ? "Y" : "";
|
||||
postData.searchPortalDisplayYn = $("#chkPortalDisplay").is(":checked") ? "Y" : "";
|
||||
return postData;
|
||||
}
|
||||
|
||||
function detail(){
|
||||
var postData = getPopupSearchData();
|
||||
$("#grid").setGridParam({ url:url,postData: postData }).trigger("reloadGrid");
|
||||
}
|
||||
|
||||
function search(){
|
||||
var postData = getSearchForJqgrid("cmd","LIST"); //jqgrid에서는 object 로
|
||||
var postData = getPopupSearchData();
|
||||
$("#grid").setGridParam({ url:url,postData: postData ,page:1 }).trigger("reloadGrid");
|
||||
}
|
||||
function ioFormatter(cellvalue,options,rowObject){
|
||||
@@ -97,7 +106,7 @@
|
||||
|
||||
function list(){
|
||||
detail()
|
||||
var gridPostData = getSearchForJqgrid("cmd","LIST"); //jqgrid에서는 object 로
|
||||
var gridPostData = getPopupSearchData();
|
||||
// gridPostData.searchApiEnableYn = 'Y';
|
||||
var urlParams = new URLSearchParams(window.location.search);
|
||||
var selectedApiIds = urlParams.get("selectedApiIds");
|
||||
@@ -116,21 +125,27 @@
|
||||
'작성자',
|
||||
'가상응답여부',
|
||||
'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 : 'eaiSvcName' , align : 'left' , width:'100', sortable:true},
|
||||
{ name : 'eaiSvcDesc' , align : 'left' , sortable:false},
|
||||
{ name : 'apiFullPath' , align : 'left' , width:'100', sortable:false},
|
||||
{ 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 : 'fromAdapter' , align : 'center' , width:'40', formatter:adapterNameShortFormatter, sortable:false },
|
||||
{ name : 'toAdapter' , align : 'center' , width:'40', formatter:adapterNameShortFormatter, sortable:false },
|
||||
{ name : 'author' , align : 'center' , width:'60', sortable:false },
|
||||
{ name : 'simYn' , align : 'center' , width:'40', sortable:false },
|
||||
{ name : 'syncAsyncType' , align : 'center' , width:'40', hidden: true},
|
||||
{ name : 'lastModifiedDate' , align : 'center' , width:'130', sortable:true },
|
||||
],
|
||||
jsonReader : {
|
||||
repeatitems : false
|
||||
},
|
||||
// 서버(ApiInterfaceController#selectList)가 sidx/sord 대신 sortName/sortOrder 파라미터로 정렬조건을 받는다
|
||||
prmNames : { sort : 'sortName', order : 'sortOrder' },
|
||||
sortname : 'lastModifiedDate',
|
||||
sortorder : 'desc',
|
||||
pager : $('#pager'),
|
||||
page : '${param.page}',
|
||||
rowNum : '${rmsDefaultRowNum}',
|
||||
@@ -152,10 +167,6 @@
|
||||
}
|
||||
});
|
||||
}
|
||||
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) {
|
||||
@@ -189,6 +200,9 @@
|
||||
$("select[name=searchEaiBzwkDstcd]").change(function() {
|
||||
search();
|
||||
});
|
||||
$("#chkApiSpec, #chkPortalDisplay").change(function() {
|
||||
search();
|
||||
});
|
||||
$("#btn_search").click(function(){
|
||||
search();
|
||||
});
|
||||
@@ -247,6 +261,18 @@
|
||||
<input type="text" name="searchApiFullPath" value="${param.searchApiFullPath}">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th style="width:180px;">포탈 노출</th>
|
||||
<td colspan="3">
|
||||
<label class="checkbox-inline">
|
||||
<input type="checkbox" id="chkApiSpec" <c:if test="${param.searchApiSpecYn eq 'Y'}">checked</c:if>> OpenAPI Spec 등록
|
||||
</label>
|
||||
|
||||
<label class="checkbox-inline">
|
||||
<input type="checkbox" id="chkPortalDisplay" <c:if test="${param.searchPortalDisplayYn eq 'Y'}">checked</c:if>> 포탈 게시
|
||||
</label>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<table id="grid" ></table>
|
||||
|
||||
@@ -56,11 +56,16 @@
|
||||
$("#expectEndDate").datepicker().inputmask("yyyy-mm-dd", {'autoUnmask': true});
|
||||
|
||||
$("#btn_app_approve").click(function () {
|
||||
if (confirm("승인하시겠습니까?")) {
|
||||
var isDeleteReq = (currentRequestType === 'DELETE');
|
||||
var gwAction = (isDeleteReq && $('#chkGwDelete').is(':checked')) ? 'DELETE' : 'BLOCK';
|
||||
var confirmMsg = isDeleteReq
|
||||
? "해지 승인 시 포털 인증키(Credential)는 삭제되며,\n게이트웨이 클라이언트는 [" + (gwAction === 'DELETE' ? "완전 삭제" : "차단(비활성화)") + "] 처리됩니다.\n승인하시겠습니까?"
|
||||
: "승인하시겠습니까?";
|
||||
if (confirm(confirmMsg)) {
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: url,
|
||||
data: {cmd: "APPROVE", id: key},
|
||||
data: {cmd: "APPROVE", id: key, gwAction: gwAction},
|
||||
success: function (args) {
|
||||
<%--alert("<%= localeMessage.getString("approval.approveSuccess") %>");--%>
|
||||
alert("승인되었습니다.");
|
||||
@@ -101,6 +106,32 @@
|
||||
}
|
||||
});
|
||||
|
||||
$("#btn_app_force_reject").click(function () {
|
||||
if (!confirm("승인자 목록에 삭제된 사용자가 있어 승인 진행이 불가능한 건입니다.\n강제 반려하시겠습니까?")) {
|
||||
return;
|
||||
}
|
||||
var rejectReason = prompt("강제 반려 사유를 입력해 주세요");
|
||||
if (rejectReason != null) {
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: url,
|
||||
data: {cmd: "FORCE_REJECT", id: key, rejectReason: rejectReason},
|
||||
success: function (args) {
|
||||
alert("강제 반려되었습니다.");
|
||||
if (window.dialogArguments && window.dialogArguments.self) {
|
||||
window.dialogArguments.self.location.reload();
|
||||
} else if (window.opener) {
|
||||
window.opener.location.reload();
|
||||
}
|
||||
window.close();
|
||||
},
|
||||
error: function (e) {
|
||||
alert(e.responseText);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$('#btn_app_redeploy').click(function () {
|
||||
if (confirm("재배포 하시겠습니까?")) {
|
||||
$.ajax({
|
||||
@@ -145,11 +176,65 @@
|
||||
}
|
||||
});
|
||||
|
||||
$("#btn_app_delete").click(function () {
|
||||
if (!confirm("이 승인 요청을 삭제하시겠습니까?\n삭제 후에는 복구할 수 없습니다.")) {
|
||||
return;
|
||||
}
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: url,
|
||||
data: {cmd: "DELETE", id: key},
|
||||
success: function (args) {
|
||||
alert("삭제되었습니다.");
|
||||
if (window.dialogArguments && window.dialogArguments.self) {
|
||||
window.dialogArguments.self.location.reload();
|
||||
} else if (window.opener) {
|
||||
window.opener.location.reload();
|
||||
}
|
||||
window.close();
|
||||
},
|
||||
error: function (e) {
|
||||
alert(approvalErrorMessage(e));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$("#btn_close").click(function () {
|
||||
window.close();
|
||||
});
|
||||
});
|
||||
|
||||
function approvalErrorMessage(e) {
|
||||
try {
|
||||
var json = JSON.parse(e.responseText);
|
||||
return json.errorMsg || e.responseText;
|
||||
} catch (ex) {
|
||||
return e.responseText;
|
||||
}
|
||||
}
|
||||
|
||||
// 요청자(PTL_USER) row 가 삭제되면 상세 조회 자체가 불가능하다. 이 경우 삭제만 가능하도록 노출한다.
|
||||
function showLoadFailure(message) {
|
||||
$("#approvalSubject").text("상세 정보를 조회할 수 없는 승인 요청");
|
||||
$("#approvalDetail").text(message);
|
||||
$("#btn_modify, #btn_app_approve, #btn_app_reject, #btn_app_force_reject, #btn_app_redeploy").hide();
|
||||
$("#gwActionSection, #gwActionResult").hide();
|
||||
document.getElementById("btn_app_delete").style.display = "";
|
||||
}
|
||||
|
||||
var currentRequestType = null; // 현재 신청 유형 (DELETE 승인 시 GW 처리방식 선택 노출용)
|
||||
|
||||
// 값이 있을 때만 행을 노출한다. (해지 신청처럼 해당 항목이 없는 유형에서 빈 행이 남지 않도록)
|
||||
function setOptionalRow(rowId, spanId, value) {
|
||||
if (value) {
|
||||
$("#" + spanId).text(value);
|
||||
$("#" + rowId).show();
|
||||
} else {
|
||||
$("#" + spanId).text("");
|
||||
$("#" + rowId).hide();
|
||||
}
|
||||
}
|
||||
|
||||
function detail(key) {
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
@@ -178,10 +263,11 @@
|
||||
$("#createdDate").text(data.createdDate).inputmask("9999-99-99 99:99:99.999", {'autoUnmask': true});
|
||||
$("#expectEndDate").val(data.expectEndDate);
|
||||
|
||||
if (data.approvers.some(approver =>
|
||||
var isCurrentApprover = data.approvers.some(approver =>
|
||||
approver.approverStatus === 'CURRENT' &&
|
||||
approver.user.USERID === loginUser
|
||||
)) {
|
||||
approver.user && approver.user.USERID === loginUser
|
||||
);
|
||||
if (isCurrentApprover) {
|
||||
document.getElementById("btn_app_approve").style.display = "";
|
||||
document.getElementById("btn_app_reject").style.display = "";
|
||||
} else {
|
||||
@@ -189,6 +275,29 @@
|
||||
document.getElementById("btn_app_reject").style.display = "none";
|
||||
}
|
||||
|
||||
// API 이용해지(DELETE) 건: 승인 가능 상태의 현재 승인자에게만 GW 처리방식 선택 노출
|
||||
currentRequestType = data.requestType;
|
||||
var inProgress = data.approvalStatus.code === 'REQUESTED' || data.approvalStatus.code === 'PROCESSING';
|
||||
if (data.requestType === 'DELETE' && isCurrentApprover && inProgress) {
|
||||
$("#gwActionSection").show();
|
||||
} else {
|
||||
$("#gwActionSection").hide();
|
||||
}
|
||||
// 처리 완료된 해지 건은 어떤 방식으로 실행됐는지 표시
|
||||
if (data.requestType === 'DELETE' && data.gwAction) {
|
||||
$("#gwActionResult").text("GW 처리방식: " + (data.gwAction === 'DELETE' ? "완전 삭제" : "차단(비활성화)")).show();
|
||||
} else {
|
||||
$("#gwActionResult").hide();
|
||||
}
|
||||
|
||||
// 삭제된 승인자(USERID 없음)로 진행 불가한 건: 요청됨/진행중 상태면 승인자가 아니어도 강제 반려 허용
|
||||
var hasDeletedApprover = data.approvers.some(approver =>
|
||||
!approver.user || !approver.user.USERID
|
||||
);
|
||||
var isProgressStatus = data.approvalStatus.code === 'REQUESTED' || data.approvalStatus.code === 'PROCESSING';
|
||||
document.getElementById("btn_app_force_reject").style.display =
|
||||
(hasDeletedApprover && isProgressStatus && !isCurrentApprover) ? "" : "none";
|
||||
|
||||
if (data.approvalStatus.code === 'FAILED') {
|
||||
document.getElementById("btn_app_redeploy").style.display = "";
|
||||
}
|
||||
@@ -207,6 +316,21 @@
|
||||
|
||||
$("#clientName").text(data.clientName);
|
||||
|
||||
// 신청서에 담긴 Client 등록 정보(승인 시 GW 클라이언트로 반영되는 값). 값이 있을 때만 노출
|
||||
setOptionalRow("appDescriptionRow", "appDescription", data.appDescription);
|
||||
setOptionalRow("ipWhitelistRow", "ipWhitelist", data.ipWhitelist);
|
||||
setOptionalRow("callbackUrlRow", "callbackUrl", data.callbackUrl);
|
||||
|
||||
// 신청 사유. 해지 신청은 사유 입력이 필수이므로 "해지 사유"로 표기하고, 그 외 유형은 "신청 사유"
|
||||
if (data.requestReason) {
|
||||
$("#requestReasonLabel").text(data.requestType === 'DELETE' ? "해지 사유" : "신청 사유");
|
||||
$("#requestReason").text(data.requestReason);
|
||||
$("#requestReasonRow").show();
|
||||
} else {
|
||||
$("#requestReason").text("");
|
||||
$("#requestReasonRow").hide();
|
||||
}
|
||||
|
||||
var apiServiceHtml = "";
|
||||
for (var i = 0; i < data.apiServices.length; i++) {
|
||||
var apiService = data.apiServices[i];
|
||||
@@ -261,7 +385,9 @@
|
||||
$("#apisTable tbody").html(apisHtml);
|
||||
},
|
||||
error: function (e) {
|
||||
alert(e.responseText);
|
||||
var message = approvalErrorMessage(e);
|
||||
alert(message);
|
||||
showLoadFailure(message);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -308,6 +434,22 @@
|
||||
<th style="width:20%;">이름</th>
|
||||
<td><span id="clientName"></span></td>
|
||||
</tr>
|
||||
<tr id="appDescriptionRow" style="display:none;">
|
||||
<th style="width:20%;">앱 설명</th>
|
||||
<td><span id="appDescription" style="white-space: pre-wrap;"></span></td>
|
||||
</tr>
|
||||
<tr id="ipWhitelistRow" style="display:none;">
|
||||
<th style="width:20%;">IP 화이트리스트</th>
|
||||
<td><span id="ipWhitelist" style="white-space: pre-wrap;"></span></td>
|
||||
</tr>
|
||||
<tr id="callbackUrlRow" style="display:none;">
|
||||
<th style="width:20%;">Callback URL</th>
|
||||
<td><span id="callbackUrl" style="white-space: pre-wrap;"></span></td>
|
||||
</tr>
|
||||
<tr id="requestReasonRow" style="display:none;">
|
||||
<th style="width:20%;"><span id="requestReasonLabel">신청 사유</span></th>
|
||||
<td><span id="requestReason" style="white-space: pre-wrap;"></span></td>
|
||||
</tr>
|
||||
</table>
|
||||
<div class="title">API 목록</div>
|
||||
<table id="apisTable" class="table_row" cellspacing="0">
|
||||
@@ -335,6 +477,13 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div id="gwActionSection" style="display:none; margin: 12px 0 0 4px; color: #c0392b;">
|
||||
<label style="cursor:pointer;">
|
||||
<input type="checkbox" id="chkGwDelete"/>
|
||||
GW 인증정보 완전 삭제 (미체크 시 차단 처리 — 인증정보 보존, 복구 가능)
|
||||
</label>
|
||||
</div>
|
||||
<div id="gwActionResult" style="display:none; margin: 12px 0 0 4px; font-weight: bold;"></div>
|
||||
<div class="popup_button">
|
||||
<button type="button" class="cssbtn" id="btn_modify" level="W"><i
|
||||
class="material-icons">save</i>수정
|
||||
@@ -345,6 +494,12 @@
|
||||
<button type="button" class="cssbtn" style="display: none;" id="btn_app_reject" level="W"><i
|
||||
class="material-icons">close</i>반려
|
||||
</button>
|
||||
<button type="button" class="cssbtn" style="display: none;" id="btn_app_force_reject" level="W"><i
|
||||
class="material-icons">block</i>강제 반려
|
||||
</button>
|
||||
<button type="button" class="cssbtn" style="display: none;" id="btn_app_delete" level="W"><i
|
||||
class="material-icons">delete</i>삭제
|
||||
</button>
|
||||
<button type="button" class="cssbtn" style="display: none;" id="btn_app_redeploy" level="W"><i
|
||||
class="material-icons">close</i>재처리
|
||||
</button>
|
||||
|
||||
@@ -131,6 +131,24 @@
|
||||
// var reverseNumber = records - ((page - 1) * rowNum + i);
|
||||
$(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) {
|
||||
var location = '<%=request.getContextPath()%>/';
|
||||
@@ -176,7 +194,7 @@
|
||||
<div class="title">승인 요청 목록<span class="tooltip">승인 요청을 관리합니다.</span></div>
|
||||
<c:if test="${hardDeleteEnabled}">
|
||||
<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>
|
||||
</c:if>
|
||||
<form id="ajaxForm" onsubmit="return false;">
|
||||
|
||||
@@ -45,6 +45,10 @@
|
||||
padding: 2px 5px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.cssbtn:disabled {
|
||||
opacity: 0.45;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
</style>
|
||||
<script language="javascript">
|
||||
<%
|
||||
@@ -150,6 +154,55 @@
|
||||
}
|
||||
});
|
||||
|
||||
$("#btn_user_force_reject").click(function() {
|
||||
if (!confirm("승인자 목록에 삭제된 사용자가 있어 승인 진행이 불가능한 건입니다.\n강제 반려하시겠습니까?")) {
|
||||
return;
|
||||
}
|
||||
var rejectReason = prompt("강제 반려 사유를 입력해 주세요");
|
||||
if (rejectReason != null) {
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: url,
|
||||
data: {cmd: "FORCE_REJECT", id: key, rejectReason: rejectReason},
|
||||
success: function(args) {
|
||||
alert("강제 반려되었습니다.");
|
||||
if (window.dialogArguments && window.dialogArguments.self) {
|
||||
window.dialogArguments.self.location.reload();
|
||||
} else if (window.opener) {
|
||||
window.opener.location.reload();
|
||||
}
|
||||
window.close();
|
||||
},
|
||||
error: function(e) {
|
||||
alert(e.responseText);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$("#btn_user_delete").click(function() {
|
||||
if (!confirm("삭제된 요청자·사용자 또는 법인이 포함되어 승인·반려할 수 없는 요청입니다.\n이 승인 요청을 삭제하시겠습니까?\n삭제 후에는 복구할 수 없습니다.")) {
|
||||
return;
|
||||
}
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: url,
|
||||
data: {cmd: "DELETE", id: key},
|
||||
success: function(args) {
|
||||
alert("삭제되었습니다.");
|
||||
if (window.dialogArguments && window.dialogArguments.self) {
|
||||
window.dialogArguments.self.location.reload();
|
||||
} else if (window.opener) {
|
||||
window.opener.location.reload();
|
||||
}
|
||||
window.close();
|
||||
},
|
||||
error: function(e) {
|
||||
alert(approvalErrorMessage(e));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$('#btn_user_redeploy').click(function() {
|
||||
if (confirm("재배포 하시겠습니까?")) {
|
||||
$.ajax({
|
||||
@@ -217,20 +270,55 @@
|
||||
|
||||
// Display Portal User details
|
||||
var portalUser = data.portalUser;
|
||||
$("#loginId").text(portalUser.loginId);
|
||||
$("#userName").text(portalUser.userName);
|
||||
$("#mobileNumber").text(portalUser.mobileNumber);
|
||||
$("#userStatusDescription").text(portalUser.userStatusDescription);
|
||||
$("#roleCodeDescription").text(portalUser.roleCodeDescription);
|
||||
if (data.approvers.some(approver =>
|
||||
approver.approverStatus === 'CURRENT' &&
|
||||
approver.user.USERID === loginUser
|
||||
)) {
|
||||
document.getElementById("btn_user_approve").style.display = "";
|
||||
document.getElementById("btn_user_reject").style.display = "";
|
||||
var portalOrg = portalUser ? portalUser.portalOrgUIs : null;
|
||||
|
||||
// 삭제된 대상 사용자/법인 판별
|
||||
var userDeleted = !portalUser
|
||||
|| portalUser.userName === '반려된 사용자'
|
||||
|| portalUser.userName === '삭제된 사용자';
|
||||
var orgDeleted = !portalOrg
|
||||
|| portalOrg.orgStatus === 'REMOVED'
|
||||
|| portalOrg.orgName === '삭제된법인';
|
||||
var deletedTarget = userDeleted || orgDeleted;
|
||||
|
||||
if (portalUser) {
|
||||
$("#loginId").text(portalUser.loginId);
|
||||
$("#userName").text(portalUser.userName);
|
||||
$("#mobileNumber").text(portalUser.mobileNumber);
|
||||
$("#userStatusDescription").text(portalUser.userStatusDescription);
|
||||
$("#roleCodeDescription").text(portalUser.roleCodeDescription);
|
||||
} else {
|
||||
document.getElementById("btn_user_approve").style.display = "none";
|
||||
document.getElementById("btn_user_reject").style.display = "none";
|
||||
$("#loginId, #userName, #mobileNumber, #userStatusDescription, #roleCodeDescription").text("삭제된 사용자");
|
||||
}
|
||||
|
||||
var isCurrentApprover = data.approvers.some(approver =>
|
||||
approver.approverStatus === 'CURRENT' &&
|
||||
approver.user && approver.user.USERID === loginUser
|
||||
);
|
||||
|
||||
if (deletedTarget) {
|
||||
// 삭제된 사용자/법인 포함 → 승인·반려 불가. 버튼은 비활성(disable) 표기, 삭제 버튼 노출
|
||||
$("#btn_user_approve, #btn_user_reject").show().prop("disabled", true);
|
||||
document.getElementById("btn_user_force_reject").style.display = "none";
|
||||
document.getElementById("btn_user_delete").style.display = "";
|
||||
} else {
|
||||
$("#btn_user_approve, #btn_user_reject").prop("disabled", false);
|
||||
document.getElementById("btn_user_delete").style.display = "none";
|
||||
if (isCurrentApprover) {
|
||||
document.getElementById("btn_user_approve").style.display = "";
|
||||
document.getElementById("btn_user_reject").style.display = "";
|
||||
} else {
|
||||
document.getElementById("btn_user_approve").style.display = "none";
|
||||
document.getElementById("btn_user_reject").style.display = "none";
|
||||
}
|
||||
|
||||
// 삭제된 승인자(USERID 없음)로 진행 불가한 건: 요청됨/진행중 상태면 승인자가 아니어도 강제 반려 허용
|
||||
var hasDeletedApprover = data.approvers.some(approver =>
|
||||
!approver.user || !approver.user.USERID
|
||||
);
|
||||
var isProgressStatus = data.approvalStatus.code === 'REQUESTED' || data.approvalStatus.code === 'PROCESSING';
|
||||
document.getElementById("btn_user_force_reject").style.display =
|
||||
(hasDeletedApprover && isProgressStatus && !isCurrentApprover) ? "" : "none";
|
||||
}
|
||||
|
||||
if (data.approvalStatus.description === '배포실패'){
|
||||
@@ -238,23 +326,27 @@
|
||||
}
|
||||
|
||||
// Display Portal Org details
|
||||
var portalOrg = portalUser.portalOrgUIs;
|
||||
$("#orgName").text(portalOrg.orgName);
|
||||
$("#orgStatusDescription").text(portalOrg.orgStatusDescription);
|
||||
$("#serviceName").text(portalOrg.serviceName);
|
||||
$("#corpRegNo").text(formatCorpRegNo(portalOrg.corpRegNo));
|
||||
$("#compRegNo").text(formatCompanyRegNo(portalOrg.compRegNo));
|
||||
if (portalOrg) {
|
||||
$("#orgName").text(portalOrg.orgName);
|
||||
$("#orgStatusDescription").text(portalOrg.orgStatusDescription);
|
||||
$("#serviceName").text(portalOrg.serviceName);
|
||||
$("#corpRegNo").text(formatCorpRegNo(portalOrg.corpRegNo));
|
||||
$("#compRegNo").text(formatCompanyRegNo(portalOrg.compRegNo));
|
||||
|
||||
$("#scPhoneNumber").text(portalOrg.scPhoneNumber); // 고객센터 전화번호
|
||||
$("#orgPhoneNumber").text(portalOrg.orgPhoneNumber); // 회사 전화번호
|
||||
$("#ceoName").text(portalOrg.ceoName); // 대표자 성명
|
||||
$("#orgIndustryType").text(portalOrg.orgIndustryType || ""); // 업종
|
||||
$("#orgAddr").text(portalOrg.orgAddr); // 소재지
|
||||
$("#orgSectors").text(portalOrg.orgSectors || ""); // 업태
|
||||
$("#orgCode").text(portalOrg.orgCode);
|
||||
$("#ipWhitelist").text(portalOrg.ipWhitelist || "");
|
||||
$("#scPhoneNumber").text(portalOrg.scPhoneNumber); // 고객센터 전화번호
|
||||
$("#orgPhoneNumber").text(portalOrg.orgPhoneNumber); // 회사 전화번호
|
||||
$("#ceoName").text(portalOrg.ceoName); // 대표자 성명
|
||||
$("#orgIndustryType").text(portalOrg.orgIndustryType || ""); // 업종
|
||||
$("#orgAddr").text(portalOrg.orgAddr); // 소재지
|
||||
$("#orgSectors").text(portalOrg.orgSectors || ""); // 업태
|
||||
$("#orgCode").text(portalOrg.orgCode);
|
||||
$("#ipWhitelist").text(portalOrg.ipWhitelist || "");
|
||||
} else {
|
||||
$("#orgName").text("삭제된법인");
|
||||
$("#orgStatusDescription, #serviceName, #corpRegNo, #compRegNo, #scPhoneNumber, #orgPhoneNumber, #ceoName, #orgIndustryType, #orgAddr, #orgSectors, #orgCode, #ipWhitelist").text("");
|
||||
}
|
||||
|
||||
if (portalOrg.compRegFile) {
|
||||
if (portalOrg && portalOrg.compRegFile) {
|
||||
$("#fileName").text(portalOrg.fileName);
|
||||
if (portalOrg.fileName) {
|
||||
$("#fileWrapper").show().off('click').on('click', function() {
|
||||
@@ -270,10 +362,31 @@
|
||||
}
|
||||
},
|
||||
error: function(e) {
|
||||
alert(e.responseText);
|
||||
var message = approvalErrorMessage(e);
|
||||
alert(message);
|
||||
showLoadFailure(message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function approvalErrorMessage(e) {
|
||||
try {
|
||||
var json = JSON.parse(e.responseText);
|
||||
return json.errorMsg || e.responseText;
|
||||
} catch (ex) {
|
||||
return e.responseText;
|
||||
}
|
||||
}
|
||||
|
||||
// 요청자(PTL_USER) row 가 삭제되면 상세 조회 자체가 불가능하다. 이 경우 삭제만 가능하도록 노출한다.
|
||||
function showLoadFailure(message) {
|
||||
$("#approvalSubject").text("상세 정보를 조회할 수 없는 승인 요청");
|
||||
$("#approvalDetail").text(message);
|
||||
$("#approvalDetailRow").show();
|
||||
$("#btn_unmask").hide();
|
||||
$("#btn_user_approve, #btn_user_reject, #btn_user_force_reject, #btn_user_redeploy").hide();
|
||||
document.getElementById("btn_user_delete").style.display = "";
|
||||
}
|
||||
function updateDetailView(data) {
|
||||
$("#approvalType").text(data.approvalType === 'USER' ? "법인사용자" : "API 사용");
|
||||
$("#approvalStatus").text(data.approvalStatus.description);
|
||||
@@ -294,15 +407,18 @@
|
||||
|
||||
// 사용자 정보 업데이트
|
||||
var portalUser = data.portalUser;
|
||||
$("#loginId").text(portalUser.loginId);
|
||||
$("#userName").text(portalUser.userName);
|
||||
$("#mobileNumber").text(portalUser.mobileNumber);
|
||||
$("#userStatusDescription").text(portalUser.userStatusDescription);
|
||||
$("#roleCodeDescription").text(portalUser.roleCodeDescription);
|
||||
if (portalUser) {
|
||||
$("#loginId").text(portalUser.loginId);
|
||||
$("#userName").text(portalUser.userName);
|
||||
$("#mobileNumber").text(portalUser.mobileNumber);
|
||||
$("#userStatusDescription").text(portalUser.userStatusDescription);
|
||||
$("#roleCodeDescription").text(portalUser.roleCodeDescription);
|
||||
} else {
|
||||
$("#loginId, #userName, #mobileNumber, #userStatusDescription, #roleCodeDescription").text("삭제된 사용자");
|
||||
}
|
||||
|
||||
// 법인 정보 업데이트
|
||||
var portalOrg = portalUser.portalOrgUIs;
|
||||
updateOrgInfo(portalOrg);
|
||||
updateOrgInfo(portalUser ? portalUser.portalOrgUIs : null);
|
||||
|
||||
// 마스킹 상태에 따라 데이터를 다르게 표시
|
||||
var isMasked = $("#btn_unmask").attr("status") === "DETAIL";
|
||||
@@ -344,6 +460,11 @@
|
||||
|
||||
// 법인 정보 업데이트 함수
|
||||
function updateOrgInfo(portalOrg) {
|
||||
if (!portalOrg) {
|
||||
$("#orgName").text("삭제된법인");
|
||||
$("#orgStatusDescription, #serviceName, #corpRegNo, #compRegNo, #scPhoneNumber, #orgPhoneNumber, #ceoName, #orgIndustryType, #orgAddr, #orgSectors, #orgCode, #ipWhitelist").text("");
|
||||
return;
|
||||
}
|
||||
$("#orgName").text(portalOrg.orgName);
|
||||
$("#orgStatusDescription").text(portalOrg.orgStatusDescription);
|
||||
$("#serviceName").text(portalOrg.serviceName);
|
||||
@@ -486,6 +607,8 @@
|
||||
<div class="popup_button">
|
||||
<button type="button" class="cssbtn" style="display: none;" id="btn_user_approve" level="W"><i class="material-icons">check</i>승인</button>
|
||||
<button type="button" class="cssbtn" style="display: none;" id="btn_user_reject" level="W"><i class="material-icons">close</i>반려</button>
|
||||
<button type="button" class="cssbtn" style="display: none;" id="btn_user_force_reject" level="W"><i class="material-icons">block</i>강제 반려</button>
|
||||
<button type="button" class="cssbtn" style="display: none;" id="btn_user_delete" level="W"><i class="material-icons">delete</i>삭제</button>
|
||||
<button type="button" class="cssbtn" style="display: none;" id="btn_user_redeploy" level="W"><i class="material-icons">close</i>재처리</button>
|
||||
<button type="button" class="cssbtn" id="btn_close" level="R"><i class="material-icons">cancel</i> <%= localeMessage.getString("button.close") %>
|
||||
</button>
|
||||
|
||||
@@ -62,7 +62,19 @@
|
||||
return '<span title="' + escapeAttr(rowObject.sentDateFull || '') + '">' + escapeHtmlJs(rowObject.sentDateShort || '') + '</span>';
|
||||
}
|
||||
|
||||
function toDashDate(yyyymmdd) {
|
||||
return yyyymmdd.substring(0, 4) + "-" + yyyymmdd.substring(4, 6) + "-" + yyyymmdd.substring(6, 8);
|
||||
}
|
||||
|
||||
function init() {
|
||||
$("#startDatepicker").datepicker().inputmask("yyyy-mm-dd",{'autoUnmask':true});
|
||||
$("#endDatepicker").datepicker().inputmask("yyyy-mm-dd",{'autoUnmask':true});
|
||||
|
||||
var today = getToday();
|
||||
|
||||
$("input[name=searchFromDate]").val(toDashDate(today.substring(0,6)+"01"));
|
||||
$("input[name=searchToDate]").val(toDashDate(today));
|
||||
|
||||
$.ajax({
|
||||
type: "POST", url: url, dataType: "json",
|
||||
data: {cmd: 'LIST_INIT_COMBO'},
|
||||
@@ -145,6 +157,20 @@
|
||||
init();
|
||||
|
||||
$("#btn_search").click(function () {
|
||||
var start = $("input[name=searchFromDate]").val().replace(/-/gi, "");
|
||||
var end = $("input[name=searchToDate]").val().replace(/-/gi, "");
|
||||
|
||||
if (start && start > getToday()) {
|
||||
alert("시작일이 오늘 이후입니다. 시작일을 확인해주세요.");
|
||||
$("input[name=searchFromDate]").focus();
|
||||
return false;
|
||||
}
|
||||
if (start && end && start > end) {
|
||||
alert("조회기간 종료일은 시작일보다 커야합니다.");
|
||||
$("input[name=searchToDate]").focus();
|
||||
return false;
|
||||
}
|
||||
|
||||
var postData = getSearchForJqgrid("cmd", "LIST");
|
||||
$("#grid").setGridParam({url: url, postData: postData, page: 1}).trigger("reloadGrid");
|
||||
});
|
||||
@@ -192,14 +218,15 @@
|
||||
<option value="">전체</option>
|
||||
<option value="EMAIL">EMAIL</option>
|
||||
<option value="SMS">SMS</option>
|
||||
<option value="KAKAO">KAKAO</option>
|
||||
<option value="MESSENGER">MESSENGER</option>
|
||||
</select>
|
||||
</div>
|
||||
</td>
|
||||
<th>요청기간</th>
|
||||
<td colspan="3">
|
||||
<input type="date" name="searchFromDate"> ~ <input type="date" name="searchToDate">
|
||||
<input type="text" name="searchFromDate" id="startDatepicker" readonly="readonly" value="" size="10" style="width:100px; border:1px solid #ebebec;">
|
||||
~
|
||||
<input type="text" name="searchToDate" value="" id="endDatepicker" size="10" readonly="readonly" style="width:100px; border:1px solid #ebebec;">
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
@@ -47,6 +47,8 @@
|
||||
$('#sentDate').text(data.sentDateFull || '');
|
||||
// 서버에서 마스킹+escape 된 안전 HTML
|
||||
$('#messageHtml').html(data.messageHtml || '');
|
||||
// 응답내용(RESPONSE_DATA) — 원문이므로 text 로만 출력
|
||||
$('#responseData').text(data.responseData || '');
|
||||
|
||||
// PENDING 건만 '실패 처리' 노출 (권한 제어는 buttonControl 이 담당)
|
||||
if (data.requestStatus !== 'PENDING') {
|
||||
@@ -142,6 +144,10 @@
|
||||
<th>메세지 내용</th>
|
||||
<td colspan="3"><div id="messageHtml" class="msg-detail"></div></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>응답내용</th>
|
||||
<td colspan="3"><div id="responseData" class="msg-detail"></div></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
function formatInquiryStatus(cellvalue, options, rowObject) {
|
||||
if (cellvalue == 'PENDING') {
|
||||
return '<span style="color: red;">문의중</span>';
|
||||
} else if (cellvalue == 'REVIEWING') {
|
||||
return '<span >문의검토중</span>'
|
||||
} else if (cellvalue == 'RESPONDED') {
|
||||
return '<span >답변완료</span>'
|
||||
} 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) {
|
||||
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;">' : '';
|
||||
@@ -99,13 +112,14 @@
|
||||
mtype: 'POST',
|
||||
url: url,
|
||||
postData : { cmd : 'LIST' },
|
||||
colNames:['No.', 'id', '제목', '법인명', '상태', '작성자', '등록일', '답변자','답변일'],
|
||||
colNames:['No.', 'id', '제목', '법인명', '상태', '공개범위', '작성자', '등록일', '답변자','답변일'],
|
||||
colModel:[
|
||||
{ name : 'rowNum' , align:'center', width:50, sortable:false },
|
||||
{ name : 'id' , align:'center', key:true, hidden:true},
|
||||
{ name : 'inquirySubject' , align:'left' , width:200 , formatter: formatFile},
|
||||
{ name : 'orgName' , align:'center' , width:100 , },
|
||||
{ name : 'inquiryStatus' , align:'center', width:70, formatter: formatInquiryStatus },
|
||||
{ name : 'visibility' , align:'center', width:70, formatter: formatVisibility },
|
||||
{ name : 'inquirer.userName', align:'center', width:70 },
|
||||
{ name : 'createdDate' , align:'center', width:100, formatter: timeStampFormat },
|
||||
{ name : 'responderName' , align:'center', width:70 },
|
||||
@@ -207,6 +221,7 @@
|
||||
<select name="searchInquiryStatus">
|
||||
<option value="">전체</option>
|
||||
<option value="PENDING">문의중</option>
|
||||
<option value="REVIEWING">문의검토중</option>
|
||||
<option value="RESPONDED">답변완료</option>
|
||||
<option value="CLOSED">답변종료</option>
|
||||
</select>
|
||||
|
||||
@@ -79,12 +79,46 @@
|
||||
margin-left: 2px;
|
||||
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>
|
||||
<script language="javascript">
|
||||
var url = '<c:url value="/onl/apim/portalinquiry/portalInquiryMan.json" />';
|
||||
var url_view = '<c:url value="/onl/apim/portalinquiry/portalInquiryMan.view" />';
|
||||
var file_download = '<c:url value="/file/download.do" />';
|
||||
var inquiryStatus = 'PENDING';
|
||||
var originalVisibility = 'ORG';
|
||||
var reasonModalCallback = null;
|
||||
|
||||
function decodeHTMLEntities(text) {
|
||||
var textArea = document.createElement('textarea');
|
||||
@@ -109,6 +143,8 @@
|
||||
inquiryStatus = data.inquiryStatus;
|
||||
if (data.inquiryStatus == 'PENDING') {
|
||||
$("#inquiryStatus").text('문의중');
|
||||
} else if (data.inquiryStatus == 'REVIEWING') {
|
||||
$("#inquiryStatus").text('문의검토중');
|
||||
} else if (data.inquiryStatus == 'RESPONDED') {
|
||||
$("#inquiryStatus").text('답변완료');
|
||||
} else if (data.inquiryStatus == 'CLOSED') {
|
||||
@@ -122,6 +158,24 @@
|
||||
var decodedContent = decodeHTMLEntities(data.responseDetail);
|
||||
$("#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);
|
||||
|
||||
@@ -147,8 +201,17 @@
|
||||
if (!data || data.length === 0) return;
|
||||
$.each(data, function (i, comment) {
|
||||
var date = comment.createdDate ? timeStampFormat(comment.createdDate) : '';
|
||||
var isPrivate = comment.visibility === 'PRIVATE';
|
||||
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');
|
||||
$('<div>').css('white-space', 'pre-wrap').text(comment.commentDetail).appendTo(content);
|
||||
var meta = $('<div>').css({'font-size': '0.9em', 'color': '#999', 'margin-top': '2px'});
|
||||
@@ -159,6 +222,19 @@
|
||||
})(comment.id, comment.inquiryId))
|
||||
.appendTo(meta);
|
||||
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);
|
||||
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) {
|
||||
if (inquiryStatus == 'CLOSED') {
|
||||
alert('답변이 종료되어 삭제할 수 없습니다');
|
||||
@@ -208,24 +362,38 @@
|
||||
alert('답변이 종료되어 수정할 수 없습니다');
|
||||
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 (!confirm("<%= localeMessage.getString("common.checkSave")%>")) return;
|
||||
saveInquiryAnswer(returnUrl);
|
||||
});
|
||||
|
||||
var postData = $('#ajaxForm').serializeArray();
|
||||
postData.push({name: "cmd", value: "INSERT"});
|
||||
$("#reasonModalOk").click(function () {
|
||||
var reason = ($('#reasonModalInput').val() || '').trim();
|
||||
if (!reason) {
|
||||
alert('변경 사유를 입력하세요.');
|
||||
return;
|
||||
}
|
||||
var cb = reasonModalCallback;
|
||||
closeReasonModal();
|
||||
if (cb) cb(reason);
|
||||
});
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: url,
|
||||
data: postData,
|
||||
success: function (json) {
|
||||
alert("<%= localeMessage.getString("common.saveMsg") %>");
|
||||
goNav(returnUrl);
|
||||
},
|
||||
error: function (e) {
|
||||
alert(e.responseText);
|
||||
}
|
||||
});
|
||||
$("#reasonModalCancel").click(function () {
|
||||
closeReasonModal();
|
||||
});
|
||||
|
||||
$("#btn_comment").click(function () {
|
||||
@@ -308,7 +476,22 @@
|
||||
<div class="info-label">작성일</div>
|
||||
<div id="createdDate" class="info-content"></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 id="attachFiles" class="info-content"></div>
|
||||
</div>
|
||||
@@ -356,5 +539,17 @@
|
||||
</form>
|
||||
</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>
|
||||
</html>
|
||||
@@ -189,7 +189,7 @@
|
||||
<td>
|
||||
<input type="text" id="keywordList" style="width:100%" placeholder="쉼표(,)로 구분하여 입력">
|
||||
<div class="help-text" style="color:#666; font-size:12px; margin-top:5px;">
|
||||
예시: #인증, #대출, #예금
|
||||
예시: 인증, 대출, 예금
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -0,0 +1,636 @@
|
||||
<%@ page language="java" contentType="text/html; charset=utf-8" %>
|
||||
<%@ page import="java.io.*" %>
|
||||
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
|
||||
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %>
|
||||
<%
|
||||
response.setHeader("Pragma", "No-cache");
|
||||
response.setHeader("Cache-Control", "no-cache");
|
||||
response.setHeader("Expires", "0");
|
||||
%>
|
||||
<html>
|
||||
<head>
|
||||
<title></title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<jsp:include page="/jsp/common/include/css.jsp"/>
|
||||
<jsp:include page="/jsp/common/include/script.jsp"/>
|
||||
<jsp:include page="/jsp/common/include/portal.jsp"/>
|
||||
|
||||
<style>
|
||||
/* ── 스윔레인 보드 ── */
|
||||
.menu-board { display: flex; gap: 12px; align-items: flex-start; }
|
||||
.menu-lane-strip { display: flex; gap: 12px; overflow-x: auto; padding-bottom: 10px; flex: 1 1 auto; }
|
||||
.menu-lane { background: #f1f3f5; border-radius: 10px; min-width: 230px; max-width: 230px; flex: 0 0 230px; }
|
||||
.menu-lane.unplaced { background: #fff4e6; border: 1px dashed #fd7e14; }
|
||||
.lane-header { display: flex; align-items: center; gap: 6px; padding: 10px 12px;
|
||||
border-bottom: 1px solid #dee2e6; cursor: grab; }
|
||||
.menu-lane.unplaced .lane-header { cursor: default; }
|
||||
.lane-title { font-weight: 700; font-size: 14px; flex: 1 1 auto; overflow: hidden;
|
||||
text-overflow: ellipsis; white-space: nowrap; }
|
||||
.lane-body { list-style: none; margin: 0; padding: 8px; min-height: 60px; }
|
||||
.menu-card { background: #fff; border: 1px solid #dee2e6; border-radius: 8px;
|
||||
padding: 8px 10px; margin-bottom: 8px; cursor: grab;
|
||||
box-shadow: 0 1px 2px rgba(0,0,0,0.06); }
|
||||
.menu-card.hidden-item, .menu-lane.hidden-item > .lane-header { opacity: 0.45; }
|
||||
.menu-card .card-name { font-size: 13px; font-weight: 600; }
|
||||
.menu-card .card-id { font-size: 11px; color: #868e96; }
|
||||
.menu-card .card-path { font-size: 11px; color: #495057; word-break: break-all; }
|
||||
.card-actions, .lane-actions { display: flex; gap: 4px; }
|
||||
.icon-btn { border: none; background: transparent; padding: 1px 3px; cursor: pointer;
|
||||
font-size: 14px; color: #495057; }
|
||||
.icon-btn:hover { color: #0d6efd; }
|
||||
.icon-btn.danger:hover { color: #dc3545; }
|
||||
.badge-src { font-size: 10px; }
|
||||
.role-check-group { display: flex; flex-wrap: wrap; gap: 4px 12px; }
|
||||
.role-check-group .form-check { min-width: 45%; }
|
||||
.dflt-hint { font-size: 11px; color: #868e96; }
|
||||
.board-toolbar { display: flex; gap: 6px; margin-bottom: 10px; align-items: center; }
|
||||
.board-toolbar .spacer { flex: 1 1 auto; }
|
||||
.sortable-placeholder { border: 2px dashed #adb5bd; border-radius: 8px; height: 48px;
|
||||
margin-bottom: 8px; background: #e9ecef; }
|
||||
.lane-placeholder { border: 2px dashed #adb5bd; border-radius: 10px; min-width: 230px; }
|
||||
</style>
|
||||
|
||||
<script language="javascript">
|
||||
// AuthorizeInterceptor 는 serviceType 을 "요청 파라미터"에서 읽으므로
|
||||
// 모든 .json 호출 URL 에 serviceType 을 부착한다 (누락 시 권한 조회 실패 → Access denied)
|
||||
var url = urlAddServiceType('<c:url value="/onl/apim/portalmenu/portalMenuMan.json" />');
|
||||
|
||||
// 화면 상태 (저장 버튼 클릭 시에만 서버 반영 — 드래그/토글은 클라이언트 상태만 변경)
|
||||
var state = { itemsById: {}, lanes: [], unplaced: [], roles: [] };
|
||||
var dirty = false;
|
||||
|
||||
function escapeHtml(s) {
|
||||
return String(s == null ? '' : s)
|
||||
.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
||||
.replace(/"/g, '"').replace(/'/g, ''');
|
||||
}
|
||||
|
||||
function loadAll(afterFn) {
|
||||
$.post(url, { cmd: 'LIST_ALL' }, function (data) {
|
||||
buildState(data);
|
||||
render();
|
||||
dirty = false;
|
||||
if (afterFn) afterFn();
|
||||
}, 'json').fail(ajaxFail);
|
||||
}
|
||||
|
||||
function buildState(data) {
|
||||
state.itemsById = {};
|
||||
state.roles = data.roles || [];
|
||||
(data.items || []).forEach(function (it) { state.itemsById[it.menuId] = it; });
|
||||
|
||||
var placedTop = [], childrenByParent = {}, unplaced = [];
|
||||
(data.items || []).forEach(function (it) {
|
||||
if (!it.placed) { unplaced.push(it.menuId); return; }
|
||||
if (!it.parentId) {
|
||||
placedTop.push(it);
|
||||
} else {
|
||||
(childrenByParent[it.parentId] = childrenByParent[it.parentId] || []).push(it);
|
||||
}
|
||||
});
|
||||
placedTop.sort(function (a, b) { return (a.sortOrder || 0) - (b.sortOrder || 0); });
|
||||
|
||||
state.lanes = placedTop.map(function (top) {
|
||||
var kids = (childrenByParent[top.menuId] || [])
|
||||
.sort(function (a, b) { return (a.sortOrder || 0) - (b.sortOrder || 0); })
|
||||
.map(function (c) { return { menuId: c.menuId, visibleYn: c.visibleYn || 'Y' }; });
|
||||
delete childrenByParent[top.menuId];
|
||||
return { menuId: top.menuId, visibleYn: top.visibleYn || 'Y', children: kids };
|
||||
});
|
||||
// 부모가 미배치/부재인 고아 배치는 미배치로 강등
|
||||
Object.keys(childrenByParent).forEach(function (pid) {
|
||||
childrenByParent[pid].forEach(function (c) { unplaced.push(c.menuId); });
|
||||
});
|
||||
state.unplaced = unplaced;
|
||||
}
|
||||
|
||||
// ── 렌더 ──
|
||||
function cardHtml(menuId, visibleYn, inLane) {
|
||||
var it = state.itemsById[menuId];
|
||||
if (!it) return '';
|
||||
var hidden = visibleYn === 'N';
|
||||
var srcBadge = it.sourceType === 'PORTAL'
|
||||
? '<span class="badge text-bg-secondary badge-src">기본</span>'
|
||||
: '<span class="badge text-bg-warning badge-src">커스텀</span>';
|
||||
var grpBadge = it.groupYn === 'Y' ? ' <span class="badge text-bg-info badge-src">그룹</span>' : '';
|
||||
var placeBtn = (!inLane && it.groupYn === 'Y')
|
||||
? '<button type="button" class="icon-btn" title="최상위 레인으로 배치" onclick="placeAsLane(\'' + menuId + '\')"><i class="bi bi-box-arrow-in-up"></i></button>'
|
||||
: '';
|
||||
var eyeBtn = inLane
|
||||
? '<button type="button" class="icon-btn" title="노출/숨김" onclick="toggleVisible(\'' + menuId + '\')"><i class="bi ' + (hidden ? 'bi-eye-slash' : 'bi-eye') + '"></i></button>'
|
||||
: '';
|
||||
return '<li class="menu-card' + (hidden ? ' hidden-item' : '') + '" data-menu-id="' + escapeHtml(menuId) + '">'
|
||||
+ '<div class="d-flex align-items-start">'
|
||||
+ '<div class="flex-grow-1">'
|
||||
+ '<div class="card-name">' + escapeHtml(it.menuName) + ' ' + srcBadge + grpBadge + '</div>'
|
||||
+ '<div class="card-id">' + escapeHtml(it.menuId) + '</div>'
|
||||
+ (it.menuPath ? '<div class="card-path">' + escapeHtml(it.menuPath) + '</div>' : '')
|
||||
+ '</div>'
|
||||
+ '<div class="card-actions">'
|
||||
+ placeBtn + eyeBtn
|
||||
+ '<button type="button" class="icon-btn" title="수정" onclick="openModal(\'' + menuId + '\')"><i class="bi bi-pencil"></i></button>'
|
||||
+ '<button type="button" class="icon-btn danger" level="W" title="' + (it.sourceType === 'PORTAL' ? '미배치로 이동' : '삭제') + '" onclick="minusItem(\'' + menuId + '\')"><i class="bi bi-dash-circle"></i></button>'
|
||||
+ '</div></div></li>';
|
||||
}
|
||||
|
||||
function laneHtml(lane) {
|
||||
var it = state.itemsById[lane.menuId];
|
||||
if (!it) return '';
|
||||
var hidden = lane.visibleYn === 'N';
|
||||
var sectionBadge = it.menuSection === 'MYPAGE'
|
||||
? '<span class="badge text-bg-primary badge-src">마이페이지</span>'
|
||||
: '<span class="badge text-bg-success badge-src">GNB</span>';
|
||||
var srcBadge = it.sourceType === 'PORTAL' ? '' : ' <span class="badge text-bg-warning badge-src">커스텀</span>';
|
||||
var cards = lane.children.map(function (c) { return cardHtml(c.menuId, c.visibleYn, true); }).join('');
|
||||
return '<div class="menu-lane' + (hidden ? ' hidden-item' : '') + '" data-menu-id="' + escapeHtml(lane.menuId) + '">'
|
||||
+ '<div class="lane-header lane-drag-handle">'
|
||||
+ '<span class="lane-title">' + escapeHtml(it.menuName) + '</span>'
|
||||
+ sectionBadge + srcBadge
|
||||
+ '<div class="lane-actions">'
|
||||
+ '<button type="button" class="icon-btn" title="노출/숨김" onclick="toggleVisible(\'' + lane.menuId + '\')"><i class="bi ' + (hidden ? 'bi-eye-slash' : 'bi-eye') + '"></i></button>'
|
||||
+ '<button type="button" class="icon-btn" title="수정" onclick="openModal(\'' + lane.menuId + '\')"><i class="bi bi-pencil"></i></button>'
|
||||
+ '<button type="button" class="icon-btn danger" level="W" title="레인 미배치" onclick="unplaceLane(\'' + lane.menuId + '\')"><i class="bi bi-dash-circle"></i></button>'
|
||||
+ '</div></div>'
|
||||
+ '<ul class="lane-body" data-lane-id="' + escapeHtml(lane.menuId) + '">' + cards + '</ul>'
|
||||
+ '</div>';
|
||||
}
|
||||
|
||||
function render() {
|
||||
var strip = state.lanes.map(laneHtml).join('');
|
||||
$('#laneStrip').html(strip);
|
||||
$('#unplacedBody').html(state.unplaced.map(function (id) { return cardHtml(id, 'Y', false); }).join(''));
|
||||
initSortables();
|
||||
buttonControl();
|
||||
}
|
||||
|
||||
function initSortables() {
|
||||
$('#laneStrip').sortable({
|
||||
items: '.menu-lane',
|
||||
handle: '.lane-drag-handle',
|
||||
placeholder: 'lane-placeholder',
|
||||
tolerance: 'pointer',
|
||||
update: function () { syncFromDom(); }
|
||||
});
|
||||
$('.lane-body, #unplacedBody').sortable({
|
||||
connectWith: '.lane-body, #unplacedBody',
|
||||
items: '.menu-card',
|
||||
placeholder: 'sortable-placeholder',
|
||||
tolerance: 'pointer',
|
||||
stop: function () { syncFromDom(); }
|
||||
});
|
||||
}
|
||||
|
||||
// 드래그 종료 후 DOM 순서 → 상태 동기화 (visible 값은 기존 상태 유지)
|
||||
function syncFromDom() {
|
||||
var visibleMap = {};
|
||||
state.lanes.forEach(function (l) {
|
||||
visibleMap[l.menuId] = l.visibleYn;
|
||||
l.children.forEach(function (c) { visibleMap[c.menuId] = c.visibleYn; });
|
||||
});
|
||||
|
||||
var lanes = [];
|
||||
$('#laneStrip .menu-lane').each(function () {
|
||||
var laneId = $(this).data('menu-id');
|
||||
var children = [];
|
||||
$(this).find('.lane-body .menu-card').each(function () {
|
||||
var id = $(this).data('menu-id');
|
||||
children.push({ menuId: id, visibleYn: visibleMap[id] || 'Y' });
|
||||
});
|
||||
lanes.push({ menuId: laneId, visibleYn: visibleMap[laneId] || 'Y', children: children });
|
||||
});
|
||||
var unplaced = [];
|
||||
$('#unplacedBody .menu-card').each(function () { unplaced.push($(this).data('menu-id')); });
|
||||
|
||||
state.lanes = lanes;
|
||||
state.unplaced = unplaced;
|
||||
dirty = true;
|
||||
render();
|
||||
}
|
||||
|
||||
// ── 카드/레인 조작 ──
|
||||
function toggleVisible(menuId) {
|
||||
state.lanes.forEach(function (l) {
|
||||
if (l.menuId === menuId) l.visibleYn = (l.visibleYn === 'N' ? 'Y' : 'N');
|
||||
l.children.forEach(function (c) {
|
||||
if (c.menuId === menuId) c.visibleYn = (c.visibleYn === 'N' ? 'Y' : 'N');
|
||||
});
|
||||
});
|
||||
dirty = true;
|
||||
render();
|
||||
}
|
||||
|
||||
function placeAsLane(menuId) {
|
||||
state.unplaced = state.unplaced.filter(function (id) { return id !== menuId; });
|
||||
state.lanes.push({ menuId: menuId, visibleYn: 'Y', children: [] });
|
||||
dirty = true;
|
||||
render();
|
||||
}
|
||||
|
||||
function minusItem(menuId) {
|
||||
var it = state.itemsById[menuId];
|
||||
if (!it) return;
|
||||
if (it.sourceType === 'PORTAL') {
|
||||
removeFromBoard(menuId);
|
||||
if (state.unplaced.indexOf(menuId) < 0) state.unplaced.push(menuId);
|
||||
dirty = true;
|
||||
render();
|
||||
} else {
|
||||
if (!confirm('커스텀 항목은 미배치 시 삭제됩니다.\n[' + it.menuName + '] 을(를) 삭제하시겠습니까?')) return;
|
||||
$.post(url, { cmd: 'DELETE', menuId: menuId }, function () {
|
||||
removeFromBoard(menuId);
|
||||
state.unplaced = state.unplaced.filter(function (id) { return id !== menuId; });
|
||||
delete state.itemsById[menuId];
|
||||
dirty = true;
|
||||
render();
|
||||
}, 'json').fail(ajaxFail);
|
||||
}
|
||||
}
|
||||
|
||||
function unplaceLane(laneId) {
|
||||
var lane = null;
|
||||
state.lanes = state.lanes.filter(function (l) {
|
||||
if (l.menuId === laneId) { lane = l; return false; }
|
||||
return true;
|
||||
});
|
||||
if (!lane) return;
|
||||
lane.children.forEach(function (c) {
|
||||
if (state.unplaced.indexOf(c.menuId) < 0) state.unplaced.push(c.menuId);
|
||||
});
|
||||
var it = state.itemsById[laneId];
|
||||
if (it && it.sourceType !== 'PORTAL') {
|
||||
if (confirm('커스텀 항목은 미배치 시 삭제됩니다.\n[' + it.menuName + '] 항목을 삭제하시겠습니까?')) {
|
||||
$.post(url, { cmd: 'DELETE', menuId: laneId }, function () {
|
||||
delete state.itemsById[laneId];
|
||||
render();
|
||||
}, 'json').fail(ajaxFail);
|
||||
} else {
|
||||
state.unplaced.push(laneId);
|
||||
}
|
||||
} else {
|
||||
if (state.unplaced.indexOf(laneId) < 0) state.unplaced.push(laneId);
|
||||
}
|
||||
dirty = true;
|
||||
render();
|
||||
}
|
||||
|
||||
function removeFromBoard(menuId) {
|
||||
state.lanes = state.lanes.filter(function (l) { return l.menuId !== menuId; });
|
||||
state.lanes.forEach(function (l) {
|
||||
l.children = l.children.filter(function (c) { return c.menuId !== menuId; });
|
||||
});
|
||||
}
|
||||
|
||||
// ── 저장 / 초기화 / 캐시 Reload ──
|
||||
function savePlacements() {
|
||||
// 미배치 상태의 커스텀 항목은 저장 시 삭제 처리 (스펙: 커스텀은 미배치=삭제)
|
||||
var unplacedAdmin = state.unplaced.filter(function (id) {
|
||||
var it = state.itemsById[id];
|
||||
return it && it.sourceType !== 'PORTAL';
|
||||
});
|
||||
if (unplacedAdmin.length > 0) {
|
||||
var names = unplacedAdmin.map(function (id) { return state.itemsById[id].menuName; }).join(', ');
|
||||
if (!confirm('미배치된 커스텀 항목은 저장 시 삭제됩니다: ' + names + '\n계속하시겠습니까?')) return;
|
||||
}
|
||||
|
||||
var rows = [];
|
||||
state.lanes.forEach(function (l, i) {
|
||||
rows.push({ menuId: l.menuId, parentId: null, sortOrder: (i + 1) * 10, visibleYn: l.visibleYn });
|
||||
l.children.forEach(function (c, j) {
|
||||
rows.push({ menuId: c.menuId, parentId: l.menuId, sortOrder: (j + 1) * 10, visibleYn: c.visibleYn });
|
||||
});
|
||||
});
|
||||
|
||||
var deleteCalls = unplacedAdmin.map(function (id) {
|
||||
return $.post(url, { cmd: 'DELETE', menuId: id });
|
||||
});
|
||||
$.when.apply($, deleteCalls).always(function () {
|
||||
$.ajax({
|
||||
url: url + '&cmd=TRANSACTION_PLACEMENT',
|
||||
type: 'POST',
|
||||
contentType: 'application/json; charset=utf-8',
|
||||
data: JSON.stringify(rows)
|
||||
}).done(function () {
|
||||
dirty = false;
|
||||
loadAll(function () {
|
||||
if (confirm('배치가 저장되었습니다.\n포탈 메뉴 캐시를 즉시 반영(Reload)하시겠습니까?\n(미반영 시 캐시 TTL 후 자동 반영)')) {
|
||||
reloadPortalCache();
|
||||
}
|
||||
});
|
||||
}).fail(ajaxFail);
|
||||
});
|
||||
}
|
||||
|
||||
function initializeMenus() {
|
||||
if (!confirm('메뉴 초기화 시 포탈 기본(menu.yml) 값으로 복원되고\n커스텀 항목은 모두 삭제됩니다. 계속하시겠습니까?')) return;
|
||||
$.post(url, { cmd: 'INITIALIZE' }, function () {
|
||||
loadAll(function () { alert('메뉴가 초기화되었습니다.'); });
|
||||
}, 'json').fail(ajaxFail);
|
||||
}
|
||||
|
||||
// ── 캐시 Reload ──
|
||||
var reloadModal;
|
||||
var reloadBtnHtml;
|
||||
|
||||
function reloadPortalCache() {
|
||||
var $btn = $('#btnReloadCache');
|
||||
if ($btn.prop('disabled')) return;
|
||||
if (reloadBtnHtml === undefined) reloadBtnHtml = $btn.html();
|
||||
$btn.prop('disabled', true)
|
||||
.html('<span class="spinner-border spinner-border-sm"></span> 리로드 중...');
|
||||
|
||||
$.post(url, { cmd: 'TRANSACTION_RELOAD' }, function (result) {
|
||||
showReloadResult(result);
|
||||
}, 'json').fail(ajaxFail).always(function () {
|
||||
$btn.prop('disabled', false).html(reloadBtnHtml);
|
||||
});
|
||||
}
|
||||
|
||||
function showReloadResult(result) {
|
||||
if (!result) {
|
||||
alert('캐시 리로드 실패: 알 수 없는 오류');
|
||||
return;
|
||||
}
|
||||
|
||||
var servers = result.servers || [];
|
||||
var $summary = $('#reloadSummary');
|
||||
$summary.removeClass('alert-success alert-danger alert-warning')
|
||||
.addClass(result.success ? 'alert-success'
|
||||
: (result.successCount > 0 ? 'alert-warning' : 'alert-danger'))
|
||||
.text(result.message || '');
|
||||
|
||||
var rows = '';
|
||||
servers.forEach(function (s) {
|
||||
var badge = s.success
|
||||
? '<span class="badge text-bg-success">성공</span>'
|
||||
: '<span class="badge text-bg-danger">실패</span>';
|
||||
var detail = s.success
|
||||
? ('항목 ' + (s.itemCount == null ? '-' : s.itemCount) + '건'
|
||||
+ (s.reloadedAt ? ' / ' + escapeHtml(String(s.reloadedAt)) : ''))
|
||||
: escapeHtml(String(s.message || '-'));
|
||||
rows += '<tr>'
|
||||
+ '<td class="text-center">' + badge + '</td>'
|
||||
+ '<td style="word-break:break-all;">' + escapeHtml(String(s.url || '')) + '</td>'
|
||||
+ '<td>' + detail + '</td>'
|
||||
+ '<td class="text-end">' + (s.elapsedMs == null ? '-' : s.elapsedMs + 'ms') + '</td>'
|
||||
+ '</tr>';
|
||||
});
|
||||
if (rows === '') {
|
||||
rows = '<tr><td colspan="4" class="text-center text-muted">'
|
||||
+ '호출 대상 서버가 없습니다. 아래 설정 안내를 확인하세요.</td></tr>';
|
||||
}
|
||||
$('#reloadResultBody').html(rows);
|
||||
|
||||
if (!reloadModal) reloadModal = new bootstrap.Modal(document.getElementById('reloadResultModal'));
|
||||
reloadModal.show();
|
||||
}
|
||||
|
||||
function ajaxFail(xhr) {
|
||||
var msg = '처리 중 오류가 발생했습니다.';
|
||||
try {
|
||||
var body = JSON.parse(xhr.responseText);
|
||||
if (body.errorMsg) msg = body.errorMsg;
|
||||
else if (body.message) msg = body.message;
|
||||
} catch (e) { /* ignore */ }
|
||||
alert(msg + ' (HTTP ' + xhr.status + ')');
|
||||
}
|
||||
|
||||
// ── 추가/수정 모달 ──
|
||||
var menuModal;
|
||||
|
||||
function roleChecksHtml(namePrefix, checkedCsv) {
|
||||
var checked = (checkedCsv || '').split(',').map(function (s) { return s.trim(); }).filter(Boolean);
|
||||
var html = '';
|
||||
var all = [{ roleCode: 'AUTHENTICATED', roleName: '로그인 사용자', roleType: '-' }].concat(state.roles);
|
||||
all.forEach(function (r) {
|
||||
var id = namePrefix + '_' + r.roleCode;
|
||||
html += '<div class="form-check form-check-sm">'
|
||||
+ '<input class="form-check-input" type="checkbox" name="' + namePrefix + '" id="' + id + '" value="' + escapeHtml(r.roleCode) + '"'
|
||||
+ (checked.indexOf(r.roleCode) >= 0 ? ' checked' : '') + '>'
|
||||
+ '<label class="form-check-label" for="' + id + '" style="font-size:12px;">'
|
||||
+ escapeHtml(r.roleName) + ' <span class="text-muted">(' + escapeHtml(r.roleCode) + ')</span></label>'
|
||||
+ '</div>';
|
||||
});
|
||||
return html;
|
||||
}
|
||||
|
||||
function openModal(menuId) {
|
||||
var isNew = !menuId;
|
||||
var it = isNew ? { sourceType: 'ADMIN', groupYn: 'N', menuSection: 'GNB', newWindowYn: 'N' }
|
||||
: state.itemsById[menuId];
|
||||
if (!it) return;
|
||||
var isPortal = it.sourceType === 'PORTAL';
|
||||
|
||||
$('#modalTitle').text(isNew ? '메뉴 추가' : '메뉴 수정' + (isPortal ? ' (기본 항목)' : ''));
|
||||
$('#fMenuId').val(it.menuId || '').prop('readonly', !isNew);
|
||||
$('#fMenuName').val(it.menuName || '');
|
||||
$('#fMenuPath').val(it.menuPath || '');
|
||||
$('#fIconClass').val(it.iconClass || '');
|
||||
$('#fMenuSection').val(it.menuSection || 'GNB').prop('disabled', isPortal);
|
||||
$('#fGroupYn').prop('checked', it.groupYn === 'Y').prop('disabled', isPortal);
|
||||
$('#fNewWindowYn').prop('checked', it.newWindowYn === 'Y');
|
||||
$('#exposeRoleChecks').html(roleChecksHtml('exposeRole', it.exposeRoles));
|
||||
$('#accessRoleChecks').html(roleChecksHtml('accessRole', it.accessRoles));
|
||||
|
||||
// 기본 항목: 기본값(DFLT_*) 병기
|
||||
if (isPortal) {
|
||||
$('#hintMenuName').text('기본값: ' + (it.dfltMenuName || '-'));
|
||||
$('#hintMenuPath').text('기본값: ' + (it.dfltMenuPath || '-'));
|
||||
$('#hintExposeRoles').text('기본값: ' + (it.dfltExposeRoles || '전체 노출'));
|
||||
$('#hintAccessRoles').text('기본값: ' + (it.dfltAccessRoles || '제한 없음'));
|
||||
$('.dflt-hint').show();
|
||||
} else {
|
||||
$('.dflt-hint').hide();
|
||||
}
|
||||
|
||||
$('#modalMode').val(isNew ? 'INSERT' : 'UPDATE');
|
||||
menuModal.show();
|
||||
}
|
||||
|
||||
function submitModal() {
|
||||
var cmd = $('#modalMode').val();
|
||||
var data = {
|
||||
cmd: cmd,
|
||||
menuId: $('#fMenuId').val().trim(),
|
||||
menuName: $('#fMenuName').val().trim(),
|
||||
menuPath: $('#fMenuPath').val().trim(),
|
||||
iconClass: $('#fIconClass').val().trim(),
|
||||
menuSection: $('#fMenuSection').val(),
|
||||
groupYn: $('#fGroupYn').is(':checked') ? 'Y' : 'N',
|
||||
newWindowYn: $('#fNewWindowYn').is(':checked') ? 'Y' : 'N',
|
||||
exposeRoles: $('input[name=exposeRole]:checked').map(function () { return this.value; }).get().join(','),
|
||||
accessRoles: $('input[name=accessRole]:checked').map(function () { return this.value; }).get().join(',')
|
||||
};
|
||||
if (!data.menuId) { alert('메뉴 ID 를 입력하세요.'); return; }
|
||||
if (cmd === 'INSERT' && !/^[a-z0-9-]+$/.test(data.menuId)) {
|
||||
alert('메뉴 ID 는 kebab-case(소문자/숫자/하이픈)만 허용합니다.');
|
||||
return;
|
||||
}
|
||||
if (!data.menuName) { alert('노출명을 입력하세요.'); return; }
|
||||
|
||||
$.post(url, data, function () {
|
||||
menuModal.hide();
|
||||
loadAll();
|
||||
}, 'json').fail(ajaxFail);
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
menuModal = new bootstrap.Modal(document.getElementById('menuModal'));
|
||||
loadAll();
|
||||
|
||||
window.addEventListener('beforeunload', function (e) {
|
||||
if (dirty) { e.preventDefault(); e.returnValue = ''; }
|
||||
});
|
||||
});
|
||||
</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" id="content_middle">
|
||||
|
||||
<div class="title">포탈 메뉴 관리</div>
|
||||
|
||||
<div class="board-toolbar">
|
||||
<button type="button" class="btn btn-sm btn-primary" level="W" onclick="openModal()">
|
||||
<i class="bi bi-plus-lg"></i> 메뉴 추가</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-danger" level="W" onclick="initializeMenus()">
|
||||
<i class="bi bi-arrow-counterclockwise"></i> 메뉴 초기화</button>
|
||||
<span class="spacer"></span>
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary" id="btnReloadCache" level="W" onclick="reloadPortalCache()">
|
||||
<i class="bi bi-arrow-repeat"></i> 캐시 Reload</button>
|
||||
<button type="button" class="btn btn-sm btn-success" level="W" onclick="savePlacements()">
|
||||
<i class="bi bi-save"></i> 저장</button>
|
||||
</div>
|
||||
|
||||
<div style="background:#e7f1ff; border:1px solid #0d6efd33; padding:8px 15px; margin-bottom:10px; border-radius:6px; font-size:12px; color:#31507a;">
|
||||
드래그로 위치를 조절한 뒤 <strong>저장</strong> 버튼으로 일괄 반영합니다.
|
||||
기본 항목([기본] 배지)은 [-] 시 <strong>미배치</strong>로만 이동하고, 커스텀 항목은 미배치 시 <strong>삭제</strong>됩니다.
|
||||
저장 후 <strong>캐시 Reload</strong> 를 실행해야 포탈에 즉시 반영됩니다(미실행 시 TTL 경과 후 반영).
|
||||
<br>
|
||||
캐시 Reload 대상 서버는 포탈 프로퍼티 <code>Portal / portal.internal.menu-reload-url</code> 로 설정합니다.
|
||||
포탈이 <strong>다중 서버(이중화)</strong> 로 기동된 경우 모든 서버의 리로드 URL 을
|
||||
<strong>콤마(,) 또는 줄바꿈</strong> 으로 나열해야 전체 서버에 반영됩니다
|
||||
(예: <code>http://10.0.0.11:39130/internal/menu/reload, http://10.0.0.12:39130/internal/menu/reload</code>).
|
||||
서버별 요청 타임아웃은 <strong>3초</strong> 이며, 실행 결과는 서버별로 표시됩니다.
|
||||
</div>
|
||||
|
||||
<div class="menu-board">
|
||||
<div class="menu-lane-strip" id="laneStrip"><!-- 렌더링 --></div>
|
||||
<div class="menu-lane unplaced">
|
||||
<div class="lane-header">
|
||||
<span class="lane-title">미배치</span>
|
||||
<span class="badge text-bg-warning badge-src">보관함</span>
|
||||
</div>
|
||||
<ul class="lane-body" id="unplacedBody"><!-- 렌더링 --></ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 추가/수정 모달 -->
|
||||
<div class="modal fade" id="menuModal" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="modalTitle">메뉴 추가</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="닫기"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<input type="hidden" id="modalMode" value="INSERT">
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label" for="fMenuId">메뉴 ID (kebab-case)</label>
|
||||
<input type="text" class="form-control form-control-sm" id="fMenuId" placeholder="ex) custom-link">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label" for="fMenuName">노출명</label>
|
||||
<input type="text" class="form-control form-control-sm" id="fMenuName">
|
||||
<div class="dflt-hint" id="hintMenuName"></div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label" for="fMenuPath">Path (그룹은 생략 가능)</label>
|
||||
<input type="text" class="form-control form-control-sm" id="fMenuPath" placeholder="/example 또는 https://...">
|
||||
<div class="dflt-hint" id="hintMenuPath"></div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label" for="fIconClass">아이콘 클래스 (마이페이지용, ex: fa-users)</label>
|
||||
<input type="text" class="form-control form-control-sm" id="fIconClass">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label" for="fMenuSection">섹션</label>
|
||||
<select class="form-select form-select-sm" id="fMenuSection">
|
||||
<option value="GNB">GNB (상단 메뉴)</option>
|
||||
<option value="MYPAGE">마이페이지</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-4 d-flex align-items-end">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="fGroupYn">
|
||||
<label class="form-check-label" for="fGroupYn">그룹(상위 메뉴)</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4 d-flex align-items-end">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="fNewWindowYn">
|
||||
<label class="form-check-label" for="fNewWindowYn">새 창 열기</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">노출 권한 (미선택 = 전체 노출)</label>
|
||||
<div class="role-check-group" id="exposeRoleChecks"></div>
|
||||
<div class="dflt-hint" id="hintExposeRoles"></div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">접근 권한 (미선택 = 제한 없음)</label>
|
||||
<div class="role-check-group" id="accessRoleChecks"></div>
|
||||
<div class="dflt-hint" id="hintAccessRoles"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-sm btn-secondary" data-bs-dismiss="modal">취소</button>
|
||||
<button type="button" class="btn btn-sm btn-primary" level="W" onclick="submitModal()">저장</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 캐시 Reload 결과 -->
|
||||
<div class="modal fade" id="reloadResultModal" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">포탈 메뉴 캐시 Reload 결과</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="닫기"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="alert py-2 px-3" id="reloadSummary" style="font-size:13px;"></div>
|
||||
<table class="table table-sm table-bordered align-middle" style="font-size:12px;">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th style="width:70px;" class="text-center">결과</th>
|
||||
<th>서버 URL</th>
|
||||
<th style="width:38%;">상세</th>
|
||||
<th style="width:80px;" class="text-end">소요</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="reloadResultBody"></tbody>
|
||||
</table>
|
||||
<div class="text-muted" style="font-size:12px;">
|
||||
대상 서버는 포탈 프로퍼티 <code>Portal / portal.internal.menu-reload-url</code> 에
|
||||
콤마(,) 또는 줄바꿈으로 나열합니다. 서버별 타임아웃 3초, 전 서버 병렬 호출입니다.
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-sm btn-secondary" data-bs-dismiss="modal">닫기</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div><!-- end content_middle -->
|
||||
</div><!-- end right_box -->
|
||||
</body>
|
||||
</html>
|
||||
@@ -19,16 +19,42 @@
|
||||
var url_view = '<c:url value="/onl/apim/portalnotice/portalNoticeMan.view" />';
|
||||
var combo;
|
||||
|
||||
// 코드 상수 — PortalNoticeUI 와 동기화
|
||||
var NOTICE_TYPE_INCIDENT = '3';
|
||||
|
||||
// IncidentState 라벨 — 상세 화면과 동일하게 유지
|
||||
var STATE_LABELS = {
|
||||
'INVESTIGATING': '조사중',
|
||||
'IDENTIFIED': '원인 파악',
|
||||
'MONITORING': '모니터링',
|
||||
'RESOLVED': '해소',
|
||||
'CANCELED': '취소'
|
||||
};
|
||||
|
||||
function formatIncidentState(cellvalue, options, rowObject) {
|
||||
if (!cellvalue) return '';
|
||||
var label = STATE_LABELS[cellvalue] || cellvalue;
|
||||
var closed = (cellvalue === 'RESOLVED' || cellvalue === 'CANCELED');
|
||||
return closed
|
||||
? '<span title="' + cellvalue + '">' + label + '</span>'
|
||||
: '<span style="color: red;" title="' + cellvalue + '">' + label + '</span>';
|
||||
}
|
||||
|
||||
function formatNoticeType(cellvalue, options, rowObject) {
|
||||
if (!combo || !combo.noticeTypeList) {
|
||||
return cellvalue;
|
||||
}
|
||||
for (var i = 0; i < combo.noticeTypeList.length; i++) {
|
||||
if (combo.noticeTypeList[i].CODE == cellvalue) {
|
||||
return combo.noticeTypeList[i].NAME;
|
||||
var name = cellvalue;
|
||||
if (combo && combo.noticeTypeList) {
|
||||
for (var i = 0; i < combo.noticeTypeList.length; i++) {
|
||||
if (combo.noticeTypeList[i].CODE == cellvalue) {
|
||||
name = combo.noticeTypeList[i].NAME;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return cellvalue;
|
||||
// 장애인데 종료 시각이 없으면 아직 진행 중이다
|
||||
if (cellvalue == NOTICE_TYPE_INCIDENT && rowObject && !rowObject.endAt) {
|
||||
return '<span style="color: red;">' + name + '-진행중</span>';
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
function formatuseYn(cellvalue, options, rowObject) {
|
||||
@@ -115,14 +141,15 @@
|
||||
searchNoticeSubject: $('input[name=searchNoticeSubject]').val(),
|
||||
searchNoticeDetail: $('input[name=searchNoticeDetail]').val()
|
||||
},
|
||||
colNames:['id', 'No.', '게시유형', '제목', '고정여부', '사용', '마지막 수정일', '등록일', '등록자', '조회수'],
|
||||
colNames:['id', 'No.', '게시유형', '제목', '고정여부', '사용', '장애상태', '마지막 수정일', '등록일', '등록자', '조회수'],
|
||||
colModel:[
|
||||
{ name : 'id' , align:'center', key:true, hidden:true},
|
||||
{ name : 'rowNum' , align:'center', width:60 },
|
||||
{ name : 'noticeType' , align:'center', width:60, formatter: formatNoticeType },
|
||||
{ name : 'noticeType' , align:'center', width:100, formatter: formatNoticeType },
|
||||
{ name : 'noticeSubject' , align:'left' , width:300, formatter: formatFile },
|
||||
{ name : 'fixYn' , align:'center', width:60, formatter: formatFixYn },
|
||||
{ name : 'useYn' , align:'center', width:60, formatter: formatuseYn },
|
||||
{ name : 'state' , align:'center', width:90, sortable:false, formatter: formatIncidentState },
|
||||
{ name : 'lastModifiedDate', align:'center', width:120 , formatter: timeStampFormat},
|
||||
{ name : 'createdDate' , align:'center', width:120, formatter: timeStampFormat },
|
||||
{ name : 'inquirerName' , align:'center', width:100 },
|
||||
|
||||
@@ -32,10 +32,35 @@
|
||||
// 영향 API 목록 (UI 상태)
|
||||
var affectedApis = [];
|
||||
|
||||
// 장애 타임라인 (UI 상태)
|
||||
var currentIncidentId = null;
|
||||
var currentIncidentState = null;
|
||||
|
||||
// 종결 상태 — 재전이 불가 (재발은 새 장애로 등록)
|
||||
var TERMINAL_STATES = ['RESOLVED', 'CANCELED'];
|
||||
|
||||
// IncidentState 라벨 — 상태 콤보 문구와 동일하게 유지
|
||||
var STATE_LABELS = {
|
||||
'INVESTIGATING': '조사중',
|
||||
'IDENTIFIED': '원인 파악',
|
||||
'MONITORING': '모니터링',
|
||||
'RESOLVED': '해소',
|
||||
'CANCELED': '취소'
|
||||
};
|
||||
|
||||
function isIncidentType(t) {
|
||||
return t === NOTICE_TYPE_INCIDENT || t === NOTICE_TYPE_MAINTENANCE;
|
||||
}
|
||||
|
||||
function stateLabel(code) {
|
||||
if (!code) return '';
|
||||
return (STATE_LABELS[code] || code) + ' (' + code + ')';
|
||||
}
|
||||
|
||||
function escapeHtml(text) {
|
||||
return $('<div>').text(text == null ? '' : text).html();
|
||||
}
|
||||
|
||||
function init() {
|
||||
var key = "${param.id}";
|
||||
isDetail = key != "" && key != "null";
|
||||
@@ -76,13 +101,57 @@
|
||||
// INCIDENT 만 상태 영역 표시
|
||||
if (t === NOTICE_TYPE_INCIDENT) {
|
||||
$('.incident-state-row').show();
|
||||
$('.incident-endat-hint').show();
|
||||
} else {
|
||||
$('.incident-state-row').hide();
|
||||
$('.incident-endat-hint').hide();
|
||||
}
|
||||
// 장애는 종결(해소·취소) 후에만 종료 시각을 손볼 수 있다. 진행 중에는 상태를 따른다.
|
||||
var closed = $.inArray(currentIncidentState, TERMINAL_STATES) >= 0;
|
||||
$('#endAt').prop('readonly', t === NOTICE_TYPE_INCIDENT && !closed);
|
||||
$('.incident-endat-hint').text(closed
|
||||
? '※ 종결된 장애입니다. 실제 복구 시각으로 수정할 수 있습니다.'
|
||||
: '※ 타임라인에서 해소·취소로 전이하면 자동 입력됩니다.');
|
||||
} else {
|
||||
$('.incident-row').hide();
|
||||
$('.incident-state-row').hide();
|
||||
$('.incident-endat-hint').hide();
|
||||
$('#endAt').prop('readonly', false);
|
||||
}
|
||||
// 양식(템플릿)은 장애 공지에만 쓴다 - 점검은 문구 체계가 다르다
|
||||
$('.template-row').toggle(t === NOTICE_TYPE_INCIDENT);
|
||||
|
||||
// 타임라인은 등록된 장애(수정 모드)에서만 다룬다
|
||||
if (t === NOTICE_TYPE_INCIDENT && isDetail && currentIncidentId) {
|
||||
$('#timelineSection').show();
|
||||
} else {
|
||||
$('#timelineSection').hide();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 장애 공지 양식(제목·본문)을 서버에서 받아 채운다.
|
||||
* 문구는 PTL_PROPERTY 'djb.apistatus.draft.*' 이며 자동 탐지 초안과 같은 양식이다.
|
||||
*
|
||||
* @param force true 면 이미 입력한 제목·본문도 (확인 후) 덮어쓴다
|
||||
*/
|
||||
function applyTemplate(force) {
|
||||
if (force) {
|
||||
var hasInput = $.trim($('#noticeSubject').val()) !== '' || !$('#contents').summernote('isEmpty');
|
||||
if (hasInput && !confirm('현재 입력한 제목·본문을 양식으로 덮어씁니다. 계속하시겠습니까?')) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
$.ajax({
|
||||
type: "POST", url: url, dataType: "json",
|
||||
data: {cmd: 'DRAFT_TEMPLATE', apisJson: JSON.stringify(affectedApis)},
|
||||
success: function (data) {
|
||||
if (!data) return;
|
||||
$('#noticeSubject').val(data.subject || '');
|
||||
$('#contents').summernote('code', data.detail || '');
|
||||
},
|
||||
error: function (e) { alert(e.responseText); }
|
||||
});
|
||||
}
|
||||
|
||||
function decodeHTMLEntities(text) {
|
||||
@@ -148,8 +217,11 @@
|
||||
// AuthorizeInterceptor 가 정상적으로 권한 인식. window.open 직접 호출 시 권한 부족 발생.
|
||||
// 본 페이지는 summernote 본문 에디터 등 다른 iframe 이 함께 존재할 수 있어
|
||||
// frames[0] 가정은 위험 → DOM 에서 .ui-dialog 내 iframe 중 src=apiGroupMan 인 것을 식별.
|
||||
// 공지 영향 API 는 포탈에 실제 노출되는 것만 고르면 되므로 두 조건을 켠 채로 연다.
|
||||
// (타 화면에서 여는 팝업은 파라미터가 없어 해제 상태 — 팝업 기본값은 건드리지 않는다)
|
||||
var selectedIds = affectedApis.map(function(a) { return a.apiId; }).join(',');
|
||||
var url2 = apiPopupUrl + '?cmd=POPUP&selectedApiIds=' + encodeURIComponent(selectedIds);
|
||||
var url2 = apiPopupUrl + '?cmd=POPUP&searchApiSpecYn=Y&searchPortalDisplayYn=Y'
|
||||
+ '&selectedApiIds=' + encodeURIComponent(selectedIds);
|
||||
var args = {};
|
||||
showModal(url2, args, 1280, 860, function () {
|
||||
var doc = args.self && args.self.document;
|
||||
@@ -192,6 +264,150 @@
|
||||
renderAffectedApis();
|
||||
}
|
||||
|
||||
// ───────────────────── 장애 처리 타임라인 ─────────────────────
|
||||
|
||||
/** 현재 상태 기준으로 전이 가능한 선택지만 남긴다. 실제 차단은 서버가 한다. */
|
||||
function applyStateChangeGuard() {
|
||||
var terminal = $.inArray(currentIncidentState, TERMINAL_STATES) >= 0;
|
||||
|
||||
$('#timelineStateChange').prop('disabled', terminal);
|
||||
if (terminal) {
|
||||
$('#timelineStateChange').prop('checked', false);
|
||||
$('#timelineNextState').prop('disabled', true).val('');
|
||||
}
|
||||
$('#timelineClosedHint').toggle(terminal);
|
||||
$('#timelineResolveHint').toggle(!terminal);
|
||||
|
||||
// 현재와 동일한 상태로는 전이할 수 없다
|
||||
$('#timelineNextState option').each(function() {
|
||||
var v = $(this).val();
|
||||
$(this).prop('disabled', v !== '' && v === currentIncidentState);
|
||||
});
|
||||
if ($('#timelineNextState').val() === currentIncidentState) {
|
||||
$('#timelineNextState').val('');
|
||||
}
|
||||
}
|
||||
|
||||
function loadTimeline() {
|
||||
if (!currentIncidentId) {
|
||||
renderTimeline([]);
|
||||
return;
|
||||
}
|
||||
$.ajax({
|
||||
type: "POST", url: url, dataType: "json",
|
||||
data: {cmd: 'TIMELINE_LIST', incidentId: currentIncidentId},
|
||||
success: function (rows) { renderTimeline(rows || []); },
|
||||
error: function (e) { alert(e.responseText); }
|
||||
});
|
||||
}
|
||||
|
||||
function renderTimeline(rows) {
|
||||
var $tb = $('#timelineTbody');
|
||||
$tb.empty();
|
||||
if (!rows.length) {
|
||||
$tb.append('<tr><td colspan="6" style="text-align:center;color:#999;">등록된 타임라인이 없습니다.</td></tr>');
|
||||
return;
|
||||
}
|
||||
$.each(rows, function(_, row) {
|
||||
var author = row.authorType === 'SYSTEM' ? 'System' : 'Admin';
|
||||
var visible = row.visibleYn === 'Y';
|
||||
var $tr = $('<tr>');
|
||||
$tr.append('<td style="text-align:center;white-space:nowrap;">' + escapeHtml(row.eventAt) + '</td>');
|
||||
$tr.append('<td style="text-align:center;">' + author + '</td>');
|
||||
$tr.append('<td style="text-align:center;white-space:nowrap;">' + (row.stateAfter ? escapeHtml(stateLabel(row.stateAfter)) : '-') + '</td>');
|
||||
$tr.append('<td>' + escapeHtml(row.body).replace(/\n/g, '<br>') + '</td>');
|
||||
|
||||
// 현재 상태(라벨) + 눌렀을 때 바뀔 상태(버튼) 를 나눠 표기 — 버튼 하나면 의미가 모호하다
|
||||
var $visibleTd = $('<td style="text-align:center;white-space:nowrap;">');
|
||||
$('<span>')
|
||||
.text(visible ? '공개중' : '비공개')
|
||||
.css({color: visible ? '#1B8C4A' : '#999', 'margin-right': '5px'})
|
||||
.appendTo($visibleTd);
|
||||
$('<button type="button" class="cssbtn" level="W" status="DETAIL" style="min-width:0;padding:3px 8px;">')
|
||||
.text(visible ? '비공개로' : '공개로')
|
||||
.click(function() { toggleTimelineVisible(row.timelineId, visible ? 'N' : 'Y'); })
|
||||
.appendTo($visibleTd);
|
||||
$tr.append($visibleTd);
|
||||
|
||||
var $deleteTd = $('<td style="text-align:center;">');
|
||||
$('<button type="button" class="cssbtn smallBtn" level="W" status="DETAIL" style="min-width:0;width:90%;">')
|
||||
.text('삭제')
|
||||
.click(function() { deleteTimeline(row.timelineId); })
|
||||
.appendTo($deleteTd);
|
||||
$tr.append($deleteTd);
|
||||
|
||||
$tb.append($tr);
|
||||
});
|
||||
buttonControl(true);
|
||||
}
|
||||
|
||||
function addTimeline() {
|
||||
if (!currentIncidentId) {
|
||||
alert('장애 정보를 먼저 저장하여 주십시오.');
|
||||
return;
|
||||
}
|
||||
var body = $('#timelineBody').val();
|
||||
if (!body || !$.trim(body)) {
|
||||
alert('타임라인 메세지를 입력하여 주십시오.');
|
||||
$('#timelineBody').focus();
|
||||
return;
|
||||
}
|
||||
var stateChange = $('#timelineStateChange').is(':checked');
|
||||
var nextState = $('#timelineNextState').val();
|
||||
if (stateChange && !nextState) {
|
||||
alert('변경할 상태를 선택하여 주십시오.');
|
||||
return;
|
||||
}
|
||||
$.ajax({
|
||||
type: "POST", url: url, dataType: "json",
|
||||
data: {cmd: 'TIMELINE_ADD', incidentId: currentIncidentId, body: body,
|
||||
stateChange: stateChange, nextState: nextState},
|
||||
success: function (data) {
|
||||
$('#timelineBody').val('');
|
||||
$('#timelineStateChange').prop('checked', false).trigger('change');
|
||||
// 상태 전이 시 서버가 상태/이전 상태/종료 시각을 함께 갱신한다
|
||||
$('#stateText').text(data.state ? stateLabel(data.state) : '');
|
||||
$('#previousState').text(data.previousState ? stateLabel(data.previousState) : '없음');
|
||||
$('#endAt').val(data.endAt || '');
|
||||
currentIncidentState = data.state || null;
|
||||
applyStateChangeGuard();
|
||||
// 종결 전이 직후부터 종료 시각을 손볼 수 있어야 한다
|
||||
toggleIncidentFields();
|
||||
loadTimeline();
|
||||
},
|
||||
error: function (e) { alert(e.responseText); }
|
||||
});
|
||||
}
|
||||
|
||||
function toggleTimelineVisible(timelineId, visibleYn) {
|
||||
$.ajax({
|
||||
type: "POST", url: url,
|
||||
data: {cmd: 'TIMELINE_VISIBLE', timelineId: timelineId, visibleYn: visibleYn},
|
||||
success: loadTimeline,
|
||||
error: function (e) { alert(e.responseText); }
|
||||
});
|
||||
}
|
||||
|
||||
function deleteTimeline(timelineId) {
|
||||
if (confirm("<%= localeMessage.getString("common.checkDelete")%>") != true) return;
|
||||
$.ajax({
|
||||
type: "POST", url: url,
|
||||
data: {cmd: 'TIMELINE_DELETE', timelineId: timelineId},
|
||||
success: loadTimeline,
|
||||
error: function (e) { alert(e.responseText); }
|
||||
});
|
||||
}
|
||||
|
||||
/** 장애/점검 공지는 최초 게시 후 게시유형을 바꿀 수 없다. */
|
||||
function lockNoticeType(noticeType) {
|
||||
if (!isIncidentType(noticeType)) return;
|
||||
$('#noticeType').prop('disabled', true);
|
||||
if (!$('#noticeTypeHidden').length) {
|
||||
$('#ajaxForm').append('<input type="hidden" id="noticeTypeHidden" name="noticeType">');
|
||||
}
|
||||
$('#noticeTypeHidden').val(noticeType);
|
||||
}
|
||||
|
||||
function detail(key) {
|
||||
if (!isDetail) return;
|
||||
$.ajax({
|
||||
@@ -207,17 +423,28 @@
|
||||
var decodedContent = decodeHTMLEntities(data.noticeDetail);
|
||||
$('#contents').summernote('code', decodedContent);
|
||||
|
||||
// 게시유형은 최초 저장 후 변경 불가 (disabled 는 전송되지 않아 hidden 으로 값을 유지)
|
||||
lockNoticeType(data.noticeType);
|
||||
|
||||
// 장애/점검 필드
|
||||
$('#summary').val(data.summary || '');
|
||||
$('#startedAt').val(data.startedAt || '');
|
||||
$('#endAt').val(data.endAt || '');
|
||||
$('#state').val(data.state || 'INVESTIGATING');
|
||||
$('#previousState').text(data.previousState || '없음');
|
||||
// 상태/이전 상태는 타임라인 게시로만 바뀐다 (직접 수정 불가)
|
||||
$('#stateText').text(data.state ? stateLabel(data.state) : '');
|
||||
$('#previousState').text(data.previousState ? stateLabel(data.previousState) : '없음');
|
||||
affectedApis = (data.affectedApis || []).map(function(a){
|
||||
return {apiId: a.apiId, apiName: a.apiName || ''};
|
||||
});
|
||||
renderAffectedApis();
|
||||
|
||||
currentIncidentId = data.incidentId || null;
|
||||
currentIncidentState = data.state || null;
|
||||
applyStateChangeGuard();
|
||||
toggleIncidentFields();
|
||||
if (data.noticeType === NOTICE_TYPE_INCIDENT && currentIncidentId) {
|
||||
loadTimeline();
|
||||
}
|
||||
|
||||
if (data.fileInfo) {
|
||||
fileInfo = {
|
||||
@@ -242,9 +469,25 @@
|
||||
|
||||
$('#noticeType').on('change', toggleIncidentFields);
|
||||
|
||||
// 신규 등록에서 장애를 고르면 빈 본문에 한해 양식을 자동으로 채운다.
|
||||
// (수정 모드나 이미 쓴 본문은 건드리지 않는다 - 덮어쓰려면 '양식 적용' 버튼)
|
||||
$('#noticeType').on('change', function () {
|
||||
if (!isDetail && $(this).val() === NOTICE_TYPE_INCIDENT && $('#contents').summernote('isEmpty')) {
|
||||
applyTemplate(false);
|
||||
}
|
||||
});
|
||||
$('#btn_applyTemplate').click(function () { applyTemplate(true); });
|
||||
|
||||
$('#btn_addAffectedApi').click(openApiPopup);
|
||||
$('#btn_removeAffectedApi').click(removeSelectedAffectedApis);
|
||||
|
||||
$('#timelineStateChange').on('change', function() {
|
||||
var checked = $(this).is(':checked');
|
||||
$('#timelineNextState').prop('disabled', !checked);
|
||||
if (!checked) $('#timelineNextState').val('');
|
||||
});
|
||||
$('#btn_addTimeline').click(addTimeline);
|
||||
|
||||
$('#fileInput').change(function(e) {
|
||||
var file = e.target.files[0];
|
||||
if (file) {
|
||||
@@ -273,6 +516,11 @@
|
||||
alert('점검 종료 시각을 입력해 주십시오.');
|
||||
return;
|
||||
}
|
||||
// datetime-local 값은 'YYYY-MM-DDTHH:mm' 이라 문자열 비교로 선후가 가려진다
|
||||
if ($('#endAt').val() && $('#endAt').val() < $('#startedAt').val()) {
|
||||
alert('종료 시각은 시작 시각보다 빠를 수 없습니다.');
|
||||
return;
|
||||
}
|
||||
if (affectedApis.length === 0) {
|
||||
alert('영향 API를 1건 이상 선택해 주십시오.');
|
||||
return;
|
||||
@@ -282,6 +530,10 @@
|
||||
if (confirm("<%= localeMessage.getString("common.checkSave")%>") != true) return;
|
||||
|
||||
var formData = new FormData($("#ajaxForm")[0]);
|
||||
// Summernote 그림 다이얼로그의 내부 파일 input(name="files")이 #ajaxForm 안에 있어
|
||||
// 선택한 이미지가 지워지지 않고 남아 있으면 여기서 함께 딸려 온다.
|
||||
// 본 화면은 첨부파일 기능을 쓰지 않으므로 항상 제거한다.
|
||||
formData.delete("files");
|
||||
formData.set("useYn", $("#useYn").is(":checked") ? "Y" : "N");
|
||||
formData.set("fixYn", $("#fixYn").is(":checked") ? "Y" : "N");
|
||||
formData.set("noticeDetail", $('#contents').summernote('code'));
|
||||
@@ -379,7 +631,12 @@
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>본문 <font color="red">*</font></th>
|
||||
<th>본문 <font color="red">*</font>
|
||||
<div class="template-row" style="display:none;margin-top:5px;">
|
||||
<button type="button" class="cssbtn smallBtn" id="btn_applyTemplate" level="W" status="DETAIL,NEW"
|
||||
style="min-width:0;width:90%;" title="장애 공지 양식으로 제목과 본문을 채웁니다.">양식 적용</button>
|
||||
</div>
|
||||
</th>
|
||||
<td colspan="3">
|
||||
<textarea id="contents" name="noticeDetail" style="width:100%;height:300px" data-required data-warning="본문을 입력하여 주십시오."></textarea>
|
||||
</td>
|
||||
@@ -394,21 +651,15 @@
|
||||
<th>종료</th>
|
||||
<td>
|
||||
<input type="datetime-local" id="endAt" name="endAt" style="width:90%" placeholder="해소시 자동 채움">
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="incident-row incident-state-row" style="display:none;">
|
||||
<th>상태</th>
|
||||
<td>
|
||||
<div class="select-style">
|
||||
<select id="state" name="state">
|
||||
<option value="INVESTIGATING">INVESTIGATING - 조사중</option>
|
||||
<option value="IDENTIFIED">IDENTIFIED - 원인 파악</option>
|
||||
<option value="MONITORING">MONITORING - 모니터링</option>
|
||||
<option value="RESOLVED">RESOLVED - 해소</option>
|
||||
<option value="CANCELED">CANCELED - 취소</option>
|
||||
</select>
|
||||
<div class="incident-endat-hint" style="display:none;color:#888;font-size:12px;margin-top:3px;">
|
||||
※ 타임라인에서 해소·취소로 전이하면 자동 입력됩니다.
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- 상태/이전 상태는 타임라인 게시로만 전이한다 (직접 수정 불가) -->
|
||||
<tr class="incident-row incident-state-row" style="display:none;">
|
||||
<th>상태</th>
|
||||
<td><span id="stateText"></span></td>
|
||||
<th>이전 상태</th>
|
||||
<td><span id="previousState">없음</span></td>
|
||||
</tr>
|
||||
@@ -435,6 +686,61 @@
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
|
||||
<!-- 타임라인은 [수정] 저장과 별개로 게시 즉시 반영되므로 폼 밖에 둔다 -->
|
||||
<div id="timelineSection" style="display:none;margin-top:20px;">
|
||||
<div class="title">타임라인</div>
|
||||
<table class="table_row" cellspacing="0" style="width:100%;">
|
||||
<colgroup>
|
||||
<col style="width: 130px"/>
|
||||
<col style="width: 80px"/>
|
||||
<col style="width: 140px"/>
|
||||
<col/>
|
||||
<col style="width: 160px"/>
|
||||
<col style="width: 80px"/>
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>시간</th>
|
||||
<th>작성자</th>
|
||||
<th>상태</th>
|
||||
<th>메세지</th>
|
||||
<th>개발자포탈 공개</th>
|
||||
<th>삭제</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="timelineTbody">
|
||||
<tr><td colspan="6" style="text-align:center;color:#999;">등록된 타임라인이 없습니다.</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div style="margin-top:8px;display:flex;align-items:center;gap:8px;">
|
||||
<label>
|
||||
<input type="checkbox" id="timelineStateChange"> 상태 변경
|
||||
</label>
|
||||
<div class="select-style" style="width:220px;">
|
||||
<select id="timelineNextState" disabled>
|
||||
<option value="">(현재 상태 유지)</option>
|
||||
<option value="INVESTIGATING">INVESTIGATING - 조사중</option>
|
||||
<option value="IDENTIFIED">IDENTIFIED - 원인 파악</option>
|
||||
<option value="MONITORING">MONITORING - 모니터링</option>
|
||||
<option value="RESOLVED">RESOLVED - 해소</option>
|
||||
<option value="CANCELED">CANCELED - 취소</option>
|
||||
</select>
|
||||
</div>
|
||||
<span id="timelineResolveHint" style="color:#888;font-size:12px;">※ 해소(RESOLVED)·취소(CANCELED)로 전이하면 종료 시각이 자동 입력됩니다.</span>
|
||||
<span id="timelineClosedHint" style="display:none;color:#C0392B;font-size:12px;">
|
||||
※ 종결된 장애입니다. 상태 변경 불가 — 재발 시 새 장애로 등록하십시오.
|
||||
</span>
|
||||
</div>
|
||||
<div style="margin-top:5px;display:flex;gap:5px;">
|
||||
<textarea id="timelineBody" style="flex:1;height:60px;"
|
||||
placeholder="타임라인 메세지 입력 예 : 모니터링 5분간 정상 응답 확인 후 완료 상태 전이 예정"></textarea>
|
||||
<button type="button" class="cssbtn" id="btn_addTimeline" level="W" status="DETAIL">
|
||||
<i class="material-icons">send</i> 게시
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
@@ -312,7 +312,9 @@ $(document).ready(function() {
|
||||
<div class="content_middle" id="content_middle">
|
||||
<div class="search_wrap">
|
||||
<button type="button" class="cssbtn" id="btn_new" level="W"><i class="material-icons">add</i> <%= localeMessage.getString("button.new") %></button>
|
||||
<c:if test="${hardDeleteEnabled}">
|
||||
<button type="button" class="cssbtn" id="btn_delete_selected" level="W" style="background-color: #dc3545; border-color: #dc3545; color: white;"><i class="material-icons">delete_forever</i> 선택 삭제</button>
|
||||
</c:if>
|
||||
<button type="button" class="cssbtn" id="btn_search" level="R"><i class="material-icons">search</i> <%= localeMessage.getString("button.search") %></button>
|
||||
</div>
|
||||
<%--법인정보관리--%>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
response.setHeader("Expires", "0");
|
||||
|
||||
MonitoringContext monitoringContext = (MonitoringContext)CommonUtil.getBean(request, "monitoringContext");
|
||||
String reverseProxyUrl = monitoringContext.getStringProperty("djb.reverse_proxy.url", "");
|
||||
String reverseProxyUrl = monitoringContext.getStringProperty(MonitoringContext.RMS_WEBHOOK_REVERSE_PROXY_URL, "");
|
||||
%>
|
||||
<%!
|
||||
public String getRequiredLabel(String label) {
|
||||
@@ -138,7 +138,11 @@
|
||||
$("#btn_delete").hide();
|
||||
}
|
||||
|
||||
$("#approvalStatus").val(portalOrgData.approvalStatus);
|
||||
$("select[name=approvalStatus]").val(portalOrgData.approvalStatus);
|
||||
// 승인상태는 읽기전용(수정 불가) - 값은 그대로 제출되도록 disabled 대신 잠금 처리
|
||||
$("select[name=approvalStatus]")
|
||||
.css({'pointer-events':'none', 'background-color':'#eee'})
|
||||
.attr('tabindex', '-1');
|
||||
$("#compRegNo").val(formatCompanyRegNo(portalOrgData.compRegNo));
|
||||
$("#corpRegNo").val(formatCorpRegNo(portalOrgData.corpRegNo));
|
||||
$("#createdDate").inputmask("9999-99-99 99:99:99", {'autoUnmask': true});
|
||||
@@ -480,8 +484,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
if (confirm("<%= localeMessage.getString("common.checkSave")%>") !== true)
|
||||
return;
|
||||
// 저장 확인/사유 입력은 유효성 검사 및 FormData 구성 후 처리 (하단 submit 분기)
|
||||
|
||||
// FormData 생성 전에 분리된 전화번호 필드 제거
|
||||
$("#scPhonePrefix, #scPhoneMiddle, #scPhoneLast, #orgPhonePrefix, #orgPhoneMiddle, #orgPhoneLast").removeAttr('name');
|
||||
@@ -516,23 +519,47 @@
|
||||
}
|
||||
|
||||
|
||||
// cmd 파라미터 추가
|
||||
formData.append("cmd", isDetail ? "UPDATE" : "INSERT");
|
||||
|
||||
$.ajax({
|
||||
type : "POST",
|
||||
url:url,
|
||||
data: formData,
|
||||
processData: false,
|
||||
contentType: false,
|
||||
success:function(json){
|
||||
alert("<%= localeMessage.getString("common.saveMsg") %>");
|
||||
goNav(returnUrl);
|
||||
},
|
||||
error:function(e){
|
||||
alert(e.responseText);
|
||||
// cmd 및 사유(auditReason) 첨부 후 전송
|
||||
function submitOrg(cmd, auditReason) {
|
||||
formData.append("cmd", cmd);
|
||||
// 감사로그(AUDIT_LOG) 발화를 위한 systemCode. 법인/가입자 리포지토리는 고정 EMS 스키마라 스키마 전환 영향 없음
|
||||
formData.append("serviceType", "APIGW");
|
||||
if (auditReason) {
|
||||
formData.append("auditReason", auditReason);
|
||||
}
|
||||
});
|
||||
|
||||
$.ajax({
|
||||
type : "POST",
|
||||
url:url,
|
||||
data: formData,
|
||||
processData: false,
|
||||
contentType: false,
|
||||
success:function(json){
|
||||
alert("<%= localeMessage.getString("common.saveMsg") %>");
|
||||
goNav(returnUrl);
|
||||
},
|
||||
error:function(e){
|
||||
alert(e.responseText);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (isDetail) {
|
||||
// 수정: 상태를 REMOVED로 바꾼 경우 삭제(cmd=DELETE)로 감사 기록 + 익명화
|
||||
var targetCmd = ($("#orgStatus").val() === 'REMOVED') ? "DELETE" : "UPDATE";
|
||||
var isRemove = (targetCmd === "DELETE");
|
||||
showReasonPrompt(
|
||||
isRemove ? "해당 법인을 삭제(탈퇴) 처리합니다. 사유를 입력해 주세요." : "법인 정보를 수정합니다. 사유를 입력해 주세요.",
|
||||
{
|
||||
title: isRemove ? "삭제 사유" : "수정 사유",
|
||||
onConfirm: function(reason) { submitOrg(targetCmd, reason); }
|
||||
}
|
||||
);
|
||||
} else {
|
||||
// 신규 등록: 사유 불필요
|
||||
if (confirm("<%= localeMessage.getString("common.checkSave")%>") !== true) return;
|
||||
submitOrg("INSERT", null);
|
||||
}
|
||||
});
|
||||
|
||||
$("#btn_previous").click(function() {
|
||||
@@ -541,25 +568,28 @@
|
||||
|
||||
$("#btn_delete").click(function(){
|
||||
|
||||
if(confirm("<%= localeMessage.getString("common.confirmMsg")%>")){
|
||||
var postData = $('#ajaxForm').serializeArray();
|
||||
postData.push({name: "cmd" , value:"DELETE"});
|
||||
showReasonPrompt("해당 법인을 삭제 처리합니다. 사유를 입력해 주세요.", {
|
||||
title: "삭제 사유",
|
||||
onConfirm: function(reason) {
|
||||
var postData = $('#ajaxForm').serializeArray();
|
||||
postData.push({name: "cmd" , value:"DELETE"});
|
||||
postData.push({name: "serviceType", value: "APIGW"});
|
||||
postData.push({name: "auditReason", value: reason});
|
||||
|
||||
$.ajax({
|
||||
type : "POST",
|
||||
url:url,
|
||||
data:postData,
|
||||
success:function(args){
|
||||
alert("<%= localeMessage.getString("common.deleteMsg") %>");
|
||||
goNav(returnUrl);
|
||||
},
|
||||
error:function(e){
|
||||
alert(e.responseText);
|
||||
}
|
||||
});
|
||||
}else{
|
||||
return false;
|
||||
}
|
||||
$.ajax({
|
||||
type : "POST",
|
||||
url:url,
|
||||
data:postData,
|
||||
success:function(args){
|
||||
alert("<%= localeMessage.getString("common.deleteMsg") %>");
|
||||
goNav(returnUrl);
|
||||
},
|
||||
error:function(e){
|
||||
alert(e.responseText);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
var url = '<c:url value="/onl/apim/portalproperty/propertyMan.json" />';
|
||||
var url_view = '<c:url value="/onl/apim/portalproperty/propertyMan.view" />';
|
||||
var lastsel2;
|
||||
var clickedCol; //클릭한 셀의 컬럼 인덱스(포커스 이동용)
|
||||
|
||||
function isValid() {
|
||||
if ($('input[name=prptyGroupName]').val() == "") {
|
||||
@@ -39,7 +40,7 @@
|
||||
if ($('input[name=prptyName]').val() == "") {
|
||||
alert("<%= localeMessage.getString("propertyDetail.checkRequired2") %>");
|
||||
return false;
|
||||
} else if ($('input[name=prpty2Val]').val() == "") {
|
||||
} else if ($('[name=prpty2Val]').val() == "") {
|
||||
alert("<%= localeMessage.getString("propertyDetail.checkRequired3") %>");
|
||||
return false;
|
||||
}
|
||||
@@ -86,9 +87,23 @@
|
||||
editurl: "clientArray",
|
||||
colNames: ['<%= localeMessage.getString("propertyDetail.propertyKey") %>',
|
||||
'<%= localeMessage.getString("propertyDetail.propertyValue") %>',
|
||||
'설명',
|
||||
'<%= localeMessage.getString("propertyDetail.delYn") %>'],
|
||||
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,
|
||||
//다행(멀티라인) 값 편집 — 관련 사이트 목록처럼 줄 단위 표기하는 값 지원
|
||||
edittype: 'textarea',
|
||||
editoptions: {rows: 3, cols: 60},
|
||||
//줄바꿈이 그리드에서도 그대로 보이도록
|
||||
cellattr: function () {
|
||||
return ' style="white-space:pre-line; word-break:break-all;"';
|
||||
}
|
||||
},
|
||||
{name: 'PRPTYDESC', width: 200, align: 'left', editable: true, edittype: 'textarea', editoptions: {rows: 2}},
|
||||
{
|
||||
name: 'DELETEYN',
|
||||
width: 20,
|
||||
@@ -101,6 +116,10 @@
|
||||
},
|
||||
loadComplete: function () {
|
||||
},
|
||||
onCellSelect: function (rowid, iCol) {
|
||||
//onSelectRow보다 먼저 발화 → 클릭한 컬럼 인덱스 저장
|
||||
clickedCol = iCol;
|
||||
},
|
||||
onSelectRow: function (rowid, status) {
|
||||
if (lastsel2 != undefined) {
|
||||
if ($("#grid tr#" + lastsel2).attr("editable") == 1) { //editable=1 means row in edit mode
|
||||
@@ -108,7 +127,8 @@
|
||||
}
|
||||
}
|
||||
$('#grid').restoreRow(lastsel2);
|
||||
$('#grid').editRow(rowid, true);
|
||||
//focusField로 클릭한 셀에 포커스(미지정 시 첫 편집 컬럼으로 감)
|
||||
$('#grid').editRow(rowid, {keys: true, focusField: clickedCol});
|
||||
lastsel2 = rowid;
|
||||
},
|
||||
onSortCol: function () {
|
||||
@@ -257,7 +277,8 @@
|
||||
}
|
||||
var data = new Object();
|
||||
data["PRPTYNAME"] = $("input[name=prptyName]").val();
|
||||
data["PRPTY2VAL"] = $("input[name=prpty2Val]").val();
|
||||
data["PRPTY2VAL"] = $("[name=prpty2Val]").val();
|
||||
data["PRPTYDESC"] = $("input[name=prptyDesc]").val();
|
||||
|
||||
var rows = $("#grid")[0].rows;
|
||||
var index = Number($(rows[rows.length - 1]).attr("id"));
|
||||
@@ -337,7 +358,13 @@
|
||||
<tr>
|
||||
<th><%= localeMessage.getString("propertyDetail.propertyValue") %>
|
||||
</th>
|
||||
<td colspan="3"><input type="text" name="prpty2Val"/>
|
||||
<td colspan="3"><textarea name="prpty2Val" rows="3"
|
||||
style="width:100%; box-sizing:border-box; resize:vertical;"></textarea>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>설명</th>
|
||||
<td colspan="3"><input type="text" name="prptyDesc"/>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
@@ -347,9 +347,11 @@
|
||||
<button type="button" class="cssbtn" id="btn_new" level="W"><i
|
||||
class="material-icons">add</i> <%= localeMessage.getString("button.new") %>
|
||||
</button>
|
||||
<c:if test="${hardDeleteEnabled}">
|
||||
<button type="button" class="cssbtn" id="btn_delete_selected" level="W" style="background-color: #dc3545; border-color: #dc3545; color: white;"><i
|
||||
class="material-icons">delete_forever</i> 선택 삭제
|
||||
</button>
|
||||
</c:if>
|
||||
<button type="button" class="cssbtn" id="btn_search" level="R"><i
|
||||
class="material-icons">search</i> <%= localeMessage.getString("button.search") %>
|
||||
</button>
|
||||
|
||||
@@ -165,6 +165,10 @@
|
||||
$("#userStatus").val(data.userStatus);
|
||||
$("#roleCode").val(data.roleCode).trigger('change');
|
||||
$("#approvalStatus").val(data.approvalStatus);
|
||||
// 승인상태는 읽기전용(수정 불가) - 값은 그대로 제출되도록 disabled 대신 잠금 처리
|
||||
$("#approvalStatus")
|
||||
.css({'pointer-events':'none', 'background-color':'#eee'})
|
||||
.attr('tabindex', '-1');
|
||||
|
||||
// 탈퇴 상태 처리
|
||||
if (originalUserStatus === STATUS.REMOVED) {
|
||||
@@ -281,7 +285,7 @@
|
||||
}
|
||||
|
||||
const fullLoginId = id + "@" + domain;
|
||||
const emailPattern = /^[A-Za-z0-9._-]*@[A-Za-z0-9][-A-Za-z0-9.]+\.[A-Za-z]{2,}$/;
|
||||
const emailPattern = /^[A-Za-z0-9._%+-]*@[A-Za-z0-9][-A-Za-z0-9.]+\.[A-Za-z]{2,}$/;
|
||||
if (!emailPattern.test(fullLoginId)) {
|
||||
alert(fullLoginId + " 은 올바른 이메일 형식이 아닙니다.");
|
||||
return;
|
||||
@@ -313,7 +317,7 @@
|
||||
}
|
||||
|
||||
function validateEmailId(input) {
|
||||
input.value = input.value.replace(/[^A-Za-z0-9._-]/g, '');
|
||||
input.value = input.value.replace(/[^A-Za-z0-9._%+-]/g, '');
|
||||
}
|
||||
|
||||
function validateDomainInput(input) {
|
||||
@@ -488,29 +492,49 @@
|
||||
formData.push({name: 'mobileNumber', value: mobileNumber});
|
||||
}
|
||||
|
||||
formData.push({
|
||||
name: 'cmd',
|
||||
value: isDetail ? "UPDATE" : "INSERT"
|
||||
});
|
||||
|
||||
if (!confirm("<%= localeMessage.getString("common.checkSave")%>")) return;
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: url,
|
||||
data: formData,
|
||||
success: function (json) {
|
||||
if (json.status === "fail") {
|
||||
alert(json.errorMsg || "저장에 실패했습니다.");
|
||||
return;
|
||||
}
|
||||
alert("<%= localeMessage.getString("common.saveMsg") %>");
|
||||
goNav(returnUrl);
|
||||
},
|
||||
error: function (e) {
|
||||
alert(e.responseText);
|
||||
// cmd 및 사유(auditReason) 첨부 후 전송
|
||||
function submitUser(cmd, auditReason) {
|
||||
formData.push({ name: 'cmd', value: cmd });
|
||||
// 감사로그(AUDIT_LOG) 발화를 위한 systemCode. 가입자 리포지토리는 고정 EMS 스키마라 스키마 전환 영향 없음
|
||||
formData.push({ name: 'serviceType', value: 'APIGW' });
|
||||
if (auditReason) {
|
||||
formData.push({ name: 'auditReason', value: auditReason });
|
||||
}
|
||||
});
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: url,
|
||||
data: formData,
|
||||
success: function (json) {
|
||||
if (json.status === "fail") {
|
||||
alert(json.errorMsg || "저장에 실패했습니다.");
|
||||
return;
|
||||
}
|
||||
alert("<%= localeMessage.getString("common.saveMsg") %>");
|
||||
goNav(returnUrl);
|
||||
},
|
||||
error: function (e) {
|
||||
alert(e.responseText);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (isDetail) {
|
||||
// 수정: 상태를 REMOVED로 바꾼 경우 삭제(cmd=DELETE)로 감사 기록 + 익명화
|
||||
var targetCmd = ($("#userStatus").val() === 'REMOVED') ? "DELETE" : "UPDATE";
|
||||
var isRemove = (targetCmd === "DELETE");
|
||||
showReasonPrompt(
|
||||
isRemove ? "해당 가입자를 삭제(탈퇴) 처리합니다. 사유를 입력해 주세요." : "가입자 정보를 수정합니다. 사유를 입력해 주세요.",
|
||||
{
|
||||
title: isRemove ? "삭제 사유" : "수정 사유",
|
||||
onConfirm: function(reason) { submitUser(targetCmd, reason); }
|
||||
}
|
||||
);
|
||||
} else {
|
||||
// 신규 등록: 사유 불필요. 초기 패스워드는 비밀번호 초기화와 동일(이메일ID + subfix)
|
||||
if (!confirm("가입자를 등록하시겠습니까?\n초기 패스워드는 '이메일ID${passwordInitSubfix}' 입니다.")) return;
|
||||
submitUser("INSERT", null);
|
||||
}
|
||||
});
|
||||
|
||||
$("#btn_previous").click(function () {
|
||||
@@ -518,27 +542,32 @@
|
||||
});
|
||||
|
||||
$("#btn_delete").click(function () {
|
||||
if (!confirm("<%= localeMessage.getString("common.confirmMsg")%>")) return;
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: url,
|
||||
data: {
|
||||
cmd: "DELETE",
|
||||
id: $('input[name=id]').val()
|
||||
},
|
||||
success: function () {
|
||||
alert("<%= localeMessage.getString("common.deleteMsg") %>");
|
||||
goNav(returnUrl);
|
||||
},
|
||||
error: function (e) {
|
||||
alert(e.responseText);
|
||||
showReasonPrompt("해당 가입자를 삭제 처리합니다. 사유를 입력해 주세요.", {
|
||||
title: "삭제 사유",
|
||||
onConfirm: function(reason) {
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: url,
|
||||
data: {
|
||||
cmd: "DELETE",
|
||||
id: $('input[name=id]').val(),
|
||||
serviceType: "APIGW",
|
||||
auditReason: reason
|
||||
},
|
||||
success: function () {
|
||||
alert("<%= localeMessage.getString("common.deleteMsg") %>");
|
||||
goNav(returnUrl);
|
||||
},
|
||||
error: function (e) {
|
||||
alert(e.responseText);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$("#btn_password_reset").click(function () {
|
||||
if (!confirm("패스워드를 초기화 하시겠습니까?\n초기 패스워드는 '!' + 이메일ID 입니다.")) return;
|
||||
if (!confirm("패스워드를 초기화 하시겠습니까?\n초기 패스워드는 '이메일ID${passwordInitSubfix}' 입니다.")) return;
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
|
||||
@@ -172,6 +172,9 @@ $(document).ready(function() {
|
||||
<form id="ajaxForm">
|
||||
<input type="hidden" name="id">
|
||||
<table class="table_row" cellspacing="0">
|
||||
<colgroup>
|
||||
<col width="200px">
|
||||
</colgroup>
|
||||
<tr>
|
||||
<th><%= localeMessage.getString("messageTemplate.messageCode") %> <font color="red"> *</font></th>
|
||||
<td><input type="text" name="messageCode" data-required data-warning="메세지 코드는 필수입니다"/></td>
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
<%@ page language="java" contentType="text/html; charset=utf-8" %>
|
||||
<%@ page import="java.io.*" %>
|
||||
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
|
||||
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %>
|
||||
<%@ include file="/jsp/common/include/localemessage.jsp" %>
|
||||
<%
|
||||
response.setHeader("Pragma", "No-cache");
|
||||
response.setHeader("Cache-Control", "no-cache");
|
||||
response.setHeader("Expires", "0");
|
||||
%>
|
||||
<html>
|
||||
<head>
|
||||
<title></title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<jsp:include page="/jsp/common/include/css.jsp"/>
|
||||
<jsp:include page="/jsp/common/include/script.jsp"/>
|
||||
<style>
|
||||
.url-cell { display:inline-block; max-width:380px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; vertical-align:middle; }
|
||||
</style>
|
||||
<script language="javascript">
|
||||
var url = '<c:url value="/onl/apim/webhook/webhookReqMan.json" />';
|
||||
var url_view = '<c:url value="/onl/apim/webhook/webhookReqMan.view" />';
|
||||
|
||||
function escapeHtmlJs(s) { return $('<div>').text(s == null ? '' : s).html(); }
|
||||
function escapeAttr(s) { return escapeHtmlJs(s).replace(/"/g, '"'); }
|
||||
|
||||
function formatTargetUrl(cellvalue, options, rowObject) {
|
||||
var v = cellvalue || '';
|
||||
return '<span class="url-cell" title="' + escapeAttr(v) + '">' + escapeHtmlJs(v) + '</span>';
|
||||
}
|
||||
|
||||
function init() {
|
||||
|
||||
// 뒤로가기 등으로 이전 검색조건 파라미터가 있으면 기본값을 덮어씀
|
||||
putSelectFromParam();
|
||||
|
||||
buildGrid();
|
||||
}
|
||||
|
||||
function buildGrid() {
|
||||
$('#grid').jqGrid({
|
||||
datatype: "json",
|
||||
mtype: 'POST',
|
||||
url: url,
|
||||
postData: getSearchForJqgrid("cmd", "LIST"),
|
||||
colNames: ['id', 'No', '기관명', '리버스 프록시 경로', 'Target URL', '신청일시'],
|
||||
colModel: [
|
||||
{name: 'id', align: 'center', key: true, hidden: true},
|
||||
{name: 'rowNum', align: 'center', width: 45, sortable: false},
|
||||
{name: 'orgName', align: 'left', width: 160},
|
||||
{name: 'reverseProxyPath', align: 'left', width: 160},
|
||||
{name: 'targetUrl', align: 'left', width: 380, formatter: formatTargetUrl},
|
||||
{name: 'createdDate', align: 'center', width: 140}
|
||||
],
|
||||
jsonReader: {repeatitems: false},
|
||||
pager: $('#pager'),
|
||||
page: '${param.page}',
|
||||
rowNum: '${rmsDefaultRowNum}',
|
||||
autoheight: true,
|
||||
height: $("#container").height(),
|
||||
autowidth: true,
|
||||
viewrecords: true,
|
||||
rowList: eval('[${rmsDefaultRowList}]'),
|
||||
ondblClickRow: function (rowId) {
|
||||
var rowData = $(this).getRowData(rowId);
|
||||
var id = rowData['id'];
|
||||
var url2 = url_view + '?cmd=DETAIL';
|
||||
url2 += '&page=' + $(this).getGridParam("page");
|
||||
url2 += '&returnUrl=' + getReturnUrl();
|
||||
url2 += '&menuId=' + '${param.menuId}';
|
||||
url2 += '&id=' + id;
|
||||
url2 += '&' + getSearchUrl();
|
||||
goNav(url2);
|
||||
},
|
||||
loadComplete: function () {
|
||||
var page = $(this).getGridParam('page');
|
||||
var rowNum = $(this).getGridParam('rowNum');
|
||||
var rows = $(this).getDataIDs();
|
||||
var colModel = $(this).getGridParam("colModel");
|
||||
for (var i = 0; i < rows.length; i++) {
|
||||
var number = ((page - 1) * rowNum + i) + 1;
|
||||
$(this).setCell(rows[i], 'rowNum', number);
|
||||
}
|
||||
// 서버 고정 정렬(신청일시 역순) — 컬럼 정렬 비활성
|
||||
for (var i = 0; i < colModel.length; i++) {
|
||||
$(this).setColProp(colModel[i].name, {sortable: false});
|
||||
}
|
||||
},
|
||||
loadError: function (jqXHR, textStatus, errorThrown) {
|
||||
var location = '<%=request.getContextPath()%>/';
|
||||
comloadError(jqXHR, textStatus, errorThrown, location);
|
||||
}
|
||||
});
|
||||
resizeJqGridWidth('grid', 'content_middle', '1000');
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
init();
|
||||
|
||||
$("#btn_search").click(function () {
|
||||
var postData = getSearchForJqgrid("cmd", "LIST");
|
||||
$("#grid").setGridParam({url: url, postData: postData, page: 1}).trigger("reloadGrid");
|
||||
});
|
||||
|
||||
$("select[name^=search]").change(function () { $("#btn_search").click(); });
|
||||
$("input[name^=search]").keydown(function (key) {
|
||||
if (key.keyCode == 13) { $("#btn_search").click(); }
|
||||
});
|
||||
|
||||
buttonControl();
|
||||
});
|
||||
</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" id="content_middle">
|
||||
<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>
|
||||
</div>
|
||||
<div class="title">웹훅 신청내역<span class="tooltip">등록된 웹훅 신청 내역을 조회합니다.</span></div>
|
||||
<form id="ajaxForm" onsubmit="return false;">
|
||||
<table class="search_condition" cellspacing=0;>
|
||||
<colgroup>
|
||||
<col style="width:120px;"><col style="width:200px;">
|
||||
<col style="width:120px;"><col style="width:200px;">
|
||||
</colgroup>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>기관명</th>
|
||||
<td><input type="text" name="searchOrgName" autocomplete="off"></td>
|
||||
<th>리버스 프록시 경로</th>
|
||||
<td><input type="text" name="searchReverseProxyPath" autocomplete="off"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Target URL</th>
|
||||
<td colspan="3"><input type="text" name="searchTargetUrl" autocomplete="off" style="width:95%;"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</form>
|
||||
<table id="grid"></table>
|
||||
<div id="pager"></div>
|
||||
</div><!-- end content_middle -->
|
||||
</div><!-- end right_box -->
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,154 @@
|
||||
<%@ page language="java" contentType="text/html; charset=utf-8" %>
|
||||
<%@ page import="java.io.*" %>
|
||||
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
|
||||
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %>
|
||||
<%@ include file="/jsp/common/include/localemessage.jsp" %>
|
||||
<%
|
||||
response.setHeader("Pragma", "No-cache");
|
||||
response.setHeader("Cache-Control", "no-cache");
|
||||
response.setHeader("Expires", "0");
|
||||
%>
|
||||
<html>
|
||||
<head>
|
||||
<title></title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<jsp:include page="/jsp/common/include/css.jsp"/>
|
||||
<jsp:include page="/jsp/common/include/script.jsp"/>
|
||||
<style>
|
||||
.sub_list { width:100%; border-collapse:collapse; }
|
||||
.sub_list th, .sub_list td { border:1px solid #ebebec; padding:6px 8px; text-align:left; }
|
||||
.sub_list th { background:#f7f7f8; width:60px; }
|
||||
.empty-msg { color:#999; padding:6px 2px; }
|
||||
</style>
|
||||
<script language="javascript">
|
||||
var url = '<c:url value="/onl/apim/webhook/webhookReqMan.json" />';
|
||||
var url_view = '<c:url value="/onl/apim/webhook/webhookReqMan.view" />';
|
||||
var key = '${param.id}';
|
||||
var combo;
|
||||
|
||||
function escapeHtmlJs(s) { return $('<div>').text(s == null ? '' : s).html(); }
|
||||
|
||||
// 이벤트유형 코드 → 모니터링 공통코드(EVENT_TYPE) 코드명
|
||||
function eventTypeName(code) {
|
||||
if (!combo || !combo.eventTypeList) { return code; }
|
||||
for (var i = 0; i < combo.eventTypeList.length; i++) {
|
||||
if (combo.eventTypeList[i].CODE == code) { return combo.eventTypeList[i].NAME; }
|
||||
}
|
||||
return code;
|
||||
}
|
||||
|
||||
function renderApiList(apiIds) {
|
||||
var $tbody = $('#apiListBody').empty();
|
||||
if (!apiIds || apiIds.length === 0) {
|
||||
$tbody.append('<tr><td class="empty-msg">등록된 API가 없습니다.</td></tr>');
|
||||
return;
|
||||
}
|
||||
for (var i = 0; i < apiIds.length; i++) {
|
||||
$tbody.append('<tr><td>' + escapeHtmlJs(apiIds[i]) + '</td></tr>');
|
||||
}
|
||||
}
|
||||
|
||||
function renderEventTypes(eventTypes) {
|
||||
if (!eventTypes || eventTypes.length === 0) {
|
||||
$('#eventTypes').text('');
|
||||
return;
|
||||
}
|
||||
var names = eventTypes.map(function (code) { return eventTypeName(code); });
|
||||
$('#eventTypes').text(names.join(', '));
|
||||
}
|
||||
|
||||
function detail() {
|
||||
if (!key) return;
|
||||
$.ajax({
|
||||
type: "POST", url: url, dataType: "json",
|
||||
data: {cmd: 'DETAIL', id: key},
|
||||
success: function (data) {
|
||||
$('#id').text(data.id != null ? data.id : '');
|
||||
$('#orgName').text(data.orgName || '');
|
||||
$('#reverseProxyPath').text(data.reverseProxyPath || '');
|
||||
$('#targetUrl').text(data.targetUrl || '');
|
||||
$('#secret').text(data.secret || '');
|
||||
$('#userSecret').text(data.userSecret || '');
|
||||
$('#createdDate').text(data.createdDate || '');
|
||||
|
||||
renderApiList(data.apiIds);
|
||||
renderEventTypes(data.eventTypes);
|
||||
},
|
||||
error: function (e) { alert(e.responseText); }
|
||||
});
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
var returnUrl = getReturnUrlForReturn();
|
||||
|
||||
buttonControl();
|
||||
|
||||
$.ajax({
|
||||
type: "POST", url: url, dataType: "json",
|
||||
data: {cmd: 'LIST_INIT_COMBO'},
|
||||
success: function (json) {
|
||||
combo = json;
|
||||
detail();
|
||||
},
|
||||
error: function (e) { alert(e.responseText); }
|
||||
});
|
||||
|
||||
$('#btn_previous').click(function () { goNav(returnUrl); });
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="right_box">
|
||||
<div class="content_top">
|
||||
<ul class="path"><li><a href="#">${rmsMenuPath}</a></li></ul>
|
||||
</div>
|
||||
<div class="content_middle">
|
||||
<div class="search_wrap">
|
||||
<button type="button" class="cssbtn" id="btn_previous" level="R" status="DETAIL,NEW"><i class="material-icons">arrow_back</i> <%= localeMessage.getString("button.previous") %></button>
|
||||
</div>
|
||||
<div class="title" id="title">웹훅 신청내역 상세</div>
|
||||
<table class="table_row" cellspacing="0">
|
||||
<colgroup>
|
||||
<col style="width:12%"/><col style="width:38%"/>
|
||||
<col style="width:12%"/><col style="width:38%"/>
|
||||
</colgroup>
|
||||
<tr>
|
||||
<th>신청 ID</th>
|
||||
<td colspan="3"><span id="id"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>기관명</th>
|
||||
<td><span id="orgName"></span></td>
|
||||
<th>신청일시</th>
|
||||
<td><span id="createdDate"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>리버스 프록시 경로</th>
|
||||
<td><span id="reverseProxyPath"></span></td>
|
||||
<th>Target URL</th>
|
||||
<td><span id="targetUrl"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Secret</th>
|
||||
<td colspan="3"><span id="secret"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>User Secret</th>
|
||||
<td colspan="3"><span id="userSecret"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>신청 이벤트</th>
|
||||
<td colspan="3"><span id="eventTypes"></span></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div class="title" style="margin-top:20px;">API 목록</div>
|
||||
<table class="table_row">
|
||||
<colgroup><col style="width:100%"/></colgroup>
|
||||
<thead><tr><th style="width:100%;">API ID</th></tr></thead>
|
||||
<tbody id="apiListBody"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,243 @@
|
||||
<%@ page language="java" contentType="text/html; charset=utf-8" %>
|
||||
<%@ page import="java.io.*" %>
|
||||
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
|
||||
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %>
|
||||
<%@ include file="/jsp/common/include/localemessage.jsp" %>
|
||||
<%
|
||||
response.setHeader("Pragma", "No-cache");
|
||||
response.setHeader("Cache-Control", "no-cache");
|
||||
response.setHeader("Expires", "0");
|
||||
%>
|
||||
<html>
|
||||
<head>
|
||||
<title></title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<jsp:include page="/jsp/common/include/css.jsp"/>
|
||||
<jsp:include page="/jsp/common/include/script.jsp"/>
|
||||
<style>
|
||||
.url-cell { display:inline-block; max-width:380px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; vertical-align:middle; }
|
||||
.succ-y { color:#1a73e8; font-weight:bold; }
|
||||
.succ-n { color:#d93025; font-weight:bold; }
|
||||
</style>
|
||||
<script language="javascript">
|
||||
var url = '<c:url value="/onl/apim/webhook/webhookSendLogMan.json" />';
|
||||
var url_view = '<c:url value="/onl/apim/webhook/webhookSendLogMan.view" />';
|
||||
var combo;
|
||||
|
||||
function escapeHtmlJs(s) { return $('<div>').text(s == null ? '' : s).html(); }
|
||||
function escapeAttr(s) { return escapeHtmlJs(s).replace(/"/g, '"'); }
|
||||
|
||||
function formatTargetUrl(cellvalue, options, rowObject) {
|
||||
var v = cellvalue || '';
|
||||
return '<span class="url-cell" title="' + escapeAttr(v) + '">' + escapeHtmlJs(v) + '</span>';
|
||||
}
|
||||
|
||||
// 이벤트유형 코드 → 모니터링 공통코드(EVENT_TYPE) 코드명
|
||||
function formatEventType(cellvalue, options, rowObject) {
|
||||
if (!combo || !combo.eventTypeList) {
|
||||
return escapeHtmlJs(cellvalue);
|
||||
}
|
||||
for (var i = 0; i < combo.eventTypeList.length; i++) {
|
||||
if (combo.eventTypeList[i].CODE == cellvalue) {
|
||||
return escapeHtmlJs(combo.eventTypeList[i].NAME);
|
||||
}
|
||||
}
|
||||
return escapeHtmlJs(cellvalue);
|
||||
}
|
||||
|
||||
function formatSuccess(cellvalue, options, rowObject) {
|
||||
if (cellvalue === true) return '<span class="succ-y">성공</span>';
|
||||
if (cellvalue === false) return '<span class="succ-n">실패</span>';
|
||||
return '';
|
||||
}
|
||||
|
||||
|
||||
function init() {
|
||||
|
||||
$("#startDatepicker").datepicker().inputmask("yyyy-mm-dd",{'autoUnmask':true});
|
||||
$("#endDatepicker").datepicker().inputmask("yyyy-mm-dd",{'autoUnmask':true});
|
||||
|
||||
var today = getToday();
|
||||
|
||||
$("input[name=searchStartYYYYMMDD]").val(today.substring(0,6)+"01");
|
||||
$("input[name=searchEndYYYYMMDD]").val(today);
|
||||
$("input[name=searchStartDate]").val(today.substring(0,6)+"01");
|
||||
$("input[name=searchEndDate]").val(today);
|
||||
|
||||
$.ajax({
|
||||
type: "POST", url: url, dataType: "json",
|
||||
data: {cmd: 'LIST_INIT_COMBO'},
|
||||
success: function (json) {
|
||||
combo = json;
|
||||
new makeOptions("CODE", "NAME").setObj($("select[name=searchEventType]"))
|
||||
.setNoValueInclude(true).setNoValue("", "<%=localeMessage.getString("combo.all")%>")
|
||||
.setData(json.eventTypeList).rendering();
|
||||
|
||||
// 뒤로가기 등으로 이전 검색조건 파라미터가 있으면 기본값을 덮어씀
|
||||
putSelectFromParam();
|
||||
|
||||
// 콤보(이벤트유형) 로드 후 그리드 생성 — formatEventType 경합 방지
|
||||
buildGrid();
|
||||
},
|
||||
error: function (e) { alert(e.responseText); }
|
||||
});
|
||||
}
|
||||
|
||||
function buildGrid() {
|
||||
$('#grid').jqGrid({
|
||||
datatype: "json",
|
||||
mtype: 'POST',
|
||||
url: url,
|
||||
postData: getSearchForJqgrid("cmd", "LIST"),
|
||||
colNames: ['id', 'No.', '기관명', '이벤트유형', 'Target URL', 'Proxy URL', 'Status', '성공여부', '재시도', '발송서버', '발송일시'],
|
||||
colModel: [
|
||||
{name: 'id', align: 'center', key: true, hidden: true},
|
||||
{name: 'rowNum', align: 'center', width: 45, sortable: false},
|
||||
{name: 'orgName', align: 'left', width: 140},
|
||||
{name: 'eventType', align: 'center', width: 120, formatter: formatEventType},
|
||||
{name: 'targetUrl', align: 'left', width: 300, formatter: formatTargetUrl},
|
||||
{name: 'proxyUrl', align: 'left', width: 300, formatter: formatTargetUrl},
|
||||
{name: 'statusCode', align: 'center', width: 60},
|
||||
{name: 'success', align: 'center', width: 70, formatter: formatSuccess},
|
||||
{name: 'retryCount', align: 'center', width: 60},
|
||||
{name: 'sendBy', align: 'center', width: 100},
|
||||
{name: 'sentAt', align: 'center', width: 140}
|
||||
],
|
||||
jsonReader: {repeatitems: false},
|
||||
pager: $('#pager'),
|
||||
page: '${param.page}',
|
||||
rowNum: '${rmsDefaultRowNum}',
|
||||
autoheight: true,
|
||||
height: $("#container").height(),
|
||||
autowidth: true,
|
||||
viewrecords: true,
|
||||
rowList: eval('[${rmsDefaultRowList}]'),
|
||||
ondblClickRow: function (rowId) {
|
||||
var rowData = $(this).getRowData(rowId);
|
||||
var id = rowData['id'];
|
||||
var url2 = url_view + '?cmd=DETAIL';
|
||||
url2 += '&page=' + $(this).getGridParam("page");
|
||||
url2 += '&returnUrl=' + getReturnUrl();
|
||||
url2 += '&menuId=' + '${param.menuId}';
|
||||
url2 += '&id=' + id;
|
||||
url2 += '&' + getSearchUrl();
|
||||
goNav(url2);
|
||||
},
|
||||
loadComplete: function () {
|
||||
var page = $(this).getGridParam('page');
|
||||
var rowNum = $(this).getGridParam('rowNum');
|
||||
var rows = $(this).getDataIDs();
|
||||
var colModel = $(this).getGridParam("colModel");
|
||||
for (var i = 0; i < rows.length; i++) {
|
||||
var number = ((page - 1) * rowNum + i) + 1;
|
||||
$(this).setCell(rows[i], 'rowNum', number);
|
||||
}
|
||||
// 서버 고정 정렬(등록일시 역순) — 컬럼 정렬 비활성
|
||||
for (var i = 0; i < colModel.length; i++) {
|
||||
$(this).setColProp(colModel[i].name, {sortable: false});
|
||||
}
|
||||
},
|
||||
loadError: function (jqXHR, textStatus, errorThrown) {
|
||||
var location = '<%=request.getContextPath()%>/';
|
||||
comloadError(jqXHR, textStatus, errorThrown, location);
|
||||
}
|
||||
});
|
||||
resizeJqGridWidth('grid', 'content_middle', '1000');
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
init();
|
||||
|
||||
$("#btn_search").click(function () {
|
||||
var start = $("input[name=searchStartYYYYMMDD]").val().replace(/-/gi, "");
|
||||
var end = $("input[name=searchEndYYYYMMDD]").val().replace(/-/gi, "");
|
||||
|
||||
if (start && start > getToday()) {
|
||||
alert("시작일이 오늘 이후입니다. 시작일을 확인해주세요.");
|
||||
$("input[name=searchStartYYYYMMDD]").focus();
|
||||
return false;
|
||||
}
|
||||
if (start && end && start > end) {
|
||||
alert("조회기간 종료일은 시작일보다 커야합니다.");
|
||||
$("input[name=searchEndYYYYMMDD]").focus();
|
||||
return false;
|
||||
}
|
||||
$("input[name=searchStartDate]").val(start);
|
||||
$("input[name=searchEndDate]").val(end);
|
||||
|
||||
var postData = getSearchForJqgrid("cmd", "LIST");
|
||||
$("#grid").setGridParam({url: url, postData: postData, page: 1}).trigger("reloadGrid");
|
||||
});
|
||||
|
||||
$("select[name^=search]").change(function () { $("#btn_search").click(); });
|
||||
$("input[name^=search]").keydown(function (key) {
|
||||
if (key.keyCode == 13) { $("#btn_search").click(); }
|
||||
});
|
||||
|
||||
|
||||
|
||||
buttonControl();
|
||||
});
|
||||
</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" id="content_middle">
|
||||
<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>
|
||||
</div>
|
||||
<div class="title">웹훅 발송 로그<span class="tooltip">외부로 발송된 웹훅 이력을 조회합니다.</span></div>
|
||||
<form id="ajaxForm" onsubmit="return false;">
|
||||
<table class="search_condition" cellspacing=0;>
|
||||
<colgroup>
|
||||
<col style="width:120px;"><col style="width:200px;">
|
||||
<col style="width:120px;"><col style="width:200px;">
|
||||
<col style="width:120px;"><col style="width:200px;">
|
||||
</colgroup>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>기관명</th>
|
||||
<td><input type="text" name="searchOrgName" autocomplete="off"></td>
|
||||
<th>이벤트유형</th>
|
||||
<td>
|
||||
<div class="select-style">
|
||||
<select name="searchEventType"></select>
|
||||
</div>
|
||||
</td>
|
||||
<th>성공여부</th>
|
||||
<td>
|
||||
<div class="select-style">
|
||||
<select name="searchSuccess">
|
||||
<option value="">전체</option>
|
||||
<option value="Y">성공</option>
|
||||
<option value="N">실패</option>
|
||||
</select>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Target URL</th>
|
||||
<td><input type="text" name="searchTargetUrl" autocomplete="off" style="width:95%;"></td>
|
||||
<th>발송기간</th>
|
||||
<td>
|
||||
<input type="text" name="searchStartYYYYMMDD" id="startDatepicker" readonly="readonly" value="" size="10" style="width:100px; border:1px solid #ebebec;">
|
||||
~
|
||||
<input type="text" name="searchEndYYYYMMDD" value="" id="endDatepicker" size="10" readonly="readonly" style="width:100px; border:1px solid #ebebec;">
|
||||
<input type="hidden" name="searchStartDate" value="">
|
||||
<input type="hidden" name="searchEndDate" value="">
|
||||
</td>
|
||||
<th></th>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</form>
|
||||
<table id="grid"></table>
|
||||
<div id="pager"></div>
|
||||
</div><!-- end content_middle -->
|
||||
</div><!-- end right_box -->
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,140 @@
|
||||
<%@ page language="java" contentType="text/html; charset=utf-8" %>
|
||||
<%@ page import="java.io.*" %>
|
||||
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
|
||||
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %>
|
||||
<%@ include file="/jsp/common/include/localemessage.jsp" %>
|
||||
<%
|
||||
response.setHeader("Pragma", "No-cache");
|
||||
response.setHeader("Cache-Control", "no-cache");
|
||||
response.setHeader("Expires", "0");
|
||||
%>
|
||||
<html>
|
||||
<head>
|
||||
<title></title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<jsp:include page="/jsp/common/include/css.jsp"/>
|
||||
<jsp:include page="/jsp/common/include/script.jsp"/>
|
||||
<style>
|
||||
.succ-y { color:#1a73e8; font-weight:bold; }
|
||||
.succ-n { color:#d93025; font-weight:bold; }
|
||||
.pre-wrap { white-space:pre-wrap; word-break:break-all; min-height:60px; line-height:1.5; }
|
||||
</style>
|
||||
<script language="javascript">
|
||||
var url = '<c:url value="/onl/apim/webhook/webhookSendLogMan.json" />';
|
||||
var url_view = '<c:url value="/onl/apim/webhook/webhookSendLogMan.view" />';
|
||||
var key = '${param.id}';
|
||||
|
||||
function escapeHtmlJs(s) { return $('<div>').text(s == null ? '' : s).html(); }
|
||||
|
||||
function detail() {
|
||||
if (!key) return;
|
||||
$.ajax({
|
||||
type: "POST", url: url, dataType: "json",
|
||||
data: {cmd: 'DETAIL', id: key},
|
||||
success: function (data) {
|
||||
$('#id').text(data.id != null ? data.id : '');
|
||||
$('#orgName').text(data.orgName || '');
|
||||
$('#eventType').text(data.eventTypeName || data.eventType || '')
|
||||
.attr('title', data.eventType || '');
|
||||
$('#targetUrl').text(data.targetUrl || '');
|
||||
$('#proxyUrl').text(data.proxyUrl || '');
|
||||
$('#statusCode').text(data.statusCode != null ? data.statusCode : '');
|
||||
$('#retryCount').text(data.retryCount != null ? data.retryCount : '');
|
||||
$('#sendBy').text(data.sendBy || '');
|
||||
$('#sentAt').text(data.sentAt || '');
|
||||
$('#signature').text(data.signature || '');
|
||||
$('#userSecret').text(data.userSecret || '');
|
||||
$('#errorMessage').text(data.errorMessage || '');
|
||||
$('#payload').text(data.payload || '');
|
||||
$('#responseBody').text(data.responseBody || '');
|
||||
|
||||
if (data.success === true) {
|
||||
$('#success').html('<span class="succ-y">성공</span>');
|
||||
} else if (data.success === false) {
|
||||
$('#success').html('<span class="succ-n">실패</span>');
|
||||
} else {
|
||||
$('#success').text('');
|
||||
}
|
||||
},
|
||||
error: function (e) { alert(e.responseText); }
|
||||
});
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
var returnUrl = getReturnUrlForReturn();
|
||||
|
||||
buttonControl();
|
||||
detail();
|
||||
|
||||
$('#btn_previous').click(function () { goNav(returnUrl); });
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="right_box">
|
||||
<div class="content_top">
|
||||
<ul class="path"><li><a href="#">${rmsMenuPath}</a></li></ul>
|
||||
</div>
|
||||
<div class="content_middle">
|
||||
<div class="search_wrap">
|
||||
<button type="button" class="cssbtn" id="btn_previous" level="R" status="DETAIL,NEW"><i class="material-icons">arrow_back</i> <%= localeMessage.getString("button.previous") %></button>
|
||||
</div>
|
||||
<div class="title" id="title">웹훅 발송 로그 상세</div>
|
||||
<table class="table_row" cellspacing="0">
|
||||
<colgroup>
|
||||
<col style="width:12%"/><col style="width:38%"/>
|
||||
<col style="width:12%"/><col style="width:38%"/>
|
||||
</colgroup>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<td><span id="id"></span></td>
|
||||
<th>기관명</th>
|
||||
<td><span id="orgName"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>이벤트유형</th>
|
||||
<td><span id="eventType"></span></td>
|
||||
<th>Status Code</th>
|
||||
<td><span id="statusCode"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Target URL</th>
|
||||
<td><span id="targetUrl"></span></td>
|
||||
<th>Proxy URL</th>
|
||||
<td><span id="proxyUrl"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>성공여부</th>
|
||||
<td><span id="success"></span></td>
|
||||
<th>재시도횟수</th>
|
||||
<td><span id="retryCount"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>발송서버</th>
|
||||
<td><span id="sendBy"></span></td>
|
||||
<th>발송일시</th>
|
||||
<td><span id="sentAt"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Signature</th>
|
||||
<td><span id="signature"></span></td>
|
||||
<th>User Secret</th>
|
||||
<td><span id="userSecret"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Payload</th>
|
||||
<td colspan="3"><div id="payload" class="pre-wrap"></div></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Response Body</th>
|
||||
<td colspan="3"><div id="responseBody" class="pre-wrap"></div></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>에러 메세지</th>
|
||||
<td colspan="3"><div id="errorMessage" class="pre-wrap"></div></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -33,6 +33,18 @@
|
||||
width: 100%;
|
||||
height: 250px;
|
||||
}
|
||||
/* 달력 팝업이 파이차트 가운데 숫자(z-index:10) 위로 오도록 */
|
||||
#ui-datepicker-div {
|
||||
z-index: 9999 !important;
|
||||
}
|
||||
.cssbtn.small {
|
||||
height: 24px;
|
||||
border-radius: 2px;
|
||||
min-width: 24px;
|
||||
box-shadow: none;
|
||||
border-color: #ebebec;
|
||||
font-size: 11px;
|
||||
}
|
||||
</style>
|
||||
<jsp:include page="/jsp/common/include/script.jsp"/>
|
||||
<script src="<c:url value="/addon/echarts/echarts.min.js"/>"></script>
|
||||
@@ -58,6 +70,7 @@
|
||||
|
||||
// 총건수 도넛 차트
|
||||
var totalDonutOption = {
|
||||
color: ['#5470C6', '#FAC858', '#EE6666'],
|
||||
title: {
|
||||
text: '총 건수 분포',
|
||||
left: 'center',
|
||||
@@ -66,22 +79,27 @@
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
formatter: '{b}: {c} ({d}%)'
|
||||
formatter: function (params) {
|
||||
return params.name + ': ' + params.value.toLocaleString() + ' (' + params.percent + '%)';
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
orient: 'horizontal',
|
||||
bottom: 10,
|
||||
orient: 'vertical',
|
||||
right: 30,
|
||||
top: 'middle',
|
||||
data: ['성공', 'Timeout', '시스템오류']
|
||||
},
|
||||
series: [{
|
||||
name: '총건수',
|
||||
type: 'pie',
|
||||
radius: ['40%', '70%'],
|
||||
center: ['50%', '50%'],
|
||||
radius: ['38%', '65%'],
|
||||
center: ['44%', '55%'],
|
||||
avoidLabelOverlap: true,
|
||||
label: {
|
||||
show: true,
|
||||
formatter: '{b}: {c}'
|
||||
formatter: function (params) {
|
||||
return params.name + ': ' + params.value.toLocaleString();
|
||||
}
|
||||
},
|
||||
emphasis: {
|
||||
label: {
|
||||
@@ -94,22 +112,10 @@
|
||||
show: true
|
||||
},
|
||||
data: [
|
||||
{ value: 0, name: '성공', itemStyle: { color: '#91CC75' } },
|
||||
{ value: 0, name: '성공', itemStyle: { color: '#5470C6' } },
|
||||
{ value: 0, name: 'Timeout', itemStyle: { color: '#FAC858' } },
|
||||
{ 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 +125,7 @@
|
||||
|
||||
// 호출량 차트
|
||||
var callOption = {
|
||||
title: { text: '호출량 추이', left: 'center', textStyle: { fontSize: 14 } },
|
||||
title: { text: '호출량 추이', left: 'center', top: 10, textStyle: { fontSize: 14 } },
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: { type: 'cross', label: { backgroundColor: '#6a7985' } }
|
||||
@@ -191,13 +197,9 @@
|
||||
{ value: totalTimeout, name: 'Timeout' },
|
||||
{ value: totalSystemErr, name: '시스템오류' }
|
||||
]
|
||||
}],
|
||||
graphic: [{
|
||||
style: {
|
||||
text: grandTotal.toLocaleString()
|
||||
}
|
||||
}]
|
||||
});
|
||||
$("#totalDonutCenterText").text(grandTotal.toLocaleString());
|
||||
|
||||
|
||||
// 호출량 차트 업데이트
|
||||
@@ -297,6 +299,28 @@
|
||||
fetchChartData();
|
||||
}
|
||||
|
||||
// 조회기간을 시작일 기준 이전/다음 달의 1일~말일로 변경 후 조회 (direction: -1 이전달, 1 다음달)
|
||||
function shiftMonthRange(direction) {
|
||||
var v = $("input[name=searchStartDateTime]").val().replace(/-/g, "");
|
||||
if (!v || v.length < 6) return;
|
||||
|
||||
var year = parseInt(v.substring(0, 4));
|
||||
var month = parseInt(v.substring(4, 6)) - 1; // 0-based
|
||||
|
||||
var first = new Date(year, month + direction, 1);
|
||||
var last = new Date(year, month + direction + 1, 0); // 해당 달의 말일
|
||||
|
||||
function pad(n) { return String(n).padStart(2, '0'); }
|
||||
function fmt(d) {
|
||||
return d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate());
|
||||
}
|
||||
|
||||
$("input[name=searchStartDateTime]").val(fmt(first));
|
||||
$("input[name=searchEndDateTime]").val(fmt(last));
|
||||
|
||||
search();
|
||||
}
|
||||
|
||||
function exportToExcel(isSummary) {
|
||||
var cmdType = isSummary ? 'EXCEL_EXPORT_SUMMARY' : 'EXCEL_EXPORT';
|
||||
console.log('[Excel Export] Starting... (type: ' + cmdType + ')');
|
||||
@@ -427,12 +451,13 @@
|
||||
mtype: 'POST',
|
||||
postData: gridPostData,
|
||||
colNames: [
|
||||
'API명',
|
||||
'API ID', 'API명',
|
||||
'총건수', '성공', 'Timeout', '시스템오류',
|
||||
'평균응답(ms)', '최소응답(ms)', '최대응답(ms)'
|
||||
],
|
||||
colModel: [
|
||||
{ name: 'apiName', align: 'left', width: '250', sortable: false },
|
||||
{ name: 'apiName', align: 'left', width: '150', sortable: false },
|
||||
{ name: 'apiDesc', align: 'left', width: '250', sortable: false },
|
||||
{ name: 'totalCnt', align: 'right', width: '80', formatter: numberFormatter, sortable: false },
|
||||
{ name: 'successCnt', align: 'right', width: '80', formatter: numberFormatter, sortable: false },
|
||||
{ name: 'timeoutCnt', align: 'right', width: '80', formatter: numberFormatter, sortable: false },
|
||||
@@ -455,6 +480,9 @@
|
||||
for (var i = 0; i < colModel.length; i++) {
|
||||
$(this).setColProp(colModel[i].name, { sortable: false });
|
||||
}
|
||||
// 조회(reloadGrid) 시 컬럼 폭이 고정폭(shrinkToFit:false)으로 남아
|
||||
// 실제 컨테이너 폭과 어긋나 가로 스크롤이 생기는 것을 방지
|
||||
$(this).jqGrid('setGridWidth', $('#content_middle').width(), true);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -466,7 +494,7 @@
|
||||
mtype: 'POST',
|
||||
postData: gridPostData,
|
||||
colNames: [
|
||||
'통계시간', 'API명', '인스턴스', '업무구분', '클라이언트ID',
|
||||
'통계시간', 'API ID', '인스턴스', '업무구분', '클라이언트ID',
|
||||
'Inbound Adapter', 'Outbound Adapter',
|
||||
'총건수', '성공', 'Timeout', '시스템오류',
|
||||
'평균응답(ms)', '최소응답(ms)', '최대응답(ms)'
|
||||
@@ -501,6 +529,9 @@
|
||||
for (var i = 0; i < colModel.length; i++) {
|
||||
$(this).setColProp(colModel[i].name, { sortable: false });
|
||||
}
|
||||
// 조회(reloadGrid) 시 컬럼 폭이 고정폭(shrinkToFit:false)으로 남아
|
||||
// 실제 컨테이너 폭과 어긋나 가로 스크롤이 생기는 것을 방지
|
||||
$(this).jqGrid('setGridWidth', $('#content_middle').width(), true);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -510,6 +541,8 @@
|
||||
$("input[name=searchStartDateTime]").inputmask("9999-99-99", { 'autoUnmask': true });
|
||||
$("input[name=searchEndDateTime]").inputmask("9999-99-99", { 'autoUnmask': true });
|
||||
|
||||
$("input[name=searchStartDateTime], input[name=searchEndDateTime]").datepicker();
|
||||
|
||||
// 기본값 설정 (어제 기준 해당 월 1일 ~ 어제)
|
||||
var today = getToday();
|
||||
var todayStr = today.replace(/-/g, '');
|
||||
@@ -549,6 +582,14 @@
|
||||
search();
|
||||
});
|
||||
|
||||
$("#btnPrevMonth").click(function() {
|
||||
shiftMonthRange(-1);
|
||||
});
|
||||
|
||||
$("#btnNextMonth").click(function() {
|
||||
shiftMonthRange(1);
|
||||
});
|
||||
|
||||
$("#btn_excel_export_summary").click(function() {
|
||||
exportToExcel(true);
|
||||
});
|
||||
@@ -587,41 +628,51 @@
|
||||
</button>
|
||||
</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>
|
||||
<tr>
|
||||
<th style="width:100px;">조회기간</th>
|
||||
<th>조회기간</th>
|
||||
<td colspan="5">
|
||||
<input type="text" name="searchStartDateTime" value="${param.searchStartDateTime}" style="width:100px;">
|
||||
~
|
||||
<input type="text" name="searchEndDateTime" value="${param.searchEndDateTime}" style="width:100px;">
|
||||
<button type="button" id="btnPrevMonth" class="cssbtn small"><</button>
|
||||
<button type="button" id="btnNextMonth" class="cssbtn small">></button>
|
||||
<span style="color:#888; font-size:12px; margin-left:10px;">(최대 31일)</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th style="width:100px;">API명</th>
|
||||
<th>API ID</th>
|
||||
<td>
|
||||
<input type="text" name="searchApiName" value="${param.searchApiName}">
|
||||
</td>
|
||||
<th style="width:100px;">업무구분</th>
|
||||
<th>업무구분</th>
|
||||
<td>
|
||||
<input type="text" name="searchBizDivCode" value="${param.searchBizDivCode}">
|
||||
</td>
|
||||
<th style="width:100px;">인스턴스</th>
|
||||
<th>인스턴스</th>
|
||||
<td>
|
||||
<input type="text" name="searchGwInstanceId" value="${param.searchGwInstanceId}">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th style="width:100px;">클라이언트ID</th>
|
||||
<th>클라이언트ID</th>
|
||||
<td>
|
||||
<input type="text" name="searchClientId" value="${param.searchClientId}">
|
||||
</td>
|
||||
<th style="width:100px;">Inbound Adapter</th>
|
||||
<th>Inbound Adapter</th>
|
||||
<td>
|
||||
<input type="text" name="searchInboundAdapter" value="${param.searchInboundAdapter}">
|
||||
</td>
|
||||
<th style="width:100px;">Outbound Adapter</th>
|
||||
<th>Outbound Adapter</th>
|
||||
<td>
|
||||
<input type="text" name="searchOutboundAdapter" value="${param.searchOutboundAdapter}">
|
||||
</td>
|
||||
@@ -630,14 +681,17 @@
|
||||
</table>
|
||||
<!-- 도넛 차트 (상단 50%씩) -->
|
||||
<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>
|
||||
|
||||
<!-- 요약 그리드 -->
|
||||
<div style="margin-top: 20px;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
|
||||
<div class="title" style="margin: 0;">API명별 요약 통계</div>
|
||||
<div class="title" style="margin: 0;">API별 요약 통계</div>
|
||||
<button type="button" class="cssbtn" id="btn_excel_export_summary" level="R">
|
||||
<i class="material-icons">file_download</i> Excel 다운로드 (요약)
|
||||
</button>
|
||||
|
||||
@@ -33,6 +33,18 @@
|
||||
width: 100%;
|
||||
height: 250px;
|
||||
}
|
||||
/* 달력 팝업이 파이차트 가운데 숫자(z-index:10) 위로 오도록 */
|
||||
#ui-datepicker-div {
|
||||
z-index: 9999 !important;
|
||||
}
|
||||
.cssbtn.small {
|
||||
height: 24px;
|
||||
border-radius: 2px;
|
||||
min-width: 24px;
|
||||
box-shadow: none;
|
||||
border-color: #ebebec;
|
||||
font-size: 11px;
|
||||
}
|
||||
</style>
|
||||
<jsp:include page="/jsp/common/include/script.jsp"/>
|
||||
<script src="<c:url value="/addon/echarts/echarts.min.js"/>"></script>
|
||||
@@ -40,6 +52,8 @@
|
||||
var url = '<c:url value="/onl/kjb/statistics/apiStatsHourMan.json"/>';
|
||||
var url_view = '<c:url value="/onl/kjb/statistics/apiStatsHourMan.view"/>';
|
||||
var url_minute_view = '<c:url value="/onl/kjb/statistics/apiStatsMinuteMan.view"/>';
|
||||
var url_left_menu = '<c:url value="/leftMenu.do"/>';
|
||||
var DASHBOARD_MENU_ID = '0305001'; // apiStatsMinuteMan(대시보드) 메뉴 ID
|
||||
|
||||
var totalDonutChart, callChart;
|
||||
|
||||
@@ -59,6 +73,7 @@
|
||||
|
||||
// 총건수 도넛 차트
|
||||
var totalDonutOption = {
|
||||
color: ['#5470C6', '#FAC858', '#EE6666'],
|
||||
title: {
|
||||
text: '총 건수 분포',
|
||||
left: 'center',
|
||||
@@ -67,22 +82,27 @@
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
formatter: '{b}: {c} ({d}%)'
|
||||
formatter: function (params) {
|
||||
return params.name + ': ' + params.value.toLocaleString() + ' (' + params.percent + '%)';
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
orient: 'horizontal',
|
||||
bottom: 10,
|
||||
orient: 'vertical',
|
||||
right: 30,
|
||||
top: 'middle',
|
||||
data: ['성공', 'Timeout', '시스템오류']
|
||||
},
|
||||
series: [{
|
||||
name: '총건수',
|
||||
type: 'pie',
|
||||
radius: ['40%', '70%'],
|
||||
center: ['50%', '50%'],
|
||||
radius: ['38%', '65%'],
|
||||
center: ['44%', '55%'],
|
||||
avoidLabelOverlap: true,
|
||||
label: {
|
||||
show: true,
|
||||
formatter: '{b}: {c}'
|
||||
formatter: function (params) {
|
||||
return params.name + ': ' + params.value.toLocaleString();
|
||||
}
|
||||
},
|
||||
emphasis: {
|
||||
label: {
|
||||
@@ -95,22 +115,10 @@
|
||||
show: true
|
||||
},
|
||||
data: [
|
||||
{ value: 0, name: '성공', itemStyle: { color: '#91CC75' } },
|
||||
{ value: 0, name: '성공', itemStyle: { color: '#5470C6' } },
|
||||
{ value: 0, name: 'Timeout', itemStyle: { color: '#FAC858' } },
|
||||
{ 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 +128,7 @@
|
||||
|
||||
// 호출량 차트
|
||||
var callOption = {
|
||||
title: { text: '호출량 추이', left: 'center', textStyle: { fontSize: 14 } },
|
||||
title: { text: '호출량 추이', left: 'center', top: 10, textStyle: { fontSize: 14 } },
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: { type: 'cross', label: { backgroundColor: '#6a7985' } }
|
||||
@@ -156,23 +164,20 @@
|
||||
var hour = parseInt(statTime.substring(8, 10));
|
||||
|
||||
var startDate = year + '-' + month + '-' + day;
|
||||
var startTime = (hour < 10 ? '0' + hour : hour) + ':00';
|
||||
|
||||
// 1시간 후 계산 (23시인 경우 다음날 00시로 자동 계산)
|
||||
var d = new Date(parseInt(year), parseInt(month) - 1, parseInt(day), hour, 0, 0);
|
||||
d.setHours(d.getHours() + 1);
|
||||
|
||||
var endDate = d.getFullYear() + '-' +
|
||||
(d.getMonth() + 1 < 10 ? '0' : '') + (d.getMonth() + 1) + '-' +
|
||||
(d.getDate() < 10 ? '0' : '') + d.getDate();
|
||||
var endTime = (d.getHours() < 10 ? '0' : '') + d.getHours() + ':00';
|
||||
var hourStr = (hour < 10 ? '0' + hour : hour);
|
||||
var startTime = hourStr + ':00';
|
||||
// 해당 시간대 끝(정시~59분)으로 이동 (예: 10:00~10:59)
|
||||
var endTime = hourStr + ':59';
|
||||
|
||||
var params = '?searchStartDate=' + startDate + '&searchStartTime=' + startTime
|
||||
+ '&searchEndDate=' + endDate + '&searchEndTime=' + endTime;
|
||||
params += '&menuId='+'${param.menuId}';
|
||||
+ '&searchEndDate=' + startDate + '&searchEndTime=' + endTime;
|
||||
params += '&menuId='+DASHBOARD_MENU_ID;
|
||||
params += '&cmd='+'LIST';
|
||||
params += '&serviceType='+'APIGW';
|
||||
|
||||
// 왼쪽 메뉴도 대시보드(분단위 통계) 메뉴로 전환
|
||||
parent.leftFrame.location.href = url_left_menu + '?menuId=' + DASHBOARD_MENU_ID
|
||||
+ '&serviceType=' + sessionStorage["serviceType"];
|
||||
location.href = url_minute_view + params;
|
||||
}
|
||||
|
||||
@@ -227,13 +232,9 @@
|
||||
{ value: totalTimeout, name: 'Timeout' },
|
||||
{ value: totalSystemErr, name: '시스템오류' }
|
||||
]
|
||||
}],
|
||||
graphic: [{
|
||||
style: {
|
||||
text: grandTotal.toLocaleString()
|
||||
}
|
||||
}]
|
||||
});
|
||||
$("#totalDonutCenterText").text(grandTotal.toLocaleString());
|
||||
|
||||
|
||||
// 호출량 차트 업데이트
|
||||
@@ -303,6 +304,25 @@
|
||||
fetchChartData();
|
||||
}
|
||||
|
||||
// 조회일자를 하루 이동 후 조회 (direction: -1 이전날, 1 이후날)
|
||||
function shiftSearchDate(direction) {
|
||||
var v = $("input[name=searchDate]").val().replace(/-/g, "");
|
||||
if (!v || v.length !== 8) return;
|
||||
|
||||
var d = new Date(
|
||||
parseInt(v.substring(0, 4)),
|
||||
parseInt(v.substring(4, 6)) - 1,
|
||||
parseInt(v.substring(6, 8))
|
||||
);
|
||||
d.setDate(d.getDate() + direction);
|
||||
|
||||
function pad(n) { return String(n).padStart(2, '0'); }
|
||||
$("input[name=searchDate]").val(
|
||||
d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate()));
|
||||
|
||||
search();
|
||||
}
|
||||
|
||||
function exportToExcel(isSummary) {
|
||||
var cmdType = isSummary ? 'EXCEL_EXPORT_SUMMARY' : 'EXCEL_EXPORT';
|
||||
console.log('[Excel Export] Starting... (type: ' + cmdType + ')');
|
||||
@@ -429,12 +449,13 @@
|
||||
mtype: 'POST',
|
||||
postData: gridPostData,
|
||||
colNames: [
|
||||
'API명',
|
||||
'API ID', 'API 명',
|
||||
'총건수', '성공', 'Timeout', '시스템오류',
|
||||
'평균응답(ms)', '최소응답(ms)', '최대응답(ms)'
|
||||
],
|
||||
colModel: [
|
||||
{ name: 'apiName', align: 'left', width: '250', sortable: false },
|
||||
{ name: 'apiName', align: 'left', width: '150', sortable: false },
|
||||
{ name: 'apiDesc', align: 'left', width: '250', sortable: false },
|
||||
{ name: 'totalCnt', align: 'right', width: '80', formatter: numberFormatter, sortable: false },
|
||||
{ name: 'successCnt', align: 'right', width: '80', formatter: numberFormatter, sortable: false },
|
||||
{ name: 'timeoutCnt', align: 'right', width: '80', formatter: numberFormatter, sortable: false },
|
||||
@@ -457,6 +478,9 @@
|
||||
for (var i = 0; i < colModel.length; i++) {
|
||||
$(this).setColProp(colModel[i].name, { sortable: false });
|
||||
}
|
||||
// 조회(reloadGrid) 시 컬럼 폭이 고정폭(shrinkToFit:false)으로 남아
|
||||
// 실제 컨테이너 폭과 어긋나 가로 스크롤이 생기는 것을 방지
|
||||
$(this).jqGrid('setGridWidth', $('#content_middle').width(), true);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -468,7 +492,7 @@
|
||||
mtype: 'POST',
|
||||
postData: gridPostData,
|
||||
colNames: [
|
||||
'통계시간', 'API명', '인스턴스', '업무구분', '클라이언트ID',
|
||||
'통계시간', 'API ID', '인스턴스', '업무구분', '클라이언트ID',
|
||||
'Inbound Adapter', 'Outbound Adapter',
|
||||
'총건수', '성공', 'Timeout', '시스템오류',
|
||||
'평균응답(ms)', '최소응답(ms)', '최대응답(ms)'
|
||||
@@ -503,6 +527,9 @@
|
||||
for (var i = 0; i < colModel.length; i++) {
|
||||
$(this).setColProp(colModel[i].name, { sortable: false });
|
||||
}
|
||||
// 조회(reloadGrid) 시 컬럼 폭이 고정폭(shrinkToFit:false)으로 남아
|
||||
// 실제 컨테이너 폭과 어긋나 가로 스크롤이 생기는 것을 방지
|
||||
$(this).jqGrid('setGridWidth', $('#content_middle').width(), true);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -511,6 +538,8 @@
|
||||
// 날짜 입력 마스크
|
||||
$("input[name=searchDate]").inputmask("9999-99-99", { 'autoUnmask': true });
|
||||
|
||||
$("input[name=searchDate]").datepicker();
|
||||
|
||||
// 기본값 설정 (오늘)
|
||||
var today = getToday();
|
||||
if (!$("input[name=searchDate]").val()) {
|
||||
@@ -529,6 +558,14 @@
|
||||
search();
|
||||
});
|
||||
|
||||
$("#btnPrevDate").click(function() {
|
||||
shiftSearchDate(-1);
|
||||
});
|
||||
|
||||
$("#btnNextDate").click(function() {
|
||||
shiftSearchDate(1);
|
||||
});
|
||||
|
||||
$("#btn_excel_export_summary").click(function() {
|
||||
exportToExcel(true);
|
||||
});
|
||||
@@ -567,41 +604,49 @@
|
||||
</button>
|
||||
</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>
|
||||
<tr>
|
||||
<th style="width:100px;">조회일자</th>
|
||||
<td colspan="3">
|
||||
<th>조회일자</th>
|
||||
<td colspan="5">
|
||||
<input type="text" name="searchDate" value="${param.searchDate}" style="width:100px;">
|
||||
<button type="button" id="btnPrevDate" class="cssbtn small"><</button>
|
||||
<button type="button" id="btnNextDate" class="cssbtn small">></button>
|
||||
<span style="color:#888; font-size:12px; margin-left:10px;">(선택일 00시 ~ 23시 조회)</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th style="width:100px;">API명</th>
|
||||
<th>API ID</th>
|
||||
<td>
|
||||
<input type="text" name="searchApiName" value="${param.searchApiName}">
|
||||
</td>
|
||||
<th style="width:100px;">인스턴스</th>
|
||||
<th>업무구분</th>
|
||||
<td>
|
||||
<input type="text" name="searchBizDivCode" value="${param.searchBizDivCode}">
|
||||
</td>
|
||||
<th>인스턴스</th>
|
||||
<td>
|
||||
<input type="text" name="searchGwInstanceId" value="${param.searchGwInstanceId}">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th style="width:100px;">업무구분</th>
|
||||
<td>
|
||||
<input type="text" name="searchBizDivCode" value="${param.searchBizDivCode}">
|
||||
</td>
|
||||
<th style="width:100px;">클라이언트ID</th>
|
||||
<th>클라이언트ID</th>
|
||||
<td>
|
||||
<input type="text" name="searchClientId" value="${param.searchClientId}">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th style="width:100px;">Inbound Adapter</th>
|
||||
<th>Inbound Adapter</th>
|
||||
<td>
|
||||
<input type="text" name="searchInboundAdapter" value="${param.searchInboundAdapter}">
|
||||
</td>
|
||||
<th style="width:100px;">Outbound Adapter</th>
|
||||
<th>Outbound Adapter</th>
|
||||
<td>
|
||||
<input type="text" name="searchOutboundAdapter" value="${param.searchOutboundAdapter}">
|
||||
</td>
|
||||
@@ -610,14 +655,17 @@
|
||||
</table>
|
||||
<!-- 도넛 차트 (상단 50%씩) -->
|
||||
<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>
|
||||
|
||||
<!-- 요약 그리드 -->
|
||||
<div style="margin-top: 20px;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
|
||||
<div class="title" style="margin: 0;">API명별 요약 통계</div>
|
||||
<div class="title" style="margin: 0;">API별 요약 통계</div>
|
||||
<button type="button" class="cssbtn" id="btn_excel_export_summary" level="R">
|
||||
<i class="material-icons">file_download</i> Excel 다운로드 (요약)
|
||||
</button>
|
||||
|
||||
@@ -26,6 +26,18 @@
|
||||
border: 1px solid #ddd;
|
||||
background: #fff;
|
||||
}
|
||||
.cssbtn.small {
|
||||
height: 24px;
|
||||
border-radius: 2px;
|
||||
min-width: 24px;
|
||||
box-shadow: none;
|
||||
border-color: #ebebec;
|
||||
font-size: 11px;
|
||||
}
|
||||
/* 달력 팝업이 파이차트 가운데 숫자(z-index:10) 위로 오도록 */
|
||||
#ui-datepicker-div {
|
||||
z-index: 9999 !important;
|
||||
}
|
||||
</style>
|
||||
<jsp:include page="/jsp/common/include/script.jsp"/>
|
||||
<script src="<c:url value="/addon/echarts/echarts.min.js"/>"></script>
|
||||
@@ -51,6 +63,9 @@
|
||||
|
||||
// 총건수 도넛 차트
|
||||
var totalDonutOption = {
|
||||
// updateCharts()에서 series[].data를 itemStyle 없이 재설정하면 조각별 itemStyle.color가
|
||||
// 병합 과정에서 유실되므로, 옵션 레벨 color 팔레트(성공/Timeout/시스템오류 순)로 색을 고정한다.
|
||||
color: ['#5470C6', '#FAC858', '#EE6666'],
|
||||
title: {
|
||||
text: '총 건수 분포',
|
||||
left: 'center',
|
||||
@@ -59,22 +74,27 @@
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
formatter: '{b}: {c} ({d}%)'
|
||||
formatter: function (params) {
|
||||
return params.name + ': ' + params.value.toLocaleString() + ' (' + params.percent + '%)';
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
orient: 'horizontal',
|
||||
bottom: 10,
|
||||
orient: 'vertical',
|
||||
right: 30,
|
||||
top: 'middle',
|
||||
data: ['성공', 'Timeout', '시스템오류']
|
||||
},
|
||||
series: [{
|
||||
name: '총건수',
|
||||
type: 'pie',
|
||||
radius: ['40%', '70%'],
|
||||
center: ['50%', '50%'],
|
||||
radius: ['38%', '65%'],
|
||||
center: ['44%', '55%'],
|
||||
avoidLabelOverlap: true,
|
||||
label: {
|
||||
show: true,
|
||||
formatter: '{b}: {c}'
|
||||
formatter: function (params) {
|
||||
return params.name + ': ' + params.value.toLocaleString();
|
||||
}
|
||||
},
|
||||
emphasis: {
|
||||
label: {
|
||||
@@ -87,22 +107,10 @@
|
||||
show: true
|
||||
},
|
||||
data: [
|
||||
{ value: 0, name: '성공', itemStyle: { color: '#91CC75' } },
|
||||
{ value: 0, name: '성공', itemStyle: { color: '#5470C6' } },
|
||||
{ value: 0, name: 'Timeout', itemStyle: { color: '#FAC858' } },
|
||||
{ value: 0, name: '시스템오류', itemStyle: { color: '#EE6666' } }
|
||||
]
|
||||
}],
|
||||
graphic: [{
|
||||
type: 'text',
|
||||
left: 'center',
|
||||
top: 'center',
|
||||
style: {
|
||||
text: '0',
|
||||
textAlign: 'center',
|
||||
fill: '#333',
|
||||
fontSize: 24,
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
}]
|
||||
};
|
||||
|
||||
@@ -112,7 +120,7 @@
|
||||
|
||||
// 호출량 차트
|
||||
var callOption = {
|
||||
title: { text: '호출량 추이', left: 'center', textStyle: { fontSize: 14 } },
|
||||
title: { text: '호출량 추이', left: 'center', top: 10, textStyle: { fontSize: 14 } },
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: { type: 'cross', label: { backgroundColor: '#6a7985' } }
|
||||
@@ -189,13 +197,9 @@
|
||||
{ value: totalTimeout, name: 'Timeout' },
|
||||
{ value: totalSystemErr, name: '시스템오류' }
|
||||
]
|
||||
}],
|
||||
graphic: [{
|
||||
style: {
|
||||
text: grandTotal.toLocaleString()
|
||||
}
|
||||
}]
|
||||
});
|
||||
$("#totalDonutCenterText").text(grandTotal.toLocaleString());
|
||||
|
||||
|
||||
// 호출량 차트 업데이트
|
||||
@@ -214,7 +218,6 @@
|
||||
function validateAndAdjustDateRange() {
|
||||
var startDate = $("input[name=searchStartDate]").val().replace(/-/g, "");
|
||||
var startTime = $("input[name=searchStartTime]").val().replace(/:/g, "");
|
||||
var endDate = $("input[name=searchEndDate]").val().replace(/-/g, "");
|
||||
var endTime = $("input[name=searchEndTime]").val().replace(/:/g, "");
|
||||
|
||||
if (!startDate || !startTime) return null;
|
||||
@@ -230,35 +233,43 @@
|
||||
);
|
||||
}
|
||||
|
||||
// UI 업데이트 헬퍼
|
||||
function updateEndDateTime(dt) {
|
||||
var d = dt.getFullYear() +
|
||||
// 종료일시(yyyyMMddHHmm) 포맷 헬퍼
|
||||
function formatDateTime(dt) {
|
||||
return dt.getFullYear() +
|
||||
String(dt.getMonth() + 1).padStart(2, '0') +
|
||||
String(dt.getDate()).padStart(2, '0');
|
||||
var t = String(dt.getHours()).padStart(2, '0') +
|
||||
String(dt.getDate()).padStart(2, '0') +
|
||||
String(dt.getHours()).padStart(2, '0') +
|
||||
String(dt.getMinutes()).padStart(2, '0');
|
||||
$("input[name=searchEndDate]").val(d.substring(0, 4) + '-' + d.substring(4, 6) + '-' + d.substring(6, 8));
|
||||
$("input[name=searchEndTime]").val(t.substring(0, 2) + ':' + t.substring(2, 4));
|
||||
return d + t;
|
||||
}
|
||||
|
||||
// 종료시각 UI 업데이트 헬퍼
|
||||
function updateEndTime(dt) {
|
||||
$("input[name=searchEndTime]").val(
|
||||
String(dt.getHours()).padStart(2, '0') + ':' + String(dt.getMinutes()).padStart(2, '0'));
|
||||
return formatDateTime(dt);
|
||||
}
|
||||
|
||||
var start = parseDateTime(startDate, startTime);
|
||||
var startDateTime = startDate + startTime;
|
||||
var endDateTime;
|
||||
|
||||
if (endDate && endTime) {
|
||||
var end = parseDateTime(endDate, endTime);
|
||||
if (endTime) {
|
||||
// 종료일은 시작일 기준. 종료시각이 시작시각보다 이르면 자정을 넘긴 것으로 보고 +1일
|
||||
var end = parseDateTime(startDate, endTime);
|
||||
if (end < start) {
|
||||
end = new Date(end.getTime() + 24 * 60 * 60 * 1000);
|
||||
}
|
||||
var diffMinutes = (end - start) / (1000 * 60);
|
||||
|
||||
if (diffMinutes > 60) {
|
||||
// 1시간 초과 시 시작 + 1시간으로 조정
|
||||
endDateTime = updateEndDateTime(new Date(start.getTime() + 60 * 60 * 1000));
|
||||
// 1시간 초과 시 시작시각 기준 1시간치(정시~59분)로 조정
|
||||
endDateTime = updateEndTime(new Date(start.getTime() + 59 * 60 * 1000));
|
||||
} else {
|
||||
endDateTime = endDate + endTime;
|
||||
endDateTime = formatDateTime(end);
|
||||
}
|
||||
} else {
|
||||
// 종료시간 미입력 시 시작 + 1시간으로 설정
|
||||
endDateTime = updateEndDateTime(new Date(start.getTime() + 60 * 60 * 1000));
|
||||
// 종료시간 미입력 시 시작시각 기준 1시간치(정시~59분)로 설정
|
||||
endDateTime = updateEndTime(new Date(start.getTime() + 59 * 60 * 1000));
|
||||
}
|
||||
|
||||
return { start: startDateTime, end: endDateTime };
|
||||
@@ -313,6 +324,45 @@
|
||||
fetchChartData(range);
|
||||
}
|
||||
|
||||
// 조회기간을 정시 단위 1시간 구간으로 이동 (direction: -1 이전, 1 이후)
|
||||
// ex) 시작 08:35, 이전 클릭 -> 08:00~08:59, 다시 -> 07:00~07:59
|
||||
// ex) 시작 08:35, 이후 클릭 -> 09:00~09:59, 다시 -> 10:00~10:59
|
||||
function shiftTimeRange(direction) {
|
||||
var startDate = $("input[name=searchStartDate]").val().replace(/-/g, "");
|
||||
var startTime = $("input[name=searchStartTime]").val().replace(/:/g, "");
|
||||
if (!startDate || !startTime) return;
|
||||
|
||||
var start = new Date(
|
||||
parseInt(startDate.substring(0, 4)),
|
||||
parseInt(startDate.substring(4, 6)) - 1,
|
||||
parseInt(startDate.substring(6, 8)),
|
||||
parseInt(startTime.substring(0, 2)),
|
||||
parseInt(startTime.substring(2, 4))
|
||||
);
|
||||
|
||||
if (direction < 0) {
|
||||
// 분 단위가 있으면 정시로 내림, 이미 정시면 1시간 전
|
||||
if (start.getMinutes() === 0) start.setHours(start.getHours() - 1);
|
||||
else start.setMinutes(0);
|
||||
} else {
|
||||
// 분 단위가 있으면 다음 정시로 올림, 이미 정시면 1시간 후
|
||||
start.setMinutes(0);
|
||||
start.setHours(start.getHours() + 1);
|
||||
}
|
||||
|
||||
// 종료시각은 같은 시간대의 59분으로 설정 (예: 10:00~10:59)
|
||||
var end = new Date(start.getTime() + 59 * 60 * 1000);
|
||||
|
||||
function pad(n) { return String(n).padStart(2, '0'); }
|
||||
|
||||
$("input[name=searchStartDate]").val(
|
||||
start.getFullYear() + '-' + pad(start.getMonth() + 1) + '-' + pad(start.getDate()));
|
||||
$("input[name=searchStartTime]").val(pad(start.getHours()) + ':' + pad(start.getMinutes()));
|
||||
$("input[name=searchEndTime]").val(pad(end.getHours()) + ':' + pad(end.getMinutes()));
|
||||
|
||||
search();
|
||||
}
|
||||
|
||||
function exportToExcel(isSummary) {
|
||||
var cmdType = isSummary ? 'EXCEL_EXPORT_SUMMARY' : 'EXCEL_EXPORT';
|
||||
console.log('[Excel Export] Starting... (type: ' + cmdType + ')');
|
||||
@@ -328,13 +378,10 @@
|
||||
serviceType: '${param.serviceType}'
|
||||
};
|
||||
|
||||
var startDate = $("input[name=searchStartDate]").val().replace(/-/g, "");
|
||||
var startTime = $("input[name=searchStartTime]").val().replace(/:/g, "");
|
||||
var endDate = $("input[name=searchEndDate]").val().replace(/-/g, "");
|
||||
var endTime = $("input[name=searchEndTime]").val().replace(/:/g, "");
|
||||
if (startDate && startTime) {
|
||||
postData.searchStartDateTime = startDate + startTime;
|
||||
postData.searchEndDateTime = endDate + endTime;
|
||||
var range = validateAndAdjustDateRange();
|
||||
if (range) {
|
||||
postData.searchStartDateTime = range.start;
|
||||
postData.searchEndDateTime = range.end;
|
||||
}
|
||||
|
||||
console.log('[Excel Export] Request data:', postData);
|
||||
@@ -442,12 +489,13 @@
|
||||
mtype: 'POST',
|
||||
postData: gridPostData,
|
||||
colNames: [
|
||||
'API명',
|
||||
'API ID', 'API 명',
|
||||
'총건수', '성공', 'Timeout', '시스템오류',
|
||||
'평균응답(ms)', '최소응답(ms)', '최대응답(ms)'
|
||||
],
|
||||
colModel: [
|
||||
{ name: 'apiName', align: 'left', width: '250', sortable: false },
|
||||
{ name: 'apiName', align: 'left', width: '150', sortable: false },
|
||||
{ name: 'apiDesc', align: 'left', width: '250', sortable: false },
|
||||
{ name: 'totalCnt', align: 'right', width: '80', formatter: numberFormatter, sortable: false },
|
||||
{ name: 'successCnt', align: 'right', width: '80', formatter: numberFormatter, sortable: false },
|
||||
{ name: 'timeoutCnt', align: 'right', width: '80', formatter: numberFormatter, sortable: false },
|
||||
@@ -470,6 +518,9 @@
|
||||
for (var i = 0; i < colModel.length; i++) {
|
||||
$(this).setColProp(colModel[i].name, { sortable: false });
|
||||
}
|
||||
// 조회(reloadGrid) 시 컬럼 폭이 고정폭(shrinkToFit:false)으로 남아
|
||||
// 실제 컨테이너 폭과 어긋나 가로 스크롤이 생기는 것을 방지
|
||||
$(this).jqGrid('setGridWidth', $('#content_middle').width(), true);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -481,7 +532,7 @@
|
||||
mtype: 'POST',
|
||||
postData: gridPostData,
|
||||
colNames: [
|
||||
'통계시간', 'API명', '인스턴스', '업무구분', '클라이언트ID',
|
||||
'통계시간', 'API ID', '인스턴스', '업무구분', '클라이언트ID',
|
||||
'Inbound Adapter', 'Outbound Adapter',
|
||||
'총건수', '성공', 'Timeout', '시스템오류',
|
||||
'평균응답(ms)', '최소응답(ms)', '최대응답(ms)'
|
||||
@@ -516,44 +567,39 @@
|
||||
for (var i = 0; i < colModel.length; i++) {
|
||||
$(this).setColProp(colModel[i].name, { sortable: false });
|
||||
}
|
||||
// 조회(reloadGrid) 시 컬럼 폭이 고정폭(shrinkToFit:false)으로 남아
|
||||
// 실제 컨테이너 폭과 어긋나 가로 스크롤이 생기는 것을 방지
|
||||
$(this).jqGrid('setGridWidth', $('#content_middle').width(), true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
// 날짜/시간 입력 마스크
|
||||
$("input[name=searchStartDate], input[name=searchEndDate]").inputmask("9999-99-99", { 'autoUnmask': true });
|
||||
$("input[name=searchStartDate]").inputmask("9999-99-99", { 'autoUnmask': true });
|
||||
$("input[name=searchStartTime], input[name=searchEndTime]").inputmask("99:99", { 'autoUnmask': true });
|
||||
|
||||
// 기본값 설정 (현재 시간 기준 1시간 전 정각 ~ 현재 정각)
|
||||
var now = new Date();
|
||||
var endHour = now.getHours();
|
||||
var startHour = endHour - 1;
|
||||
var startDate, endDate;
|
||||
$("input[name=searchStartDate]").datepicker();
|
||||
|
||||
if (startHour < 0) {
|
||||
startHour = 23;
|
||||
var yesterday = new Date(now);
|
||||
yesterday.setDate(yesterday.getDate() - 1);
|
||||
startDate = yesterday.getFullYear() + '-' +
|
||||
String(yesterday.getMonth() + 1).padStart(2, '0') + '-' +
|
||||
String(yesterday.getDate()).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;
|
||||
// 기본값 설정 (현재 시간(분) 기준 59분 전 ~ 현재 시간(분))
|
||||
var now = new Date();
|
||||
var oneHourAgo = new Date(now.getTime() - 59 * 60 * 1000);
|
||||
|
||||
function formatDate(d) {
|
||||
return d.getFullYear() + '-' +
|
||||
String(d.getMonth() + 1).padStart(2, '0') + '-' +
|
||||
String(d.getDate()).padStart(2, '0');
|
||||
}
|
||||
function formatTime(d) {
|
||||
return String(d.getHours()).padStart(2, '0') + ':' + String(d.getMinutes()).padStart(2, '0');
|
||||
}
|
||||
|
||||
if (!$("input[name=searchStartDate]").val()) {
|
||||
$("input[name=searchStartDate]").val(startDate);
|
||||
$("input[name=searchStartTime]").val(String(startHour).padStart(2, '0') + ':00');
|
||||
$("input[name=searchStartDate]").val(formatDate(oneHourAgo));
|
||||
$("input[name=searchStartTime]").val(formatTime(oneHourAgo));
|
||||
}
|
||||
if (!$("input[name=searchEndDate]").val()) {
|
||||
$("input[name=searchEndDate]").val(endDate);
|
||||
$("input[name=searchEndTime]").val(String(endHour).padStart(2, '0') + ':00');
|
||||
if (!$("input[name=searchEndTime]").val()) {
|
||||
$("input[name=searchEndTime]").val(formatTime(now));
|
||||
}
|
||||
|
||||
initCharts();
|
||||
@@ -568,6 +614,14 @@
|
||||
search();
|
||||
});
|
||||
|
||||
$("#btnPrevTime").click(function() {
|
||||
shiftTimeRange(-1);
|
||||
});
|
||||
|
||||
$("#btnNextTime").click(function() {
|
||||
shiftTimeRange(1);
|
||||
});
|
||||
|
||||
$("#btn_excel_export_summary").click(function() {
|
||||
exportToExcel(true);
|
||||
});
|
||||
@@ -582,8 +636,8 @@
|
||||
}
|
||||
});
|
||||
|
||||
// 종료일/시간 변경 시 1시간 초과 검증
|
||||
$("input[name=searchEndDate], input[name=searchEndTime]").on('change blur', function() {
|
||||
// 종료시각 변경 시 1시간 초과 검증
|
||||
$("input[name=searchEndTime]").on('change blur', function() {
|
||||
validateAndAdjustDateRange();
|
||||
});
|
||||
|
||||
@@ -615,20 +669,28 @@
|
||||
<tbody>
|
||||
<tr>
|
||||
<th style="width:100px;">조회기간</th>
|
||||
<td colspan="3">
|
||||
<input type="text" name="searchStartDate" value="${param.searchStartDate}" style="width:100px;">
|
||||
<input type="text" name="searchStartTime" value="${param.searchStartTime}" style="width:60px;">
|
||||
<td style="width:400px">
|
||||
<input type="text" name="searchStartDate" value="${param.searchStartDate}" style="width:80px;">
|
||||
<input type="text" name="searchStartTime" value="${param.searchStartTime}" style="width:40px;">
|
||||
~
|
||||
<input type="text" name="searchEndDate" value="${param.searchEndDate}" style="width:100px;">
|
||||
<input type="text" name="searchEndTime" value="${param.searchEndTime}" style="width:60px;">
|
||||
<input type="text" name="searchEndTime" value="${param.searchEndTime}" style="width:40px;">
|
||||
<button type="button" id="btnPrevTime" class="cssbtn small"><</button>
|
||||
<button type="button" id="btnNextTime" class="cssbtn small">></button>
|
||||
<span style="color:#888; font-size:12px; margin-left:10px;">(최대 1시간, 초과시 자동 조정)</span>
|
||||
</td>
|
||||
<th style="width:100px;">API ID</th>
|
||||
<td style="width:400px">
|
||||
<input type="text" name="searchApiName" value="${param.searchApiName}">
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<!-- 도넛 차트 (상단 50%씩) -->
|
||||
<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>
|
||||
|
||||
@@ -645,7 +707,17 @@
|
||||
<div id="pagerSummary"></div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 상세 그리드 -->
|
||||
<div style="margin-top: 20px;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
|
||||
<div class="title" style="margin: 0;">상세 통계</div>
|
||||
<button type="button" class="cssbtn" id="btn_excel_export" level="R">
|
||||
<i class="material-icons">file_download</i> Excel 다운로드 (상세)
|
||||
</button>
|
||||
</div>
|
||||
<table id="grid"></table>
|
||||
<div id="pager"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
@@ -58,6 +58,7 @@
|
||||
|
||||
// 총건수 도넛 차트
|
||||
var totalDonutOption = {
|
||||
color: ['#5470C6', '#FAC858', '#EE6666'],
|
||||
title: {
|
||||
text: '총 건수 분포',
|
||||
left: 'center',
|
||||
@@ -66,22 +67,27 @@
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
formatter: '{b}: {c} ({d}%)'
|
||||
formatter: function (params) {
|
||||
return params.name + ': ' + params.value.toLocaleString() + ' (' + params.percent + '%)';
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
orient: 'horizontal',
|
||||
bottom: 10,
|
||||
orient: 'vertical',
|
||||
right: 30,
|
||||
top: 'middle',
|
||||
data: ['성공', 'Timeout', '시스템오류']
|
||||
},
|
||||
series: [{
|
||||
name: '총건수',
|
||||
type: 'pie',
|
||||
radius: ['40%', '70%'],
|
||||
center: ['50%', '50%'],
|
||||
radius: ['38%', '65%'],
|
||||
center: ['44%', '55%'],
|
||||
avoidLabelOverlap: true,
|
||||
label: {
|
||||
show: true,
|
||||
formatter: '{b}: {c}'
|
||||
formatter: function (params) {
|
||||
return params.name + ': ' + params.value.toLocaleString();
|
||||
}
|
||||
},
|
||||
emphasis: {
|
||||
label: {
|
||||
@@ -94,22 +100,10 @@
|
||||
show: true
|
||||
},
|
||||
data: [
|
||||
{ value: 0, name: '성공', itemStyle: { color: '#91CC75' } },
|
||||
{ value: 0, name: '성공', itemStyle: { color: '#5470C6' } },
|
||||
{ value: 0, name: 'Timeout', itemStyle: { color: '#FAC858' } },
|
||||
{ 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 +113,7 @@
|
||||
|
||||
// 호출량 차트
|
||||
var callOption = {
|
||||
title: { text: '호출량 추이', left: 'center', textStyle: { fontSize: 14 } },
|
||||
title: { text: '호출량 추이', left: 'center', top: 10, textStyle: { fontSize: 14 } },
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: { type: 'cross', label: { backgroundColor: '#6a7985' } }
|
||||
@@ -189,13 +183,9 @@
|
||||
{ value: totalTimeout, name: 'Timeout' },
|
||||
{ value: totalSystemErr, name: '시스템오류' }
|
||||
]
|
||||
}],
|
||||
graphic: [{
|
||||
style: {
|
||||
text: grandTotal.toLocaleString()
|
||||
}
|
||||
}]
|
||||
});
|
||||
$("#totalDonutCenterText").text(grandTotal.toLocaleString());
|
||||
|
||||
|
||||
|
||||
@@ -432,12 +422,13 @@
|
||||
mtype: 'POST',
|
||||
postData: gridPostData,
|
||||
colNames: [
|
||||
'API명',
|
||||
'API ID', 'API 명',
|
||||
'총건수', '성공', 'Timeout', '시스템오류',
|
||||
'평균응답(ms)', '최소응답(ms)', '최대응답(ms)'
|
||||
],
|
||||
colModel: [
|
||||
{ name: 'apiName', align: 'left', width: '250', sortable: false },
|
||||
{ name: 'apiName', align: 'left', width: '150', sortable: false },
|
||||
{ name: 'apiDesc', align: 'left', width: '250', sortable: false },
|
||||
{ name: 'totalCnt', align: 'right', width: '80', formatter: numberFormatter, sortable: false },
|
||||
{ name: 'successCnt', align: 'right', width: '80', formatter: numberFormatter, sortable: false },
|
||||
{ name: 'timeoutCnt', align: 'right', width: '80', formatter: numberFormatter, sortable: false },
|
||||
@@ -460,6 +451,9 @@
|
||||
for (var i = 0; i < colModel.length; i++) {
|
||||
$(this).setColProp(colModel[i].name, { sortable: false });
|
||||
}
|
||||
// 조회(reloadGrid) 시 컬럼 폭이 고정폭(shrinkToFit:false)으로 남아
|
||||
// 실제 컨테이너 폭과 어긋나 가로 스크롤이 생기는 것을 방지
|
||||
$(this).jqGrid('setGridWidth', $('#content_middle').width(), true);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -471,7 +465,7 @@
|
||||
mtype: 'POST',
|
||||
postData: gridPostData,
|
||||
colNames: [
|
||||
'통계시간', 'API명', '인스턴스', '업무구분', '클라이언트ID',
|
||||
'통계시간', 'API ID', '인스턴스', '업무구분', '클라이언트ID',
|
||||
'Inbound Adapter', 'Outbound Adapter',
|
||||
'총건수', '성공', 'Timeout', '시스템오류',
|
||||
'평균응답(ms)', '최소응답(ms)', '최대응답(ms)'
|
||||
@@ -506,6 +500,9 @@
|
||||
for (var i = 0; i < colModel.length; i++) {
|
||||
$(this).setColProp(colModel[i].name, { sortable: false });
|
||||
}
|
||||
// 조회(reloadGrid) 시 컬럼 폭이 고정폭(shrinkToFit:false)으로 남아
|
||||
// 실제 컨테이너 폭과 어긋나 가로 스크롤이 생기는 것을 방지
|
||||
$(this).jqGrid('setGridWidth', $('#content_middle').width(), true);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -515,11 +512,21 @@
|
||||
$("input[name=searchStartDateTime]").inputmask("9999-99", { 'autoUnmask': true });
|
||||
$("input[name=searchEndDateTime]").inputmask("9999-99", { 'autoUnmask': true });
|
||||
|
||||
// 기본값 설정 (최근 12개월)
|
||||
// 기본값 설정: 올해 1월 ~ 지난달 (현재가 1월이면 작년 1월 ~ 작년 12월)
|
||||
var today = new Date();
|
||||
var endMonth = today.getFullYear() + '-' + (today.getMonth() + 1 < 10 ? '0' : '') + (today.getMonth() + 1);
|
||||
var startDate = new Date(today.getFullYear(), today.getMonth() - 11, 1);
|
||||
var startMonth = startDate.getFullYear() + '-' + (startDate.getMonth() + 1 < 10 ? '0' : '') + (startDate.getMonth() + 1);
|
||||
var startDate, endDate;
|
||||
if (today.getMonth() === 0) { // 1월
|
||||
startDate = new Date(today.getFullYear() - 1, 0, 1);
|
||||
endDate = new Date(today.getFullYear() - 1, 11, 1);
|
||||
} else {
|
||||
startDate = new Date(today.getFullYear(), 0, 1);
|
||||
endDate = new Date(today.getFullYear(), today.getMonth() - 1, 1);
|
||||
}
|
||||
function toMonthStr(d) {
|
||||
return d.getFullYear() + '-' + (d.getMonth() + 1 < 10 ? '0' : '') + (d.getMonth() + 1);
|
||||
}
|
||||
var startMonth = toMonthStr(startDate);
|
||||
var endMonth = toMonthStr(endDate);
|
||||
|
||||
if (!$("input[name=searchStartDateTime]").val()) {
|
||||
$("input[name=searchStartDateTime]").val(startMonth);
|
||||
@@ -578,11 +585,19 @@
|
||||
</button>
|
||||
</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>
|
||||
<tr>
|
||||
<th style="width:100px;">조회기간</th>
|
||||
<td colspan="3">
|
||||
<th>조회기간</th>
|
||||
<td colspan="5">
|
||||
<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">
|
||||
@@ -590,31 +605,29 @@
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th style="width:100px;">API명</th>
|
||||
<th>API ID</th>
|
||||
<td>
|
||||
<input type="text" name="searchApiName" value="${param.searchApiName}">
|
||||
</td>
|
||||
<th style="width:100px;">인스턴스</th>
|
||||
<th>업무구분</th>
|
||||
<td>
|
||||
<input type="text" name="searchBizDivCode" value="${param.searchBizDivCode}">
|
||||
</td>
|
||||
<th>인스턴스</th>
|
||||
<td>
|
||||
<input type="text" name="searchGwInstanceId" value="${param.searchGwInstanceId}">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th style="width:100px;">업무구분</th>
|
||||
<td>
|
||||
<input type="text" name="searchBizDivCode" value="${param.searchBizDivCode}">
|
||||
</td>
|
||||
<th style="width:100px;">클라이언트ID</th>
|
||||
<th>클라이언트ID</th>
|
||||
<td>
|
||||
<input type="text" name="searchClientId" value="${param.searchClientId}">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th style="width:100px;">Inbound Adapter</th>
|
||||
<th>Inbound Adapter</th>
|
||||
<td>
|
||||
<input type="text" name="searchInboundAdapter" value="${param.searchInboundAdapter}">
|
||||
</td>
|
||||
<th style="width:100px;">Outbound Adapter</th>
|
||||
<th>Outbound Adapter</th>
|
||||
<td>
|
||||
<input type="text" name="searchOutboundAdapter" value="${param.searchOutboundAdapter}">
|
||||
</td>
|
||||
@@ -623,7 +636,10 @@
|
||||
</table>
|
||||
<!-- 도넛 차트 (상단 50%씩) -->
|
||||
<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>
|
||||
|
||||
@@ -631,7 +647,7 @@
|
||||
<!-- 요약 그리드 -->
|
||||
<div style="margin-top: 20px;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
|
||||
<div class="title" style="margin: 0;">API명별 요약 통계</div>
|
||||
<div class="title" style="margin: 0;">API별 요약 통계</div>
|
||||
<button type="button" class="cssbtn" id="btn_excel_export_summary" level="R">
|
||||
<i class="material-icons">file_download</i> Excel 다운로드 (요약)
|
||||
</button>
|
||||
|
||||
@@ -58,6 +58,7 @@
|
||||
|
||||
// 총건수 도넛 차트
|
||||
var totalDonutOption = {
|
||||
color: ['#5470C6', '#FAC858', '#EE6666'],
|
||||
title: {
|
||||
text: '총 건수 분포',
|
||||
left: 'center',
|
||||
@@ -66,22 +67,27 @@
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
formatter: '{b}: {c} ({d}%)'
|
||||
formatter: function (params) {
|
||||
return params.name + ': ' + params.value.toLocaleString() + ' (' + params.percent + '%)';
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
orient: 'horizontal',
|
||||
bottom: 10,
|
||||
orient: 'vertical',
|
||||
right: 30,
|
||||
top: 'middle',
|
||||
data: ['성공', 'Timeout', '시스템오류']
|
||||
},
|
||||
series: [{
|
||||
name: '총건수',
|
||||
type: 'pie',
|
||||
radius: ['40%', '70%'],
|
||||
center: ['50%', '50%'],
|
||||
radius: ['38%', '65%'],
|
||||
center: ['44%', '55%'],
|
||||
avoidLabelOverlap: true,
|
||||
label: {
|
||||
show: true,
|
||||
formatter: '{b}: {c}'
|
||||
formatter: function (params) {
|
||||
return params.name + ': ' + params.value.toLocaleString();
|
||||
}
|
||||
},
|
||||
emphasis: {
|
||||
label: {
|
||||
@@ -94,22 +100,10 @@
|
||||
show: true
|
||||
},
|
||||
data: [
|
||||
{ value: 0, name: '성공', itemStyle: { color: '#91CC75' } },
|
||||
{ value: 0, name: '성공', itemStyle: { color: '#5470C6' } },
|
||||
{ value: 0, name: 'Timeout', itemStyle: { color: '#FAC858' } },
|
||||
{ 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 +113,7 @@
|
||||
|
||||
// 호출량 차트
|
||||
var callOption = {
|
||||
title: { text: '호출량 추이', left: 'center', textStyle: { fontSize: 14 } },
|
||||
title: { text: '호출량 추이', left: 'center', top: 10, textStyle: { fontSize: 14 } },
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: { type: 'cross', label: { backgroundColor: '#6a7985' } }
|
||||
@@ -191,13 +185,9 @@
|
||||
{ value: totalTimeout, name: 'Timeout' },
|
||||
{ value: totalSystemErr, name: '시스템오류' }
|
||||
]
|
||||
}],
|
||||
graphic: [{
|
||||
style: {
|
||||
text: grandTotal.toLocaleString()
|
||||
}
|
||||
}]
|
||||
});
|
||||
$("#totalDonutCenterText").text(grandTotal.toLocaleString());
|
||||
|
||||
|
||||
|
||||
@@ -408,12 +398,13 @@
|
||||
mtype: 'POST',
|
||||
postData: gridPostData,
|
||||
colNames: [
|
||||
'API명',
|
||||
'API ID', 'API 명',
|
||||
'총건수', '성공', 'Timeout', '시스템오류',
|
||||
'평균응답(ms)', '최소응답(ms)', '최대응답(ms)'
|
||||
],
|
||||
colModel: [
|
||||
{ name: 'apiName', align: 'left', width: '250', sortable: false },
|
||||
{ name: 'apiName', align: 'left', width: '150', sortable: false },
|
||||
{ name: 'apiDesc', align: 'left', width: '250', sortable: false },
|
||||
{ name: 'totalCnt', align: 'right', width: '80', formatter: numberFormatter, sortable: false },
|
||||
{ name: 'successCnt', align: 'right', width: '80', formatter: numberFormatter, sortable: false },
|
||||
{ name: 'timeoutCnt', align: 'right', width: '80', formatter: numberFormatter, sortable: false },
|
||||
@@ -436,6 +427,9 @@
|
||||
for (var i = 0; i < colModel.length; i++) {
|
||||
$(this).setColProp(colModel[i].name, { sortable: false });
|
||||
}
|
||||
// 조회(reloadGrid) 시 컬럼 폭이 고정폭(shrinkToFit:false)으로 남아
|
||||
// 실제 컨테이너 폭과 어긋나 가로 스크롤이 생기는 것을 방지
|
||||
$(this).jqGrid('setGridWidth', $('#content_middle').width(), true);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -447,7 +441,7 @@
|
||||
mtype: 'POST',
|
||||
postData: gridPostData,
|
||||
colNames: [
|
||||
'통계시간', 'API명', '인스턴스', '업무구분', '클라이언트ID',
|
||||
'통계시간', 'API ID', '인스턴스', '업무구분', '클라이언트ID',
|
||||
'Inbound Adapter', 'Outbound Adapter',
|
||||
'총건수', '성공', 'Timeout', '시스템오류',
|
||||
'평균응답(ms)', '최소응답(ms)', '최대응답(ms)'
|
||||
@@ -482,6 +476,9 @@
|
||||
for (var i = 0; i < colModel.length; i++) {
|
||||
$(this).setColProp(colModel[i].name, { sortable: false });
|
||||
}
|
||||
// 조회(reloadGrid) 시 컬럼 폭이 고정폭(shrinkToFit:false)으로 남아
|
||||
// 실제 컨테이너 폭과 어긋나 가로 스크롤이 생기는 것을 방지
|
||||
$(this).jqGrid('setGridWidth', $('#content_middle').width(), true);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -549,42 +546,48 @@
|
||||
</button>
|
||||
</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>
|
||||
<tr>
|
||||
<th style="width:100px;">조회기간</th>
|
||||
<td colspan="3">
|
||||
<th>조회기간</th>
|
||||
<td colspan="5">
|
||||
<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">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th style="width:100px;">API명</th>
|
||||
<th>API ID</th>
|
||||
<td>
|
||||
<input type="text" name="searchApiName" value="${param.searchApiName}">
|
||||
</td>
|
||||
<th style="width:100px;">인스턴스</th>
|
||||
<th>업무구분</th>
|
||||
<td>
|
||||
<input type="text" name="searchBizDivCode" value="${param.searchBizDivCode}">
|
||||
</td>
|
||||
<th>인스턴스</th>
|
||||
<td>
|
||||
<input type="text" name="searchGwInstanceId" value="${param.searchGwInstanceId}">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th style="width:100px;">업무구분</th>
|
||||
<td>
|
||||
<input type="text" name="searchBizDivCode" value="${param.searchBizDivCode}">
|
||||
</td>
|
||||
<th style="width:100px;">클라이언트ID</th>
|
||||
<th>클라이언트ID</th>
|
||||
<td>
|
||||
<input type="text" name="searchClientId" value="${param.searchClientId}">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th style="width:100px;">Inbound Adapter</th>
|
||||
<th>Inbound Adapter</th>
|
||||
<td>
|
||||
<input type="text" name="searchInboundAdapter" value="${param.searchInboundAdapter}">
|
||||
</td>
|
||||
<th style="width:100px;">Outbound Adapter</th>
|
||||
<th>Outbound Adapter</th>
|
||||
<td>
|
||||
<input type="text" name="searchOutboundAdapter" value="${param.searchOutboundAdapter}">
|
||||
</td>
|
||||
@@ -593,14 +596,17 @@
|
||||
</table>
|
||||
<!-- 도넛 차트 (상단 50%씩) -->
|
||||
<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>
|
||||
|
||||
<!-- 요약 그리드 -->
|
||||
<div style="margin-top: 20px;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
|
||||
<div class="title" style="margin: 0;">API명별 요약 통계</div>
|
||||
<div class="title" style="margin: 0;">API별 요약 통계</div>
|
||||
<button type="button" class="cssbtn" id="btn_excel_export_summary" level="R">
|
||||
<i class="material-icons">file_download</i> Excel 다운로드 (요약)
|
||||
</button>
|
||||
|
||||
@@ -15,6 +15,19 @@
|
||||
<jsp:include page="/jsp/common/include/css.jsp"/>
|
||||
<jsp:include page="/jsp/common/include/script.jsp"/>
|
||||
<script src="<c:url value="/addon/echarts/echarts.min.js"/>"></script>
|
||||
<style>
|
||||
#gbox_grid .ui-jqgrid-ftable tr.footrow td {
|
||||
height: 26px;
|
||||
}
|
||||
.cssbtn.small {
|
||||
height: 24px;
|
||||
border-radius: 2px;
|
||||
min-width: 24px;
|
||||
box-shadow: none;
|
||||
border-color: #ebebec;
|
||||
font-size: 11px;
|
||||
}
|
||||
</style>
|
||||
<script language="javascript">
|
||||
var url = '<c:url value="/onl/kjb/statistics/apiUseStatsMan.json"/>';
|
||||
var url_view = '<c:url value="/onl/kjb/statistics/apiUseStatsMan.view"/>';
|
||||
@@ -60,13 +73,53 @@
|
||||
}
|
||||
|
||||
|
||||
// 조회구분(searchType)에 따라 '구분' / '구분명' 컬럼의 헤더명·폭·표시여부를 조정
|
||||
function applyGridColumnsByType(type) {
|
||||
var $g = $("#grid");
|
||||
if (type === 'API') {
|
||||
$g.jqGrid('setLabel', 'orgName', 'API ID');
|
||||
$g.jqGrid('setLabel', 'apiDesc', 'API명');
|
||||
$g.jqGrid('setColProp', 'orgName', { widthOrg: 150, width: 150 });
|
||||
$g.jqGrid('setColProp', 'apiDesc', { widthOrg: 250, width: 250 });
|
||||
$g.jqGrid('showCol', 'apiDesc');
|
||||
} else {
|
||||
$g.jqGrid('setLabel', 'orgName', (type === 'DATE') ? '사용일' : '제휴사');
|
||||
$g.jqGrid('setColProp', 'orgName', { widthOrg: 250, width: 250 });
|
||||
$g.jqGrid('hideCol', 'apiDesc');
|
||||
}
|
||||
$g.jqGrid('setGridWidth', $('#content_middle').width(), true);
|
||||
}
|
||||
|
||||
function search() {
|
||||
var postData = getPostData("cmd", "LIST");
|
||||
if (postData) {
|
||||
$("#grid").setGridParam({ url: url, postData: postData, page: 1 }).trigger("reloadGrid");
|
||||
applyGridColumnsByType($('input[name="searchType"]:checked').val());
|
||||
$("#grid").setGridParam({ url: url, postData: postData }).trigger("reloadGrid");
|
||||
}
|
||||
}
|
||||
|
||||
// 조회기간을 시작일 기준 이전/다음 달의 1일~말일로 변경 후 조회 (direction: -1 이전달, 1 다음달)
|
||||
function shiftMonthRange(direction) {
|
||||
var v = $("input[name=searchStartDate]").val().replace(/-/g, "");
|
||||
if (!v || v.length < 6) return;
|
||||
|
||||
var year = parseInt(v.substring(0, 4));
|
||||
var month = parseInt(v.substring(4, 6)) - 1; // 0-based
|
||||
|
||||
var first = new Date(year, month + direction, 1);
|
||||
var last = new Date(year, month + direction + 1, 0); // 해당 달의 말일
|
||||
|
||||
function pad(n) { return String(n).padStart(2, '0'); }
|
||||
function fmt(d) {
|
||||
return d.getFullYear() + '' + pad(d.getMonth() + 1) + pad(d.getDate());
|
||||
}
|
||||
|
||||
$("input[name=searchStartDate]").val(fmt(first));
|
||||
$("input[name=searchEndDate]").val(fmt(last));
|
||||
|
||||
search();
|
||||
}
|
||||
|
||||
function exportToExcel() {
|
||||
|
||||
var postData = getPostData("cmd", "EXCEL_EXPORT");
|
||||
@@ -181,12 +234,13 @@
|
||||
mtype: 'POST',
|
||||
postData: gridPostData,
|
||||
colNames: [
|
||||
'구분',
|
||||
'제휴사', 'API명',
|
||||
'총건수', '성공', '성공율(%)', '실패율(%)', 'Timeout', '시스템오류',
|
||||
'평균응답(ms)', '최소응답(ms)', '최대응답(ms)'
|
||||
],
|
||||
colModel: [
|
||||
{ name: 'orgName', align: 'left', width: '250', sortable: false },
|
||||
{ name: 'apiDesc', align: 'left', width: '250', sortable: false, hidden: true },
|
||||
{ name: 'totalCnt', align: 'right', width: '80', formatter: numberFormatter, sortable: false },
|
||||
{ name: 'successCnt', align: 'right', width: '80', formatter: numberFormatter, sortable: false },
|
||||
{ name: 'successRate', align: 'right', width: '80', formatter: decimalFormatter, sortable: false },
|
||||
@@ -198,19 +252,18 @@
|
||||
{ name: 'maxRespTime', align: 'right', width: '90', formatter: numberFormatter, sortable: false }
|
||||
],
|
||||
jsonReader: { repeatitems: false },
|
||||
pager: $('#pager'),
|
||||
page: '${param.page}',
|
||||
rowNum: '${rmsDefaultRowNum}',
|
||||
autoheight: true,
|
||||
height: 'auto',
|
||||
height: 500,
|
||||
rowNum: 10000,
|
||||
autowidth: true,
|
||||
viewrecords: true,
|
||||
rowList: eval('[${rmsDefaultRowList}]'),
|
||||
footerrow: true,
|
||||
userDataOnFooter: true,
|
||||
loadComplete: function(d) {
|
||||
var colModel = $(this).getGridParam("colModel");
|
||||
for (var i = 0; i < colModel.length; i++) {
|
||||
$(this).setColProp(colModel[i].name, { sortable: false });
|
||||
}
|
||||
|
||||
$(".ui-jqgrid-ftable td").css({ "font-weight": "bold", "background-color": "#f5f5f5" });
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -221,9 +274,22 @@
|
||||
$("input[name=searchStartDate], input[name=searchEndDate]").inputmask("9999-99-99", { 'autoUnmask': true });
|
||||
$("input[name=searchStartDate], input[name=searchEndDate]").datepicker();
|
||||
|
||||
// 기본값 설정: 이번달 1일 ~ 어제 (오늘이 1일이면 지난달 1일 ~ 지난달 말일)
|
||||
var today = getToday();
|
||||
var startDate = today.substring(0,6)+"01";
|
||||
var endDate = today;
|
||||
var now = new Date();
|
||||
function toYmd(d) {
|
||||
var m = d.getMonth() + 1;
|
||||
var day = d.getDate();
|
||||
return d.getFullYear() + '' + (m < 10 ? '0' : '') + m + (day < 10 ? '0' : '') + day;
|
||||
}
|
||||
var startDate, endDate;
|
||||
if (now.getDate() === 1) {
|
||||
startDate = toYmd(new Date(now.getFullYear(), now.getMonth() - 1, 1));
|
||||
endDate = toYmd(new Date(now.getFullYear(), now.getMonth(), 0));
|
||||
} else {
|
||||
startDate = today.substring(0,6) + "01";
|
||||
endDate = toYmd(new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1));
|
||||
}
|
||||
|
||||
|
||||
if (!$("input[name=searchStartDate]").val()) {
|
||||
@@ -242,6 +308,18 @@
|
||||
search();
|
||||
});
|
||||
|
||||
$("#btnPrevMonth").click(function() {
|
||||
shiftMonthRange(-1);
|
||||
});
|
||||
|
||||
$("#btnNextMonth").click(function() {
|
||||
shiftMonthRange(1);
|
||||
});
|
||||
|
||||
$("input[name=searchType]").click(function() {
|
||||
search();
|
||||
});
|
||||
|
||||
$("#btn_excel").click(function() {
|
||||
exportToExcel();
|
||||
});
|
||||
@@ -280,6 +358,8 @@
|
||||
<input type="text" name="searchStartDate" id="searchStartDate" value="${param.searchStartDate}" style="width:100px;">
|
||||
~
|
||||
<input type="text" name="searchEndDate" id="searchEndDate" value="${param.searchEndDate}" style="width:100px;">
|
||||
<button type="button" id="btnPrevMonth" class="cssbtn small"><</button>
|
||||
<button type="button" id="btnNextMonth" class="cssbtn small">></button>
|
||||
<span style="color:#888; font-size:12px; margin-left:10px;">(최대 31일)</span>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -294,7 +374,7 @@
|
||||
<td>
|
||||
<input type="text" name="searchOrgName" id="searchOrgName" value="${param.searchOrgName}">
|
||||
</td>
|
||||
<th style="width:120px;">API명</th>
|
||||
<th style="width:120px;">API ID</th>
|
||||
<td>
|
||||
<input type="text" name="searchApiName" id="searchApiName" value="${param.searchApiName}">
|
||||
</td>
|
||||
@@ -304,7 +384,6 @@
|
||||
|
||||
<div>
|
||||
<table id="grid"></table>
|
||||
<div id="pager"></div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
<%@ 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=utf-8">
|
||||
<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_loader = '<c:url value="/loader.do"/>';
|
||||
|
||||
function loadList() {
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: url,
|
||||
dataType: "json",
|
||||
data: {cmd: 'LIST_DEPLOY_FILES'},
|
||||
success: function (json) {
|
||||
console.log('list', json)
|
||||
if (json) {
|
||||
$('#grid').jqGrid('clearGridData').jqGrid('setGridParam', {data: json.rows || []}).trigger('reloadGrid');
|
||||
}
|
||||
},
|
||||
error: function (e) {
|
||||
alert(e.responseText);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function callDeploy(filePath) {
|
||||
var postData = [];
|
||||
postData.push({name: "cmd", value: "interface"});
|
||||
postData.push({name: "control", value: "uploadfull"});
|
||||
postData.push({name: "filePath", value: filePath});
|
||||
return new Promise(function (resolve, reject) {
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: url_loader,
|
||||
data: postData,
|
||||
success: function (response) {
|
||||
resolve(response);
|
||||
},
|
||||
error: function (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
$("#btn_close").click(function () {
|
||||
window.close();
|
||||
});
|
||||
|
||||
$('#grid').jqGrid({
|
||||
datatype: "local",
|
||||
colNames: ['업무그룹', '인터페이스', '생성일시', 'filePath'],
|
||||
colModel: [
|
||||
{name: 'bizCode', align: 'center', width: '80', sortable: true},
|
||||
{name: 'interfaceId', align: 'left', sortable: true},
|
||||
{name: 'createdDate', align: 'center', width: '130', sortable: true},
|
||||
{name: 'filePath', hidden: true}
|
||||
],
|
||||
pager: $('#pager'),
|
||||
rowNum: 20,
|
||||
rowList: [10, 20, 50, 100],
|
||||
autoheight: false,
|
||||
height: 460,
|
||||
width: 650,
|
||||
autowidth: false,
|
||||
viewrecords: true,
|
||||
shrinkToFit: true,
|
||||
multiselect: true,
|
||||
multiboxonly: false
|
||||
});
|
||||
|
||||
loadList();
|
||||
|
||||
$("#btn_deploy").click(async function () {
|
||||
var rowIds = $('#grid').jqGrid('getGridParam', 'selarrrow');
|
||||
if (rowIds.length === 0) {
|
||||
alert('배포할 인터페이스를 선택하세요.');
|
||||
return;
|
||||
}
|
||||
if (!confirm(rowIds.length + '건을 배포 하시겠습니까?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$("#btn_deploy").prop("disabled", true);
|
||||
|
||||
var successCount = 0;
|
||||
var failedList = [];
|
||||
for (var i = 0; i < rowIds.length; i++) {
|
||||
var rowData = $("#grid").getRowData(rowIds[i]);
|
||||
var target = rowData['bizCode'] + '/' + rowData['interfaceId'];
|
||||
try {
|
||||
var response = await callDeploy(rowData['filePath']);
|
||||
if (response != null && response.includes('status=success')) {
|
||||
successCount++;
|
||||
} else {
|
||||
failedList.push(target);
|
||||
}
|
||||
} catch (error) {
|
||||
failedList.push(target);
|
||||
}
|
||||
}
|
||||
|
||||
$("#btn_deploy").prop("disabled", false);
|
||||
|
||||
var msg = '배포 완료 - 성공[' + successCount + ']건, 실패[' + failedList.length + ']건';
|
||||
if (failedList.length > 0) {
|
||||
msg += '\n실패목록 : ' + failedList.join(', ');
|
||||
}
|
||||
alert(msg);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="popup_box">
|
||||
<div class="search_wrap">
|
||||
<button type="button" class="cssbtn" id="btn_deploy" level="W"><i class="material-icons">cloud_upload</i> 배포
|
||||
</button>
|
||||
<button type="button" class="cssbtn" id="btn_close" level="R"><i class="material-icons">cancel</i> <%= localeMessage.getString("button.close") %>
|
||||
</button>
|
||||
</div>
|
||||
<div class="title">배포 대상 인터페이스 목록</div>
|
||||
<table id="grid"></table>
|
||||
<div id="pager"></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,5 +1,6 @@
|
||||
<%@ page language="java" contentType="text/html; charset=utf-8" %>
|
||||
<%@ page import="java.io.*" %>
|
||||
<%@ page import="com.eactive.eai.common.util.SystemUtil" %>
|
||||
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
|
||||
<%@ include file="/jsp/common/include/localemessage.jsp" %>
|
||||
<%
|
||||
@@ -37,6 +38,13 @@
|
||||
success: function (json) {
|
||||
new makeOptions("BIZCODE", "BIZNAME").setObj($("select[name=searchEaiBzwkDstcd]")).setNoValueInclude(true).setNoValue("", "전체").setData(json.bizList).setFormat(codeName3OptionFormat).rendering();
|
||||
|
||||
var apiGroupSelect = $('select[name=searchApiGroupId]').empty();
|
||||
apiGroupSelect.append($('<option>').val('').text('전체'));
|
||||
apiGroupSelect.append($('<option>').val('UNREGISTERED').text('미등록'));
|
||||
(json.apiGroupList || []).forEach(function (group) {
|
||||
apiGroupSelect.append($('<option>').val(group.id).text(group.groupName));
|
||||
});
|
||||
|
||||
setSearchable(selectName); // 콤보에 searchable 설정
|
||||
putSelectFromParam();
|
||||
|
||||
@@ -76,6 +84,26 @@
|
||||
return cellvalue.substring(1, 4);
|
||||
}
|
||||
|
||||
function apiStatusFormatter(cellvalue, options, rowObject) {
|
||||
switch (cellvalue) {
|
||||
case 'E': return '장애';
|
||||
case 'D': return '지연';
|
||||
case 'C': return '점검';
|
||||
default: return '정상';
|
||||
}
|
||||
}
|
||||
|
||||
function specStatusFormatter(cellvalue, options, rowObject) {
|
||||
switch (cellvalue) {
|
||||
case 'PUBLISHED':
|
||||
var groupNames = rowObject.publishedGroupNames || [];
|
||||
var tooltip = groupNames.length ? groupNames.join('\n') : '게시된 그룹 없음';
|
||||
return $('<span>').text('게시(' + groupNames.length + ')').attr('title', tooltip).css('cursor', 'help').prop('outerHTML');
|
||||
case 'REGISTERED': return '등록';
|
||||
default: return '';
|
||||
}
|
||||
}
|
||||
|
||||
function list() {
|
||||
detail()
|
||||
var gridPostData = getSearchForJqgrid("cmd", "LIST"); //jqgrid에서는 object 로
|
||||
@@ -90,6 +118,8 @@
|
||||
'API FULL PATH',
|
||||
'요청',
|
||||
'응답',
|
||||
'상태',
|
||||
'스펙 상태',
|
||||
'작성자',
|
||||
'가상응답여부',
|
||||
'변경일시(*)',
|
||||
@@ -99,11 +129,13 @@
|
||||
{name: 'eaiSvcName', align: 'left', width: '100', sortable: true},
|
||||
{name: 'eaiSvcDesc', align: 'left', sortable: false},
|
||||
{name: 'apiFullPath', align: 'left', sortable: false},
|
||||
{name: 'fromAdapter', align: 'center', width: '40', formatter: adapterNameShortFormatter, sortable: false},
|
||||
{name: 'toAdapter', align: 'center', width: '40', formatter: adapterNameShortFormatter, sortable: false},
|
||||
{name: 'author', align: 'center', width: '60', sortable: false},
|
||||
{name: 'fromAdapter', align: 'center', width: '30', formatter: adapterNameShortFormatter, sortable: false},
|
||||
{name: 'toAdapter', align: 'center', width: '30', formatter: adapterNameShortFormatter, sortable: false},
|
||||
{name: 'statusCode', align: 'center', width: '30', formatter: apiStatusFormatter, sortable: false},
|
||||
{name: 'specStatus', align: 'center', width: '40', formatter: specStatusFormatter, sortable: false, title: false},
|
||||
{name: 'author', align: 'center', width: '40', sortable: false},
|
||||
{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},
|
||||
],
|
||||
jsonReader: {
|
||||
@@ -235,6 +267,11 @@
|
||||
window.alert('이관 대상 API 성공['+successCount+']건, 실패['+failedCount+']건 서버에 다운로드 완료');
|
||||
});
|
||||
|
||||
$("#btn_deploy").click(function(){
|
||||
var url2 = '<c:url value="/onl/transaction/apim/apiInterfaceMan.view"/>?cmd=DEPLOY_POPUP';
|
||||
showModal2(url2, {}, 663, 600);
|
||||
});
|
||||
|
||||
buttonControl();
|
||||
|
||||
// 25.11.25 파일 가져오기 할 때 중복 검사 기능추가.
|
||||
@@ -369,7 +406,8 @@
|
||||
</div><!-- end content_top -->
|
||||
<div class="content_middle" id="content_middle">
|
||||
<div class="search_wrap">
|
||||
<button type="button" class="cssbtn" id="btn_download" level="W" status="DETAIL"><i class="material-icons">cloud_download</i> 이관</button>
|
||||
<button type="button" class="cssbtn" id="btn_download" level="W" status="DETAIL"><i class="material-icons">cloud_download</i> 이관</button>
|
||||
<!-- <button type="button" class="cssbtn" id="btn_deploy" level="W" status="DETAIL"><i class="material-icons">cloud_upload</i> 배포</button> -->
|
||||
<button type="button" class="cssbtn" id="btn_import" level="R" data-bs-toggle="modal"
|
||||
data-bs-target="#uploadModal"><i
|
||||
class="material-icons">upload</i> <%=localeMessage.getString("button.import")%>
|
||||
@@ -385,16 +423,10 @@
|
||||
<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}">
|
||||
@@ -404,6 +436,49 @@
|
||||
<input type="text" name="searchApiFullPath" value="${param.searchApiFullPath}">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th style="width:180px;">업무구분명</th>
|
||||
<td>
|
||||
<select name="searchEaiBzwkDstcd" value="${param.searchEaiBzwkDstcd}"></select>
|
||||
</td>
|
||||
<th style="width:180px;">당타발 구분</th>
|
||||
<td>
|
||||
<select name="searchInOutType" style="width: 100%">
|
||||
<option value="">전체</option>
|
||||
<option value="1">당발</option>
|
||||
<option value="2">타발</option>
|
||||
</select>
|
||||
</td>
|
||||
<th style="width:180px;">상태</th>
|
||||
<td>
|
||||
<select name="searchStatusCode" style="width: 100%">
|
||||
<option value="">전체</option>
|
||||
<option value="N">정상</option>
|
||||
<option value="D">지연</option>
|
||||
<option value="E">장애</option>
|
||||
<option value="C">점검</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th style="width:180px;">API 스펙 상태</th>
|
||||
<td>
|
||||
<select name="searchSpecStatus" style="width: 100%">
|
||||
<option value="">전체</option>
|
||||
<option value="UNREGISTERED">미등록</option>
|
||||
<option value="REGISTERED">등록</option>
|
||||
<option value="PUBLISHED">게시</option>
|
||||
</select>
|
||||
</td>
|
||||
<th style="width:180px;">API 그룹</th>
|
||||
<td>
|
||||
<select name="searchApiGroupId" style="width: 100%">
|
||||
<option value="">전체</option>
|
||||
<option value="UNREGISTERED">미등록</option>
|
||||
</select>
|
||||
</td>
|
||||
<th></th><td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="help-text" style="color:#666; font-size:12px; margin-top:5px;">(*) : 정렬가능항목</div>
|
||||
|
||||
@@ -78,12 +78,30 @@
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
/* iframe 모달(showModal) 콘텐츠 패딩 제거 — iframe 이 다이얼로그 폭을 최대한 사용해 내부 가로스크롤 방지 */
|
||||
.ui-dialog .ui-dialog-content { padding: 0 !important; }
|
||||
|
||||
/* OpenAPI Spec 버튼 상태 색상 (PTL_API_SPEC_INFO 기준) */
|
||||
#btn_openapi_spec.spec-exists { background:#ffe08a !important; border-color:#f0c040 !important; color:#5a4600 !important; }
|
||||
#btn_openapi_spec.spec-public { background:#b8e6a0 !important; border-color:#7cc257 !important; color:#2c4a15 !important; }
|
||||
/* 호버 상태 안내 레이어 */
|
||||
#specStatusLayer {
|
||||
position: fixed; z-index: 100000; display: none;
|
||||
background: rgba(30,30,30,0.92); color: #fff;
|
||||
padding: 8px 12px; border-radius: 6px;
|
||||
font-size: 12px; line-height: 1.6; max-width: 340px;
|
||||
white-space: pre-line; pointer-events: none;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
</style>
|
||||
<script language="javascript" >
|
||||
var isDetail = false;
|
||||
var url ='<c:url value="/onl/transaction/apim/apiInterfaceMan.json" />';
|
||||
const url_excel ='<c:url value="/onl/transaction/apim/apiInterfaceMan.excel" />';
|
||||
var url_spec = '<c:url value="/onl/transaction/apim/apiSpecMan.view"/>';
|
||||
var url_openapi_spec = '<c:url value="/onl/transaction/apim/djbApiSpecMan.view"/>';
|
||||
var url_openapi_spec_json = '<c:url value="/onl/transaction/apim/djbApiSpecMan.json"/>';
|
||||
var returnUrl ;
|
||||
let headerValues = [];
|
||||
let headerNames = [];
|
||||
@@ -486,6 +504,7 @@
|
||||
}
|
||||
|
||||
if(data.errResponseTransform != null && data.errResponseTransform != '' && data.errTransformYn == 'Y') {
|
||||
$('#toggleTransformYn').bootstrapToggle('on');
|
||||
$('#toggleErrTransformYn').bootstrapToggle('on');
|
||||
$('#rowErrResponseTransform').show();
|
||||
} else {
|
||||
@@ -535,6 +554,8 @@
|
||||
|
||||
$('#inboundHttpMethod').trigger('change');
|
||||
$('#outboundHttpMethod').trigger('change');
|
||||
|
||||
applyOpenApiSpecStatus(); // OpenAPI Spec 버튼 색상(PTL_API_SPEC_INFO)
|
||||
},
|
||||
error:function(e){
|
||||
alert(e.responseText);
|
||||
@@ -625,7 +646,7 @@
|
||||
const svcDesc = encodeURI(encodeURIComponent($('#eaiSvcDesc').val()));
|
||||
const cnvsnDesc = encodeURI(encodeURIComponent(transformDesc));
|
||||
var args = {title:transformName+' 수정', parent: true};
|
||||
var url='<c:url value="/onl/admin/rule/transform2/transform2Man.view"/>';
|
||||
var url='<c:url value="/onl/admin/rule/transformMan.view"/>';
|
||||
url += '?cmd=DETAIL';
|
||||
url += '&cnvsnName='+transformName;
|
||||
url += '&eaiSvcName='+svcName;
|
||||
@@ -772,7 +793,7 @@
|
||||
function updateFirstLayout(newTransformName, transformDesc){
|
||||
$.ajax({
|
||||
type : "POST",
|
||||
url: '<c:url value="/onl/admin/rule/transform2/transform2Man.json" />',
|
||||
url: '<c:url value="/onl/admin/rule/transformMan.json" />',
|
||||
data: { cmd: 'UPDATE',
|
||||
gridData: '[]',
|
||||
eaiSvcName: svcName,
|
||||
@@ -803,7 +824,7 @@
|
||||
};
|
||||
$.ajax({
|
||||
type : "POST",
|
||||
url: '<c:url value="/onl/admin/rule/transform2/transform2Man.json" />',
|
||||
url: '<c:url value="/onl/admin/rule/transformMan.json" />',
|
||||
data: { cmd: 'INSERT',
|
||||
gridData: '[]',
|
||||
cnvsnType,
|
||||
@@ -992,6 +1013,64 @@
|
||||
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, function(){ applyOpenApiSpecStatus(); });
|
||||
}
|
||||
}
|
||||
|
||||
// OpenAPI Spec 버튼 색상 = PTL_API_SPEC_INFO(api_id = getFullSvcName()) 상태.
|
||||
// - 미등록: 기본색 - 등록·비공개(display_yn≠Y): 노랑 - 등록·공개(display_yn=Y): 하늘
|
||||
function applyOpenApiSpecStatus() {
|
||||
var $btn = $('#btn_openapi_spec');
|
||||
$btn.removeClass('spec-exists spec-public').data('specMsg', '');
|
||||
var eaiSvcName = getFullSvcName();
|
||||
if (!eaiSvcName || eaiSvcName.length < 2) return;
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: url_openapi_spec_json,
|
||||
dataType: 'json',
|
||||
data: { cmd: 'SPEC_STATUS', eaiSvcName: eaiSvcName },
|
||||
success: function(res) {
|
||||
var exists = res && (res.exists === true || res.exists === 'true');
|
||||
var isPublic = res && (res.displayYn === 'Y');
|
||||
var msg;
|
||||
if (!exists) {
|
||||
msg = 'OpenAPI Spec 미등록\n버튼을 눌러 스펙을 생성/편집하세요.';
|
||||
} else if (isPublic) {
|
||||
$btn.addClass('spec-public');
|
||||
msg = 'OpenAPI Spec 등록됨 · 포탈 공개중 (display_yn = Y)\n포탈 메인/문서에 노출됩니다.';
|
||||
} else {
|
||||
$btn.addClass('spec-exists');
|
||||
msg = 'OpenAPI Spec 등록됨 · 비공개 (display_yn ≠ Y)\n포탈에는 노출되지 않습니다.';
|
||||
}
|
||||
$btn.data('specMsg', msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
makeValidate();
|
||||
// var bootstrapButton = $.fn.button.noConflict()
|
||||
@@ -1006,9 +1085,31 @@
|
||||
}
|
||||
init(url,key,detail);
|
||||
|
||||
// v1: 기존 API 스펙 (모달) — 기능 비교용 복원
|
||||
$("#btn_api_spec").click(function() {
|
||||
showApiSpecPopup();
|
||||
});
|
||||
// v2: 신규 OpenAPI Spec (새 탭)
|
||||
$("#btn_openapi_spec").click(function(e) {
|
||||
showOpenApiSpecPopup(e);
|
||||
});
|
||||
|
||||
// OpenAPI Spec 버튼 호버 시 현재 상태 안내 레이어
|
||||
if ($('#specStatusLayer').length === 0) {
|
||||
$('body').append('<div id="specStatusLayer"></div>');
|
||||
}
|
||||
$("#btn_openapi_spec")
|
||||
.on('mouseenter', function() {
|
||||
var msg = $(this).data('specMsg');
|
||||
if (!msg) return;
|
||||
$('#specStatusLayer').text(msg).css('display', 'block');
|
||||
})
|
||||
.on('mousemove', function(e) {
|
||||
$('#specStatusLayer').css({ left: (e.clientX + 14) + 'px', top: (e.clientY + 16) + 'px' });
|
||||
})
|
||||
.on('mouseleave', function() {
|
||||
$('#specStatusLayer').hide();
|
||||
});
|
||||
|
||||
$("#btn_modify").click(modifyApi);
|
||||
$("#btn_delete").click(function(){
|
||||
@@ -1507,7 +1608,12 @@
|
||||
<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_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 스펙 (모달). PTL_PROPERTY(Portal / djb.apispec.v1.enabled) 로 노출 제어. --%>
|
||||
<c:if test="${apiSpecV1Enabled}">
|
||||
<button type="button" class="cssbtn" id="btn_api_spec" level="R" status="DETAIL"><i class="material-icons">description</i> API 스펙</button>
|
||||
</c:if>
|
||||
<!-- 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>
|
||||
</div>
|
||||
<div class="title" id="title" style="font-size:1.4em">${rmsMenuName}</div>
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
</style>
|
||||
<script language="javascript">
|
||||
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 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,179 @@
|
||||
<%@ 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}",
|
||||
useProxy: ${useProxy},
|
||||
tokenUseProxy: ${tokenUseProxy},
|
||||
exportEnabled: ${exportEnabled},
|
||||
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>
|
||||
<%-- 내보내기 버튼은 PTL_PROPERTY(djb.openapi.export.enabled=true) 일 때만 노출. 기본 미노출. --%>
|
||||
<c:if test="${exportEnabled}">
|
||||
<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>
|
||||
</c:if>
|
||||
<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>
|
||||
@@ -55,10 +55,10 @@ $(document).ready(function() {
|
||||
'200',
|
||||
'300',
|
||||
'400',
|
||||
'900',
|
||||
/* '900', */
|
||||
'300',
|
||||
'400',
|
||||
'900',
|
||||
/* '900', */
|
||||
'IF',
|
||||
'<%= localeMessage.getString("tranStat.recv") %>',
|
||||
'<%= localeMessage.getString("common.total") %>',
|
||||
@@ -75,11 +75,11 @@ $(document).ready(function() {
|
||||
{ name : 'E200' , align : 'center' , width:'50' ,formatter:'integer',formatoptions:{thousandsSeparator:","},summaryType:'sum'},
|
||||
{ name : 'E300' , align : 'center' , width:'50' ,formatter:'integer',formatoptions:{thousandsSeparator:","},summaryType:'sum'},
|
||||
{ name : 'E400' , align : 'center' , width:'50' ,formatter:'integer',formatoptions:{thousandsSeparator:","},summaryType:'sum'},
|
||||
{ name : 'E900' , align : 'center' , width:'50' ,formatter:'integer',formatoptions:{thousandsSeparator:","},summaryType:'sum'},
|
||||
/* { name : 'E900' , align : 'center' , width:'50' ,formatter:'integer',formatoptions:{thousandsSeparator:","},summaryType:'sum'}, */
|
||||
|
||||
{ name : 'E9300' , align : 'center' , width:'50' ,formatter:'integer',formatoptions:{thousandsSeparator:","},summaryType:'sum'},
|
||||
{ name : 'E9400' , align : 'center' , width:'50' ,formatter:'integer',formatoptions:{thousandsSeparator:","},summaryType:'sum'},
|
||||
{ name : 'E9900' , align : 'center' , width:'50' ,formatter:'integer',formatoptions:{thousandsSeparator:","},summaryType:'sum'},
|
||||
/* { name : 'E9900' , align : 'center' , width:'50' ,formatter:'integer',formatoptions:{thousandsSeparator:","},summaryType:'sum'}, */
|
||||
|
||||
{ name : 'AVGEAI' , align : 'center' , width:'46' ,formatter:'integer',formatoptions:{thousandsSeparator:","},summaryType:'avg'},
|
||||
{ name : 'AVGPSV' , align : 'center' , width:'46' ,formatter:'integer',formatoptions:{thousandsSeparator:","},summaryType:'avg'},
|
||||
@@ -152,8 +152,8 @@ $(document).ready(function() {
|
||||
useColSpanStyle: true,
|
||||
groupHeaders:[
|
||||
{startColumnName: 'P100', numberOfColumns: 4, titleText: '<%= localeMessage.getString("tranStat.tranNo") %>'},
|
||||
{startColumnName: 'E100', numberOfColumns: 5, titleText: '<%= localeMessage.getString("tranStat.errNo") %>'},
|
||||
{startColumnName: 'E9300', numberOfColumns: 3, titleText: '<%= localeMessage.getString("tranStat.timeout") %>'},
|
||||
{startColumnName: 'E100', numberOfColumns: 4, titleText: '<%= localeMessage.getString("tranStat.errNo") %>'},
|
||||
{startColumnName: 'E9300', numberOfColumns: 2, titleText: '<%= localeMessage.getString("tranStat.timeout") %>'},
|
||||
{startColumnName: 'AVGEAI', numberOfColumns: 3, titleText: '<%= localeMessage.getString("tranStat.avgTime") %>'}
|
||||
]
|
||||
});
|
||||
@@ -210,9 +210,9 @@ $(document).ready(function() {
|
||||
var merge = new Array();
|
||||
merge.push(makeMerge("업무구분","0","1","0","0"));
|
||||
merge.push(makeMerge("구간별 처리건수","0","0","1","4"));
|
||||
merge.push(makeMerge("구간별 에러건수","0","0","5","9"));
|
||||
merge.push(makeMerge("타임아웃","0","0","10","12"));
|
||||
merge.push(makeMerge("평균처리시간(초)","0","0","13","15"));
|
||||
merge.push(makeMerge("구간별 에러건수","0","0","5","8"));
|
||||
merge.push(makeMerge("타임아웃","0","0","9","11"));
|
||||
merge.push(makeMerge("평균처리시간(초)","0","0","12","14"));
|
||||
|
||||
gridToExcelSubmit(url,"LIST_GRID_TO_EXCEL",$("#grid"),$("#ajaxForm"),"업무별 거래현황",merge);
|
||||
return false;
|
||||
@@ -224,9 +224,9 @@ $(document).ready(function() {
|
||||
var merge = new Array();
|
||||
merge.push(makeMerge("업무구분","0","1","0","0"));
|
||||
merge.push(makeMerge("구간별 처리건수","0","0","1","4"));
|
||||
merge.push(makeMerge("구간별 에러건수","0","0","5","9"));
|
||||
merge.push(makeMerge("타임아웃","0","0","10","12"));
|
||||
merge.push(makeMerge("평균처리시간(초)","0","0","13","15"));
|
||||
merge.push(makeMerge("구간별 에러건수","0","0","5","8"));
|
||||
merge.push(makeMerge("타임아웃","0","0","9","11"));
|
||||
merge.push(makeMerge("평균처리시간(초)","0","0","12","14"));
|
||||
gridToExcelSubmit(url_excel,"LIST_GRID_TO_EXCEL",$("#grid"),$("#ajaxForm"),'업무별 거래현황',merge);
|
||||
return false;
|
||||
});
|
||||
@@ -273,7 +273,6 @@ $(document).ready(function() {
|
||||
<%-- <img src="<c:url value="/img/btn_10min.png" />" alt="" id="btn_10min" level="W" /> --%>
|
||||
<button type="button" class="cssbtn" id="btn_detail" level="R" ><i class="material-icons">details</i> <%= localeMessage.getString("button.detail") %></button>
|
||||
<button type="button" class="cssbtn" id="btn_excel" level="W" ><i class="material-icons">table_view</i> <%= localeMessage.getString("button.excel") %></button>
|
||||
<button type="button" class="cssbtn" id="btn_initialize" level="W" ><i class="material-icons">restart_alt</i> <%= localeMessage.getString("button.reset") %></button>
|
||||
<button type="button" class="cssbtn" id="btn_search" level="R" ><i class="material-icons">search</i> <%= localeMessage.getString("button.search") %></button>
|
||||
<%-- <img src="<c:url value="/img/btn_detail.png" />" alt="" id="btn_detail" level="R" /> --%>
|
||||
<%-- <img src="<c:url value="/img/btn_excel.png" />" alt="" id="btn_excel" level="W" /> --%>
|
||||
@@ -282,6 +281,9 @@ $(document).ready(function() {
|
||||
<%-- <img id="btn_excel" src="<c:url value="/img/btn_excel.png"/>" level="W" status="DETAIL"/> --%>
|
||||
</div>
|
||||
<div class="title"><%= localeMessage.getString("tranStat.title") %><span class="tooltip"><%= localeMessage.getString("tranStat.tooltip") %></span></div>
|
||||
<div style="padding-bottom: 10px; color: #555">
|
||||
※ 100 : 송신>APIM 200 : APIM>수신 300 : 수신>APIM 400 : APIM>송신
|
||||
</div>
|
||||
<ajax>
|
||||
<table id="grid" ></table>
|
||||
</ajax>
|
||||
|
||||
@@ -59,10 +59,10 @@ function w2GridToExcelSubmit(url,cmd,gridObj,formObject,fileName,merge){
|
||||
records[i]['E200'] = data[i]['E200'].toString();
|
||||
records[i]['E300'] = data[i]['E300'].toString();
|
||||
records[i]['E400'] = data[i]['E400'].toString();
|
||||
records[i]['E900'] = data[i]['E900'].toString();
|
||||
/* records[i]['E900'] = data[i]['E900'].toString(); */
|
||||
records[i]['E9300'] = data[i]['E9300'].toString();
|
||||
records[i]['E9400'] = data[i]['E9400'].toString();
|
||||
records[i]['E9900'] = data[i]['E9900'].toString();
|
||||
/* records[i]['E9900'] = data[i]['E9900'].toString(); */
|
||||
|
||||
records[i]['BIZNAME'] = data[i]['BIZNAME'];
|
||||
records[i]['SVCCODE'] = data[i]['SVCCODE'];
|
||||
@@ -245,8 +245,8 @@ $( document ).ready(function() {
|
||||
{span : 4, caption:'<%= localeMessage.getString("tranStat.interface") %>'},
|
||||
{span : 4, caption:'<%= localeMessage.getString("tranStat.tranNo") %>'},
|
||||
{span : 3, caption:'<%= localeMessage.getString("tranStat.avgTime") %>'},
|
||||
{span : 5, caption:'<%= localeMessage.getString("tranStat.errNo") %>'},
|
||||
{span : 3, caption:'<%= localeMessage.getString("tranStat.timeout") %>'}
|
||||
{span : 4, caption:'<%= localeMessage.getString("tranStat.errNo") %>'},
|
||||
{span : 2, caption:'<%= localeMessage.getString("tranStat.timeout") %>'}
|
||||
],
|
||||
|
||||
columns: [
|
||||
@@ -269,11 +269,11 @@ $( document ).ready(function() {
|
||||
{ field : 'E200' ,type:'text' ,caption : '200' , size: '65px' ,style:'text-align:right', render:thousandsSeparator },
|
||||
{ field : 'E300' ,type:'text' ,caption : '300' , size: '65px' ,style:'text-align:right', render:thousandsSeparator },
|
||||
{ field : 'E400' ,type:'text' ,caption : '400' , size: '65px' ,style:'text-align:right', render:thousandsSeparator },
|
||||
{ field : 'E900' ,type:'text' ,caption : '900' , size: '65px' ,style:'text-align:right', render:thousandsSeparator },
|
||||
/* { field : 'E900' ,type:'text' ,caption : '900' , size: '65px' ,style:'text-align:right', render:thousandsSeparator }, */
|
||||
|
||||
{ field : 'E9300' ,type:'text' ,caption : '300' , size: '50px' ,style:'text-align:right', render:thousandsSeparator },
|
||||
{ field : 'E9400' ,type:'text' ,caption : '400' , size: '50px' ,style:'text-align:right', render:thousandsSeparator },
|
||||
{ field : 'E9900' ,type:'text' ,caption : '900' , size: '50px' ,style:'text-align:right', render:thousandsSeparator }
|
||||
/* { field : 'E9900' ,type:'text' ,caption : '900' , size: '50px' ,style:'text-align:right', render:thousandsSeparator } */
|
||||
]
|
||||
|
||||
,summary:[
|
||||
@@ -346,8 +346,8 @@ $( document ).ready(function() {
|
||||
merge.push(makeMerge("API","0","0","0","3"));
|
||||
merge.push(makeMerge("전문구간별 처리건수","0","0","4","7"));
|
||||
merge.push(makeMerge("평균처리시간(초)","0","0","8","10"));
|
||||
merge.push(makeMerge("전문구간별 에러건수","0","0","11","15"));
|
||||
merge.push(makeMerge("타임아웃","0","0","16","18"));
|
||||
merge.push(makeMerge("전문구간별 에러건수","0","0","11","14"));
|
||||
merge.push(makeMerge("타임아웃","0","0","15","16"));
|
||||
|
||||
w2GridToExcelSubmit(url_excel,"LIST_GRID_TO_EXCEL",w2ui['myGrid'],$("#ajaxForm"),"전문별 거래현황",merge);
|
||||
return false;
|
||||
@@ -376,8 +376,8 @@ $( document ).ready(function() {
|
||||
merge.push(makeMerge("API","0","0","0","3"));
|
||||
merge.push(makeMerge("구간별 처리건수","0","0","4","7"));
|
||||
merge.push(makeMerge("평균처리시간(초)","0","0","8","10"));
|
||||
merge.push(makeMerge("구간별 에러건수","0","0","11","15"));
|
||||
merge.push(makeMerge("타임아웃","0","0","16","18"));
|
||||
merge.push(makeMerge("구간별 에러건수","0","0","11","14"));
|
||||
merge.push(makeMerge("타임아웃","0","0","15","16"));
|
||||
|
||||
w2GridToExcelSubmit(url_excel,"LIST_GRID_TO_EXCEL",w2ui['myGrid'],$("#ajaxForm"),"전문별 거래현황",merge);
|
||||
return false;
|
||||
@@ -429,7 +429,6 @@ $( document ).ready(function() {
|
||||
<input type="checkbox" id="recentYnVal" value="N"/> <b><%= localeMessage.getString("tranStat.rec10Min") %></b>
|
||||
<button type="button" class="cssbtn" id="btn_detail" level="R" ><i class="material-icons">details</i> <%= localeMessage.getString("button.detail") %></button>
|
||||
<button type="button" class="cssbtn" id="btn_excel" level="W" ><i class="material-icons">table_view</i> <%= localeMessage.getString("button.excel") %></button>
|
||||
<button type="button" class="cssbtn" id="btn_initialize" level="W" ><i class="material-icons">restart_alt</i> <%= localeMessage.getString("button.reset") %></button>
|
||||
<button type="button" class="cssbtn" id="btn_search" level="R" ><i class="material-icons">search</i> <%= localeMessage.getString("button.search") %></button>
|
||||
<%-- <img src="<c:url value="/img/btn_10min.png"/>" alt="" id="btn_10min" level="W" /> --%>
|
||||
<%-- <img src="<c:url value="/img/btn_detail.png"/>" alt="" id="btn_detail" level="R" /> --%>
|
||||
@@ -438,6 +437,9 @@ $( document ).ready(function() {
|
||||
<%-- <img src="<c:url value="/img/btn_search.png"/>" alt="" id="btn_search" level="R" /> --%>
|
||||
</div>
|
||||
<div class="title"><%= localeMessage.getString("tranStatDetail.title") %><span class="tooltip"><%= localeMessage.getString("tranStat.tooltip") %></span></div>
|
||||
<div style="padding-bottom: 10px; color: #555">
|
||||
※ 100 : 송신>APIM 200 : APIM>수신 300 : 수신>APIM 400 : APIM>송신
|
||||
</div>
|
||||
<table class="search_condition" cellspacing=0;>
|
||||
<tbody>
|
||||
<tr>
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
var roleString = "<%=SessionManager.getRoleIdString(request)%>";
|
||||
let totalLogs = 0;
|
||||
let displayedLogs = 0;
|
||||
var bizErrCodeSet = new Set(); // ERRCODE_BIZ(업무 오류) 코드 목록 - 그리드 배경색 구분용
|
||||
|
||||
function adminChk() {
|
||||
var arrRoleID = roleString.split(',');
|
||||
@@ -106,6 +107,8 @@
|
||||
new makeOptions("CODE", "NAME").setObj($("select[name=searchExtBizCd]")).setNoValueInclude(true).setNoValue("", "전체").setData(json.extBizList).setFormat(codeName3OptionFormat).rendering();
|
||||
new makeOptions("CODE", "NAME").setObj($("select[name=searchLogPrcssSerno]")).setNoValueInclude(true).setNoValue("", "전체").setData(json.logPrcssSernoList).rendering();
|
||||
|
||||
bizErrCodeSet = new Set((json.bizErrCodeList || []).map(function (c) { return c.CODE; }));
|
||||
|
||||
setSearchable("searchEaiBzwkDstcd"); // 콤보에 searchable 설정
|
||||
setSearchable("searchExtBizCd");
|
||||
|
||||
@@ -222,63 +225,6 @@
|
||||
$("input[name=searchEndHHMM]").val(endHHMM);
|
||||
}
|
||||
|
||||
function guidEvent() {
|
||||
var searchGuid = $("input[name=searchGuid]").val();
|
||||
if (searchGuid.trim() == '')
|
||||
return;
|
||||
|
||||
var searchTm = searchGuid.trim();
|
||||
/*
|
||||
var yy = searchTm.substr(13,4);
|
||||
var mm = searchTm.substr(17,2);
|
||||
var dd = searchTm.substr(19,2);
|
||||
var hh = searchTm.substr(21,2);
|
||||
var MM = searchTm.substr(23,2);
|
||||
*/
|
||||
var yy = searchTm.substr(3,4);
|
||||
var mm = searchTm.substr(7,2);
|
||||
var dd = searchTm.substr(9,2);
|
||||
var hh = searchTm.substr(11,2);
|
||||
var MM = searchTm.substr(13,2);
|
||||
|
||||
var ms = Date.UTC(yy,mm-1,dd,hh,MM,00);
|
||||
var date = new Date(ms);
|
||||
var startYYYYMMDD;
|
||||
var startHHMM;
|
||||
var endYYYYMMDD;
|
||||
var endHHMM;
|
||||
//Local Time Setting
|
||||
date.setHours(date.getHours() - 9);
|
||||
//5분 간격
|
||||
date.setMinutes(date.getMinutes() - 5);
|
||||
|
||||
startYYYYMMDD = date.getFullYear() + right("0" + (date.getMonth() + 1), 2) + right("0" + (date.getDate()), 2);
|
||||
startHHMM = right("0" + date.getHours(), 2) + right("0" + date.getMinutes(), 2) + "00";
|
||||
|
||||
date.setMinutes(date.getMinutes() + 10);
|
||||
|
||||
endYYYYMMDD = date.getFullYear() + right("0" + (date.getMonth() + 1), 2) + right("0" + (date.getDate()), 2);
|
||||
endHHMM = right("0" + date.getHours(), 2) + right("0" + date.getMinutes(), 2) + "00";
|
||||
|
||||
//추가 코드 입력 2012.05.16 권세환 (일자가 바뀔경우 시간 간격을 +-5분 -> +-1분)
|
||||
if (startYYYYMMDD.substr(6, 2) != endYYYYMMDD.substr(6, 2)) {
|
||||
date.setMinutes(date.getMinutes() - 6);
|
||||
|
||||
startYYYYMMDD = date.getFullYear() + right("0" + (date.getMonth() + 1), 2) + right("0" + (date.getDate()), 2);
|
||||
startHHMM = right("0" + date.getHours(), 2) + right("0" + date.getMinutes(), 2) + "00";
|
||||
|
||||
date.setMinutes(date.getMinutes() + 2);
|
||||
|
||||
endYYYYMMDD = date.getFullYear() + right("0" + (date.getMonth() + 1), 2) + right("0" + (date.getDate()), 2);
|
||||
endHHMM = right("0" + date.getHours(), 2) + right("0" + date.getMinutes(), 2) + "00";
|
||||
}
|
||||
$("input[name=searchStartYYYYMMDD]").val(startYYYYMMDD);
|
||||
$("input[name=searchEndYYYYMMDD]").val(endYYYYMMDD);
|
||||
$("input[name=searchStartHHMM]").val(startHHMM);
|
||||
$("input[name=searchEndHHMM]").val(endHHMM);
|
||||
|
||||
}
|
||||
|
||||
function timeStampFormat2(cellvalue, options, rowObject) {
|
||||
if (cellvalue == null || cellvalue.length < 17)
|
||||
return '';
|
||||
@@ -368,7 +314,8 @@
|
||||
'<%=localeMessage.getString("tracking.companyitcontactname")%>',
|
||||
'<%=localeMessage.getString("tracking.companyitcontacttelno")%>',
|
||||
'<%=localeMessage.getString("tracking.itcontactpointperson1")%>',
|
||||
'<%=localeMessage.getString("tracking.bzwkcontactperson")%>'
|
||||
'<%=localeMessage.getString("tracking.bzwkcontactperson")%>',
|
||||
'EAIERRCD'
|
||||
],
|
||||
colModel: [
|
||||
{name: 'EAISVCSERNO', hidden: true},
|
||||
@@ -380,15 +327,15 @@
|
||||
{name: 'TRACKASISKEY2CTNT', hidden: true},
|
||||
|
||||
{name: 'EAISVCSERNO_TMP', align: 'left', width: '220', formatter: trimFormatter},
|
||||
{name: 'KEYMGTMSGCTNT', align: 'left', width: '240'},
|
||||
{name: 'KEYMGTMSGCTNT', align: 'left', width: '300'},
|
||||
{name: 'LOGPRCSSSERNO_TMP', align: 'center', width: '40'},
|
||||
{name: 'TELGMRECVTRANCD', align: 'left', width: '135', hidden: true},
|
||||
{name: 'EAISVCNAME', align: 'left', width: '130'},
|
||||
{name: 'EAISVCNAME', align: 'left', width: '160'},
|
||||
{name: 'EAISVCDESC', align: 'left', width: '280'},
|
||||
{name: 'EAIBZWKDSTCD_TMP', align: 'center', width: '50'},
|
||||
{name: 'EXTBIZCD', align: 'center', width: '50', hidden: isApiGw},
|
||||
{name: 'REFKEY', align: 'left', width: '280', hidden: isApiGw},
|
||||
{name: 'CLIENTNAME', align: 'left', width: '260', hidden: !isApiGw},
|
||||
{name: 'CLIENTNAME', align: 'left', width: '120', hidden: !isApiGw},
|
||||
{name: 'ORGNAME', align: 'left', width: '100', hidden: !isApiGw, formatter: orgNameFormatter},
|
||||
{name: 'MSGDPSTYMS_TMP', align: 'center', width: '150', formatter: timeStampFormat2},
|
||||
{name: 'MSGPRCSSYMS', align: 'center', width: '150', formatter: timeStampFormat2},
|
||||
@@ -398,7 +345,8 @@
|
||||
{name: 'COMPANYITCONTACTNAME', align: 'left', width: '110', hidden: true},
|
||||
{name: 'COMPANYITCONTACTTELNO', align: 'left', width: '120', hidden: true},
|
||||
{name: 'ITCONTACTPOINTPERSON1', align: 'left', width: '120', hidden: true},
|
||||
{name: 'BZWKCONTACTPERSON', align: 'left', width: '120', hidden: true}
|
||||
{name: 'BZWKCONTACTPERSON', align: 'left', width: '120', hidden: true},
|
||||
{name: 'EAIERRCD', hidden: true}
|
||||
],
|
||||
jsonReader: {
|
||||
repeatitems: false
|
||||
@@ -435,7 +383,7 @@
|
||||
|
||||
},
|
||||
loadComplete: function (d) {
|
||||
$("#grid").tuiTableRowSpan("7");
|
||||
|
||||
|
||||
// 거래 건수 표시
|
||||
var uniqueCount = calculateUniqueLogCount("#grid");
|
||||
@@ -444,14 +392,22 @@
|
||||
//에러 행 색상 표시
|
||||
var rows = $(this).getDataIDs();
|
||||
for (var i = 0; i < rows.length; i++) {
|
||||
var val = $("#grid").getCell(rows[i], 'ERRYN');
|
||||
if (val == "Y") {
|
||||
var errYn = $("#grid").getCell(rows[i], 'ERRYN');
|
||||
var eaiErrCd = $("#grid").getCell(rows[i], 'EAIERRCD');
|
||||
var isBizErr = eaiErrCd != null && bizErrCodeSet.has(String(eaiErrCd).trim());
|
||||
|
||||
if (isBizErr) {
|
||||
// 업무(BIZ) 오류코드(ERRCODE_BIZ) - 하늘색 배경
|
||||
$("#" + rows[i]).find("td").css("background-color", "#BEEDFF");
|
||||
} else if (errYn == "Y") {
|
||||
$("#" + rows[i]).find("td").css("background-color", "#FFCC99");
|
||||
}
|
||||
}
|
||||
|
||||
$("#grid").tuiTableRowSpan("7");
|
||||
|
||||
//1000건이상 alert
|
||||
if (d.records == 1000) {
|
||||
if (d.records >= 1000) {
|
||||
setTimeout(() => {
|
||||
alert("<%=localeMessage.getString("tracking.alert5")%>"); // 조회 결과 1,000건 초과
|
||||
}, 0); // Alert는 비동기적으로 처리
|
||||
@@ -481,7 +437,6 @@
|
||||
//$("#grid").jqGrid('setFrozenColumns');
|
||||
|
||||
$("#btn_search").click(function () {
|
||||
$("input[name=searchGuid]").blur();
|
||||
detail();
|
||||
getTransactionCounts();
|
||||
});
|
||||
@@ -551,12 +506,6 @@
|
||||
return false;
|
||||
});
|
||||
|
||||
//GUID
|
||||
$("input[name=searchGuid]").blur(function (event) {
|
||||
guidEvent();
|
||||
|
||||
});
|
||||
|
||||
buttonControl();
|
||||
|
||||
var local = localStorage.getItem("guid");
|
||||
@@ -701,7 +650,7 @@
|
||||
<td colspan="3"><input type="text" name="searchEaiSvcSerno" value="${param.searchEaiSvcSerno}"></td>
|
||||
|
||||
<th><%=localeMessage.getString("tracking.keymgtmsgctnt")%></th>
|
||||
<td colspan="2"><input type="text" name="searchKeyMgtMsgCtnt" value="${param.searchKeyMgtMsgCtnt}"></td>
|
||||
<td colspan="2"><input type="text" name="searchGuid" value="${param.searchGuid}" ></td>
|
||||
|
||||
<%-- <th><%=localeMessage.getString("tracking.orgGUID")%></th>
|
||||
<td colspan="4"><input type="text" name="searchGuid" value="" maxlength="<%=DbTrackingService.GUID_LENGTH%>"></td> --%>
|
||||
|
||||
@@ -23,6 +23,9 @@
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.table_row td { padding: 0 5px; }
|
||||
.table_row input { box-sizing: border-box; }
|
||||
|
||||
</style>
|
||||
<script language="javascript" >
|
||||
// var eaiBzwkDstcd = window.dialogArguments["eaiBzwkDstcd"];
|
||||
@@ -253,7 +256,7 @@
|
||||
var name = $(this).attr("name");
|
||||
if ("${rmsMenuAuth}" =="W"){ //2020.07.08 admin 만 버튼보이게 수정
|
||||
$("#"+name).show();
|
||||
$("input[name="+name+"]").css("width","185px");
|
||||
$("input[name="+name+"]").css("width","calc(100% - 60px)");
|
||||
}
|
||||
});
|
||||
|
||||
@@ -261,7 +264,7 @@
|
||||
var name = $(this).attr("name");
|
||||
if($("input[name="+name+"]").val().trim() != ""){
|
||||
$("#"+name).show();
|
||||
$("input[name="+name+"]").css("width","185px");
|
||||
$("input[name="+name+"]").css("width","calc(100% - 60px)");
|
||||
}
|
||||
});
|
||||
|
||||
@@ -376,7 +379,7 @@
|
||||
const value = bodyCodeEditor.getValue();
|
||||
let prettyValue = value;
|
||||
if(syntax === 'JSN'){
|
||||
prettyValue = vkbeautify.json(value);
|
||||
prettyValue = preserveExponential(value); //vkbeautify.json(value);
|
||||
}else if(syntax === 'XML'){
|
||||
prettyValue = vkbeautify.xml(value).trim();
|
||||
}
|
||||
@@ -407,6 +410,19 @@
|
||||
}
|
||||
});
|
||||
}
|
||||
function preserveExponential(jsonString) {
|
||||
// 1. 지수 형태의 숫자(예: 1e+21, 5.43e-7)를 찾아 문자열 ""로 감쌉니다.
|
||||
// JSON의 key 뒤에 오는 value 위치의 지수 표기법을 매칭합니다.
|
||||
let protectedJson = jsonString.replace(/:\s*([+-]?\d+\.?\d*e[+-]?\d+)/gi, ': "$1"');
|
||||
|
||||
// 2. 보호된 상태로 vkbeautify를 실행하여 예쁘게 정렬합니다.
|
||||
let prettyJson = vkbeautify.json(protectedJson);
|
||||
|
||||
// 3. (선택사항) 만약 최종 결과물에서 반드시 큰따옴표("")를 다시 제거해야 한다면 아래 주석을 해제하세요.
|
||||
prettyJson = prettyJson.replace(/:\s*"([+-]?\d+\.?\d*e[+-]?\d+)"/gi, ': $1');
|
||||
|
||||
return prettyJson;
|
||||
}
|
||||
function doChangeSyntax(syntax){
|
||||
if(syntax === 'JSN'){
|
||||
$('#btn_pretty_body').css('display', 'block');
|
||||
@@ -789,6 +805,35 @@ $(document).ready(function() {
|
||||
$("html, hbody").css("overflow-x","scroll");
|
||||
}
|
||||
|
||||
function copyTextToClipboard(text, successMessage){
|
||||
if (typeof(navigator.clipboard) == 'undefined'){
|
||||
var textArea = document.createElement("textarea");
|
||||
textArea.value = text;
|
||||
textArea.style.position = "fixed";
|
||||
document.body.appendChild(textArea);
|
||||
textArea.focus();
|
||||
textArea.select();
|
||||
var successful = false;
|
||||
try{
|
||||
successful = document.execCommand('copy');
|
||||
}catch(err){
|
||||
|
||||
}
|
||||
document.body.removeChild(textArea);
|
||||
if (successful){
|
||||
alert(successMessage);
|
||||
}else{
|
||||
prompt("Ctrl+C 를 눌러 클립보드로 복사하세요.", text);
|
||||
}
|
||||
}else{
|
||||
navigator.clipboard.writeText(text).then(function(){
|
||||
alert(successMessage);
|
||||
}).catch(function(){
|
||||
prompt("클립보드 복사에 실패했습니다. Ctrl+C 를 눌러 복사하세요.", text);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$('#headerToClipboard').click(function(){
|
||||
if($('#grid1').get(0) == null || $('#grid1').get(0).jexcel == null){
|
||||
return;
|
||||
@@ -800,13 +845,11 @@ $(document).ready(function() {
|
||||
paddedValues += row[5].padEnd(row[4], " ");
|
||||
}
|
||||
|
||||
navigator.clipboard.writeText(paddedValues);//클립보드로 복사
|
||||
alert('헤더값이 복사되었습니다.')
|
||||
copyTextToClipboard(paddedValues, '헤더값이 복사되었습니다.');
|
||||
});
|
||||
|
||||
$('#messageToClipboard').click(function() {
|
||||
navigator.clipboard.writeText(fullMessageData);//클립보드로 복사
|
||||
alert('전체 전문 복사되었습니다.')
|
||||
copyTextToClipboard(fullMessageData, '전체 전문 복사되었습니다.');
|
||||
});
|
||||
|
||||
$('#btn_pretty_print').click(function () {
|
||||
@@ -937,15 +980,23 @@ html,hbody {
|
||||
<input type=hidden name=realHeader>
|
||||
<div class="table_row_title"><%=localeMessage.getString("trackingDetail.common")%></div>
|
||||
<table class="table_row" cellspacing="0">
|
||||
<colgroup>
|
||||
<col style="width:180px"/>
|
||||
<col style="width:300px" />
|
||||
<col style="width:180px"/>
|
||||
<col style="width:300px" />
|
||||
</colgroup>
|
||||
<tr>
|
||||
<th><%=localeMessage.getString("trackingDetail.eaiSvcSerno")%></th>
|
||||
<td colspan="3"><input type="text" name="eaiSvcSerno" readonly="readonly"/> </td>
|
||||
<td><input type="text" name="eaiSvcSerno" readonly="readonly"/> </td>
|
||||
<th><%=localeMessage.getString("trackingDetail.rspnsErrcdName")%></th>
|
||||
<td><input type="text" name="rspnsErrcdName" readonly="readonly"/> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th style="width:20%;"><%=localeMessage.getString("trackingDetail.eaiBzwkDstcd")%></th>
|
||||
<td style="width:30%;"><input type="text" name="eaiBzwkDstcd" readonly="readonly"/> </td>
|
||||
<th style="width:20%;"><%=localeMessage.getString("trackingDetail.msgDpstYMS")%></th>
|
||||
<td style="width:30%;"><input type="text" name="msgDpstYMS" readonly="readonly"/> </td>
|
||||
<th><%=localeMessage.getString("trackingDetail.eaiBzwkDstcd")%></th>
|
||||
<td><input type="text" name="eaiBzwkDstcd" readonly="readonly"/> </td>
|
||||
<th><%=localeMessage.getString("trackingDetail.msgDpstYMS")%></th>
|
||||
<td><input type="text" name="msgDpstYMS" readonly="readonly"/> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><%= localeMessage.getString("eaiMessage.eaiSvcName")%></th><td><input type="text" name="eaiSvcName" readonly="readonly" />
|
||||
@@ -979,11 +1030,17 @@ html,hbody {
|
||||
</table>
|
||||
<div class="table_row_title">API 정보</div>
|
||||
<table class="table_row" cellspacing="0">
|
||||
<colgroup>
|
||||
<col style="width:180px"/>
|
||||
<col style="width:300px" />
|
||||
<col style="width:180px"/>
|
||||
<col style="width:300px" />
|
||||
</colgroup>
|
||||
<tr>
|
||||
<th style="width:20%;">APP(Client) ID</th>
|
||||
<td style="width:30%;"><input type="text" name="clientId" readonly="readonly"/> </td>
|
||||
<th style="width:20%;">APP 명</th>
|
||||
<td style="width:30%;"><input type="text" name="clientName" readonly="readonly"/> </td>
|
||||
<th>APP(Client) ID</th>
|
||||
<td><input type="text" name="clientId" readonly="readonly"/> </td>
|
||||
<th>APP 명</th>
|
||||
<td><input type="text" name="clientName" readonly="readonly"/> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>법인 ID</th><td ><input type="text" name="orgId" readonly="readonly"/> </td>
|
||||
@@ -1105,20 +1162,27 @@ html,hbody {
|
||||
</div><!-- end.container-1 -->
|
||||
<div class="table_row_title"><%=localeMessage.getString("trackingDetail.inbound")%></div>
|
||||
<table class="table_row" cellspacing="0" >
|
||||
<colgroup>
|
||||
<col style="width:180px"/>
|
||||
<col style="width:300px" />
|
||||
<col style="width:180px"/>
|
||||
<col style="width:300px" />
|
||||
</colgroup>
|
||||
<tr>
|
||||
<th style="width:20%;"><%=localeMessage.getString("trackingDetail.svcMotivUseDstcd")%></th>
|
||||
<td style="width:30%;"><input type="text" name="svcMotivUseDstcd" readonly="readonly"/> </td>
|
||||
<%-- <th style="width:20%;"><%=localeMessage.getString("trackingDetail.svcMotivUseDstcd")%></th>
|
||||
<td style="width:30%;"><input type="text" name="svcMotivUseDstcd" readonly="readonly"/> </td> --%>
|
||||
<th style="width:20%;"><%=localeMessage.getString("trackingDetail.gstatSysAdptrBzwkGroupName")%></th>
|
||||
<td style="width:30%;"><input type="text" name="gstatSysAdptrBzwkGroupName" readonly="readonly"/>
|
||||
<td style="width:30%;"><input type="text" name="gstatSysAdptrBzwkGroupName" readonly="readonly" style="width: calc(100% - 60px);"/>
|
||||
<%-- <img src="<c:url value="/images/bt/pop_detail.gif"/>" alt="" id="gstatSysAdptrBzwkGroupName" class="adapter_btn" level="W"/> --%>
|
||||
<button type="button" class="cssbtn smallBtn2" id="gstatSysAdptrBzwkGroupName" style="vertical-align:middle; font-weight:bold;" level="W"><i class="material-icons">details</i><%= localeMessage.getString("button.detail") %></button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><%=localeMessage.getString("trackingDetail.flowCtrlRoutName")%></th><td><input type="text" name="flowCtrlRoutName" readonly="readonly"/> </td>
|
||||
<th><%=localeMessage.getString("trackingDetail.stndMsgUseYn")%></th><td><input type="text" name="stndMsgUseYn" readonly="readonly"/> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<%-- <tr>
|
||||
<th><%=localeMessage.getString("trackingDetail.flowCtrlRoutName")%></th><td><input type="text" name="flowCtrlRoutName" readonly="readonly"/> </td>
|
||||
|
||||
</tr> --%>
|
||||
<%-- <tr>
|
||||
<th><%=localeMessage.getString("trackingDetail.svcPrcssDsticName")%></th><td><input type="text" name="svcPrcssDsticName" readonly="readonly"/> </td>
|
||||
<th><%=localeMessage.getString("trackingDetail.svcBfClmnLogYn")%></th><td><input type="text" name="svcBfClmnLogYn" readonly="readonly"/> </td>
|
||||
</tr>
|
||||
@@ -1129,39 +1193,52 @@ html,hbody {
|
||||
<tr>
|
||||
<th><%=localeMessage.getString("trackingDetail.gstatSvcDsticName")%></th><td><input type="text" name="gstatSvcDsticName" readonly="readonly"/> </td>
|
||||
<th><%=localeMessage.getString("trackingDetail.prsntMsgIdName")%></th><td><input type="text" name="prsntMsgIdName" readonly="readonly"/> </td>
|
||||
</tr>
|
||||
</tr> --%>
|
||||
</table>
|
||||
<div class="table_row_title"><%=localeMessage.getString("trackingDetail.outbound")%></div>
|
||||
<table class="table_row" cellspacing="0" >
|
||||
<colgroup>
|
||||
<col style="width:180px"/>
|
||||
<col style="width:300px" />
|
||||
<col style="width:180px"/>
|
||||
<col style="width:300px" />
|
||||
</colgroup>
|
||||
<tr>
|
||||
<th style="width:20%;"><%=localeMessage.getString("trackingDetail.psvIntfacDsticName")%></th>
|
||||
<td style="width:30%;"><input type="text" name="psvIntfacDsticName" readonly="readonly"/> </td>
|
||||
<%-- <th style="width:20%;"><%=localeMessage.getString("trackingDetail.psvIntfacDsticName")%></th>--%>
|
||||
<%-- <td style="width:30%;"><input type="text" name="psvIntfacDsticName" readonly="readonly"/> </td> --%>
|
||||
<th style="width:20%;"><%=localeMessage.getString("trackingDetail.psvSysAdptrBzwkGroupName")%></th>
|
||||
<td style="width:30%;"><input type="text" name="psvSysAdptrBzwkGroupName" readonly="readonly"/>
|
||||
<td style="width:30%;"><input type="text" name="psvSysAdptrBzwkGroupName" readonly="readonly" style="width: calc(100% - 60px);"/>
|
||||
<%-- <img src="<c:url value="/images/bt/pop_detail.gif"/>" alt="" id="psvSysAdptrBzwkGroupName" class="adapter_btn" level="W"/> --%>
|
||||
<button type="button" class="cssbtn smallBtn2" id="psvSysAdptrBzwkGroupName" style="vertical-align:middle; font-weight:bold;" level="W"><i class="material-icons">details</i><%= localeMessage.getString("button.detail") %></button>
|
||||
</td>
|
||||
<th><%=localeMessage.getString("trackingDetail.psvBzwkSysName")%></th><td><input type="text" name="psvBzwkSysName" readonly="readonly"/> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><%=localeMessage.getString("trackingDetail.outbndRoutName")%></th><td><input type="text" name="outbndRoutName" readonly="readonly"/> </td>
|
||||
<th><%=localeMessage.getString("trackingDetail.toutVal")%></th><td><input type="text" name="toutVal" readonly="readonly"/> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<%-- <tr>
|
||||
<th><%=localeMessage.getString("trackingDetail.psvSysSvcDsticName")%></th><td><input type="text" name="psvSysSvcDsticName" readonly="readonly"/> </td>
|
||||
<th><%=localeMessage.getString("trackingDetail.psvBzwkSysName")%></th><td><input type="text" name="psvBzwkSysName" readonly="readonly"/> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
||||
</tr>--%>
|
||||
<%-- <tr>
|
||||
<th><%=localeMessage.getString("trackingDetail.rspnsErrFldName")%></th><td><input type="text" name="rspnsErrFldName" readonly="readonly"/> </td>
|
||||
<th><%=localeMessage.getString("trackingDetail.psvSysIdName")%></th><td><input type="text" name="psvSysIdName" readonly="readonly"/> </td>
|
||||
</tr>
|
||||
</tr> --%>
|
||||
</table>
|
||||
<div class="table_row_title"><%=localeMessage.getString("trackingDetail.MAPPING")%></div>
|
||||
<table class="table_row" cellspacing="0" >
|
||||
<colgroup>
|
||||
<col style="width:180px"/>
|
||||
<col style="width:300px" />
|
||||
<col style="width:180px"/>
|
||||
<col style="width:300px" />
|
||||
</colgroup>
|
||||
<tr>
|
||||
<th style="width:20%;"><%=localeMessage.getString("trackingDetail.chngYn")%></th>
|
||||
<td style="width:30%;"><input type="text" name="chngYn" readonly="readonly"/> </td>
|
||||
<th style="width:20%;"><%=localeMessage.getString("trackingDetail.chngMsgIdName")%></th>
|
||||
<td style="width:30%;"><input type="text" name="chngMsgIdName" readonly="readonly"/>
|
||||
<th><%=localeMessage.getString("trackingDetail.chngYn")%></th>
|
||||
<td><input type="text" name="chngYn" readonly="readonly"/> </td>
|
||||
<th><%=localeMessage.getString("trackingDetail.chngMsgIdName")%></th>
|
||||
<td><input type="text" name="chngMsgIdName" readonly="readonly" style="width: calc(100% - 60px);"/>
|
||||
<!-- img src="<c:url value="/images/bt/pop_detail.gif"/>" alt="" id="chngMsgIdName" class="layout_btn" /-->
|
||||
<button type="button" class="cssbtn smallBtn2" id="chngMsgIdName" style="vertical-align:middle; font-weight:bold;" level="W"><i class="material-icons">details</i><%= localeMessage.getString("button.detail") %></button>
|
||||
</td>
|
||||
@@ -1170,7 +1247,7 @@ html,hbody {
|
||||
<th><%=localeMessage.getString("trackingDetail.bascRspnsChngYn")%></th><td><input type="text" name="bascRspnsChngYn" readonly="readonly"/> </td>
|
||||
<th><%=localeMessage.getString("trackingDetail.bascRspnsChngMsgIdName")%></th>
|
||||
<td>
|
||||
<input type="text" name="bascRspnsChngMsgIdName" readonly="readonly"/>
|
||||
<input type="text" name="bascRspnsChngMsgIdName" readonly="readonly" style="width: calc(100% - 60px);"/>
|
||||
<!-- img src="<c:url value="/images/bt/pop_detail.gif"/>" alt="" id="bascRspnsChngMsgIdName" class="layout_btn" /-->
|
||||
<button type="button" class="cssbtn smallBtn2" id="bascRspnsChngMsgIdName" style="vertical-align:middle; font-weight:bold;" level="W"><i class="material-icons">details</i><%= localeMessage.getString("button.detail") %></button>
|
||||
</td>
|
||||
@@ -1180,10 +1257,10 @@ html,hbody {
|
||||
<th><%=localeMessage.getString("trackingDetail.errRspnsChngYn")%></th><td><input type="text" name="errRspnsChngYn" readonly="readonly"/> </td>
|
||||
<th><%=localeMessage.getString("trackingDetail.errRspnsChngMsgIdName")%></th>
|
||||
<td>
|
||||
<input type="text" name="errRspnsChngMsgIdName" readonly="readonly"/>
|
||||
<input type="text" name="errRspnsChngMsgIdName" readonly="readonly" style="width: calc(100% - 60px);"/>
|
||||
<img src="<c:url value="/images/bt/pop_detail.gif"/>" alt="" id="errRspnsChngMsgIdName" class="layout_btn" /> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<%-- <tr>
|
||||
<th><%=localeMessage.getString("trackingDetail.inptMsgIDName")%></th>
|
||||
<td>
|
||||
<input type="text" name="inptMsgIDName" readonly="readonly" />
|
||||
@@ -1193,9 +1270,9 @@ html,hbody {
|
||||
<tr>
|
||||
<th><%=localeMessage.getString("trackingDetail.bascRspnsMsgCmprCtnt")%></th><td><input type="text" name="bascRspnsMsgCmprCtnt" readonly="readonly"/> </td>
|
||||
<th><%=localeMessage.getString("trackingDetail.flovrYn")%></th><td><input type="text" name="flovrYn" readonly="readonly"/> </td>
|
||||
</tr>
|
||||
</tr> --%>
|
||||
</table>
|
||||
<div class="table_row_title"><%=localeMessage.getString("trackingDetail.etc")%></div>
|
||||
<%-- <div class="table_row_title"><%=localeMessage.getString("trackingDetail.etc")%></div>
|
||||
<table class="table_row" cellspacing="0" >
|
||||
<tr>
|
||||
<th style="width:20%;"><%=localeMessage.getString("trackingDetail.rspnsErrcdName")%></th>
|
||||
@@ -1227,7 +1304,7 @@ html,hbody {
|
||||
<th><%=localeMessage.getString("trackingDetail.trackAsisKey3Ctnt")%></th><td><input type="text" name="trackAsisKey3Ctnt" readonly="readonly"/> </td>
|
||||
<th><%=localeMessage.getString("trackingDetail.trackAsisKey4Ctnt")%></th><td><input type="text" name="trackAsisKey4Ctnt" readonly="readonly"/> </td>
|
||||
</tr>
|
||||
</table>
|
||||
</table> --%>
|
||||
</form>
|
||||
</div><!-- end.popup_box -->
|
||||
<div id="bizLogObjectDialog" style="white-space: pre-wrap; overflow-y:auto" title="Biz Log Object Detail">
|
||||
|
||||
@@ -1682,7 +1682,7 @@ $(document).ready(function () {
|
||||
var args = new Object();
|
||||
var url = '<c:url value="/onl/admin/rule/layoutMan.view" />';
|
||||
url += "?cmd=DETAILPOPUP";
|
||||
url += '&loutName='+layoutName;
|
||||
url += '&searchLoutName='+layoutName;
|
||||
url += '&pop=true';
|
||||
|
||||
showModal2(url,args,890,940,'','scroll:on');
|
||||
|
||||
+2
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
@@ -0,0 +1,343 @@
|
||||
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %><%@
|
||||
page import="org.quartz.*,
|
||||
org.quartz.impl.matchers.GroupMatcher,
|
||||
org.springframework.web.context.WebApplicationContext,
|
||||
org.springframework.web.context.support.WebApplicationContextUtils,
|
||||
java.util.*,
|
||||
java.text.SimpleDateFormat"
|
||||
%><%!
|
||||
private String fmt(Date date) {
|
||||
if (date == null) return "-";
|
||||
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date);
|
||||
}
|
||||
|
||||
private String relative(Date date) {
|
||||
if (date == null) return "";
|
||||
long diff = date.getTime() - System.currentTimeMillis();
|
||||
String prefix = diff >= 0 ? "+" : "-";
|
||||
long abs = Math.abs(diff) / 1000;
|
||||
if (abs < 60) return prefix + abs + "s";
|
||||
if (abs < 3600) return prefix + (abs / 60) + "m " + (abs % 60) + "s";
|
||||
return prefix + (abs / 3600) + "h " + ((abs % 3600) / 60) + "m";
|
||||
}
|
||||
|
||||
private String stateClass(Trigger.TriggerState state) {
|
||||
if (state == Trigger.TriggerState.NORMAL) return "state-normal";
|
||||
if (state == Trigger.TriggerState.PAUSED) return "state-paused";
|
||||
if (state == Trigger.TriggerState.BLOCKED) return "state-blocked";
|
||||
if (state == Trigger.TriggerState.ERROR) return "state-error";
|
||||
return "";
|
||||
}
|
||||
|
||||
private Map<String, Object> buildTriggerInfo(Scheduler scheduler, Trigger trigger) throws Exception {
|
||||
Map<String, Object> info = new LinkedHashMap<>();
|
||||
Trigger.TriggerState state = scheduler.getTriggerState(trigger.getKey());
|
||||
info.put("triggerName", trigger.getKey().getName());
|
||||
info.put("triggerGroup", trigger.getKey().getGroup());
|
||||
info.put("state", state.name());
|
||||
info.put("stateClass", stateClass(state));
|
||||
info.put("prevFire", fmt(trigger.getPreviousFireTime()));
|
||||
info.put("prevFireRel", relative(trigger.getPreviousFireTime()));
|
||||
info.put("nextFire", fmt(trigger.getNextFireTime()));
|
||||
info.put("nextFireRel", relative(trigger.getNextFireTime()));
|
||||
info.put("priority", trigger.getPriority());
|
||||
if (trigger instanceof CronTrigger) {
|
||||
info.put("type", "Cron");
|
||||
info.put("cronExp", ((CronTrigger) trigger).getCronExpression());
|
||||
} else if (trigger instanceof SimpleTrigger) {
|
||||
SimpleTrigger st = (SimpleTrigger) trigger;
|
||||
info.put("type", "Simple");
|
||||
info.put("cronExp", "interval=" + st.getRepeatInterval() + "ms, repeat=" +
|
||||
(st.getRepeatCount() == SimpleTrigger.REPEAT_INDEFINITELY ? "INF" : st.getRepeatCount()));
|
||||
} else {
|
||||
info.put("type", trigger.getClass().getSimpleName());
|
||||
info.put("cronExp", "-");
|
||||
}
|
||||
if (trigger.getStartTime() != null) info.put("startTime", fmt(trigger.getStartTime()));
|
||||
if (trigger.getEndTime() != null) info.put("endTime", fmt(trigger.getEndTime()));
|
||||
return info;
|
||||
}
|
||||
|
||||
private List<Map<String, Object>> getJobList(Scheduler scheduler) throws Exception {
|
||||
List<Map<String, Object>> jobs = new ArrayList<>();
|
||||
for (String group : scheduler.getJobGroupNames()) {
|
||||
for (JobKey jobKey : scheduler.getJobKeys(GroupMatcher.jobGroupEquals(group))) {
|
||||
JobDetail detail = scheduler.getJobDetail(jobKey);
|
||||
List<? extends Trigger> triggers = scheduler.getTriggersOfJob(jobKey);
|
||||
|
||||
Map<String, Object> job = new LinkedHashMap<>();
|
||||
job.put("name", jobKey.getName());
|
||||
job.put("group", jobKey.getGroup());
|
||||
job.put("jobClass", detail.getJobClass().getSimpleName());
|
||||
job.put("disallowConcurrent", detail.isConcurrentExectionDisallowed());
|
||||
|
||||
StringBuilder dataStr = new StringBuilder();
|
||||
for (String key : detail.getJobDataMap().getKeys()) {
|
||||
if (dataStr.length() > 0) dataStr.append(", ");
|
||||
dataStr.append(key).append("=").append(detail.getJobDataMap().getString(key));
|
||||
}
|
||||
job.put("jobData", dataStr.toString());
|
||||
job.put("triggerCount", triggers.size());
|
||||
|
||||
List<Map<String, Object>> triggerList = new ArrayList<>();
|
||||
for (Trigger trigger : triggers) {
|
||||
triggerList.add(buildTriggerInfo(scheduler, trigger));
|
||||
}
|
||||
job.put("triggerList", triggerList);
|
||||
|
||||
if (!triggers.isEmpty()) {
|
||||
Trigger trigger = triggers.get(0);
|
||||
Trigger.TriggerState state = scheduler.getTriggerState(trigger.getKey());
|
||||
job.put("state", state.name());
|
||||
job.put("stateClass", stateClass(state));
|
||||
job.put("prevFire", fmt(trigger.getPreviousFireTime()));
|
||||
job.put("prevFireRel", relative(trigger.getPreviousFireTime()));
|
||||
job.put("nextFire", fmt(trigger.getNextFireTime()));
|
||||
job.put("nextFireRel", relative(trigger.getNextFireTime()));
|
||||
if (trigger instanceof CronTrigger) {
|
||||
job.put("cronExp", ((CronTrigger) trigger).getCronExpression());
|
||||
} else {
|
||||
job.put("cronExp", "-");
|
||||
}
|
||||
} else {
|
||||
job.put("state", "NO_TRIGGER");
|
||||
job.put("stateClass", "state-error");
|
||||
job.put("prevFire", "-");
|
||||
job.put("prevFireRel", "");
|
||||
job.put("nextFire", "-");
|
||||
job.put("nextFireRel", "");
|
||||
job.put("cronExp", "-");
|
||||
}
|
||||
jobs.add(job);
|
||||
}
|
||||
}
|
||||
jobs.sort(Comparator.comparing(m -> (String) m.get("name")));
|
||||
return jobs;
|
||||
}
|
||||
%><%
|
||||
WebApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(application);
|
||||
|
||||
Scheduler scheduler = null;
|
||||
Scheduler clusteredScheduler = null;
|
||||
String error = null;
|
||||
|
||||
try {
|
||||
scheduler = (Scheduler) ctx.getBean("scheduler");
|
||||
} catch (Exception e) {
|
||||
error = "scheduler bean not found: " + e.getMessage();
|
||||
}
|
||||
try {
|
||||
clusteredScheduler = (Scheduler) ctx.getBean("clusteredScheduler");
|
||||
} catch (Exception e) {
|
||||
// clusteredScheduler is optional
|
||||
}
|
||||
|
||||
// 자동 새로고침 간격 (초), 기본 10초
|
||||
String refreshParam = request.getParameter("refresh");
|
||||
int refreshSec = 10;
|
||||
if (refreshParam != null) {
|
||||
try { refreshSec = Integer.parseInt(refreshParam); } catch (NumberFormatException e) { /* ignore */ }
|
||||
if (refreshSec < 1) refreshSec = 1;
|
||||
}
|
||||
boolean autoRefresh = refreshParam != null;
|
||||
%>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<% if (autoRefresh) { %>
|
||||
<meta http-equiv="refresh" content="<%= refreshSec %>;url=quartzStatus.jsp?refresh=<%= refreshSec %>">
|
||||
<% } %>
|
||||
<title>Quartz Scheduler Status</title>
|
||||
<style>
|
||||
body { font-family: 'Segoe UI', Arial, sans-serif; margin: 20px; background: #f5f5f5; color: #333; }
|
||||
h1 { font-size: 20px; margin-bottom: 5px; }
|
||||
h2 { font-size: 16px; color: #555; margin: 20px 0 8px 0; border-bottom: 2px solid #ddd; padding-bottom: 4px; }
|
||||
.info { font-size: 13px; color: #666; margin-bottom: 15px; }
|
||||
table { width: 100%%; border-collapse: collapse; font-size: 13px; background: #fff; box-shadow: 0 1px 3px rgba(0,0,0,0.1); margin-bottom: 20px; }
|
||||
th { background: #4a5568; color: #fff; padding: 8px 10px; text-align: left; font-weight: 500; }
|
||||
td { padding: 7px 10px; border-bottom: 1px solid #eee; }
|
||||
tr:hover td { background: #f7fafc; }
|
||||
.state-normal { color: #38a169; font-weight: bold; }
|
||||
.state-paused { color: #d69e2e; font-weight: bold; }
|
||||
.state-blocked { color: #e53e3e; font-weight: bold; }
|
||||
.state-error { color: #e53e3e; font-weight: bold; }
|
||||
.mono { font-family: 'Consolas', 'Monaco', monospace; font-size: 12px; }
|
||||
.rel { color: #718096; font-size: 11px; margin-left: 4px; }
|
||||
.badge { display: inline-block; padding: 2px 6px; border-radius: 3px; font-size: 11px; }
|
||||
.badge-yes { background: #c6f6d5; color: #276749; }
|
||||
.badge-no { background: #fed7d7; color: #9b2c2c; }
|
||||
.scheduler-meta { display: flex; gap: 20px; flex-wrap: wrap; margin-bottom: 10px; }
|
||||
.scheduler-meta span { font-size: 13px; }
|
||||
.scheduler-meta strong { color: #4a5568; }
|
||||
.error { color: #e53e3e; padding: 10px; background: #fff5f5; border: 1px solid #fc8181; border-radius: 4px; }
|
||||
.toolbar { font-size: 12px; color: #718096; }
|
||||
.toolbar a { color: #4299e1; text-decoration: none; margin: 0 4px; }
|
||||
.toolbar a:hover { text-decoration: underline; }
|
||||
.toolbar .active { font-weight: bold; color: #2b6cb0; background: #ebf4ff; padding: 2px 6px; border-radius: 3px; }
|
||||
.auto-badge { display: inline-block; padding: 1px 6px; border-radius: 3px; font-size: 11px; background: #bee3f8; color: #2a4365; margin-left: 6px; }
|
||||
.trigger-toggle { cursor: pointer; user-select: none; }
|
||||
.trigger-toggle:hover td { background: #edf2f7; }
|
||||
.trigger-row { display: none; }
|
||||
.trigger-row.open { display: table-row; }
|
||||
.trigger-row td { background: #f7fafc; padding: 5px 10px 5px 30px; border-bottom: 1px solid #e2e8f0; font-size: 12px; }
|
||||
.trigger-detail-table { width: 100%; border-collapse: collapse; font-size: 12px; margin: 0; }
|
||||
.trigger-detail-table th { background: #e2e8f0; color: #4a5568; padding: 5px 8px; font-weight: 500; font-size: 11px; }
|
||||
.trigger-detail-table td { padding: 4px 8px; border-bottom: 1px solid #e2e8f0; }
|
||||
.trigger-detail-table tr:last-child td { border-bottom: none; }
|
||||
.toggle-icon { display: inline-block; width: 16px; font-size: 11px; color: #718096; transition: transform 0.15s; }
|
||||
.toggle-icon.open { transform: rotate(90deg); }
|
||||
.trigger-count { font-size: 11px; color: #718096; font-weight: normal; margin-left: 4px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Quartz Scheduler Status
|
||||
<% if (autoRefresh) { %><span class="auto-badge">Auto <%= refreshSec %>s</span><% } %>
|
||||
</h1>
|
||||
<p class="info">
|
||||
Instance: <strong><%= System.getProperty("inst.Name", "N/A") %></strong>
|
||||
| Server Time: <strong><%= new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) %></strong>
|
||||
</p>
|
||||
<p class="toolbar">
|
||||
Auto Refresh:
|
||||
<% if (!autoRefresh) { %><span class="active">OFF</span><% } else { %><a href="quartzStatus.jsp">OFF</a><% } %>
|
||||
|
|
||||
<% int[] intervals = {5, 10, 30, 60}; %>
|
||||
<% for (int iv : intervals) { %>
|
||||
<% if (autoRefresh && refreshSec == iv) { %><span class="active"><%= iv %>s</span><% } else { %><a href="quartzStatus.jsp?refresh=<%= iv %>"><%= iv %>s</a><% } %>
|
||||
<% } %>
|
||||
</p>
|
||||
|
||||
<% if (error != null) { %>
|
||||
<div class="error"><%= error %></div>
|
||||
<% } %>
|
||||
|
||||
<% if (scheduler != null) { %>
|
||||
<h2>Scheduler: <%= scheduler.getSchedulerName() %> (RAM)</h2>
|
||||
<div class="scheduler-meta">
|
||||
<span><strong>Status:</strong> <span class="<%= scheduler.isStarted() ? "state-normal" : "state-error" %>"><%= scheduler.isStarted() ? "STARTED" : "STOPPED" %></span></span>
|
||||
<span><strong>Instance ID:</strong> <%= scheduler.getSchedulerInstanceId() %></span>
|
||||
<span><strong>Job Count:</strong> <%= getJobList(scheduler).size() %></span>
|
||||
</div>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Job Name</th>
|
||||
<th>Job Class</th>
|
||||
<th>Cron</th>
|
||||
<th>State</th>
|
||||
<th>Concurrent</th>
|
||||
<th>Prev Fire</th>
|
||||
<th>Next Fire</th>
|
||||
<th>Job Data</th>
|
||||
</tr>
|
||||
<% int jobIdx = 0; for (Map<String, Object> job : getJobList(scheduler)) { String rowId = "s_" + jobIdx; List<Map<String, Object>> tList = (List<Map<String, Object>>) job.get("triggerList"); %>
|
||||
<tr class="trigger-toggle" onclick="toggleTriggers('<%= rowId %>')">
|
||||
<td><span class="toggle-icon" id="icon_<%= rowId %>">▶</span><strong><%= job.get("name") %></strong><span class="trigger-count">(<%= job.get("triggerCount") %> triggers)</span></td>
|
||||
<td class="mono"><%= job.get("jobClass") %></td>
|
||||
<td class="mono"><%= job.get("cronExp") %></td>
|
||||
<td class="<%= job.get("stateClass") %>"><%= job.get("state") %></td>
|
||||
<td><span class="badge <%= (Boolean)job.get("disallowConcurrent") ? "badge-yes" : "badge-no" %>">
|
||||
<%= (Boolean)job.get("disallowConcurrent") ? "NO (protected)" : "YES (allowed)" %></span></td>
|
||||
<td class="mono"><%= job.get("prevFire") %><span class="rel"><%= job.get("prevFireRel") %></span></td>
|
||||
<td class="mono"><%= job.get("nextFire") %><span class="rel"><%= job.get("nextFireRel") %></span></td>
|
||||
<td class="mono"><%= job.get("jobData") %></td>
|
||||
</tr>
|
||||
<tr class="trigger-row" id="triggers_<%= rowId %>">
|
||||
<td colspan="8" style="padding: 6px 10px 6px 30px;">
|
||||
<% if (tList.isEmpty()) { %>
|
||||
<em style="color:#a0aec0;">No triggers</em>
|
||||
<% } else { %>
|
||||
<table class="trigger-detail-table">
|
||||
<tr><th>Trigger Name</th><th>Group</th><th>Type</th><th>Expression / Interval</th><th>State</th><th>Priority</th><th>Prev Fire</th><th>Next Fire</th></tr>
|
||||
<% for (Map<String, Object> t : tList) { %>
|
||||
<tr>
|
||||
<td><%= t.get("triggerName") %></td>
|
||||
<td><%= t.get("triggerGroup") %></td>
|
||||
<td><%= t.get("type") %></td>
|
||||
<td class="mono"><%= t.get("cronExp") %></td>
|
||||
<td class="<%= t.get("stateClass") %>"><%= t.get("state") %></td>
|
||||
<td><%= t.get("priority") %></td>
|
||||
<td class="mono"><%= t.get("prevFire") %><span class="rel"><%= t.get("prevFireRel") %></span></td>
|
||||
<td class="mono"><%= t.get("nextFire") %><span class="rel"><%= t.get("nextFireRel") %></span></td>
|
||||
</tr>
|
||||
<% } %>
|
||||
</table>
|
||||
<% } %>
|
||||
</td>
|
||||
</tr>
|
||||
<% jobIdx++; } %>
|
||||
</table>
|
||||
<% } %>
|
||||
|
||||
<% if (clusteredScheduler != null) { %>
|
||||
<h2>Clustered Scheduler: <%= clusteredScheduler.getSchedulerName() %> (DB)</h2>
|
||||
<div class="scheduler-meta">
|
||||
<span><strong>Status:</strong> <span class="<%= clusteredScheduler.isStarted() ? "state-normal" : "state-error" %>"><%= clusteredScheduler.isStarted() ? "STARTED" : "STOPPED" %></span></span>
|
||||
<span><strong>Instance ID:</strong> <%= clusteredScheduler.getSchedulerInstanceId() %></span>
|
||||
<span><strong>Job Count:</strong> <%= getJobList(clusteredScheduler).size() %></span>
|
||||
</div>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Job Name</th>
|
||||
<th>Job Class</th>
|
||||
<th>Cron</th>
|
||||
<th>State</th>
|
||||
<th>Concurrent</th>
|
||||
<th>Prev Fire</th>
|
||||
<th>Next Fire</th>
|
||||
<th>Job Data</th>
|
||||
</tr>
|
||||
<% int cJobIdx = 0; for (Map<String, Object> job : getJobList(clusteredScheduler)) { String rowId = "c_" + cJobIdx; List<Map<String, Object>> tList = (List<Map<String, Object>>) job.get("triggerList"); %>
|
||||
<tr class="trigger-toggle" onclick="toggleTriggers('<%= rowId %>')">
|
||||
<td><span class="toggle-icon" id="icon_<%= rowId %>">▶</span><strong><%= job.get("name") %></strong><span class="trigger-count">(<%= job.get("triggerCount") %> triggers)</span></td>
|
||||
<td class="mono"><%= job.get("jobClass") %></td>
|
||||
<td class="mono"><%= job.get("cronExp") %></td>
|
||||
<td class="<%= job.get("stateClass") %>"><%= job.get("state") %></td>
|
||||
<td><span class="badge <%= (Boolean)job.get("disallowConcurrent") ? "badge-yes" : "badge-no" %>">
|
||||
<%= (Boolean)job.get("disallowConcurrent") ? "NO (protected)" : "YES (allowed)" %></span></td>
|
||||
<td class="mono"><%= job.get("prevFire") %><span class="rel"><%= job.get("prevFireRel") %></span></td>
|
||||
<td class="mono"><%= job.get("nextFire") %><span class="rel"><%= job.get("nextFireRel") %></span></td>
|
||||
<td class="mono"><%= job.get("jobData") %></td>
|
||||
</tr>
|
||||
<tr class="trigger-row" id="triggers_<%= rowId %>">
|
||||
<td colspan="8" style="padding: 6px 10px 6px 30px;">
|
||||
<% if (tList.isEmpty()) { %>
|
||||
<em style="color:#a0aec0;">No triggers</em>
|
||||
<% } else { %>
|
||||
<table class="trigger-detail-table">
|
||||
<tr><th>Trigger Name</th><th>Group</th><th>Type</th><th>Expression / Interval</th><th>State</th><th>Priority</th><th>Prev Fire</th><th>Next Fire</th></tr>
|
||||
<% for (Map<String, Object> t : tList) { %>
|
||||
<tr>
|
||||
<td><%= t.get("triggerName") %></td>
|
||||
<td><%= t.get("triggerGroup") %></td>
|
||||
<td><%= t.get("type") %></td>
|
||||
<td class="mono"><%= t.get("cronExp") %></td>
|
||||
<td class="<%= t.get("stateClass") %>"><%= t.get("state") %></td>
|
||||
<td><%= t.get("priority") %></td>
|
||||
<td class="mono"><%= t.get("prevFire") %><span class="rel"><%= t.get("prevFireRel") %></span></td>
|
||||
<td class="mono"><%= t.get("nextFire") %><span class="rel"><%= t.get("nextFireRel") %></span></td>
|
||||
</tr>
|
||||
<% } %>
|
||||
</table>
|
||||
<% } %>
|
||||
</td>
|
||||
</tr>
|
||||
<% cJobIdx++; } %>
|
||||
</table>
|
||||
<% } %>
|
||||
|
||||
<script>
|
||||
function toggleTriggers(rowId) {
|
||||
var row = document.getElementById('triggers_' + rowId);
|
||||
var icon = document.getElementById('icon_' + rowId);
|
||||
if (row.classList.contains('open')) {
|
||||
row.classList.remove('open');
|
||||
icon.classList.remove('open');
|
||||
} else {
|
||||
row.classList.add('open');
|
||||
icon.classList.add('open');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
+30
-17
@@ -4,7 +4,7 @@ plugins {
|
||||
id 'eclipse-wtp'
|
||||
id 'idea'
|
||||
id 'war'
|
||||
id 'com.diffplug.eclipse.apt' version '3.41.1'
|
||||
id 'org.cyclonedx.bom' version '3.2.4'
|
||||
}
|
||||
|
||||
group 'com.eactive'
|
||||
@@ -49,10 +49,6 @@ compileJava {
|
||||
options.encoding = 'UTF-8'
|
||||
options.compilerArgs = ['-parameters']
|
||||
options.generatedSourceOutputDirectory = project.file(generatedJavaDir)
|
||||
|
||||
aptOptions {
|
||||
processorArgs = [ 'querydsl.generatedAnnotationClass' : 'com.querydsl.core.annotations.Generated' ]
|
||||
}
|
||||
}
|
||||
|
||||
war {
|
||||
@@ -89,11 +85,10 @@ dependencies {
|
||||
implementation project(':elink-online-emsclient')
|
||||
implementation project(':elink-portal-common')
|
||||
//implementation project(':kjb-safedb')
|
||||
implementation project(":eapim-admin-djb")
|
||||
//implementation project(":eapim-admin-djb")
|
||||
|
||||
/* Custom Libs (damo-manager.jar 는 Tomcat lib 가 런타임 제공 → compileOnly, WAR 미포함) */
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'], exclude: ['damo-manager.jar'])
|
||||
compileOnly files('libs/damo-manager.jar')
|
||||
/* Custom Libs */
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
|
||||
//implementation "org.dom4j:dom4j:2.1.3"
|
||||
//implementation "com.rabbitmq:amqp-client:3.6.6"
|
||||
@@ -118,9 +113,15 @@ dependencies {
|
||||
// https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api
|
||||
compileOnly group: 'javax.servlet', name: 'javax.servlet-api', version: '4.0.1'
|
||||
|
||||
implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.13.1'
|
||||
implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.13.2.2'
|
||||
//implementation group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-xml', version: '2.13.1'
|
||||
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.13.1'
|
||||
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.13.2'
|
||||
|
||||
// apistatus-draft.yml (자동 탐지 초안 양식) 로딩용.
|
||||
// admin 은 Spring Boot 가 아니라 yml 자동 바인딩이 없어 직접 파싱한다.
|
||||
// 2.x 를 쓴다 - 1.x 는 CVE-2022-1471(Constructor 역직렬화 RCE) 및
|
||||
// CVE-2022-25857/38749~38752(DoS) 영향. 로딩도 SafeConstructor 로 한다.
|
||||
implementation 'org.yaml:snakeyaml:2.4'
|
||||
|
||||
implementation "javax.jms:javax.jms-api:2.0"
|
||||
implementation "org.snmp4j:snmp4j:1.10.1"
|
||||
@@ -139,13 +140,18 @@ dependencies {
|
||||
|
||||
implementation "org.apache.ibatis:ibatis-sqlmap:2.3.4.726"
|
||||
implementation "org.jdom:jdom:1.1"
|
||||
implementation "com.google.code.gson:gson:2.3.1"
|
||||
implementation "com.google.code.gson:gson:2.8.9"
|
||||
implementation "javax.annotation:javax.annotation-api:1.2"
|
||||
implementation "net.sf.json-lib:json-lib-ext-spring:1.0.2"
|
||||
implementation "taglibs:standard:1.1.2"
|
||||
implementation "javax.servlet:jstl:1.1.2"
|
||||
implementation group: 'com.jcraft', name: 'jsch', version: '0.1.51'
|
||||
|
||||
// springapp-servlet.xml 의 CommonsMultipartResolver 가 사용.
|
||||
// 기존엔 elink-online-common(api 'commons-fileupload:1.5')에서 전이적으로 받아왔으나,
|
||||
// eapim-online 쪽에서 해당 의존성을 제거할 예정이라 직접 선언으로 전환.
|
||||
implementation "commons-fileupload:commons-fileupload:1.6.0"
|
||||
|
||||
implementation "org.quartz-scheduler:quartz:${quartzVersion}"
|
||||
|
||||
// https://mvnrepository.com/artifact/org.apache.poi/poi
|
||||
@@ -233,6 +239,11 @@ configurations.all {
|
||||
// 일부 transitive 가 끌어오는 xml-apis:1.0.b2 (2002, DOM L2) 는 이 클래스를 포함하지 않아
|
||||
// xercesImpl 가 NoClassDefFoundError 를 일으킴 → 1.4.01 로 강제 통일.
|
||||
force 'xml-apis:xml-apis:1.4.01'
|
||||
|
||||
// logback 1.2.10 은 SLF4J 1.7 바인딩(StaticLoggerBinder)만 제공한다.
|
||||
// transitive (Java-WebSocket:1.5.7 등) 가 slf4j-api 2.x 를 끌어올리면
|
||||
// SLF4J 가 provider 를 못 찾아 NOP 로거로 폴백 → 콘솔/파일 로그가 전부 사라진다.
|
||||
force 'org.slf4j:slf4j-api:1.7.36'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -257,11 +268,11 @@ eclipse {
|
||||
}
|
||||
synchronizationTasks settingEclipseEncoding, initDirs
|
||||
jdt {
|
||||
apt {
|
||||
// generated 된 패스 경로를 .setting/org.eclipse.jdt.apt.core.prefs 의 org.eclipse.jdt.apt.genSrcDir에 적용한다.
|
||||
// project > properties > Java Compiler > Annoation Processing 화면에서 확인 가능하다.
|
||||
genSrcDir = file(generatedJavaDir)
|
||||
}
|
||||
// apt {
|
||||
// // generated 된 패스 경로를 .setting/org.eclipse.jdt.apt.core.prefs 의 org.eclipse.jdt.apt.genSrcDir에 적용한다.
|
||||
// // project > properties > Java Compiler > Annoation Processing 화면에서 확인 가능하다.
|
||||
// genSrcDir = file(generatedJavaDir)
|
||||
// }
|
||||
}
|
||||
project {
|
||||
if (!natures.contains('org.eclipse.buildship.core.gradleprojectnature')) {
|
||||
@@ -276,3 +287,5 @@ tasks.withType(JavaCompile) {
|
||||
options.forkOptions.memoryMaximumSize = '4g' // 컴파일러에 2GB 할당 (필요시 4g로 증량)
|
||||
options.encoding = 'UTF-8'
|
||||
}
|
||||
// CycloneDX SBOM -> xlsx 변환 (gradle sbomXlsx)
|
||||
apply from: "$projectDir/gradle/sbom-xlsx.gradle"
|
||||
|
||||
@@ -87,8 +87,8 @@ dependencies {
|
||||
implementation project(":eapim-admin-djb")
|
||||
|
||||
/* Custom Libs (damo-manager.jar 는 Tomcat lib 가 런타임 제공 → compileOnly, WAR 미포함) */
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'], exclude: ['damo-manager.jar'])
|
||||
compileOnly files('libs/damo-manager.jar')
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'], exclude: ['damo-manager.jar'])
|
||||
compileOnly files('libs/damo-manager.jar')
|
||||
|
||||
//implementation "org.dom4j:dom4j:2.1.3"
|
||||
//implementation "com.rabbitmq:amqp-client:3.6.6"
|
||||
|
||||
@@ -0,0 +1,326 @@
|
||||
/*
|
||||
* CycloneDX SBOM(bom.json) -> Excel(xlsx) 변환 태스크.
|
||||
*
|
||||
* gradle sbomXlsx # cyclonedxBom 실행 후 변환
|
||||
* gradle sbomXlsx -PsbomJson=path.json # 기존 bom.json 사용(cyclonedxBom 생략)
|
||||
* gradle sbomXlsx -PsbomOut=out.xlsx # 출력 경로 지정
|
||||
*
|
||||
* buildscript 블록이 이 스크립트에만 적용되므로 POI 의존성이 메인 빌드
|
||||
* classpath 나 WAR 산출물에는 포함되지 않는다.
|
||||
*
|
||||
* 시트: 요약 / WAR 기준
|
||||
* 산출 기준은 war 태스크의 classpath(= runtimeClasspath) 이므로
|
||||
* test·annotationProcessor·developmentOnly·compileOnly 의존은 모두 제외된다.
|
||||
* bom.json 은 라이선스/해시/설명/직접-전이 판별을 위한 메타 소스로만 쓴다.
|
||||
*/
|
||||
buildscript {
|
||||
repositories {
|
||||
maven {
|
||||
url "https://nexus.eactive.synology.me:8090/repository/maven-public/"
|
||||
allowInsecureProtocol = true
|
||||
}
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'org.apache.poi:poi-ooxml:3.17'
|
||||
}
|
||||
}
|
||||
|
||||
import groovy.json.JsonSlurper
|
||||
import org.apache.poi.ss.usermodel.BorderStyle
|
||||
import org.apache.poi.ss.usermodel.FillPatternType
|
||||
import org.apache.poi.ss.usermodel.HorizontalAlignment
|
||||
import org.apache.poi.ss.usermodel.IndexedColors
|
||||
import org.apache.poi.ss.usermodel.VerticalAlignment
|
||||
import org.apache.poi.ss.util.CellRangeAddress
|
||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook
|
||||
|
||||
// 엑셀 셀 문자열 상한(32767)보다 여유를 둔 절단 길이
|
||||
ext.SBOM_CELL_LIMIT = 32000
|
||||
|
||||
task sbomXlsx {
|
||||
group = 'sbom'
|
||||
description = 'CycloneDX bom.json 을 WAR 수록 기준 xlsx 로 변환한다'
|
||||
|
||||
// -PsbomJson 으로 기존 산출물을 지정하면 재생성하지 않는다
|
||||
if (!project.hasProperty('sbomJson')) {
|
||||
dependsOn 'cyclonedxBom'
|
||||
}
|
||||
|
||||
doLast {
|
||||
File src = resolveBomJson(project)
|
||||
File out = project.hasProperty('sbomOut')
|
||||
? project.file(project.property('sbomOut'))
|
||||
: new File(project.buildDir, "reports/sbom/${sbomFileName(project)}")
|
||||
out.parentFile.mkdirs()
|
||||
|
||||
def bom = new JsonSlurper().parse(src, 'UTF-8')
|
||||
def deploy = collectDeployJars(project)
|
||||
def warRows = joinWarRows(deploy.jars, indexComponents(bom))
|
||||
|
||||
def wb = new XSSFWorkbook()
|
||||
def st = createStyles(wb)
|
||||
writeSummarySheet(wb, st, bom, warRows, src, deploy.label)
|
||||
writeWarSheet(wb, st, warRows)
|
||||
|
||||
out.withOutputStream { os -> wb.write(os) }
|
||||
wb.close()
|
||||
|
||||
int unmatched = warRows.count { it.matched == 'N' }
|
||||
logger.lifecycle("SBOM xlsx 생성: ${out.absolutePath} " +
|
||||
"(배포 수록 ${warRows.size()}개, SBOM 미매칭 ${unmatched}개, 원본 ${src.name})")
|
||||
}
|
||||
}
|
||||
|
||||
/** 산출 파일명: 배포 패키지명 기준 (war 있으면 war 파일명, 없으면 project 이름[-버전]) */
|
||||
String sbomFileName(Project p) {
|
||||
def warTask = p.tasks.findByName('war')
|
||||
if (warTask != null) {
|
||||
String archive = warTask.archiveFileName.get()
|
||||
return archive.replaceAll(/\.(war|jar|ear)$/, '') + '-sbom.xlsx'
|
||||
}
|
||||
String ver = (p.version == null || p.version.toString() in ['', 'unspecified']) ? '' : "-${p.version}"
|
||||
return "${p.name}${ver}-sbom.xlsx"
|
||||
}
|
||||
|
||||
/** bom.json 위치 결정: -PsbomJson > cyclonedxBom 산출 경로 후보 */
|
||||
File resolveBomJson(Project p) {
|
||||
if (p.hasProperty('sbomJson')) {
|
||||
File f = p.file(p.property('sbomJson'))
|
||||
if (!f.exists()) {
|
||||
throw new GradleException("bom.json 없음: ${f.absolutePath}")
|
||||
}
|
||||
return f
|
||||
}
|
||||
def candidates = [
|
||||
new File(p.buildDir, 'reports/cyclonedx/bom.json'),
|
||||
new File(p.buildDir, 'reports/bom.json'),
|
||||
]
|
||||
File found = candidates.find { it.exists() }
|
||||
if (found == null) {
|
||||
throw new GradleException(
|
||||
"bom.json 을 찾지 못했다. 확인한 경로: " + candidates*.absolutePath.join(', ') +
|
||||
"\n'gradle cyclonedxBom' 실행 후 재시도하거나 -PsbomJson=<경로> 로 지정한다.")
|
||||
}
|
||||
return found
|
||||
}
|
||||
|
||||
/**
|
||||
* 실제 배포물에 packaging 되는 jar 목록.
|
||||
* war 프로젝트는 war 태스크 classpath(= runtimeClasspath), 그 외는 runtimeClasspath 를
|
||||
* 기준으로 하므로 test/annotationProcessor/developmentOnly/compileOnly 는 자동으로 빠진다.
|
||||
*
|
||||
* @return [label: 기준 설명, jars: 행 목록]
|
||||
*/
|
||||
Map collectDeployJars(Project p) {
|
||||
def cfg = p.configurations.findByName('runtimeClasspath')
|
||||
if (cfg == null) {
|
||||
p.logger.warn("[${p.name}] runtimeClasspath 가 없어 배포 기준 시트를 비운다")
|
||||
return [label: '(없음)', jars: []]
|
||||
}
|
||||
|
||||
def warTask = p.tasks.findByName('war')
|
||||
def files
|
||||
String label
|
||||
if (warTask != null) {
|
||||
files = warTask.classpath.files
|
||||
label = 'WAR WEB-INF/lib (war 태스크 classpath)'
|
||||
} else {
|
||||
files = cfg.files
|
||||
label = 'runtimeClasspath (war 태스크 없음)'
|
||||
}
|
||||
|
||||
def coordByFile = [:]
|
||||
cfg.resolvedConfiguration.resolvedArtifacts.each { a ->
|
||||
def id = a.moduleVersion.id
|
||||
coordByFile[a.file] = [group: id.group, name: id.name, version: id.version]
|
||||
}
|
||||
def jars = files.findAll { it.name.endsWith('.jar') }.collect { f ->
|
||||
def c = coordByFile[f]
|
||||
[
|
||||
file : f.name,
|
||||
group : c?.group ?: '',
|
||||
name : c?.name ?: f.name.replaceAll(/\.jar$/, ''),
|
||||
version: c?.version ?: '',
|
||||
coord : c ? "${c.group}:${c.name}:${c.version}".toString() : '',
|
||||
]
|
||||
}.sort { it.file }
|
||||
|
||||
return [label: label, jars: jars]
|
||||
}
|
||||
|
||||
/** bom.json 컴포넌트를 'group:name:version' 키로 색인 (라이선스/해시/설명/직접-전이) */
|
||||
Map indexComponents(bom) {
|
||||
String rootRef = bom.metadata?.component?.'bom-ref'
|
||||
Set directRefs = (bom.dependencies?.find { it.ref == rootRef }?.dependsOn ?: []) as Set
|
||||
|
||||
def index = [:]
|
||||
bom.components?.each { c ->
|
||||
def hashes = [:]
|
||||
c.hashes?.each { h -> hashes[h.alg] = h.content }
|
||||
|
||||
def licenses = (c.licenses ?: []).collect { l ->
|
||||
l.license?.id ?: l.license?.name ?: l.expression ?: ''
|
||||
}.findAll { it }
|
||||
|
||||
index["${c.group ?: ''}:${c.name ?: ''}:${c.version ?: ''}".toString()] = [
|
||||
direct : directRefs.contains(c.'bom-ref') ? '직접' : '전이',
|
||||
licenses : licenses.join('; '),
|
||||
licenseList: licenses.isEmpty() ? ['(미상)'] : licenses,
|
||||
purl : c.purl ?: '',
|
||||
sha256 : hashes['SHA-256'] ?: '',
|
||||
sha1 : hashes['SHA-1'] ?: '',
|
||||
description: c.description ?: '',
|
||||
]
|
||||
}
|
||||
return index
|
||||
}
|
||||
|
||||
/** WAR jar 목록에 SBOM 메타를 좌표로 결합 */
|
||||
List joinWarRows(List warJars, Map index) {
|
||||
def result = []
|
||||
warJars.eachWithIndex { j, i ->
|
||||
def m = j.coord ? index[j.coord] : null
|
||||
result << [
|
||||
no : i + 1,
|
||||
file : j.file,
|
||||
group : j.group,
|
||||
name : j.name,
|
||||
version : j.version,
|
||||
direct : m?.direct ?: '',
|
||||
licenses : m?.licenses ?: '',
|
||||
licenseList: m?.licenseList ?: ['(미상)'],
|
||||
purl : m?.purl ?: '',
|
||||
sha256 : m?.sha256 ?: '',
|
||||
sha1 : m?.sha1 ?: '',
|
||||
matched : (m != null) ? 'Y' : 'N',
|
||||
description: m?.description ?: '',
|
||||
]
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
Map createStyles(wb) {
|
||||
def headFont = wb.createFont()
|
||||
headFont.setBold(true)
|
||||
headFont.setColor(IndexedColors.WHITE.getIndex())
|
||||
|
||||
def head = wb.createCellStyle()
|
||||
head.setFont(headFont)
|
||||
head.setFillForegroundColor(IndexedColors.DARK_BLUE.getIndex())
|
||||
head.setFillPattern(FillPatternType.SOLID_FOREGROUND)
|
||||
head.setAlignment(HorizontalAlignment.CENTER)
|
||||
head.setVerticalAlignment(VerticalAlignment.CENTER)
|
||||
head.setBorderBottom(BorderStyle.THIN)
|
||||
|
||||
def body = wb.createCellStyle()
|
||||
body.setVerticalAlignment(VerticalAlignment.TOP)
|
||||
|
||||
def wrap = wb.createCellStyle()
|
||||
wrap.setVerticalAlignment(VerticalAlignment.TOP)
|
||||
wrap.setWrapText(true)
|
||||
|
||||
def labelFont = wb.createFont()
|
||||
labelFont.setBold(true)
|
||||
def label = wb.createCellStyle()
|
||||
label.setFont(labelFont)
|
||||
|
||||
return [head: head, body: body, wrap: wrap, label: label]
|
||||
}
|
||||
|
||||
/** 헤더 행 생성 + 폭 지정 + 틀고정 */
|
||||
def writeHeader(sheet, style, List<String> headers, List<Integer> widths) {
|
||||
def row = sheet.createRow(0)
|
||||
row.setHeightInPoints(20f)
|
||||
headers.eachWithIndex { h, i ->
|
||||
def cell = row.createCell(i)
|
||||
cell.setCellValue(h)
|
||||
cell.setCellStyle(style)
|
||||
sheet.setColumnWidth(i, widths[i] * 256)
|
||||
}
|
||||
sheet.createFreezePane(0, 1)
|
||||
}
|
||||
|
||||
def cellOf(row, int idx, value, style) {
|
||||
def cell = row.createCell(idx)
|
||||
String s = (value == null) ? '' : value.toString()
|
||||
if (s.length() > SBOM_CELL_LIMIT) {
|
||||
s = s.substring(0, SBOM_CELL_LIMIT) + '…(생략)'
|
||||
}
|
||||
cell.setCellValue(s)
|
||||
cell.setCellStyle(style)
|
||||
return cell
|
||||
}
|
||||
|
||||
def writeSummarySheet(wb, st, bom, List warRows, File src, String basisLabel) {
|
||||
def sheet = wb.createSheet('요약')
|
||||
def comp = bom.metadata?.component ?: [:]
|
||||
def tool = bom.metadata?.tools?.components?.getAt(0)
|
||||
Set licenseKinds = warRows.collectMany { it.licenseList } as Set
|
||||
|
||||
def items = [
|
||||
['대상 프로젝트', "${comp.group ?: ''}:${comp.name ?: ''}:${comp.version ?: ''}"],
|
||||
['산출 기준', "${basisLabel} — test/annotationProcessor/compileOnly 제외"],
|
||||
['BOM 포맷', "${bom.bomFormat ?: ''} ${bom.specVersion ?: ''}"],
|
||||
['serialNumber', bom.serialNumber ?: ''],
|
||||
['생성 시각', bom.metadata?.timestamp ?: ''],
|
||||
['생성 도구', tool ? "${tool.name} ${tool.version}" : ''],
|
||||
['원본 파일', src.absolutePath],
|
||||
['배포 수록 jar', warRows.size()],
|
||||
[' └ 직접 의존', warRows.count { it.direct == '직접' }],
|
||||
[' └ 전이 의존', warRows.count { it.direct == '전이' }],
|
||||
[' └ SBOM 미매칭', warRows.count { it.matched == 'N' }],
|
||||
['라이선스 종류', licenseKinds.size()],
|
||||
['라이선스 미상', warRows.count { it.licenses.isEmpty() }],
|
||||
]
|
||||
|
||||
writeHeader(sheet, st.head, ['항목', '값'], [30, 90])
|
||||
items.eachWithIndex { item, i ->
|
||||
def row = sheet.createRow(i + 1)
|
||||
cellOf(row, 0, item[0], st.label)
|
||||
cellOf(row, 1, item[1], st.body)
|
||||
}
|
||||
|
||||
// 라이선스 분포 (요약 하단)
|
||||
def byLicense = [:].withDefault { 0 }
|
||||
warRows.each { r -> r.licenseList.each { lic -> byLicense[lic] = byLicense[lic] + 1 } }
|
||||
def sorted = byLicense.entrySet().sort { a, b -> (b.value <=> a.value) ?: (a.key <=> b.key) }
|
||||
|
||||
int base = items.size() + 2
|
||||
def hdr = sheet.createRow(base)
|
||||
cellOf(hdr, 0, '라이선스', st.head)
|
||||
cellOf(hdr, 1, 'jar 수', st.head)
|
||||
sorted.eachWithIndex { e, i ->
|
||||
def row = sheet.createRow(base + 1 + i)
|
||||
cellOf(row, 0, e.key, st.body)
|
||||
cellOf(row, 1, e.value, st.body)
|
||||
}
|
||||
}
|
||||
|
||||
/** 실제 배포물(WAR WEB-INF/lib) 기준 시트 */
|
||||
def writeWarSheet(wb, st, List warRows) {
|
||||
def sheet = wb.createSheet('WAR 기준')
|
||||
def headers = ['No', 'jar 파일명', 'Group', 'Name', 'Version', '구분',
|
||||
'License', 'purl', 'SHA-256', 'SHA-1', 'SBOM매칭', 'Description']
|
||||
def widths = [6, 46, 32, 34, 16, 7, 30, 60, 40, 30, 10, 60]
|
||||
writeHeader(sheet, st.head, headers, widths)
|
||||
|
||||
warRows.eachWithIndex { r, i ->
|
||||
def row = sheet.createRow(i + 1)
|
||||
cellOf(row, 0, r.no, st.body)
|
||||
cellOf(row, 1, r.file, st.body)
|
||||
cellOf(row, 2, r.group, st.body)
|
||||
cellOf(row, 3, r.name, st.body)
|
||||
cellOf(row, 4, r.version, st.body)
|
||||
cellOf(row, 5, r.direct, st.body)
|
||||
cellOf(row, 6, r.licenses, st.body)
|
||||
cellOf(row, 7, r.purl, st.body)
|
||||
cellOf(row, 8, r.sha256, st.body)
|
||||
cellOf(row, 9, r.sha1, st.body)
|
||||
cellOf(row, 10, r.matched, st.body)
|
||||
cellOf(row, 11, r.description, st.wrap)
|
||||
}
|
||||
if (!warRows.isEmpty()) {
|
||||
sheet.setAutoFilter(new CellRangeAddress(0, warRows.size(), 0, headers.size() - 1))
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
POST http://localhost:7090/monitoring/_onl/admin/authserver/clientMan.json?serviceType=APIGW
|
||||
Content-Type: application/json
|
||||
User-Agent: insomnia/10.1.1
|
||||
X-API-KEY: EAPIM_KEY
|
||||
X-Internal-Token: {{internalApiToken}}
|
||||
action: insert
|
||||
target_host: stg
|
||||
|
||||
|
||||
Binary file not shown.
@@ -1,470 +1,368 @@
|
||||
# EAPIM Admin
|
||||
# eapim-admin
|
||||
|
||||
광주은행 엔터프라이즈 API 관리 시스템 - 관리 콘솔
|
||||
제주은행(DJB) eLink EMS 관리 콘솔
|
||||
|
||||
## 프로젝트 개요
|
||||
## 개요
|
||||
|
||||
**EAPIM Admin**은 광주은행의 eLink EMS (eLink Management System) 관리 콘솔로, API 게이트웨이 구성, 모니터링, 통계, 트랜잭션 로그 조회 기능을 제공하는 웹 기반 플랫폼입니다.
|
||||
**eapim-admin** 은 eLink EMS(eLink Management System)의 웹 기반 관리 콘솔입니다.
|
||||
API 게이트웨이 구성, 실시간 모니터링/대시보드, 통계, 트랜잭션 로그 조회, 인터페이스·레이아웃·어댑터·라우팅 관리 기능을 제공합니다.
|
||||
|
||||
eLink EMS 는 세 가지 애플리케이션으로 구성됩니다.
|
||||
|
||||
| 애플리케이션 | 설명 |
|
||||
|---|---|
|
||||
| **eapim-admin** | 관리 콘솔 (이 저장소) |
|
||||
| eapim-online | 실시간 트랜잭션 처리 API 게이트웨이 |
|
||||
| eapim-portal | API 소비자용 개발자 포털 (Spring Boot) |
|
||||
|
||||
> 패키지·클래스에 남아 있는 `kjb` / `KJB` 명칭은 초기 광주은행 커스터마이징에서 유래한 것으로,
|
||||
> 현재 배포 대상은 제주은행(DJB)입니다. 제주은행 전용 코드는 `com.eactive.eai.rms.ext.djb` 하위에 있습니다.
|
||||
|
||||
## 기술 스택
|
||||
|
||||
- Spring Framework 5.3.27
|
||||
- Java 17
|
||||
- Gradle 8.7
|
||||
- Oracle 19c
|
||||
- Spring Data JPA 2.5.2
|
||||
- Hibernate 5.4.33/5.6.15
|
||||
- QueryDSL 5.0.0
|
||||
- iBATIS 2.3.4 (레거시 SQL 매핑)
|
||||
- Quartz 2.2.1
|
||||
- Logback 1.2.10
|
||||
| 구분 | 버전 / 내용 |
|
||||
|---|---|
|
||||
| Java | **8** (Gradle toolchain 으로 강제, `JavaLanguageVersion.of(8)`) |
|
||||
| 빌드 | Gradle **8.7** (wrapper), `war` 패키징 |
|
||||
| Spring Framework | 5.3.27 (MVC / ORM / JDBC) |
|
||||
| Spring Data JPA | 2.5.2 |
|
||||
| Hibernate | 5.6.15.Final |
|
||||
| QueryDSL | 5.0.0 (빌드 시 Q-class 생성) |
|
||||
| iBATIS | 2.3.4 (레거시 SQL 매핑) |
|
||||
| Quartz | 2.2.1 (스케줄링) |
|
||||
| 로깅 | SLF4J + Logback 1.2.10 (모든 `log4j` 의존성 제외) |
|
||||
| 기타 | Apache POI 3.17(Excel), Jackson 2.13.1, AWS SDK 2.x(S3), EhCache, Lombok, MapStruct |
|
||||
|
||||
## 초기 세팅 가이드
|
||||
애플리케이션 프레임워크는 Spring Boot 가 아닌 전통적 Spring 5.3 WAR 이며 Tomcat / WebLogic 등에 배포됩니다.
|
||||
(Spring Boot 는 테스트 유틸리티 `spring-boot-starter-test:2.6.15` 에만 사용)
|
||||
|
||||
### 사전 요구사항
|
||||
## 사전 요구사항
|
||||
|
||||
- JDK 17 이상
|
||||
- Gradle 8.7
|
||||
- **JDK 8** - `.envrc`(direnv) 또는 셸 rc 에서 `JAVA_HOME` 을 JDK 8 위치로 export. Gradle 8 toolchain auto-detect 가 `JAVA_HOME` 도 후보로 인식합니다. `gradle.properties` 에 JDK 경로를 넣지 않습니다.
|
||||
- Git
|
||||
- Oracle 19c (또는 개발 환경에 따라 접근 가능한 DB)
|
||||
- Oracle 접근 (개발/테스트 환경에 따라). 테스트는 H2 인메모리 DB 사용.
|
||||
- **형제 디렉토리 체크아웃 필요** - 멀티 모듈 프로젝트로 아래 저장소가 같은 부모 디렉토리에 있어야 합니다.
|
||||
```
|
||||
<workspace>/
|
||||
├── eapim-admin/ # 이 저장소
|
||||
├── eapim-online/ # elink-online-* 모듈
|
||||
└── elink-portal-common/ # 포털 공통 JPA 엔티티
|
||||
```
|
||||
|
||||
### 환경별 설정 파일
|
||||
프로젝트 클론 후 필요한 환경 설정을 수정하세요:
|
||||
## 멀티 모듈 구조
|
||||
|
||||
- `WebContent/WEB-INF/properties/env.D.properties` - 개발 환경
|
||||
- `WebContent/WEB-INF/properties/env.T.properties` - 테스트/스테이징 환경
|
||||
- `WebContent/WEB-INF/properties/env.P.properties` - 운영 환경
|
||||
`settings.gradle` 에서 형제 디렉토리의 소스 프로젝트를 참조합니다.
|
||||
|
||||
| 모듈 | 위치 | 설명 |
|
||||
|---|---|---|
|
||||
| elink-online-core | `../eapim-online/elink-online-core` | 핵심 인터페이스 / 도메인 모델 |
|
||||
| elink-online-core-jpa | `../eapim-online/elink-online-core-jpa` | JPA 엔티티 / 리포지토리 |
|
||||
| elink-online-transformer | `../eapim-online/elink-online-transformer` | 메시지 변환 로직 |
|
||||
| elink-online-common | `../eapim-online/elink-online-common` | 공통 유틸리티 |
|
||||
| elink-online-emsclient | `../eapim-online/elink-online-emsclient` | EMS 통신 클라이언트 |
|
||||
| elink-portal-common | `../elink-portal-common` | 포털 공통 컴포넌트 (JPA 엔티티) |
|
||||
|
||||
### 주의사항
|
||||
- **SafeDB**: 암호화 기능이 필요한 경우 `-Dkjb_safedb.mode=fake` 설정 (개발 환경) 또는 실제 SafeDB 라이브러리 설치
|
||||
- **멀티 모듈 구조**: eapim-online의 여러 모듈들을 참조하므로 반드시 eapim-online도 클론해야 합니다
|
||||
|
||||
> `kjb-safedb`, `eapim-admin-djb` 는 `settings.gradle` 에 주석 처리되어 있으며,
|
||||
> 필요 시 주석을 해제하고 해당 디렉토리를 체크아웃합니다.
|
||||
|
||||
## 빌드 및 실행
|
||||
|
||||
### Gradle 명령어
|
||||
|
||||
프로젝트 루트의 Gradle wrapper(`./gradlew`) 를 사용합니다.
|
||||
|
||||
### 빌드 및 실행 명령어
|
||||
|
||||
```bash
|
||||
# 표준 빌드
|
||||
./gradlew build
|
||||
|
||||
# Weblogic 배포용 빌드 (테스트 제외)
|
||||
./gradlew build -x test -Pprofile=weblogic
|
||||
|
||||
# WAR 파일 빌드[ems_stdout.log](../../logs/emsSvr-RinjaeMA/ems_stdout.log)
|
||||
./gradlew war
|
||||
|
||||
# 클린 빌드
|
||||
./gradlew clean build
|
||||
|
||||
# 테스트 실행
|
||||
./gradlew test
|
||||
# WAR 빌드 (build/libs/eapim-admin.war)
|
||||
./gradlew war
|
||||
|
||||
# 패키징 없이 클래스만 컴파일
|
||||
# WebLogic 배포용 빌드 (테스트 제외, weblogic-web.xml 사용)
|
||||
./gradlew build -x test -Pprofile=weblogic
|
||||
|
||||
# 패키징 없이 컴파일만
|
||||
./gradlew classes
|
||||
|
||||
# QueryDSL Q-classes 및 기타 애노테이션 생성
|
||||
# QueryDSL Q-class 등 생성 코드만 생성
|
||||
./gradlew compileJava
|
||||
|
||||
# 의존성 트리 보기
|
||||
./gradlew dependencies
|
||||
# 전체 테스트
|
||||
./gradlew test
|
||||
|
||||
# 사용 가능한 모든 태스크 목록
|
||||
# 특정 테스트
|
||||
./gradlew test --tests "com.eactive.eai.rms.*"
|
||||
|
||||
# 의존성 트리 / 태스크 목록
|
||||
./gradlew dependencies
|
||||
./gradlew tasks --all
|
||||
```
|
||||
|
||||
- WAR 파일명: `eapim-admin.war`, 컨텍스트 경로: `/monitoring`
|
||||
- 프로필: `-Pprofile=weblogic` 지정 시 `web.xml` 대신 `weblogic-web.xml` 사용 (DefaultServlet 문제 회피)
|
||||
|
||||
## 로컬 개발 빠른 시작
|
||||
|
||||
1. **생성 코드 생성** - 최초 1회 및 pull 이후
|
||||
```bash
|
||||
./gradlew compileJava # Q-class IDE 오류 시: ./gradlew clean compileJava
|
||||
```
|
||||
2. **빌드/테스트**
|
||||
```bash
|
||||
./gradlew build
|
||||
```
|
||||
3. **IDE 열기** - 아래 "IDE 설정" 참조
|
||||
4. **WAS(Tomcat 등) 실행 구성** 에 `eapim-admin` 배포 + 아래 VM 옵션 추가 후 기동
|
||||
|
||||
### 로컬 Tomcat VM 옵션 예시
|
||||
|
||||
전체 목록과 설명은 `CLAUDE.md` 의 "필수 환경 변수 (Tomcat)" 를 참조하세요.
|
||||
|
||||
```
|
||||
-Deai.datasource.type=DEV
|
||||
-Dinst.Name=emsSvr11
|
||||
-Deai.tableowner=EMSADM
|
||||
-Deai.systemmode=D
|
||||
-Dfile.encoding=utf-8
|
||||
-Dlogin.mode=db
|
||||
-DLOGBACK_LOG_LEVEL=DEBUG
|
||||
-Ddamo-manager.enabled=true
|
||||
-Dlogging.log-path=/logs/prod/eapim/emsSvr11
|
||||
-Dlogback.configurationFile=classpath:logback-dev.xml
|
||||
-Dhibernate.dialect=org.hibernate.dialect.Oracle12cDialect
|
||||
```
|
||||
|
||||
- `inst.Name` 은 클러스터 내에서 인스턴스마다 고유해야 합니다.
|
||||
- `eai.tableowner` 는 Oracle 스키마 소유자.
|
||||
|
||||
## 프로젝트 구조
|
||||
|
||||
### 전체 프로젝트 구조
|
||||
```
|
||||
kjb-eapim/
|
||||
├── eapim-admin/ # Admin 관리 콘솔 (현재 프로젝트)
|
||||
├── eapim-online/ # Online 게이트웨이 코어 모듈
|
||||
│ ├── elink-online-core/
|
||||
│ ├── elink-online-core-jpa/
|
||||
│ ├── elink-online-transformer/
|
||||
│ ├── elink-online-common/
|
||||
│ ├── elink-online-emsclient/
|
||||
│ └── elink-online-adapter/
|
||||
├── elink-portal-common/ # 포털 공통 컴포넌트 (JPA 엔티티)
|
||||
├── kjb-safedb/ # SafeDB 암호화 라이브러리
|
||||
└── eapim-portal/ # Portal 개발자 포털 (선택사항)
|
||||
```
|
||||
|
||||
### eapim-admin 내부 구조
|
||||
```
|
||||
src/main/java/com/eactive/eai/
|
||||
├── agent/command - 커맨드 패턴 구현
|
||||
├── common/ - 공통 유틸리티, iBatis, JSON 직렬화
|
||||
├── custom/ - 고객사별 커스터마이제이션
|
||||
├── rms/ - 메인 애플리케이션 코드
|
||||
│ ├── bap/ - 배치 처리 (BAP)
|
||||
│ ├── bat/ - 추가 배치 기능
|
||||
│ ├── common/ - RMS 공통 (필터, 시작, 서비스)
|
||||
│ ├── data/ - 데이터 엔티티, 리포지토리
|
||||
│ ├── env/ - 환경 설정
|
||||
│ ├── kakao/ - 카카오 연동
|
||||
│ ├── onl/ - 온라인 트랜잭션 관리
|
||||
│ └── service/ - 비즈니스 서비스
|
||||
└── ext.kjb/ - 광주은행(KJB) 확장
|
||||
```
|
||||
├── common - 공통 유틸리티, iBatis, JSON 직렬화
|
||||
├── custom - 고객사별 커스터마이제이션
|
||||
├── rms - 메인 애플리케이션 코드
|
||||
│ ├── bap - 배치 처리(BAP): adaptor / manage / tansaction
|
||||
│ ├── bat - 추가 배치 기능
|
||||
│ ├── common - RMS 공통 (필터, 시작, 서비스)
|
||||
│ ├── data - 데이터 엔티티, 리포지토리
|
||||
│ ├── env - 환경 설정
|
||||
│ ├── kakao - 카카오 연동
|
||||
│ ├── onl - 온라인 트랜잭션 관리 (apim / manage / transaction)
|
||||
│ ├── service - 비즈니스 서비스
|
||||
│ └── ext
|
||||
│ ├── djb - 제주은행(DJB) 확장 (webhook, UMS 연동, 통계 등)
|
||||
│ └── kjb - 광주은행(KJB) 유래 확장
|
||||
└── (com.eactive.ext.kjb) - 통계 화면 등 일부 확장 코드
|
||||
|
||||
### 웹 리소스 구조
|
||||
```
|
||||
WebContent/
|
||||
├── WEB-INF/
|
||||
│ ├── applicationContext.xml - Root Spring 설정
|
||||
│ ├── springapp-servlet.xml - Servlet Spring 설정
|
||||
│ ├── web.xml - Tomcat용 배포 디스크립터
|
||||
│ ├── weblogic-web.xml - WebLogic용 배포 디스크립터
|
||||
│ └── properties/ - 환경별 프로퍼티
|
||||
│ ├── env.D.properties - 개발
|
||||
│ ├── env.T.properties - 테스트
|
||||
│ └── env.P.properties - 운영
|
||||
└── guide/ - 사용자 가이드
|
||||
│ ├── applicationContext.xml - Root Spring 설정
|
||||
│ ├── springapp-servlet.xml - Servlet Spring 설정
|
||||
│ ├── web.xml / weblogic-web.xml
|
||||
│ └── properties/ - env.D / env.T / env.P / env.L .properties
|
||||
├── jsp/ - 화면
|
||||
└── guide/ - 사용자 가이드(docsify)
|
||||
```
|
||||
|
||||
## 주요 기능
|
||||
## 데이터 액세스 (하이브리드 ORM)
|
||||
|
||||
다음과 같은 관리 기능을 수행하며, 모든 관리 대상은 실시간으로 DB, 메모리에 있는 정보가 수정, 반영됩니다.
|
||||
JPA 와 iBATIS 를 함께 사용합니다.
|
||||
|
||||
| 기능 | 설명 |
|
||||
| -------------------- | ------------------------------------------------------------ |
|
||||
| 사용자 관리 | 사용자 및 권한을 관리하는 기능 |
|
||||
| 로그 조회 | 거래 수행 내역을 조회 하는 기능(온라인/배치/일괄) |
|
||||
| 통계 조회 | 거래 통계 조회 기능 |
|
||||
| 대시보드 조회 | 거래 내역 및 장애상황을 모니터링 할 수 있는 대시보드 조회 기능 |
|
||||
| 인터페이스 관리 | 인터페이스 등록/관리 기능 |
|
||||
| 레이아웃 관리 | 레이아웃 등록/관리 기능 |
|
||||
| 변환 관리 | 변환 등록/관리 기능 |
|
||||
| 어댑터 관리 | 통신 어댑터 등록/관리 기능 |
|
||||
| 라우팅룰 관리 | 라우팅 룰 등록/관리 기능 |
|
||||
| 배치 어댑터 관리 | 배치 어댑터 등록/관리 기능 |
|
||||
| 배치 인터페이스 관리 | 배치 인터페이스 등록/관리 기능 |
|
||||
| 일괄 관리 | 일괄 등록/관리 기능 |
|
||||
- **신규 기능**: JPA + Spring Data 리포지토리 (`com.eactive.eai.rms.data.entity`, `elink-portal-common`)
|
||||
- **레거시**: iBATIS SQL 매퍼 `src/main/resources/com/eactive/eai/**/*.xml` (Spring XML 설정, 점진적 마이그레이션 대상)
|
||||
- 벤더별 매핑: Oracle `*-oracle.xml`, MariaDB `*-mariadb.xml`, PostgreSQL `*-postgresql.xml`
|
||||
|
||||
## 외부 모듈
|
||||
### Multi-tenancy (런타임 스키마 전환)
|
||||
|
||||
이 프로젝트는 다음 모듈에 의존합니다 (settings.gradle 참조):
|
||||
|
||||
| 이름 | 버전 | 설명 |
|
||||
| ------------------------ | ------------ | ---------------------------------------------- |
|
||||
| elink-online-core | 4.5-SNAPSHOT | 코어 모듈로 인터페이스 클래스 정의 |
|
||||
| elink-online-core-jpa | 4.5-SNAPSHOT | JPA 엔티티 및 리포지토리 |
|
||||
| elink-online-transformer | 4.5-SNAPSHOT | 변환 모듈로 메시지 및 변환 관련 클래스 정의 |
|
||||
| elink-online-adapter | 4.5-SNAPSHOT | 어댑터 모듈로 통신에 필요한 어댑터 클래스 정의 |
|
||||
| elink-online-common | 4.5-SNAPSHOT | 나머지 공통 프로그램이 정의되어있다 |
|
||||
| elink-online-emsclient | 4.5-SNAPSHOT | ems와 통신하기 위한 모듈 |
|
||||
| elink-portal-common | - | 포털 공통 컴포넌트 (JPA 엔티티) |
|
||||
| kjb-safedb | - | 커스텀 암호화 라이브러리 |
|
||||
| eapim-admin-kjb | - | KJB 은행 특화 커스터마이제이션 |
|
||||
|
||||
## 하이브리드 ORM 방식
|
||||
|
||||
이 애플리케이션은 **JPA와 iBATIS 모두를 사용**합니다:
|
||||
- **JPA/Hibernate**: `elink-portal-common`을 통한 현대적인 데이터 액세스 (Spring Data repositories)
|
||||
- **iBATIS**: 기존 코드를 위한 레거시 SQL 매핑 (점진적으로 마이그레이션 예정)
|
||||
|
||||
데이터 액세스 작업 시:
|
||||
- 새로운 기능은 JPA와 Spring Data repositories를 사용해야 합니다
|
||||
- 레거시 iBATIS 매퍼는 `src/main/resources/com/eactive/eai/**/*.xml`에 있으며 Spring XML을 통해 설정됩니다
|
||||
Hibernate Multi-tenancy 로 요청 시점에 스키마를 전환합니다.
|
||||
`DataSourceContextHolder`(ThreadLocal) → `TenantIdentifierResolver` → `ConfigurableMultiTenantConnectionProvider.setSchema()`.
|
||||
요청 파라미터 `serviceType`(예: `APIGW`) 를 `DataSourceTypeInterceptor` 가 읽어 자동 설정하거나, 컨트롤러에서 명시 설정합니다.
|
||||
자세한 내용은 `CLAUDE.md` 의 "Multi-tenancy" 절 참조.
|
||||
|
||||
## 코드 생성
|
||||
|
||||
빌드 프로세스에서 생성되는 것들:
|
||||
- **QueryDSL Q-classes**: 타입 안전 쿼리 클래스
|
||||
- **Lombok**: 애노테이션 프로세싱을 통한 Getters/setters/builders
|
||||
- **MapStruct**: DTO와 엔티티 간의 객체 매퍼
|
||||
|
||||
### 이중 Q-Class 생성 (Gradle + Eclipse)
|
||||
|
||||
이 프로젝트는 **Gradle과 Eclipse 애노테이션 프로세서 모두를 사용**하므로, Q-classes가 **두 위치에 생성**됩니다:
|
||||
|
||||
1. **`build/generated/java/`** - `gradle compileJava` 또는 `gradle build` 실행 시 Gradle이 생성
|
||||
2. **`WebContent/generated/`** - Eclipse IDE에서 빌드 시 Eclipse APT가 생성
|
||||
|
||||
**이는 예상된 동작이며 의도적입니다.** 두 디렉토리 모두 `.gitignore`에 포함되어 있으며 커밋해서는 안 됩니다.
|
||||
|
||||
## AI 코딩 어시스턴트 지침 파일
|
||||
|
||||
이 프로젝트는 여러 AI 코딩 어시스턴트를 지원합니다. 각 도구는 다음 파일을 참조합니다:
|
||||
|
||||
### 지침 파일 위치
|
||||
|
||||
| AI 도구 | 지침 파일 경로 | 용도 |
|
||||
|---------|---------------|------|
|
||||
| **Claude Code** | `CLAUDE.md` | 마스터 지침 파일 (한글) |
|
||||
| **GitHub Copilot** | `.github/copilot-instructions.md` | Copilot용 지침 |
|
||||
| **Cursor** | `.cursorrules` | Cursor용 간결한 규칙 |
|
||||
|
||||
### 지침 파일 관리 방법
|
||||
|
||||
1. **마스터 파일**: `CLAUDE.md`
|
||||
- 가장 상세한 프로젝트 지침을 포함
|
||||
- 한글로 작성되어 한국 개발팀이 이해하기 쉬움
|
||||
- 수정 시 이 파일을 먼저 업데이트
|
||||
|
||||
2. **동기화**:
|
||||
```bash
|
||||
# CLAUDE.md 수정 후 다른 파일들 동기화
|
||||
cp CLAUDE.md .github/copilot-instructions.md
|
||||
# .cursorrules는 간결한 버전이므로 필요시 수동 업데이트
|
||||
```
|
||||
|
||||
3. **새로운 AI 도구 지원 추가**:
|
||||
- Windsurf: `.windsurfrules` 파일 생성
|
||||
- 기타 도구: 해당 도구의 규칙 파일명 확인 후 생성
|
||||
빌드 시 생성됩니다.
|
||||
|
||||
- **QueryDSL Q-class** - `build/generated/java/` 에 생성, `.gitignore` 대상 (커밋 금지)
|
||||
- **Lombok** - getter/setter/builder 등
|
||||
- **MapStruct** - DTO ↔ 엔티티 매퍼
|
||||
|
||||
pull 이후 또는 Q-class IDE 오류 시 `./gradlew compileJava` (또는 `clean compileJava`) 로 재생성합니다.
|
||||
|
||||
## 환경 설정
|
||||
|
||||
### 시스템 모드
|
||||
|
||||
- **D (DEV)**: 개발 환경
|
||||
- **T (TEST)**: 테스트/스테이징 환경
|
||||
- **P (PROD)**: 운영 환경
|
||||
`-Deai.systemmode` 로 지정하며 해당 프로퍼티 파일을 로드합니다.
|
||||
|
||||
시스템 모드는 `-Deai.systemmode` JVM 옵션으로 설정하며, 해당 환경의 프로퍼티 파일(`env.D.properties` 등)을 로드합니다.
|
||||
| 값 | 환경 | 파일 |
|
||||
|---|---|---|
|
||||
| D | 개발 | `env.D.properties` |
|
||||
| T | 테스트 / 스테이징 | `env.T.properties` |
|
||||
| P | 운영 | `env.P.properties` |
|
||||
| L | 로컬 | `env.L.properties` |
|
||||
|
||||
### TOMCAT 기동 옵션 참조
|
||||
### 데이터베이스
|
||||
|
||||
#### 은행 개발 서버용
|
||||
```
|
||||
-Deai.datasource.type=DEV
|
||||
-Dinst.Name=emsSvr11
|
||||
-Deai.tableowner=EMSAPP
|
||||
-Deai.systemmode=D
|
||||
-Dfile.encoding=utf-8
|
||||
-DLOGBACK_LOG_LEVEL=info
|
||||
```
|
||||
- **Oracle**: 운영 주 DB (`hibernate.dialect=org.hibernate.dialect.Oracle12cDialect`)
|
||||
- **MariaDB / PostgreSQL**: 벤더별 SQL 매핑으로 지원 (JDBC 의존성은 필요 시 활성화)
|
||||
- **H2**: 테스트용 인메모리 DB (`src/test/resources`)
|
||||
- 연결 타입은 `-Deai.datasource.type`(DEV/STG/PROD) 로 선택
|
||||
|
||||
#### 로컬 개발용
|
||||
```
|
||||
-Deai.datasource.type=DEV
|
||||
-Dinst.Name=emsSvr99
|
||||
-Deai.tableowner=EMSAPP
|
||||
-Deai.systemmode=D
|
||||
-Dfile.encoding=utf-8
|
||||
-Dlogin.mode=db
|
||||
-DLOGBACK_LOG_LEVEL=DEBUG
|
||||
-Dlogback.configurationFile=classpath:logback-dev.xml
|
||||
-Dhibernate.dialect=org.hibernate.dialect.Oracle12cDialect
|
||||
-Dkjb_safedb.mode=fake
|
||||
### DB 암호화
|
||||
|
||||
`damo-manager.jar` 모듈은 환경에 따라 다른 암호화로직을 제공한다.
|
||||
|
||||
| 환경 | 설명 |
|
||||
|---|---|
|
||||
| 운영 | 실제 D'amo 네이티브 라이브러리 |
|
||||
| 개발 | SHA-256 |
|
||||
|
||||
로컬 개발 시 `-Ddamo-manager.enabled=false`
|
||||
|
||||
## DJB (제주은행) 전용 설정
|
||||
|
||||
DJB 전용 프로퍼티는 DB 테이블 `TSEAIRM24` 에 그룹명 `'Monitoring'` 과
|
||||
DB 테이블 `PTL_PROPERTY` 에 그룹명 `'Portal'` 로 저장된다.
|
||||
|
||||
> Monitoring 프라퍼티 (환경정보 > EMS관리 > 모니터링 프라퍼티)
|
||||
|
||||
**계정관리**
|
||||
- `djb.sms_auth.enabled` - 로그인시 2-factor 인증 적용여부
|
||||
- `djb.sms_auth.fixed_value` - sms_auth.mode=fixed일 경우 고정인증값
|
||||
- `djb.sms_auth.mode` - 2-factor 인증 모드 (real:실제, fixed:고정값-개발환경에서 사용)
|
||||
- `rms.DUAL_LOGIN_ENABLED` - 중복 로그인 허용 여부
|
||||
- `rms.auto.logout.timeout` - 자동 로그아웃 시간(분)
|
||||
- `rms.password.combi.check` - 비밀번호 영문/숫자/특수문자 조합 종류수
|
||||
- `rms.password.fail.count` - 비밀번호 실패 허용 횟수(이 값 이상 실패시 계정 잠김)
|
||||
- `rms.password.history.count` - 이전 비밀번호 변경 불가 횟수
|
||||
- `rms.password.init.subfix` - 관리자가 비밀번호 초기화시 추가 문자 ex) 행번@!
|
||||
- `rms.password.length.check` - 비밀번호 최소 자리수
|
||||
- `rms.password.repeat.check` - 비밀번호 동일문자,연속문자 반복 불가 횟수
|
||||
- `rms.password.sms_auth.enabled` - 비밀번호 변경시 2-factor 인증 여부
|
||||
|
||||
**인터페이스 배포**
|
||||
- `iomap.download.path` - 인터페이스.json 파일 다운로드 서버 경로
|
||||
- `iomap.upload.path` - 인터페이스.json 파일 업로드 서버 경로
|
||||
|
||||
**UMS(공통) 연동**
|
||||
- `djb.ums.was_ip_address` - 관리자포탈 WAS IP 주소 (UMS 연동시 필요)
|
||||
- `djb.ums.was_mac_address` - 관리자포탈 WAS MAC 주소 (UMS 연동 시 필요)
|
||||
|
||||
**UMS(이메일) 연동**
|
||||
- `djb.ums.email.url` - 이메일 API 엔드포인트 (EAI)
|
||||
- `djb.ums.email.cstno` - 고객번호
|
||||
- `djb.ums.email.ums_evnt_id` - UMS이벤트 ID
|
||||
|
||||
**UMS(사내메신저) 연동**
|
||||
- `djb.ums.messenger.url` - 사내메신저 API 앤드포인트 (FEP)
|
||||
- `djb.ums.messenger.client_id` - 사내메신저 클라이언트 아이디
|
||||
- `djb.ums.messenger.client_secret` - 사내메신저 클라이언트 시크릿
|
||||
- `djb.ums.messenger.api-monitor.enabled` - API 상태변화시 직원에게 사내메신저 발송 여부
|
||||
|
||||
**UMS(SMS) 연동**
|
||||
- `djb.ums.sms.url` - SMS API 엔드포인트 (EAI)
|
||||
- `djb.ums.sms.almtk_snd_prtl_key.intra` - 카톡 알림톡 채널 ID (제주은행 임직원)
|
||||
- `djb.ums.sms.almtk_snd_prtl_key.public` - 카톡 알림톡 채널 ID (제주은행)
|
||||
|
||||
**Webhook 발송**
|
||||
- `rms.webhook.enabled` - 웹훅 발송 여부
|
||||
- `rms.webhook.retry_count` - 웹훅 재발송 횟수
|
||||
- `rms.webhook.retry_time` - 웹훅 재발송 시간간격
|
||||
- `rms.webhook.reverse_proxy.url` - 리버스 프록시 경로
|
||||
|
||||
|
||||
-Dlogback.configurationFile="C:\eactive\workspaces\kjb-eapim\eapim-admin\src\main\resources\logback-rinjae.xml"
|
||||
```
|
||||
> Portal 프라퍼티 (파트너포탈 > 포탈관리 > Property관리)
|
||||
|
||||
### 데이터베이스 지원
|
||||
**UMS 연동**
|
||||
- `djb.ums.email.if_id` - 이메일 연동 인터페이스 ID
|
||||
- `djb.ums.email.tx_id` - 이메일 거래 ID
|
||||
- `djb.ums.messenger.if_id` - 사내메신저 인터페이스 ID
|
||||
- `djb.ums.messenger.tx_id` - 사내메신저 거래 ID
|
||||
- `djb.ums.sms.if_id` - SMS 인터페이스 ID
|
||||
- `djb.ums.sms.tx_id` - SMS 거래 ID
|
||||
|
||||
- **Oracle**: 운영 데이터베이스 (주)
|
||||
- **PostgreSQL/MariaDB**: 대체 운영 DB
|
||||
- **H2**: 테스트용 인메모리 데이터베이스
|
||||
|
||||
데이터베이스 벤더는 프로퍼티 파일의 `db.vendor`와 `hibernate.dialect`를 통해 설정됩니다.
|
||||
## 스케쥴러
|
||||
|
||||
### SafeDB 암호화 라이브러리
|
||||
**API 상태 모니터링**
|
||||
- `ApiStatusMonitorJob11, ApiStatusMonitorJob21` - API 상태 변화를 모니터링한다. API 상태 변화 발생시 직원에게 메신저 및 제휴사에게 웹훅을 발송한다.
|
||||
- 실행간격(1분)이 짧아서 CLUSTERED는 적용을 못하고, 서버수만큼 등록하여 번갈아 가면서 실행하도록 설정
|
||||
- `api.status.error.range_minute` - API 장애 판단을 위한 로그 구간(분)
|
||||
- `api.status.error.rate` - API 장애판단을 위한 오류율 (ex : 10분동안 90% 이상 오류 발생시 장애로 판단)
|
||||
- `api.status.delay.range_minute` - API 지연 상태 판단을 위한 로그 구간(분)
|
||||
- `api.status.delay.avg_resp_time` - API 지연 상태 판단을 위한 평균 응답속도 (ex: 10분동안 평균 응답속도가 10초이상이면 지연으로 판단)
|
||||
|
||||
`kjb-safedb` 모듈은 세 가지 동작 모드를 제공합니다:
|
||||
- **REAL**: 실제 SafeDB 네이티브 라이브러리 사용
|
||||
- **FAKE**: SHA-256/AES-256을 사용하는 개발 모드 (SafeDB 설치 불필요)
|
||||
- **NONE**: 암호화 우회 (테스트 전용)
|
||||
**유량제어토큰실패감시**
|
||||
- `InflowTokenMonitorJob` - 유량제어 토큰 획득 실패(TSEAIFR11) 발생시 직원에게 메신저를 발송한다
|
||||
- `api.inflow.fail.range_minute` - 최근 몇분동안 감시하는지 설정
|
||||
|
||||
개발 시 SafeDB가 로컬에 설치되지 않은 경우 `-Dkjb_safedb.mode=fake`를 설정합니다.
|
||||
**API 통계**
|
||||
- `ApiStatsHourlyAggregationJob` - 당일 API 로그 정보를 시간별로 집계 (TSEAILG00 > API_STATS_HOUR)
|
||||
- `APISTATSDAILYJOB` - API 사용통계 일집계 (API_STATS_HOUR > API_STATS_DAY)
|
||||
- `APISTATSMONTHLYJOB` - API 사용통계 월집계 (API_STATS_DAY > API_STATS_MONTH)
|
||||
- `APISTATSYEARLYJOB` - API 사용통계 년집계 (API_STATS_MONTH > API_STATS_YEAR)
|
||||
|
||||
**UMS발송**
|
||||
- `UMSDISPATCHJOB11, UMSDISPATCHJOB21` - UMS를 발송한다.
|
||||
- 실행간격(5초)이 짧아서 CLUSTERED는 적용을 못하고, 서버수만큼 등록하여 번갈아 가면서 실행하도록 설정
|
||||
|
||||
**게시글종료**
|
||||
- `PortalInquiryClosingJob` - 답변완료 이후 일정기간 댓글이 없는 경우 게시글을 종료한다
|
||||
- `inquiry.comment.closing_day` - 게시글 종료 대기일수
|
||||
|
||||
**메모리초기화**
|
||||
- `MEMORYTRINIT` - 관리자포탈 서버 메모리 초기화(G/W에서 받은 거래현황)
|
||||
|
||||
**토큰발급이력삭제**
|
||||
- `TOKENISSUANCELOGCLEANUPJOB` - 7일이 지난 토큰 발급내역을 삭제한다
|
||||
|
||||
## 배포
|
||||
|
||||
### 지원 서버
|
||||
- **Tomcat** (기본, 개발)
|
||||
- **WebLogic 14.1.2** (운영, `-Pprofile=weblogic` 필요)
|
||||
- **JEUS** (지원)
|
||||
- **JBoss/WildFly** (descriptors와 함께 지원)
|
||||
| 대상 | 비고 |
|
||||
|---|---|
|
||||
| Tomcat | 기본, 개발 |
|
||||
| WebLogic 14.1.2 | 운영, `-Pprofile=weblogic` 필요 |
|
||||
| JEUS | 지원 |
|
||||
| JBoss / WildFly | descriptor 포함 지원 |
|
||||
|
||||
### WebLogic 전용 빌드
|
||||
|
||||
WebLogic용 빌드 시, DefaultServlet 문제를 해결하기 위해 `web.xml`을 `weblogic-web.xml`로 교체하는 `weblogic` 프로필을 사용합니다:
|
||||
|
||||
```bash
|
||||
gradle build -x test -Pprofile=weblogic
|
||||
```
|
||||
|
||||
### WAR 배포
|
||||
|
||||
WAR 파일은 context path `/monitoring`으로 `eapim-admin.war`로 빌드됩니다.
|
||||
- 모든 관리 변경사항은 DB 와 메모리에 즉시 반영됩니다.
|
||||
- 인코딩은 UTF-8. `CharacterEncodingFilter` 가 `*.excel` 을 제외한 모든 요청에 적용.
|
||||
- XSS 보호: `com.eactive.eai.rms.common.filter.CrossScriptingFilter` 전역 적용.
|
||||
|
||||
## IDE 설정
|
||||
|
||||
### Eclipse
|
||||
`build.gradle` 은 `eclipse` / `eclipse-wtp` / `idea` 플러그인을 모두 포함합니다.
|
||||
IntelliJ 전용 변형은 `build.gradle.intellij` 로 별도 관리합니다 (필요 시 `build.gradle` 로 교체).
|
||||
|
||||
```bash
|
||||
gradle eclipse
|
||||
# Eclipse
|
||||
./gradlew eclipse # .project / .classpath, WTP(context path: /monitoring), APT 설정 생성
|
||||
|
||||
# IntelliJ IDEA
|
||||
./gradlew idea # 또는 IDE 에서 "Import Gradle Project"
|
||||
```
|
||||
|
||||
다음을 생성합니다:
|
||||
- `.project` 및 `.classpath` 파일
|
||||
- Eclipse WTP 설정 (context path: `/monitoring`)
|
||||
- UTF-8 인코딩 설정
|
||||
- QueryDSL/Lombok을 위한 애노테이션 프로세서 설정
|
||||
|
||||
### IntelliJ IDEA
|
||||
|
||||
```bash
|
||||
gradle idea
|
||||
```
|
||||
|
||||
또는 IntelliJ에서 직접 "Import Gradle Project"를 사용합니다.
|
||||
|
||||
## KJB (광주은행) 전용 설정
|
||||
|
||||
### 프로퍼티 관리
|
||||
KJB 전용 프로퍼티는 데이터베이스 테이블 `TSEAIRM24`에 그룹명 `'Monitoring'`으로 저장됩니다.
|
||||
|
||||
|
||||
### 주요 프로퍼티 설정
|
||||
|
||||
**이메일 알림 (EAI Batch를 통해)**
|
||||
- `kjb.eai_batch.url` - 이메일 발송을 위한 EAI 배치 서버 URL
|
||||
- 개발: http://172.31.32.111:10230/BATAppWeb/EaiBatCall
|
||||
- QA: http://172.31.33.111:10430/BATAppWeb/EaiBatCall
|
||||
- 운영: http://172.21.1.40:10860/BATAppWeb/EaiBatCall
|
||||
- 입력하지 않을 경우 EAI 배치 호출을 하지 않고 내부적으로 통신하지 않으며 warning 로그만 남김
|
||||
- `kjb.ums.eai_batch.send_dir` - 이메일 파일 발송 디렉토리 (기본값: `/Data/eapim/portal/sendmail/snd`)
|
||||
- `kjb.ums.eai_batch.backup_dir` - 이메일 백업 디렉토리 (기본값: `/Data/eapim/portal/sendmail/bak`)
|
||||
- `kjb.ums.eai_batch.interface_id` - 고객 이메일용 인터페이스 ID (예: `UAGFF00001UIE`)
|
||||
|
||||
**UMS (SMS/LMS/카카오톡) 연동**
|
||||
- `kjb.ums.host_url` - UMS API 엔드포인트
|
||||
- `kjb.ums.api_key` - UMS API 인증 키
|
||||
- `kjb.ums.connection_timeout` - 연결 타임아웃 (초 단위)
|
||||
- `kjb.ums.response_timeout` - 응답 타임아웃 (초 단위)
|
||||
|
||||
### 보안
|
||||
- 커스텀 패스워드 인코더: SafeDB 연동을 위한 `KjbSafedbPasswordEncoder`
|
||||
|
||||
## 테스트
|
||||
|
||||
테스트에 사용되는 것들:
|
||||
- **JUnit 5 (Jupiter)**: 테스트 프레임워크
|
||||
- **Spring Boot Test 2.6.15**: 통합 테스트 유틸리티
|
||||
- **H2 Database**: 테스트용 인메모리 데이터베이스
|
||||
- **Mockito**: 모킹 프레임워크 (Spring Boot Test를 통해)
|
||||
- **JUnit 5 (Jupiter)** - 테스트 프레임워크
|
||||
- **Spring Boot Test 2.6.15** - 통합 테스트 유틸리티
|
||||
- **H2** - 인메모리 DB (`src/test/resources`)
|
||||
- **Mockito** - 모킹 (Spring Boot Test 경유)
|
||||
|
||||
```bash
|
||||
# 모든 테스트 실행
|
||||
gradle test
|
||||
|
||||
# 특정 테스트 클래스 실행
|
||||
gradle test --tests "com.example.ClassName"
|
||||
|
||||
# 특정 테스트 패키지 실행 (JUnit 플랫폼 사용)
|
||||
gradle test --tests "com.eactive.eai.rms.*"
|
||||
./gradlew test
|
||||
./gradlew test --tests "com.eactive.eai.rms.*"
|
||||
```
|
||||
|
||||
## 관련 프로젝트
|
||||
|
||||
프로젝트 구조 (kjb-eapim 디렉토리 기준):
|
||||
- `eapim-admin/` - Admin 관리 콘솔 (현재 프로젝트)
|
||||
- `eapim-online/` - Online 게이트웨이 (코어 모듈 포함)
|
||||
- `eapim-portal/` - Portal 개발자 포털
|
||||
- `elink-portal-common/` - 공통 유틸리티 라이브러리
|
||||
- `kjb-safedb/` - SafeDB 암호화 라이브러리
|
||||
- `kjb-eapim-sql/` - SQL 스크립트 (선택사항)
|
||||
|
||||
## 문서
|
||||
|
||||
- **프로젝트 상세 지침**: `CLAUDE.md` (한글)
|
||||
- **사용자 가이드**: `WebContent/guide/` (한글)
|
||||
- **프로젝트 상세 지침**: `CLAUDE.md` (AI 어시스턴트 및 개발자용 마스터 문서, 한글)
|
||||
- **사용자 가이드**: `WebContent/guide/` (docsify)
|
||||
|
||||
## 참고: KJB 데이터베이스 파라미터 설정
|
||||
|
||||
### 추가 파라미터 SQL
|
||||
```sql
|
||||
-- 로컬 개발용 (외부)
|
||||
-- EAI, UMS 주소 미기입시 내부적으론 호출 시도 하지 않고 warning 로그만 남기며 정상 처리
|
||||
--insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.eai_batch.url', '');
|
||||
insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.eai.batch.connection_timeout', '5');
|
||||
insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.eai.batch.response_timeout', '5');
|
||||
insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.eai.batch.timeout', '10');
|
||||
insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.eai.batch.charset', 'euc-kr');
|
||||
|
||||
insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.eai_batch.send_dir', '/Data/eapim/portal/sendmail/snd');
|
||||
insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.eai_batch.backup_dir', '/Data/eapim/portal/sendmail/bak');
|
||||
insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.eai_batch.error_dir', '/Data/eapim/portal/sendmail/bak');
|
||||
insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.eai_batch.retention_date', '60');
|
||||
insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.eai_batch.interface_id', 'UIEFF00001UAG');
|
||||
|
||||
-- insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.host_url', '');
|
||||
insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.api_key', '7cca6d58-e4f7-474d-9edd-525806bc99ff');
|
||||
insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.connection_timeout', '5');
|
||||
insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.response_timeout', '5');
|
||||
insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.timeout', '10');
|
||||
|
||||
--insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.obp.host_url', '');
|
||||
insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.obp.trust_system', 'APIMPT-0002-QVBJTVBULTAwMDI=');
|
||||
insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.obp.commission_uri', '/api/billing/findBillingForCondition');
|
||||
insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.obp.commission_print_uri', '/api/billing/billing/findBillingForCondition/print');
|
||||
insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.obp.query_partnercode_uri', '/api/customer/findCustomerByBusinessManRegistrationNo/%s');
|
||||
insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.obp.create_partnercode_uri', '/not-yet-implement/%s');
|
||||
commit;
|
||||
|
||||
|
||||
-- 은행 개발 서버용
|
||||
insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.eai_batch.url', 'http://172.31.32.111:10230/BATAppWeb/EaiBatCall');
|
||||
insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.eai.batch.connection_timeout', '5');
|
||||
insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.eai.batch.response_timeout', '5');
|
||||
insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.eai.batch.timeout', '10');
|
||||
insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.eai.batch.charset', 'euc-kr');
|
||||
insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.eai_batch.send_dir', '/Data/eapim/portal/sendmail/snd');
|
||||
insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.eai_batch.backup_dir', '/Data/eapim/portal/sendmail/bak');
|
||||
insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.eai_batch.error_dir', '/Data/eapim/portal/sendmail/bak');
|
||||
insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.eai_batch.retention_date', '60');
|
||||
insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.eai_batch.interface_id', 'UIEFF00001UAG');
|
||||
insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.host_url', 'http://172.31.35.144:9021');
|
||||
insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.api_key', '7cca6d58-e4f7-474d-9edd-525806bc99ff');
|
||||
insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.connection_timeout', '5');
|
||||
insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.response_timeout', '5');
|
||||
insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.timeout', '10');
|
||||
|
||||
insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.obp.host_url', 'http://192.168.246.15:8181');
|
||||
insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.obp.trust_system', 'APIMPT-0002-QVBJTVBULTAwMDI=');
|
||||
insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.obp.commission_uri', '/api/billing/findBillingForCondition');
|
||||
insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.obp.commission_print_uri', '/api/billing/billing/findBillingForCondition/print');
|
||||
insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.obp.query_partnercode_uri', '/api/customer/findCustomerByBusinessManRegistrationNo/%s');
|
||||
insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.obp.create_partnercode_uri', '/not-yet-implement/%s');
|
||||
commit;
|
||||
|
||||
|
||||
-- 은행 운영 서버용
|
||||
insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.eai_batch.url', 'http://172.21.1.40:10860/BATAppWeb/EaiBatCall');
|
||||
insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.eai.batch.connection_timeout', '5');
|
||||
insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.eai.batch.response_timeout', '5');
|
||||
insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.eai.batch.timeout', '10');
|
||||
insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.eai.batch.charset', 'euc-kr');
|
||||
insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.eai_batch.send_dir', '/Data/eapim/portal/sendmail/snd');
|
||||
insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.eai_batch.backup_dir', '/Data/eapim/portal/sendmail/bak');
|
||||
insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.eai_batch.error_dir', '/Data/eapim/portal/sendmail/bak');
|
||||
insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.eai_batch.retention_date', '60');
|
||||
insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.eai_batch.interface_id', 'UIEFF00001UAG');
|
||||
insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.host_url', 'http://172.24.128.66:9021');
|
||||
insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.api_key', '7cca6d58-e4f7-474d-9edd-525806bc99ff');
|
||||
insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.connection_timeout', '5');
|
||||
insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.response_timeout', '5');
|
||||
insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.timeout', '10');
|
||||
|
||||
insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.obp.host_url', 'http://192.168.246.15:8181');
|
||||
insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.obp.trust_system', 'APIMPT-0002-QVBJTVBULTAwMDI=');
|
||||
insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.obp.commission_uri', '/api/billing/findBillingForCondition');
|
||||
insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.obp.commission_print_uri', '/api/billing/billing/findBillingForCondition/print');
|
||||
insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.obp.query_partnercode_uri', '/api/customer/findCustomerByBusinessManRegistrationNo/%s');
|
||||
insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.obp.create_partnercode_uri', '/not-yet-implement/%s');
|
||||
commit;
|
||||
```
|
||||
## 라이선스
|
||||
|
||||
© 광주은행 (Kwangju Bank)
|
||||
사내 전용(proprietary). 제주은행 eLink EMS.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user