Files
spring-batch-quartz/batch-quartz/src/main/resources/static/js/pages/dashboard/dashboard.js
mindol1004 d63b268765 commit
2024-10-22 12:11:09 +09:00

275 lines
8.9 KiB
JavaScript

import { addEvents, formatDateTime } from '../../common/common.js';
import dashBoardService from '../../apis/dashboard-api.js';
let selectedMonth;
document.addEventListener('DOMContentLoaded', () => {
initMonthPicker();
fetchDataAndRender();
setupEventListeners();
});
const initMonthPicker = () => {
const monthPicker = document.getElementById('monthPicker');
selectedMonth = dayjs().format('YYYY-MM');
monthPicker.value = selectedMonth;
monthPicker.addEventListener('click', (event) => {
event.preventDefault();
monthPicker.showPicker();
});
monthPicker.addEventListener('change', ({ target: { value } }) => {
selectedMonth = value;
if (selectedMonth) fetchDataAndRender();
});
};
const fetchDataAndRender = async () => {
const [year, month] = selectedMonth.split('-');
const [batchData, recentJobs] = await Promise.all([
dashBoardService.getBatchJobExecutionData(year, month),
dashBoardService.getRecentJobs()
]);
renderBatchExecutionTimeChart(batchData.jobAvgSummary);
renderBatchStatusChart(batchData.statusCounts);
renderHourlyJobExecutionChart(batchData.jobHourSummary);
renderDailyJobExecutionsChart(batchData.jobExecutionSummary);
renderRecentJobsTable(recentJobs);
};
const setupEventListeners = () => {
const eventMap = {'refreshBtn': { event: 'click', handler: fetchDataAndRender }};
addEvents(eventMap);
};
const chartOptions = {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { position: 'top' },
title: { font: { size: 16 } }
}
};
let batchExecutionTimeChart;
const renderBatchExecutionTimeChart = (data) => {
if (batchExecutionTimeChart) batchExecutionTimeChart.destroy();
const jobExecutionTimes = data.reduce((acc, job) => {
if (job.endTime && job.startTime) {
const duration = (new Date(job.endTime) - new Date(job.startTime)) / 1000;
if (!acc[job.jobName]) acc[job.jobName] = { total: 0, count: 0 };
acc[job.jobName].total += duration;
acc[job.jobName].count++;
}
return acc;
}, {});
const averageExecutionTimes = Object.fromEntries(
Object.entries(jobExecutionTimes).map(([jobName, { total, count }]) => [jobName, total / count])
);
const ctx = document.getElementById('batchExecutionTimeChart').getContext('2d');
batchExecutionTimeChart = new Chart(ctx, {
type: 'bar',
data: {
labels: Object.keys(averageExecutionTimes),
datasets: [{
label: '평균 실행 시간 (초)',
data: Object.values(averageExecutionTimes),
backgroundColor: 'rgba(54, 162, 235, 0.6)',
borderColor: 'rgba(54, 162, 235, 3)',
borderWidth: 3
}]
},
options: {
...chartOptions,
plugins: {
...chartOptions.plugins,
tooltip: {
callbacks: {
label: (context) => `${context.dataset.label || ''}: ${context.parsed.y.toFixed(2)}`
}
}
},
scales: {
y: {
beginAtZero: true,
title: { display: true, text: '시간 (초)' }
}
}
}
});
};
let batchStatusChart;
const renderBatchStatusChart = (data) => {
if (batchStatusChart) batchStatusChart.destroy();
const statusTotals = data.reduce((acc, { status, count }) => {
acc[status] = (acc[status] || 0) + count;
return acc;
}, {});
const statusColors = {
'COMPLETED': 'rgba(75, 192, 192, 0.6)',
'FAILED': 'rgba(255, 99, 132, 0.6)',
'STARTING': 'rgba(255, 206, 86, 0.6)',
'STARTED': 'rgba(54, 162, 235, 0.6)',
'STOPPING': 'rgba(153, 102, 255, 0.6)',
'STOPPED': 'rgba(255, 159, 64, 0.6)',
'ABANDONED': 'rgba(201, 203, 207, 0.6)'
};
const ctx = document.getElementById('batchStatusChart').getContext('2d');
batchStatusChart = new Chart(ctx, {
type: 'pie',
data: {
labels: Object.keys(statusTotals),
datasets: [{
data: Object.values(statusTotals),
backgroundColor: Object.values(statusColors)
}]
},
options: {
...chartOptions,
plugins: {
...chartOptions.plugins,
tooltip: {
callbacks: {
title: () => null,
label: ({ label, parsed }) => `${label}: ${parsed}`,
afterLabel: ({ label }) => data
.filter(item => item.status === label)
.map(item => ` ${item.jobName}: ${item.count}`)
.join('\n')
}
}
}
}
});
};
let hourlyJobExecutionChart;
const renderHourlyJobExecutionChart = (data) => {
if (hourlyJobExecutionChart) hourlyJobExecutionChart.destroy();
const ctx = document.getElementById('hourlyJobExecutionChart').getContext('2d');
const hours = Array.from({ length: 24 }, (_, i) => i);
const jobNames = [...new Set(data.map(item => item.jobName))];
const datasets = jobNames.map(jobName => ({
label: jobName,
data: hours.map(hour => data.find(item => item.jobName === jobName && item.hour === hour)?.count || 0),
borderColor: getRandomColor(),
backgroundColor: 'rgba(0, 0, 0, 0.1)',
fill: false
}));
hourlyJobExecutionChart = new Chart(ctx, {
type: 'line',
data: { labels: hours.map(hour => `${hour}:00`), datasets },
options: {
...chartOptions,
plugins: {
...chartOptions.plugins,
tooltip: { mode: 'index', intersect: false }
},
scales: {
x: { display: true, title: { display: true, text: '시간' } },
y: {
display: true,
title: { display: true, text: '실행 횟수' },
suggestedMin: 0,
beginAtZero: true
}
}
}
});
};
let dailyJobExecutionsChart;
const renderDailyJobExecutionsChart = (data) => {
if (dailyJobExecutionsChart) dailyJobExecutionsChart.destroy();
const ctx = document.getElementById('dailyJobExecutionsChart').getContext('2d');
const [year, month] = selectedMonth.split('-');
const firstDay = new Date(year, month - 1, 1);
const lastDay = new Date(year, month, 0);
const dates = Array.from({ length: lastDay.getDate() }, (_, i) =>
new Date(year, month - 1, i + 1).toISOString().split('T')[0]
);
const groupedData = data.reduce((acc, item) => {
const date = item.executionDate.split('T')[0];
if (!acc[date]) acc[date] = {};
acc[date][item.jobName] = (acc[date][item.jobName] || 0) + item.executionCount;
return acc;
}, {});
const jobNames = [...new Set(data.map(item => item.jobName))];
const datasets = jobNames.map(jobName => {
const color = getRandomColor();
return {
label: jobName,
data: dates.map(date => ({
x: luxon.DateTime.fromISO(date).toJSDate(),
y: groupedData[date]?.[jobName] || 0
})),
borderColor: color,
backgroundColor: color,
fill: false,
spanGaps: true
};
});
dailyJobExecutionsChart = new Chart(ctx, {
type: 'line',
data: { datasets },
options: {
...chartOptions,
plugins: {
...chartOptions.plugins,
tooltip: {
callbacks: {
title: (context) => luxon.DateTime.fromJSDate(context[0].parsed.x).toFormat('yyyy-MM-dd')
}
}
},
scales: {
x: {
type: 'time',
time: { unit: 'day', displayFormats: { day: 'MM-dd' } },
title: { display: true, text: '날짜' },
min: firstDay,
max: lastDay
},
y: {
beginAtZero: true,
title: { display: true, text: '실행 횟수' }
}
}
}
});
};
const renderRecentJobsTable = (recentJobs) => {
document.getElementById('recentJobsTable').innerHTML = recentJobs
.map(({ jobGroup, jobName, firedTime, state }) => `
<tr>
<td>${jobGroup}</td>
<td>${jobName}</td>
<td>${formatDateTime(firedTime)}</td>
<td>${state}</td>
</tr>
`).join('');
};
const getRandomColor = () => {
const [r, g, b] = Array.from({ length: 3 }, () => Math.floor(Math.random() * 255));
return `rgb(${r}, ${g}, ${b})`;
};