diff --git a/WebContent/quartzStatus.jsp b/WebContent/quartzStatus.jsp new file mode 100644 index 0000000..9882ebd --- /dev/null +++ b/WebContent/quartzStatus.jsp @@ -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 buildTriggerInfo(Scheduler scheduler, Trigger trigger) throws Exception { + Map 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> getJobList(Scheduler scheduler) throws Exception { + List> jobs = new ArrayList<>(); + for (String group : scheduler.getJobGroupNames()) { + for (JobKey jobKey : scheduler.getJobKeys(GroupMatcher.jobGroupEquals(group))) { + JobDetail detail = scheduler.getJobDetail(jobKey); + List triggers = scheduler.getTriggersOfJob(jobKey); + + Map 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> 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; +%> + + + + + <% if (autoRefresh) { %> + + <% } %> + Quartz Scheduler Status + + + +

Quartz Scheduler Status + <% if (autoRefresh) { %>Auto <%= refreshSec %>s<% } %> +

+

+ Instance: <%= System.getProperty("inst.Name", "N/A") %> + | Server Time: <%= new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) %> +

+

+ Auto Refresh: + <% if (!autoRefresh) { %>OFF<% } else { %>OFF<% } %> + | + <% int[] intervals = {5, 10, 30, 60}; %> + <% for (int iv : intervals) { %> + <% if (autoRefresh && refreshSec == iv) { %><%= iv %>s<% } else { %><%= iv %>s<% } %> + <% } %> +

+ +<% if (error != null) { %> +
<%= error %>
+<% } %> + +<% if (scheduler != null) { %> +

Scheduler: <%= scheduler.getSchedulerName() %> (RAM)

+
+ Status: "><%= scheduler.isStarted() ? "STARTED" : "STOPPED" %> + Instance ID: <%= scheduler.getSchedulerInstanceId() %> + Job Count: <%= getJobList(scheduler).size() %> +
+ + + + + + + + + + + + <% int jobIdx = 0; for (Map job : getJobList(scheduler)) { String rowId = "s_" + jobIdx; List> tList = (List>) job.get("triggerList"); %> + + + + + + + + + + + + + + <% jobIdx++; } %> +
Job NameJob ClassCronStateConcurrentPrev FireNext FireJob Data
<%= job.get("name") %>(<%= job.get("triggerCount") %> triggers)<%= job.get("jobClass") %><%= job.get("cronExp") %>"><%= job.get("state") %>"> + <%= (Boolean)job.get("disallowConcurrent") ? "NO (protected)" : "YES (allowed)" %><%= job.get("prevFire") %><%= job.get("prevFireRel") %><%= job.get("nextFire") %><%= job.get("nextFireRel") %><%= job.get("jobData") %>
+ <% if (tList.isEmpty()) { %> + No triggers + <% } else { %> + + + <% for (Map t : tList) { %> + + + + + + + + + + + <% } %> +
Trigger NameGroupTypeExpression / IntervalStatePriorityPrev FireNext Fire
<%= t.get("triggerName") %><%= t.get("triggerGroup") %><%= t.get("type") %><%= t.get("cronExp") %>"><%= t.get("state") %><%= t.get("priority") %><%= t.get("prevFire") %><%= t.get("prevFireRel") %><%= t.get("nextFire") %><%= t.get("nextFireRel") %>
+ <% } %> +
+<% } %> + +<% if (clusteredScheduler != null) { %> +

Clustered Scheduler: <%= clusteredScheduler.getSchedulerName() %> (DB)

+
+ Status: "><%= clusteredScheduler.isStarted() ? "STARTED" : "STOPPED" %> + Instance ID: <%= clusteredScheduler.getSchedulerInstanceId() %> + Job Count: <%= getJobList(clusteredScheduler).size() %> +
+ + + + + + + + + + + + <% int cJobIdx = 0; for (Map job : getJobList(clusteredScheduler)) { String rowId = "c_" + cJobIdx; List> tList = (List>) job.get("triggerList"); %> + + + + + + + + + + + + + + <% cJobIdx++; } %> +
Job NameJob ClassCronStateConcurrentPrev FireNext FireJob Data
<%= job.get("name") %>(<%= job.get("triggerCount") %> triggers)<%= job.get("jobClass") %><%= job.get("cronExp") %>"><%= job.get("state") %>"> + <%= (Boolean)job.get("disallowConcurrent") ? "NO (protected)" : "YES (allowed)" %><%= job.get("prevFire") %><%= job.get("prevFireRel") %><%= job.get("nextFire") %><%= job.get("nextFireRel") %><%= job.get("jobData") %>
+ <% if (tList.isEmpty()) { %> + No triggers + <% } else { %> + + + <% for (Map t : tList) { %> + + + + + + + + + + + <% } %> +
Trigger NameGroupTypeExpression / IntervalStatePriorityPrev FireNext Fire
<%= t.get("triggerName") %><%= t.get("triggerGroup") %><%= t.get("type") %><%= t.get("cronExp") %>"><%= t.get("state") %><%= t.get("priority") %><%= t.get("prevFire") %><%= t.get("prevFireRel") %><%= t.get("nextFire") %><%= t.get("nextFireRel") %>
+ <% } %> +
+<% } %> + + + +