OWL ITS + 탐지시스템(인터넷 진흥원)
jhjang
2021-11-15 42d7d22a1832dbf91cafcedf872c65817401fb94
- api 관련 메뉴 추가
=> api 토큰 생성 기능 추가

- UI문서 제작을 위한 view 수정(기능 구현 필요)
=> 이슈 유형 추가 항목 수정
=> 사용자 정의 필드 유형 추가
=> 프로젝트 담당자->담당부서로 수정
16개 파일 추가됨
25개 파일 변경됨
1852 ■■■■ 파일 변경됨
pom.xml 12 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/domain/ApiToken.java 58 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/domain/CustomFieldApiDefault.java 73 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/domain/CustomFieldApiOverlap.java 63 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/domain/IssueApiDefault.java 161 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/domain/User.java 23 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/repository/ApiTokenRepository.java 11 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/ApiTokenService.java 34 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/impl/ApiTokenServiceImpl.java 131 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/impl/IssueTypeServiceImpl.java 8 ●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/vo/ApiTokenVo.java 47 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/condition/ApiTokenCondition.java 54 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/controller/ApiController.java 50 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/controller/ApiTokenController.java 71 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/form/ApiTokenForm.java 51 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/resources/migration/V1_11__Alter_Table.sql 73 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/webapp/WEB-INF/i18n/code_ko_KR.properties 6 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/webapp/assets/styles/main.css 2 ●●● 패치 | 보기 | raw | blame | 히스토리
src/main/webapp/custom_components/js-workflow/js-workflow.html 18 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/webapp/i18n/ko/global.json 9 ●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/webapp/scripts/app/api/api.js 6 ●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/webapp/scripts/app/api/apiAuth.controller.js 84 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/webapp/scripts/app/api/apiSetting.controller.js 27 ●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/webapp/scripts/app/customField/customFieldAdd.controller.js 6 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/webapp/scripts/app/customField/customFieldList.controller.js 18 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/webapp/scripts/app/ispField/ispField.js 2 ●●● 패치 | 보기 | raw | blame | 히스토리
src/main/webapp/scripts/app/issueType/issueTypeAdd.controller.js 10 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/webapp/scripts/app/issueType/issueTypeList.controller.js 7 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/webapp/scripts/app/project/projectList.controller.js 8 ●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/webapp/scripts/components/api/api.service.js 29 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/webapp/scripts/main.js 1 ●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/webapp/views/api/apiAuth.html 20 ●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/webapp/views/api/apiOverlapAdd.html 36 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/webapp/views/api/apiSetting.html 266 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/webapp/views/api/apiSettingColumn.html 252 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/webapp/views/api/apiSettingOverlap.html 36 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/webapp/views/common/sidebar.html 54 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/webapp/views/customField/customFieldAdd.html 7 ●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/webapp/views/issueType/issueTypeAdd.html 22 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/webapp/views/login/login.html 2 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/webapp/views/project/projectModify.html 4 ●●●● 패치 | 보기 | raw | blame | 히스토리
pom.xml
@@ -493,6 +493,18 @@
            <version>${elastic.search.version}</version>
        </dependency>
        <!-- JWT (api token) -->
        <dependency>
            <groupId>javax.xml.bind</groupId>
            <artifactId>jaxb-api</artifactId>
            <version>2.3.1</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>
    </dependencies>
    <repositories>
src/main/java/kr/wisestone/owl/domain/ApiToken.java
New file
@@ -0,0 +1,58 @@
package kr.wisestone.owl.domain;
import javax.persistence.*;
import java.io.Serializable;
@Entity
public class ApiToken extends BaseEntity implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id")
    private User user;
    private String appName;
    private String token;
    public ApiToken(){}
    public static long getSerialVersionUID() {
        return serialVersionUID;
    }
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public User getUser() {
        return user;
    }
    public void setUser(User user) {
        this.user = user;
    }
    public String getAppName() {
        return appName;
    }
    public void setAppName(String appName) {
        this.appName = appName;
    }
    public String getToken() {
        return token;
    }
    public void setToken(String token) {
        this.token = token;
    }
}
src/main/java/kr/wisestone/owl/domain/CustomFieldApiDefault.java
New file
@@ -0,0 +1,73 @@
package kr.wisestone.owl.domain;
import javax.persistence.*;
import java.io.Serializable;
@Entity
public class CustomFieldApiDefault extends BaseEntity implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id")
    private User user;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "issue_type_id")
    private IssueType issueType;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "custom_field_id")
    private CustomField customField;
    private String customFieldValue;
    public CustomFieldApiDefault(){}
    public static long getSerialVersionUID() {
        return serialVersionUID;
    }
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public User getUser() {
        return user;
    }
    public void setUser(User user) {
        this.user = user;
    }
    public IssueType getIssueType() {
        return issueType;
    }
    public void setIssueType(IssueType issueType) {
        this.issueType = issueType;
    }
    public CustomField getCustomField() {
        return customField;
    }
    public void setCustomField(CustomField customField) {
        this.customField = customField;
    }
    public String getCustomFieldValue() {
        return customFieldValue;
    }
    public void setCustomFieldValue(String customFieldValue) {
        this.customFieldValue = customFieldValue;
    }
}
src/main/java/kr/wisestone/owl/domain/CustomFieldApiOverlap.java
New file
@@ -0,0 +1,63 @@
package kr.wisestone.owl.domain;
import javax.persistence.*;
import java.io.Serializable;
@Entity
public class CustomFieldApiOverlap extends BaseEntity implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id")
    private User user;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "issue_type_id")
    private IssueType issueType;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "custom_field_id")
    private CustomField customField;
    public CustomFieldApiOverlap(){}
    public static long getSerialVersionUID() {
        return serialVersionUID;
    }
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public User getUser() {
        return user;
    }
    public void setUser(User user) {
        this.user = user;
    }
    public IssueType getIssueType() {
        return issueType;
    }
    public void setIssueType(IssueType issueType) {
        this.issueType = issueType;
    }
    public CustomField getCustomField() {
        return customField;
    }
    public void setCustomField(CustomField customField) {
        this.customField = customField;
    }
}
src/main/java/kr/wisestone/owl/domain/IssueApiDefault.java
New file
@@ -0,0 +1,161 @@
package kr.wisestone.owl.domain;
import javax.persistence.*;
import java.io.Serializable;
import java.util.Date;
@Entity
public class IssueApiDefault extends BaseEntity implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id")
    private User user;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "issue_type_id")
    private IssueType issueType;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "issue_status_id")
    private IssueStatus issueStatus;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "project_id")
    private Project project;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "priority_id")
    private Priority priority;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "severity_id")
    private Severity severity;
    private String title;
    private String description;
    private  Long reverseIndex;
    private  Long issue_number;
    @Temporal(TemporalType.TIMESTAMP)
    private Date start_date;
    @Temporal(TemporalType.TIMESTAMP)
    private Date complete_date;
    public IssueApiDefault(){}
    public static long getSerialVersionUID() {
        return serialVersionUID;
    }
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public User getUser() {
        return user;
    }
    public void setUser(User user) {
        this.user = user;
    }
    public IssueType getIssueType() {
        return issueType;
    }
    public void setIssueType(IssueType issueType) {
        this.issueType = issueType;
    }
    public IssueStatus getIssueStatus() {
        return issueStatus;
    }
    public void setIssueStatus(IssueStatus issueStatus) {
        this.issueStatus = issueStatus;
    }
    public Project getProject() {
        return project;
    }
    public void setProject(Project project) {
        this.project = project;
    }
    public Priority getPriority() {
        return priority;
    }
    public void setPriority(Priority priority) {
        this.priority = priority;
    }
    public Severity getSeverity() {
        return severity;
    }
    public void setSeverity(Severity severity) {
        this.severity = severity;
    }
    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }
    public String getDescription() {
        return description;
    }
    public void setDescription(String description) {
        this.description = description;
    }
    public Long getReverseIndex() {
        return reverseIndex;
    }
    public void setReverseIndex(Long reverseIndex) {
        this.reverseIndex = reverseIndex;
    }
    public Long getIssue_number() {
        return issue_number;
    }
    public void setIssue_number(Long issue_number) {
        this.issue_number = issue_number;
    }
    public Date getStart_date() {
        return start_date;
    }
    public void setStart_date(Date start_date) {
        this.start_date = start_date;
    }
    public Date getComplete_date() {
        return complete_date;
    }
    public void setComplete_date(Date complete_date) {
        this.complete_date = complete_date;
    }
}
src/main/java/kr/wisestone/owl/domain/User.java
@@ -19,6 +19,9 @@
    public static final String DEFAULT_RESERVATION_NOTIFY_TIME = "09:00";    //  기본 이메일 알림 예정 시간
    public static final String DEFAULT_LANGUAGE = "ko"; //  기본 언어
    public static final String INSERT_TYPE_NORMAL = "N";    // 추가 타입(일반)
    public static final String INSERT_TYPE_API = "A";       // 추가 타입(API)
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
@@ -37,6 +40,7 @@
    private String reservationNotifyTime;   //  이메일 알림 시간 예정
    private String language;
    private String licensekey;
    private String insertType = User.INSERT_TYPE_NORMAL;
    @OneToMany(mappedBy = "user", cascade = {CascadeType.ALL}, orphanRemoval = true)
    private Set<SystemRoleUser> systemRoleUsers = new HashSet<>();
