Compare commits
4 Commits
cb8a99cb8d
...
c9af386129
| Author | SHA1 | Date | |
|---|---|---|---|
| c9af386129 | |||
| 072682c646 | |||
| 72a05db7c3 | |||
| 02f9c55032 |
89
Jenkinsfile
vendored
89
Jenkinsfile
vendored
@ -1,108 +1,59 @@
|
|||||||
pipeline {
|
pipeline {
|
||||||
agent any
|
agent any
|
||||||
|
|
||||||
environment {
|
environment {
|
||||||
DEPLOY_DIR = '/var/www/zioinfo'
|
SRC = '/opt/zioinfo/src'
|
||||||
APP_DIR = '/opt/zioinfo/app'
|
JAR_DIR = '/opt/zioinfo/app'
|
||||||
JAVA_HOME = '/usr/lib/jvm/java-21-openjdk-amd64'
|
STATIC = '/var/www/zioinfo'
|
||||||
MVN = '/usr/bin/mvn'
|
MVN = '/usr/bin/mvn'
|
||||||
NODE_HOME = '/usr/bin'
|
NOTIFY = "${ITSM_BASE_URL}/api/messenger/webhook"
|
||||||
}
|
}
|
||||||
|
|
||||||
options {
|
options {
|
||||||
buildDiscarder(logRotator(numToKeepStr: '5'))
|
buildDiscarder(logRotator(numToKeepStr: '5'))
|
||||||
timeout(time: 20, unit: 'MINUTES')
|
timeout(time: 20, unit: 'MINUTES')
|
||||||
|
timestamps()
|
||||||
}
|
}
|
||||||
|
|
||||||
stages {
|
stages {
|
||||||
stage('Checkout') {
|
stage('Checkout') {
|
||||||
steps {
|
steps {
|
||||||
echo "브랜치: ${env.GIT_BRANCH ?: 'main'} | 커밋: ${env.GIT_COMMIT?.take(7) ?: '-'}"
|
|
||||||
checkout([
|
checkout([
|
||||||
$class: 'GitSCM',
|
$class: 'GitSCM', branches: scm.branches,
|
||||||
branches: scm.branches,
|
|
||||||
userRemoteConfigs: scm.userRemoteConfigs,
|
userRemoteConfigs: scm.userRemoteConfigs,
|
||||||
extensions: [
|
extensions: [[$class: 'SparseCheckoutPaths',
|
||||||
// manual/ 폴더 체크아웃 제외 (배포 대상 아님)
|
sparseCheckoutPaths: [[path: 'frontend'], [path: 'backend']]
|
||||||
[$class: 'SparseCheckoutPaths', sparseCheckoutPaths: [
|
|
||||||
[path: 'frontend'],
|
|
||||||
[path: 'backend'],
|
|
||||||
]]
|
]]
|
||||||
]
|
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stage('Frontend Build') {
|
stage('Frontend Build') {
|
||||||
steps {
|
steps {
|
||||||
dir('frontend') {
|
dir('frontend') {
|
||||||
sh '''
|
sh 'npm ci --legacy-peer-deps --prefer-offline 2>/dev/null || npm install --legacy-peer-deps'
|
||||||
echo "=== [1/3] React 빌드 ==="
|
sh 'npm run build'
|
||||||
npm ci --legacy-peer-deps --prefer-offline 2>/dev/null || npm install --legacy-peer-deps
|
|
||||||
npm run build
|
|
||||||
echo "빌드 결과: $(ls ../backend/src/main/resources/static/assets/ | wc -l) 파일"
|
|
||||||
'''
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stage('Backend Build') {
|
stage('Backend Build') {
|
||||||
steps {
|
steps {
|
||||||
dir('backend') {
|
dir('backend') {
|
||||||
sh '''
|
sh "${MVN} clean package -DskipTests -q"
|
||||||
echo "=== [2/3] Spring Boot 빌드 ==="
|
|
||||||
${MVN} clean package -DskipTests -q
|
|
||||||
JAR=$(find target -name "*.jar" ! -name "*sources*" | head -1)
|
|
||||||
echo "JAR: $JAR ($(du -sh $JAR | cut -f1))"
|
|
||||||
'''
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stage('Deploy') {
|
stage('Deploy') {
|
||||||
|
when { branch 'main' }
|
||||||
steps {
|
steps {
|
||||||
sh '''
|
sh """
|
||||||
echo "=== [3/3] 배포 ==="
|
cp backend/target/*.jar ${JAR_DIR}/app.jar
|
||||||
JAR=$(find backend/target -name "*.jar" ! -name "*sources*" | head -1)
|
cp -r backend/src/main/resources/static/. ${STATIC}/
|
||||||
|
systemctl restart zioinfo
|
||||||
# 앱 디렉터리 확인
|
|
||||||
mkdir -p ${APP_DIR} ${DEPLOY_DIR}
|
|
||||||
|
|
||||||
# JAR 배포
|
|
||||||
cp "$JAR" ${APP_DIR}/app.jar
|
|
||||||
|
|
||||||
# React 정적 파일 배포
|
|
||||||
cp -r backend/src/main/resources/static/. ${DEPLOY_DIR}/
|
|
||||||
|
|
||||||
# Spring Boot 서비스 재시작
|
|
||||||
systemctl restart zioinfo || true
|
|
||||||
sleep 4
|
sleep 4
|
||||||
|
systemctl is-active zioinfo || exit 1
|
||||||
# 헬스체크
|
"""
|
||||||
for i in 1 2 3 4 5; do
|
|
||||||
HTTP=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:8080/api/company 2>/dev/null)
|
|
||||||
if [ "$HTTP" = "200" ]; then
|
|
||||||
echo "배포 성공 (Spring Boot HTTP $HTTP)"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
echo "헬스체크 ${i}/5 대기중 (HTTP: $HTTP)..."
|
|
||||||
sleep 3
|
|
||||||
done
|
|
||||||
echo "경고: Spring Boot 응답 없음 — 서비스 상태 확인 필요"
|
|
||||||
'''
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
post {
|
post {
|
||||||
success {
|
success { sh "curl -sf -X POST ${NOTIFY} -H 'Content-Type:application/json' -d '{\"event\":\"build_result\",\"room\":\"ops\",\"success\":true,\"result_summary\":\"✅ zioinfo-web 배포 완료 #${BUILD_NUMBER}\"}' 2>/dev/null || true" }
|
||||||
echo "✅ 배포 완료: ${currentBuild.displayName} (${currentBuild.durationString})"
|
failure { sh "curl -sf -X POST ${NOTIFY} -H 'Content-Type:application/json' -d '{\"event\":\"build_result\",\"room\":\"ops\",\"success\":false,\"result_summary\":\"❌ zioinfo-web 빌드 실패 #${BUILD_NUMBER}\"}' 2>/dev/null || true" }
|
||||||
}
|
|
||||||
failure {
|
|
||||||
echo "❌ 배포 실패: ${currentBuild.displayName} — 로그 확인 필요"
|
|
||||||
}
|
|
||||||
always {
|
|
||||||
cleanWs(cleanWhenNotBuilt: false, cleanWhenSuccess: false)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -25,8 +25,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* 로고 */
|
/* 로고 */
|
||||||
.logo { display: flex; align-items: center; gap: 10px; flex-shrink: 0; }
|
.logo { display: flex; align-items: center; gap: 10px; flex-shrink: 0; cursor: pointer; text-decoration: none; }
|
||||||
.logo img { height: 40px; width: auto; filter: brightness(0) invert(1); }
|
.logo img { height: 40px; width: auto; }
|
||||||
.logo-text { color: #fff; font-size: 20px; font-weight: 700; }
|
.logo-text { color: #fff; font-size: 20px; font-weight: 700; }
|
||||||
.logo-text strong { color: var(--accent); }
|
.logo-text strong { color: var(--accent); }
|
||||||
|
|
||||||
|
|||||||
@ -60,6 +60,7 @@ export default function Header() {
|
|||||||
const [activeMenu, setActiveMenu] = useState(null);
|
const [activeMenu, setActiveMenu] = useState(null);
|
||||||
const [mobileOpen, setMobileOpen] = useState(false);
|
const [mobileOpen, setMobileOpen] = useState(false);
|
||||||
const [member, setMember] = useState(null);
|
const [member, setMember] = useState(null);
|
||||||
|
const closeTimer = React.useRef(null);
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
@ -117,10 +118,11 @@ export default function Header() {
|
|||||||
{MENU.map(menu => (
|
{MENU.map(menu => (
|
||||||
<div key={menu.id}
|
<div key={menu.id}
|
||||||
className={`nav-item ${isActive(menu) ? 'active' : ''}`}
|
className={`nav-item ${isActive(menu) ? 'active' : ''}`}
|
||||||
onMouseEnter={() => setActiveMenu(menu.id)}
|
onMouseEnter={() => { clearTimeout(closeTimer.current); setActiveMenu(menu.id); }}
|
||||||
onMouseLeave={() => setActiveMenu(null)}>
|
onMouseLeave={() => { closeTimer.current = setTimeout(() => setActiveMenu(null), 200); }}>
|
||||||
<button className="nav-trigger" aria-haspopup="true"
|
<button className="nav-trigger" aria-haspopup="true"
|
||||||
aria-expanded={activeMenu === menu.id}>
|
aria-expanded={activeMenu === menu.id}
|
||||||
|
onClick={() => navigate(menu.children[0].path)}>
|
||||||
{menu.label}
|
{menu.label}
|
||||||
</button>
|
</button>
|
||||||
{activeMenu === menu.id && (
|
{activeMenu === menu.id && (
|
||||||
|
|||||||
@ -287,6 +287,7 @@ export default function SolutionPage() {
|
|||||||
<Route path="erp" element={<ERP />} />
|
<Route path="erp" element={<ERP />} />
|
||||||
<Route path="crm" element={<CRM />} />
|
<Route path="crm" element={<CRM />} />
|
||||||
<Route path="bi" element={<BI />} />
|
<Route path="bi" element={<BI />} />
|
||||||
|
<Route path="*" element={<ERP />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user