- url 하드코딩 제거
- bootstrap 로컬 저장, 참조수정
This commit is contained in:
HeungTak Lee 2025-12-04 15:38:01 +09:00
부모 55d4dd5886
커밋 322ecb12a6
11개의 변경된 파일2183개의 추가작업 그리고 71개의 파일을 삭제

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다. Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

파일 보기

@ -265,8 +265,8 @@
<div class="header">
<h1>실행 상세 정보</h1>
<div class="button-group">
<a href="/" class="back-btn secondary">← 대시보드로</a>
<a href="/executions" class="back-btn">← 실행 이력으로</a>
<a th:href="@{/}" href="/" class="back-btn secondary">← 대시보드로</a>
<a th:href="@{/executions}" href="/executions" class="back-btn">← 실행 이력으로</a>
</div>
</div>
@ -275,7 +275,10 @@
</div>
</div>
<script>
<script th:inline="javascript">
// Context path for API calls
const contextPath = /*[[@{/}]]*/ '/';
// URL에서 실행 ID 추출 (두 가지 형식 지원)
// 1. Path parameter: /executions/123
// 2. Query parameter: /execution-detail?id=123
@ -297,7 +300,7 @@
async function loadExecutionDetail() {
try {
const response = await fetch(`/api/batch/executions/${executionId}/detail`);
const response = await fetch(contextPath + `api/batch/executions/${executionId}/detail`);
if (!response.ok) {
throw new Error('실행 정보를 찾을 수 없습니다.');

파일 보기

@ -5,10 +5,10 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>작업 실행 이력 - SNP 배치</title>
<!-- Bootstrap 5 CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Bootstrap Icons -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css">
<!-- Bootstrap 5 CSS (로컬) -->
<link th:href="@{/css/bootstrap.min.css}" href="/css/bootstrap.min.css" rel="stylesheet">
<!-- Bootstrap Icons (로컬) -->
<link th:href="@{/css/bootstrap-icons.css}" href="/css/bootstrap-icons.css" rel="stylesheet">
<style>
:root {
@ -119,7 +119,7 @@
<!-- Header -->
<div class="page-header d-flex justify-content-between align-items-center">
<h1><i class="bi bi-clock-history"></i> 작업 실행 이력</h1>
<a href="/" class="btn btn-primary">
<a th:href="@{/}" href="/" class="btn btn-primary">
<i class="bi bi-house-door"></i> 대시보드로 돌아가기
</a>
</div>
@ -165,16 +165,18 @@
</div>
</div>
<!-- Bootstrap 5 JS Bundle -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<!-- Bootstrap 5 JS Bundle (로컬) -->
<script th:src="@{/js/bootstrap.bundle.min.js}" src="/js/bootstrap.bundle.min.js"></script>
<script>
<script th:inline="javascript">
// Context path for API calls
const contextPath = /*[[@{/}]]*/ '/';
let currentJobName = null;
// Load jobs for filter dropdown
async function loadJobs() {
try {
const response = await fetch('/api/batch/jobs');
const response = await fetch(contextPath + 'api/batch/jobs');
const jobs = await response.json();
const urlParams = new URLSearchParams(window.location.search);
@ -231,7 +233,7 @@
`;
try {
const response = await fetch(`/api/batch/jobs/${currentJobName}/executions`);
const response = await fetch(contextPath + `api/batch/jobs/${currentJobName}/executions`);
const executions = await response.json();
if (executions.length === 0) {
@ -352,7 +354,7 @@
}
try {
const response = await fetch(`/api/batch/executions/${executionId}/stop`, {
const response = await fetch(contextPath + `api/batch/executions/${executionId}/stop`, {
method: 'POST'
});
@ -371,7 +373,7 @@
// View execution details
function viewDetails(executionId) {
window.location.href = `/executions/${executionId}`;
window.location.href = contextPath + `executions/${executionId}`;
}
// Initialize on page load

파일 보기

@ -5,10 +5,10 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>S&P 배치 관리 시스템</title>
<!-- Bootstrap 5 CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Bootstrap Icons -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css">
<!-- Bootstrap 5 CSS (로컬) -->
<link th:href="@{/css/bootstrap.min.css}" href="/css/bootstrap.min.css" rel="stylesheet">
<!-- Bootstrap Icons (로컬) -->
<link th:href="@{/css/bootstrap-icons.css}" href="/css/bootstrap-icons.css" rel="stylesheet">
<style>
:root {
@ -262,7 +262,7 @@
<div class="container">
<!-- Header -->
<div class="dashboard-header">
<a href="/swagger-ui/index.html" target="_blank" class="swagger-btn" title="Swagger API 문서 열기">
<a th:href="@{/swagger-ui/index.html}" href="/swagger-ui/index.html" target="_blank" class="swagger-btn" title="Swagger API 문서 열기">
<i class="bi bi-file-earmark-code"></i>
<span>API 문서</span>
</a>
@ -275,34 +275,34 @@
<div class="section-title">
<i class="bi bi-clock-history"></i>
스케줄 현황
<a href="/schedule-timeline" class="btn btn-warning btn-sm ms-auto">
<a th:href="@{/schedule-timeline}" href="/schedule-timeline" class="btn btn-warning btn-sm ms-auto">
<i class="bi bi-calendar3"></i> 스케줄 타임라인
</a>
</div>
<div class="row g-3" id="scheduleStats">
<div class="col-md-3 col-sm-6">
<div class="stat-card" onclick="location.href='/schedules'">
<div class="stat-card" onclick="navigateTo('schedules')">
<div class="icon"><i class="bi bi-calendar-check text-primary"></i></div>
<div class="value" id="totalSchedules">-</div>
<div class="label">전체 스케줄</div>
</div>
</div>
<div class="col-md-3 col-sm-6">
<div class="stat-card" onclick="location.href='/schedules'">
<div class="stat-card" onclick="navigateTo('schedules')">
<div class="icon"><i class="bi bi-play-circle text-success"></i></div>
<div class="value" id="activeSchedules">-</div>
<div class="label">활성 스케줄</div>
</div>
</div>
<div class="col-md-3 col-sm-6">
<div class="stat-card" onclick="location.href='/schedules'">
<div class="stat-card" onclick="navigateTo('schedules')">
<div class="icon"><i class="bi bi-pause-circle text-warning"></i></div>
<div class="value" id="inactiveSchedules">-</div>
<div class="label">비활성 스케줄</div>
</div>
</div>
<div class="col-md-3 col-sm-6">
<div class="stat-card" onclick="location.href='/jobs'">
<div class="stat-card" onclick="navigateTo('jobs')">
<div class="icon"><i class="bi bi-file-earmark-code text-info"></i></div>
<div class="value" id="totalJobs">-</div>
<div class="label">등록된 Job</div>
@ -341,7 +341,7 @@
</div>
</div>
<div class="view-all-link">
<a href="/executions">전체 실행 이력 보기 <i class="bi bi-arrow-right"></i></a>
<a th:href="@{/executions}" href="/executions">전체 실행 이력 보기 <i class="bi bi-arrow-right"></i></a>
</div>
</div>
@ -355,13 +355,13 @@
<button class="btn btn-primary" onclick="showExecuteJobModal()">
<i class="bi bi-play-fill"></i> 작업 즉시 실행
</button>
<a href="/jobs" class="btn btn-info">
<a th:href="@{/jobs}" href="/jobs" class="btn btn-info">
<i class="bi bi-list-ul"></i> 모든 작업 보기
</a>
<a href="/schedules" class="btn btn-success">
<a th:href="@{/schedules}" href="/schedules" class="btn btn-success">
<i class="bi bi-calendar-plus"></i> 스케줄 관리
</a>
<a href="/executions" class="btn btn-secondary">
<a th:href="@{/executions}" href="/executions" class="btn btn-secondary">
<i class="bi bi-clock-history"></i> 실행 이력
</a>
</div>
@ -392,11 +392,18 @@
</div>
</div>
<!-- Bootstrap 5 JS Bundle -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<!-- Bootstrap 5 JS Bundle (로컬) -->
<script th:src="@{/js/bootstrap.bundle.min.js}" src="/js/bootstrap.bundle.min.js"></script>
<script>
<script th:inline="javascript">
let executeModal;
// Context path for API calls
const contextPath = /*[[@{/}]]*/ '/';
// Navigate to a page with context path
function navigateTo(path) {
location.href = contextPath + path;
}
// Initialize on page load
document.addEventListener('DOMContentLoaded', function() {
@ -410,7 +417,7 @@
// Load all dashboard data (single API call)
async function loadDashboardData() {
try {
const response = await fetch('/api/batch/dashboard');
const response = await fetch(contextPath + 'api/batch/dashboard');
const data = await response.json();
// Update stats
@ -460,7 +467,7 @@
`;
} else {
recentContainer.innerHTML = data.recentExecutions.map(exec => `
<div class="execution-item" onclick="location.href='/executions/${exec.executionId}'">
<div class="execution-item" onclick="location.href='${contextPath}executions/${exec.executionId}'">
<div class="execution-info">
<div class="job-name">${exec.jobName}</div>
<div class="execution-meta">
@ -499,7 +506,7 @@
// Show execute job modal
async function showExecuteJobModal() {
try {
const response = await fetch('/api/batch/jobs');
const response = await fetch(contextPath + 'api/batch/jobs');
const jobs = await response.json();
const select = document.getElementById('jobSelect');
@ -528,7 +535,7 @@
}
try {
const response = await fetch(`/api/batch/jobs/${jobName}/execute`, {
const response = await fetch(`${contextPath}api/batch/jobs/${jobName}/execute`, {
method: 'POST'
});

파일 보기

@ -187,7 +187,7 @@
<div class="container">
<div class="header">
<h1>배치 작업</h1>
<a href="/" class="back-btn">← 대시보드로 돌아가기</a>
<a th:href="@{/}" href="/" class="back-btn">← 대시보드로 돌아가기</a>
</div>
<div class="content">
@ -205,10 +205,13 @@
</div>
</div>
<script>
<script th:inline="javascript">
// Context path for API calls
const contextPath = /*[[@{/}]]*/ '/';
async function loadJobs() {
try {
const response = await fetch('/api/batch/jobs');
const response = await fetch(contextPath + 'api/batch/jobs');
const jobs = await response.json();
const jobListDiv = document.getElementById('jobList');
@ -250,7 +253,7 @@
}
try {
const response = await fetch(`/api/batch/jobs/${jobName}/execute`, {
const response = await fetch(contextPath + `api/batch/jobs/${jobName}/execute`, {
method: 'POST'
});
@ -267,7 +270,7 @@
}
function viewExecutions(jobName) {
window.location.href = `/executions?job=${jobName}`;
window.location.href = contextPath + `executions?job=${jobName}`;
}
function showModal(title, message) {

파일 보기

@ -5,10 +5,10 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>스케줄 타임라인 - SNP 배치</title>
<!-- Bootstrap 5 CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Bootstrap Icons -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css">
<!-- Bootstrap 5 CSS (로컬) -->
<link th:href="@{/css/bootstrap.min.css}" href="/css/bootstrap.min.css" rel="stylesheet">
<!-- Bootstrap Icons (로컬) -->
<link th:href="@{/css/bootstrap-icons.css}" href="/css/bootstrap-icons.css" rel="stylesheet">
<style>
:root {
@ -371,10 +371,10 @@
<div class="page-header d-flex justify-content-between align-items-center">
<h1><i class="bi bi-calendar3"></i> 스케줄 타임라인</h1>
<div>
<a href="/schedules" class="btn btn-outline-primary me-2">
<a th:href="@{/schedules}" href="/schedules" class="btn btn-outline-primary me-2">
<i class="bi bi-calendar-check"></i> 스케줄 관리
</a>
<a href="/" class="btn btn-primary">
<a th:href="@{/}" href="/" class="btn btn-primary">
<i class="bi bi-house-door"></i> 대시보드
</a>
</div>
@ -471,10 +471,13 @@
<!-- Custom Tooltip -->
<div id="customTooltip" class="custom-tooltip"></div>
<!-- Bootstrap 5 JS Bundle -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<!-- Bootstrap 5 JS Bundle (로컬) -->
<script th:src="@{/js/bootstrap.bundle.min.js}" src="/js/bootstrap.bundle.min.js"></script>
<script th:inline="javascript">
// Context path for API calls
const contextPath = /*[[@{/}]]*/ '/';
<script>
let currentView = 'day';
let currentDate = new Date();
@ -505,7 +508,7 @@
// Load timeline data
async function loadTimeline() {
try {
const response = await fetch(`/api/batch/timeline?view=${currentView}&date=${currentDate.toISOString()}`);
const response = await fetch(contextPath + `api/batch/timeline?view=${currentView}&date=${currentDate.toISOString()}`);
const data = await response.json();
renderTimeline(data);
@ -729,7 +732,7 @@
}, 100);
try {
const response = await fetch(`/api/batch/timeline/period-executions?jobName=${encodeURIComponent(jobName)}&view=${currentView}&periodKey=${encodeURIComponent(periodKey)}`);
const response = await fetch(contextPath + `api/batch/timeline/period-executions?jobName=${encodeURIComponent(jobName)}&view=${currentView}&periodKey=${encodeURIComponent(periodKey)}`);
const executions = await response.json();
renderPeriodExecutions(executions);
@ -783,14 +786,14 @@
tableHTML += `
<tr>
<td><a href="/executions/${exec.executionId}" class="text-primary" style="text-decoration: none; font-weight: 600;">#${exec.executionId}</a></td>
<td><a href="${contextPath}executions/${exec.executionId}" class="text-primary" style="text-decoration: none; font-weight: 600;">#${exec.executionId}</a></td>
<td>${statusBadge}</td>
<td>${startTime}</td>
<td>${endTime}</td>
<td><code style="font-size: 12px;">${exec.exitCode}</code></td>
<td style="max-width: 300px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;" title="${exitMessage}">${exitMessage}</td>
<td>
<a href="/executions/${exec.executionId}" class="btn btn-sm btn-outline-primary" style="font-size: 12px;">
<a href="${contextPath}executions/${exec.executionId}" class="btn btn-sm btn-outline-primary" style="font-size: 12px;">
<i class="bi bi-eye"></i> 상세
</a>
</td>

파일 보기

@ -5,10 +5,10 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>작업 스케줄 - SNP 배치</title>
<!-- Bootstrap 5 CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Bootstrap Icons -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css">
<!-- Bootstrap 5 CSS (로컬) -->
<link th:href="@{/css/bootstrap.min.css}" href="/css/bootstrap.min.css" rel="stylesheet">
<!-- Bootstrap Icons (로컬) -->
<link th:href="@{/css/bootstrap-icons.css}" href="/css/bootstrap-icons.css" rel="stylesheet">
<style>
:root {
@ -139,7 +139,7 @@
<!-- Header -->
<div class="page-header d-flex justify-content-between align-items-center">
<h1><i class="bi bi-calendar-check"></i> 작업 스케줄</h1>
<a href="/" class="btn btn-primary">
<a th:href="@{/}" href="/" class="btn btn-primary">
<i class="bi bi-house-door"></i> 대시보드로 돌아가기
</a>
</div>
@ -205,14 +205,17 @@
</div>
</div>
<!-- Bootstrap 5 JS Bundle -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<!-- Bootstrap 5 JS Bundle (로컬) -->
<script th:src="@{/js/bootstrap.bundle.min.js}" src="/js/bootstrap.bundle.min.js"></script>
<script th:inline="javascript">
// Context path for API calls
const contextPath = /*[[@{/}]]*/ '/';
<script>
// Load jobs for dropdown
async function loadJobs() {
try {
const response = await fetch('/api/batch/jobs');
const response = await fetch(contextPath + 'api/batch/jobs');
const jobs = await response.json();
const select = document.getElementById('jobName');
@ -241,7 +244,7 @@
}
try {
const response = await fetch(`/api/batch/schedules/${jobName}`);
const response = await fetch(contextPath + `api/batch/schedules/${jobName}`);
if (response.ok) {
const schedule = await response.json();
@ -283,7 +286,7 @@
// Load schedules
async function loadSchedules() {
try {
const response = await fetch('/api/batch/schedules');
const response = await fetch(contextPath + 'api/batch/schedules');
const data = await response.json();
const schedules = data.schedules || [];
@ -389,11 +392,11 @@
try {
// Check if schedule already exists
let method = 'POST';
let url = '/api/batch/schedules';
let url = contextPath + 'api/batch/schedules';
let scheduleExists = false;
try {
const checkResponse = await fetch(`/api/batch/schedules/${jobName}`);
const checkResponse = await fetch(contextPath + `api/batch/schedules/${jobName}`);
if (checkResponse.ok) {
scheduleExists = true;
}
@ -408,7 +411,7 @@
return;
}
method = 'PUT';
url = `/api/batch/schedules/${jobName}`;
url = contextPath + `api/batch/schedules/${jobName}`;
}
const response = await fetch(url, {
@ -451,7 +454,7 @@
}
try {
const response = await fetch(`/api/batch/schedules/${jobName}/toggle`, {
const response = await fetch(contextPath + `api/batch/schedules/${jobName}/toggle`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json'
@ -479,7 +482,7 @@
}
try {
const response = await fetch(`/api/batch/schedules/${jobName}`, {
const response = await fetch(contextPath + `api/batch/schedules/${jobName}`, {
method: 'DELETE'
});