@@ -62,6 +66,9 @@
    @ManyToOne(targetEntity = UserLevel.class, fetch = FetchType.LAZY)
    @JoinColumn(name="level_id")
    private UserLevel userLevel;
    @OneToMany(mappedBy = "user", cascade = {CascadeType.ALL}, orphanRemoval = true)
    private Set<ApiToken> apiTokens = new HashSet<>();
//    @ManyToOne(targetEntity = Department.class, fetch = FetchType.LAZY)
//    @JoinColumn(name="department_id")
@@ -204,6 +211,14 @@
        this.projectRoleUsers = projectRoleUsers;
    }
    public String getInsertType() {
        return insertType;
    }
    public void setInsertType(String insertType) {
        this.insertType = insertType;
    }
    public void addProjectRole(ProjectRole projectRole) {
        if (this.projectRoleUsers == null) {
            this.projectRoleUsers = new HashSet<>();
@@ -312,6 +327,14 @@
        this.licensekey = licensekey;
    }
    public Set<ApiToken> getApiTokens() {
        return apiTokens;
    }
    public void setApiTokens(Set<ApiToken> apiTokens) {
        this.apiTokens = apiTokens;
    }
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        // TODO Auto-generated method stub
src/main/java/kr/wisestone/owl/repository/ApiTokenRepository.java
New file
@@ -0,0 +1,11 @@
package kr.wisestone.owl.repository;
import kr.wisestone.owl.domain.ApiToken;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.query.Param;
import java.util.List;
public interface ApiTokenRepository extends JpaRepository<ApiToken, Long> {
    List<ApiToken> findByUserId(@Param("userId") Long userId);
}
src/main/java/kr/wisestone/owl/service/ApiTokenService.java
New file
@@ -0,0 +1,34 @@
package kr.wisestone.owl.service;
import kr.wisestone.owl.domain.ApiToken;
import kr.wisestone.owl.domain.CompanyField;
import kr.wisestone.owl.domain.CustomField;
import kr.wisestone.owl.domain.Event;
import kr.wisestone.owl.vo.ApiTokenVo;
import kr.wisestone.owl.vo.CompanyFieldVo;
import kr.wisestone.owl.vo.EventVo;
import kr.wisestone.owl.web.condition.ApiTokenCondition;
import kr.wisestone.owl.web.condition.CompanyFieldCondition;
import kr.wisestone.owl.web.condition.EventCondition;
import kr.wisestone.owl.web.form.ApiTokenForm;
import kr.wisestone.owl.web.form.CompanyFieldForm;
import kr.wisestone.owl.web.form.DepartmentForm;
import kr.wisestone.owl.web.form.EventForm;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Service;
import org.springframework.ui.Model;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
public interface ApiTokenService extends AbstractService<ApiToken, Long, JpaRepository<ApiToken, Long>> {
    ApiToken add(ApiTokenForm apiTokenForm);
    ApiTokenVo find();
    void remove(ApiTokenForm apiTokenForm);
}
src/main/java/kr/wisestone/owl/service/impl/ApiTokenServiceImpl.java
New file
@@ -0,0 +1,131 @@
package kr.wisestone.owl.service.impl;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import kr.wisestone.owl.domain.ApiToken;
import kr.wisestone.owl.domain.User;
import kr.wisestone.owl.repository.ApiTokenRepository;
import kr.wisestone.owl.service.ApiTokenService;
import kr.wisestone.owl.util.ConvertUtil;
import kr.wisestone.owl.util.DateUtil;
import kr.wisestone.owl.util.WebAppUtil;
import kr.wisestone.owl.vo.ApiTokenVo;
import kr.wisestone.owl.vo.UserVo;
import kr.wisestone.owl.web.form.ApiTokenForm;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Service;
import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.util.List;
import java.lang.Long;
import com.fasterxml.jackson.databind.ObjectMapper;
@Service
public class ApiTokenServiceImpl extends AbstractServiceImpl<ApiToken, Long, JpaRepository<ApiToken, Long>> implements ApiTokenService {
    private static final String ENCRYPT_STRING = "mpxowl";
    private static final String DATA_KEY = "user";
    static final Logger log = LoggerFactory.getLogger(ApiTokenServiceImpl.class);
    @Autowired
    private ApiTokenRepository apiTokenRepository;
    @Autowired
    protected WebAppUtil webAppUtil;
    // 토큰 생성
    @Override
    public ApiToken add(ApiTokenForm apiTokenForm) {
        ApiToken apiToken = ConvertUtil.copyProperties(apiTokenForm, ApiToken.class);
        String appName = apiToken.getAppName();
        apiToken.setUser(this.webAppUtil.getLoginUserObject());
        // 기존 토큰 삭제
        this.remove(null);
        UserVo user = this.webAppUtil.getLoginUser();
        if (appName != null && !appName.isEmpty()) {
            Long currentTime = System.currentTimeMillis();
            Date now = new Date(currentTime);
            String token = Jwts.builder()
                            .setSubject(appName)
                            .setHeaderParam("typ", "JWT")
                            .setExpiration(DateUtil.addDays(now, 36500))
                            .setIssuedAt(now)
                            .claim(DATA_KEY, user)
                            .signWith(SignatureAlgorithm.HS256, this.generateKey())
                            .compact();
            apiToken.setToken(token);
        }
        return this.apiTokenRepository.save(apiToken);
    }
    // 키 생성
    private byte[] generateKey(){
        byte[] key = null;
        try {
            key = ENCRYPT_STRING.getBytes("UTF-8");
        } catch (UnsupportedEncodingException e) {
            log.error("Making secret Key Error :: ", e);
        }
        System.out.println("비밀 key : " + key);
        return key;
    }
    //JWT 복호화
    public UserVo getUser(String jwt) {
        //결과값 = Claims
        Jws<Claims> claims = null;
        try {
            //비밀키를 이용해서 복호화 하는 작업
            claims = Jwts.parser()
                    .setSigningKey(this.generateKey())
                    .parseClaimsJws(jwt);
        } catch (Exception e) {
            log.debug(e.getMessage(), e);
        }
        ObjectMapper objectMapper = new ObjectMapper();
        //반환 타입은 LinkedHashMap 이다. 이를 User 타입으로 변환하기 위해 ObjectMapper 사용
        return objectMapper.convertValue(claims.getBody().get(DATA_KEY), UserVo.class);
    }
    // 토큰 조회
    @Override
    public ApiTokenVo find() {
        User user = this.webAppUtil.getLoginUserObject();
        List<ApiToken> apiTokens = this.apiTokenRepository.findByUserId(user.getId());
        if (apiTokens != null && apiTokens.size() >0 ) {
            return ConvertUtil.copyProperties(apiTokens.get(0), ApiTokenVo.class);
        }
        return null;
    }
    // 토큰 삭제
    @Override
    public void remove(ApiTokenForm apiTokenForm) {
        ApiTokenVo apiTokenVo = this.find();
        if (apiTokenVo != null) {
            this.apiTokenRepository.deleteById(apiTokenVo.getId());
        }
    }
    @Override
    protected JpaRepository<ApiToken, Long> getRepository() {
        return this.apiTokenRepository;
    }
}
src/main/java/kr/wisestone/owl/service/impl/IssueTypeServiceImpl.java
@@ -75,16 +75,16 @@
            switch (projectType) {
                case BTS_PROJECT:
                    issueTypes.add(new IssueType(workspace, workflow, this.messageAccessor.message("common.bug"), "", "#ff5f99")); // 버그
                    issueTypes.add(new IssueType(workspace, workflow, this.messageAccessor.message("common.improvement"), "", "#3598fe")); // 개선
                    issueTypes.add(new IssueType(workspace, workflow, "악성 도메인", "", "#ff5f99")); // 버그
                    issueTypes.add(new IssueType(workspace, workflow, "경유지 대응", "", "#3598fe")); // 개선
                    break;
                case RMS_PROJECT:
                    issueTypes.add(new IssueType(workspace, workflow, this.messageAccessor.message("common.requirement"), "", "#3bcde2")); // 요구 사항
                    issueTypes.add(new IssueType(workspace, workflow, "유포지 대응", "", "#3bcde2")); // 요구 사항
                    break;
                case TCM_PROJECT:
                    issueTypes.add(new IssueType(workspace, workflow, this.messageAccessor.message("common.testcase"), "", "#008ca7")); // 테스트 케이스, 실행 순서, 전제 조건, 기대 결과
                    issueTypes.add(new IssueType(workspace, workflow, "분석결과 대응", "", "#008ca7")); // 테스트 케이스, 실행 순서, 전제 조건, 기대 결과
                    break;
            }
src/main/java/kr/wisestone/owl/vo/ApiTokenVo.java
New file
@@ -0,0 +1,47 @@
package kr.wisestone.owl.vo;
import com.google.common.collect.Lists;
import kr.wisestone.owl.domain.User;
import java.util.List;
public class ApiTokenVo extends BaseVo{
    private Long id;
    private User user;
    private String appName;
    private String token;
    public ApiTokenVo(){}
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public User getUser() {
        return user;
    }
    public void setUser(User user) {
        this.user = user;
    }
    public String getAppName() {
        return appName;
    }
    public void setAppName(String appName) {
        this.appName = appName;
    }
    public String getToken() {
        return token;
    }
    public void setToken(String token) {
        this.token = token;
    }
}
src/main/java/kr/wisestone/owl/web/condition/ApiTokenCondition.java
New file
@@ -0,0 +1,54 @@
package kr.wisestone.owl.web.condition;
import kr.wisestone.owl.domain.User;
import kr.wisestone.owl.util.CommonUtil;
import kr.wisestone.owl.util.ConvertUtil;
import kr.wisestone.owl.util.DateUtil;
import kr.wisestone.owl.util.MapUtil;
import org.springframework.util.StringUtils;
import javax.persistence.*;
import java.util.Date;
import java.util.List;
import java.util.Map;
public class ApiTokenCondition {
    private Long id;
    private User user;
    private String appName;
    private String token;
    public ApiTokenCondition(){}
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public User getUser() {
        return user;
    }
    public void setUser(User user) {
        this.user = user;
    }
    public String getAppName() {
        return appName;
    }
    public void setAppName(String appName) {
        this.appName = appName;
    }
    public String getToken() {
        return token;
    }
    public void setToken(String token) {
        this.token = token;
    }
}
src/main/java/kr/wisestone/owl/web/controller/ApiController.java
New file
@@ -0,0 +1,50 @@
package kr.wisestone.owl.web.controller;
import kr.wisestone.owl.constant.Constants;
import kr.wisestone.owl.service.GuideService;
import kr.wisestone.owl.web.condition.GuideCondition;
import kr.wisestone.owl.web.form.GuideForm;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.HashMap;
import java.util.Map;
@Controller
public class ApiController extends BaseController {
    @Autowired
    private GuideService guideService;
    //  이슈 추가
    @RequestMapping(value = "/api/add", produces = MediaType.APPLICATION_JSON_VALUE)
    public
    @ResponseBody
    Map<String, Object> add(@RequestBody Map<String, Map<String, Object>> params) {
        Map<String, Object> resJsonData = new HashMap<>();
        // todo
        return this.setSuccessMessage(resJsonData);
    }
    //  이슈 조회
    @RequestMapping(value = "/api/find", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
    public
    @ResponseBody
    Map<String, Object> find(@RequestBody Map<String, Map<String, Object>> params) {
        Map<String, Object> resJsonData = new HashMap<>();
        Pageable pageable = this.pageUtil.convertPageable(this.getPageVo(params));
        // todo
        return this.setSuccessMessage(resJsonData);
    }
}
src/main/java/kr/wisestone/owl/web/controller/ApiTokenController.java
New file
@@ -0,0 +1,71 @@
package kr.wisestone.owl.web.controller;
import kr.wisestone.owl.constant.Constants;
import kr.wisestone.owl.domain.ApiToken;
import kr.wisestone.owl.service.ApiTokenService;
import kr.wisestone.owl.util.ConvertUtil;
import kr.wisestone.owl.vo.ApiTokenVo;
import kr.wisestone.owl.web.condition.ApiTokenCondition;
import kr.wisestone.owl.web.form.ApiTokenForm;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Controller
public class ApiTokenController extends BaseController {
    @Autowired
    private ApiTokenService apiTokenService;
    //  토큰 생성
    @RequestMapping(value = "/apiToken/add", produces = MediaType.APPLICATION_JSON_VALUE)
    public
    @ResponseBody
    Map<String, Object> add(@RequestBody Map<String, Map<String, Object>> params) {
        Map<String, Object> resJsonData = new HashMap<>();
        ApiTokenForm form = ConvertUtil.convertMapToClass(params.get(Constants.REQ_KEY_CONTENT), ApiTokenForm.class);
        ApiToken apiToken = apiTokenService.add(form);
        resJsonData.put(Constants.RES_KEY_CONTENTS, ConvertUtil.copyProperties(apiToken, ApiTokenVo.class));
        return this.setSuccessMessage(resJsonData);
    }
    //  토큰 조회
    @RequestMapping(value = "/apiToken/find", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
    public
    @ResponseBody
    Map<String, Object> find(@RequestBody Map<String, Map<String, Object>> params) {
        Map<String, Object> resJsonData = new HashMap<>();
        Pageable pageable = this.pageUtil.convertPageable(this.getPageVo(params));
        ApiTokenVo token = this.apiTokenService.find();
        resJsonData.put(Constants.RES_KEY_CONTENTS, token);
        return this.setSuccessMessage(resJsonData);
    }
    //  토큰 삭제
    @RequestMapping(value = "/apiToken/remove", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
    public
    @ResponseBody
    Map<String, Object> remove(@RequestBody Map<String, Map<String, Object>> params) {
        Map<String, Object> resJsonData = new HashMap<>();
        ApiTokenForm apiTokenForm = ConvertUtil.convertMapToClass(params.get(Constants.REQ_KEY_CONTENT), ApiTokenForm.class);
        this.apiTokenService.remove(apiTokenForm);
        return this.setSuccessMessage(resJsonData);
    }
}
src/main/java/kr/wisestone/owl/web/form/ApiTokenForm.java
New file
@@ -0,0 +1,51 @@
package kr.wisestone.owl.web.form;
import com.google.common.collect.Lists;
import kr.wisestone.owl.domain.User;
import kr.wisestone.owl.util.ConvertUtil;
import kr.wisestone.owl.util.MapUtil;
import java.util.List;
import java.util.Map;
public class ApiTokenForm {
    private Long id;
    private User user;
    private String appName;
    private String token;
    public ApiTokenForm() {
    }
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public User getUser() {
        return user;
    }
    public void setUser(User user) {
        this.user = user;
    }
    public String getAppName() {
        return appName;
    }
    public void setAppName(String appName) {
        this.appName = appName;
    }
    public String getToken() {
        return token;
    }
    public void setToken(String token) {
        this.token = token;
    }
}
src/main/resources/migration/V1_11__Alter_Table.sql
@@ -52,4 +52,75 @@
    `modify_id` BIGINT(20) NOT NULL,
    `modify_date` TIMESTAMP NULL,
    PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- api 관련
ALTER TABLE `user` ADD COLUMN  `insert_type` VARCHAR(1) NOT NULL DEFAULT 'N';
CREATE TABLE `api_token`(
    `id` BIGINT(11) AUTO_INCREMENT,
    `user_id` BIGINT(50) NOT NULL,
    `app_name` VARCHAR(50) NOT NULL,
    `token` VARCHAR(1024) NOT NULL,
    `register_id` BIGINT(20) NOT NULL,
    `register_date` TIMESTAMP NULL,
    `modify_id` BIGINT(20) NOT NULL,
    `modify_date` TIMESTAMP NULL,
    PRIMARY KEY (`id`) USING BTREE,
    INDEX `userIdIndex` (`user_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `issue_api_default`(
    `id` BIGINT(11) AUTO_INCREMENT,
    `user_id` BIGINT(11) NOT NULL,
    `issue_type_id` bigint(11) NOT NULL,
    `issue_status_id` bigint(20) DEFAULT NULL,
    `project_id` bigint(20) DEFAULT NULL,
    `priority_id` bigint(20) DEFAULT NULL,
    `severity_id` bigint(20) DEFAULT NULL,
    `title` varchar(300) DEFAULT NULL,
    `description` mediumtext COMMENT 'description',
    `reverse_index` bigint(20) DEFAULT NULL,
    `issue_number` bigint(20) DEFAULT NULL,
    `start_date` varchar(20) DEFAULT NULL,
    `complete_date` varchar(20) DEFAULT NULL,
    `register_id` BIGINT(20) NOT NULL,
    `register_date` TIMESTAMP NULL,
    `modify_id` BIGINT(20) NOT NULL,
    `modify_date` TIMESTAMP NULL,
    PRIMARY KEY (`id`) USING BTREE,
    INDEX `userIdIndex` (`user_id`) USING BTREE,
    INDEX `issueTypeIdIndex` (`issue_type_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `custom_field_api_default`(
   `id` BIGINT(11) AUTO_INCREMENT,
   `user_id` BIGINT(11) NOT NULL,
   `issue_type_id` BIGINT(11) NOT NULL,
   `custom_field_id` BIGINT(11) NOT NULL,
   `custom_field_value` varchar(300) NOT NULL,
   `register_id` BIGINT(20) NOT NULL,
   `register_date` TIMESTAMP NULL,
   `modify_id` BIGINT(20) NOT NULL,
   `modify_date` TIMESTAMP NULL,
   PRIMARY KEY (`id`) USING BTREE,
   INDEX `userIdIndex` (`user_id`) USING BTREE,
   INDEX `issueTypeIdIndex` (`issue_type_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `custom_field_api_overlap`(
   `id` BIGINT(11) AUTO_INCREMENT,
   `user_id` BIGINT(11) NOT NULL,
   `issue_type_id` BIGINT(11) NOT NULL,
   `custom_field_id` BIGINT(11) NOT NULL,
   `register_id` BIGINT(20) NOT NULL,
   `register_date` TIMESTAMP NULL,
   `modify_id` BIGINT(20) NOT NULL,
   `modify_date` TIMESTAMP NULL,
   PRIMARY KEY (`id`) USING BTREE,
   INDEX `userIdIndex` (`user_id`) USING BTREE,
   INDEX `issueTypeIdIndex` (`issue_type_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
src/main/webapp/WEB-INF/i18n/code_ko_KR.properties
@@ -75,6 +75,12 @@
common.stringField=\uBB38\uC790\uC5F4 \uD544\uB4DC
common.singleSelectionField=\uB2E8\uC77C \uC120\uD0DD \uD544\uB4DC
common.multipleSelectionField=\uB2E4\uC911 \uC120\uD0DD \uD544\uB4DC
common.numberField=\uC22B\uC790 \uD544\uB4DC
common.datetimeField=\uB0A0\uC9DC \uD544\uB4DC
common.ipAddressField=IP \uC8FC\uC18C \uD544\uB4DC
common.emailField=\uC774\uBA54\uC77C \uD544\uB4DC
common.siteField=\uD648\uD398\uC774\uC9C0 \uD544\uB4DC
common.telField=\uC804\uD654\uBC88\uD638 \uD544\uB4DC
common.customField=\uC0AC\uC6A9\uC790 \uC815\uC758 \uD544\uB4DC
common.customFieldList=\uC0AC\uC6A9\uC790 \uC815\uC758 \uD544\uB4DC \uBAA9\uB85D
common.fieldType=\uD544\uB4DC \uC720\uD615
src/main/webapp/assets/styles/main.css
@@ -5162,7 +5162,7 @@
}*/
.modal-open {
    overflow: hidden
    overflow: auto
}
.modal {
src/main/webapp/custom_components/js-workflow/js-workflow.html
@@ -59,6 +59,24 @@
                    </div>
                </div>
            </div>
            <div class="form-group">
                <label><span translate="project.projectDepartment">담당 부서</span></label>
                <div class="input-group">
                    <select class="form-control input-sm issue-select-label"
                            name="targetStatusId"
                            ng-style="{ 'color' : fn.getOptionColor(vm.targetIssueStatusList, vm.targetStatusId) }"
                            ng-model="vm.targetStatusId">
                        <option value="" translate="common.choose">선택하세요.</option>
                        <option value="{{issueStatus.id}}"
                                ng-style="{ 'color' : issueStatus.color, 'font-weight': 600 }"
                                ng-repeat="issueStatus in vm.targetIssueStatusList">
                            ●&nbsp;{{issueStatus.name}}
                        </option>
                    </select>
                </div>
            </div>
        </div>
        <div class="col-md-9">
            <div class="form-group workflowbox">
src/main/webapp/i18n/ko/global.json
@@ -786,7 +786,14 @@
        "delete" : "토큰 삭제",
        "create" : "토큰 생성",
        "example" : "사용 예시",
        "columnSetting" : "기본값 설정"
        "columnSetting" : "기본값 설정",
        "overlapSetting" : "중복 필드 설정",
        "addOverlapField" : "중복 필드 추가",
        "failedToApiTokenFind" : "API 토큰 조회 실패",
        "failedToApiTokenRemove" : "API 토큰 삭제 실패",
        "failedToApiTokenAdd" : "API 토큰 생성 실패",
        "successToApiTokenRemove" : "API 토큰 삭제 성공",
        "successToApiTokenAdd" : "API 토큰 생성 성공"
    },
    "companyField" : {
        "add" : "업체 생성",
src/main/webapp/scripts/app/api/api.js
@@ -33,7 +33,7 @@
                        loadController : ["$q", function ($q) {
                            var deferred = $q.defer();
                            require(["apiAuthController",
                                , 'formSubmit', 'jsShortCut', 'inputRegex'], function () {
                                "apiService", 'formSubmit', 'jsShortCut', 'inputRegex'], function () {
                                deferred.resolve();
                            });
@@ -52,7 +52,7 @@
                        loadController : ["$q", function ($q) {
                            var deferred = $q.defer();
                            require(["apiSettingController",
                                , 'formSubmit', 'jsShortCut', 'inputRegex'], function () {
                                "apiService", 'formSubmit', 'jsShortCut', 'inputRegex'], function () {
                                deferred.resolve();
                            });
@@ -71,7 +71,7 @@
                        loadController : ["$q", function ($q) {
                            var deferred = $q.defer();
                            require(["apiMonitorController",
                                , 'formSubmit', 'jsShortCut', 'inputRegex'], function () {
                                "apiService", 'formSubmit', 'jsShortCut', 'inputRegex'], function () {
                                deferred.resolve();
                            });
src/main/webapp/scripts/app/api/apiAuth.controller.js
@@ -7,20 +7,94 @@
        'app', 'angular'
    ],
    function (app, angular) {
        app.controller('apiAuthController', ['$scope', '$rootScope', '$log', '$resourceProvider', 'SweetAlert', '$timeout', '$filter',
            function ($scope, $rootScope, $log, $resourceProvider, SweetAlert, $timeout, $filter) {
        app.controller('apiAuthController', ['$scope', '$rootScope', '$log', '$resourceProvider', '$state', 'Api', 'SweetAlert', '$timeout', '$filter',
            function ($scope, $rootScope, $log, $resourceProvider, $state, Api, SweetAlert, $timeout, $filter) {
                $scope.fn = {
                    formSubmit : formSubmit,
                    remove : remove,
                    find : find,
                    formCheck : formCheck
                };
                $scope.vm = {
                    responseData : {
                        data : []
                    },
                    form : {
                        name : "홈페이지 변조 탐지 시스템",
                        token : "77751b541212de21a44f87024174d3bd"
                        appName : "",
                        token : ""
                    }
                };
                //  폼 체크
                function formCheck(formInvalid) {
                    if (formInvalid) {
                        return true;
                    }
                    return false;
                }
                function formSubmit() {
                    var conditions = {
                        appName : $scope.vm.form.appName
                    }
                    Api.add($resourceProvider.getContent(conditions,
                        $resourceProvider.getPageContent(0, 1))).then(function (result) {
                        if (result.data.message.status === "success") {
                            if (angular.isDefined(result.data.data)) {
                                $scope.vm.form.token = result.data.data.token;
                            }
                            SweetAlert.swal($filter("translate")("api.successToApiTokenAdd"), result.data.message.message, "success"); // "api 토큰 생성 성공"
                        }
                        else {
                            SweetAlert.swal($filter("translate")("api.failedToApiTokenAdd"), result.data.message.message, "error"); // "api 토큰 생성 실패"
                        }
                    });
                }
                function remove() {
                    var content = {
                        id : ""
                    }
                    Api.remove($resourceProvider.getContent(content,
                        $resourceProvider.getPageContent(0, 1))).then(function (result) {
                        if (result.data.message.status === "success") {
                            $scope.vm.form.token = "";
                            SweetAlert.swal($filter("translate")("api.successToApiTokenRemove"), result.data.message.message, "success"); // "api 토큰 생성 성공"
                        }
                        else {
                            SweetAlert.swal($filter("translate")("api.failedToApiTokenRemove"), result.data.message.message, "error"); // "api 토큰 삭제 실패"
                        }
                    });
                }
                function find() {
                    var conditions = {
                        appName : $scope.vm.form.appName
                    }
                    Api.find($resourceProvider.getContent(conditions,
                        $resourceProvider.getPageContent(0, 1))).then(function (result) {
                        if (result.data.message.status === "success") {
                            if (result.data.data != null) {
                                $scope.vm.form.appName = result.data.data.appName;
                                $scope.vm.form.token = result.data.data.token;
                            }
                        }
                        else {
                            SweetAlert.swal($filter("translate")("api.failedToApiTokenFind"), result.data.message.message, "error"); // "api 토큰 조회 실패"
                        }
                    });
                }
                $scope.fn.find();
            }]);
    });
src/main/webapp/scripts/app/api/apiSetting.controller.js
@@ -7,19 +7,40 @@
        'app', 'angular'
    ],
    function (app, angular) {
        app.controller('apiSettingController', ['$scope', '$rootScope', '$log', '$resourceProvider', 'SweetAlert', '$timeout', '$filter',
            function ($scope, $rootScope, $log, $resourceProvider, SweetAlert, $timeout, $filter) {
        app.controller('apiSettingController', ['$scope', '$rootScope', '$log', '$resourceProvider','$uibModal', 'SweetAlert', '$timeout', '$filter',
            function ($scope, $rootScope, $log, $resourceProvider, $uibModal, SweetAlert, $timeout, $filter) {
                $scope.fn = {
                    changeTab : changeTab,
                    add : add,
                };
                $scope.vm = {
                    tab : "API_COL_SETTING",
                    form : {
                    }
                };
                //  생성 팝업
                function add() {
                    $uibModal.open({
                        templateUrl : 'views/api/apiOverlapAdd.html',
                        size : "sm",
                        // controller : 'apiOverlapAddController',
                        backdrop : 'static'
                    });
                }
                function changeTab(tab) {
                    $scope.vm.tab = tab;
                    if (tab === "API_COL_SETTING") {
                        $rootScope.$broadcast("changeColumnSettingTab");
                    } else if (tab === "API_OVERLAP_SETTING") {
                        $rootScope.$broadcast("changeOverlapSettingTab");
                    }
                }
            }]);
    });
src/main/webapp/scripts/app/customField/customFieldAdd.controller.js
@@ -69,7 +69,8 @@
                    if (!duplication) {
                        if (!$rootScope.isDefined($scope.vm.form.optionText)) {
                            $scope.vm.form.optionText = "";
                            SweetAlert.warning($filter("translate")("customField.emptyInputValue"), $filter("translate")("customField.emptyAddValue")); //  입력 값 확인 알림, 입력한 값이 없습니다.
                            SweetAlert.warning($filter("translate")("customField.emptyInputValue"),
                                $filter("translate")("customField.emptyAddValue")); //  입력 값 확인 알림, 입력한 값이 없습니다.
                            return;
                        }
                        $scope.vm.form.options.push($scope.vm.form.optionText);
@@ -81,7 +82,8 @@
                        }, 200);
                    }
                    else {
                        SweetAlert.warning($filter("translate")("customField.duplicateInputValue"), $filter("translate")("customField.alreadyAddedValue")); // "입력 값 중복 알림", "입력한 값이 이미 추가되어 있습니다."
                        SweetAlert.warning($filter("translate")("customField.duplicateInputValue"),
                            $filter("translate")("customField.alreadyAddedValue")); // "입력 값 중복 알림", "입력한 값이 이미 추가되어 있습니다."
                    }
                }
src/main/webapp/scripts/app/customField/customFieldList.controller.js
@@ -46,6 +46,24 @@
                        }, {
                            fieldKey : "SINGLE_SELECT",
                            fieldValue : $filter("translate")("common.singleSelectionField") //"단일 선택 필드"
                        }, {
                            fieldKey : "NUMBER",
                            fieldValue : $filter("translate")("common.numberField") //"숫자 필드"
                        }, {
                            fieldKey : "DATETIME",
                            fieldValue : $filter("translate")("common.datetimeField") //"날짜 필드"
                        }, {
                            fieldKey : "IP_ADDRESS",
                            fieldValue : $filter("translate")("common.ipAddressField") //"IP Address 필드"
                        }, {
                            fieldKey : "EMAIL",
                            fieldValue : $filter("translate")("common.emailField") //"이메일 필드"
                        }, {
                            fieldKey : "SITE",
                            fieldValue : $filter("translate")("common.siteField") //"홈페이지 필드"
                        }, {
                            fieldKey : "TEL",
                            fieldValue : $filter("translate")("common.telField") //"전화번호 필드"
                        }]
                    }
                };
src/main/webapp/scripts/app/ispField/ispField.js
@@ -32,7 +32,7 @@
                    resolve : {
                        loadController : ["$q", function ($q) {
                            var deferred = $q.defer();
                            require(["ispFieldListController", 'jsTable', 'tableColumnGenerator', 'ispFieldService', 'modalFormAutoScroll'
                            require(["ispFieldListController", "ispFieldAddController", 'jsTable', 'tableColumnGenerator', 'ispFieldService', 'modalFormAutoScroll'
                                , 'ispFieldAddController', 'ispFieldModifyController'
                                , 'formSubmit', 'jsShortCut', 'inputRegex'], function () {
                                deferred.resolve();
src/main/webapp/scripts/app/issueType/issueTypeAdd.controller.js
@@ -33,6 +33,16 @@
                        }
                    },
                    options : {
                        emailTemplates : [{
                            fieldKey : "COMPANY",
                            fieldValue : "업체"
                        }, {
                            fieldKey : "ISP",
                            fieldValue : "ISP",
                        }, {
                            fieldKey : "HOSTING",
                            fieldValue : "호스팅"
                        }],
                        callbacks: {
                            onImageUpload: function (data) {
                                data.pop();
src/main/webapp/scripts/app/issueType/issueTypeList.controller.js
@@ -63,6 +63,13 @@
                        .setDAlign("text-center")
                        .setDRenderer("COMMON_MODIFY"));
                    $scope.vm.tableConfigs.push($tableProvider.config()
                        .setHName("issue.useProjects")
                        .setHWidth("bold")
                        .setDType("renderer")
                        .setDName("name")
                        .setDAlign("text-center")
                        .setDRenderer("USE_PROJECT_LIST"));
                    $scope.vm.tableConfigs.push($tableProvider.config()
                        .setHName("common.color")
                        .setHWidth("width-140-p bold")
                        .setDType("renderer")
src/main/webapp/scripts/app/project/projectList.controller.js
@@ -107,17 +107,11 @@
                        .setDAlign("text-center")
                        .setDRenderer("PROJECT_MANAGER"));
                    $scope.vm.tableConfigs.push($tableProvider.config()
                        .setHName("dashboard.teamMember")
                        .setHName("project.projectDepartment")
                        .setHWidth("width-140-p bold")
                        .setDType("renderer")
                        .setDAlign("text-center")
                        .setDRenderer("PROJECT_USER"));
                    $scope.vm.tableConfigs.push($tableProvider.config()
                        .setHName("common.period")
                        .setHWidth("width-120-p bold")
                        .setDType("renderer")
                        .setDAlign("text-center")
                        .setDRenderer("PROJECT_DUE_DATE"));
                    $scope.vm.tableConfigs.push($tableProvider.config()
                        .setHName("project.projectKey")
                        .setHWidth("width-100-p bold")
src/main/webapp/scripts/components/api/api.service.js
New file
@@ -0,0 +1,29 @@
'use strict';
define([
    'app'
], function (app) {
    app.factory("Api", ['$http', '$log', function ($http, $log) {
        return {
            find : function (conditions) {
                return $http.post("apiToken/find", conditions).then(function (response) {
                    $log.debug("토큰 데이터 : ", response);
                    return response;
                });
            },
            add : function (conditions) {
                return $http.post("apiToken/add", conditions).then(function (response) {
                    $log.debug("토큰 생성 결과 : ", response);
                    return response;
                });
            },
            remove : function (conditions) {
                return $http.post("apiToken/remove", conditions).then(function (response) {
                    $log.debug("토큰 삭제 결과 : ", response);
                    return response;
                });
            }
        }
    }
    ]);
});
src/main/webapp/scripts/main.js
@@ -325,6 +325,7 @@
        /* api */
        'apiRoute' : 'app/api/api',  //  api route 정보
        'apiService' : 'components/api/api.service',  // api 관련된 통신 담당
        'apiAuthController' :'app/api/apiAuth.controller',   // api 인증 컨트롤러
        'apiSettingController' : 'app/api/apiSetting.controller',    // api 설정 컨트롤러
        'apiMonitorController' : 'app/api/apiMonitor.controller', // api 모니터링 컨트롤러
src/main/webapp/views/api/apiAuth.html
@@ -8,7 +8,7 @@
            </h6>
            <div class="element-box">
                <form role="form" name="issueStatusAddForm">
                <form role="form" name="apiTokenAddForm">
                    <div class="form-group">
                        <label for="apiApplicationForm1"><span translate="api.application">어플리케이션 이름</span> <code class="highlighter-rouge">*</code></label>
                        <input id="apiApplicationForm1"
@@ -19,7 +19,7 @@
                               kr-input
                               autocomplete="off"
                               maxlength="50"
                               ng-model="vm.form.name"
                               ng-model="vm.form.appName"
                               ng-maxlength="20"
                               required>
                    </div>
@@ -32,18 +32,18 @@
                                   name="token"
                                   class="form-control"
                                   disabled
                                   ng-model="vm.form.token"
                                   required>
                                   ng-model="vm.form.token">
                        </div>
                    </div>
                </form>
                <div class="modal-footer buttons-on-right">
                    <button type="button" class="btn btn-md btn-grey" ng-click="fn.cancel()"><span translate="api.delete">취소</span></button>
                    <button type="button" class="btn btn-md btn-primary bold"
                            js-short-cut
                            js-short-cut-action="(fn.formCheck(issueStatusAddForm.$invalid) || $root.spinner) ? null : fn.formSubmit()"
                            ng-click="fn.formSubmit()"><span translate="api.create">생성</span>
                <div class="buttons-on-right">
                    <button type="button" class="btn btn-md btn-grey" ng-click="fn.remove()"><span translate="api.delete">취소</span></button>
                        <button type="button" class="btn btn-md btn-primary bold"
                                js-short-cut
                                js-short-cut-action="(fn.formCheck(apiTokenAddForm.$invalid) || $root.spinner) ? null : fn.formSubmit()"
                                ng-disabled="fn.formCheck(apiTokenAddForm.$invalid)"
                                ng-click="fn.formSubmit()"><span translate="api.create">생성</span>
                    </button>
                </div>
            </div>
src/main/webapp/views/api/apiOverlapAdd.html
New file
@@ -0,0 +1,36 @@
<div class="formModal">
    <div class="modal-header faded smaller">
        <div class="modal-title">
            <strong translate="api.addOverlapField">사용자 정의 필드 만들기</strong>
        </div>
        <button aria-label="Close" class="close" type="button" ng-click="fn.cancel()">
            <span aria-hidden="true"> &times;</span>
        </button>
    </div>
    <div class="modal-body">
        <form role="form" name="customFieldAddForm">
            <div class="form-group">
                <label for="customFieldAddForm2"><span>필드 선택</span> <code
                        class="highlighter-rouge">*</code></label>
                <select id="customFieldAddForm2" class="form-control" ng-model="vm.form.customFieldType"
                        ng-change="fn.changeFieldType()">
                    <option value="INPUT" >경유지</option>
                    <option value="SINGLE_SELECT" >IP</option>
                    <option value="NUMBER" >국가</option>
                </select>
            </div>
        </form>
    </div>
    <div class="modal-footer buttons-on-right">
        <button type="button" class="btn btn-md btn-grey" ng-click="fn.cancel()"><span
                translate="common.cancel">취소</span></button>
        <button type="button" class="btn btn-md btn-primary bold"
                js-short-cut
                js-short-cut-action="(fn.formCheck(customFieldAddForm.$invalid) || $root.spinner) ? null : fn.formSubmit()"
                ng-disabled="fn.formCheck(customFieldAddForm.$invalid)"
                ng-click="fn.formSubmit()"><span translate="common.save">저장</span>
        </button>
    </div>
</div>
src/main/webapp/views/api/apiSetting.html
@@ -1,272 +1,42 @@
<div class="row">
    <div class="col-sm-12">
        <div class="element-wrapper">
            <div class="element-actions">
                <button ng-click="fn.add()"
                        class="btn btn-xlg btn-danger"><i class="os-icon os-icon-plus"></i> <span translate="api.addOverlapField">필드 만들기</span>
                </button>
            </div>
            <div class="element-actions" ng-if="$root.checkMngPermission('USER_PERMISSION_MNG_ISSUE_STATUS')">
            </div>
            <h6 class="element-header" translate="api.columnSetting">
            <h6 class="element-header" translate="api.setting">
                설정
            </h6>
            <!--
            <div class="os-tabs-w">
                <div class="os-tabs-controls">
                    <ul class="nav nav-tabs upper">
                        <li class="nav-item">
                            <a class="nav-link cursor" translate="api.example">사용 예시</a>
                            <a class="nav-link cursor" ng-class="{ 'active' : vm.tab == 'API_COL_SETTING' }" ng-click="fn.changeTab('API_COL_SETTING')" translate="api.columnSetting">사용자 등급 관리</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link cursor" ng-class="{ 'active' : vm.tab == 'API_OVERLAP_SETTING' }" ng-click="fn.changeTab('API_OVERLAP_SETTING')" translate="api.overlapSetting">부서 관리</a>
                        </li>
                    </ul>
                </div>
            </div>
            <div class="element-box">
            </div>
-->
            <div class="row">
                <div class="col-md-4">
                    <div class="form-group mb10">
                        <label for="issueAddForm4" class="issue-label"> <span
                                translate="issue.issueType">이슈 타입</span>
                        </label>
                        <select id="issueAddForm4"
                                name="issueType"
                                class="form-control input-sm issue-select-label"
                                ng-model="vm.form.issueTypeId"
                                ng-change="fn.getIssueTypeCustomFields()"
                                ng-style="{ 'color' : fn.getOptionColor(vm.issueTypes, vm.form.issueTypeId) }"
                                required>
                            <option ng-style="{ 'color' : '#353535' }" value="">홈페이지 변조 감지
                            </option>
                            <option ng-style="{ 'color' : '#353535' }" value="">경유지 탐지
                            </option>
                            <option ng-repeat="issueType in vm.issueTypes"
                                    ng-style="{ 'color' : issueType.color, 'font-weight': 600 }"
                                    value="{{issueType.id}}">●&nbsp;{{issueType.name}}
                            </option>
                        </select>
                    </div>
            <div class="tab-content mt-30">
                <div ng-show="vm.tab == 'API_COL_SETTING'">
                    <div ng-include include-replace src="'views/api/apiSettingColumn.html'"></div>
                </div>
                <div ng-show="vm.tab == 'API_OVERLAP_SETTING'">
                    <div ng-include include-replace src="'views/api/apiSettingOverlap.html'"></div>
                </div>
            </div>
            <div class="element-box">
                <form role="form" name="issueAddForm">
                    <div class="form-group mb10">
                        <label for="issueAddForm1" class="issue-label"><span translate="issue.issueTitle">일감 제목</span> </label>
                        <input id="issueAddForm1"
                               class="form-control input-sm"
                               ng-model="vm.form.title"
                               name="title"
                               required
                               kr-input
                               value="홈페이지 변조 감지 건"
                               maxlength="300"
                               autocomplete="off"
                               autofocus
                               owl-auto-focus>
                    </div>
                    <div class="row">
                        <div class="col-lg-4">
                            <div class="form-group mb10">
                                <label class="issue-label"> <span translate="common.project">프로젝트</span> </label>
                                <select id="issueAddForm6"
                                        name="issueType"
                                        class="form-control input-sm issue-select-label"
                                        ng-model="vm.form.issueTypeId"
                                        ng-change="fn.getIssueTypeCustomFields()"
                                        ng-style="{ 'color' : fn.getOptionColor(vm.issueTypes, vm.form.issueTypeId) }"
                                        required>
                                    <option ng-style="{ 'color' : '#353535' }" value="">홈페이지 변조 감지
                                    </option>
                                    <option ng-style="{ 'color' : '#353535' }" value="">MCF
                                    </option>
                                    <option ng-repeat="issueType in vm.issueTypes"
                                            ng-style="{ 'color' : issueType.color, 'font-weight': 600 }"
                                            value="{{issueType.id}}">●&nbsp;{{issueType.name}}
                                    </option>
                                </select>
                            </div>
                        </div>
                        <div class="col-lg-8 bdl1">
                            <div class="row">
                                <div class="col-md-4">
                                    <div class="form-group mb10">
                                        <label for="issueAddForm2" class="issue-label">
                                            <span translate="common.priority">우선 순위</span>
                                        </label>
                                        <select id="issueAddForm2"
                                                name="priority"
                                                class="form-control input-sm issue-select-label"
                                                ng-model="vm.form.priorityId"
                                                ng-style="{ 'color' : fn.getOptionColor(vm.priorities, vm.form.priorityId) }"
                                                required>
                                            <option value="" ng-style="{ 'color' : '#353535' }">
                                                <span >보통</span>
                                            </option>
                                            <option value="" ng-style="{ 'color' : '#353535' }">
                                                <span >높음</span>
                                            </option>
                                            <option value="" ng-style="{ 'color' : '#353535' }">
                                                <span >낮음</span>
                                            </option>
                                            <option ng-repeat="priority in vm.priorities"
                                                    ng-style="{ 'color' : priority.color, 'font-weight': 600 }"
                                                    value="{{priority.id}}"
                                                    translate="{{priority.name}}">
                                            </option>
                                        </select>
                                    </div>
                                </div>
                                <div class="col-md-4">
                                    <div class="form-group mb10">
                                        <label for="issueAddForm3" class="issue-label"> <span
                                                translate="common.importance">중요도</span> </label>
                                        <select id="issueAddForm3"
                                                name="severity"
                                                class="form-control input-sm issue-select-label"
                                                ng-model="vm.form.severityId"
                                                ng-style="{ 'color' : fn.getOptionColor(vm.severities, vm.form.severityId) }"
                                                required>
                                            <option value="" ng-style="{ color : '#353535' }">
                                                <span>보통</span>
                                            </option>
                                            <option value="" ng-style="{ color : '#353535' }">
                                                <span>높음</span>
                                            </option>
                                            <option value="" ng-style="{ color : '#353535' }">
                                                <span>낮음</span>
                                            </option>
                                            <option ng-repeat="severity in vm.severities"
                                                    ng-style="{ color : severity.color, 'font-weight': 600 }"
                                                    value="{{severity.id}}"
                                                    translate="{{severity.name}}">
                                            </option>
                                        </select>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                    <div class="form-group mb10">
                        <label class="issue-label"><span translate="common.content">내용</span></label>
                        <summernote
                                class="summernote"
                                lang="ko-KR"
                                summer-note-auto-focus
                                ng-model="vm.form.description"
                                data-editor="vm.summerNote.editor"
                                data-editable="vm.summerNote.editable"
                                on-image-upload="fn.imageUpload(files)"
                                target=".note-editable"></summernote>
                    </div>
                    <div class="row">
                        <div class="col-lg-12">
                            <div class="form-group mb10">
                                <label for="issueAddForm5" class="issue-label"> <span translate="common.period">기간</span>
                                </label>
                                <input id="issueAddForm5"
                                       tabindex="-1"
                                       type="text"
                                       readonly
                                       class="form-control cursor"
                                       placeholder="{{'issue.clickToSelectDate' | translate}}"
                                       ng-model="vm.form.startCompleteDateRange"
                                       modal-form-auto-scroll
                                       date-format="YYYY-MM-DD"
                                       parent-el="'#createdWidget'"
                                       date-range-picker>
                                <div class="row">
                                    <div class="col-xs-12">
                                        <div id="createdWidget" class="bootstrap-datepicker"></div>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                    <div class="row">
                        <div class="col-lg-3">
                            <div class="form-group mb10">
                                <div class="form-group mb10">
                                    <label class="issue-label"> <span translate="common.assigneeTeam">담당부서</span> </label>
                                    <select id="issueAddForm"
                                            name="issueType"
                                            class="form-control input-sm issue-select-label"
                                            ng-model="vm.form.issueTypeId"
                                            ng-change="fn.getIssueTypeCustomFields()"
                                            ng-style="{ 'color' : fn.getOptionColor(vm.issueTypes, vm.form.issueTypeId) }"
                                            required>
                                        <option value="" ng-style="{ 'color' : '#353535' }"><span>조치용역</span></option>
                                        <option value="" ng-style="{ 'color' : '#353535' }"><span>분석용역</span></option>
                                        <option value="" ng-style="{ 'color' : '#353535' }"><span>상황실</span></option>
                                        <option ng-repeat="issueType in vm.issueTypes"
                                                ng-style="{ 'color' : issueType.color, 'font-weight': 600 }"
                                                value="{{issueType.id}}">●&nbsp;{{issueType.name}}
                                        </option>
                                    </select>
                                </div>
                            </div>
                        </div>
                        <div class="col-lg-3">
                            <div class="form-group mb10">
                                <div class="form-group mb10">
                                    <label class="issue-label"> <span>업종</span> </label>
                                    <select id="issueAddFormIP"
                                            name="issueType"
                                            class="form-control input-sm issue-select-label"
                                            ng-model="vm.form.issueTypeId"
                                            ng-change="fn.getIssueTypeCustomFields()"
                                            ng-style="{ 'color' : fn.getOptionColor(vm.issueTypes, vm.form.issueTypeId) }"
                                            required>
                                        <option value="" translate="common.selectTarget" ng-style="{ 'color' : '#353535' }"><span>대상 선택</span>
                                        </option>
                                        <option ng-repeat="issueType in vm.issueTypes"
                                                ng-style="{ 'color' : issueType.color, 'font-weight': 600 }"
                                                value="{{issueType.id}}">●&nbsp;{{issueType.name}}
                                        </option>
                                    </select>
                                </div>
                            </div>
                        </div>
                        <div class="col-lg-3">
                            <div class="form-group mb10">
                                <div class="form-group mb10">
                                    <label class="issue-label"> <span>도메인</span> </label>
                                    <input id="issueAddForm8"
                                           tabindex="-1"
                                           type="text"
                                           class="form-control cursor"
                                           />
                                </div>
                            </div>
                        </div>
                        <div class="col-lg-3">
                            <div class="form-group mb10">
                                <div class="form-group mb10">
                                    <label class="issue-label"> <span>경유지IP</span> </label>
                                    <input id="issueAddForm*"
                                           tabindex="-1"
                                           type="text"
                                           class="form-control cursor"
                                           />
                                </div>
                            </div>
                        </div>
                    </div>
                </form>
            </div>
        </div>
src/main/webapp/views/api/apiSettingColumn.html
New file
@@ -0,0 +1,252 @@
<div class="row">
    <div class="col-md-4">
        <div class="form-group mb10">
            <label for="issueAddForm4" class="issue-label"> <span
                    translate="issue.issueType">이슈 타입</span>
            </label>
            <select id="issueAddForm4"
                    name="issueType"
                    class="form-control input-sm issue-select-label"
                    ng-model="vm.form.issueTypeId"
                    ng-change="fn.getIssueTypeCustomFields()"
                    ng-style="{ 'color' : fn.getOptionColor(vm.issueTypes, vm.form.issueTypeId) }"
                    required>
                <option ng-style="{ 'color' : '#353535' }" value="">홈페이지 변조 감지
                </option>
                <option ng-style="{ 'color' : '#353535' }" value="">경유지 탐지
                </option>
                <option ng-repeat="issueType in vm.issueTypes"
                        ng-style="{ 'color' : issueType.color, 'font-weight': 600 }"
                        value="{{issueType.id}}">●&nbsp;{{issueType.name}}
                </option>
            </select>
        </div>
    </div>
</div>
<div class="element-box">
    <form role="form" name="issueAddForm">
        <div class="form-group mb10">
            <label for="issueAddForm1" class="issue-label"><span translate="issue.issueTitle">일감 제목</span> </label>
            <input id="issueAddForm1"
                   class="form-control input-sm"
                   ng-model="vm.form.title"
                   name="title"
                   required
                   kr-input
                   value="홈페이지 변조 감지 건"
                   maxlength="300"
                   autocomplete="off"
                   autofocus
                   owl-auto-focus>
        </div>
        <div class="row">
            <div class="col-lg-4">
                <div class="form-group mb10">
                    <label class="issue-label"> <span translate="common.project">프로젝트</span> </label>
                    <select id="issueAddForm6"
                            name="issueType"
                            class="form-control input-sm issue-select-label"
                            ng-model="vm.form.issueTypeId"
                            ng-change="fn.getIssueTypeCustomFields()"
                            ng-style="{ 'color' : fn.getOptionColor(vm.issueTypes, vm.form.issueTypeId) }"
                            required>
                        <option ng-style="{ 'color' : '#353535' }" value="">홈페이지 변조 감지
                        </option>
                        <option ng-style="{ 'color' : '#353535' }" value="">MCF
                        </option>
                        <option ng-repeat="issueType in vm.issueTypes"
                                ng-style="{ 'color' : issueType.color, 'font-weight': 600 }"
                                value="{{issueType.id}}">●&nbsp;{{issueType.name}}
                        </option>
                    </select>
                </div>
            </div>
            <div class="col-lg-8 bdl1">
                <div class="row">
                    <div class="col-md-4">
                        <div class="form-group mb10">
                            <label for="issueAddForm2" class="issue-label">
                                <span translate="common.priority">우선 순위</span>
                            </label>
                            <select id="issueAddForm2"
                                    name="priority"
                                    class="form-control input-sm issue-select-label"
                                    ng-model="vm.form.priorityId"
                                    ng-style="{ 'color' : fn.getOptionColor(vm.priorities, vm.form.priorityId) }"
                                    required>
                                <option value="" ng-style="{ 'color' : '#353535' }">
                                    <span >보통</span>
                                </option>
                                <option value="" ng-style="{ 'color' : '#353535' }">
                                    <span >높음</span>
                                </option>
                                <option value="" ng-style="{ 'color' : '#353535' }">
                                    <span >낮음</span>
                                </option>
                                <option ng-repeat="priority in vm.priorities"
                                        ng-style="{ 'color' : priority.color, 'font-weight': 600 }"
                                        value="{{priority.id}}"
                                        translate="{{priority.name}}">
                                </option>
                            </select>
                        </div>
                    </div>
                    <div class="col-md-4">
                        <div class="form-group mb10">
                            <label for="issueAddForm3" class="issue-label"> <span
                                    translate="common.importance">중요도</span> </label>
                            <select id="issueAddForm3"
                                    name="severity"
                                    class="form-control input-sm issue-select-label"
                                    ng-model="vm.form.severityId"
                                    ng-style="{ 'color' : fn.getOptionColor(vm.severities, vm.form.severityId) }"
                                    required>
                                <option value="" ng-style="{ color : '#353535' }">
                                    <span>보통</span>
                                </option>
                                <option value="" ng-style="{ color : '#353535' }">
                                    <span>높음</span>
                                </option>
                                <option value="" ng-style="{ color : '#353535' }">
                                    <span>낮음</span>
                                </option>
                                <option ng-repeat="severity in vm.severities"
                                        ng-style="{ color : severity.color, 'font-weight': 600 }"
                                        value="{{severity.id}}"
                                        translate="{{severity.name}}">
                                </option>
                            </select>
                        </div>
                    </div>
                </div>
            </div>
        </div>
        <div class="form-group mb10">
            <label class="issue-label"><span translate="common.content">내용</span></label>
            <summernote
                    class="summernote"
                    lang="ko-KR"
                    summer-note-auto-focus
                    ng-model="vm.form.description"
                    data-editor="vm.summerNote.editor"
                    data-editable="vm.summerNote.editable"
                    on-image-upload="fn.imageUpload(files)"
                    target=".note-editable"></summernote>
        </div>
        <div class="row">
            <div class="col-lg-12">
                <div class="form-group mb10">
                    <label for="issueAddForm5" class="issue-label"> <span translate="common.period">기간</span>
                    </label>
                    <input id="issueAddForm5"
                           tabindex="-1"
                           type="text"
                           readonly
                           class="form-control cursor"
                           placeholder="{{'issue.clickToSelectDate' | translate}}"
                           ng-model="vm.form.startCompleteDateRange"
                           modal-form-auto-scroll
                           date-format="YYYY-MM-DD"
                           parent-el="'#createdWidget'"
                           date-range-picker>
                    <div class="row">
                        <div class="col-xs-12">
                            <div id="createdWidget" class="bootstrap-datepicker"></div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
        <div class="row">
            <div class="col-lg-3">
                <div class="form-group mb10">
                    <div class="form-group mb10">
                        <label class="issue-label"> <span translate="common.assigneeTeam">담당부서</span> </label>
                        <select id="issueAddForm"
                                name="issueType"
                                class="form-control input-sm issue-select-label"
                                ng-model="vm.form.issueTypeId"
                                ng-change="fn.getIssueTypeCustomFields()"
                                ng-style="{ 'color' : fn.getOptionColor(vm.issueTypes, vm.form.issueTypeId) }"
                                required>
                            <option value="" ng-style="{ 'color' : '#353535' }"><span>조치용역</span></option>
                            <option value="" ng-style="{ 'color' : '#353535' }"><span>분석용역</span></option>
                            <option value="" ng-style="{ 'color' : '#353535' }"><span>상황실</span></option>
                            <option ng-repeat="issueType in vm.issueTypes"
                                    ng-style="{ 'color' : issueType.color, 'font-weight': 600 }"
                                    value="{{issueType.id}}">●&nbsp;{{issueType.name}}
                            </option>
                        </select>
                    </div>
                </div>
            </div>
            <div class="col-lg-3">
                <div class="form-group mb10">
                    <div class="form-group mb10">
                        <label class="issue-label"> <span>업종</span> </label>
                        <select id="issueAddFormIP"
                                name="issueType"
                                class="form-control input-sm issue-select-label"
                                ng-model="vm.form.issueTypeId"
                                ng-change="fn.getIssueTypeCustomFields()"
                                ng-style="{ 'color' : fn.getOptionColor(vm.issueTypes, vm.form.issueTypeId) }"
                                required>
                            <option value="" translate="common.selectTarget" ng-style="{ 'color' : '#353535' }"><span>대상 선택</span>
                            </option>
                            <option ng-repeat="issueType in vm.issueTypes"
                                    ng-style="{ 'color' : issueType.color, 'font-weight': 600 }"
                                    value="{{issueType.id}}">●&nbsp;{{issueType.name}}
                            </option>
                        </select>
                    </div>
                </div>
            </div>
            <div class="col-lg-3">
                <div class="form-group mb10">
                    <div class="form-group mb10">
                        <label class="issue-label"> <span>도메인</span> </label>
                        <input id="issueAddForm8"
                               tabindex="-1"
                               type="text"
                               class="form-control cursor"
                        />
                    </div>
                </div>
            </div>
            <div class="col-lg-3">
                <div class="form-group mb10">
                    <div class="form-group mb10">
                        <label class="issue-label"> <span>경유지IP</span> </label>
                        <input id="issueAddForm*"
                               tabindex="-1"
                               type="text"
                               class="form-control cursor"
                        />
                    </div>
                </div>
            </div>
        </div>
    </form>
    <div class="modal-footer buttons-on-right">
        <button type="button" class="btn btn-md btn-primary bold"
                js-short-cut
                js-short-cut-action="(fn.formCheck(issueStatusAddForm.$invalid) || $root.spinner) ? null : fn.formSubmit()"
                ng-click="fn.formSubmit()"><span translate="common.saved">생성</span>
        </button>
    </div>
</div>
src/main/webapp/views/api/apiSettingOverlap.html
New file
@@ -0,0 +1,36 @@
<div class="row">
    <div class="col-sm-5">
        <div class="element-wrapper">
            <label for="issueAddForm4" class="issue-label"> <span
                    translate="issue.issueType">이슈 타입</span>
            </label>
            <select id="issueAddForm4"
                    name="issueType"
                    class="form-control input-sm issue-select-label"
                    ng-model="vm.form.issueTypeId"
                    ng-change="fn.getIssueTypeCustomFields()"
                    ng-style="{ 'color' : fn.getOptionColor(vm.issueTypes, vm.form.issueTypeId) }"
                    required>
                <option ng-style="{ 'color' : '#353535' }" value="">홈페이지 변조 감지
                </option>
                <option ng-style="{ 'color' : '#353535' }" value="">경유지 탐지
                </option>
                <option ng-repeat="issueType in vm.issueTypes"
                        ng-style="{ 'color' : issueType.color, 'font-weight': 600 }"
                        value="{{issueType.id}}">●&nbsp;{{issueType.name}}
                </option>
            </select>
        </div>
    </div>
</div>
<div class="element-box">
    <p class="btn btn-secondary">
        <span >경유지</span>
        <span>×</span>
    </p>
</div>
src/main/webapp/views/common/sidebar.html
@@ -69,8 +69,6 @@
                    </ul>
                </div>
            </div>
        </div>
        <!--------------------
@@ -85,14 +83,6 @@
                    <span translate="common.dashboard">대시보드</span>
                </a>
            </li>
            <li class="pointer">
                <a ng-click="fn.moveMenu('issues.list')" tabindex="-1">
                    <div class="icon-w">
                        <div class="os-icon os-icon-layers"></div>
                    </div>
                    <span translate="issue.managementIssue">이슈 관리</span></a>
            </li>
            <!--
            <li class="">
                <a ui-sref="tasks.agileBoard" tabindex="-1">
                    <div class="icon-w">
@@ -258,16 +248,42 @@
            </a>
        </li>
        <li class="sub-header">
            <span>Tasks</span>
            <span>ISSUE LIST</span>
        </li>
        <li class="pointer">
            <a ng-click="fn.moveMenu('issues.list')" tabindex="-1">
        <!-- UI문서 제작용 임시 코드 시작-->
        <li class="">
            <a ui-sref="dashboards.dashboard" tabindex="-1">
                <div class="icon-w">
                    <div class="os-icon os-icon-layers"></div>
                    <div class="os-icon os-icon-layout"></div>
                </div>
                <span translate="issue.managementIssue">이슈 관리</span>
                <span>악성 도메인</span>
            </a>
        </li>
        <li class="">
            <a ui-sref="dashboards.dashboard" tabindex="-1">
                <div class="icon-w">
                    <div class="os-icon os-icon-layout"></div>
                </div>
                <span>경유지 대응</span>
            </a>
        </li>
        <li class="">
            <a ui-sref="dashboards.dashboard" tabindex="-1">
                <div class="icon-w">
                    <div class="os-icon os-icon-layout"></div>
                </div>
                <span>유포지 대응</span>
            </a>
        </li>
        <li class="">
            <a ui-sref="dashboards.dashboard" tabindex="-1">
                <div class="icon-w">
                    <div class="os-icon os-icon-layout"></div>
                </div>
                <span>분석결과 대응</span>
            </a>
        </li>
        <!-- UI문서 제작용 임시 코드 끝-->
        <!--
        <li class="">
            <a ui-sref="tasks.agileBoard" tabindex="-1">
@@ -389,11 +405,11 @@
                <span translate="guide.manageGuide">가이드 관리</span></a>
        </li>
        <li class="sub-header">
        <li class="sub-header" ng-if="$root.checkMngPermission('USER_PERMISSION_MNG_API')">
            <span>API</span>
        </li>
        <li>
        <li ng-if="$root.checkMngPermission('USER_PERMISSION_MNG_API')">
            <a ui-sref="api.auth" tabindex="-1">
                <div class="icon-w">
                    <div class="os-icon os-icon-package"></div>
@@ -401,14 +417,14 @@
                <span translate="api.auth">API 인증</span></a>
        </li>
        <li>
        <li ng-if="$root.checkMngPermission('USER_PERMISSION_MNG_API')">
            <a ui-sref="api.setting" tabindex="-1">
                <div class="icon-w">
                    <div class="os-icon os-icon-package"></div>
                </div>
                <span translate="api.setting">API 설정</span></a>
        </li>
        <li>
        <li ng-if="$root.checkMngPermission('USER_PERMISSION_MNG_API')">
            <a ui-sref="api.monitor" tabindex="-1">
                <div class="icon-w">
                    <div class="os-icon os-icon-package"></div>
src/main/webapp/views/customField/customFieldAdd.html
@@ -38,7 +38,12 @@
                        ng-change="fn.changeFieldType()">
                    <option value="INPUT" translate="common.stringField">문자열 필드</option>
                    <option value="SINGLE_SELECT" translate="common.singleSelectionField">단일 선택 필드</option>
                    <option value="MULTI_SELECT" translate="common.multipleSelectionField">다중 선택 필드</option>
                    <option value="NUMBER" translate="common.numberField">숫자 필드</option>
                    <option value="DATETIME" translate="common.datetimeField">날짜 필드</option>
                    <option value="IP_ADDRESS" translate="common.ipAddressField">IP 주소 필드</option>
                    <option value="EMAIL" translate="common.emailField">이메일 필드</option>
                    <option value="SITE" translate="common.siteField">홈페이지 주소 필드</option>
                    <option value="TEL" translate="common.telField">전화번호 필드</option>
                </select>
            </div>
src/main/webapp/views/issueType/issueTypeAdd.html
@@ -43,6 +43,28 @@
            </div>
            <div class="form-group">
                <label><span translate="issue.useProjects">사용 프로젝트</span></label>
                <js-autocomplete-single data-input-name="workflow"
                                        selected-model="vm.form.workflows"
                                        search="vm.workflowName"
                                        source="fn.getWorkflowList(vm.workflowName, vm.form.workflows, vm.autoCompletePage.workflow.page, fn.getWorkflowListCallBack)"
                                        page="vm.autoCompletePage.workflow.page"
                                        total-page="vm.autoCompletePage.workflow.totalPage"
                                        input-disabled="false"
                                        extra-settings="{ displayProp : 'name' , idProp : 'id', imageable : false, imagePathProp : '', type : '', maxlength : 200, autoResize : false }"></js-autocomplete-single>
            </div>
            <div class="form-group">
                <label><span
                        translate="issue.companyInfo">업체/ISP/호스팅 정보</span></label>
                <ng-dropdown-multiselect class="multiSelect cursor"
                                         data-input-name="issueStatusTypes"
                                         selected-model="vm.search.issueStatusTypes"
                                         extra-settings="{ stringTypeOption : true }"
                                         options="vm.options.emailTemplates"></ng-dropdown-multiselect>
            </div>
            <div class="form-group">
                <label for="issueTypeAddForm2"><span translate="common.color">색상</span> <code class="highlighter-rouge">*</code></label>
                <div class="input-group">
                    <input id="issueTypeAddForm2"
src/main/webapp/views/login/login.html
@@ -122,12 +122,14 @@
                    <small>CopyRight WISESTONE All rights reserved.</small>
                </div>
                -->
                <!-- 임시 코드 (슈퍼관리자 추가)
                <div>
                    <button class="btn btn-light"
                            ui-sref="superJoin"
                            translate="users.workspaceJoin">&nbsp;&nbsp;회원가입
                    </button>
                </div>
                -->
                <div class="footer-s" >
                    <small>CopyRight WISESTONE All rights reserved.</small>
                </div>
src/main/webapp/views/project/projectModify.html
@@ -33,7 +33,7 @@
            <div class="row">
                <div class="col-lg-6">
                    <div class="form-group">
                        <label for="projectModifyForm2"><span translate="common.period">기간</span> <code class="highlighter-rouge">*</code></label>
                        <label for="projectModifyForm2"><span translate="common.period">기간</span></label>
                        <input id="projectModifyForm2"
                               type="text"
                               class="form-control input-readonly"
@@ -106,7 +106,7 @@
            </div>
            <div class="form-group">
                <label><span translate="project.projectTeam">프로젝트 팀원</span> </label>
                <label><span translate="project.projectDepartment">프로젝트 부서</span> </label>
                <js-autocomplete-multi data-input-name="users"
                                       selected-model="vm.form.users"
                                       search="vm.userName"