commit
This commit is contained in:
@@ -16,7 +16,10 @@
|
||||
|
||||
<properties>
|
||||
<java.version>11</java.version>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<maven.compiler.release>11</maven.compiler.release>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
|
||||
@@ -17,7 +17,8 @@ public enum ExceptionRule implements ErrorRule {
|
||||
INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "서버에 오류가 발생했습니다."),
|
||||
CONFLICT(HttpStatus.CONFLICT, "데이터 충돌이 발생했습니다."),
|
||||
UNPROCESSABLE_ENTITY(HttpStatus.UNPROCESSABLE_ENTITY, "요청을 처리할 수 없습니다."),
|
||||
USER_NOT_FOUND(HttpStatus.NOT_FOUND, "사용자를 찾을 수 없습니다.");
|
||||
USER_NOT_FOUND(HttpStatus.NOT_FOUND, "사용자를 찾을 수 없습니다."),
|
||||
SCHEDULE_NOT_FOUND(HttpStatus.NOT_FOUND, "스케쥴 정보를 찾을 수 없습니다.");
|
||||
|
||||
private final HttpStatus status;
|
||||
private String message;
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
package com.spring.domain.batch.entity;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.ForeignKey;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.JoinColumn;
|
||||
import javax.persistence.OneToOne;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
// @Entity
|
||||
// @Table(name = "BATCH_JOB_EXECUTION_CONTEXT")
|
||||
@Getter
|
||||
public class BatchJobExecutionContext {
|
||||
|
||||
@Id
|
||||
@Column(name = "JOB_EXECUTION_ID")
|
||||
private Long jobExecutionId;
|
||||
|
||||
@Column(name = "SHORT_CONTEXT", length = 2500, nullable = false)
|
||||
private String shortContext;
|
||||
|
||||
@Column(name = "SERIALIZED_CONTEXT")
|
||||
private String serializedContext;
|
||||
|
||||
@OneToOne
|
||||
@JoinColumn(name = "JOB_EXECUTION_ID", insertable = false, updatable = false, foreignKey = @ForeignKey(name = "JOB_EXEC_CTX_FK"))
|
||||
private BatchJobExecution batchJobExecution;
|
||||
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
package com.spring.domain.batch.entity;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Embeddable;
|
||||
import javax.persistence.EmbeddedId;
|
||||
import javax.persistence.ForeignKey;
|
||||
import javax.persistence.JoinColumn;
|
||||
import javax.persistence.ManyToOne;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
// @Entity
|
||||
// @Table(name = "BATCH_JOB_EXECUTION_PARAMS")
|
||||
@Getter
|
||||
public class BatchJobExecutionParams {
|
||||
|
||||
@EmbeddedId
|
||||
private BatchJobExecutionParamsId id;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "JOB_EXECUTION_ID", insertable = false, updatable = false, foreignKey = @ForeignKey(name = "JOB_EXEC_PARAMS_FK"))
|
||||
private BatchJobExecution jobExecution;
|
||||
|
||||
@Column(name = "PARAMETER_TYPE", nullable = false)
|
||||
private String parameterType;
|
||||
|
||||
@Column(name = "PARAMETER_VALUE", length = 2500)
|
||||
private String parameterValue;
|
||||
|
||||
@Column(name = "IDENTIFYING", nullable = false)
|
||||
private Character identifying;
|
||||
|
||||
@Embeddable
|
||||
@Getter
|
||||
@EqualsAndHashCode
|
||||
public static class BatchJobExecutionParamsId implements Serializable {
|
||||
@Column(name = "JOB_EXECUTION_ID")
|
||||
private Long jobExecutionId;
|
||||
|
||||
@Column(name = "PARAMETER_NAME")
|
||||
private String parameterName;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
package com.spring.domain.batch.entity;
|
||||
|
||||
import java.sql.Timestamp;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.ForeignKey;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.GenerationType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.JoinColumn;
|
||||
import javax.persistence.ManyToOne;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
// @Entity
|
||||
// @Table(name = "BATCH_STEP_EXECUTION")
|
||||
@Getter
|
||||
public class BatchStepExecution {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Column(name = "STEP_EXECUTION_ID")
|
||||
private Long stepExecutionId;
|
||||
|
||||
@Column(name = "VERSION", nullable = false)
|
||||
private Long version;
|
||||
|
||||
@Column(name = "STEP_NAME", length = 100, nullable = false)
|
||||
private String stepName;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "JOB_EXECUTION_ID", nullable = false, foreignKey = @ForeignKey(name = "JOB_EXEC_STEP_FK"))
|
||||
private BatchJobExecution batchJobExecution;
|
||||
|
||||
@Column(name = "CREATE_TIME", nullable = false)
|
||||
private Timestamp createTime;
|
||||
|
||||
@Column(name = "START_TIME")
|
||||
private Timestamp startTime;
|
||||
|
||||
@Column(name = "END_TIME")
|
||||
private Timestamp endTime;
|
||||
|
||||
@Column(name = "STATUS", length = 10)
|
||||
private String status;
|
||||
|
||||
@Column(name = "COMMIT_COUNT")
|
||||
private Long commitCount;
|
||||
|
||||
@Column(name = "READ_COUNT")
|
||||
private Long readCount;
|
||||
|
||||
@Column(name = "FILTER_COUNT")
|
||||
private Long filterCount;
|
||||
|
||||
@Column(name = "WRITE_COUNT")
|
||||
private Long writeCount;
|
||||
|
||||
@Column(name = "READ_SKIP_COUNT")
|
||||
private Long readSkipCount;
|
||||
|
||||
@Column(name = "WRITE_SKIP_COUNT")
|
||||
private Long writeSkipCount;
|
||||
|
||||
@Column(name = "PROCESS_SKIP_COUNT")
|
||||
private Long processSkipCount;
|
||||
|
||||
@Column(name = "ROLLBACK_COUNT")
|
||||
private Long rollbackCount;
|
||||
|
||||
@Column(name = "EXIT_CODE", length = 2500)
|
||||
private String exitCode;
|
||||
|
||||
@Column(name = "EXIT_MESSAGE", length = 2500)
|
||||
private String exitMessage;
|
||||
|
||||
@Column(name = "LAST_UPDATED")
|
||||
private Timestamp lastUpdated;
|
||||
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
package com.spring.domain.batch.entity;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.ForeignKey;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.JoinColumn;
|
||||
import javax.persistence.OneToOne;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
// @Entity
|
||||
// @Table(name = "BATCH_STEP_EXECUTION_CONTEXT")
|
||||
@Getter
|
||||
public class BatchStepExecutionContext {
|
||||
|
||||
@Id
|
||||
@Column(name = "STEP_EXECUTION_ID")
|
||||
private Long stepExecutionId;
|
||||
|
||||
@Column(name = "SHORT_CONTEXT", length = 2500, nullable = false)
|
||||
private String shortContext;
|
||||
|
||||
@Column(name = "SERIALIZED_CONTEXT")
|
||||
private String serializedContext;
|
||||
|
||||
@OneToOne
|
||||
@JoinColumn(name = "STEP_EXECUTION_ID", insertable = false, updatable = false, foreignKey = @ForeignKey(name = "STEP_EXEC_CTX_FK"))
|
||||
private BatchStepExecution batchStepExecution;
|
||||
|
||||
}
|
||||
@@ -17,31 +17,25 @@ import lombok.extern.slf4j.Slf4j;
|
||||
@BatchJobInfo(
|
||||
group = "EMAIL",
|
||||
jobName = "emailSendJob",
|
||||
cronExpression = "*/5 * * * * ?"
|
||||
cronExpression = "*/10 * * * * ?"
|
||||
)
|
||||
public class EmailSendBatch extends AbstractBatchTask {
|
||||
|
||||
@Override
|
||||
protected List<Step> createSteps() {
|
||||
return List.of(
|
||||
addStep("emailSendJobStep1111", createTasklet()),
|
||||
addStep("emailSendJobStep2222", createSendTasklet())
|
||||
addStep("emailSendJobStep1", createTasklet()),
|
||||
addStep("emailSendJobStep2", createSendTasklet())
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Tasklet createTasklet() {
|
||||
return ((contribution, chunkContext) -> {
|
||||
log.info(">>>>> emailSendTasklet1111111");
|
||||
return RepeatStatus.FINISHED;
|
||||
});
|
||||
return ((contribution, chunkContext) -> RepeatStatus.FINISHED);
|
||||
}
|
||||
|
||||
private Tasklet createSendTasklet() {
|
||||
return ((contribution, chunkContext) -> {
|
||||
log.info(">>>>> emailSendTasklet2222222");
|
||||
return RepeatStatus.FINISHED;
|
||||
});
|
||||
return ((contribution, chunkContext) -> RepeatStatus.FINISHED);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -21,24 +21,21 @@ import lombok.extern.slf4j.Slf4j;
|
||||
@BatchJobInfo(
|
||||
group = "POST",
|
||||
jobName = "postCreateJob",
|
||||
cronExpression = "0/2 * * * * ?"
|
||||
cronExpression = "0/20 * * * * ?"
|
||||
)
|
||||
@RequiredArgsConstructor
|
||||
public class PostCreateBatch extends AbstractBatchTask {
|
||||
|
||||
private final PostMapper postMapper;
|
||||
|
||||
@Autowired
|
||||
@Override
|
||||
public void setTransactionManager(
|
||||
@Qualifier(SecondaryJpaConfig.TRANSACTION_MANAGER) PlatformTransactionManager transactionManager
|
||||
) {
|
||||
super.setTransactionManager(transactionManager);
|
||||
}
|
||||
// @Autowired
|
||||
// @Override
|
||||
// public void setTransactionManager(@Qualifier(SecondaryJpaConfig.TRANSACTION_MANAGER) PlatformTransactionManager transactionManager) {
|
||||
// super.setTransactionManager(transactionManager);
|
||||
// }
|
||||
|
||||
@Override
|
||||
protected Tasklet createTasklet() {
|
||||
log.info(">>>>> PostCreateBatch-createTasklet");
|
||||
return ((contribution, chunkContext) -> {
|
||||
postMapper.save(Post.builder().title("testTitle").content("testPost").build());
|
||||
return RepeatStatus.FINISHED;
|
||||
|
||||
@@ -42,14 +42,14 @@ import lombok.extern.slf4j.Slf4j;
|
||||
public class PostCreateBatchChunk {
|
||||
|
||||
private final JobRepository jobRepository;
|
||||
private final @Qualifier(SecondaryJpaConfig.TRANSACTION_MANAGER) PlatformTransactionManager transactionManager;
|
||||
private final @Qualifier(SecondaryJpaConfig.ENTITY_MANAGER_FACTORY) EntityManagerFactory entityManagerFactory;
|
||||
private final PlatformTransactionManager transactionManager;
|
||||
private final EntityManagerFactory entityManagerFactory;
|
||||
private final PostRepository postRepository;
|
||||
private final PostBackUpRepository postBackUpRepository;
|
||||
|
||||
private List<Post> list = new ArrayList<>();
|
||||
|
||||
@QuartzJob(group = "POST", jobName = "testPostJob", cronExpression = "0/5 * * * * ?")
|
||||
@QuartzJob(group = "POST", jobName = "testPostJob", cronExpression = "0/30 * * * * ?")
|
||||
@Bean
|
||||
Job testPostJob() {
|
||||
return new JobBuilder("testPostJob")
|
||||
@@ -70,7 +70,6 @@ public class PostCreateBatchChunk {
|
||||
}
|
||||
|
||||
private Step readListStep() {
|
||||
log.info(">>>>> readListStep");
|
||||
return new StepBuilder("readListStep")
|
||||
.repository(jobRepository)
|
||||
.transactionManager(transactionManager)
|
||||
@@ -79,7 +78,6 @@ public class PostCreateBatchChunk {
|
||||
}
|
||||
|
||||
private Tasklet readListTasklet() {
|
||||
log.info(">>>>> readListTasklet");
|
||||
return (contribution, chunkContext) -> {
|
||||
list = postRepository.findAll();
|
||||
return RepeatStatus.FINISHED;
|
||||
@@ -87,7 +85,6 @@ public class PostCreateBatchChunk {
|
||||
}
|
||||
|
||||
private Step processStep() {
|
||||
log.info(">>>>> processStep");
|
||||
return new StepBuilder("processStep")
|
||||
.repository(jobRepository)
|
||||
.transactionManager(transactionManager)
|
||||
@@ -99,7 +96,6 @@ public class PostCreateBatchChunk {
|
||||
}
|
||||
|
||||
private JpaPagingItemReader<Post> testReader() {
|
||||
log.info(">>>>> JpaPagingItemReader");
|
||||
return new JpaPagingItemReaderBuilder<Post>()
|
||||
.name("testReader")
|
||||
.entityManagerFactory(entityManagerFactory)
|
||||
@@ -114,12 +110,10 @@ public class PostCreateBatchChunk {
|
||||
}
|
||||
|
||||
private ItemWriter<PostBackUp> testWriter() {
|
||||
log.info(">>>>> testWriter");
|
||||
return postBackUpRepository::saveAll;
|
||||
}
|
||||
|
||||
private Step terminateStep() {
|
||||
log.info(">>>>> terminateStep");
|
||||
return new StepBuilder("terminateStep")
|
||||
.repository(jobRepository)
|
||||
.transactionManager(transactionManager)
|
||||
@@ -128,7 +122,6 @@ public class PostCreateBatchChunk {
|
||||
}
|
||||
|
||||
private Tasklet terminateTasklet() {
|
||||
log.info(">>>>> terminateTasklet");
|
||||
return (contribution, chunkContext) -> {
|
||||
log.error("List Read Error : List is null");
|
||||
return RepeatStatus.FINISHED;
|
||||
|
||||
@@ -14,7 +14,7 @@ import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@DatabaseSelector(SecondaryDataSourceConfig.DATABASE)
|
||||
// @DatabaseSelector(SecondaryDataSourceConfig.DATABASE)
|
||||
@Entity
|
||||
@Table(name = "APP_POST")
|
||||
@Getter
|
||||
|
||||
@@ -12,7 +12,7 @@ import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@DatabaseSelector(SecondaryDataSourceConfig.DATABASE)
|
||||
// @DatabaseSelector(SecondaryDataSourceConfig.DATABASE)
|
||||
@Entity
|
||||
@Table(name = "APP_POST_BACKUP")
|
||||
@Getter
|
||||
|
||||
@@ -5,9 +5,10 @@ import java.util.List;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import com.spring.domain.post.entity.Post;
|
||||
import com.spring.infra.db.orm.mybatis.annotation.SecondaryMapper;
|
||||
import com.spring.infra.db.orm.mybatis.annotation.PrimaryMapper;
|
||||
// import com.spring.infra.db.orm.mybatis.annotation.SecondaryMapper;
|
||||
|
||||
@SecondaryMapper
|
||||
@PrimaryMapper
|
||||
public interface PostMapper {
|
||||
List<Post> findAll();
|
||||
void save(@Param("post") Post post);
|
||||
|
||||
@@ -6,7 +6,7 @@ import com.spring.domain.post.entity.PostBackUp;
|
||||
import com.spring.infra.db.SecondaryDataSourceConfig;
|
||||
import com.spring.infra.db.orm.jpa.annotation.DatabaseSelector;
|
||||
|
||||
@DatabaseSelector(SecondaryDataSourceConfig.DATABASE)
|
||||
// @DatabaseSelector(SecondaryDataSourceConfig.DATABASE)
|
||||
public interface PostBackUpRepository extends JpaRepository<PostBackUp, Long> {
|
||||
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import com.spring.domain.post.entity.Post;
|
||||
import com.spring.infra.db.SecondaryDataSourceConfig;
|
||||
import com.spring.infra.db.orm.jpa.annotation.DatabaseSelector;
|
||||
|
||||
@DatabaseSelector(SecondaryDataSourceConfig.DATABASE)
|
||||
// @DatabaseSelector(SecondaryDataSourceConfig.DATABASE)
|
||||
public interface PostRepository extends JpaRepository<Post, Long> {
|
||||
|
||||
}
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
package com.spring.domain.quartz.entity;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Embeddable;
|
||||
import javax.persistence.EmbeddedId;
|
||||
import javax.persistence.JoinColumn;
|
||||
import javax.persistence.ManyToOne;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
// @Entity
|
||||
// @Table(name = "QRTZ_BLOB_TRIGGERS")
|
||||
@Getter
|
||||
public class QrtzBlobTriggers {
|
||||
|
||||
@EmbeddedId
|
||||
private QrtzBlobTriggersId id;
|
||||
|
||||
@Column(name = "BLOB_DATA")
|
||||
private byte[] blobData;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "SCHED_NAME", referencedColumnName = "SCHED_NAME", insertable = false, updatable = false)
|
||||
@JoinColumn(name = "TRIGGER_NAME", referencedColumnName = "TRIGGER_NAME", insertable = false, updatable = false)
|
||||
@JoinColumn(name = "TRIGGER_GROUP", referencedColumnName = "TRIGGER_GROUP", insertable = false, updatable = false)
|
||||
private QrtzTriggers qrtzTriggers;
|
||||
|
||||
@Embeddable
|
||||
@Getter
|
||||
@EqualsAndHashCode
|
||||
public static class QrtzBlobTriggersId implements Serializable {
|
||||
@Column(name = "SCHED_NAME", length = 120, nullable = false)
|
||||
private String schedName;
|
||||
|
||||
@Column(name = "TRIGGER_NAME", length = 200, nullable = false)
|
||||
private String triggerName;
|
||||
|
||||
@Column(name = "TRIGGER_GROUP", length = 200, nullable = false)
|
||||
private String triggerGroup;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
package com.spring.domain.quartz.entity;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Embeddable;
|
||||
import javax.persistence.EmbeddedId;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
// @Entity
|
||||
// @Table(name = "QRTZ_CALENDARS")
|
||||
@Getter
|
||||
public class QrtzCalendars {
|
||||
|
||||
@EmbeddedId
|
||||
private QrtzCalendarsId id;
|
||||
|
||||
@Column(name = "CALENDAR", nullable = false)
|
||||
private byte[] calendar;
|
||||
|
||||
@Embeddable
|
||||
@Getter
|
||||
@EqualsAndHashCode
|
||||
public static class QrtzCalendarsId implements Serializable {
|
||||
@Column(name = "SCHED_NAME", length = 120, nullable = false)
|
||||
private String schedName;
|
||||
|
||||
@Column(name = "CALENDAR_NAME", length = 200, nullable = false)
|
||||
private String calendarName;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
package com.spring.domain.quartz.entity;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Embeddable;
|
||||
import javax.persistence.EmbeddedId;
|
||||
import javax.persistence.ForeignKey;
|
||||
import javax.persistence.JoinColumn;
|
||||
import javax.persistence.JoinColumns;
|
||||
import javax.persistence.ManyToOne;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
// @Entity
|
||||
// @Table(name = "QRTZ_CRON_TRIGGERS")
|
||||
@Getter
|
||||
public class QrtzCronTriggers {
|
||||
|
||||
@EmbeddedId
|
||||
private QrtzCronTriggersId id;
|
||||
|
||||
@Column(name = "CRON_EXPRESSION", length = 120, nullable = false)
|
||||
private String cronExpression;
|
||||
|
||||
@Column(name = "TIME_ZONE_ID", length = 80)
|
||||
private String timeZoneId;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumns(value = {
|
||||
@JoinColumn(name = "SCHED_NAME", referencedColumnName = "SCHED_NAME", insertable = false, updatable = false),
|
||||
@JoinColumn(name = "TRIGGER_NAME", referencedColumnName = "TRIGGER_NAME", insertable = false, updatable = false),
|
||||
@JoinColumn(name = "TRIGGER_GROUP", referencedColumnName = "TRIGGER_GROUP", insertable = false, updatable = false)
|
||||
}, foreignKey = @ForeignKey(name = "FK_QRTZ_CRON_TRIGGERS_QRTZ_TRIGGERS"))
|
||||
private QrtzTriggers qrtzTriggers;
|
||||
|
||||
@Embeddable
|
||||
@Getter
|
||||
@EqualsAndHashCode
|
||||
public static class QrtzCronTriggersId implements Serializable {
|
||||
@Column(name = "SCHED_NAME", length = 120, nullable = false)
|
||||
private String schedName;
|
||||
|
||||
@Column(name = "TRIGGER_NAME", length = 200, nullable = false)
|
||||
private String triggerName;
|
||||
|
||||
@Column(name = "TRIGGER_GROUP", length = 200, nullable = false)
|
||||
private String triggerGroup;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
package com.spring.domain.quartz.entity;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Embeddable;
|
||||
import javax.persistence.EmbeddedId;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
// @Entity
|
||||
// @Table(name = "QRTZ_FIRED_TRIGGERS")
|
||||
@Getter
|
||||
public class QrtzFiredTriggers {
|
||||
|
||||
@EmbeddedId
|
||||
private QrtzFiredTriggersId id;
|
||||
|
||||
@Column(name = "TRIGGER_NAME", length = 200, nullable = false)
|
||||
private String triggerName;
|
||||
|
||||
@Column(name = "TRIGGER_GROUP", length = 200, nullable = false)
|
||||
private String triggerGroup;
|
||||
|
||||
@Column(name = "INSTANCE_NAME", length = 200, nullable = false)
|
||||
private String instanceName;
|
||||
|
||||
@Column(name = "FIRED_TIME", nullable = false)
|
||||
private long firedTime;
|
||||
|
||||
@Column(name = "SCHED_TIME", nullable = false)
|
||||
private long schedTime;
|
||||
|
||||
@Column(name = "PRIORITY", nullable = false)
|
||||
private int priority;
|
||||
|
||||
@Column(name = "STATE", length = 16, nullable = false)
|
||||
private String state;
|
||||
|
||||
@Column(name = "JOB_NAME", length = 200)
|
||||
private String jobName;
|
||||
|
||||
@Column(name = "JOB_GROUP", length = 200)
|
||||
private String jobGroup;
|
||||
|
||||
@Column(name = "IS_NONCONCURRENT")
|
||||
private Boolean isNonConcurrent;
|
||||
|
||||
@Column(name = "REQUESTS_RECOVERY")
|
||||
private Boolean requestsRecovery;
|
||||
|
||||
@Embeddable
|
||||
@Getter
|
||||
@EqualsAndHashCode
|
||||
public static class QrtzFiredTriggersId implements Serializable {
|
||||
@Column(name = "SCHED_NAME", length = 120, nullable = false)
|
||||
private String schedName;
|
||||
|
||||
@Column(name = "ENTRY_ID", length = 95, nullable = false)
|
||||
private String entryId;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
package com.spring.domain.quartz.entity;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Embeddable;
|
||||
import javax.persistence.EmbeddedId;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
// @Entity
|
||||
// @Table(name = "QRTZ_JOB_DETAILS")
|
||||
@Getter
|
||||
public class QrtzJobDetails {
|
||||
|
||||
@EmbeddedId
|
||||
private QrtzJobDetailsId id;
|
||||
|
||||
@Column(name = "DESCRIPTION", length = 250)
|
||||
private String description;
|
||||
|
||||
@Column(name = "JOB_CLASS_NAME", length = 250, nullable = false)
|
||||
private String jobClassName;
|
||||
|
||||
@Column(name = "IS_DURABLE", nullable = false)
|
||||
private boolean isDurable;
|
||||
|
||||
@Column(name = "IS_NONCONCURRENT", nullable = false)
|
||||
private boolean isNonConcurrent;
|
||||
|
||||
@Column(name = "IS_UPDATE_DATA", nullable = false)
|
||||
private boolean isUpdateData;
|
||||
|
||||
@Column(name = "REQUESTS_RECOVERY", nullable = false)
|
||||
private boolean requestsRecovery;
|
||||
|
||||
@Column(name = "JOB_DATA")
|
||||
private byte[] jobData;
|
||||
|
||||
@Embeddable
|
||||
@Getter
|
||||
@EqualsAndHashCode
|
||||
public static class QrtzJobDetailsId implements Serializable {
|
||||
@Column(name = "SCHED_NAME", length = 120, nullable = false)
|
||||
private String schedName;
|
||||
|
||||
@Column(name = "JOB_NAME", length = 200, nullable = false)
|
||||
private String jobName;
|
||||
|
||||
@Column(name = "JOB_GROUP", length = 200, nullable = false)
|
||||
private String jobGroup;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package com.spring.domain.quartz.entity;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Embeddable;
|
||||
import javax.persistence.EmbeddedId;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
// @Entity
|
||||
// @Table(name = "QRTZ_LOCKS")
|
||||
@Getter
|
||||
public class QrtzLocks {
|
||||
|
||||
@EmbeddedId
|
||||
private QrtzLocksId id;
|
||||
|
||||
@Embeddable
|
||||
@Getter
|
||||
@EqualsAndHashCode
|
||||
public static class QrtzLocksId implements Serializable {
|
||||
@Column(name = "SCHED_NAME", length = 120, nullable = false)
|
||||
private String schedName;
|
||||
|
||||
@Column(name = "LOCK_NAME", length = 40, nullable = false)
|
||||
private String lockName;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package com.spring.domain.quartz.entity;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Embeddable;
|
||||
import javax.persistence.EmbeddedId;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
// @Entity
|
||||
// @Table(name = "QRTZ_PAUSED_TRIGGER_GRPS")
|
||||
@Getter
|
||||
public class QrtzPausedTriggerGrps {
|
||||
|
||||
@EmbeddedId
|
||||
private QrtzPausedTriggerGrpsId id;
|
||||
|
||||
@Embeddable
|
||||
@Getter
|
||||
@EqualsAndHashCode
|
||||
public static class QrtzPausedTriggerGrpsId implements Serializable {
|
||||
@Column(name = "SCHED_NAME", length = 120, nullable = false)
|
||||
private String schedName;
|
||||
|
||||
@Column(name = "TRIGGER_GROUP", length = 200, nullable = false)
|
||||
private String triggerGroup;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
package com.spring.domain.quartz.entity;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Embeddable;
|
||||
import javax.persistence.EmbeddedId;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
// @Entity
|
||||
// @Table(name = "QRTZ_SCHEDULER_STATE")
|
||||
@Getter
|
||||
public class QrtzSchedulerState {
|
||||
|
||||
@EmbeddedId
|
||||
private QrtzSchedulerStateId id;
|
||||
|
||||
@Column(name = "LAST_CHECKIN_TIME", nullable = false)
|
||||
private long lastCheckinTime;
|
||||
|
||||
@Column(name = "CHECKIN_INTERVAL", nullable = false)
|
||||
private long checkinInterval;
|
||||
|
||||
@Embeddable
|
||||
@Getter
|
||||
@EqualsAndHashCode
|
||||
public static class QrtzSchedulerStateId implements Serializable {
|
||||
@Column(name = "SCHED_NAME", length = 120, nullable = false)
|
||||
private String schedName;
|
||||
|
||||
@Column(name = "INSTANCE_NAME", length = 200, nullable = false)
|
||||
private String instanceName;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
package com.spring.domain.quartz.entity;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Embeddable;
|
||||
import javax.persistence.EmbeddedId;
|
||||
import javax.persistence.ForeignKey;
|
||||
import javax.persistence.JoinColumn;
|
||||
import javax.persistence.JoinColumns;
|
||||
import javax.persistence.ManyToOne;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
// @Entity
|
||||
// @Table(name = "QRTZ_SIMPLE_TRIGGERS")
|
||||
@Getter
|
||||
public class QrtzSimpleTriggers {
|
||||
|
||||
@EmbeddedId
|
||||
private QrtzSimpleTriggersId id;
|
||||
|
||||
@Column(name = "REPEAT_COUNT", nullable = false)
|
||||
private long repeatCount;
|
||||
|
||||
@Column(name = "REPEAT_INTERVAL", nullable = false)
|
||||
private long repeatInterval;
|
||||
|
||||
@Column(name = "TIMES_TRIGGERED", nullable = false)
|
||||
private long timesTriggered;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumns(value = {
|
||||
@JoinColumn(name = "SCHED_NAME", referencedColumnName = "SCHED_NAME", insertable = false, updatable = false),
|
||||
@JoinColumn(name = "TRIGGER_NAME", referencedColumnName = "TRIGGER_NAME", insertable = false, updatable = false),
|
||||
@JoinColumn(name = "TRIGGER_GROUP", referencedColumnName = "TRIGGER_GROUP", insertable = false, updatable = false)
|
||||
}, foreignKey = @ForeignKey(name = "FK_QRTZ_SIMPLE_TRIGGERS_QRTZ_TRIGGERS"))
|
||||
private QrtzTriggers qrtzTriggers;
|
||||
|
||||
@Embeddable
|
||||
@Getter
|
||||
@EqualsAndHashCode
|
||||
public static class QrtzSimpleTriggersId implements Serializable {
|
||||
@Column(name = "SCHED_NAME", length = 120, nullable = false)
|
||||
private String schedName;
|
||||
|
||||
@Column(name = "TRIGGER_NAME", length = 200, nullable = false)
|
||||
private String triggerName;
|
||||
|
||||
@Column(name = "TRIGGER_GROUP", length = 200, nullable = false)
|
||||
private String triggerGroup;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
package com.spring.domain.quartz.entity;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Embeddable;
|
||||
import javax.persistence.EmbeddedId;
|
||||
import javax.persistence.ForeignKey;
|
||||
import javax.persistence.JoinColumn;
|
||||
import javax.persistence.JoinColumns;
|
||||
import javax.persistence.ManyToOne;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
// @Entity
|
||||
// @Table(name = "QRTZ_SIMPROP_TRIGGERS")
|
||||
@Getter
|
||||
public class QrtzSimpropTriggers {
|
||||
|
||||
@EmbeddedId
|
||||
private QrtzSimpropTriggersId id;
|
||||
|
||||
@Column(name = "STR_PROP_1", length = 512)
|
||||
private String strProp1;
|
||||
|
||||
@Column(name = "STR_PROP_2", length = 512)
|
||||
private String strProp2;
|
||||
|
||||
@Column(name = "STR_PROP_3", length = 512)
|
||||
private String strProp3;
|
||||
|
||||
@Column(name = "INT_PROP_1")
|
||||
private Integer intProp1;
|
||||
|
||||
@Column(name = "INT_PROP_2")
|
||||
private Integer intProp2;
|
||||
|
||||
@Column(name = "LONG_PROP_1")
|
||||
private Long longProp1;
|
||||
|
||||
@Column(name = "LONG_PROP_2")
|
||||
private Long longProp2;
|
||||
|
||||
@Column(name = "DEC_PROP_1", precision = 13, scale = 4)
|
||||
private BigDecimal decProp1;
|
||||
|
||||
@Column(name = "DEC_PROP_2", precision = 13, scale = 4)
|
||||
private BigDecimal decProp2;
|
||||
|
||||
@Column(name = "BOOL_PROP_1")
|
||||
private Boolean boolProp1;
|
||||
|
||||
@Column(name = "BOOL_PROP_2")
|
||||
private Boolean boolProp2;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumns(value = {
|
||||
@JoinColumn(name = "SCHED_NAME", referencedColumnName = "SCHED_NAME", insertable = false, updatable = false),
|
||||
@JoinColumn(name = "TRIGGER_NAME", referencedColumnName = "TRIGGER_NAME", insertable = false, updatable = false),
|
||||
@JoinColumn(name = "TRIGGER_GROUP", referencedColumnName = "TRIGGER_GROUP", insertable = false, updatable = false)
|
||||
}, foreignKey = @ForeignKey(name = "FK_QRTZ_SIMPROP_TRIGGERS_QRTZ_TRIGGERS"))
|
||||
private QrtzTriggers qrtzTriggers;
|
||||
|
||||
@Embeddable
|
||||
@Getter
|
||||
@EqualsAndHashCode
|
||||
public static class QrtzSimpropTriggersId implements Serializable {
|
||||
@Column(name = "SCHED_NAME", length = 120, nullable = false)
|
||||
private String schedName;
|
||||
|
||||
@Column(name = "TRIGGER_NAME", length = 200, nullable = false)
|
||||
private String triggerName;
|
||||
|
||||
@Column(name = "TRIGGER_GROUP", length = 200, nullable = false)
|
||||
private String triggerGroup;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.spring.domain.schedule.api;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import com.spring.domain.schedule.dto.BatchChartResponse;
|
||||
import com.spring.domain.schedule.dto.RecentJobResponse;
|
||||
import com.spring.domain.schedule.service.BatchChartService;
|
||||
import com.spring.domain.schedule.service.DashBoardJobService;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@RequestMapping("/api/dashboard")
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
public class DashBoardApi {
|
||||
|
||||
private final BatchChartService batchChartService;
|
||||
private final DashBoardJobService dashBoardJobService;
|
||||
|
||||
@GetMapping("/chart/batch")
|
||||
public BatchChartResponse getBatchJobExecutionData() {
|
||||
return batchChartService.getBatchJobExecutionData();
|
||||
}
|
||||
|
||||
@GetMapping("/recent-job")
|
||||
public List<RecentJobResponse> getRecentJobs() {
|
||||
return dashBoardJobService.getRecentJobs();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -12,7 +12,6 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import com.spring.domain.schedule.dto.ReScheduleJobRequest;
|
||||
import com.spring.domain.schedule.dto.ScheduleJobResponse;
|
||||
import com.spring.domain.schedule.service.FindJobGroupService;
|
||||
import com.spring.domain.schedule.service.FindScheduleJobService;
|
||||
import com.spring.domain.schedule.service.ReScheduleJobService;
|
||||
import com.spring.domain.schedule.service.ScheduleControlService;
|
||||
@@ -25,26 +24,17 @@ import lombok.RequiredArgsConstructor;
|
||||
public class ScheduleJobApi {
|
||||
|
||||
private final FindScheduleJobService findScheduleJobService;
|
||||
private final FindJobGroupService findJobGroupService;
|
||||
private final ReScheduleJobService reScheduleJobService;
|
||||
private final ScheduleControlService scheduleControlService;
|
||||
|
||||
@GetMapping
|
||||
public List<ScheduleJobResponse> getAllJobs(
|
||||
@RequestParam(required = false) String groupName,
|
||||
@RequestParam(required = false) String jobName
|
||||
) {
|
||||
public List<ScheduleJobResponse> getAllJobs(@RequestParam(required = false) String groupName, @RequestParam(required = false) String jobName) {
|
||||
return findScheduleJobService.getAllJobs(groupName, jobName);
|
||||
}
|
||||
|
||||
@GetMapping("/groups")
|
||||
public List<String> getJobGroups() {
|
||||
return findJobGroupService.getJobGroups();
|
||||
}
|
||||
|
||||
@GetMapping("/group/{groupName}/names")
|
||||
public List<String> getJobNamesByGroup(@PathVariable String groupName) {
|
||||
return findJobGroupService.getJobNamesByGroup(groupName);
|
||||
@GetMapping("/{groupName}/{jobName}")
|
||||
public ScheduleJobResponse getJobDetail(@PathVariable String groupName, @PathVariable String jobName) {
|
||||
return findScheduleJobService.getJobDetail(groupName, jobName);
|
||||
}
|
||||
|
||||
@GetMapping("/pause/{groupName}/{jobName}")
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.spring.domain.schedule.dto;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.batch.core.BatchStatus;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public class BatchChartResponse {
|
||||
|
||||
private final List<BatchJobAverageDurationProjection> jobAvgSummary;
|
||||
private final Map<BatchStatus, Long> statusCounts;
|
||||
private final List<BatchJobHourProjection> jobHourSummary;
|
||||
private final List<BatchJobExecutionProjection> jobExecutionSummary;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.spring.domain.schedule.dto;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
public interface BatchJobAverageDurationProjection {
|
||||
String getJobName();
|
||||
LocalDateTime getStartTime();
|
||||
LocalDateTime getEndTime();
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.spring.domain.schedule.dto;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
public interface BatchJobExecutionProjection {
|
||||
LocalDateTime getExecutionDate();
|
||||
String getJobName();
|
||||
Long getExecutionCount();
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.spring.domain.schedule.dto;
|
||||
|
||||
public interface BatchJobHourProjection {
|
||||
|
||||
String getJobName();
|
||||
Integer getHour();
|
||||
Long getCount();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.spring.domain.schedule.dto;
|
||||
|
||||
import org.springframework.batch.core.BatchStatus;
|
||||
|
||||
public interface BatchJobStatusCountProjection {
|
||||
BatchStatus getStatus();
|
||||
Long getCount();
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.spring.domain.schedule.dto;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public class QuartzChartResponse {
|
||||
|
||||
private final List<QuartzJobFrequencyProjection> jobFrequencys;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.spring.domain.schedule.dto;
|
||||
|
||||
public interface QuartzJobFrequencyProjection {
|
||||
String getJobName();
|
||||
Long getCount();
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.spring.domain.schedule.dto;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public class RecentJobResponse {
|
||||
|
||||
private final String jobName;
|
||||
private final String jobGroup;
|
||||
private final Long firedTime;
|
||||
private final String state;
|
||||
|
||||
}
|
||||
@@ -1,22 +1,23 @@
|
||||
package com.spring.domain.batch.entity;
|
||||
package com.spring.domain.schedule.entity;
|
||||
|
||||
import java.sql.Timestamp;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.ForeignKey;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.GenerationType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.JoinColumn;
|
||||
import javax.persistence.ManyToOne;
|
||||
import javax.persistence.Table;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
// @Entity
|
||||
// @Table(name = "BATCH_JOB_EXECUTION")
|
||||
@Entity
|
||||
@Table(name = "BATCH_JOB_EXECUTION")
|
||||
@Getter
|
||||
public class BatchJobExecution {
|
||||
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Column(name = "JOB_EXECUTION_ID")
|
||||
@@ -26,17 +27,17 @@ public class BatchJobExecution {
|
||||
private Long version;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "JOB_INSTANCE_ID", nullable = false, foreignKey = @ForeignKey(name = "JOB_INST_EXEC_FK"))
|
||||
private BatchJobInstance batchJobInstance;
|
||||
@JoinColumn(name = "JOB_INSTANCE_ID", nullable = false)
|
||||
private BatchJobInstance jobInstance;
|
||||
|
||||
@Column(name = "CREATE_TIME", nullable = false)
|
||||
private Timestamp createTime;
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@Column(name = "START_TIME")
|
||||
private Timestamp startTime;
|
||||
private LocalDateTime startTime;
|
||||
|
||||
@Column(name = "END_TIME")
|
||||
private Timestamp endTime;
|
||||
private LocalDateTime endTime;
|
||||
|
||||
@Column(name = "STATUS", length = 10)
|
||||
private String status;
|
||||
@@ -48,6 +49,9 @@ public class BatchJobExecution {
|
||||
private String exitMessage;
|
||||
|
||||
@Column(name = "LAST_UPDATED")
|
||||
private Timestamp lastUpdated;
|
||||
|
||||
private LocalDateTime lastUpdated;
|
||||
|
||||
@Column(name = "JOB_CONFIGURATION_LOCATION", length = 2500)
|
||||
private String jobConfigurationLocation;
|
||||
|
||||
}
|
||||
@@ -1,18 +1,19 @@
|
||||
package com.spring.domain.batch.entity;
|
||||
package com.spring.domain.schedule.entity;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.GenerationType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Table;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
// @Entity
|
||||
// @Table(name = "BATCH_JOB_INSTANCE",
|
||||
// uniqueConstraints = @UniqueConstraint(name = "JOB_INST_UN", columnNames = {"JOB_NAME", "JOB_KEY"}))
|
||||
@Entity
|
||||
@Table(name = "BATCH_JOB_INSTANCE")
|
||||
@Getter
|
||||
public class BatchJobInstance {
|
||||
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Column(name = "JOB_INSTANCE_ID")
|
||||
@@ -21,10 +22,10 @@ public class BatchJobInstance {
|
||||
@Column(name = "VERSION")
|
||||
private Long version;
|
||||
|
||||
@Column(name = "JOB_NAME", length = 100, nullable = false)
|
||||
@Column(name = "JOB_NAME", nullable = false, length = 100)
|
||||
private String jobName;
|
||||
|
||||
@Column(name = "JOB_KEY", length = 32, nullable = false)
|
||||
@Column(name = "JOB_KEY", nullable = false, length = 32)
|
||||
private String jobKey;
|
||||
|
||||
}
|
||||
@@ -1,26 +1,31 @@
|
||||
package com.spring.domain.quartz.entity;
|
||||
package com.spring.domain.schedule.entity;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Embeddable;
|
||||
import javax.persistence.EmbeddedId;
|
||||
import javax.persistence.ForeignKey;
|
||||
import javax.persistence.JoinColumn;
|
||||
import javax.persistence.JoinColumns;
|
||||
import javax.persistence.ManyToOne;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Lob;
|
||||
import javax.persistence.Table;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
// @Entity
|
||||
// @Table(name = "QRTZ_TRIGGERS")
|
||||
@Entity
|
||||
@Table(name = "QRTZ_TRIGGERS")
|
||||
@Getter
|
||||
public class QrtzTriggers {
|
||||
|
||||
@EmbeddedId
|
||||
private QrtzTriggersId id;
|
||||
|
||||
@Column(name = "JOB_NAME", nullable = false, length = 200)
|
||||
private String jobName;
|
||||
|
||||
@Column(name = "JOB_GROUP", nullable = false, length = 200)
|
||||
private String jobGroup;
|
||||
|
||||
@Column(name = "DESCRIPTION", length = 250)
|
||||
private String description;
|
||||
|
||||
@@ -28,15 +33,15 @@ public class QrtzTriggers {
|
||||
private Long nextFireTime;
|
||||
|
||||
@Column(name = "PREV_FIRE_TIME")
|
||||
private Long prevFireTime;
|
||||
private Long previousFireTime;
|
||||
|
||||
@Column(name = "PRIORITY")
|
||||
private Integer priority;
|
||||
|
||||
@Column(name = "TRIGGER_STATE", length = 16, nullable = false)
|
||||
@Column(name = "TRIGGER_STATE", nullable = false, length = 16)
|
||||
private String triggerState;
|
||||
|
||||
@Column(name = "TRIGGER_TYPE", length = 8, nullable = false)
|
||||
@Column(name = "TRIGGER_TYPE", nullable = false, length = 8)
|
||||
private String triggerType;
|
||||
|
||||
@Column(name = "START_TIME", nullable = false)
|
||||
@@ -49,19 +54,12 @@ public class QrtzTriggers {
|
||||
private String calendarName;
|
||||
|
||||
@Column(name = "MISFIRE_INSTR")
|
||||
private Short misfireInstr;
|
||||
private Short misfireInstruction;
|
||||
|
||||
@Lob
|
||||
@Column(name = "JOB_DATA")
|
||||
private byte[] jobData;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumns(value = {
|
||||
@JoinColumn(name = "SCHED_NAME", referencedColumnName = "SCHED_NAME", insertable = false, updatable = false),
|
||||
@JoinColumn(name = "JOB_NAME", referencedColumnName = "JOB_NAME", insertable = false, updatable = false),
|
||||
@JoinColumn(name = "JOB_GROUP", referencedColumnName = "JOB_GROUP", insertable = false, updatable = false)
|
||||
}, foreignKey = @ForeignKey(name = "FK_QRTZ_TRIGGERS_QRTZ_JOB_DETAILS"))
|
||||
private QrtzJobDetails qrtzJobDetails;
|
||||
|
||||
|
||||
@Embeddable
|
||||
@Getter
|
||||
@EqualsAndHashCode
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.spring.domain.schedule.error;
|
||||
|
||||
import com.spring.common.error.BizBaseException;
|
||||
import com.spring.common.error.ExceptionRule;
|
||||
|
||||
public class ScheduleNotFoundException extends BizBaseException {
|
||||
|
||||
public ScheduleNotFoundException() {
|
||||
super(ExceptionRule.SCHEDULE_NOT_FOUND);
|
||||
}
|
||||
|
||||
public ScheduleNotFoundException(ExceptionRule exceptionRule) {
|
||||
super(exceptionRule);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.spring.domain.schedule.repository;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
|
||||
import com.spring.domain.schedule.dto.BatchJobAverageDurationProjection;
|
||||
import com.spring.domain.schedule.dto.BatchJobExecutionProjection;
|
||||
import com.spring.domain.schedule.dto.BatchJobHourProjection;
|
||||
import com.spring.domain.schedule.dto.BatchJobStatusCountProjection;
|
||||
import com.spring.domain.schedule.entity.BatchJobExecution;
|
||||
|
||||
public interface BatchJobExecutionRepository extends JpaRepository<BatchJobExecution, Long> {
|
||||
|
||||
@Query("SELECT bji.jobName AS jobName, " +
|
||||
"bje.startTime AS startTime, " +
|
||||
"bje.endTime AS endTime " +
|
||||
"FROM BatchJobExecution bje " +
|
||||
"JOIN bje.jobInstance bji " +
|
||||
"WHERE bje.status = 'COMPLETED' AND bje.startTime >= :startDate " +
|
||||
"ORDER BY bje.startTime")
|
||||
List<BatchJobAverageDurationProjection> findJobAverageDurations(@Param("startDate") LocalDateTime startDate);
|
||||
|
||||
@Query("SELECT bje.status AS status, COUNT(bje) AS count " +
|
||||
"FROM BatchJobExecution bje " +
|
||||
"GROUP BY bje.status")
|
||||
List<BatchJobStatusCountProjection> findJobStatusCounts();
|
||||
|
||||
@Query("SELECT bje.startTime AS executionDate, " +
|
||||
"bji.jobName AS jobName, " +
|
||||
"COUNT(bje) AS executionCount " +
|
||||
"FROM BatchJobExecution bje " +
|
||||
"JOIN bje.jobInstance bji " +
|
||||
"WHERE bje.startTime >= :startDate " +
|
||||
"GROUP BY bje.startTime, bji.jobName " +
|
||||
"ORDER BY bje.startTime, bji.jobName")
|
||||
List<BatchJobExecutionProjection> findJobExecutionSummaryDays(@Param("startDate") LocalDateTime startDate);
|
||||
|
||||
@Query("SELECT bji.jobName AS jobName, " +
|
||||
"FUNCTION('HOUR', bje.startTime) AS hour, " +
|
||||
"COUNT(bje) AS count " +
|
||||
"FROM BatchJobExecution bje " +
|
||||
"JOIN bje.jobInstance bji " +
|
||||
"WHERE bje.startTime >= :startDate " +
|
||||
"GROUP BY bji.jobName, FUNCTION('HOUR', bje.startTime) " +
|
||||
"ORDER BY bji.jobName, hour")
|
||||
List<BatchJobHourProjection> findHourlyJobExecutionDistribution(@Param("startDate") LocalDateTime startDate);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package com.spring.domain.schedule.service;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.batch.core.BatchStatus;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.spring.domain.schedule.dto.BatchChartResponse;
|
||||
import com.spring.domain.schedule.dto.BatchJobAverageDurationProjection;
|
||||
import com.spring.domain.schedule.dto.BatchJobExecutionProjection;
|
||||
import com.spring.domain.schedule.dto.BatchJobHourProjection;
|
||||
import com.spring.domain.schedule.dto.BatchJobStatusCountProjection;
|
||||
import com.spring.domain.schedule.repository.BatchJobExecutionRepository;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class BatchChartService {
|
||||
|
||||
private final BatchJobExecutionRepository batchJobExecutionRepository;
|
||||
|
||||
public BatchChartResponse getBatchJobExecutionData() {
|
||||
return new BatchChartResponse(
|
||||
getJobAverageDurations(),
|
||||
getJobStatusCounts(),
|
||||
getHourlyJobExecutionDistribution(),
|
||||
getJobExecutionSummary()
|
||||
);
|
||||
}
|
||||
|
||||
private List<BatchJobAverageDurationProjection> getJobAverageDurations() {
|
||||
LocalDateTime startDate = LocalDateTime.now().minusDays(30);
|
||||
return batchJobExecutionRepository.findJobAverageDurations(startDate);
|
||||
}
|
||||
|
||||
private Map<BatchStatus, Long> getJobStatusCounts() {
|
||||
return batchJobExecutionRepository.findJobStatusCounts().stream()
|
||||
.collect(Collectors.toMap(
|
||||
BatchJobStatusCountProjection::getStatus,
|
||||
BatchJobStatusCountProjection::getCount
|
||||
));
|
||||
}
|
||||
|
||||
private List<BatchJobHourProjection> getHourlyJobExecutionDistribution() {
|
||||
LocalDateTime startDate = LocalDateTime.now().minusDays(30);
|
||||
return batchJobExecutionRepository.findHourlyJobExecutionDistribution(startDate);
|
||||
}
|
||||
|
||||
private List<BatchJobExecutionProjection> getJobExecutionSummary() {
|
||||
LocalDateTime startDate = LocalDateTime.now().minusDays(30);
|
||||
return batchJobExecutionRepository.findJobExecutionSummaryDays(startDate);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.spring.domain.schedule.service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.quartz.JobKey;
|
||||
import org.quartz.Scheduler;
|
||||
import org.quartz.SchedulerException;
|
||||
import org.quartz.Trigger;
|
||||
import org.quartz.impl.matchers.GroupMatcher;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.spring.common.error.BizBaseException;
|
||||
import com.spring.domain.schedule.dto.RecentJobResponse;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class DashBoardJobService {
|
||||
|
||||
private final Scheduler scheduler;
|
||||
|
||||
public List<RecentJobResponse> getRecentJobs() {
|
||||
List<RecentJobResponse> recentJobs = new ArrayList<>();
|
||||
try {
|
||||
for (String groupName : scheduler.getJobGroupNames()) {
|
||||
for (JobKey jobKey : scheduler.getJobKeys(GroupMatcher.jobGroupEquals(groupName))) {
|
||||
List<? extends Trigger> triggers = scheduler.getTriggersOfJob(jobKey);
|
||||
for (Trigger trigger : triggers) {
|
||||
recentJobs.add(new RecentJobResponse(
|
||||
jobKey.getName(),
|
||||
jobKey.getGroup(),
|
||||
trigger.getPreviousFireTime() != null ? trigger.getPreviousFireTime().getTime() : null,
|
||||
scheduler.getTriggerState(trigger.getKey()).name()
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (SchedulerException e) {
|
||||
throw new BizBaseException();
|
||||
}
|
||||
return recentJobs.stream()
|
||||
.filter(job -> job.getFiredTime() != null)
|
||||
.sorted((j1, j2) -> Long.compare(j2.getFiredTime(), j1.getFiredTime()))
|
||||
.limit(10)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package com.spring.domain.schedule.service;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.quartz.JobKey;
|
||||
import org.quartz.Scheduler;
|
||||
import org.quartz.SchedulerException;
|
||||
import org.quartz.impl.matchers.GroupMatcher;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class FindJobGroupService {
|
||||
|
||||
private final Scheduler scheduler;
|
||||
|
||||
public List<String> getJobGroups() {
|
||||
try {
|
||||
return scheduler.getJobGroupNames();
|
||||
} catch (SchedulerException e) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
public List<String> getJobNamesByGroup(String groupName) {
|
||||
try {
|
||||
return scheduler.getJobKeys(GroupMatcher.jobGroupEquals(groupName))
|
||||
.stream()
|
||||
.map(JobKey::getName)
|
||||
.collect(Collectors.toList());
|
||||
} catch (SchedulerException e) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -19,7 +19,9 @@ import org.quartz.Trigger;
|
||||
import org.quartz.impl.matchers.GroupMatcher;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.spring.common.error.BizBaseException;
|
||||
import com.spring.domain.schedule.dto.ScheduleJobResponse;
|
||||
import com.spring.domain.schedule.error.ScheduleNotFoundException;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@@ -29,7 +31,7 @@ public class FindScheduleJobService {
|
||||
|
||||
private final Scheduler scheduler;
|
||||
|
||||
public List<ScheduleJobResponse> getAllJobs(String groupName, String jobName) {
|
||||
public List<ScheduleJobResponse> getAllJobs(final String groupName, final String jobName) {
|
||||
try {
|
||||
return getFilteredJobKeys(groupName, jobName)
|
||||
.map(this::createScheduleSafely)
|
||||
@@ -39,19 +41,31 @@ public class FindScheduleJobService {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
public ScheduleJobResponse getJobDetail(final String groupName, final String jobName) {
|
||||
try {
|
||||
JobKey jobKey = new JobKey(jobName, groupName);
|
||||
if (scheduler.checkExists(jobKey)) {
|
||||
return createSchedule(jobKey);
|
||||
}
|
||||
throw new ScheduleNotFoundException();
|
||||
} catch (SchedulerException e) {
|
||||
throw new BizBaseException();
|
||||
}
|
||||
}
|
||||
|
||||
private Stream<JobKey> getFilteredJobKeys(String groupName, String jobName) throws SchedulerException {
|
||||
private Stream<JobKey> getFilteredJobKeys(final String groupName, final String jobName) throws SchedulerException {
|
||||
return scheduler.getJobGroupNames().stream()
|
||||
.filter(group -> isGroupMatched(group, groupName))
|
||||
.flatMap(this::getJobKeysForGroup)
|
||||
.filter(jobKey -> isJobMatched(jobKey, jobName));
|
||||
}
|
||||
|
||||
private boolean isGroupMatched(String group, String groupName) {
|
||||
private boolean isGroupMatched(final String group, final String groupName) {
|
||||
return groupName == null || groupName.isEmpty() || group.equals(groupName);
|
||||
}
|
||||
|
||||
private Stream<JobKey> getJobKeysForGroup(String group) {
|
||||
private Stream<JobKey> getJobKeysForGroup(final String group) {
|
||||
try {
|
||||
return scheduler.getJobKeys(GroupMatcher.jobGroupEquals(group)).stream();
|
||||
} catch (SchedulerException e) {
|
||||
@@ -59,11 +73,11 @@ public class FindScheduleJobService {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isJobMatched(JobKey jobKey, String jobName) {
|
||||
private boolean isJobMatched(final JobKey jobKey, final String jobName) {
|
||||
return jobName == null || jobName.isEmpty() || jobKey.getName().equals(jobName);
|
||||
}
|
||||
|
||||
private ScheduleJobResponse createScheduleSafely(JobKey jobKey) {
|
||||
private ScheduleJobResponse createScheduleSafely(final JobKey jobKey) {
|
||||
try {
|
||||
return createSchedule(jobKey);
|
||||
} catch (SchedulerException e) {
|
||||
@@ -71,10 +85,9 @@ public class FindScheduleJobService {
|
||||
}
|
||||
}
|
||||
|
||||
private ScheduleJobResponse createSchedule(JobKey jobKey) throws SchedulerException {
|
||||
private ScheduleJobResponse createSchedule(final JobKey jobKey) throws SchedulerException {
|
||||
JobDetail jobDetail = scheduler.getJobDetail(jobKey);
|
||||
Optional<Trigger> trigger = Optional.ofNullable(scheduler.getTriggersOfJob(jobKey).stream().findFirst().orElse(null));
|
||||
|
||||
return new ScheduleJobResponse(
|
||||
jobKey.getName(),
|
||||
jobKey.getGroup(),
|
||||
@@ -83,23 +96,23 @@ public class FindScheduleJobService {
|
||||
getTriggerState(trigger),
|
||||
jobDetail.getJobClass().getName(),
|
||||
jobDetail.getJobDataMap().toString(),
|
||||
trigger.map(t -> t.getClass().getSimpleName()).orElse("UNKNOWN"),
|
||||
trigger.map(t -> t.getClass().getSimpleName()).orElse(""),
|
||||
trigger.map(Trigger::getNextFireTime).map(this::convertToLocalDateTime).orElse(null),
|
||||
trigger.map(Trigger::getPreviousFireTime).map(this::convertToLocalDateTime).orElse(null)
|
||||
);
|
||||
}
|
||||
|
||||
private String getTriggerState(Optional<Trigger> trigger) {
|
||||
private String getTriggerState(final Optional<Trigger> trigger) {
|
||||
return trigger.map(t -> {
|
||||
try {
|
||||
return scheduler.getTriggerState(t.getKey()).name();
|
||||
} catch (SchedulerException e) {
|
||||
return "UNKNOWN";
|
||||
return "";
|
||||
}
|
||||
}).orElse("UNKNOWN");
|
||||
}).orElse("");
|
||||
}
|
||||
|
||||
private String getCronExpression(Optional<Trigger> trigger) {
|
||||
private String getCronExpression(final Optional<Trigger> trigger) {
|
||||
return trigger.map(t -> {
|
||||
if (t instanceof CronTrigger) {
|
||||
return ((CronTrigger) t).getCronExpression();
|
||||
@@ -108,7 +121,7 @@ public class FindScheduleJobService {
|
||||
}).orElse("N/A");
|
||||
}
|
||||
|
||||
private LocalDateTime convertToLocalDateTime(Date date) {
|
||||
private LocalDateTime convertToLocalDateTime(final Date date) {
|
||||
if (date == null) return null;
|
||||
return date.toInstant()
|
||||
.atZone(ZoneId.systemDefault())
|
||||
|
||||
@@ -77,7 +77,7 @@ public class QuartzConfig {
|
||||
factory.setDataSource(dataSource);
|
||||
factory.setTransactionManager(transactionManager);
|
||||
factory.setJobFactory(jobFactory);
|
||||
factory.setAutoStartup(false);
|
||||
factory.setAutoStartup(true);
|
||||
factory.setWaitForJobsToCompleteOnShutdown(true);
|
||||
return factory;
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ public class SigninSuccessHandler implements AuthenticationSuccessHandler {
|
||||
jwtTokenService.generateAccessToken(response, authentication);
|
||||
jwtTokenService.generateRefreshToken(response, authentication);
|
||||
SavedRequest savedRequest = requestCache.getRequest(request, response);
|
||||
String targetUrl = (savedRequest != null) ? savedRequest.getRedirectUrl() : "/main";
|
||||
String targetUrl = (savedRequest != null) ? savedRequest.getRedirectUrl() : "/dashboard";
|
||||
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
||||
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
|
||||
Map<String, Object> responseBody = Map.of("status", true, "redirectUrl", targetUrl);
|
||||
|
||||
@@ -5,12 +5,12 @@ import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
@Controller
|
||||
@RequestMapping("/main")
|
||||
public class MainController {
|
||||
@RequestMapping("/dashboard")
|
||||
public class DashBoardController {
|
||||
|
||||
@GetMapping
|
||||
public String main() {
|
||||
return "pages/main/main";
|
||||
}
|
||||
public String dashboard() {
|
||||
return "pages/dashboard/dashboard";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -10,6 +10,7 @@ public class ScheduleController {
|
||||
|
||||
@GetMapping
|
||||
public String schedule() {
|
||||
return "pages/schedule/schedule-list";
|
||||
return "pages/schedule/schedule";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -23,9 +23,4 @@ public class SignController {
|
||||
return "pages/sign/sign-in";
|
||||
}
|
||||
|
||||
@GetMapping("/sign-up")
|
||||
public String signUp() {
|
||||
return "pages/sign/sign-up";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ spring:
|
||||
# init:
|
||||
# mode: always
|
||||
# schema-locations:
|
||||
# - classpath:batch-schema.sql
|
||||
# - classpath:sql-schema/test-schema.sql
|
||||
# - classpath:quartz-schema.sql
|
||||
|
||||
jpa:
|
||||
@@ -92,7 +92,7 @@ spring:
|
||||
jwt:
|
||||
access-token:
|
||||
secret: bnhjdXMyLjAtcGxhdGZvcm0tcHJvamVjdC13aXRoLXNwcmluZy1ib290bnhjdF9zdHJvbmdfY29tcGxleF9zdHJvbmc=
|
||||
expiration: 1
|
||||
expiration: 15
|
||||
refresh-token:
|
||||
secret: bnhjdXMyLjAtcGxhdGZvcm0tcHJvamVjdC13aXRoLXNwcmluZy1ib290bnhjdF9zdHJvbmdfY29tcGxleF9zdHJvbmc=
|
||||
expiration: 10080
|
||||
|
||||
@@ -685,7 +685,6 @@ h6 {
|
||||
font-weight: 600;
|
||||
color: #4154f1;
|
||||
transition: 0.3;
|
||||
background: #f6f9ff;
|
||||
padding: 10px 15px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import apiClient from '../common/axios-instance.js';
|
||||
|
||||
export const getBatchJobExecutionData = async () => {
|
||||
const response = await apiClient.get('/api/dashboard/chart/batch');
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export const getRecentJobs = async () => {
|
||||
const response = await apiClient.get('/api/dashboard/recent-job');
|
||||
return response.data;
|
||||
}
|
||||
@@ -5,13 +5,8 @@ export const getAllJobs = async (searchParams) => {
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export const getJobGroups = async () => {
|
||||
const response = await apiClient.get('/api/schedule/groups');
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export const getJobNamesByGroup = async (groupName) => {
|
||||
const response = await apiClient.get(`/api/schedule/group/${groupName}/names`);
|
||||
export const getJobDetail = async (groupName, jobName) => {
|
||||
const response = await apiClient.get(`/api/schedule/${groupName}/${jobName}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,9 +2,10 @@ import apiClient from '../common/axios-instance.js';
|
||||
|
||||
export const signIn = async (username, password) => {
|
||||
const response = await apiClient.post('/sign-in', {username, password});
|
||||
return response.data;
|
||||
return response;
|
||||
};
|
||||
|
||||
export const signUp = async (loginId, password, userName) => {
|
||||
await apiClient.post('/api/user/sign-up', {loginId, password, userName});
|
||||
const response = await apiClient.post('/api/user/sign-up', {loginId, password, userName});
|
||||
return response.data;
|
||||
};
|
||||
@@ -20,16 +20,26 @@ apiClient.interceptors.request.use(
|
||||
// 응답 인터셉터 추가
|
||||
apiClient.interceptors.response.use(
|
||||
(response) => {
|
||||
return response;
|
||||
return response.data;
|
||||
},
|
||||
async (error) => {
|
||||
if (error.response) {
|
||||
const status = error.response.status;
|
||||
const message = error.response.data.message;
|
||||
if (message) {
|
||||
alert(message);
|
||||
const responseData = error.response.data;
|
||||
let alertMessage = responseData.message;
|
||||
|
||||
if (responseData.data && Array.isArray(responseData.data) && responseData.data.length > 0) {
|
||||
const firstError = responseData.data[0];
|
||||
if (firstError.reason) {
|
||||
alertMessage = firstError.reason;
|
||||
}
|
||||
}
|
||||
if (status == 401) {
|
||||
|
||||
if (alertMessage) {
|
||||
alert(alertMessage);
|
||||
}
|
||||
|
||||
if (status === 401) {
|
||||
location.href = "/";
|
||||
}
|
||||
return new Promise(() => {});
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
dayjs.locale('ko');
|
||||
7
batch-quartz/src/main/resources/static/js/lib/bootstrap/bootstrap.bundle.min.js
vendored
Normal file
7
batch-quartz/src/main/resources/static/js/lib/bootstrap/bootstrap.bundle.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
7
batch-quartz/src/main/resources/static/js/lib/bootstrap/bootstrap.min.js
vendored
Normal file
7
batch-quartz/src/main/resources/static/js/lib/bootstrap/bootstrap.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
7
batch-quartz/src/main/resources/static/js/lib/bootstrap/chartjs-adapter-luxon.umd.min.js
vendored
Normal file
7
batch-quartz/src/main/resources/static/js/lib/bootstrap/chartjs-adapter-luxon.umd.min.js
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
/*!
|
||||
* chartjs-adapter-luxon v1.3.1
|
||||
* https://www.chartjs.org
|
||||
* (c) 2023 chartjs-adapter-luxon Contributors
|
||||
* Released under the MIT license
|
||||
*/
|
||||
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(require("chart.js"),require("luxon")):"function"==typeof define&&define.amd?define(["chart.js","luxon"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).Chart,e.luxon)}(this,(function(e,t){"use strict";const n={datetime:t.DateTime.DATETIME_MED_WITH_SECONDS,millisecond:"h:mm:ss.SSS a",second:t.DateTime.TIME_WITH_SECONDS,minute:t.DateTime.TIME_SIMPLE,hour:{hour:"numeric"},day:{day:"numeric",month:"short"},week:"DD",month:{month:"short",year:"numeric"},quarter:"'Q'q - yyyy",year:{year:"numeric"}};e._adapters._date.override({_id:"luxon",_create:function(e){return t.DateTime.fromMillis(e,this.options)},init(e){this.options.locale||(this.options.locale=e.locale)},formats:function(){return n},parse:function(e,n){const i=this.options,r=typeof e;return null===e||"undefined"===r?null:("number"===r?e=this._create(e):"string"===r?e="string"==typeof n?t.DateTime.fromFormat(e,n,i):t.DateTime.fromISO(e,i):e instanceof Date?e=t.DateTime.fromJSDate(e,i):"object"!==r||e instanceof t.DateTime||(e=t.DateTime.fromObject(e,i)),e.isValid?e.valueOf():null)},format:function(e,t){const n=this._create(e);return"string"==typeof t?n.toFormat(t):n.toLocaleString(t)},add:function(e,t,n){const i={};return i[n]=t,this._create(e).plus(i).valueOf()},diff:function(e,t,n){return this._create(e).diff(this._create(t)).as(n).valueOf()},startOf:function(e,t,n){if("isoWeek"===t){n=Math.trunc(Math.min(Math.max(0,n),6));const t=this._create(e);return t.minus({days:(t.weekday-n+7)%7}).startOf("day").valueOf()}return t?this._create(e).startOf(t).valueOf():e},endOf:function(e,t){return this._create(e).endOf(t).valueOf()}})}));
|
||||
1
batch-quartz/src/main/resources/static/js/lib/bootstrap/luxon.min.js
vendored
Normal file
1
batch-quartz/src/main/resources/static/js/lib/bootstrap/luxon.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
6
batch-quartz/src/main/resources/static/js/lib/bootstrap/popper.min.js
vendored
Normal file
6
batch-quartz/src/main/resources/static/js/lib/bootstrap/popper.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
batch-quartz/src/main/resources/static/js/lib/dayjs/dayjs.min.js
vendored
Normal file
1
batch-quartz/src/main/resources/static/js/lib/dayjs/dayjs.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
!function(e,_){"object"==typeof exports&&"undefined"!=typeof module?module.exports=_(require("dayjs")):"function"==typeof define&&define.amd?define(["dayjs"],_):(e="undefined"!=typeof globalThis?globalThis:e||self).dayjs_locale_ko=_(e.dayjs)}(this,(function(e){"use strict";function _(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var d=_(e),t={name:"ko",weekdays:"일요일_월요일_화요일_수요일_목요일_금요일_토요일".split("_"),weekdaysShort:"일_월_화_수_목_금_토".split("_"),weekdaysMin:"일_월_화_수_목_금_토".split("_"),months:"1월_2월_3월_4월_5월_6월_7월_8월_9월_10월_11월_12월".split("_"),monthsShort:"1월_2월_3월_4월_5월_6월_7월_8월_9월_10월_11월_12월".split("_"),ordinal:function(e){return e+"일"},formats:{LT:"A h:mm",LTS:"A h:mm:ss",L:"YYYY.MM.DD.",LL:"YYYY년 MMMM D일",LLL:"YYYY년 MMMM D일 A h:mm",LLLL:"YYYY년 MMMM D일 dddd A h:mm",l:"YYYY.MM.DD.",ll:"YYYY년 MMMM D일",lll:"YYYY년 MMMM D일 A h:mm",llll:"YYYY년 MMMM D일 dddd A h:mm"},meridiem:function(e){return e<12?"오전":"오후"},relativeTime:{future:"%s 후",past:"%s 전",s:"몇 초",m:"1분",mm:"%d분",h:"한 시간",hh:"%d시간",d:"하루",dd:"%d일",M:"한 달",MM:"%d달",y:"일 년",yy:"%d년"}};return d.default.locale(t,null,!0),t}));
|
||||
@@ -0,0 +1,269 @@
|
||||
import { getBatchJobExecutionData, getRecentJobs } from '../../apis/dashboard-api.js';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
fetchDataAndRender();
|
||||
});
|
||||
|
||||
const fetchDataAndRender = async () => {
|
||||
const batchData = await getBatchJobExecutionData();
|
||||
const recentJobs = await getRecentJobs();
|
||||
|
||||
renderBatchExecutionTimeChart(batchData.jobAvgSummary);
|
||||
renderBatchStatusChart(batchData.statusCounts);
|
||||
renderHourlyJobExecutionChart(batchData.jobHourSummary);
|
||||
renderDailyJobExecutionsChart(batchData.jobExecutionSummary);
|
||||
renderRecentJobsTable(recentJobs);
|
||||
};
|
||||
|
||||
const chartOptions = {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: {
|
||||
position: 'top',
|
||||
},
|
||||
title: {
|
||||
font: {
|
||||
size: 16
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let batchExecutionTimeChart;
|
||||
const renderBatchExecutionTimeChart = (data) => {
|
||||
const jobExecutionTimes = {};
|
||||
data.forEach(job => {
|
||||
if (job.endTime && job.startTime) {
|
||||
const duration = (new Date(job.endTime) - new Date(job.startTime)) / 1000; // 초 단위
|
||||
if (!jobExecutionTimes[job.jobName]) {
|
||||
jobExecutionTimes[job.jobName] = { total: 0, count: 0 };
|
||||
}
|
||||
jobExecutionTimes[job.jobName].total += duration;
|
||||
jobExecutionTimes[job.jobName].count++;
|
||||
}
|
||||
});
|
||||
|
||||
const averageExecutionTimes = Object.entries(jobExecutionTimes).reduce((acc, [jobName, job]) => {
|
||||
acc[jobName] = job.total / job.count;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
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(75, 192, 192, 0.6)',
|
||||
borderColor: 'rgba(75, 192, 192, 1)',
|
||||
borderWidth: 1
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
...chartOptions,
|
||||
plugins: {
|
||||
...chartOptions.plugins,
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
label: (context) => {
|
||||
const label = context.dataset.label || '';
|
||||
const value = context.parsed.y.toFixed(2);
|
||||
return `${label}: ${value} 초`;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
title: {
|
||||
display: true,
|
||||
text: '시간 (초)'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
let batchStatusChart;
|
||||
const renderBatchStatusChart = (data) => {
|
||||
const ctx = document.getElementById('batchStatusChart').getContext('2d');
|
||||
batchStatusChart = new Chart(ctx, {
|
||||
type: 'pie',
|
||||
data: {
|
||||
labels: Object.keys(data),
|
||||
datasets: [{
|
||||
data: Object.values(data),
|
||||
backgroundColor: [
|
||||
'rgba(75, 192, 192, 0.6)',
|
||||
'rgba(255, 99, 132, 0.6)',
|
||||
'rgba(255, 206, 86, 0.6)',
|
||||
'rgba(54, 162, 235, 0.6)'
|
||||
]
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
...chartOptions,
|
||||
plugins: {
|
||||
...chartOptions.plugins
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
let hourlyJobExecutionChart;
|
||||
const renderHourlyJobExecutionChart = (data) => {
|
||||
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 => {
|
||||
const jobData = hours.map(hour => {
|
||||
const hourData = data.find(item => item.jobName === jobName && item.hour === hour);
|
||||
return hourData ? hourData.count : 0;
|
||||
});
|
||||
|
||||
return {
|
||||
label: jobName,
|
||||
data: jobData,
|
||||
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: 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) => {
|
||||
const ctx = document.getElementById('dailyJobExecutionsChart').getContext('2d');
|
||||
const now = new Date();
|
||||
const firstDay = new Date(now.getFullYear(), now.getMonth(), 1);
|
||||
const lastDay = new Date(now.getFullYear(), now.getMonth() + 1, 0);
|
||||
const dates = [];
|
||||
for (let d = new Date(firstDay); d <= lastDay; d.setDate(d.getDate() + 1)) {
|
||||
dates.push(d.toISOString().split('T')[0]);
|
||||
}
|
||||
|
||||
const groupedData = data.reduce((acc, item) => {
|
||||
const date = item.executionDate.split('T')[0];
|
||||
if (!acc[date]) {
|
||||
acc[date] = {};
|
||||
}
|
||||
if (!acc[date][item.jobName]) {
|
||||
acc[date][item.jobName] = 0;
|
||||
}
|
||||
acc[date][item.jobName] += 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] || null
|
||||
})),
|
||||
borderColor: color,
|
||||
backgroundColor: color,
|
||||
fill: false,
|
||||
spanGaps: true
|
||||
};
|
||||
});
|
||||
|
||||
dailyJobExecutionsChart = new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: {
|
||||
datasets: datasets
|
||||
},
|
||||
options: {
|
||||
...chartOptions,
|
||||
plugins: {
|
||||
...chartOptions.plugins
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
type: 'time',
|
||||
time: {
|
||||
unit: 'day',
|
||||
displayFormats: {
|
||||
day: 'MM-dd'
|
||||
},
|
||||
tooltipFormat: 'yyyy-MM-dd'
|
||||
},
|
||||
title: {
|
||||
display: true,
|
||||
text: '날짜'
|
||||
}
|
||||
},
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
title: {
|
||||
display: true,
|
||||
text: '실행 횟수'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const renderRecentJobsTable = (recentJobs) => {
|
||||
const tableBody = document.getElementById('recentJobsTable');
|
||||
tableBody.innerHTML = recentJobs.map(job => `
|
||||
<tr>
|
||||
<td>${job.jobName}</td>
|
||||
<td>${job.jobGroup}</td>
|
||||
<td>${new Date(job.firedTime).toLocaleString()}</td>
|
||||
<td>${job.state}</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
};
|
||||
|
||||
const getRandomColor = () => {
|
||||
const r = Math.floor(Math.random() * 255);
|
||||
const g = Math.floor(Math.random() * 255);
|
||||
const b = Math.floor(Math.random() * 255);
|
||||
return `rgb(${r}, ${g}, ${b})`;
|
||||
};
|
||||
@@ -1,65 +0,0 @@
|
||||
import {getAllJobs} from '../../apis/schedule-api.js';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const searchForm = document.getElementById('searchForm');
|
||||
const tableBody = document.querySelector('tbody');
|
||||
|
||||
searchForm.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const formData = new FormData(searchForm);
|
||||
const searchParams = new URLSearchParams(formData);
|
||||
const response = await getAllJobs(searchParams);
|
||||
updateTable(response.data);
|
||||
});
|
||||
|
||||
function updateTable(jobs) {
|
||||
tableBody.innerHTML = '';
|
||||
jobs.forEach(job => {
|
||||
const row = `
|
||||
<tr>
|
||||
<td>${job.group}</td>
|
||||
<td>${job.name}</td>
|
||||
<td>${job.description || '-'}</td>
|
||||
<td>${job.cronExpression}</td>
|
||||
<td>${formatDateTime(job.nextFireTime)}</td>
|
||||
<td>${formatDateTime(job.previousFireTime)}</td>
|
||||
<td class="text-center">
|
||||
<span class="badge ${getStatusBadgeClass(job.status)} w-100 py-2">${job.status}</span>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<span class="badge bg-secondary w-100 py-2"><i class="bi bi-eye"></i> 상세</span>
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
tableBody.insertAdjacentHTML('beforeend', row);
|
||||
});
|
||||
}
|
||||
|
||||
function formatDateTime(dateTimeString) {
|
||||
if (!dateTimeString) return '-';
|
||||
const date = new Date(dateTimeString);
|
||||
return date.toLocaleString('ko-KR', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
});
|
||||
}
|
||||
|
||||
function getStatusBadgeClass(status) {
|
||||
switch (status) {
|
||||
case 'NORMAL':
|
||||
return 'bg-success';
|
||||
case 'PAUSED':
|
||||
return 'bg-warning';
|
||||
case 'COMPLETE':
|
||||
return 'bg-info';
|
||||
case 'ERROR':
|
||||
return 'bg-danger';
|
||||
default:
|
||||
return 'bg-secondary';
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,90 @@
|
||||
import { getAllJobs, getJobDetail, triggerJob, pauseJob, resumeJob } from '../../apis/schedule-api.js';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const searchForm = document.getElementById('searchForm');
|
||||
const tableBody = document.querySelector('tbody');
|
||||
|
||||
searchForm.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const formData = new FormData(searchForm);
|
||||
const searchParams = new URLSearchParams(formData);
|
||||
const response = await getAllJobs(searchParams);
|
||||
updateTable(response);
|
||||
});
|
||||
|
||||
const updateTable = (jobs) => {
|
||||
tableBody.innerHTML = jobs.map(job => `
|
||||
<tr>
|
||||
<td>${job.group}</td>
|
||||
<td>${job.name}</td>
|
||||
<td>${job.cronExpression}</td>
|
||||
<td><span class="badge ${getStatusBadgeClass(job.status)}">${job.status}</span></td>
|
||||
<td>
|
||||
<button class="badge btn btn-sm btn-secondary detail-btn" data-group="${job.group}" data-name="${job.name}">
|
||||
<i class="bi bi-eye"></i> 상세
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
|
||||
document.querySelectorAll('.detail-btn').forEach(btn => {
|
||||
btn.addEventListener('click', showJobDetail);
|
||||
});
|
||||
};
|
||||
|
||||
const showJobDetail = async (e) => {
|
||||
const { group, name } = e.target.closest('button').dataset;
|
||||
const jobDetail = await getJobDetail(group, name);
|
||||
const detailContent = document.getElementById('scheduleDetailContent');
|
||||
detailContent.innerHTML = `
|
||||
<div class="card">
|
||||
<ul class="list-group list-group-flush">
|
||||
${createDetailItem('그룹', jobDetail.group, 'bi-people')}
|
||||
${createDetailItem('잡 이름', jobDetail.name, 'bi-briefcase')}
|
||||
${createDetailItem('설명', jobDetail.description || '-', 'bi-card-text')}
|
||||
${createDetailItem('스케줄', `<input type="text" class="form-control form-control-sm" id="cronExpression" value="${jobDetail.cronExpression}">`, 'bi-calendar-event')}
|
||||
${createDetailItem('다음 실행', formatDateTime(jobDetail.nextFireTime), 'bi-clock')}
|
||||
${createDetailItem('이전 실행', formatDateTime(jobDetail.previousFireTime), 'bi-clock-history')}
|
||||
${createDetailItem('상태', `<span class="badge ${getStatusBadgeClass(jobDetail.status)}">${jobDetail.status}</span>`, 'bi-activity')}
|
||||
</ul>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const modal = new bootstrap.Modal(document.getElementById('scheduleDetailModal'));
|
||||
modal.show();
|
||||
|
||||
document.getElementById('startJobBtn').onclick = () => triggerJob(group, name);
|
||||
document.getElementById('pauseJobBtn').onclick = () => pauseJob(group, name);
|
||||
document.getElementById('resumeJobBtn').onclick = () => resumeJob(group, name);
|
||||
};
|
||||
|
||||
const createDetailItem = (label, value, iconClass) => `
|
||||
<li class="list-group-item">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-4">
|
||||
<i class="bi ${iconClass} text-primary me-2"></i>
|
||||
<strong>${label}</strong>
|
||||
</div>
|
||||
<div class="col-8">
|
||||
${value}
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
`;
|
||||
|
||||
const formatDateTime = (dateTimeString) => {
|
||||
if (!dateTimeString) return '-';
|
||||
const date = new Date(dateTimeString);
|
||||
return dayjs(date).format("YYYY-MM-DD ddd A HH:mm:ss");
|
||||
};
|
||||
|
||||
const getStatusBadgeClass = (status) => {
|
||||
const statusClasses = {
|
||||
'NORMAL': 'bg-success',
|
||||
'PAUSED': 'bg-warning',
|
||||
'COMPLETE': 'bg-info',
|
||||
'ERROR': 'bg-danger'
|
||||
};
|
||||
return statusClasses[status] || 'bg-secondary';
|
||||
};
|
||||
});
|
||||
@@ -1,13 +1,37 @@
|
||||
import {signIn} from '../../apis/sign-api.js';
|
||||
import {signIn, signUp} from '../../apis/sign-api.js';
|
||||
|
||||
document.getElementById('signinForm').addEventListener('submit', function(event) {
|
||||
event.preventDefault();
|
||||
const username = document.getElementById('username').value;
|
||||
const password = document.getElementById('password').value;
|
||||
signIn(username, password)
|
||||
.then(response => {
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
const signInButton = document.getElementById('signIn');
|
||||
const signupButton = document.getElementById('signUp');
|
||||
const signupModal = new bootstrap.Modal(document.getElementById('signupModal'));
|
||||
const signupSubmit = document.getElementById('signupSubmit');
|
||||
|
||||
signInButton.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
const username = document.getElementById('username').value;
|
||||
const password = document.getElementById('password').value;
|
||||
signIn(username, password).then(response => {
|
||||
if (response.status) {
|
||||
window.location.href = response.redirectUrl;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
signupButton.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
signupModal.show();
|
||||
});
|
||||
|
||||
signupSubmit.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
const loginId = document.getElementById('loginId').value;
|
||||
const password = document.getElementById('loginPassword').value;
|
||||
const userName = document.getElementById('userName').value;
|
||||
signUp(loginId, password, userName).then(response => {
|
||||
alert(`회원가입이 완료 되었습니다.`);
|
||||
signupModal.hide();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
@@ -1,9 +0,0 @@
|
||||
import {signUp} from '../../apis/sign-api.js';
|
||||
|
||||
document.getElementById('signupForm').addEventListener('submit', function(event) {
|
||||
event.preventDefault();
|
||||
const loginId = document.getElementById('loginId').value;
|
||||
const password = document.getElementById('password').value;
|
||||
const userName = document.getElementById('userName').value;
|
||||
signUp(loginId, password, userName);
|
||||
});
|
||||
@@ -9,4 +9,13 @@
|
||||
</script>
|
||||
<script th:src="@{/js/lib/axios/axios.min.js}"></script>
|
||||
<script th:src="@{/js/lib/cookie/js.cookie.min.js}"></script>
|
||||
<script th:src="@{/js/lib/bootstrap/popper.min.js}"></script>
|
||||
<script th:src="@{/js/lib/bootstrap/bootstrap.min.js}"></script>
|
||||
<script th:src="@{/js/lib/bootstrap/bootstrap.bundle.min.js}"></script>
|
||||
<script th:src="@{/js/lib/bootstrap/chart.js}"></script>
|
||||
<script th:src="@{/js/lib/bootstrap/luxon.min.js}"></script>
|
||||
<script th:src="@{/js/lib/bootstrap/chartjs-adapter-luxon.umd.min.js}"></script>
|
||||
<script th:src="@{/js/lib/dayjs/dayjs.min.js}"></script>
|
||||
<script th:src="@{/js/lib/dayjs/locale/ko.js}"></script>
|
||||
<script th:src="@{/js/common/common.js}"></script>
|
||||
</html>
|
||||
@@ -1,6 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns:th="http://www.thymeleaf.org" th:fragment="footer" lang="ko" xml:lang="ko">
|
||||
<footer class="bg-light text-center p-3">
|
||||
<p>회사 정보: © 2023 회사명. 모든 권리 보유.</p>
|
||||
</footer>
|
||||
</html>
|
||||
@@ -3,42 +3,13 @@
|
||||
<aside id="sidebar" class="sidebar">
|
||||
<ul class="sidebar-nav" id="sidebar-nav">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link " href="index.html"><i class="bi bi-grid"></i><span>Dashboard</span></a>
|
||||
<a class="nav-link" href="/dashboard"><i class="bi bi-grid"></i><span>Dashboard</span></a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link " href="index.html"><i class="bi bi-grid"></i><span>Quartz Job</span></a>
|
||||
<a class="nav-link" href="/schedule"><i class="bi bi-menu-button-wide"></i><span>Schedule</span></a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link " href="index.html"><i class="bi bi-grid"></i><span>CronTrigger</span></a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link " href="index.html"><i class="bi bi-grid"></i><span>SimpleTrigger</span></a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link collapsed" data-bs-target="#components-nav" data-bs-toggle="collapse" href="#">
|
||||
<i class="bi bi-menu-button-wide"></i><span>Schedule</span><i class="bi bi-chevron-down ms-auto"></i>
|
||||
</a>
|
||||
<ul id="components-nav" class="nav-content collapse " data-bs-parent="#sidebar-nav">
|
||||
<li>
|
||||
<a href="components-alerts.html"><i class="bi bi-circle"></i><span>Alerts</span></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="components-accordion.html"><i class="bi bi-circle"></i><span>Accordion</span></a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link collapsed" data-bs-target="#charts-nav" data-bs-toggle="collapse" href="#">
|
||||
<i class="bi bi-bar-chart"></i><span>Charts</span><i class="bi bi-chevron-down ms-auto"></i>
|
||||
</a>
|
||||
<ul id="charts-nav" class="nav-content collapse " data-bs-parent="#sidebar-nav">
|
||||
<li>
|
||||
<a href="charts-chartjs.html"><i class="bi bi-circle"></i><span>Chart.js</span></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="charts-apexcharts.html"><i class="bi bi-circle"></i><span>ApexCharts</span></a>
|
||||
</li>
|
||||
</ul>
|
||||
<a class="nav-link" href="index.html"><i class="bi bi-bar-chart"></i><span>Charts</span></a>
|
||||
</li>
|
||||
</ul>
|
||||
</aside>
|
||||
|
||||
@@ -5,6 +5,5 @@
|
||||
<head th:replace="fragments/config :: config"/>
|
||||
<body>
|
||||
<section layout:fragment="content"></section>
|
||||
<footer th:replace="fragments/footer::footer"></footer>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,98 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns:th="http://www.thymeleaf.org"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||
layout:decorate="~{layouts/layout}"
|
||||
layout:fragment="content" lang="ko" xml:lang="ko">
|
||||
<head>
|
||||
<title>DashBoard</title>
|
||||
</head>
|
||||
<body>
|
||||
<main id="main" class="main">
|
||||
<div class="pagetitle">
|
||||
<div class="row align-items-center">
|
||||
<div class="col">
|
||||
<h1><i class="bi bi-speedometer2"></i> 대시보드</h1>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<nav>
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="index.html">홈</a></li>
|
||||
<li class="breadcrumb-item active">대시보드</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<section class="section">
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-4">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<i class="bi bi-bar-chart-line me-2"></i>작업별 평균 실행 시간
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<canvas id="batchExecutionTimeChart" style="height: 250px;"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 mb-4">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<i class="bi bi-pie-chart-fill me-2"></i>작업 상태 분포
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<canvas id="batchStatusChart" style="height: 250px;"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-4">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<i class="bi bi-graph-up me-2"></i>시간대별 작업 실행 분포
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<canvas id="hourlyJobExecutionChart" style="height: 250px;"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 mb-4">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<i class="bi bi-clock-history me-2"></i>일별 작업 실행 횟수
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<canvas id="dailyJobExecutionsChart" style="height: 250px;"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<i class="bi bi-clock-history me-2"></i>최근 실행된 작업
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><i class="bi bi-briefcase me-2"></i>작업 이름</th>
|
||||
<th><i class="bi bi-folder me-2"></i>그룹</th>
|
||||
<th><i class="bi bi-calendar-event me-2"></i>실행 시간</th>
|
||||
<th><i class="bi bi-flag me-2"></i>상태</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="recentJobsTable">
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script type="module" th:src="@{/js/pages/dashboard/dashboard.js}" defer></script>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,11 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns:th="http://www.thymeleaf.org"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||
layout:decorate="~{layouts/layout}"
|
||||
layout:fragment="content" lang="ko" xml:lang="ko">
|
||||
|
||||
<div>
|
||||
본문 영역입니다.
|
||||
</div>
|
||||
|
||||
</html>
|
||||
@@ -11,7 +11,7 @@
|
||||
<div class="pagetitle">
|
||||
<div class="row align-items-center">
|
||||
<div class="col">
|
||||
<h1><i class="bi bi-calendar3"></i> 스케줄 목록</h1>
|
||||
<h1><i class="bi bi-calendar3"></i> 스케줄</h1>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<nav>
|
||||
@@ -23,7 +23,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="section">
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
@@ -53,24 +52,20 @@
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">
|
||||
<i class="bi bi-list-ul"></i> 스케줄 목록
|
||||
</h5>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-hover">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="col-1 text-nowrap"><i class="bi bi-people"></i> 그룹</th>
|
||||
<th class="col-1 text-nowrap"><i class="bi bi-briefcase"></i> 잡 이름</th>
|
||||
<th class="col-2 text-nowrap"><i class="bi bi-info-circle"></i> 설명</th>
|
||||
<th class="col-2 text-nowrap"><i class="bi bi-calendar-event"></i> 스케줄</th>
|
||||
<th class="col-2 text-nowrap"><i class="bi bi-clock"></i> 다음 실행</th>
|
||||
<th class="col-2 text-nowrap"><i class="bi bi-clock-history"></i> 이전 실행</th>
|
||||
<th class="col-1 text-nowrap"><i class="bi bi-calendar-event"></i> 스케줄</th>
|
||||
<th class="col-1 text-nowrap"><i class="bi bi-activity"></i> 상태</th>
|
||||
<th class="col-2 text-nowrap"><i class="bi bi-gear"></i> 액션</th>
|
||||
<th class="col-1 text-nowrap"><i class="bi bi-gear"></i> 액션</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -82,7 +77,45 @@
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<script type="module" th:src="@{/js/pages/schedule/schedule-list.js}" defer></script>
|
||||
|
||||
<div class="modal fade" id="scheduleDetailModal" tabindex="-1" aria-labelledby="scheduleDetailModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-scrollable modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header bg-dark text-white">
|
||||
<h5 class="modal-title" id="scheduleDetailModalLabel">
|
||||
<i class="bi bi-info-circle-fill me-2"></i>스케줄 정보
|
||||
</h5>
|
||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body" id="scheduleDetailContent">
|
||||
</div>
|
||||
<div class="modal-footer justify-content-center">
|
||||
<fieldset>
|
||||
<legend class="visually-hidden">작업 제어 버튼</legend>
|
||||
<div class="d-flex justify-content-between" style="width: 300px;">
|
||||
<button type="button" class="btn btn-outline-success rounded-circle" id="startJobBtn" data-bs-toggle="tooltip" data-bs-placement="top" title="시작">
|
||||
<i class="bi bi-play-fill"></i>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-danger rounded-circle" id="pauseJobBtn" data-bs-toggle="tooltip" data-bs-placement="top" title="정지">
|
||||
<i class="bi bi-pause-fill"></i>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-info rounded-circle" id="resumeJobBtn" data-bs-toggle="tooltip" data-bs-placement="top" title="재시작">
|
||||
<i class="bi bi-arrow-clockwise"></i>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-primary rounded-circle" id="updateCronBtn" data-bs-toggle="tooltip" data-bs-placement="top" title="저장">
|
||||
<i class="bi bi-save"></i>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary rounded-circle" data-bs-dismiss="modal" data-bs-toggle="tooltip" data-bs-placement="top" title="닫기">
|
||||
<i class="bi bi-x-lg"></i>
|
||||
</button>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="module" th:src="@{/js/pages/schedule/schedule.js}" defer></script>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
@@ -8,27 +8,86 @@
|
||||
<body>
|
||||
<section layout:fragment="content">
|
||||
<div class="container">
|
||||
<div class="icon">
|
||||
<img src="/images/user.png" alt="User Icon">
|
||||
<div class="row justify-content-center align-items-center min-vh-100">
|
||||
<div class="col-lg-4 col-md-6">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-body p-5">
|
||||
<div class="text-center mb-4">
|
||||
<i class="bi bi-person-circle text-primary" style="font-size: 3rem;"></i>
|
||||
<h2 class="card-title mt-3">로그인</h2>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">
|
||||
<i class="bi bi-person"></i>
|
||||
</span>
|
||||
<input type="text" id="username" name="username" class="form-control" placeholder="아이디" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">
|
||||
<i class="bi bi-lock"></i>
|
||||
</span>
|
||||
<input type="password" id="password" name="password" class="form-control" placeholder="비밀번호" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-grid gap-2">
|
||||
<button id="signIn" type="button" class="btn btn-primary">
|
||||
<i class="bi bi-box-arrow-in-right me-2"></i>로그인
|
||||
</button>
|
||||
<button id="signUp" type="button" class="btn btn-outline-secondary">
|
||||
<i class="bi bi-person-plus-fill me-2"></i>회원가입
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<h1>로그인</h1>
|
||||
<form id="signinForm" method="post">
|
||||
<div class="input-group">
|
||||
<div class="input-icon">
|
||||
<img src="/images/user-id.png" alt="User Icon">
|
||||
<input type="text" id="username" name="username" placeholder="아이디">
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<div class="input-icon">
|
||||
<img src="/images/user-lock.png" alt="Password Icon">
|
||||
<input type="password" id="password" name="password" placeholder="비밀번호">
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit">로그인</button>
|
||||
</form>
|
||||
<button id="signup" th:onclick="|location.href='@{/sign-up}'|" class="small-button">회원가입</button>
|
||||
</div>
|
||||
|
||||
<!-- 회원가입 모달 -->
|
||||
<div class="modal fade" id="signupModal" tabindex="-1" aria-labelledby="signupModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-scrollable modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header bg-dark text-white">
|
||||
<h5 class="modal-title" id="signupModalLabel">회원가입</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="signupForm">
|
||||
<div class="mb-3">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text"><i class="bi bi-person"></i></span>
|
||||
<input type="text" class="form-control" id="loginId" placeholder="아이디" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text"><i class="bi bi-key"></i></span>
|
||||
<input type="password" class="form-control" id="loginPassword" placeholder="비밀번호" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text"><i class="bi bi-envelope"></i></span>
|
||||
<input type="text" class="form-control" id="userName" placeholder="사용자명" required>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">
|
||||
<i class="bi bi-x-circle me-2"></i>취소
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary" id="signupSubmit">
|
||||
<i class="bi bi-check-circle me-2"></i>가입하기
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="module" th:src="@{/js/pages/sign/sign-in.js}" defer></script>
|
||||
</section>
|
||||
</body>
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns:th="http://www.thymeleaf.org"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||
layout:decorate="~{layouts/signin-layout}" lang="ko" xml:lang="ko">
|
||||
<head>
|
||||
<title>회원가입 페이지</title>
|
||||
</head>
|
||||
<body>
|
||||
<section layout:fragment="content">
|
||||
<div class="container">
|
||||
<h1>회원가입</h1>
|
||||
<form id="signupForm" method="post">
|
||||
<div class="input-group">
|
||||
<input type="text" id="loginId" name="loginId" placeholder="아이디">
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<input type="password" id="password" name="password" placeholder="비밀번호">
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<input type="text" id="userName" name="userName" placeholder="이름">
|
||||
</div>
|
||||
<button type="submit">회원가입</button>
|
||||
</form>
|
||||
</div>
|
||||
<script type="module" th:src="@{/js/pages/sign/sign-up.js}" defer></script>
|
||||
</section>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user