275 lines
8.9 KiB
JavaScript
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})`;
|
|
}; |