diff --git a/.gitignore b/.gitignore index 023ebdc..2adda4b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ - # Package Files # *.war @@ -32,7 +31,7 @@ local.properties # PyDev specific (Python IDE for Eclipse) *.pydevproject -# CDT-specific (C/C++ Development Tooling) +# CDT-specific (C/++ Development Tooling) .cproject # CDT- autotools @@ -110,7 +109,7 @@ local.properties # *.ipr # CMake -cmake-build-*/ +cmake-build-* # Mongo Explorer plugin .idea/**/mongoSettings.xml @@ -243,4 +242,6 @@ gradle-gf63.sh eapim-admin-kjb/ -.claude \ No newline at end of file +.claude +*.report.md +*.claude.md \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md index 1d04763..e51bbbb 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,6 +1,17 @@ -# CLAUDE.md +# AI-Assisted Development Guide -이 파일은 Claude Code (claude.ai/code)가 이 저장소의 코드를 작업할 때 참고할 가이드를 제공합니다. +이 문서는 AI 어시스턴트가 이 저장소의 코드를 이해하고 기여하는 데 필요한 가이드를 제공합니다. + +## 프로젝트 주요 특징 + +AI 어시스턴트가 작업을 시작하기 전에 반드시 이해해야 할 핵심적인 특징들입니다. + +- **하이브리드 데이터 액세스**: 이 프로젝트는 **JPA(Hibernate)와 iBATIS를 함께 사용**합니다. + - **신규 기능**: 항상 **JPA**와 Spring Data 리포지토리를 사용해야 합니다. + - **레거시 코드**: 기존 iBATIS (`*.xml` 매퍼) 코드를 수정할 때는 해당 패턴을 따라야 합니다. +- **멀티 모듈 Gradle 프로젝트**: `settings.gradle`에 정의된 것처럼 여러 하위 모듈(`elink-online-*`, `kjb-*` 등)로 구성되어 있습니다. 특정 기능은 다른 모듈에 위치할 수 있습니다. +- **QueryDSL 코드 생성**: 빌드 시 `QueryDSL`을 사용하여 Q-class가 자동으로 생성됩니다. IDE에서 엔티티를 찾을 수 없다고 표시되면, `kjb-gradle.sh compileJava`를 먼저 실행하여 코드를 생성해야 합니다. +- **고객사별 커스터마이징**: `ext.kjb` 또는 `custom` 패키지는 특정 고객사(광주은행)를 위한 코드를 포함하므로, 일반적인 기능 수정 시에는 주의가 필요합니다. ## 프로젝트 개요 @@ -27,48 +38,89 @@ eLink EMS (eLink Management System)는 eLink의 웹 기반 관리 서비스로, ## 빌드 및 개발 명령어 ### 빌드 명령어 + +**⚠️ 중요**: 환경변수 문제로 인해 `gradle` 대신 `kjb-gradle.sh` 스크립트를 사용해야 합니다. +- 스크립트 위치: `/c/eactive/workspaces/shell-scripts/kjb-gradle.sh` +- PATH에 등록되어 있으므로 바로 `kjb-gradle.sh` 명령어 사용 가능 + ```bash # 표준 빌드 -gradle build +kjb-gradle.sh build # Weblogic 배포용 빌드 (테스트 제외) -gradle build -x test -Pprofile=weblogic +kjb-gradle.sh build -x test -Pprofile=weblogic # WAR 파일 빌드 -gradle war +kjb-gradle.sh war # 클린 빌드 -gradle clean build +kjb-gradle.sh clean build ``` ### 테스트 실행 ```bash # 모든 테스트 실행 -gradle test +kjb-gradle.sh test # 특정 테스트 클래스 실행 -gradle test --tests "com.example.ClassName" +kjb-gradle.sh test --tests "com.example.ClassName" # 특정 테스트 패키지 실행 (JUnit 플랫폼 사용) -gradle test --tests "com.eactive.eai.rms.*" +kjb-gradle.sh test --tests "com.eactive.eai.rms.*" ``` ### 개발 태스크 ```bash # 패키징 없이 클래스만 컴파일 -gradle classes +kjb-gradle.sh classes # QueryDSL Q-classes 및 기타 애노테이션 생성 -gradle compileJava +kjb-gradle.sh compileJava # 의존성 트리 보기 -gradle dependencies +kjb-gradle.sh dependencies # 사용 가능한 모든 태스크 목록 -gradle tasks --all +kjb-gradle.sh tasks --all ``` +## 로컬 개발 빠른 시작 + +새로운 환경에서 프로젝트를 처음 설정하고 실행하는 권장 절차입니다. + +1. **IDE 설정 파일 생성 (최초 1회)** + ```bash + # Eclipse 사용 시 (기본) + kjb-gradle.sh eclipse + + # IntelliJ IDEA 사용 시 (build.gradle.intellij 파일 사용 필요) + # 1. build.gradle을 build.gradle.eclipse로 백업 + # 2. build.gradle.intellij를 build.gradle로 복사 + # 3. kjb-gradle.sh idea 실행 + ``` + +2. **QueryDSL 등 소스 코드 생성** + ```bash + kjb-gradle.sh compileJava + ``` + +3. **전체 빌드 및 테스트 실행** + ```bash + kjb-gradle.sh build + ``` + +4. **IDE에서 프로젝트 열기** + - Eclipse: `kjb-gradle.sh eclipse` 실행 후, IDE에서 프로젝트를 엽니다. + - IntelliJ: IntelliJ 전용 `build.gradle.intellij`를 `build.gradle`로 교체 후 사용합니다. + +5. **실행 구성(Run Configuration) 설정** + - 사용하는 WAS(Tomcat 등)에 `eapim-admin` 프로젝트를 배포하도록 설정합니다. + - **VM Options**에 위에 명시된 "필수 환경 변수 (Tomcat)"를 모두 추가합니다. + +6. **서버 실행** + - IDE에서 설정한 실행 구성을 시작하여 서버를 실행합니다. + ## 아키텍처 및 모듈 의존성 ### 멀티 모듈 구조 @@ -107,31 +159,32 @@ eapim-admin (root project) - **Lombok**: 애노테이션 프로세싱을 통한 Getters/setters/builders - **MapStruct**: DTO와 엔티티 간의 객체 매퍼 -#### 이중 Q-Class 생성 (Gradle + Eclipse) +#### Q-Class 생성 위치 -이 프로젝트는 **Gradle과 Eclipse 애노테이션 프로세서 모두를 사용**하므로, Q-classes가 **두 위치에 생성**됩니다: - -1. **`build/generated/java/`** - `gradle compileJava` 또는 `gradle build` 실행 시 Gradle이 생성 -2. **`WebContent/generated/`** - Eclipse IDE에서 빌드 시 Eclipse APT가 생성 - -**이는 예상된 동작이며 의도적입니다.** 두 디렉토리 모두 `.gitignore`에 포함되어 있으며 커밋해서는 안 됩니다. +Q-classes는 `build/generated/java/` 디렉토리에 생성됩니다. +- `kjb-gradle.sh compileJava` 또는 `kjb-gradle.sh build` 실행 시 Gradle이 자동으로 생성 +- 이 디렉토리는 `.gitignore`에 포함되어 있으며 커밋해서는 안 됩니다 #### 생성된 코드 작업하기 - **Gradle 빌드**: `build/generated/java/`의 Q-classes가 자동으로 classpath에 포함됩니다 -- **Eclipse IDE**: `WebContent/generated/`의 Q-classes가 자동으로 classpath에 포함됩니다 -- **업데이트 pull 후**: `gradle compileJava`를 실행하거나 Eclipse 프로젝트를 새로고침하여 Q-classes를 재생성합니다 -- **Q-classes IDE 오류 시**: `gradle clean compileJava` 또는 Eclipse → Project → Clean으로 재생성합니다 +- **업데이트 pull 후**: `kjb-gradle.sh compileJava`를 실행하여 Q-classes를 재생성합니다 +- **Q-classes IDE 오류 시**: `kjb-gradle.sh clean compileJava`로 재생성합니다 -중복이 문제를 일으키지 않는 이유: -- 두 도구 모두 동일한 JPA 엔티티에서 동일한 Q-classes를 생성합니다 -- 두 출력 디렉토리 모두 gitignore됩니다 -- 각 도구는 자체 생성 클래스를 사용합니다 (classpath 충돌 없음) +#### IDE별 빌드 설정 파일 + +- **`build.gradle`**: Eclipse 전용 (기본, Eclipse APT 플러그인 포함) +- **`build.gradle.intellij`**: IntelliJ IDEA 전용 + +IntelliJ를 사용하려면 `build.gradle.intellij`를 `build.gradle`로 교체해야 합니다. ## 로컬 개발 환경 설정 ### 필수 환경 변수 (Tomcat) +다음은 로컬 Tomcat 환경에서 개발 시 필요한 시스템 프로퍼티(-D)입니다. +**설정 방법**: IntelliJ, Eclipse 등 IDE의 실행 구성(Run Configuration)에서 'VM options'에 추가하거나, 사용하는 Tomcat의 `bin` 폴더에 `setenv.bat` (Windows) 또는 `setenv.sh` (Linux/macOS) 파일을 생성하여 아래 내용들을 `CATALINA_OPTS` 또는 `JAVA_OPTS` 환경 변수에 추가합니다. + ``` -Deai.datasource.type=DEV -Dinst.Name=emsSvr99 @@ -202,7 +255,7 @@ eapim-admin (root project) WebLogic용 빌드 시, DefaultServlet 문제를 해결하기 위해 `web.xml`을 `weblogic-web.xml`로 교체하는 `weblogic` 프로필을 사용합니다: ```bash -gradle build -x test -Pprofile=weblogic +kjb-gradle.sh build -x test -Pprofile=weblogic ``` ## 기술 스택 @@ -263,6 +316,50 @@ com.eactive.eai 리포지토리는 Spring Data JPA 규칙을 사용하며, 서비스는 `@Service`, 컨트롤러는 `@RestController`/`@Controller`를 사용합니다. +### Multi-tenancy (동적 스키마 전환) + +애플리케이션은 Hibernate Multi-tenancy를 사용하여 **런타임에 스키마를 동적으로 전환**합니다. + +**핵심 컴포넌트:** +- `DataSourceContextHolder` - ThreadLocal로 현재 데이터소스 타입 관리 +- `DataSourceTypeManager` - 데이터소스 타입 상수 정의 (MONITORING, APIGW, EAI, BAT 등) +- `TenantIdentifierResolver` - `DataSourceContextHolder`에서 tenant 식별자 반환 +- `ConfigurableMultiTenantConnectionProvider` - tenant에 따라 `connection.setSchema()` 호출 +- `DataSourceTypeInterceptor` - 요청 파라미터 `serviceType`에서 데이터소스 자동 설정 + +**동작 흐름:** +1. `DataSourceTypeInterceptor.preHandle()` → `request.getParameter("serviceType")` 읽음 +2. `DataSourceContextHolder.setDataSourceType(dataSourceType)` 설정 +3. JPA 쿼리 실행 시 `TenantIdentifierResolver` → `DataSourceContextHolder`에서 tenant 반환 +4. `ConfigurableMultiTenantConnectionProvider` → `connection.setSchema(schema)` 호출 + +**사용 방법:** + +1. **URL 파라미터 방식** (인터셉터가 자동 처리): + ``` + /api/endpoint?serviceType=APIGW + ``` + +2. **Controller에서 명시적 설정** (특정 데이터소스 강제): + ```java + @Controller + public class MyController { + @RequestMapping("/my/api.json") + public String myApi() { + // 서비스 호출 전에 데이터소스 설정 + DataSourceContextHolder.setDataSourceType( + DataSourceTypeManager.getDataSourceType(DataSourceTypeManager.APIGW)); + + return myService.getData(); + } + } + ``` + +**주의사항:** +- `@PersistenceContext`만 사용 (기본 `entityManagerFactory` 사용, Multi-tenancy 적용됨) +- `@PersistenceContext(unitName = "entityManagerFactoryForEMS")`는 **Multi-tenancy 미적용** (MONITORING 스키마 고정) +- Controller에서 `DataSourceContextHolder` 설정 후 Service 메서드를 호출해야 트랜잭션 시작 시점에 올바른 스키마가 적용됨 + ### 주요 초기화 - `AppInitializer` - 시스템 프로퍼티를 설정하는 메인 애플리케이션 시작 빈 - `CustomizingAppInitializer` - 고객사별 초기화 @@ -276,26 +373,38 @@ com.eactive.eai ## IDE 설정 -### Eclipse +### IntelliJ IDEA (권장) ```bash -gradle eclipse -``` - -다음을 생성합니다: -- `.project` 및 `.classpath` 파일 -- Eclipse WTP 설정 (context path: `/monitoring`) -- UTF-8 인코딩 설정 -- QueryDSL/Lombok을 위한 애노테이션 프로세서 설정 - -### IntelliJ IDEA - -```bash -gradle idea +kjb-gradle.sh idea ``` 또는 IntelliJ에서 직접 "Import Gradle Project"를 사용합니다. +기본 `build.gradle`은 IntelliJ 전용으로 설정되어 있습니다. + +### Eclipse + +Eclipse를 사용하려면 Eclipse APT 플러그인이 포함된 별도의 빌드 설정 파일을 사용해야 합니다: + +```bash +# 1. 기존 build.gradle 백업 (필요시) +cp build.gradle build.gradle.intellij + +# 2. Eclipse 전용 설정으로 교체 +cp build.gradle.eclipse build.gradle + +# 3. Eclipse 프로젝트 생성 +kjb-gradle.sh eclipse +``` + +Eclipse 전용 설정(`build.gradle.eclipse`)은 다음을 포함합니다: +- `com.diffplug.eclipse.apt` 플러그인 (Eclipse APT 지원) +- `.project` 및 `.classpath` 파일 생성 +- Eclipse WTP 설정 (context path: `/monitoring`) +- UTF-8 인코딩 설정 +- QueryDSL/Lombok을 위한 애노테이션 프로세서 설정 + ## 테스트 테스트에 사용되는 것들: diff --git a/GEMINI.md b/GEMINI.md new file mode 100644 index 0000000..60e6004 --- /dev/null +++ b/GEMINI.md @@ -0,0 +1,4 @@ +# GEMINI.md + +이 프로젝트에 대한 주요 정보 및 지침은 `CLAUDE.md` 파일을 참조하십시오. +(For key information and guidelines regarding this project, please refer to the `CLAUDE.md` file.) \ No newline at end of file diff --git a/WebContent/WEB-INF/springapp-servlet.xml b/WebContent/WEB-INF/springapp-servlet.xml index 24b98ec..f3a55c6 100644 --- a/WebContent/WEB-INF/springapp-servlet.xml +++ b/WebContent/WEB-INF/springapp-servlet.xml @@ -78,6 +78,9 @@ + + + @@ -99,6 +102,7 @@ + @@ -136,4 +140,5 @@ + diff --git a/WebContent/WEB-INF/sqlmap-config/oracle/sqlMapConfigOfOnl.xml b/WebContent/WEB-INF/sqlmap-config/oracle/sqlMapConfigOfOnl.xml index 9511ee5..44e1f9b 100644 --- a/WebContent/WEB-INF/sqlmap-config/oracle/sqlMapConfigOfOnl.xml +++ b/WebContent/WEB-INF/sqlmap-config/oracle/sqlMapConfigOfOnl.xml @@ -50,6 +50,8 @@ + @@ -199,4 +201,5 @@ + \ No newline at end of file diff --git a/WebContent/WEB-INF/web.xml b/WebContent/WEB-INF/web.xml index f108b9c..1416b0a 100644 --- a/WebContent/WEB-INF/web.xml +++ b/WebContent/WEB-INF/web.xml @@ -9,7 +9,7 @@ contextConfigLocation /WEB-INF/applicationContext.xml - + CrossScriptingFilter com.eactive.eai.rms.common.filter.CrossScriptingFilter @@ -18,7 +18,7 @@ CrossScriptingFilter /* - + encodingFilter org.springframework.web.filter.CharacterEncodingFilter @@ -168,6 +168,6 @@ - 120 + 60 \ No newline at end of file diff --git a/WebContent/WEB-INF/weblogic-web.xml b/WebContent/WEB-INF/weblogic-web.xml index 18e2775..78dcfc4 100644 --- a/WebContent/WEB-INF/weblogic-web.xml +++ b/WebContent/WEB-INF/weblogic-web.xml @@ -141,6 +141,6 @@ - 120 + 60 \ No newline at end of file diff --git a/WebContent/WEB-INF/weblogic.xml b/WebContent/WEB-INF/weblogic.xml index c0900f7..14d1451 100644 --- a/WebContent/WEB-INF/weblogic.xml +++ b/WebContent/WEB-INF/weblogic.xml @@ -6,9 +6,10 @@ monitoring - 1800 + 3600 JSESSIONID_EMS - replicated_if_clustered + memory + diff --git a/WebContent/addon/ag-grid/ag-grid-community.min.js b/WebContent/addon/ag-grid/ag-grid-community.min.js new file mode 100644 index 0000000..800a783 --- /dev/null +++ b/WebContent/addon/ag-grid/ag-grid-community.min.js @@ -0,0 +1 @@ +!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.agGrid=t():e.agGrid=t()}(self,(function(){return function(){var e={7886:function(e,t,a){var r,n=Object.defineProperty,o=Object.getOwnPropertyDescriptor,i=Object.getOwnPropertyNames,s=Object.prototype.hasOwnProperty,l={};((e,t)=>{for(var a in t)n(e,a,{get:t[a],enumerable:!0})})(l,{ClientSideRowModelModule:()=>F}),e.exports=(r=l,((e,t,a,r)=>{if(t&&"object"==typeof t||"function"==typeof t)for(let l of i(t))s.call(e,l)||l===a||n(e,l,{get:()=>t[l],enumerable:!(r=o(t,l))||r.enumerable});return e})(n({},"__esModule",{value:!0}),r));var g=a(3423),c=a(3423),d=a(3423),h=a(3423),u=class{constructor(e,t,a,r,n,o){this.nextId=0,this.rowCountReady=!1,this.allNodesMap={},this.rootNode=e,this.gos=t,this.eventService=a,this.funcColsService=r,this.beans=o,this.selectionService=n,this.rootNode.group=!0,this.rootNode.level=-1,this.rootNode.id="ROOT_NODE_ID",this.rootNode.allLeafChildren=[],this.rootNode.childrenAfterGroup=[],this.rootNode.childrenAfterSort=[],this.rootNode.childrenAfterAggFilter=[],this.rootNode.childrenAfterFilter=[]}getCopyOfNodesMap(){return(0,h._cloneObject)(this.allNodesMap)}getRowNode(e){return this.allNodesMap[e]}setRowData(e){if("string"==typeof e)return void(0,h._warnOnce)("rowData must be an array.");this.rowCountReady=!0,this.dispatchRowDataUpdateStartedEvent(e);const t=this.rootNode,a=this.rootNode.sibling;t.childrenAfterFilter=null,t.childrenAfterGroup=null,t.childrenAfterAggFilter=null,t.childrenAfterSort=null,t.childrenMapped=null,t.updateHasChildren(),this.nextId=0,this.allNodesMap={},e?t.allLeafChildren=e.map(((e,t)=>this.createNode(e,this.rootNode,0,t))):(t.allLeafChildren=[],t.childrenAfterGroup=[]),a&&(a.childrenAfterFilter=t.childrenAfterFilter,a.childrenAfterGroup=t.childrenAfterGroup,a.childrenAfterAggFilter=t.childrenAfterAggFilter,a.childrenAfterSort=t.childrenAfterSort,a.childrenMapped=t.childrenMapped,a.allLeafChildren=t.allLeafChildren)}updateRowData(e){this.rowCountReady=!0,this.dispatchRowDataUpdateStartedEvent(e.add);const t={rowNodeTransaction:{remove:[],update:[],add:[]},rowsInserted:!1},a=[];return this.executeRemove(e,t,a),this.executeUpdate(e,t,a),this.executeAdd(e,t),this.updateSelection(a,"rowDataChanged"),t}updateRowOrderFromRowData(e){const t=this.rootNode.allLeafChildren,a=t?.length??0,r=new Map;let n=-1,o=-1;for(let i=0;i0;a&&this.selectionService.setNodesSelected({newValue:!1,nodes:e,suppressFinishActions:!0,source:t}),this.selectionService.updateGroupsFromChildrenSelections(t),a&&this.eventService.dispatchEvent({type:"selectionChanged",source:t})}executeAdd(e,t){const a=e.add;if((0,h._missingOrEmpty)(a))return;const r=this.rootNode.allLeafChildren;let n=r.length;if("number"==typeof e.addIndex&&(n=this.sanitizeAddIndex(e.addIndex),n>0)){if(this.gos.get("treeData"))for(let e=0;ethis.createNode(e,this.rootNode,0,n+t)));if(n=t||Number.isNaN(e)?t:Math.ceil(e)}executeRemove(e,{rowNodeTransaction:t},a){const{remove:r}=e;if((0,h._missingOrEmpty)(r))return;const n={};r.forEach((e=>{const r=this.lookupRowNode(e);r&&(r.isSelected()&&a.push(r),r.clearRowTopAndRowIndex(),n[r.id]=!0,delete this.allNodesMap[r.id],t.remove.push(r))})),this.rootNode.allLeafChildren=this.rootNode.allLeafChildren?.filter((e=>!n[e.id]))??null,this.rootNode.allLeafChildren?.forEach(((e,t)=>{e.sourceRowIndex=t}));const o=this.rootNode.sibling;o&&(o.allLeafChildren=this.rootNode.allLeafChildren)}executeUpdate(e,{rowNodeTransaction:t},a){const{update:r}=e;(0,h._missingOrEmpty)(r)||r.forEach((e=>{const r=this.lookupRowNode(e);r&&(r.updateData(e),!r.selectable&&r.isSelected()&&a.push(r),this.setMasterForRow(r,e,0,!1),t.update.push(r))}))}lookupRowNode(e){const t=(0,h._getRowIdCallback)(this.gos);let a;if(t){const r=t({data:e,level:0});if(a=this.allNodesMap[r],!a)return(0,h._errorOnce)(`could not find row id=${r}, data item was not found for this id`),null}else if(a=this.rootNode.allLeafChildren?.find((t=>t.data===e)),!a)return(0,h._errorOnce)("could not find data item as object was not found",e),(0,h._errorOnce)("Consider using getRowId to help the Grid find matching row data"),null;return a||null}createNode(e,t,a,r){const n=new h.RowNode(this.beans);return n.sourceRowIndex=r,n.group=!1,this.setMasterForRow(n,e,a,!0),t&&(n.parent=t),n.level=a,n.setDataAndId(e,this.nextId.toString()),this.allNodesMap[n.id]&&(0,h._warnOnce)(`duplicate node id '${n.id}' detected from getRowId callback, this could cause issues in your grid.`),this.allNodesMap[n.id]=n,this.nextId++,n}setMasterForRow(e,t,a,r){if(this.gos.get("treeData"))e.setMaster(!1),r&&(e.expanded=!1);else{if(this.gos.get("masterDetail")){const a=this.gos.get("isRowMaster");a?e.setMaster(a(t)):e.setMaster(!0)}else e.setMaster(!1);if(r){const t=this.funcColsService.getRowGroupColumns(),r=a+(t?t.length:0);e.expanded=!!e.master&&this.isExpanded(r)}}}isExpanded(e){const t=this.gos.get("groupDefaultExpanded");return-1===t||e{const g=l.changeSet?.properties;if(!g)return;const c=e=>g.some((t=>e.has(t)));c(e)?this.setRowData(this.rootNode.allLeafChildren.map((e=>e.data))):c(t)?this.refreshModel({step:d.ClientSideRowModelSteps.EVERYTHING}):c(a)?this.refreshModel({step:d.ClientSideRowModelSteps.FILTER}):c(r)?this.refreshModel({step:d.ClientSideRowModelSteps.PIVOT}):c(n)?this.refreshModel({step:d.ClientSideRowModelSteps.AGGREGATE}):c(o)?this.refreshModel({step:d.ClientSideRowModelSteps.SORT}):c(i)?this.refreshModel({step:d.ClientSideRowModelSteps.FILTER_AGGREGATES}):c(s)&&this.refreshModel({step:d.ClientSideRowModelSteps.MAP})})),this.addManagedPropertyListener("rowHeight",(()=>this.resetRowHeights()))}start(){this.hasStarted=!0,this.shouldSkipSettingDataOnStart?this.dispatchUpdateEventsAndRefresh():this.setInitialData()}setInitialData(){const e=this.gos.get("rowData");e&&(this.shouldSkipSettingDataOnStart=!0,this.setRowData(e))}ensureRowHeightsValid(e,t,a,r){let n,o=!1;do{n=!1;const i=this.getRowIndexAtPixel(e),s=this.getRowIndexAtPixel(t),l=Math.max(i,a),g=Math.min(s,r);for(let e=l;e<=g;e++){const t=this.getRow(e);if(t.rowHeightEstimated){const e=(0,d._getRowHeightForNode)(this.gos,t);t.setRowHeight(e.height),n=!0,o=!0}}n&&this.setRowTopAndRowIndex()}while(n);return o}setRowTopAndRowIndex(){const e=this.environment.getDefaultRowHeight();let t=0;const a=new Set,r=(0,d._isDomLayout)(this.gos,"normal");for(let n=0;n{e&&null!=e.id&&!t.has(e.id)&&e.clearRowTopAndRowIndex()},n=e=>{if(r(e),r(e.detailNode),r(e.sibling),e.hasChildren()&&e.childrenAfterGroup){const t=-1==e.level;a&&!t&&!e.expanded||e.childrenAfterGroup.forEach(n)}};n(this.rootNode)}ensureRowsAtPixel(e,t,a=0){const r=this.getRowIndexAtPixel(t),n=this.getRow(r),o=!this.gos.get("suppressAnimationFrame");if(n===e[0])return!1;const i=this.rootNode.allLeafChildren;return e.forEach((e=>{(0,d._removeFromArray)(i,e)})),e.forEach(((e,t)=>{(0,d._insertIntoArray)(i,e,Math.max(r+a,0)+t)})),e.forEach(((e,t)=>{e.sourceRowIndex=t})),this.refreshModel({step:d.ClientSideRowModelSteps.EVERYTHING,keepRenderedRows:!0,keepEditingRows:!0,animate:o,rowNodesOrderChanged:!0}),!0}highlightRowAtPixel(e,t){const a=null!=t?this.getRowIndexAtPixel(t):null,r=null!=a?this.getRow(a):null;if(!r||!e||null==t)return void this.clearHighlightedRow();const n=this.getHighlightPosition(t,r),o=this.isHighlightingCurrentPosition(e,r,n),i=null!=this.lastHighlightedRow&&this.lastHighlightedRow!==r;(o||i)&&(this.clearHighlightedRow(),o)||(r.setHighlighted(n),this.lastHighlightedRow=r)}getHighlightPosition(e,t){if(!t){const a=this.getRowIndexAtPixel(e);if(!(t=this.getRow(a||0)))return d.RowHighlightPosition.Below}const{rowTop:a,rowHeight:r}=t;return e-a=this.rootNode.childrenAfterSort.length;if(a.footer&&r)return a.rowIndex;let n=this.rootNode.childrenAfterSort[t];if(this.gos.get("groupHideOpenParents"))for(;n.expanded&&n.childrenAfterSort&&n.childrenAfterSort.length>0;)n=n.childrenAfterSort[0];return n.rowIndex}getRowBounds(e){if((0,d._missing)(this.rowsToDisplay))return null;const t=this.rowsToDisplay[e];return t?{rowTop:t.rowTop,rowHeight:t.rowHeight}:null}onRowGroupOpened(){const e=(0,d._isAnimateRows)(this.gos);this.refreshModel({step:d.ClientSideRowModelSteps.MAP,keepRenderedRows:!0,animate:e})}onFilterChanged(e){if(e.afterDataChange)return;const t=(0,d._isAnimateRows)(this.gos),a=0===e.columns.length||e.columns.some((e=>e.isPrimary()))?d.ClientSideRowModelSteps.FILTER:d.ClientSideRowModelSteps.FILTER_AGGREGATES;this.refreshModel({step:a,keepRenderedRows:!0,animate:t})}onSortChanged(){const e=(0,d._isAnimateRows)(this.gos);this.refreshModel({step:d.ClientSideRowModelSteps.SORT,keepRenderedRows:!0,animate:e,keepEditingRows:!0})}getType(){return"clientSide"}onValueChanged(){this.columnModel.isPivotActive()?this.refreshModel({step:d.ClientSideRowModelSteps.PIVOT}):this.refreshModel({step:d.ClientSideRowModelSteps.AGGREGATE})}createChangePath(e){const t=(0,d._missingOrEmpty)(e),a=new d.ChangedPath(!1,this.rootNode);return t&&a.setInactive(),a}isSuppressModelUpdateAfterUpdateTransaction(e){if(!this.gos.get("suppressModelUpdateAfterUpdateTransaction"))return!1;if(null==e.rowNodeTransactions)return!1;const t=e.rowNodeTransactions.filter((e=>null!=e.add&&e.add.length>0||null!=e.remove&&e.remove.length>0));return null==t||0==t.length}buildRefreshModelParams(e){let t=d.ClientSideRowModelSteps.EVERYTHING;const a={everything:d.ClientSideRowModelSteps.EVERYTHING,group:d.ClientSideRowModelSteps.EVERYTHING,filter:d.ClientSideRowModelSteps.FILTER,map:d.ClientSideRowModelSteps.MAP,aggregate:d.ClientSideRowModelSteps.AGGREGATE,filter_aggregates:d.ClientSideRowModelSteps.FILTER_AGGREGATES,sort:d.ClientSideRowModelSteps.SORT,pivot:d.ClientSideRowModelSteps.PIVOT,nothing:d.ClientSideRowModelSteps.NOTHING};if((0,d._exists)(e)&&(t=a[e]),(0,d._missing)(t))return void(0,d._errorOnce)(`invalid step ${e}, available steps are ${Object.keys(a).join(", ")}`);return{step:t,keepRenderedRows:!0,keepEditingRows:!0,animate:!this.gos.get("suppressAnimationFrame")}}refreshModel(e){if(!this.hasStarted||this.isRefreshingModel||this.columnModel.isChangeEventsDispatching())return;const t="object"==typeof e&&"step"in e?e:this.buildRefreshModelParams(e);if(!t)return;if(this.isSuppressModelUpdateAfterUpdateTransaction(t))return;const a=this.createChangePath(t.rowNodeTransactions);switch(this.isRefreshingModel=!0,t.step){case d.ClientSideRowModelSteps.EVERYTHING:this.doRowGrouping(t.rowNodeTransactions,a,!!t.rowNodesOrderChanged,!!t.afterColumnsChanged);case d.ClientSideRowModelSteps.FILTER:this.doFilter(a);case d.ClientSideRowModelSteps.PIVOT:this.doPivot(a);case d.ClientSideRowModelSteps.AGGREGATE:this.doAggregate(a);case d.ClientSideRowModelSteps.FILTER_AGGREGATES:this.doFilterAggregates(a);case d.ClientSideRowModelSteps.SORT:this.doSort(t.rowNodeTransactions,a);case d.ClientSideRowModelSteps.MAP:this.doRowsToDisplay()}const r=this.setRowTopAndRowIndex();this.clearRowTopAndRowIndex(a,r),this.isRefreshingModel=!1,this.eventService.dispatchEvent({type:"modelUpdated",animate:t.animate,keepRenderedRows:t.keepRenderedRows,newData:t.newData,newPage:!1,keepUndoRedoStack:t.keepUndoRedoStack})}isEmpty(){const e=(0,d._missing)(this.rootNode.allLeafChildren)||0===this.rootNode.allLeafChildren.length;return(0,d._missing)(this.rootNode)||e||!this.columnModel.isReady()}isRowsToRender(){return(0,d._exists)(this.rowsToDisplay)&&this.rowsToDisplay.length>0}getNodesInRangeForSelection(e,t){let a=!1,r=!1;const n=[],o=(0,d._getGroupSelectsDescendants)(this.gos);return this.forEachNodeAfterFilterAndSort((i=>{if(r)return;if(a&&(i===t||i===e)&&(r=!0,i.group&&o))return void n.push(...i.allLeafChildren);if(!a){if(i!==t&&i!==e)return;a=!0}(!i.group||!o)&&n.push(i)})),n}setDatasource(e){(0,d._errorOnce)("should never call setDatasource on clientSideRowController")}getTopLevelNodes(){return this.rootNode?this.rootNode.childrenAfterGroup:null}getRootNode(){return this.rootNode}getRow(e){return this.rowsToDisplay[e]}isRowPresent(e){return this.rowsToDisplay.indexOf(e)>=0}getRowIndexAtPixel(e){if(this.isEmpty()||0===this.rowsToDisplay.length)return-1;let t=0,a=this.rowsToDisplay.length-1;if(e<=0)return 0;if((0,d._last)(this.rowsToDisplay).rowTop<=e)return this.rowsToDisplay.length-1;let r=-1,n=-1;for(;;){const o=Math.floor((t+a)/2),i=this.rowsToDisplay[o];if(this.isRowInPixel(i,e))return o;i.rowTope&&(a=o-1);if(r===t&&n===a)return o;r=t,n=a}}isRowInPixel(e,t){const a=e.rowTop,r=e.rowTop+e.rowHeight;return a<=t&&r>t}forEachLeafNode(e){this.rootNode.allLeafChildren&&this.rootNode.allLeafChildren.forEach(((t,a)=>e(t,a)))}forEachNode(e,t=!1){this.recursivelyWalkNodesAndCallback({nodes:[...this.rootNode.childrenAfterGroup||[]],callback:e,recursionType:0,index:0,includeFooterNodes:t})}forEachNodeAfterFilter(e,t=!1){this.recursivelyWalkNodesAndCallback({nodes:[...this.rootNode.childrenAfterAggFilter||[]],callback:e,recursionType:1,index:0,includeFooterNodes:t})}forEachNodeAfterFilterAndSort(e,t=!1){this.recursivelyWalkNodesAndCallback({nodes:[...this.rootNode.childrenAfterSort||[]],callback:e,recursionType:2,index:0,includeFooterNodes:t})}forEachPivotNode(e,t=!1){this.recursivelyWalkNodesAndCallback({nodes:[this.rootNode],callback:e,recursionType:3,index:0,includeFooterNodes:t})}recursivelyWalkNodesAndCallback(e){const{nodes:t,callback:a,recursionType:r,includeFooterNodes:n}=e;let{index:o}=e;const i=e=>{const r=t[0]?.parent;if(!r)return;const i=n&&(0,d._getGrandTotalRow)(this.gos),s=(0,d._getGroupTotalRowCallback)(this.gos),l=n&&s({node:r});r===this.rootNode?i===e&&(r.createFooter(),a(r.sibling,o++)):l===e&&(r.createFooter(),a(r.sibling,o++))};i("top");for(let e=0;e{n&&n.forEach((n=>{const o=()=>{n.expanded=e,r(n.childrenAfterGroup)};if(t){return void((0,d._exists)(n.childrenAfterGroup)&&o())}if(a){return void(!n.leafGroup&&o())}n.group&&o()}))};this.rootNode&&r(this.rootNode.childrenAfterGroup),this.refreshModel({step:d.ClientSideRowModelSteps.MAP}),this.eventService.dispatchEvent({type:"expandOrCollapseAll",source:e?"expandAll":"collapseAll"})}doSort(e,t){this.sortStage.execute({rowNode:this.rootNode,rowNodeTransactions:e,changedPath:t})}doRowGrouping(e,t,a,r){if(this.groupStage){if(e?this.groupStage.execute({rowNode:this.rootNode,rowNodeTransactions:e,rowNodesOrderChanged:a,changedPath:t}):this.groupStage.execute({rowNode:this.rootNode,changedPath:t,afterColumnsChanged:r}),(0,d._getGroupSelectsDescendants)(this.gos)){this.selectionService.updateGroupsFromChildrenSelections("rowGroupChanged",t)&&this.eventService.dispatchEvent({type:"selectionChanged",source:"rowGroupChanged"})}}else{const e=this.rootNode,t=e.sibling;e.childrenAfterGroup=e.allLeafChildren,t&&(t.childrenAfterGroup=e.childrenAfterGroup),this.rootNode.updateHasChildren()}this.nodeManager.isRowCountReady()&&(this.rowCountReady=!0,this.eventService.dispatchEventOnce({type:"rowCountReady"}))}doFilter(e){this.filterStage.execute({rowNode:this.rootNode,changedPath:e})}doPivot(e){this.pivotStage?.execute({rowNode:this.rootNode,changedPath:e})}getNodeManager(){return this.nodeManager}getRowNode(e){if("string"==typeof e&&0==e.indexOf(d.RowNode.ID_PREFIX_ROW_GROUP)){let t;return this.forEachNode((a=>{a.id===e&&(t=a)})),t}return this.nodeManager.getRowNode(e)}setRowData(e){this.selectionService.reset("rowDataChanged"),this.nodeManager.setRowData(e),this.hasStarted&&this.dispatchUpdateEventsAndRefresh()}dispatchUpdateEventsAndRefresh(){this.eventService.dispatchEvent({type:"rowDataUpdated"}),this.refreshModel({step:d.ClientSideRowModelSteps.EVERYTHING,newData:!0})}batchUpdateRowData(e,t){if(null==this.applyAsyncTransactionsTimeout){this.rowDataTransactionBatch=[];const e=this.gos.get("asyncTransactionWaitMillis");this.applyAsyncTransactionsTimeout=window.setTimeout((()=>{this.isAlive()&&this.executeBatchUpdateRowData()}),e)}this.rowDataTransactionBatch.push({rowDataTransaction:e,callback:t})}flushAsyncTransactions(){null!=this.applyAsyncTransactionsTimeout&&(clearTimeout(this.applyAsyncTransactionsTimeout),this.executeBatchUpdateRowData())}executeBatchUpdateRowData(){this.valueCache.onDataChanged();const e=[],t=[];let a=!1;this.rowDataTransactionBatch?.forEach((r=>{const{rowNodeTransaction:n,rowsInserted:o}=this.nodeManager.updateRowData(r.rowDataTransaction);o&&(a=!0),t.push(n),r.callback&&e.push(r.callback.bind(null,n))})),this.commonUpdateRowData(t,a),e.length>0&&window.setTimeout((()=>{e.forEach((e=>e()))}),0),t.length>0&&this.eventService.dispatchEvent({type:"asyncTransactionsFlushed",results:t}),this.rowDataTransactionBatch=null,this.applyAsyncTransactionsTimeout=void 0}updateRowData(e){this.valueCache.onDataChanged();const{rowNodeTransaction:t,rowsInserted:a}=this.nodeManager.updateRowData(e);return this.commonUpdateRowData([t],a),t}afterImmutableDataChange(e,t){this.commonUpdateRowData([e],t)}commonUpdateRowData(e,t){if(!this.hasStarted)return;const a=!this.gos.get("suppressAnimationFrame");this.eventService.dispatchEvent({type:"rowDataUpdated"}),this.refreshModel({step:d.ClientSideRowModelSteps.EVERYTHING,rowNodeTransactions:e,rowNodesOrderChanged:t,keepRenderedRows:!0,keepEditingRows:!0,animate:a})}doRowsToDisplay(){this.rowsToDisplay=this.flattenStage.execute({rowNode:this.rootNode})}onRowHeightChanged(){this.refreshModel({step:d.ClientSideRowModelSteps.MAP,keepRenderedRows:!0,keepEditingRows:!0,keepUndoRedoStack:!0})}onRowHeightChangedDebounced(){this.onRowHeightChanged_debounced()}resetRowHeights(){const e=this.resetRowHeightsForAllRowNodes();this.rootNode.setRowHeight(this.rootNode.rowHeight,!0),this.rootNode.sibling&&this.rootNode.sibling.setRowHeight(this.rootNode.sibling.rowHeight,!0),e&&this.onRowHeightChanged()}resetRowHeightsForAllRowNodes(){let e=!1;return this.forEachNode((t=>{t.setRowHeight(t.rowHeight,!0);const a=t.detailNode;a&&a.setRowHeight(a.rowHeight,!0),t.sibling&&t.sibling.setRowHeight(t.sibling.rowHeight,!0),e=!0})),e}onGridStylesChanges(e){if(e.rowHeightChanged){if(this.columnModel.isAutoRowHeightActive())return;this.resetRowHeights()}}onGridReady(){this.hasStarted||this.setInitialData()}isRowDataLoaded(){return this.rowCountReady}destroy(){super.destroy(),this.clearHighlightedRow()}},m=a(3423);var v=a(3423),f=class extends v.BeanStub{constructor(){super(...arguments),this.beanName="filterStage"}wireBeans(e){this.filterManager=e.filterManager}execute(e){const{changedPath:t}=e;this.filter(t)}filter(e){const t=!!this.filterManager?.isChildFilterPresent();this.filterNodes(t,e)}filterNodes(e,t){const a=(t,a)=>{t.hasChildren()?t.childrenAfterFilter=e&&!a?t.childrenAfterGroup.filter((e=>{const t=e.childrenAfterFilter&&e.childrenAfterFilter.length>0,a=e.data&&this.filterManager.doesRowPassFilter({rowNode:e});return t||a})):t.childrenAfterGroup:t.childrenAfterFilter=t.childrenAfterGroup,t.sibling&&(t.sibling.childrenAfterFilter=t.childrenAfterFilter)};if(this.doingTreeDataFiltering()){const e=(t,r)=>{if(t.childrenAfterGroup)for(let n=0;ne(t,!1);t.executeFromRootNode(r)}else{const e=e=>a(e,!1);t.forEachChangedNodeDepthFirst(e,!0)}}doingTreeDataFiltering(){return this.gos.get("treeData")&&!this.gos.get("excludeChildrenWhenTreeDataFiltering")}},b=a(3423),w=class extends b.BeanStub{constructor(){super(...arguments),this.beanName="flattenStage"}wireBeans(e){this.beans=e,this.columnModel=e.columnModel}execute(e){const t=e.rowNode,a=[],r=this.columnModel.isPivotMode(),n=r&&t.leafGroup,o=n?[t]:t.childrenAfterSort,i=this.getFlattenDetails();this.recursivelyAddToRowsToDisplay(i,o,a,r,0);if(!n&&a.length>0&&i.grandTotalRow){t.createFooter();const e="top"===i.grandTotalRow;this.addRowNodeToRowsToDisplay(i,t.sibling,a,0,e)}return a}getFlattenDetails(){const e=this.gos.get("groupRemoveSingleChildren");return{groupRemoveLowestSingleChildren:!e&&this.gos.get("groupRemoveLowestSingleChildren"),groupRemoveSingleChildren:e,isGroupMultiAutoColumn:(0,b._isGroupMultiAutoColumn)(this.gos),hideOpenParents:this.gos.get("groupHideOpenParents"),grandTotalRow:(0,b._getGrandTotalRow)(this.gos),groupTotalRow:(0,b._getGroupTotalRowCallback)(this.gos)}}recursivelyAddToRowsToDisplay(e,t,a,r,n){if(!(0,b._missingOrEmpty)(t))for(let o=0;othis.onRowDataUpdated())))}isActive(){const e=this.gos.exists("getRowId");return!this.gos.get("resetRowDataOnUpdate")&&e}setRowData(e){const t=this.createTransactionForRowData(e);if(!t)return;const a=this.clientSideRowModel.getNodeManager(),{rowNodeTransaction:r,rowsInserted:n}=a.updateRowData(t);let o=!1;this.gos.get("suppressMaintainUnsortedOrder")||(o=a.updateRowOrderFromRowData(e)),this.clientSideRowModel.afterImmutableDataChange(r,o||n)}createTransactionForRowData(e){if(!(0,C._isClientSideRowModel)(this.gos))return(0,C._errorOnce)("ImmutableService only works with ClientSideRowModel"),null;const t=(0,C._getRowIdCallback)(this.gos);if(null==t)return(0,C._errorOnce)("ImmutableService requires getRowId() callback to be implemented, your row data needs IDs!"),null;const a=this.clientSideRowModel.getNodeManager().getCopyOfNodesMap(),r=[],n=[],o=[];return(0,C._exists)(e)&&e.forEach((e=>{const r=t({data:e,level:0}),i=a[r];if(i){i.data!==e&&n.push(e),a[r]=void 0}else o.push(e)})),(0,C._iterateObject)(a,((e,t)=>{t&&r.push(t.data)})),{remove:r,update:n,add:o}}onRowDataUpdated(){const e=this.gos.get("rowData");e&&(this.isActive()?this.setRowData(e):(this.selectionService.reset("rowDataChanged"),this.clientSideRowModel.setRowData(e)))}},k=a(3423),S=class extends k.BeanStub{constructor(){super(...arguments),this.beanName="sortService"}wireBeans(e){this.columnModel=e.columnModel,this.funcColsService=e.funcColsService,this.rowNodeSorter=e.rowNodeSorter,this.showRowGroupColsService=e.showRowGroupColsService}sort(e,t,a,r,n,o){const i=this.gos.get("groupMaintainOrder"),s=this.columnModel.getCols().some((e=>e.isRowGroupActive()));let l={};a&&r&&(l=this.calculateDirtyNodes(r));const g=this.columnModel.isPivotMode(),c=this.gos.getCallback("postSortRows"),d=r=>{this.pullDownGroupDataForHideOpenParents(r.childrenAfterAggFilter,!0);const d=g&&r.leafGroup;if(i&&s&&!r.leafGroup&&!o){const e=this.funcColsService.getRowGroupColumns()?.[r.level+1],t=null===e?.getSort(),a=r.childrenAfterAggFilter.slice(0);if(r.childrenAfterSort&&!t){const e={};r.childrenAfterSort.forEach(((t,a)=>{e[t.id]=a})),a.sort(((t,a)=>(e[t.id]??0)-(e[a.id]??0)))}r.childrenAfterSort=a}else r.childrenAfterSort=!t||d?r.childrenAfterAggFilter.slice(0):a?this.doDeltaSort(r,l,n,e):this.rowNodeSorter.doFullSort(r.childrenAfterAggFilter,e);if(r.sibling&&(r.sibling.childrenAfterSort=r.childrenAfterSort),this.updateChildIndexes(r),c){const e={nodes:r.childrenAfterSort};c(e)}};n&&n.forEachChangedNodeDepthFirst(d),this.updateGroupDataForHideOpenParents(n)}calculateDirtyNodes(e){const t={},a=e=>{e&&e.forEach((e=>t[e.id]=!0))};return e&&e.forEach((e=>{a(e.add),a(e.update),a(e.remove)})),t}doDeltaSort(e,t,a,r){const n=e.childrenAfterAggFilter,o=e.childrenAfterSort;if(!o)return this.rowNodeSorter.doFullSort(n,r);const i={},s=[];n.forEach((e=>{t[e.id]||!a.canSkip(e)?s.push(e):i[e.id]=!0}));const l=o.filter((e=>i[e.id])),g=(e,t)=>({currentPos:t,rowNode:e}),c=s.map(g).sort(((e,t)=>this.rowNodeSorter.compareRowNodes(r,e,t)));return this.mergeSortedArrays(r,c,l.map(g)).map((({rowNode:e})=>e))}mergeSortedArrays(e,t,a){const r=[];let n=0,o=0;for(;n{this.pullDownGroupDataForHideOpenParents(e.childrenAfterSort,!1),e.childrenAfterSort.forEach((e=>{e.hasChildren()&&t(e)}))};e&&e.executeFromRootNode((e=>t(e)))}pullDownGroupDataForHideOpenParents(e,t){this.gos.get("groupHideOpenParents")&&!(0,k._missing)(e)&&e.forEach((e=>{(this.showRowGroupColsService?.getShowRowGroupCols()??[]).forEach((a=>{const r=a.getColDef().showRowGroup;if("string"!=typeof r)return void(0,k._errorOnce)("groupHideOpenParents only works when specifying specific columns for colDef.showRowGroup");const n=r,o=this.columnModel.getColDefCol(n);if(!(o===e.rowGroupColumn))if(t)e.setGroupValue(a.getId(),void 0);else{const t=this.getFirstChildOfFirstChild(e,o);t&&e.setGroupValue(a.getId(),t.key)}}))}))}getFirstChildOfFirstChild(e,t){let a=e;for(;a;){const e=a.parent;if(!e||!a.firstChild)return null;if(e.rowGroupColumn===t)return e;a=e}return null}},x=a(3423),z=class extends x.BeanStub{constructor(){super(...arguments),this.beanName="sortStage"}wireBeans(e){this.sortService=e.sortService,this.sortController=e.sortController}execute(e){const t=this.sortController.getSortOptions(),a=(0,x._exists)(t)&&t.length>0,r=a&&(0,x._exists)(e.rowNodeTransactions)&&this.gos.get("deltaSort"),n=t.some((e=>(0,x._isColumnsSortingCoupledToGroup)(this.gos)?e.column.isPrimary()&&e.column.isRowGroupActive():!!e.column.getColDef().showRowGroup));this.sortService.sort(t,a,r,e.rowNodeTransactions,e.changedPath,n)}},E="32.3.2",R=(0,g._defineModule)({version:E,moduleName:`${c.ModuleNames.ClientSideRowModelModule}-core`,rowModel:"clientSide",beans:[p,f,z,w,S,y]}),M=(0,g._defineModule)({version:E,moduleName:`${c.ModuleNames.ClientSideRowModelModule}-api`,beans:[c.RowModelHelperService],apiFunctions:{onGroupExpandedOrCollapsed:function(e){e.expansionService.onGroupExpandedOrCollapsed()},refreshClientSideRowModel:function(e,t){e.rowModelHelperService?.getClientSideRowModel()?.refreshModel(t)},isRowDataEmpty:function(e){return e.rowModelHelperService?.getClientSideRowModel()?.isEmpty()??!0},forEachLeafNode:function(e,t){e.rowModelHelperService?.getClientSideRowModel()?.forEachLeafNode(t)},forEachNodeAfterFilter:function(e,t){e.rowModelHelperService?.getClientSideRowModel()?.forEachNodeAfterFilter(t)},forEachNodeAfterFilterAndSort:function(e,t){e.rowModelHelperService?.getClientSideRowModel()?.forEachNodeAfterFilterAndSort(t)},resetRowHeights:function(e){e.columnModel.isAutoRowHeightActive()?(0,m._warnOnce)("calling gridApi.resetRowHeights() makes no sense when using Auto Row Height."):e.rowModelHelperService?.getClientSideRowModel()?.resetRowHeights()},applyTransaction:function(e,t){return e.frameworkOverrides.wrapIncoming((()=>e.rowModelHelperService?.getClientSideRowModel()?.updateRowData(t)))},applyTransactionAsync:function(e,t,a){e.frameworkOverrides.wrapIncoming((()=>e.rowModelHelperService?.getClientSideRowModel()?.batchUpdateRowData(t,a)))},flushAsyncTransactions:function(e){e.frameworkOverrides.wrapIncoming((()=>e.rowModelHelperService?.getClientSideRowModel()?.flushAsyncTransactions()))},getBestCostNodeSelection:function(e){return e.selectionService.getBestCostNodeSelection()}},dependantModules:[R,c._CsrmSsrmSharedApiModule]}),F=(0,g._defineModule)({version:E,moduleName:c.ModuleNames.ClientSideRowModelModule,dependantModules:[R,M]})},3423:function(e,t,a){var r,n=Object.defineProperty,o=Object.getOwnPropertyDescriptor,i=Object.getOwnPropertyNames,s=Object.prototype.hasOwnProperty,l={};((e,t)=>{for(var a in t)n(e,a,{get:t[a],enumerable:!0})})(l,{ALWAYS_SYNC_GLOBAL_EVENTS:()=>Lc,AbstractHeaderCellCtrl:()=>di,AgAbstractField:()=>an,AgAbstractInputField:()=>rn,AgAbstractLabel:()=>tn,AgCheckbox:()=>nn,AgCheckboxSelector:()=>on,AgColumn:()=>ft,AgColumnGroup:()=>Vt,AgInputDateField:()=>Nl,AgInputNumberField:()=>To,AgInputNumberFieldSelector:()=>Io,AgInputTextArea:()=>Rl,AgInputTextField:()=>Ao,AgInputTextFieldSelector:()=>Po,AgPickerField:()=>So,AgPromise:()=>bn,AgProvidedColumnGroup:()=>Qe,AgRadioButton:()=>yo,AgSelect:()=>xo,AgSelectSelector:()=>zo,AgToggleButton:()=>ug,AgToggleButtonSelector:()=>pg,AnimateShowChangeCellRenderer:()=>Xr,AnimateSlideCellRenderer:()=>$r,AnimationFrameService:()=>Sl,AriaAnnouncementService:()=>Kc,AutoScrollService:()=>Jn,AutoWidthCalculator:()=>tg,BarColumnLabelPlacement:()=>kd,BaseComponentWrapper:()=>Cd,BeanStub:()=>Ke,BodyDropPivotTarget:()=>ji,BodyDropTarget:()=>Ji,CellComp:()=>sl,CellCtrl:()=>Os,CellNavigationService:()=>Sg,CellPositionUtils:()=>Wg,CellRangeType:()=>As,ChangedPath:()=>$c,ChartMappings:()=>yd,CheckboxCellEditor:()=>Wl,CheckboxCellRenderer:()=>sn,CheckboxSelectionComponent:()=>xs,ClientSideRowModelSteps:()=>xl,ColumnApplyStateService:()=>Ft,ColumnAutosizeService:()=>Rt,ColumnFactory:()=>Ct,ColumnKeyCreator:()=>wt,ColumnModel:()=>kt,ColumnMoveService:()=>At,ColumnNameService:()=>Nt,ColumnSizeService:()=>Bt,CommunityFeaturesModule:()=>Tc,Component:()=>Gr,ComponentUtil:()=>$t,Context:()=>Un,CssClassManager:()=>pa,CtrlsService:()=>Vg,DataTypeService:()=>oc,DateCellEditor:()=>Hl,DateFilter:()=>ri,DateStringCellEditor:()=>Vl,DragAndDropService:()=>Zn,DragService:()=>eo,DragSourceType:()=>_n,Environment:()=>_g,EventService:()=>Xg,ExcelFactoryMode:()=>Kn,ExpansionService:()=>zl,ExpressionService:()=>id,FakeHScrollComp:()=>ms,FakeVScrollComp:()=>bs,FilterManager:()=>lo,FilterWrapperComp:()=>go,FocusService:()=>ec,FuncColsService:()=>Mt,GROUP_AUTO_COLUMN_ID:()=>Je,Grid:()=>pd,GridBodyComp:()=>ul,GridBodyCtrl:()=>il,GridComp:()=>rc,GridCoreCreator:()=>vd,GridCoreModule:()=>Ac,GridCtrl:()=>ac,GridHeaderComp:()=>ls,GridHeaderCtrl:()=>Vi,GridOptionsService:()=>Hc,GroupInstanceIdCreator:()=>Wt,HeaderCellCtrl:()=>es,HeaderFilterCellComp:()=>Li,HeaderFilterCellCtrl:()=>ui,HeaderGroupCellCtrl:()=>rs,HeaderNavigationDirection:()=>Hi,HeaderNavigationService:()=>qi,HeaderPositionUtils:()=>qc,HeaderRowComp:()=>Bi,HeaderRowContainerComp:()=>ss,HeaderRowContainerCtrl:()=>is,HeaderRowCtrl:()=>os,HeaderRowType:()=>Gi,HorizontalDirection:()=>Qn,HorizontalResizeService:()=>Cl,KeyCode:()=>en,LargeTextCellEditor:()=>Fl,LayoutCssClasses:()=>cs,LocalEventService:()=>g,LocaleService:()=>Vc,ManagedFocusFeature:()=>vo,MenuService:()=>El,ModuleNames:()=>c,ModuleRegistry:()=>Wn,MouseEventService:()=>bl,MoveColumnFeature:()=>Zi,NavigationService:()=>wl,NumberCellEditor:()=>Ol,NumberFilter:()=>Bo,NumberSequence:()=>Pr,OverlayWrapperComponent:()=>hs,PinnedRowModel:()=>og,PivotResultColsService:()=>Gt,PopupComponent:()=>un,PopupEditorWrapper:()=>Dl,PopupService:()=>Cg,PositionableFeature:()=>po,PropertyKeys:()=>Jt,ProvidedFilter:()=>Co,RefPlaceholder:()=>Nr,ResizeObserverService:()=>kl,RowContainerComp:()=>gl,RowContainerCtrl:()=>nl,RowCtrl:()=>Bs,RowDragComp:()=>to,RowHighlightPosition:()=>Xn,RowModelHelperService:()=>fd,RowNode:()=>so,RowNodeBlock:()=>lg,RowNodeBlockLoader:()=>gg,RowNodeSorter:()=>Xc,RowPositionUtils:()=>Ug,RowRenderer:()=>rg,ScalarFilter:()=>Mo,ScrollVisibleService:()=>ml,SelectCellEditor:()=>Al,SelectableService:()=>td,SelectionHandleType:()=>Ds,ServerSideTransactionResultStatus:()=>sg,SetLeftFeature:()=>ni,SimpleFilter:()=>Ro,SortController:()=>rd,SortIndicatorComp:()=>Qr,SortIndicatorSelector:()=>_r,StandardMenuFactory:()=>yl,StylingService:()=>hg,TabGuardClassNames:()=>mg,TabGuardComp:()=>bg,TabGuardCtrl:()=>vg,TabGuardFeature:()=>fg,TextCellEditor:()=>Il,TextFilter:()=>Wo,TextFloatingFilter:()=>Si,TooltipFeature:()=>Lr,TooltipStateManager:()=>Ir,TouchListener:()=>Ur,UserComponentFactory:()=>Hn,UserComponentRegistry:()=>fn,ValueCache:()=>sd,ValueService:()=>ld,VanillaFrameworkOverrides:()=>kg,VerticalDirection:()=>Yn,VisibleColsService:()=>jt,_ColumnFilterModule:()=>Ei,_CommunityMenuApiModule:()=>dc,_CsrmSsrmSharedApiModule:()=>bd,_EditCoreModule:()=>_l,_EmptyBean:()=>aa,_FilterCoreModule:()=>xi,_FloatingFilterModule:()=>Fi,_ReadOnlyFloatingFilterModule:()=>Di,_RowNodeBlockModule:()=>cg,_SsrmInfiniteSharedApiModule:()=>wd,_addFocusableContainerListener:()=>vc,_areEqual:()=>Ee,_bindCellRendererToHtmlElement:()=>Ar,_capitalise:()=>Lt,_clearElement:()=>vr,_cloneObject:()=>st,_combineAttributesAndGridOptions:()=>ea,_compose:()=>x,_createIcon:()=>qr,_createIconNoSpan:()=>Vr,_debounce:()=>y,_defaultComparator:()=>I,_defineModule:()=>pi,_doOnce:()=>h,_errorOnce:()=>m,_escapeString:()=>Ot,_exists:()=>E,_existsAndNotEmpty:()=>xe,_flatten:()=>Le,_forEachReverse:()=>Ne,_formatNumberCommas:()=>Qo,_formatNumberTwoDecimalPlacesAndCommas:()=>Yo,_fuzzySuggestions:()=>mn,_getAbsoluteHeight:()=>cr,_getAbsoluteWidth:()=>dr,_getActiveDomElement:()=>J,_getAllValuesInObject:()=>gt,_getAriaPosInSet:()=>Ca,_getCtrlForEventTarget:()=>je,_getDocument:()=>Q,_getFillHandle:()=>me,_getGlobalGridOption:()=>hd,_getGrandTotalRow:()=>re,_getGroupAggFiltering:()=>ae,_getGroupSelection:()=>ye,_getGroupSelectsDescendants:()=>ke,_getGroupTotalRowCallback:()=>ne,_getHeaderCheckbox:()=>ge,_getHeaderClassesFromColDef:()=>oi,_getInnerHeight:()=>lr,_getInnerWidth:()=>gr,_getIsRowSelectable:()=>be,_getRowContainerOptions:()=>_s,_getRowHeightAsNumber:()=>j,_getRowHeightForNode:()=>W,_getRowIdCallback:()=>se,_getRowSelectionMode:()=>we,_getSuppressMultiRanges:()=>he,_getToolPanelClassesFromColDef:()=>ii,_includes:()=>Ie,_insertArrayIntoArray:()=>Pe,_insertIntoArray:()=>Ae,_isAnimateRows:()=>$,_isCellSelectionEnabled:()=>ue,_isClientSideRowModel:()=>N,_isColumnsSortingCoupledToGroup:()=>te,_isDomLayout:()=>B,_isElementInEventPath:()=>Ue,_isEventFromPrintableCharacter:()=>Fo,_isGetRowHeightFunction:()=>V,_isGroupMultiAutoColumn:()=>oe,_isGroupUseEntireRow:()=>ie,_isIOSUserAgent:()=>_a,_isMultiRowSelection:()=>Ce,_isNodeOrElement:()=>Mr,_isNothingFocused:()=>X,_isRowSelection:()=>H,_isServerSideRowModel:()=>G,_isStopPropagationForAgGrid:()=>Ve,_isUsingNewCellSelectionAPI:()=>de,_isUsingNewRowSelectionAPI:()=>ce,_isVisible:()=>wr,_iterateObject:()=>it,_jsonEquals:()=>T,_last:()=>ze,_loadTemplate:()=>Cr,_log:()=>u,_makeNull:()=>z,_mergeDeep:()=>ct,_missing:()=>R,_missingOrEmpty:()=>M,_parseDateTimeFromString:()=>Xo,_processOnChange:()=>ta,_radioCssClass:()=>er,_removeAriaExpanded:()=>Da,_removeAriaSort:()=>Ba,_removeFromArray:()=>De,_removeFromParent:()=>fr,_removeRepeatsFromArray:()=>Me,_serialiseDate:()=>_o,_setAriaActiveDescendant:()=>Ma,_setAriaChecked:()=>qa,_setAriaColCount:()=>La,_setAriaColIndex:()=>Oa,_setAriaColSpan:()=>Na,_setAriaControls:()=>Va,_setAriaDescribedBy:()=>Sa,_setAriaDisabled:()=>Ea,_setAriaExpanded:()=>Fa,_setAriaHidden:()=>Ra,_setAriaLabel:()=>ya,_setAriaLabelledBy:()=>ka,_setAriaLevel:()=>za,_setAriaPosInSet:()=>Pa,_setAriaRole:()=>wa,_setAriaRowCount:()=>Ta,_setAriaRowIndex:()=>Ia,_setAriaSelected:()=>Ha,_setAriaSetSize:()=>Aa,_setAriaSort:()=>Ga,_setDisabled:()=>or,_setDisplayed:()=>rr,_setFixedWidth:()=>zr,_setVisible:()=>nr,_shallowCompare:()=>Re,_stopPropagationForAgGrid:()=>qe,_toStringOrNull:()=>F,_unwrapUserComp:()=>qn,_utf8_encode:()=>It,_values:()=>L,_waitUntil:()=>S,_warnOnce:()=>p,createGrid:()=>ud,getDefaultFloatingFilterType:()=>vi,isColumn:()=>vt,isColumnControlsCol:()=>at,isColumnGroup:()=>qt,isColumnGroupAutoCol:()=>tt,isProvidedColumnGroup:()=>Ye,isSelectionUIEvent:()=>Oc,provideGlobalGridOptions:()=>dd}),e.exports=(r=l,((e,t,a,r)=>{if(t&&"object"==typeof t||"function"==typeof t)for(let l of i(t))s.call(e,l)||l===a||n(e,l,{get:()=>t[l],enumerable:!(r=o(t,l))||r.enumerable});return e})(n({},"__esModule",{value:!0}),r));var g=class{constructor(){this.allSyncListeners=new Map,this.allAsyncListeners=new Map,this.globalSyncListeners=new Set,this.globalAsyncListeners=new Set,this.asyncFunctionsQueue=[],this.scheduled=!1,this.firedEvents={}}setFrameworkOverrides(e){this.frameworkOverrides=e}getListeners(e,t,a){const r=t?this.allAsyncListeners:this.allSyncListeners;let n=r.get(e);return!n&&a&&(n=new Set,r.set(e,n)),n}noRegisteredListenersExist(){return 0===this.allSyncListeners.size&&0===this.allAsyncListeners.size&&0===this.globalSyncListeners.size&&0===this.globalAsyncListeners.size}addEventListener(e,t,a=!1){this.getListeners(e,a,!0).add(t)}removeEventListener(e,t,a=!1){const r=this.getListeners(e,a,!1);if(r&&(r.delete(t),0===r.size)){(a?this.allAsyncListeners:this.allSyncListeners).delete(e)}}addGlobalListener(e,t=!1){(t?this.globalAsyncListeners:this.globalSyncListeners).add(e)}removeGlobalListener(e,t=!1){(t?this.globalAsyncListeners:this.globalSyncListeners).delete(e)}dispatchEvent(e){const t=e;this.dispatchToListeners(t,!0),this.dispatchToListeners(t,!1),this.firedEvents[t.type]=!0}dispatchEventOnce(e){this.firedEvents[e.type]||this.dispatchEvent(e)}dispatchToListeners(e,t){const a=e.type;if(t&&"event"in e){const t=e.event;t instanceof Event&&(e.eventPath=t.composedPath())}const r=(a,r)=>a.forEach((a=>{if(!r.has(a))return;const n=this.frameworkOverrides?()=>this.frameworkOverrides.wrapIncoming((()=>a(e))):()=>a(e);t?this.dispatchAsync(n):n()})),n=this.getListeners(a,t,!1)??new Set,o=new Set(n);o.size>0&&r(o,n);new Set(t?this.globalAsyncListeners:this.globalSyncListeners).forEach((r=>{const n=this.frameworkOverrides?()=>this.frameworkOverrides.wrapIncoming((()=>r(a,e))):()=>r(a,e);t?this.dispatchAsync(n):n()}))}dispatchAsync(e){if(this.asyncFunctionsQueue.push(e),!this.scheduled){const e=()=>{window.setTimeout(this.flushAsyncQueue.bind(this),0)};this.frameworkOverrides?this.frameworkOverrides.wrapIncoming(e):e(),this.scheduled=!0}}flushAsyncQueue(){this.scheduled=!1;const e=this.asyncFunctionsQueue.slice();this.asyncFunctionsQueue=[],e.forEach((e=>e()))}},c=(e=>(e.CommunityCoreModule="@ag-grid-community/core",e.InfiniteRowModelModule="@ag-grid-community/infinite-row-model",e.ClientSideRowModelModule="@ag-grid-community/client-side-row-model",e.CsvExportModule="@ag-grid-community/csv-export",e.EnterpriseCoreModule="@ag-grid-enterprise/core",e.RowGroupingModule="@ag-grid-enterprise/row-grouping",e.ColumnsToolPanelModule="@ag-grid-enterprise/column-tool-panel",e.FiltersToolPanelModule="@ag-grid-enterprise/filter-tool-panel",e.MenuModule="@ag-grid-enterprise/menu",e.SetFilterModule="@ag-grid-enterprise/set-filter",e.MultiFilterModule="@ag-grid-enterprise/multi-filter",e.StatusBarModule="@ag-grid-enterprise/status-bar",e.SideBarModule="@ag-grid-enterprise/side-bar",e.RangeSelectionModule="@ag-grid-enterprise/range-selection",e.MasterDetailModule="@ag-grid-enterprise/master-detail",e.RichSelectModule="@ag-grid-enterprise/rich-select",e.GridChartsModule="@ag-grid-enterprise/charts",e.ViewportRowModelModule="@ag-grid-enterprise/viewport-row-model",e.ServerSideRowModelModule="@ag-grid-enterprise/server-side-row-model",e.ExcelExportModule="@ag-grid-enterprise/excel-export",e.ClipboardModule="@ag-grid-enterprise/clipboard",e.SparklinesModule="@ag-grid-enterprise/sparklines",e.AdvancedFilterModule="@ag-grid-enterprise/advanced-filter",e.AngularModule="@ag-grid-community/angular",e.ReactModule="@ag-grid-community/react",e.VueModule="@ag-grid-community/vue",e))(c||{}),d={};function h(e,t){d[t]||(e(),d[t]=!0)}function u(e,...t){console.log("AG Grid: "+e,...t)}function p(e,...t){h((()=>console.warn("AG Grid: "+e,...t)),e+t?.join(""))}function m(e,...t){h((()=>console.error("AG Grid: "+e,...t)),e+t?.join(""))}function v(e){return!!(e&&e.constructor&&e.call&&e.apply)}function f(e){!function(e,t=0){e.length>0&&window.setTimeout((()=>e.forEach((e=>e()))),t)}(e,400)}var b=[],w=!1;function C(e){b.push(e),w||(w=!0,window.setTimeout((()=>{const e=b.slice();b.length=0,w=!1,e.forEach((e=>e()))}),0))}function y(e,t){let a;return function(...r){const n=this;window.clearTimeout(a),a=window.setTimeout((function(){e.apply(n,r)}),t)}}function k(e,t){let a=0;return function(...r){const n=(new Date).getTime();n-a{const s=(new Date).getTime()-n>a;(e()||s)&&(t(),i=!0,null!=o&&(window.clearInterval(o),o=null),s&&r&&p(r))};s(),i||(o=window.setInterval(s,10))}function x(...e){return t=>e.reduce(((e,t)=>t(e)),t)}function z(e){return null==e||""===e?null:e}function E(e,t=!1){return null!=e&&(""!==e||t)}function R(e){return!E(e)}function M(e){return null==e||0===e.length}function F(e){return null!=e&&"function"==typeof e.toString?e.toString():null}function D(e){if(void 0===e)return;if(null===e||""===e)return null;if("number"==typeof e)return isNaN(e)?void 0:e;const t=parseInt(e,10);return isNaN(t)?void 0:t}function A(e){if(void 0!==e)return null!==e&&""!==e&&P(e)}function P(e){return"boolean"==typeof e?e:"string"==typeof e&&("TRUE"===e.toUpperCase()||""==e)}function T(e,t){return(e?JSON.stringify(e):null)===(t?JSON.stringify(t):null)}function I(e,t,a=!1){const r=null==e,n=null==t;if(e&&e.toNumber&&(e=e.toNumber()),t&&t.toNumber&&(t=t.toNumber()),r&&n)return 0;if(r)return-1;if(n)return 1;function o(e,t){return e>t?1:et.push(e))),t}return Object.values(e)}function O(e,t){return e.get("rowModelType")===t}function N(e){return O(e,"clientSide")}function G(e){return O(e,"serverSide")}function B(e,t){return e.get("domLayout")===t}function H(e){return void 0!==we(e)}function q(e){return!e.get("suppressAsyncEvents")}function V(e){return"function"==typeof e.get("getRowHeight")}function W(e,t,a=!1,r){if(null==r&&(r=e.environment.getDefaultRowHeight()),V(e)){if(a)return{height:r,estimated:!0};const n={node:t,data:t.data},o=e.getCallback("getRowHeight")(n);if(U(o))return 0===o&&p("The return of `getRowHeight` cannot be zero. If the intention is to hide rows, use a filter instead."),{height:Math.max(1,o),estimated:!1}}if(t.detail&&e.get("masterDetail"))return function(e){if(e.get("detailRowAutoHeight"))return{height:1,estimated:!1};const t=e.get("detailRowHeight");if(U(t))return{height:t,estimated:!1};return{height:300,estimated:!1}}(e);const n=e.get("rowHeight");return{height:n&&U(n)?n:r,estimated:!1}}function j(e){const{environment:t}=e,a=e.get("rowHeight");if(!a||R(a))return t.getDefaultRowHeight();const r=t.refreshRowHeightVariable();return-1!==r?r:(p("row height must be a number if not using standard row model"),t.getDefaultRowHeight())}function U(e){return!isNaN(e)&&"number"==typeof e&&isFinite(e)}function K(e,t,a){const r=t[e.getDomDataKey()];return r?r[a]:void 0}function Y(e,t,a,r){const n=e.getDomDataKey();let o=t[n];R(o)&&(o={},t[n]=o),o[a]=r}function Q(e){let t=null;const a=e.get("getDocument");return a&&E(a)?t=a():e.eGridDiv&&(t=e.eGridDiv.ownerDocument),t&&E(t)?t:document}function _(e){return Q(e).defaultView||window}function Z(e){return e.eGridDiv.getRootNode()}function J(e){return Z(e).activeElement}function X(e){const t=Q(e),a=J(e);return null===a||a===t.body}function $(e){return!e.get("ensureDomOrder")&&e.get("animateRows")}function ee(e){return!(e.get("paginateChildRows")||e.get("groupHideOpenParents")||B(e,"print"))}function te(e){const t=e.get("autoGroupColumnDef");return!t?.comparator&&!e.get("treeData")}function ae(e){const t=e.get("groupAggFiltering");return"function"==typeof t?e.getCallback("groupAggFiltering"):!0===t?()=>!0:void 0}function re(e){const t=e.get("grandTotalRow");if(t)return t;return e.get("groupIncludeTotalFooter")?"bottom":void 0}function ne(e){const t=e.get("groupTotalRow");if("function"==typeof t)return e.getCallback("groupTotalRow");if(t)return()=>t;const a=e.get("groupIncludeFooter");if("function"==typeof a){const t=e.getCallback("groupIncludeFooter");return e=>t(e)?"bottom":void 0}return()=>a?"bottom":void 0}function oe(e){return e.exists("groupDisplayType")?"multipleColumns"===e.get("groupDisplayType"):e.get("groupHideOpenParents")}function ie(e,t){return!t&&"groupRows"===e.get("groupDisplayType")}function se(e){const t=e.getCallback("getRowId");return void 0===t?t:e=>{let a=t(e);return"string"!=typeof a&&(p("The getRowId callback must return a string. The ID ",a," is being cast to a string."),a=String(a)),a}}function le(e){return e?.checkboxes??!0}function ge(e){return"multiRow"===e?.mode&&(e.headerCheckbox??!0)}function ce(e){return"string"!=typeof e.get("rowSelection")}function de(e){return void 0!==e.get("cellSelection")}function he(e){const t=e.get("cellSelection");return void 0!==t?"boolean"!=typeof t&&(t?.suppressMultiRanges??!1):e.get("suppressMultiRangeSelection")}function ue(e){const t=e.get("cellSelection");return void 0!==t?!!t:e.get("enableRangeSelection")}function pe(e){const t=e.get("cellSelection");return void 0!==t?"boolean"!=typeof t&&"fill"===t.handle?.mode:e.get("enableFillHandle")}function me(e){const t=e.get("cellSelection");return void 0!==t?"boolean"!=typeof t&&"fill"===t.handle?.mode?t.handle:void 0:{mode:"fill",setFillValue:e.get("fillOperation"),direction:e.get("fillHandleDirection"),suppressClearOnFillReduction:e.get("suppressClearOnFillReduction")}}function ve(e){const t=e.get("rowSelection")??"single";if("string"==typeof t){const t=e.get("suppressRowClickSelection"),a=e.get("suppressRowDeselection");return(!t||!a)&&(t?"enableDeselection":!a||"enableSelection")}return("singleRow"===t.mode||"multiRow"===t.mode)&&(t.enableClickSelection??!1)}function fe(e){const t=ve(e);return!0===t||"enableDeselection"===t}function be(e){const t=e.get("rowSelection");return"string"==typeof t?e.get("isRowSelectable"):t?.isRowSelectable}function we(e){const t=e.get("rowSelection");if("string"==typeof t)switch(t){case"multiple":return"multiRow";case"single":return"singleRow";default:return}return t?.mode}function Ce(e){return"multiRow"===we(e)}function ye(e){const t=e.get("rowSelection");if("string"==typeof t){const t=e.get("groupSelectsChildren"),a=e.get("groupSelectsFiltered");return t&&a?"filteredDescendants":t?"descendants":"self"}return"multiRow"===t?.mode?t.groupSelects:void 0}function ke(e){const t=ye(e);return"descendants"===t||"filteredDescendants"===t}function Se(e){return e.isModuleRegistered("@ag-grid-enterprise/set-filter")&&!e.get("suppressSetFilterByDefault")}function xe(e){return null!=e&&e.length>0}function ze(e){if(e&&e.length)return e[e.length-1]}function Ee(e,t,a){return null==e&&null==t||null!=e&&null!=t&&e.length===t.length&&e.every(((e,r)=>a?a(e,t[r]):t[r]===e))}function Re(e,t){return Ee(e,t)}function Me(e,t){if(e)for(let a=e.length-2;a>=0;a--){const r=e[a]===t,n=e[a+1]===t;r&&n&&e.splice(a+1,1)}}function Fe(e,t){const a=e.indexOf(t);a>=0&&(e[a]=e[e.length-1],e.pop())}function De(e,t){const a=e.indexOf(t);a>=0&&e.splice(a,1)}function Ae(e,t,a){e.splice(a,0,t)}function Pe(e,t,a){if(null!=e&&null!=t)for(let r=t.length-1;r>=0;r--){Ae(e,t[r],a)}}function Te(e,t,a){!function(e,t){for(let a=0;aAe(e,t,a)))}function Ie(e,t){return e.indexOf(t)>-1}function Le(e){return e?.flatMap((e=>e))??[]}function Oe(e,t){null!=t&&null!=e&&t.forEach((t=>e.push(t)))}function Ne(e,t){if(null!=e)for(let a=e.length-1;a>=0;a--)t(e[a],a)}var Ge="__ag_Grid_Stop_Propagation",Be=["touchstart","touchend","touchmove","touchcancel","scroll"],He={};function qe(e){e[Ge]=!0}function Ve(e){return!0===e[Ge]}var We=(()=>{const e={select:"input",change:"input",submit:"form",reset:"form",error:"img",load:"img",abort:"img"};return t=>{if("boolean"==typeof He[t])return He[t];const a=document.createElement(e[t]||"div");return He[t="on"+t]=t in a}})();function je(e,t,a){let r=t;for(;r;){const t=K(e,r,a);if(t)return t;r=r.parentElement}return null}function Ue(e,t){return!(!t||!e)&&function(e){const t=e;if(t.path)return t.path;if(t.composedPath)return t.composedPath();return function(e){const t=[];let a=e.target;for(;a;)t.push(a),a=a.parentElement;return t}(t)}(t).indexOf(e)>=0}var Ke=class{constructor(){this.destroyFunctions=[],this.destroyed=!1,this.__v_skip=!0,this.propertyListenerId=0,this.lastChangeSetIdLookup={},this.isAlive=()=>!this.destroyed}preWireBeans(e){this.frameworkOverrides=e.frameworkOverrides,this.stubContext=e.context,this.eventService=e.eventService,this.gos=e.gos,this.localeService=e.localeService}getFrameworkOverrides(){return this.frameworkOverrides}destroy(){for(let e=0;enull;e instanceof HTMLElement?function(e,t,a,r){const n=Ie(Be,a)?{passive:!0}:void 0;e&&e.addEventListener&&e.addEventListener(t,a,r,n)}(this.getFrameworkOverrides(),e,t,a):e.addEventListener(t,a);const r=()=>(e.removeEventListener(t,a),null);return this.destroyFunctions.push(r),()=>(r(),this.destroyFunctions=this.destroyFunctions.filter((e=>e!==r)),null)}setupGridOptionListener(e,t){this.gos.addPropertyEventListener(e,t);const a=()=>(this.gos.removePropertyEventListener(e,t),null);return this.destroyFunctions.push(a),()=>(a(),this.destroyFunctions=this.destroyFunctions.filter((e=>e!==a)),null)}addManagedPropertyListener(e,t){return this.destroyed?()=>null:this.setupGridOptionListener(e,t)}addManagedPropertyListeners(e,t){if(this.destroyed)return;const a=e.join("-")+this.propertyListenerId++,r=e=>{if(e.changeSet){if(e.changeSet&&e.changeSet.id===this.lastChangeSetIdLookup[a])return;this.lastChangeSetIdLookup[a]=e.changeSet.id}const r={type:"gridPropertyChanged",changeSet:e.changeSet,source:e.source};t(r)};e.forEach((e=>this.setupGridOptionListener(e,r)))}addDestroyFunc(e){this.isAlive()?this.destroyFunctions.push(e):e()}createManagedBean(e,t){const a=this.createBean(e,t);return this.addDestroyFunc(this.destroyBean.bind(this,e,t)),a}createBean(e,t,a){return(t||this.stubContext).createBean(e,a)}destroyBean(e,t){return(t||this.stubContext).destroyBean(e)}destroyBeans(e,t){return(t||this.stubContext).destroyBeans(e)}};function Ye(e){return e instanceof Qe}var Qe=class extends Ke{constructor(e,t,a,r){super(),this.isColumn=!1,this.expandable=!1,this.instanceId=mt(),this.expandableListenerRemoveCallback=null,this.colGroupDef=e,this.groupId=t,this.expanded=!!e&&!!e.openByDefault,this.padding=a,this.level=r}destroy(){this.expandableListenerRemoveCallback&&this.reset(null,void 0),super.destroy()}reset(e,t){this.colGroupDef=e,this.level=t,this.originalParent=null,this.expandableListenerRemoveCallback&&this.expandableListenerRemoveCallback(),this.children=void 0,this.expandable=void 0}getInstanceId(){return this.instanceId}setOriginalParent(e){this.originalParent=e}getOriginalParent(){return this.originalParent}getLevel(){return this.level}isVisible(){return!!this.children&&this.children.some((e=>e.isVisible()))}isPadding(){return this.padding}setExpanded(e){this.expanded=void 0!==e&&e,this.dispatchLocalEvent({type:"expandedChanged"})}isExpandable(){return this.expandable}isExpanded(){return this.expanded}getGroupId(){return this.groupId}getId(){return this.getGroupId()}setChildren(e){this.children=e}getChildren(){return this.children}getColGroupDef(){return this.colGroupDef}getLeafColumns(){const e=[];return this.addLeafColumns(e),e}addLeafColumns(e){this.children&&this.children.forEach((t=>{vt(t)?e.push(t):Ye(t)&&t.addLeafColumns(e)}))}getColumnGroupShow(){const e=this.colGroupDef;if(e)return e.columnGroupShow}setupExpandable(){this.setExpandable(),this.expandableListenerRemoveCallback&&this.expandableListenerRemoveCallback();const e=this.onColumnVisibilityChanged.bind(this);this.getLeafColumns().forEach((t=>t.addEventListener("visibleChanged",e))),this.expandableListenerRemoveCallback=()=>{this.getLeafColumns().forEach((t=>t.removeEventListener("visibleChanged",e))),this.expandableListenerRemoveCallback=null}}setExpandable(){if(this.isPadding())return;let e=!1,t=!1,a=!1;const r=this.findChildrenRemovingPadding();for(let n=0,o=r.length;n{a.forEach((a=>{Ye(a)&&a.isPadding()?t(a.children):e.push(a)}))};return t(this.children),e}onColumnVisibilityChanged(){this.setExpandable()}},_e="ag-Grid-ControlsColumn",Ze=class extends Ke{constructor(){super(...arguments),this.beanName="controlsColService"}createControlsCols(){const{gos:e}=this,t=e.get("rowSelection");if(!t||"object"!=typeof t)return[];const a=le(t),r=ge(t);if(a||r){const t=e.get("selectionColumnDef"),a={maxWidth:50,resizable:!1,suppressHeaderMenuButton:!0,sortable:!1,suppressMovable:!0,lockPosition:e.get("enableRtl")?"right":"left",comparator(e,t,a,r){const n=a.isSelected(),o=r.isSelected();return n&&o?0:n?1:-1},editable:!1,suppressFillHandle:!0,...t,colId:`${_e}`},r=new ft(a,null,a.colId,!1);return this.createBean(r),[r]}return[]}},Je="ag-Grid-AutoColumn";function Xe(e){const t=[],a=e=>{for(let r=0;re+t.getActualWidth()),0)}function et(e,t,a){const r={};if(!t)return;yt(null,t,(e=>{r[e.getInstanceId()]=e})),a&&yt(null,a,(e=>{r[e.getInstanceId()]=null}));const n=Object.values(r).filter((e=>null!=e));e.destroyBeans(n)}function tt(e){return e.getId().startsWith(Je)}function at(e){return e.getColId().startsWith(_e)}function rt(e){let t=[];if(e instanceof Array){e.some((e=>"string"!=typeof e))?p("if colDef.type is supplied an array it should be of type 'string[]'"):t=e}else"string"==typeof e?t=e.split(","):p("colDef.type should be of type 'string' | 'string[]'");return t}var nt=class{constructor(e){this.frameworkOverrides=e,this.wrappedListeners=new Map,this.wrappedGlobalListeners=new Map}wrap(e){let t=e;return this.frameworkOverrides.shouldWrapOutgoing&&(t=t=>{this.frameworkOverrides.wrapOutgoing((()=>e(t)))},this.wrappedListeners.set(e,t)),t}wrapGlobal(e){let t=e;return this.frameworkOverrides.shouldWrapOutgoing&&(t=(t,a)=>{this.frameworkOverrides.wrapOutgoing((()=>e(t,a)))},this.wrappedGlobalListeners.set(e,t)),t}unwrap(e){return this.wrappedListeners.get(e)??e}unwrapGlobal(e){return this.wrappedGlobalListeners.get(e)??e}},ot=new Set(["__proto__","constructor","prototype"]);function it(e,t){if(null!=e)if(Array.isArray(e))for(let a=0;a{if(t&&t.indexOf(e)>=0||ot.has(e))return;const n=a[e],o=ht(n)&&n.constructor===Object;r[e]=o?lt(n):n})),r}function gt(e){if(!e)return[];const t=Object;if("function"==typeof t.values)return t.values(e);const a=[];for(const t in e)e.hasOwnProperty(t)&&e.propertyIsEnumerable(t)&&a.push(e[t]);return a}function ct(e,t,a=!0,r=!1){E(t)&&it(t,((t,n)=>{if(ot.has(t))return;let o=e[t];if(o!==n){if(r){if(null==o&&null!=n){"object"==typeof n&&n.constructor===Object&&(o={},e[t]=o)}}ht(n)&&ht(o)&&!Array.isArray(o)?ct(o,n,a,r):(a||void 0!==n)&&(e[t]=n)}}))}function dt(e,t,a){if(!t||!e)return;if(!a)return e[t];const r=t.split(".");let n=e;for(let e=0;e=0&&!e,this.tooltipFieldContainsDots=E(this.colDef.tooltipField)&&this.colDef.tooltipField.indexOf(".")>=0&&!e}initMinAndMaxWidths(){const e=this.colDef;this.minWidth=e.minWidth??this.gos.environment.getDefaultColumnMinWidth(),this.maxWidth=e.maxWidth??Number.MAX_SAFE_INTEGER}initTooltip(){this.tooltipEnabled=E(this.colDef.tooltipField)||E(this.colDef.tooltipValueGetter)||E(this.colDef.tooltipComponent)}resetActualWidth(e){const t=this.calculateColInitialWidth(this.colDef);this.setActualWidth(t,e,!0)}calculateColInitialWidth(e){let t;const a=D(e.width),r=D(e.initialWidth);return t=null!=a?a:null!=r?r:200,Math.max(Math.min(t,this.maxWidth),this.minWidth)}isEmptyGroup(){return!1}isRowGroupDisplayed(e){if(R(this.colDef)||R(this.colDef.showRowGroup))return!1;const t=!0===this.colDef.showRowGroup,a=this.colDef.showRowGroup===e;return t||a}isPrimary(){return this.primary}isFilterAllowed(){return!!this.colDef.filter}isFieldContainsDots(){return this.fieldContainsDots}isTooltipEnabled(){return this.tooltipEnabled}isTooltipFieldContainsDots(){return this.tooltipFieldContainsDots}getHighlighted(){return this.highlighted}addEventListener(e,t){this.frameworkOverrides.shouldWrapOutgoing&&!this.frameworkEventListenerService&&(this.columnEventService.setFrameworkOverrides(this.frameworkOverrides),this.frameworkEventListenerService=new nt(this.frameworkOverrides));const a=this.frameworkEventListenerService?.wrap(t)??t;this.columnEventService.addEventListener(e,a)}removeEventListener(e,t){const a=this.frameworkEventListenerService?.unwrap(t)??t;this.columnEventService.removeEventListener(e,a)}createColumnFunctionCallbackParams(e){return this.gos.addGridCommonParams({node:e,data:e.data,column:this,colDef:this.colDef})}isSuppressNavigable(e){if("boolean"==typeof this.colDef.suppressNavigable)return this.colDef.suppressNavigable;if("function"==typeof this.colDef.suppressNavigable){const t=this.createColumnFunctionCallbackParams(e);return(0,this.colDef.suppressNavigable)(t)}return!1}isCellEditable(e){if(e.group)if(this.gos.get("treeData")){if(!e.data&&!this.gos.get("enableGroupEdit"))return!1}else if(!this.gos.get("enableGroupEdit"))return!1;return this.isColumnFunc(e,this.colDef.editable)}isSuppressFillHandle(){return!!this.colDef.suppressFillHandle}isAutoHeight(){return!!this.colDef.autoHeight}isAutoHeaderHeight(){return!!this.colDef.autoHeaderHeight}isRowDrag(e){return this.isColumnFunc(e,this.colDef.rowDrag)}isDndSource(e){return this.isColumnFunc(e,this.colDef.dndSource)}isCellCheckboxSelection(e){const t=this.gos.get("rowSelection");if(t&&"string"!=typeof t){const a=at(this)&&le(t);return this.isColumnFunc(e,a)}return this.isColumnFunc(e,this.colDef.checkboxSelection)}isSuppressPaste(e){return this.isColumnFunc(e,this.colDef?this.colDef.suppressPaste:null)}isResizable(){return!!this.getColDefValue("resizable")}getColDefValue(e){return this.colDef[e]??ut[e]}isColumnFunc(e,t){if("boolean"==typeof t)return t;if("function"==typeof t){return t(this.createColumnFunctionCallbackParams(e))}return!1}setHighlighted(e){this.highlighted!==e&&(this.highlighted=e,this.columnEventService.dispatchEvent(this.createColumnEvent("headerHighlightChanged","uiColumnMoved")))}setMoving(e,t){this.moving=e,this.columnEventService.dispatchEvent(this.createColumnEvent("movingChanged",t))}createColumnEvent(e,t){return this.gos.addGridCommonParams({type:e,column:this,columns:[this],source:t})}isMoving(){return this.moving}getSort(){return this.sort}setSort(e,t){this.sort!==e&&(this.sort=e,this.columnEventService.dispatchEvent(this.createColumnEvent("sortChanged",t))),this.dispatchStateUpdatedEvent("sort")}isSortable(){return!!this.getColDefValue("sortable")}isSortAscending(){return"asc"===this.sort}isSortDescending(){return"desc"===this.sort}isSortNone(){return R(this.sort)}isSorting(){return E(this.sort)}getSortIndex(){return this.sortIndex}setSortIndex(e){this.sortIndex=e,this.dispatchStateUpdatedEvent("sortIndex")}setMenuVisible(e,t){this.menuVisible!==e&&(this.menuVisible=e,this.columnEventService.dispatchEvent(this.createColumnEvent("menuVisibleChanged",t)))}isMenuVisible(){return this.menuVisible}setAggFunc(e){this.aggFunc=e,this.dispatchStateUpdatedEvent("aggFunc")}getAggFunc(){return this.aggFunc}getLeft(){return this.left}getOldLeft(){return this.oldLeft}getRight(){return this.left+this.actualWidth}setLeft(e,t){this.oldLeft=this.left,this.left!==e&&(this.left=e,this.columnEventService.dispatchEvent(this.createColumnEvent("leftChanged",t)))}isFilterActive(){return this.filterActive}setFilterActive(e,t,a){this.filterActive!==e&&(this.filterActive=e,this.columnEventService.dispatchEvent(this.createColumnEvent("filterActiveChanged",t)));const r=this.createColumnEvent("filterChanged",t);a&&ct(r,a),this.columnEventService.dispatchEvent(r)}isHovered(){return this.columnHoverService.isHovered(this)}setPinned(e){this.pinned=!0===e||"left"===e?"left":"right"===e?"right":null,this.dispatchStateUpdatedEvent("pinned")}setFirstRightPinned(e,t){this.firstRightPinned!==e&&(this.firstRightPinned=e,this.columnEventService.dispatchEvent(this.createColumnEvent("firstRightPinnedChanged",t)))}setLastLeftPinned(e,t){this.lastLeftPinned!==e&&(this.lastLeftPinned=e,this.columnEventService.dispatchEvent(this.createColumnEvent("lastLeftPinnedChanged",t)))}isFirstRightPinned(){return this.firstRightPinned}isLastLeftPinned(){return this.lastLeftPinned}isPinned(){return"left"===this.pinned||"right"===this.pinned}isPinnedLeft(){return"left"===this.pinned}isPinnedRight(){return"right"===this.pinned}getPinned(){return this.pinned}setVisible(e,t){const a=!0===e;this.visible!==a&&(this.visible=a,this.columnEventService.dispatchEvent(this.createColumnEvent("visibleChanged",t))),this.dispatchStateUpdatedEvent("hide")}isVisible(){return this.visible}isSpanHeaderHeight(){return!this.getColDef().suppressSpanHeaderHeight}getColumnGroupPaddingInfo(){let e=this.getParent();if(!e||!e.isPadding())return{numberOfParents:0,isSpanningTotal:!1};const t=e.getPaddingLevel()+1;let a=!0;for(;e;){if(!e.isPadding()){a=!1;break}e=e.getParent()}return{numberOfParents:t,isSpanningTotal:a}}getColDef(){return this.colDef}getDefinition(){return this.colDef}getColumnGroupShow(){return this.colDef.columnGroupShow}getColId(){return this.colId}getId(){return this.colId}getUniqueId(){return this.colId}getActualWidth(){return this.actualWidth}getAutoHeaderHeight(){return this.autoHeaderHeight}setAutoHeaderHeight(e){const t=e!==this.autoHeaderHeight;return this.autoHeaderHeight=e,t}createBaseColDefParams(e){return this.gos.addGridCommonParams({node:e,data:e.data,colDef:this.colDef,column:this})}getColSpan(e){if(R(this.colDef.colSpan))return 1;const t=this.createBaseColDefParams(e),a=this.colDef.colSpan(t);return Math.max(a,1)}getRowSpan(e){if(R(this.colDef.rowSpan))return 1;const t=this.createBaseColDefParams(e),a=this.colDef.rowSpan(t);return Math.max(a,1)}setActualWidth(e,t,a=!1){e=Math.max(e,this.minWidth),e=Math.min(e,this.maxWidth),this.actualWidth!==e&&(this.actualWidth=e,this.flex&&"flex"!==t&&"gridInitializing"!==t&&(this.flex=null),a||this.fireColumnWidthChangedEvent(t)),this.dispatchStateUpdatedEvent("width")}fireColumnWidthChangedEvent(e){this.columnEventService.dispatchEvent(this.createColumnEvent("widthChanged",e))}isGreaterThanMax(e){return e>this.maxWidth}getMinWidth(){return this.minWidth}getMaxWidth(){return this.maxWidth}getFlex(){return this.flex||0}setFlex(e){this.flex!==e&&(this.flex=e),this.dispatchStateUpdatedEvent("flex")}setMinimum(e){this.setActualWidth(this.minWidth,e)}setRowGroupActive(e,t){this.rowGroupActive!==e&&(this.rowGroupActive=e,this.columnEventService.dispatchEvent(this.createColumnEvent("columnRowGroupChanged",t))),this.dispatchStateUpdatedEvent("rowGroup")}isRowGroupActive(){return this.rowGroupActive}setPivotActive(e,t){this.pivotActive!==e&&(this.pivotActive=e,this.columnEventService.dispatchEvent(this.createColumnEvent("columnPivotChanged",t))),this.dispatchStateUpdatedEvent("pivot")}isPivotActive(){return this.pivotActive}isAnyFunctionActive(){return this.isPivotActive()||this.isRowGroupActive()||this.isValueActive()}isAnyFunctionAllowed(){return this.isAllowPivot()||this.isAllowRowGroup()||this.isAllowValue()}setValueActive(e,t){this.aggregationActive!==e&&(this.aggregationActive=e,this.columnEventService.dispatchEvent(this.createColumnEvent("columnValueChanged",t)))}isValueActive(){return this.aggregationActive}isAllowPivot(){return!0===this.colDef.enablePivot}isAllowValue(){return!0===this.colDef.enableValue}isAllowRowGroup(){return!0===this.colDef.enableRowGroup}dispatchStateUpdatedEvent(e){this.columnEventService.dispatchEvent({type:"columnStateUpdated",key:e})}},bt={numericColumn:{headerClass:"ag-right-aligned-header",cellClass:"ag-right-aligned-cell"},rightAligned:{headerClass:"ag-right-aligned-header",cellClass:"ag-right-aligned-cell"}},wt=class{constructor(){this.existingKeys={}}addExistingKeys(e){for(let t=0;t{Ye(e)&&e.setupExpandable(),e.setOriginalParent(t)})),{columnTree:c,treeDept:g}}extractExistingTreeData(e){const t=[],a=[],r=[];return e&&yt(null,e,(e=>{if(Ye(e)){const t=e;a.push(t)}else{const a=e;r.push(a.getId()),t.push(a)}})),{existingCols:t,existingGroups:a,existingColKeys:r}}balanceTreeForAutoCols(e,t){const a=[],r=this.findDepth(t);return e.forEach((e=>{let t=e;for(let a=r-1;a>=0;a--){const r=new Qe(null,`FAKE_PATH_${e.getId()}}_${a}`,!0,a);this.createBean(r),r.setChildren([t]),t.setOriginalParent(r),t=r}0===r&&e.setOriginalParent(null),a.push(t)})),[a,r]}findDepth(e){let t=0,a=e;for(;a&&a[0]&&Ye(a[0]);)t++,a=a[0].getChildren();return t}balanceColumnTree(e,t,a,r){const n=[];for(let o=0;o=t;e--){const e=r.getUniqueKey(null,null),a=this.createMergedColGroupDef(null),n=new Qe(a,e,!0,t);this.createBean(n),s&&s.setChildren([n]),s=n,o||(o=s)}if(o&&s){n.push(o);if(e.some((e=>Ye(e)))){s.setChildren([i]);continue}s.setChildren(e);break}n.push(i)}}return n}findMaxDept(e,t){let a=t;for(let r=0;r{if(e in a)p(`the column type '${e}' is a default column type and cannot be overridden.`);else{t.type&&p("Column type definitions 'columnTypes' with a 'type' attribute are not supported because a column type cannot refer to another column type. Only column definitions 'columnDefs' can use the 'type' attribute to refer to a column type."),a[e]=t}})),e.forEach((e=>{const r=a[e.trim()];r?ct(t,r,!1,!0):p("colDef.type '"+e+"' does not correspond to defined gridOptions.columnTypes")}))}isColumnGroup(e){return void 0!==e.children}};function yt(e,t,a){if(t)for(let r=0;rthis.refreshAll(St(e.source)))),this.addManagedPropertyListener("rowSelection",(e=>{this.onSelectionOptionsChanged(e.currentValue,e.previousValue,St(e.source))})),this.addManagedPropertyListener("autoGroupColumnDef",(e=>this.onAutoGroupColumnDefChanged(St(e.source)))),this.addManagedPropertyListeners(["defaultColDef","defaultColGroupDef","columnTypes","suppressFieldDotNotation"],(e=>this.recreateColumnDefs(St(e.source)))),this.addManagedPropertyListener("pivotMode",(e=>this.setPivotMode(this.gos.get("pivotMode"),St(e.source)))),this.addManagedEventListeners({firstDataRendered:()=>this.onFirstDataRendered()})}createColsFromColDefs(e){const t=this.colDefs?this.columnApplyStateService.compareColumnStatesAndDispatchEvents(e):void 0;this.valueCache.expire();const a=this.colDefCols?.list,r=this.colDefCols?.tree,n=this.columnFactory.createColumnTree(this.colDefs,!0,r,e);et(this.context,this.colDefCols?.tree,n.columnTree);const o=n.columnTree,i=n.treeDept,s=Xe(o),l={};s.forEach((e=>l[e.getId()]=e)),this.colDefCols={tree:o,treeDepth:i,list:s,map:l},this.funcColsService.extractCols(e,a),this.ready=!0,this.refreshCols(!0),this.visibleColsService.refresh(e),this.columnViewportService.checkViewportColumns(),this.eventDispatcher.everythingChanged(e),t&&(this.changeEventsDispatching=!0,t(),this.changeEventsDispatching=!1),this.eventDispatcher.newColumnsLoaded(e),"gridInitializing"===e&&this.columnSizeService.applyAutosizeStrategy()}refreshCols(e){if(!this.colDefCols)return;const t=this.cols?.tree;this.saveColOrder(),this.selectCols(),this.createAutoCols(),this.addAutoCols(),this.createControlsCols(),this.addControlsCols();const a=(r=this.gos,this.showingPivotResult?!r.get("enableStrictPivotColumnOrder"):r.get("maintainColumnOrder"));var r;e&&!a||this.restoreColOrder(),this.positionLockedCols(),this.showRowGroupColsService?.refresh(),this.quickFilterService?.refreshQuickFilterCols(),this.setColSpanActive(),this.setAutoHeightActive(),this.visibleColsService.clear(),this.columnViewportService.clear();!Ee(t,this.cols.tree)&&this.eventDispatcher.gridColumns()}selectCols(){const e=this.pivotResultColsService.getPivotResultCols();if(this.showingPivotResult=null!=e,e){const{map:t,list:a,tree:r,treeDepth:n}=e;this.cols={list:a.slice(),map:{...t},tree:r.slice(),treeDepth:n};e.list.some((e=>void 0!==this.cols?.map[e.getColId()]))||(this.lastPivotOrder=null)}else{const{map:e,list:t,tree:a,treeDepth:r}=this.colDefCols;this.cols={list:t.slice(),map:{...e},tree:a.slice(),treeDepth:r}}}getColsToShow(){const e=this.isPivotMode()&&!this.isShowingPivotResult(),t=this.funcColsService.getValueColumns();return this.cols.list.filter((a=>{const r=tt(a);if(e){const e=t&&Ie(t,a);return r||e}return r||a.isVisible()}))}addAutoCols(){null!=this.autoCols&&(this.cols.list=this.autoCols.list.concat(this.cols.list),this.cols.tree=this.autoCols.tree.concat(this.cols.tree),xt(this.cols))}createAutoCols(){const e=ie(this.gos,this.pivotMode),t=this.pivotMode?this.gos.get("pivotSuppressAutoColumn"):this.isSuppressAutoCol(),a=this.funcColsService.getRowGroupColumns(),r=()=>{this.autoCols&&(et(this.context,this.autoCols.tree),this.autoCols=null)};if(!(a.length>0||this.gos.get("treeData"))||t||e||!this.autoColService)return void r();const n=this.autoColService.createAutoCols(a)??[],o=Et(n,this.autoCols?.list||null),i=this.cols.treeDepth,s=this.autoCols?this.autoCols.treeDepth:-1;if(o&&s==i)return;r();const[l,g]=this.columnFactory.balanceTreeForAutoCols(n,this.cols.tree);this.autoCols={list:n,tree:l,treeDepth:g,map:{}};const c=e=>{if(!e)return null;const t=e.filter((e=>!tt(e)));return[...n,...t]};this.lastOrder=c(this.lastOrder),this.lastPivotOrder=c(this.lastPivotOrder)}createControlsCols(){const e=()=>{et(this.context,this.controlsCols?.tree),this.controlsCols=null};this.controlsColService||e();const t=this.cols.treeDepth,a=(this.controlsCols?.treeDepth??-1)==t,r=this.controlsColService?.createControlsCols()??[];if(Et(r,this.controlsCols?.list??[])&&a)return;e();const[n,o]=this.columnFactory.balanceTreeForAutoCols(r,this.cols.tree);this.controlsCols={list:r,tree:n,treeDepth:o,map:{}};const i=e=>{if(!e)return null;const t=e.filter((e=>!at(e)));return[...r,...t]};this.lastOrder=i(this.lastOrder),this.lastPivotOrder=i(this.lastPivotOrder)}addControlsCols(){null!=this.controlsCols&&(this.cols.list=this.controlsCols.list.concat(this.cols.list),this.cols.tree=this.controlsCols.tree.concat(this.cols.tree),xt(this.cols))}refreshAll(e){this.isReady()&&(this.refreshCols(!1),this.visibleColsService.refresh(e))}setColsVisible(e,t=!1,a){this.columnApplyStateService.applyColumnState({state:e.map((e=>({colId:"string"==typeof e?e:e.getColId(),hide:!t})))},a)}setColsPinned(e,t,a){if(!this.cols)return;if(M(e))return;if(B(this.gos,"print"))return void p("Changing the column pinning status is not allowed with domLayout='print'");let r;this.columnAnimationService.start(),r=!0===t||"left"===t?"left":"right"===t?"right":null;const n=[];e.forEach((e=>{if(!e)return;const t=this.getCol(e);t&&t.getPinned()!==r&&(t.setPinned(r),n.push(t))})),n.length&&(this.visibleColsService.refresh(a),this.eventDispatcher.columnPinned(n,a)),this.columnAnimationService.finish()}setColumnGroupOpened(e,t,a){let r;r=Ye(e)?e.getId():e||"",this.columnGroupStateService.setColumnGroupState([{groupId:r,open:t}],a)}getProvidedColGroup(e){let t=null;return yt(null,this.cols?.tree,(a=>{Ye(a)&&a.getId()===e&&(t=a)})),t}isColGroupLocked(e){const t=this.gos.get("groupLockGroupColumns");if(!e.isRowGroupActive()||0===t)return!1;if(-1===t)return!0;return t>this.funcColsService.getRowGroupColumns().findIndex((t=>t.getColId()===e.getColId()))}isSuppressAutoCol(){if("custom"===this.gos.get("groupDisplayType"))return!0;return"custom"===this.gos.get("treeDataDisplayType")}setAutoHeightActive(){if(this.autoHeightActive=this.cols.list.some((e=>e.isVisible()&&e.isAutoHeight())),this.autoHeightActive){this.autoHeightActiveAtLeastOnce=!0;N(this.gos)||G(this.gos)||p("autoHeight columns only work with Client Side Row Model and Server Side Row Model.")}}restoreColOrder(){const e=this.showingPivotResult?this.lastPivotOrder:this.lastOrder;if(!e)return;const t=new Map(e.map(((e,t)=>[e,t])));if(!this.cols.list.some((e=>t.has(e))))return;const a=new Map(this.cols.list.map((e=>[e,!0]))),r=e.filter((e=>a.has(e))),n=new Map(r.map((e=>[e,!0]))),o=this.cols.list.filter((e=>!n.has(e))),i=r.slice();o.forEach((e=>{let t=e.getOriginalParent();if(!t)return void i.push(e);const a=[];for(;!a.length&&t;){t.getLeafColumns().forEach((e=>{const t=i.indexOf(e)>=0,r=a.indexOf(e)<0;t&&r&&a.push(e)})),t=t.getOriginalParent()}if(!a.length)return void i.push(e);const r=a.map((e=>i.indexOf(e))),n=Math.max(...r);Ae(i,e,n+1)})),this.cols.list=i}sortColsLikeKeys(e){if(null==this.cols)return;let t=[];const a={};e.forEach((e=>{if(a[e])return;const r=this.cols.map[e];r&&(t.push(r),a[e]=!0)}));let r=0;this.cols.list.forEach((e=>{const n=e.getColId();if(null!=a[n])return;n.startsWith(Je)?Ae(t,e,r++):t.push(e)})),t=this.columnMoveService.placeLockedColumns(t),this.columnMoveService.doesMovePassMarryChildren(t)?this.cols.list=t:p("Applying column order broke a group where columns should be married together. Applying new order has been discarded.")}sortColsLikeCols(e){if(!e||e.length<=1)return;e.filter((e=>this.cols.list.indexOf(e)<0)).length>0||e.sort(((e,t)=>this.cols.list.indexOf(e)-this.cols.list.indexOf(t)))}resetColDefIntoCol(e,t){const a=e.getUserProvidedColDef();if(!a)return!1;const r=this.columnFactory.addColumnDefaultAndTypes(a,e.getColId());return e.setColDef(r,a,t),!0}queueResizeOperations(){this.shouldQueueResizeOperations=!0}isShouldQueueResizeOperations(){return this.shouldQueueResizeOperations}processResizeOperations(){this.shouldQueueResizeOperations=!1,this.resizeOperationQueue.forEach((e=>e())),this.resizeOperationQueue=[]}pushResizeOperation(e){this.resizeOperationQueue.push(e)}moveInCols(e,t,a){Te(this.cols?.list,e,t),this.visibleColsService.refresh(a)}positionLockedCols(){this.cols.list=this.columnMoveService.placeLockedColumns(this.cols.list)}saveColOrder(){this.showingPivotResult?this.lastPivotOrder=this.cols?.list:this.lastOrder=this.cols?.list}getColumnDefs(){if(!this.colDefCols)return;const e=this.colDefCols.list.slice();this.showingPivotResult?e.sort(((e,t)=>this.lastOrder.indexOf(e)-this.lastOrder.indexOf(t))):this.lastOrder&&e.sort(((e,t)=>this.cols.list.indexOf(e)-this.cols.list.indexOf(t)));const t=this.funcColsService.getRowGroupColumns(),a=this.funcColsService.getPivotColumns();return this.columnDefFactory.buildColumnDefs(e,t,a)}isShowingPivotResult(){return this.showingPivotResult}isChangeEventsDispatching(){return this.changeEventsDispatching}isColSpanActive(){return this.colSpanActive}isProvidedColGroupsPresent(){return this.colDefCols?.treeDepth>0}setColSpanActive(){this.colSpanActive=this.cols.list.some((e=>null!=e.getColDef().colSpan))}isAutoRowHeightActive(){return this.autoHeightActive}wasAutoRowHeightEverActive(){return this.autoHeightActiveAtLeastOnce}getHeaderRowCount(){return this.cols?this.cols.treeDepth+1:-1}isReady(){return this.ready}isPivotMode(){return this.pivotMode}setPivotMode(e,t){e!==this.pivotMode&&this.isPivotSettingAllowed(this.pivotMode)&&(this.pivotMode=e,this.ready&&(this.refreshCols(!1),this.visibleColsService.refresh(t),this.eventDispatcher.pivotModeChanged()))}isPivotSettingAllowed(e){return!e||!this.gos.get("treeData")||(p("Pivot mode not available with treeData."),!1)}isPivotActive(){const e=this.funcColsService.getPivotColumns();return this.pivotMode&&!M(e)}recreateColumnDefs(e){this.cols&&(this.autoCols&&this.autoColService.updateAutoCols(this.autoCols.list,e),this.createColsFromColDefs(e))}setColumnDefs(e,t){this.colDefs=e,this.createColsFromColDefs(t)}destroy(){et(this.context,this.colDefCols?.tree),et(this.context,this.autoCols?.tree),et(this.context,this.controlsCols?.tree),super.destroy()}getColTree(){return this.cols.tree}getColDefColTree(){return this.colDefCols.tree}getColDefCols(){return this.colDefCols?.list?this.colDefCols.list:null}getCols(){return this.cols?.list??[]}getAllCols(){const e=this.pivotResultColsService.getPivotResultCols(),t=e?.list;return[this.colDefCols?.list??[],this.autoCols?.list??[],this.controlsCols?.list??[],t??[]].flat()}getColsForKeys(e){return e?e.map((e=>this.getCol(e))).filter((e=>null!=e)):[]}getColDefCol(e){return this.colDefCols?.list?this.getColFromCollection(e,this.colDefCols):null}getCol(e){return null==e?null:this.getColFromCollection(e,this.cols)}getColFromCollection(e,t){if(null==t)return null;const{map:a,list:r}=t;if("string"==typeof e&&a[e])return a[e];for(let t=0;tzt(t,e)))??null}getAutoCols(){return this.autoCols?.list??null}setColHeaderHeight(e,t){e.setAutoHeaderHeight(t)&&(e.isColumn?this.eventDispatcher.headerHeight(e):this.eventDispatcher.groupHeaderHeight(e))}getGroupRowsHeight(){const e=[],t=this.ctrlsService.getHeaderRowContainerCtrls();for(const a of t){if(!a)continue;const t=a.getGroupRowCount()||0;for(let r=0;rn)&&(e[r]=a)}}}return e}getColumnGroupHeaderRowHeight(e){const t=this.isPivotMode()?this.getPivotGroupHeaderHeight():this.getGroupHeaderHeight();let a=0;const r=e.getHeaderCtrls();for(const e of r){const t=e.getColumn();if(t.isAutoHeaderHeight()){const e=t.getAutoHeaderHeight();null!=e&&e>a&&(a=e)}}return Math.max(t,a)}getColumnHeaderRowHeight(){const e=this.isPivotMode()?this.getPivotHeaderHeight():this.getHeaderHeight(),t=this.visibleColsService.getAllCols().filter((e=>e.isAutoHeaderHeight())).map((e=>e.getAutoHeaderHeight()||0));return Math.max(e,...t)}getHeaderHeight(){return this.gos.get("headerHeight")??this.environment.getDefaultHeaderHeight()}getFloatingFiltersHeight(){return this.gos.get("floatingFiltersHeight")??this.getHeaderHeight()}getGroupHeaderHeight(){return this.gos.get("groupHeaderHeight")??this.getHeaderHeight()}getPivotHeaderHeight(){return this.gos.get("pivotHeaderHeight")??this.getHeaderHeight()}getPivotGroupHeaderHeight(){return this.gos.get("pivotGroupHeaderHeight")??this.getGroupHeaderHeight()}onFirstDataRendered(){const e=this.gos.get("autoSizeStrategy");if("fitCellContents"!==e?.type)return;const{colIds:t,skipHeader:a}=e;setTimeout((()=>{t?this.columnAutosizeService.autoSizeCols({colKeys:t,skipHeader:a,source:"autosizeColumns"}):this.columnAutosizeService.autoSizeAllColumns("autosizeColumns",a)}))}onAutoGroupColumnDefChanged(e){this.autoCols&&this.autoColService.updateAutoCols(this.autoCols.list,e)}onSelectionOptionsChanged(e,t,a){const r=(t&&"string"!=typeof t?le(t):void 0)!==(e&&"string"!=typeof e?le(e):void 0),n=t&&"string"!=typeof t?ge(t):void 0,o=e&&"string"!=typeof e?ge(e):void 0;(r||n!==o)&&this.refreshAll(a)}};function St(e){return"gridOptionsUpdated"===e?"gridOptionsChanged":e}function xt(e){e.map={},e.list.forEach((t=>e.map[t.getId()]=t))}function zt(e,t){const a=e===t,r=e.getColDef()===t,n=e.getColId()==t;return a||r||n}function Et(e,t){return Ee(e,t,((e,t)=>e.getColId()===t.getColId()))}var Rt=class extends Ke{constructor(){super(...arguments),this.beanName="columnAutosizeService",this.timesDelayed=0}wireBeans(e){this.columnModel=e.columnModel,this.visibleColsService=e.visibleColsService,this.animationFrameService=e.animationFrameService,this.autoWidthCalculator=e.autoWidthCalculator,this.eventDispatcher=e.columnEventDispatcher,this.ctrlsService=e.ctrlsService,this.renderStatusService=e.renderStatusService}autoSizeCols(e){if(this.columnModel.isShouldQueueResizeOperations())return void this.columnModel.pushResizeOperation((()=>this.autoSizeCols(e)));const{colKeys:t,skipHeader:a,skipHeaderGroups:r,stopAtGroup:n,source:o="api"}=e;if(this.animationFrameService.flushAllFrames(),this.timesDelayed<5&&this.renderStatusService&&!this.renderStatusService.areHeaderCellsRendered())return this.timesDelayed++,void setTimeout((()=>this.autoSizeCols(e)));this.timesDelayed=0;const i=[];let s=-1;const l=null!=a?a:this.gos.get("skipHeaderOnAutoSize"),g=null!=r?r:l;for(;0!==s;){s=0;const e=[];t.forEach((t=>{if(!t)return;const a=this.columnModel.getCol(t);if(!a)return;if(i.indexOf(a)>=0)return;const r=this.autoWidthCalculator.getPreferredWidthForColumn(a,l);if(r>0){const e=this.normaliseColumnWidth(a,r);a.setActualWidth(e,o),i.push(a),s++}e.push(a)})),e.length&&this.visibleColsService.refresh(o)}g||this.autoSizeColumnGroupsByColumns(t,o,n),this.eventDispatcher.columnResized(i,!0,"autosizeColumns")}autoSizeColumn(e,t,a){e&&this.autoSizeCols({colKeys:[e],skipHeader:a,skipHeaderGroups:!0,source:t})}autoSizeColumnGroupsByColumns(e,t,a){const r=new Set;let n;this.columnModel.getColsForKeys(e).forEach((e=>{let t=e.getParent();for(;t&&t!=a;)t.isPadding()||r.add(t),t=t.getParent()}));for(const e of r){for(const t of this.ctrlsService.getHeaderRowContainerCtrls())if(n=t.getHeaderCtrlForColumn(e),n)break;n&&n.resizeLeafColumnsToFit(t)}return[]}autoSizeAllColumns(e,t){if(this.columnModel.isShouldQueueResizeOperations())return void this.columnModel.pushResizeOperation((()=>this.autoSizeAllColumns(e,t)));const a=this.visibleColsService.getAllCols();this.autoSizeCols({colKeys:a,skipHeader:t,source:e})}normaliseColumnWidth(e,t){const a=e.getMinWidth();tthis.rowGroupCols.push(e),removeGroupCol:e=>De(this.rowGroupCols,e),addPivotCol:e=>this.pivotCols.push(e),removePivotCol:e=>De(this.pivotCols,e),addValueCol:e=>this.valueCols.push(e),removeValueCol:e=>De(this.valueCols,e)}}getSourceColumnsForGroupColumn(e){const t=e.getColDef().showRowGroup;if(!t)return null;if(!0===t)return this.rowGroupCols.slice(0);const a=this.columnModel.getColDefCol(t);return a?[a]:null}sortRowGroupColumns(e){this.rowGroupCols.sort(e)}sortPivotColumns(e){this.pivotCols.sort(e)}getValueColumns(){return this.valueCols?this.valueCols:[]}getPivotColumns(){return this.pivotCols?this.pivotCols:[]}getRowGroupColumns(){return this.rowGroupCols?this.rowGroupCols:[]}isRowGroupEmpty(){return M(this.rowGroupCols)}setColumnAggFunc(e,t,a){if(!e)return;const r=this.columnModel.getColDefCol(e);r&&(r.setAggFunc(t),this.eventDispatcher.columnChanged("columnValueChanged",[r],a))}setRowGroupColumns(e,t){this.setColList(e,this.rowGroupCols,"columnRowGroupChanged",!0,!0,((e,a)=>this.setRowGroupActive(e,a,t)),t)}setRowGroupActive(e,t,a){e!==t.isRowGroupActive()&&(t.setRowGroupActive(e,a),e&&!this.gos.get("suppressRowGroupHidesColumns")&&this.columnModel.setColsVisible([t],!1,a),e||this.gos.get("suppressMakeColumnVisibleAfterUnGroup")||this.columnModel.setColsVisible([t],!0,a))}addRowGroupColumns(e,t){this.updateColList(e,this.rowGroupCols,!0,!0,(e=>this.setRowGroupActive(!0,e,t)),"columnRowGroupChanged",t)}removeRowGroupColumns(e,t){this.updateColList(e,this.rowGroupCols,!1,!0,(e=>this.setRowGroupActive(!1,e,t)),"columnRowGroupChanged",t)}addPivotColumns(e,t){this.updateColList(e,this.pivotCols,!0,!1,(e=>e.setPivotActive(!0,t)),"columnPivotChanged",t)}setPivotColumns(e,t){this.setColList(e,this.pivotCols,"columnPivotChanged",!0,!1,((e,a)=>{a.setPivotActive(e,t)}),t)}removePivotColumns(e,t){this.updateColList(e,this.pivotCols,!1,!1,(e=>e.setPivotActive(!1,t)),"columnPivotChanged",t)}setValueColumns(e,t){this.setColList(e,this.valueCols,"columnValueChanged",!1,!1,((e,a)=>this.setValueActive(e,a,t)),t)}setValueActive(e,t,a){if(e!==t.isValueActive()&&(t.setValueActive(e,a),e&&!t.getAggFunc()&&this.aggFuncService)){const e=this.aggFuncService.getDefaultAggFunc(t);t.setAggFunc(e)}}addValueColumns(e,t){this.updateColList(e,this.valueCols,!0,!1,(e=>this.setValueActive(!0,e,t)),"columnValueChanged",t)}removeValueColumns(e,t){this.updateColList(e,this.valueCols,!1,!1,(e=>this.setValueActive(!1,e,t)),"columnValueChanged",t)}moveRowGroupColumn(e,t,a){if(this.isRowGroupEmpty())return;const r=this.rowGroupCols[e],n=this.rowGroupCols.slice(e,t);this.rowGroupCols.splice(e,1),this.rowGroupCols.splice(t,0,r),this.eventDispatcher.rowGroupChanged(n,a)}setColList(e,t,a,r,n,o,i){if(!this.columnModel.getCols())return;const s=new Map;t.forEach(((e,t)=>s.set(e,t))),t.length=0,E(e)&&e.forEach((e=>{const a=this.columnModel.getColDefCol(e);a&&t.push(a)})),t.forEach(((e,t)=>{const a=s.get(e);void 0!==a?r&&a!==t||s.delete(e):s.set(e,0)}));(this.columnModel.getColDefCols()||[]).forEach((e=>{const a=t.indexOf(e)>=0;o(a,e)})),n&&this.columnModel.refreshCols(!1),this.visibleColsService.refresh(i),this.eventDispatcher.columnChanged(a,[...s.keys()],i)}updateColList(e,t,a,r,n,o,i){if(!e||M(e))return;let s=!1;const l=new Set;e.forEach((e=>{if(!e)return;const r=this.columnModel.getColDefCol(e);if(r){if(l.add(r),a){if(t.indexOf(r)>=0)return;t.push(r)}else{const e=t.indexOf(r);if(e<0)return;for(let a=e+1;at.setValueActive(a,e)),(()=>{}),(()=>{}),(e=>{const t=e.aggFunc;return null===t||""===t?null:void 0!==t?!!t:void 0}),(e=>null!=e.initialAggFunc&&""!=e.initialAggFunc)),this.valueCols.forEach((e=>{const t=e.getColDef();null!=t.aggFunc&&""!=t.aggFunc?e.setAggFunc(t.aggFunc):e.getAggFunc()||e.setAggFunc(t.initialAggFunc)}))}extractRowGroupCols(e,t){this.rowGroupCols=this.extractColsCommon(t,this.rowGroupCols,((t,a)=>t.setRowGroupActive(a,e)),(e=>e.rowGroupIndex),(e=>e.initialRowGroupIndex),(e=>e.rowGroup),(e=>e.initialRowGroup))}extractPivotCols(e,t){this.pivotCols=this.extractColsCommon(t,this.pivotCols,((t,a)=>t.setPivotActive(a,e)),(e=>e.pivotIndex),(e=>e.initialPivotIndex),(e=>e.pivot),(e=>e.initialPivot))}extractColsCommon(e=[],t=[],a,r,n,o,i){const s=[],l=[];(this.columnModel.getColDefCols()||[]).forEach((a=>{const g=e.indexOf(a)<0,c=a.getColDef(),d=A(o(c)),h=A(i(c)),u=D(r(c)),p=D(n(c));let m;if(m=void 0!==d?d:void 0!==u?null!==u&&u>=0:g?void 0!==h?h:!!(void 0!==p)&&(null!=p&&p>=0):t.indexOf(a)>=0,m){(g?null!=u||null!=p:null!=u)?s.push(a):l.push(a)}}));const g=e=>{const t=r(e.getColDef()),a=n(e.getColDef());return null!=t?t:a};s.sort(((e,t)=>{const a=g(e),r=g(t);return a===r?0:a{l.indexOf(e)>=0&&c.push(e)})),l.forEach((e=>{c.indexOf(e)<0&&c.push(e)})),t.forEach((e=>{c.indexOf(e)<0&&a(e,!1)})),c.forEach((e=>{t.indexOf(e)<0&&a(e,!0)})),c}generateColumnStateForRowGroupAndPivotIndexes(e,t){const a={},r=(e,t,r,n,o,i)=>{const s=this.columnModel.getColDefCols();if(!t.length||!s)return[];const l=Object.keys(e),g=new Set(l),c=new Set(l),d=new Set(t.map((e=>{const t=e.getColId();return c.delete(t),t})).concat(l)),h=[],u={};let p=0;for(let e=0;e{const a=u[t];for(let t=f;t{const s=t.getColId();if(g.has(s))b(s),e[s][o]=m++;else{const l=t.getColDef();if(null===l[o]||void 0===l[o]&&null==l[i]){if(!v){l[r]||void 0===l[r]&&l[n]?b(s):(c.forEach((t=>{e[t][o]=m+u[t]})),m+=h.length,v=!0)}a[s]||(a[s]={colId:s}),a[s][o]=m++}}}))};return r(e,this.rowGroupCols,"rowGroup","initialRowGroup","rowGroupIndex","initialRowGroupIndex"),r(t,this.pivotCols,"pivot","initialPivot","pivotIndex","initialPivotIndex"),Object.values(a)}},Ft=class extends Ke{constructor(){super(...arguments),this.beanName="columnApplyStateService"}wireBeans(e){this.columnModel=e.columnModel,this.eventDispatcher=e.columnEventDispatcher,this.sortController=e.sortController,this.columnGetStateService=e.columnGetStateService,this.funcColsService=e.funcColsService,this.visibleColsService=e.visibleColsService,this.columnAnimationService=e.columnAnimationService,this.pivotResultColsService=e.pivotResultColsService}applyColumnState(e,t){const a=this.columnModel.getColDefCols()||[];if(M(a))return!1;if(e&&e.state&&!e.state.forEach)return p("applyColumnState() - the state attribute should be an array, however an array was not found. Please provide an array of items (one for each col you want to change) for state."),!1;const r=this.funcColsService.getModifyColumnsNoEventsCallbacks(),n=(a,n,o)=>{const i=this.compareColumnStatesAndDispatchEvents(t),s=n.slice(),l={},g={},c=[],d=[];let h=0;const u=this.funcColsService.getRowGroupColumns().slice(),p=this.funcColsService.getPivotColumns().slice();a.forEach((a=>{const n=a.colId||"";if(n.startsWith(Je))return c.push(a),void d.push(a);const i=o(n);i?(this.syncColumnWithStateItem(i,a,e.defaultState,l,g,!1,t,r),De(s,i)):(d.push(a),h+=1)}));const m=a=>this.syncColumnWithStateItem(a,null,e.defaultState,l,g,!1,t,r);s.forEach(m),this.funcColsService.sortRowGroupColumns(Dt.bind(this,l,u)),this.funcColsService.sortPivotColumns(Dt.bind(this,g,p)),this.columnModel.refreshCols(!1);const v=(this.columnModel.getAutoCols()||[]).slice();return c.forEach((a=>{const n=this.columnModel.getAutoCol(a.colId);De(v,n),this.syncColumnWithStateItem(n,a,e.defaultState,null,null,!0,t,r)})),v.forEach(m),this.orderLiveColsLikeState(e),this.visibleColsService.refresh(t),this.eventDispatcher.everythingChanged(t),i(),{unmatchedAndAutoStates:d,unmatchedCount:h}};this.columnAnimationService.start();let{unmatchedAndAutoStates:o,unmatchedCount:i}=n(e.state||[],a,(e=>this.columnModel.getColDefCol(e)));if(o.length>0||E(e.defaultState)){const e=this.pivotResultColsService.getPivotResultCols(),t=e?.list;i=n(o,t||[],(e=>this.pivotResultColsService.getPivotResultCol(e))).unmatchedCount}return this.columnAnimationService.finish(),0===i}resetColumnState(e){if(M(this.columnModel.getColDefCols()))return;const t=Xe(this.columnModel.getColDefColTree()),a=[];let r=1e3,n=1e3,o=[];const i=this.columnModel.getAutoCols();i&&(o=o.concat(i)),t&&(o=o.concat(t)),o.forEach((e=>{const t=this.getColumnStateFromColDef(e);R(t.rowGroupIndex)&&t.rowGroup&&(t.rowGroupIndex=r++),R(t.pivotIndex)&&t.pivot&&(t.pivotIndex=n++),a.push(t)})),this.applyColumnState({state:a,applyOrder:!0},e)}getColumnStateFromColDef(e){const t=(e,t)=>null!=e?e:null!=t?t:null,a=e.getColDef(),r=t(a.sort,a.initialSort),n=t(a.sortIndex,a.initialSortIndex),o=t(a.hide,a.initialHide),i=t(a.pinned,a.initialPinned),s=t(a.width,a.initialWidth),l=t(a.flex,a.initialFlex);let g=t(a.rowGroupIndex,a.initialRowGroupIndex),c=t(a.rowGroup,a.initialRowGroup);null!=g||null!=c&&0!=c||(g=null,c=null);let d=t(a.pivotIndex,a.initialPivotIndex),h=t(a.pivot,a.initialPivot);null!=d||null!=h&&0!=h||(d=null,h=null);const u=t(a.aggFunc,a.initialAggFunc);return{colId:e.getColId(),sort:r,sortIndex:n,hide:o,pinned:i,width:s,flex:l,rowGroup:c,rowGroupIndex:g,pivot:h,pivotIndex:d,aggFunc:u}}syncColumnWithStateItem(e,t,a,r,n,o,i,s){if(!e)return;const l=(e,r)=>{const n={value1:void 0,value2:void 0};let o=!1;return t&&(void 0!==t[e]&&(n.value1=t[e],o=!0),E(r)&&void 0!==t[r]&&(n.value2=t[r],o=!0)),!o&&a&&(void 0!==a[e]&&(n.value1=a[e]),E(r)&&void 0!==a[r]&&(n.value2=a[r])),n},g=l("hide").value1;void 0!==g&&e.setVisible(!g,i);const c=l("pinned").value1;void 0!==c&&e.setPinned(c);const d=e.getColDef().minWidth??this.gos.environment.getDefaultColumnMinWidth(),h=l("flex").value1;if(void 0!==h&&e.setFlex(h),null==h){const t=l("width").value1;null!=t&&null!=d&&t>=d&&e.setActualWidth(t,i)}const u=l("sort").value1;void 0!==u&&("desc"===u||"asc"===u?e.setSort(u,i):e.setSort(void 0,i));const m=l("sortIndex").value1;if(void 0!==m&&e.setSortIndex(m),o||!e.isPrimary())return;const v=l("aggFunc").value1;void 0!==v&&("string"==typeof v?(e.setAggFunc(v),e.isValueActive()||(e.setValueActive(!0,i),s.addValueCol(e))):(E(v)&&p("stateItem.aggFunc must be a string. if using your own aggregation functions, register the functions first before using them in get/set state. This is because it is intended for the column state to be stored and retrieved as simple JSON."),e.isValueActive()&&(e.setValueActive(!1,i),s.removeValueCol(e))));const{value1:f,value2:b}=l("rowGroup","rowGroupIndex");void 0===f&&void 0===b||("number"==typeof b||f?(e.isRowGroupActive()||(e.setRowGroupActive(!0,i),s.addGroupCol(e)),r&&"number"==typeof b&&(r[e.getId()]=b)):e.isRowGroupActive()&&(e.setRowGroupActive(!1,i),s.removeGroupCol(e)));const{value1:w,value2:C}=l("pivot","pivotIndex");void 0===w&&void 0===C||("number"==typeof C||w?(e.isPivotActive()||(e.setPivotActive(!0,i),s.addPivotCol(e)),n&&"number"==typeof C&&(n[e.getId()]=C)):e.isPivotActive()&&(e.setPivotActive(!1,i),s.removePivotCol(e)))}orderLiveColsLikeState(e){if(!e.applyOrder||!e.state)return;const t=[];e.state.forEach((e=>{null!=e.colId&&t.push(e.colId)})),this.columnModel.sortColsLikeKeys(t)}compareColumnStatesAndDispatchEvents(e){const t={rowGroupColumns:this.funcColsService.getRowGroupColumns().slice(),pivotColumns:this.funcColsService.getPivotColumns().slice(),valueColumns:this.funcColsService.getValueColumns().slice()},a=this.columnGetStateService.getColumnState(),r={};return a.forEach((e=>{r[e.colId]=e})),()=>{const n=this.columnModel.getAllCols(),o=(t,a,r,n)=>{if(Ee(a.map(n),r.map(n)))return;const o=new Set(a);r.forEach((e=>{o.delete(e)||o.add(e)}));const i=[...o];this.eventService.dispatchEvent({type:t,columns:i,column:1===i.length?i[0]:null,source:e})},i=e=>{const t=[];return n.forEach((a=>{const n=r[a.getColId()];n&&e(n,a)&&t.push(a)})),t},s=e=>e.getColId();o("columnRowGroupChanged",t.rowGroupColumns,this.funcColsService.getRowGroupColumns(),s),o("columnPivotChanged",t.pivotColumns,this.funcColsService.getPivotColumns(),s);const l=i(((e,t)=>{const a=null!=e.aggFunc,r=a!=t.isValueActive(),n=a&&e.aggFunc!=t.getAggFunc();return r||n}));l.length>0&&this.eventDispatcher.columnChanged("columnValueChanged",l,e);this.eventDispatcher.columnResized(i(((e,t)=>e.width!=t.getActualWidth())),!0,e);this.eventDispatcher.columnPinned(i(((e,t)=>e.pinned!=t.getPinned())),e);this.eventDispatcher.columnVisible(i(((e,t)=>e.hide==t.isVisible())),e);const g=i(((e,t)=>e.sort!=t.getSort()||e.sortIndex!=t.getSortIndex()));g.length>0&&this.sortController.dispatchSortChangedEvents(e,g),this.normaliseColumnMovedEventForColumnState(a,e)}}normaliseColumnMovedEventForColumnState(e,t){const a=this.columnGetStateService.getColumnState(),r={};a.forEach((e=>r[e.colId]=e));const n={};e.forEach((e=>{r[e.colId]&&(n[e.colId]=!0)}));const o=e.filter((e=>n[e.colId])),i=a.filter((e=>n[e.colId])),s=[];i.forEach(((e,t)=>{const a=o&&o[t];if(a&&a.colId!==e.colId){const e=this.columnModel.getCol(a.colId);e&&s.push(e)}})),s.length&&this.eventDispatcher.columnMoved({movedColumns:s,source:t,finished:!0})}},Dt=(e,t,a,r)=>{const n=e[a.getId()],o=e[r.getId()],i=null!=n,s=null!=o;if(i&&s)return n-o;if(i)return-1;if(s)return 1;const l=t.indexOf(a),g=t.indexOf(r),c=l>=0;return c&&g>=0?l-g:c?-1:1},At=class extends Ke{constructor(){super(...arguments),this.beanName="columnMoveService"}wireBeans(e){this.columnModel=e.columnModel,this.columnAnimationService=e.columnAnimationService,this.eventDispatcher=e.columnEventDispatcher}moveColumnByIndex(e,t,a){const r=this.columnModel.getCols();if(!r)return;const n=r[e];this.moveColumns([n],t,a)}moveColumns(e,t,a,r=!0){const n=this.columnModel.getCols();if(!n)return;if(t>n.length-e.length)return p("tried to insert columns in invalid location, toIndex = ",t),void p("remember that you should not count the moving columns when calculating the new index");this.columnAnimationService.start();const o=this.columnModel.getColsForKeys(e);this.doesMovePassRules(o,t)&&(this.columnModel.moveInCols(o,t,a),this.eventDispatcher.columnMoved({movedColumns:o,source:a,toIndex:t,finished:r})),this.columnAnimationService.finish()}doesMovePassRules(e,t){const a=this.getProposedColumnOrder(e,t);return this.doesOrderPassRules(a)}doesOrderPassRules(e){return!!this.doesMovePassMarryChildren(e)&&!!this.doesMovePassLockedPositions(e)}getProposedColumnOrder(e,t){const a=this.columnModel.getCols().slice();return Te(a,e,t),a}doesMovePassLockedPositions(e){const t=this.gos.get("enableRtl");let a=t?1:-1,r=!0;return e.forEach((e=>{const n=(o=e.getColDef().lockPosition)?"left"===o||!0===o?-1:1:0;var o;t?n>a&&(r=!1):n{if(!Ye(a))return;const r=a,n=r.getColGroupDef();if(!(n&&n.marryChildren))return;const o=[];r.getLeafColumns().forEach((t=>{const a=e.indexOf(t);o.push(a)}));Math.max.apply(Math,o)-Math.min.apply(Math,o)>r.getLeafColumns().length-1&&(t=!1)})),t}placeLockedColumns(e){const t=[],a=[],r=[];e.forEach((e=>{const n=e.getColDef().lockPosition;"right"===n?r.push(e):"left"===n||!0===n?t.push(e):a.push(e)}));return this.gos.get("enableRtl")?[...r,...a,...t]:[...t,...a,...r]}},Pt=/[&<>"']/g,Tt={"&":"&","<":"<",">":">",'"':""","'":"'"};function It(e){const t=String.fromCharCode;function a(e,a){return t(e>>a&63|128)}function r(e){if(!(4294967168&e))return t(e);let r="";return 4294965248&e?4294901760&e?4292870144&e||(r=t(e>>18&7|240),r+=a(e,12),r+=a(e,6)):(!function(e){if(e>=55296&&e<=57343)throw Error("Lone surrogate U+"+e.toString(16).toUpperCase()+" is not a scalar value")}(e),r=t(e>>12&15|224),r+=a(e,6)):r=t(e>>6&31|192),r+=t(63&e|128),r}const n=function(e){const t=[];if(!e)return[];const a=e.length;let r,n,o=0;for(;o=55296&&r<=56319&&oTt[e]))}var Nt=class extends Ke{constructor(){super(...arguments),this.beanName="columnNameService"}wireBeans(e){this.expressionService=e.expressionService,this.funcColsService=e.funcColsService,this.columnModel=e.columnModel}getDisplayNameForColumn(e,t,a=!1){if(!e)return null;const r=this.getHeaderName(e.getColDef(),e,null,null,t);return a?this.wrapHeaderNameWithAggFunc(e,r):r}getDisplayNameForProvidedColumnGroup(e,t,a){const r=t?t.getColGroupDef():null;return r?this.getHeaderName(r,null,e,t,a):null}getDisplayNameForColumnGroup(e,t){return this.getDisplayNameForProvidedColumnGroup(e,e.getProvidedColumnGroup(),t)}getHeaderName(e,t,a,r,n){const o=e.headerValueGetter;if(o){const i=this.gos.addGridCommonParams({colDef:e,column:t,columnGroup:a,providedColumnGroup:r,location:n});return"function"==typeof o?o(i):"string"==typeof o?this.expressionService.evaluate(o,i):(p("headerValueGetter must be a function or a string"),"")}return null!=e.headerName?e.headerName:e.field?(i=e.field)&&null!=i?i.replace(/([a-z])([A-Z])/g,"$1 $2").replace(/([A-Z]+)([A-Z])([a-z])/g,"$1 $2$3").replace(/\./g," ").split(" ").map((e=>e.substring(0,1).toUpperCase()+(e.length>1?e.substring(1,e.length):""))).join(" "):null:"";var i}wrapHeaderNameWithAggFunc(e,t){if(this.gos.get("suppressAggFuncInHeader"))return t;const a=e.getColDef().pivotValueColumn;let r,n=null;if(E(a)){const o=this.funcColsService.getValueColumns(),i=this.gos.get("removePivotHeaderRowWhenSingleValueColumn")&&1===o.length,s=void 0!==e.getColDef().pivotTotalColumnIds;if(i&&!s)return t;n=a?a.getAggFunc():null,r=!0}else{const t=e.isValueActive(),a=this.columnModel.isPivotMode()||!this.funcColsService.isRowGroupEmpty();t&&a?(n=e.getAggFunc(),r=!0):r=!1}if(r){const e="string"==typeof n?n:"func";return`${this.localeService.getLocaleTextFunc()(e,e)}(${t})`}return t}},Gt=class extends Ke{constructor(){super(...arguments),this.beanName="pivotResultColsService"}wireBeans(e){this.context=e.context,this.columnModel=e.columnModel,this.columnFactory=e.columnFactory,this.visibleColsService=e.visibleColsService}destroy(){et(this.context,this.pivotResultCols?.tree),super.destroy()}isPivotResultColsPresent(){return null!=this.pivotResultCols}lookupPivotResultCol(e,t){if(null==this.pivotResultCols)return null;const a=this.columnModel.getColDefCol(t);let r=null;return this.pivotResultCols.list.forEach((t=>{const n=t.getColDef().pivotKeys,o=t.getColDef().pivotValueColumn;Ee(n,e)&&o===a&&(r=t)})),r}getPivotResultCols(){return this.pivotResultCols}getPivotResultCol(e){return this.pivotResultCols?this.columnModel.getColFromCollection(e,this.pivotResultCols):null}setPivotResultCols(e,t){if(this.columnModel.isReady()&&(null!=e||null!=this.pivotResultCols)){if(e){this.processPivotResultColDef(e);const a=this.columnFactory.createColumnTree(e,!1,this.pivotResultCols?.tree||this.previousPivotResultCols||void 0,t);et(this.context,this.pivotResultCols?.tree,a.columnTree);const r=a.columnTree,n=a.treeDept,o=Xe(r),i={};this.pivotResultCols={tree:r,treeDepth:n,list:o,map:i},this.pivotResultCols.list.forEach((e=>this.pivotResultCols.map[e.getId()]=e));const s=!!this.previousPivotResultCols;this.previousPivotResultCols=null,this.columnModel.refreshCols(!s)}else this.previousPivotResultCols=this.pivotResultCols?this.pivotResultCols.tree:null,this.pivotResultCols=null,this.columnModel.refreshCols(!1);this.visibleColsService.refresh(t)}}processPivotResultColDef(e){const t=this.gos.get("processPivotResultColDef"),a=this.gos.get("processPivotResultColGroupDef");if(!t&&!a)return;const r=e=>{e.forEach((e=>{if(E(e.children)){const t=e;a&&a(t),r(t.children)}else{t&&t(e)}}))};e&&r(e)}},Bt=class extends Ke{constructor(){super(...arguments),this.beanName="columnSizeService"}wireBeans(e){this.columnModel=e.columnModel,this.columnViewportService=e.columnViewportService,this.eventDispatcher=e.columnEventDispatcher,this.visibleColsService=e.visibleColsService,this.ctrlsService=e.ctrlsService}setColumnWidths(e,t,a,r){const n=[];e.forEach((e=>{const a=this.columnModel.getColDefCol(e.key)||this.columnModel.getCol(e.key);if(!a)return;n.push({width:e.newWidth,ratios:[1],columns:[a]});if("shift"===this.gos.get("colResizeDefault")&&(t=!t),t){const t=this.visibleColsService.getColAfter(a);if(!t)return;const r=a.getActualWidth()-e.newWidth,o=t.getActualWidth()+r;n.push({width:o,ratios:[1],columns:[t]})}})),0!==n.length&&this.resizeColumnSets({resizeSets:n,finished:a,source:r})}resizeColumnSets(e){const{resizeSets:t,finished:a,source:r}=e;if(!(!t||t.every((e=>this.checkMinAndMaxWidthsForSet(e))))){if(a){const e=t&&t.length>0?t[0].columns:null;this.eventDispatcher.columnResized(e,a,r)}return}const n=[],o=[];t.forEach((e=>{const{width:t,columns:a,ratios:i}=e,s={},l={};a.forEach((e=>o.push(e)));let g=!0,c=0;for(;g;){if(c++,c>1e3){m("infinite loop in resizeColumnSets");break}g=!1;const e=[];let r=0,n=t;a.forEach(((t,a)=>{if(l[t.getId()])n-=s[t.getId()];else{e.push(t);const n=i[a];r+=n}}));const o=1/r;e.forEach(((a,r)=>{let c;r===e.length-1?c=n:(c=Math.round(i[r]*t*o),n-=c);const d=a.getMinWidth(),h=a.getMaxWidth();c0&&c>h&&(c=h,l[a.getId()]=!0,g=!0),s[a.getId()]=c}))}a.forEach((e=>{const t=s[e.getId()];e.getActualWidth()!==t&&(e.setActualWidth(t,r),n.push(e))}))}));const i=n.length>0;let s=[];i&&(s=this.refreshFlexedColumns({resizingCols:o,skipSetLeft:!0}),this.visibleColsService.setLeftValues(r),this.visibleColsService.updateBodyWidths(),this.columnViewportService.checkViewportColumns());const l=o.concat(s);(i||a)&&this.eventDispatcher.columnResized(l,a,r,s)}checkMinAndMaxWidthsForSet(e){const{columns:t,width:a}=e;let r=0,n=0,o=!0;t.forEach((e=>{const t=e.getMinWidth();r+=t||0;const a=e.getMaxWidth();a>0?n+=a:o=!1}));return a>=r&&(!o||a<=n)}refreshFlexedColumns(e={}){const t=e.source?e.source:"flex";if(null!=e.viewportWidth&&(this.flexViewportWidth=e.viewportWidth),!this.flexViewportWidth)return[];const a=this.visibleColsService.getCenterCols();let r=-1;if(e.resizingCols){const t=new Set(e.resizingCols);for(let e=a.length-1;e>=0;e--)if(t.has(a[e])){r=e;break}}let n=0,o=[],i=0,s=0;for(let e=0;er?(o.push(a[e]),s+=a[e].getFlex(),i+=a[e].getMinWidth()):n+=a[e].getActualWidth()}if(!o.length)return[];let l=[];n+i>this.flexViewportWidth&&(o.forEach((e=>e.setActualWidth(e.getMinWidth(),t))),l=o,o=[]);const g=[];let c;e:for(;;){c=this.flexViewportWidth-n;const e=c/s;for(let a=0;ah&&(c=h),c){r.setActualWidth(c,t),Fe(o,r),s-=r.getFlex(),l.push(r),n+=r.getActualWidth();continue e}g[a]=Math.floor(i)}break}let d=c;return o.forEach(((e,a)=>{const r=athis.sizeColumnsToFit(e,t,a,r)));const n={};r&&r?.columnLimits?.forEach((({key:e,...t})=>{n["string"==typeof e?e:e.getColId()]=t}));const o=this.visibleColsService.getAllCols(),i=e===$e(o);if(e<=0||!o.length||i)return;const s=[],l=[];o.forEach((e=>{!0===e.getColDef().suppressSizeToFit?l.push(e):s.push(e)}));const g=s.slice(0);let c=!1;const d=e=>{De(s,e),l.push(e)};for(s.forEach((e=>{e.resetActualWidth(t);const a=n?.[e.getId()],o=a?.minWidth??r?.defaultMinWidth,i=a?.maxWidth??r?.defaultMaxWidth,s=e.getActualWidth();"number"==typeof o&&si&&e.setActualWidth(i,t,!0)}));!c;){c=!0;const a=e-$e(l);if(a<=0)s.forEach((e=>{const a=n?.[e.getId()]?.minWidth??r?.defaultMinWidth;"number"!=typeof a?e.setMinimum(t):e.setActualWidth(a,t,!0)}));else{const e=a/$e(s);let o=a;for(let a=s.length-1;a>=0;a--){const i=s[a],l=n?.[i.getId()],g=l?.minWidth??r?.defaultMinWidth,h=l?.maxWidth??r?.defaultMaxWidth,u=i.getMinWidth(),p=i.getMaxWidth(),m="number"==typeof g&&g>u?g:u,v="number"==typeof h&&hv?(f=v,d(i),c=!1):0===a&&(f=o),i.setActualWidth(f,t,!0),o-=f}}}g.forEach((e=>{e.fireColumnWidthChangedEvent(t)})),this.visibleColsService.setLeftValues(t),this.visibleColsService.updateBodyWidths(),a||this.eventDispatcher.columnResized(g,!0,t)}applyAutosizeStrategy(){const e=this.gos.get("autoSizeStrategy");if(!e)return;const{type:t}=e;setTimeout((()=>{if("fitGridWidth"===t){const{columnLimits:t,defaultMinWidth:a,defaultMaxWidth:r}=e,n=t?.map((({colId:e,minWidth:t,maxWidth:a})=>({key:e,minWidth:t,maxWidth:a})));this.ctrlsService.getGridBodyCtrl().sizeColumnsToFit({defaultMinWidth:a,defaultMaxWidth:r,columnLimits:n})}else"fitProvidedWidth"===t&&this.sizeColumnsToFit(e.width,"sizeColumnsToFit")}))}};function Ht(e,t){return e+"_"+t}function qt(e){return e instanceof Vt}var Vt=class extends Ke{constructor(e,t,a,r){super(),this.isColumn=!1,this.displayedChildren=[],this.autoHeaderHeight=null,this.parent=null,this.groupId=t,this.partId=a,this.providedColumnGroup=e,this.pinned=r}reset(){this.parent=null,this.children=null,this.displayedChildren=null}getParent(){return this.parent}setParent(e){this.parent=e}getUniqueId(){return Ht(this.groupId,this.partId)}isEmptyGroup(){return 0===this.displayedChildren.length}isMoving(){const e=this.getProvidedColumnGroup().getLeafColumns();return!(!e||0===e.length)&&e.every((e=>e.isMoving()))}checkLeft(){if(this.displayedChildren.forEach((e=>{qt(e)&&e.checkLeft()})),this.displayedChildren.length>0)if(this.gos.get("enableRtl")){const e=ze(this.displayedChildren).getLeft();this.setLeft(e)}else{const e=this.displayedChildren[0].getLeft();this.setLeft(e)}else this.setLeft(null)}getLeft(){return this.left}getOldLeft(){return this.oldLeft}setLeft(e){this.oldLeft=this.left,this.left!==e&&(this.left=e,this.dispatchLocalEvent({type:"leftChanged"}))}getPinned(){return this.pinned}getGroupId(){return this.groupId}getPartId(){return this.partId}getActualWidth(){let e=0;return this.displayedChildren&&this.displayedChildren.forEach((t=>{e+=t.getActualWidth()})),e}isResizable(){if(!this.displayedChildren)return!1;let e=!1;return this.displayedChildren.forEach((t=>{t.isResizable()&&(e=!0)})),e}getMinWidth(){let e=0;return this.displayedChildren.forEach((t=>{e+=t.getMinWidth()})),e}addChild(e){this.children||(this.children=[]),this.children.push(e)}getDisplayedChildren(){return this.displayedChildren}getLeafColumns(){const e=[];return this.addLeafColumns(e),e}getDisplayedLeafColumns(){const e=[];return this.addDisplayedLeafColumns(e),e}getDefinition(){return this.providedColumnGroup.getColGroupDef()}getColGroupDef(){return this.providedColumnGroup.getColGroupDef()}isPadding(){return this.providedColumnGroup.isPadding()}isExpandable(){return this.providedColumnGroup.isExpandable()}isExpanded(){return this.providedColumnGroup.isExpanded()}setExpanded(e){this.providedColumnGroup.setExpanded(e)}isAutoHeaderHeight(){return!!this.getColGroupDef()?.autoHeaderHeight}getAutoHeaderHeight(){return this.autoHeaderHeight}setAutoHeaderHeight(e){const t=e!==this.autoHeaderHeight;return this.autoHeaderHeight=e,t}addDisplayedLeafColumns(e){this.displayedChildren.forEach((t=>{vt(t)?e.push(t):qt(t)&&t.addDisplayedLeafColumns(e)}))}addLeafColumns(e){this.children.forEach((t=>{vt(t)?e.push(t):qt(t)&&t.addLeafColumns(e)}))}getChildren(){return this.children}getColumnGroupShow(){return this.providedColumnGroup.getColumnGroupShow()}getProvidedColumnGroup(){return this.providedColumnGroup}getPaddingLevel(){const e=this.getParent();return this.isPadding()&&e&&e.isPadding()?1+e.getPaddingLevel():0}calculateDisplayedColumns(){this.displayedChildren=[];let e=this;for(;null!=e&&e.isPadding();)e=e.getParent();if(!(!!e&&e.getProvidedColumnGroup().isExpandable()))return this.displayedChildren=this.children,void this.dispatchLocalEvent({type:"displayedChildrenChanged"});this.children.forEach((t=>{if(qt(t)&&(!t.displayedChildren||!t.displayedChildren.length))return;switch(t.getColumnGroupShow()){case"open":e.getProvidedColumnGroup().isExpanded()&&this.displayedChildren.push(t);break;case"closed":e.getProvidedColumnGroup().isExpanded()||this.displayedChildren.push(t);break;default:this.displayedChildren.push(t)}})),this.dispatchLocalEvent({type:"displayedChildrenChanged"})}},Wt=class{constructor(){this.existingIds={}}getInstanceIdForKey(e){const t=this.existingIds[e];let a;return a="number"!=typeof t?0:t+1,this.existingIds[e]=a,a}},jt=class extends Ke{constructor(){super(...arguments),this.beanName="visibleColsService",this.colsAndGroupsMap={},this.columnsLeft=[],this.columnsRight=[],this.columnsCenter=[],this.columns=[],this.bodyWidth=0,this.leftWidth=0,this.rightWidth=0,this.bodyWidthDirty=!0}wireBeans(e){this.columnModel=e.columnModel,this.columnSizeService=e.columnSizeService,this.columnViewportService=e.columnViewportService,this.eventDispatcher=e.columnEventDispatcher}refresh(e,t=!1){t||this.buildTrees(),this.updateOpenClosedVisibilityInColumnGroups(),this.columnsLeft=Kt(this.treeLeft),this.columnsCenter=Kt(this.treeCenter),this.columnsRight=Kt(this.treeRight),this.joinColsAriaOrder(),this.joinCols(),this.setLeftValues(e),this.autoHeightCols=this.columns.filter((e=>e.isAutoHeight())),this.columnSizeService.refreshFlexedColumns(),this.updateBodyWidths(),this.columnViewportService.checkViewportColumns(!1),this.setFirstRightAndLastLeftPinned(e),this.eventDispatcher.visibleCols(e)}updateBodyWidths(){const e=$e(this.columnsCenter),t=$e(this.columnsLeft),a=$e(this.columnsRight);this.bodyWidthDirty=this.bodyWidth!==e;(this.bodyWidth!==e||this.leftWidth!==t||this.rightWidth!==a)&&(this.bodyWidth=e,this.leftWidth=t,this.rightWidth=a,this.eventService.dispatchEvent({type:"columnContainerWidthChanged"}),this.eventService.dispatchEvent({type:"displayedColumnsWidthChanged"}))}setLeftValues(e){this.setLeftValuesOfCols(e),this.setLeftValuesOfGroups()}setFirstRightAndLastLeftPinned(e){let t,a;this.gos.get("enableRtl")?(t=this.columnsLeft?this.columnsLeft[0]:null,a=this.columnsRight?ze(this.columnsRight):null):(t=this.columnsLeft?ze(this.columnsLeft):null,a=this.columnsRight?this.columnsRight[0]:null),this.columnModel.getCols().forEach((r=>{r.setLastLeftPinned(r===t,e),r.setFirstRightPinned(r===a,e)}))}buildTrees(){const e=this.columnModel.getColsToShow(),t=e.filter((e=>"left"==e.getPinned())),a=e.filter((e=>"right"==e.getPinned())),r=e.filter((e=>"left"!=e.getPinned()&&"right"!=e.getPinned())),n=new Wt;this.treeLeft=this.createGroups({columns:t,idCreator:n,pinned:"left",oldDisplayedGroups:this.treeLeft}),this.treeRight=this.createGroups({columns:a,idCreator:n,pinned:"right",oldDisplayedGroups:this.treeRight}),this.treeCenter=this.createGroups({columns:r,idCreator:n,pinned:null,oldDisplayedGroups:this.treeCenter}),this.updateColsAndGroupsMap()}clear(){this.columnsLeft=[],this.columnsRight=[],this.columnsCenter=[],this.columns=[],this.ariaOrderColumns=[]}joinColsAriaOrder(){const e=this.columnModel.getCols(),t=[],a=[],r=[];for(const n of e){const e=n.getPinned();e?!0===e||"left"===e?t.push(n):r.push(n):a.push(n)}this.ariaOrderColumns=t.concat(a).concat(r)}getAriaColIndex(e){let t;return t=qt(e)?e.getLeafColumns()[0]:e,this.ariaOrderColumns.indexOf(t)+1}getAllAutoHeightCols(){return this.autoHeightCols}setLeftValuesOfGroups(){[this.treeLeft,this.treeRight,this.treeCenter].forEach((e=>{e.forEach((e=>{if(qt(e)){e.checkLeft()}}))}))}setLeftValuesOfCols(e){if(!this.columnModel.getColDefCols())return;const t=this.columnModel.getCols().slice(0),a=this.gos.get("enableRtl");[this.columnsLeft,this.columnsRight,this.columnsCenter].forEach((r=>{if(a){let t=$e(r);r.forEach((a=>{t-=a.getActualWidth(),a.setLeft(t,e)}))}else{let t=0;r.forEach((a=>{a.setLeft(t,e),t+=a.getActualWidth()}))}!function(e,t){for(let a=0;a{t.setLeft(null,e)}))}joinCols(){this.gos.get("enableRtl")?this.columns=this.columnsRight.concat(this.columnsCenter).concat(this.columnsLeft):this.columns=this.columnsLeft.concat(this.columnsCenter).concat(this.columnsRight)}getColsCenter(){return this.columnsCenter}getAllTrees(){return this.treeLeft&&this.treeRight&&this.treeCenter?this.treeLeft.concat(this.treeCenter).concat(this.treeRight):null}getTreeLeft(){return this.treeLeft}getTreeRight(){return this.treeRight}getTreeCenter(){return this.treeCenter}getAllCols(){return this.columns}isColDisplayed(e){return this.getAllCols().indexOf(e)>=0}getLeftColsForRow(e){return this.columnModel.isColSpanActive()?this.getColsForRow(e,this.columnsLeft):this.columnsLeft}getRightColsForRow(e){return this.columnModel.isColSpanActive()?this.getColsForRow(e,this.columnsRight):this.columnsRight}getColsForRow(e,t,a,r){const n=[];let o=null;for(let i=0;i1){const e=g-1;for(let a=1;a<=e;a++)c.push(t[i+a]);i+=e}let d;if(a?(d=!1,c.forEach((e=>{a(e)&&(d=!0)}))):d=!0,d){if(0===n.length&&o){!!r&&r(s)&&n.push(o)}n.push(s)}o=s}return n}getBodyContainerWidth(){return this.bodyWidth}getContainerWidth(e){switch(e){case"left":return this.leftWidth;case"right":return this.rightWidth;default:return this.bodyWidth}}getCenterCols(){return this.columnsCenter}getLeftCols(){return this.columnsLeft}getRightCols(){return this.columnsRight}getColBefore(e){const t=this.getAllCols(),a=t.indexOf(e);return a>0?t[a-1]:null}getGroupAtDirection(e,t){const a=e.getProvidedColumnGroup().getLevel()+e.getPaddingLevel(),r=e.getDisplayedLeafColumns(),n="After"===t?ze(r):r[0],o=`getCol${t}`;for(;;){const t=this[o](n);if(!t)return null;const r=this.getColGroupAtLevel(t,a);if(r!==e)return r}}getColGroupAtLevel(e,t){let a,r,n=e.getParent();for(;;){if(a=n.getProvidedColumnGroup().getLevel(),r=n.getPaddingLevel(),a+r<=t)break;n=n.getParent()}return n}isPinningLeft(){return this.columnsLeft.length>0}isPinningRight(){return this.columnsRight.length>0}updateColsAndGroupsMap(){this.colsAndGroupsMap={};const e=e=>{this.colsAndGroupsMap[e.getUniqueId()]=e};Ut(this.treeCenter,!1,e),Ut(this.treeLeft,!1,e),Ut(this.treeRight,!1,e)}isVisible(e){return this.colsAndGroupsMap[e.getUniqueId()]===e}updateOpenClosedVisibilityInColumnGroups(){Ut(this.getAllTrees(),!1,(e=>{qt(e)&&e.calculateDisplayedColumns()}))}getFirstColumn(){const e=this.gos.get("enableRtl"),t=["getLeftCols","getCenterCols","getRightCols"];e&&t.reverse();for(let a=0;a{if(qt(a)){const o=a;let i;i=r?e===o.getGroupId()&&t===o.getPartId():e===o.getGroupId(),i&&(n=o)}})),n}getColAfter(e){const t=this.getAllCols(),a=t.indexOf(e);return a{const g=t;t=n;const c=e[g],d=(qt(c)?c.getProvidedColumnGroup():c).getOriginalParent();if(null==d){for(let t=g;t{e.forEach((e=>{if(qt(e)){const r=e;t[e.getUniqueId()]=r,a(r.getChildren())}}))};return e&&a(e),t}setupParentsIntoCols(e,t){e.forEach((e=>{if(e.setParent(t),qt(e)){const t=e;this.setupParentsIntoCols(t.getChildren(),t)}}))}};function Ut(e,t,a){if(e)for(let r=0;r{vt(e)&&t.push(e)})),t}var Yt=["columnEverythingChanged","newColumnsLoaded","columnPivotModeChanged","pivotMaxColumnsExceeded","columnRowGroupChanged","expandOrCollapseAll","columnPivotChanged","gridColumnsChanged","columnValueChanged","columnMoved","columnVisible","columnPinned","columnGroupOpened","columnResized","displayedColumnsChanged","virtualColumnsChanged","columnHeaderMouseOver","columnHeaderMouseLeave","columnHeaderClicked","columnHeaderContextMenu","asyncTransactionsFlushed","rowGroupOpened","rowDataUpdated","pinnedRowDataChanged","rangeSelectionChanged","cellSelectionChanged","chartCreated","chartRangeSelectionChanged","chartOptionsChanged","chartDestroyed","toolPanelVisibleChanged","toolPanelSizeChanged","modelUpdated","cutStart","cutEnd","pasteStart","pasteEnd","fillStart","fillEnd","cellSelectionDeleteStart","cellSelectionDeleteEnd","rangeDeleteStart","rangeDeleteEnd","undoStarted","undoEnded","redoStarted","redoEnded","cellClicked","cellDoubleClicked","cellMouseDown","cellContextMenu","cellValueChanged","cellEditRequest","rowValueChanged","headerFocused","cellFocused","rowSelected","selectionChanged","tooltipShow","tooltipHide","cellKeyDown","cellMouseOver","cellMouseOut","filterChanged","filterModified","filterOpened","advancedFilterBuilderVisibleChanged","sortChanged","virtualRowRemoved","rowClicked","rowDoubleClicked","gridReady","gridPreDestroyed","gridSizeChanged","viewportChanged","firstDataRendered","dragStarted","dragStopped","dragCancelled","rowEditingStarted","rowEditingStopped","cellEditingStarted","cellEditingStopped","bodyScroll","bodyScrollEnd","paginationChanged","componentStateChanged","storeRefreshed","stateUpdated","columnMenuVisibleChanged","contextMenuVisibleChanged","rowDragEnter","rowDragMove","rowDragLeave","rowDragEnd","rowDragCancel"],Qt=[...Yt,"scrollbarWidthChanged","keyShortcutChangedCellStart","keyShortcutChangedCellEnd","pinnedHeightChanged","cellFocusCleared","fullWidthRowFocused","checkboxChanged","heightScaleChanged","suppressMovableColumns","suppressMenuHide","suppressFieldDotNotation","columnPanelItemDragStart","columnPanelItemDragEnd","bodyHeightChanged","columnContainerWidthChanged","displayedColumnsWidthChanged","scrollVisibilityChanged","scrollGapChanged","columnHoverChanged","flashCells","paginationPixelOffsetChanged","displayedRowsChanged","leftPinnedWidthChanged","rightPinnedWidthChanged","rowContainerHeightChanged","headerHeightChanged","columnGroupHeaderHeightChanged","columnHeaderHeightChanged","gridStylesChanged","storeUpdated","filterDestroyed","rowDataUpdateStarted","rowCountReady","advancedFilterEnabledChanged","dataTypesInferred","fieldValueChanged","fieldPickerValueSelected","richSelectListRowSelected","sideBarUpdated","alignedGridScroll","alignedGridColumn","gridOptionsChanged","chartTitleEdit","recalculateRowBounds","stickyTopOffsetChanged","overlayExclusiveChanged"],_t={enableBrowserTooltips:!0,tooltipTrigger:!0,tooltipMouseTrack:!0,tooltipShowMode:!0,tooltipInteraction:!0,defaultColGroupDef:!0,suppressAutoSize:!0,skipHeaderOnAutoSize:!0,autoSizeStrategy:!0,components:!0,stopEditingWhenCellsLoseFocus:!0,undoRedoCellEditing:!0,undoRedoCellEditingLimit:!0,excelStyles:!0,cacheQuickFilter:!0,advancedFilterModel:!0,customChartThemes:!0,chartThemeOverrides:!0,chartToolPanelsDef:!0,loadingCellRendererSelector:!0,localeText:!0,keepDetailRows:!0,keepDetailRowsCount:!0,detailRowHeight:!0,detailRowAutoHeight:!0,tabIndex:!0,valueCache:!0,valueCacheNeverExpires:!0,enableCellExpressions:!0,suppressTouch:!0,suppressAsyncEvents:!0,suppressBrowserResizeObserver:!0,suppressPropertyNamesCheck:!0,debug:!0,dragAndDropImageComponent:!0,loadingOverlayComponent:!0,suppressLoadingOverlay:!0,noRowsOverlayComponent:!0,paginationPageSizeSelector:!0,paginateChildRows:!0,pivotPanelShow:!0,pivotSuppressAutoColumn:!0,suppressExpandablePivotGroups:!0,aggFuncs:!0,suppressAggFuncInHeader:!0,allowShowChangeAfterFilter:!0,ensureDomOrder:!0,enableRtl:!0,suppressColumnVirtualisation:!0,suppressMaxRenderedRowRestriction:!0,suppressRowVirtualisation:!0,rowDragText:!0,suppressGroupMaintainValueType:!0,groupLockGroupColumns:!0,rowGroupPanelSuppressSort:!0,suppressGroupRowsSticky:!0,rowModelType:!0,cacheOverflowSize:!0,infiniteInitialRowCount:!0,serverSideInitialRowCount:!0,suppressServerSideInfiniteScroll:!0,maxBlocksInCache:!0,maxConcurrentDatasourceRequests:!0,blockLoadDebounceMillis:!0,serverSideOnlyRefreshFilteredGroups:!0,serverSidePivotResultFieldSeparator:!0,viewportRowModelPageSize:!0,viewportRowModelBufferSize:!0,debounceVerticalScrollbar:!0,suppressAnimationFrame:!0,suppressPreventDefaultOnMouseWheel:!0,scrollbarWidth:!0,icons:!0,suppressRowTransform:!0,gridId:!0,enableGroupEdit:!0,initialState:!0,processUnpinnedColumns:!0,createChartContainer:!0,getLocaleText:!0,getRowId:!0,reactiveCustomComponents:!0,columnMenu:!0,suppressSetFilterByDefault:!0},Zt=class{};Zt.STRING_PROPERTIES=["overlayLoadingTemplate","overlayNoRowsTemplate","gridId","quickFilterText","rowModelType","editType","domLayout","clipboardDelimiter","rowGroupPanelShow","multiSortKey","pivotColumnGroupTotals","pivotRowTotals","pivotPanelShow","fillHandleDirection","groupDisplayType","treeDataDisplayType","colResizeDefault","tooltipTrigger","serverSidePivotResultFieldSeparator","columnMenu","tooltipShowMode","grandTotalRow"],Zt.OBJECT_PROPERTIES=["components","rowStyle","context","autoGroupColumnDef","localeText","icons","datasource","dragAndDropImageComponentParams","serverSideDatasource","viewportDatasource","groupRowRendererParams","aggFuncs","fullWidthCellRendererParams","defaultColGroupDef","defaultColDef","defaultCsvExportParams","defaultExcelExportParams","columnTypes","rowClassRules","detailCellRendererParams","loadingCellRendererParams","loadingOverlayComponentParams","noRowsOverlayComponentParams","popupParent","statusBar","sideBar","theme","chartThemeOverrides","customChartThemes","chartToolPanelsDef","dataTypeDefinitions","advancedFilterModel","advancedFilterParent","advancedFilterBuilderParams","initialState","autoSizeStrategy","cellSelection","selectionColumnDef"],Zt.ARRAY_PROPERTIES=["sortingOrder","alignedGrids","rowData","columnDefs","excelStyles","pinnedTopRowData","pinnedBottomRowData","chartThemes","rowClass","paginationPageSizeSelector"],Zt.NUMBER_PROPERTIES=["rowHeight","detailRowHeight","rowBuffer","headerHeight","groupHeaderHeight","groupLockGroupColumns","floatingFiltersHeight","pivotHeaderHeight","pivotGroupHeaderHeight","groupDefaultExpanded","pivotDefaultExpanded","viewportRowModelPageSize","viewportRowModelBufferSize","autoSizePadding","maxBlocksInCache","maxConcurrentDatasourceRequests","tooltipShowDelay","tooltipHideDelay","cacheOverflowSize","paginationPageSize","cacheBlockSize","infiniteInitialRowCount","serverSideInitialRowCount","scrollbarWidth","asyncTransactionWaitMillis","blockLoadDebounceMillis","keepDetailRowsCount","undoRedoCellEditingLimit","cellFlashDelay","cellFadeDelay","cellFlashDuration","cellFadeDuration","tabIndex","pivotMaxGeneratedColumns"],Zt.BOOLEAN_PROPERTIES=["suppressMakeColumnVisibleAfterUnGroup","suppressRowClickSelection","suppressCellFocus","suppressHeaderFocus","suppressHorizontalScroll","groupSelectsChildren","alwaysShowHorizontalScroll","alwaysShowVerticalScroll","debug","enableBrowserTooltips","enableCellExpressions","groupIncludeTotalFooter","groupSuppressBlankHeader","suppressMenuHide","suppressRowDeselection","unSortIcon","suppressMultiSort","alwaysMultiSort","singleClickEdit","suppressLoadingOverlay","suppressNoRowsOverlay","suppressAutoSize","skipHeaderOnAutoSize","suppressColumnMoveAnimation","suppressMoveWhenColumnDragging","suppressMovableColumns","suppressFieldDotNotation","enableRangeSelection","enableRangeHandle","enableFillHandle","suppressClearOnFillReduction","deltaSort","suppressTouch","suppressAsyncEvents","allowContextMenuWithControlKey","suppressContextMenu","enableCellChangeFlash","suppressDragLeaveHidesColumns","suppressRowGroupHidesColumns","suppressMiddleClickScrolls","suppressPreventDefaultOnMouseWheel","suppressCopyRowsToClipboard","copyHeadersToClipboard","copyGroupHeadersToClipboard","pivotMode","suppressAggFuncInHeader","suppressColumnVirtualisation","alwaysAggregateAtRootLevel","suppressFocusAfterRefresh","functionsReadOnly","animateRows","groupSelectsFiltered","groupRemoveSingleChildren","groupRemoveLowestSingleChildren","enableRtl","suppressClickEdit","rowDragEntireRow","rowDragManaged","suppressRowDrag","suppressMoveWhenRowDragging","rowDragMultiRow","enableGroupEdit","embedFullWidthRows","suppressPaginationPanel","groupHideOpenParents","groupAllowUnbalanced","pagination","paginationAutoPageSize","suppressScrollOnNewData","suppressScrollWhenPopupsAreOpen","purgeClosedRowNodes","cacheQuickFilter","includeHiddenColumnsInQuickFilter","ensureDomOrder","accentedSort","suppressChangeDetection","valueCache","valueCacheNeverExpires","aggregateOnlyChangedColumns","suppressAnimationFrame","suppressExcelExport","suppressCsvExport","includeHiddenColumnsInAdvancedFilter","suppressMultiRangeSelection","enterNavigatesVerticallyAfterEdit","enterNavigatesVertically","suppressPropertyNamesCheck","rowMultiSelectWithClick","suppressRowHoverHighlight","suppressRowTransform","suppressClipboardPaste","suppressLastEmptyLineOnPaste","enableCharts","suppressMaintainUnsortedOrder","enableCellTextSelection","suppressBrowserResizeObserver","suppressMaxRenderedRowRestriction","excludeChildrenWhenTreeDataFiltering","tooltipMouseTrack","tooltipInteraction","keepDetailRows","paginateChildRows","preventDefaultOnContextMenu","undoRedoCellEditing","allowDragFromColumnsToolPanel","pivotSuppressAutoColumn","suppressExpandablePivotGroups","debounceVerticalScrollbar","detailRowAutoHeight","serverSideSortAllLevels","serverSideEnableClientSideSort","serverSideOnlyRefreshFilteredGroups","serverSideSortOnServer","serverSideFilterOnServer","suppressAggFilteredOnly","showOpenedGroup","suppressClipboardApi","suppressModelUpdateAfterUpdateTransaction","stopEditingWhenCellsLoseFocus","groupMaintainOrder","columnHoverHighlight","readOnlyEdit","suppressRowVirtualisation","enableCellEditingOnBackspace","resetRowDataOnUpdate","removePivotHeaderRowWhenSingleValueColumn","suppressCopySingleCellRanges","suppressGroupRowsSticky","suppressCutToClipboard","suppressServerSideInfiniteScroll","rowGroupPanelSuppressSort","allowShowChangeAfterFilter","enableAdvancedFilter","masterDetail","treeData","suppressGroupMaintainValueType","reactiveCustomComponents","applyQuickFilterBeforePivotOrAgg","suppressServerSideFullWidthLoadingRow","suppressAdvancedFilterEval","loading","maintainColumnOrder","enableStrictPivotColumnOrder","suppressSetFilterByDefault"],Zt.OTHER_PROPERTIES=["suppressStickyTotalRow","loadThemeGoogleFonts","rowSelection"],Zt.FUNCTION_PROPERTIES=["doesExternalFilterPass","processPivotResultColDef","processPivotResultColGroupDef","getBusinessKeyForNode","isRowSelectable","rowDragText","groupRowRenderer","dragAndDropImageComponent","fullWidthCellRenderer","loadingCellRenderer","loadingOverlayComponent","noRowsOverlayComponent","detailCellRenderer","quickFilterParser","quickFilterMatcher","getLocaleText","isExternalFilterPresent","getRowHeight","getRowClass","getRowStyle","getContextMenuItems","getMainMenuItems","processRowPostCreate","processCellForClipboard","getGroupRowAgg","isFullWidthRow","sendToClipboard","focusGridInnerElement","navigateToNextHeader","tabToNextHeader","navigateToNextCell","tabToNextCell","processCellFromClipboard","getDocument","postProcessPopup","getChildCount","getDataPath","isRowMaster","postSortRows","processHeaderForClipboard","processUnpinnedColumns","processGroupHeaderForClipboard","paginationNumberFormatter","processDataFromClipboard","getServerSideGroupKey","isServerSideGroup","createChartContainer","getChartToolbarItems","fillOperation","isApplyServerSideTransaction","getServerSideGroupLevelParams","isServerSideGroupOpenByDefault","isGroupOpenByDefault","initialGroupOrderComparator","groupIncludeFooter","loadingCellRendererSelector","getRowId","groupAggFiltering","chartMenuItems","groupTotalRow"],Zt.ALL_PROPERTIES=[...Zt.ARRAY_PROPERTIES,...Zt.OBJECT_PROPERTIES,...Zt.STRING_PROPERTIES,...Zt.NUMBER_PROPERTIES,...Zt.FUNCTION_PROPERTIES,...Zt.BOOLEAN_PROPERTIES,...Zt.OTHER_PROPERTIES];var Jt=Zt,Xt=class{static getCallbackForEvent(e){return!e||e.length<2?e:"on"+e[0].toUpperCase()+e.substring(1)}};Xt.VUE_OMITTED_PROPERTY="AG-VUE-OMITTED-PROPERTY",Xt.PUBLIC_EVENTS=Yt,Xt.EVENT_CALLBACKS=Qt.map((e=>Xt.getCallbackForEvent(e))),Xt.BOOLEAN_PROPERTIES=Jt.BOOLEAN_PROPERTIES,Xt.ALL_PROPERTIES=Jt.ALL_PROPERTIES,Xt.ALL_PROPERTIES_AND_CALLBACKS=[...Xt.ALL_PROPERTIES,...Xt.EVENT_CALLBACKS],Xt.ALL_PROPERTIES_AND_CALLBACKS_SET=new Set(Xt.ALL_PROPERTIES_AND_CALLBACKS);var $t=Xt;function ea(e,t){"object"!=typeof e&&(e={});const a={...e};return $t.ALL_PROPERTIES_AND_CALLBACKS.forEach((e=>{const r=t[e];void 0!==r&&r!==$t.VUE_OMITTED_PROPERTY&&(a[e]=r)})),a}function ta(e,t){if(!e)return;const a={};let r=!1;if(Object.keys(e).filter((e=>$t.ALL_PROPERTIES_AND_CALLBACKS_SET.has(e))).forEach((t=>{a[t]=e[t],r=!0})),!r)return;const n={type:"gridOptionsChanged",options:a};t.dispatchEvent(n);const o={type:"componentStateChanged"};it(a,((e,t)=>{o[e]=t})),t.dispatchEvent(o)}var aa=class extends Ke{};function ra(e,t,a){return a&&e.addDestroyFunc((()=>t.destroyBean(a))),a??e}var na,oa,ia,sa,la,ga,ca,da,ha,ua,pa=class{constructor(e){this.cssClassStates={},this.getGui=e}addCssClass(e){const t=(e||"").split(" ");if(t.length>1)return void t.forEach((e=>this.addCssClass(e)));if(!0!==this.cssClassStates[e]&&e.length){const t=this.getGui();t&&t.classList.add(e),this.cssClassStates[e]=!0}}removeCssClass(e){const t=(e||"").split(" ");if(t.length>1)return void t.forEach((e=>this.removeCssClass(e)));if(!1!==this.cssClassStates[e]&&e.length){const t=this.getGui();t&&t.classList.remove(e),this.cssClassStates[e]=!1}}containsCssClass(e){const t=this.getGui();return!!t&&t.classList.contains(e)}addOrRemoveCssClass(e,t){if(!e)return;if(e.indexOf(" ")>=0){const a=(e||"").split(" ");if(a.length>1)return void a.forEach((e=>this.addOrRemoveCssClass(e,t)))}if(this.cssClassStates[e]!==t&&e.length){const a=this.getGui();a&&a.classList.toggle(e,t),this.cssClassStates[e]=t}}};function ma(e,t,a){null==a||"string"==typeof a&&""==a?fa(e,t):va(e,t,a)}function va(e,t,a){e.setAttribute(ba(t),a.toString())}function fa(e,t){e.removeAttribute(ba(t))}function ba(e){return`aria-${e}`}function wa(e,t){t?e.setAttribute("role",t):e.removeAttribute("role")}function Ca(e){return parseInt(e.getAttribute("aria-posinset"),10)}function ya(e,t){ma(e,"label",t)}function ka(e,t){ma(e,"labelledby",t)}function Sa(e,t){ma(e,"describedby",t)}function xa(e,t){ma(e,"live",t)}function za(e,t){ma(e,"level",t)}function Ea(e,t){ma(e,"disabled",t)}function Ra(e,t){ma(e,"hidden",t)}function Ma(e,t){ma(e,"activedescendant",t)}function Fa(e,t){va(e,"expanded",t)}function Da(e){fa(e,"expanded")}function Aa(e,t){va(e,"setsize",t)}function Pa(e,t){va(e,"posinset",t)}function Ta(e,t){va(e,"rowcount",t)}function Ia(e,t){va(e,"rowindex",t)}function La(e,t){va(e,"colcount",t)}function Oa(e,t){va(e,"colindex",t)}function Na(e,t){va(e,"colspan",t)}function Ga(e,t){va(e,"sort",t)}function Ba(e){fa(e,"sort")}function Ha(e,t){ma(e,"selected",t)}function qa(e,t){va(e,"checked",void 0===t?"mixed":t)}function Va(e,t){ma(e,"controls",t.id),ka(t,e.id)}function Wa(e,t){return void 0===t?e("ariaIndeterminate","indeterminate"):!0===t?e("ariaChecked","checked"):e("ariaUnchecked","unchecked")}function ja(){return void 0===na&&(na=/^((?!chrome|android).)*safari/i.test(navigator.userAgent)),na}function Ua(){if(void 0===oa)if(ja()){const e=navigator.userAgent.match(/version\/(\d+)/i);e&&(oa=null!=e[1]?parseFloat(e[1]):0)}else oa=0;return oa}function Ka(){if(void 0===ia){const e=window;ia=!!e.chrome&&(!!e.chrome.webstore||!!e.chrome.runtime)||/Chrome/.test(navigator.userAgent)&&/Google Inc/.test(navigator.vendor)}return ia}function Ya(){return void 0===sa&&(sa=/(firefox)/i.test(navigator.userAgent)),sa}function Qa(){return void 0===la&&(la=/(Mac|iPhone|iPod|iPad)/i.test(navigator.platform)),la}function _a(){return void 0===ga&&(ga=/iPad|iPhone|iPod/.test(navigator.platform)||"MacIntel"===navigator.platform&&navigator.maxTouchPoints>1),ga}function Za(){return!ja()||Ua()>=15}function Ja(e){if(!e)return null;const t=e.tabIndex,a=e.getAttribute("tabIndex");return-1!==t||null!==a&&(""!==a||Ya())?t.toString():null}function Xa(){const e=document.body,t=document.createElement("div");t.style.width=t.style.height="100px",t.style.opacity="0",t.style.overflow="scroll",t.style.msOverflowStyle="scrollbar",t.style.position="absolute",e.appendChild(t);let a=t.offsetWidth-t.clientWidth;0===a&&0===t.clientWidth&&(a=null),t.parentNode&&t.parentNode.removeChild(t),null!=a&&(da=a,ca=0===a)}function $a(){return null==ca&&Xa(),ca}function er(e,t,a){const r=e.parentElement;let n=r&&r.firstChild;for(;n;)t&&n.classList.toggle(t,n===e),a&&n.classList.toggle(a,n!==e),n=n.nextSibling}var tr="[disabled], .ag-disabled:not(.ag-button), .ag-disabled *";function ar(e){const t=Element.prototype.matches||Element.prototype.msMatchesSelector,a=t.call(e,"input, select, button, textarea"),r=t.call(e,tr),n=wr(e);return a&&!r&&n}function rr(e,t,a={}){const{skipAriaHidden:r}=a;e.classList.toggle("ag-hidden",!t),r||Ra(e,!t)}function nr(e,t,a={}){const{skipAriaHidden:r}=a;e.classList.toggle("ag-invisible",!t),r||Ra(e,!t)}function or(e,t){const a="disabled",r=t?e=>e.setAttribute(a,""):e=>e.removeAttribute(a);r(e),Dr(e.querySelectorAll("input"),(e=>r(e)))}function ir(e,t,a){let r=0;for(;e;){if(e.classList.contains(t))return!0;if(e=e.parentElement,"number"==typeof a){if(++r>a)break}else if(e===a)break}return!1}function sr(e){const{height:t,width:a,borderTopWidth:r,borderRightWidth:n,borderBottomWidth:o,borderLeftWidth:i,paddingTop:s,paddingRight:l,paddingBottom:g,paddingLeft:c,marginTop:d,marginRight:h,marginBottom:u,marginLeft:p,boxSizing:m}=window.getComputedStyle(e);return{height:parseFloat(t||"0"),width:parseFloat(a||"0"),borderTopWidth:parseFloat(r||"0"),borderRightWidth:parseFloat(n||"0"),borderBottomWidth:parseFloat(o||"0"),borderLeftWidth:parseFloat(i||"0"),paddingTop:parseFloat(s||"0"),paddingRight:parseFloat(l||"0"),paddingBottom:parseFloat(g||"0"),paddingLeft:parseFloat(c||"0"),marginTop:parseFloat(d||"0"),marginRight:parseFloat(h||"0"),marginBottom:parseFloat(u||"0"),marginLeft:parseFloat(p||"0"),boxSizing:m}}function lr(e){const t=sr(e);return"border-box"===t.boxSizing?t.height-t.paddingTop-t.paddingBottom:t.height}function gr(e){const t=sr(e);return"border-box"===t.boxSizing?t.width-t.paddingLeft-t.paddingRight:t.width}function cr(e){const{height:t,marginBottom:a,marginTop:r}=sr(e);return Math.floor(t+a+r)}function dr(e){const{width:t,marginLeft:a,marginRight:r}=sr(e);return Math.floor(t+a+r)}function hr(e){const t=e.getBoundingClientRect(),{borderTopWidth:a,borderLeftWidth:r,borderRightWidth:n,borderBottomWidth:o}=sr(e);return{top:t.top+(a||0),left:t.left+(r||0),right:t.right+(n||0),bottom:t.bottom+(o||0)}}function ur(){if("boolean"==typeof ua)return ua;const e=document.createElement("div");return e.style.direction="rtl",e.style.width="1px",e.style.height="1px",e.style.position="fixed",e.style.top="0px",e.style.overflow="hidden",e.dir="rtl",e.innerHTML='
\n \n \n
',document.body.appendChild(e),e.scrollLeft=1,ua=0===Math.floor(e.scrollLeft),document.body.removeChild(e),ua}function pr(e,t){let a=e.scrollLeft;return t&&(a=Math.abs(a),Ka()&&!ur()&&(a=e.scrollWidth-e.getBoundingClientRect().width-a)),a}function mr(e,t,a){a&&(ur()?t*=-1:(ja()||Ka())&&(t=e.scrollWidth-e.getBoundingClientRect().width-t)),e.scrollLeft=t}function vr(e){for(;e&&e.firstChild;)e.removeChild(e.firstChild)}function fr(e){e&&e.parentNode&&e.parentNode.removeChild(e)}function br(e){return!!e.offsetParent}function wr(e){const t=e;if(t.checkVisibility)return t.checkVisibility({checkVisibilityCSS:!0});return!(!br(e)||"visible"!==window.getComputedStyle(e).visibility)}function Cr(e){const t=document.createElement("div");return t.innerHTML=(e||"").trim(),t.firstChild}function yr(e,t,a){a&&a.nextSibling===t||(a?a.nextSibling?e.insertBefore(t,a.nextSibling):e.appendChild(t):e.firstChild&&e.firstChild!==t&&e.insertAdjacentElement("afterbegin",t))}function kr(e,t){for(let a=0;a`-${e.toLocaleLowerCase()}`)),n=r.toString(),o=n.replace(/\s*!important/g,""),i=o.length!=n.length?"important":void 0;e.style.setProperty(t,o,i)}}function xr(e,t){"flex"===t?(e.style.removeProperty("width"),e.style.removeProperty("minWidth"),e.style.removeProperty("maxWidth"),e.style.flex="1 1 auto"):zr(e,t)}function zr(e,t){t=Rr(t),e.style.width=t.toString(),e.style.maxWidth=t.toString(),e.style.minWidth=t.toString()}function Er(e,t){t=Rr(t),e.style.height=t.toString(),e.style.maxHeight=t.toString(),e.style.minHeight=t.toString()}function Rr(e){return"number"==typeof e?`${e}px`:e}function Mr(e){return e instanceof Node||e instanceof HTMLElement}function Fr(e,t,a){null==a||""===a?e.removeAttribute(t):e.setAttribute(t,a.toString())}function Dr(e,t){if(null!=e)for(let a=0;a{const a=e.getGui();null!=a&&("object"==typeof a?t.appendChild(a):t.innerHTML=a)}))}var Pr=class{constructor(e=0,t=1){this.nextValue=e,this.step=t}next(){const e=this.nextValue;return this.nextValue+=this.step,e}peek(){return this.nextValue}skip(e){this.nextValue+=e}},Tr=class e extends Ke{constructor(e,t,a,r){super(),this.parentComp=e,this.tooltipShowDelayOverride=t,this.tooltipHideDelayOverride=a,this.shouldDisplayTooltip=r,this.interactionEnabled=!1,this.isInteractingWithTooltip=!1,this.state=0,this.tooltipInstanceCount=0,this.tooltipMouseTrack=!1}wireBeans(e){this.popupService=e.popupService,this.userComponentFactory=e.userComponentFactory}postConstruct(){this.gos.get("tooltipInteraction")&&(this.interactionEnabled=!0),this.tooltipTrigger=this.getTooltipTrigger(),this.tooltipMouseTrack=this.gos.get("tooltipMouseTrack");const e=this.parentComp.getGui();0===this.tooltipTrigger&&this.addManagedListeners(e,{mouseenter:this.onMouseEnter.bind(this),mouseleave:this.onMouseLeave.bind(this)}),1===this.tooltipTrigger&&this.addManagedListeners(e,{focusin:this.onFocusIn.bind(this),focusout:this.onFocusOut.bind(this)}),this.addManagedListeners(e,{mousemove:this.onMouseMove.bind(this)}),this.interactionEnabled||this.addManagedListeners(e,{mousedown:this.onMouseDown.bind(this),keydown:this.onKeyDown.bind(this)})}getGridOptionsTooltipDelay(e){const t=this.gos.get(e);return t<0&&p(`${e} should not be lower than 0`),Math.max(200,t)}getTooltipDelay(e){return"show"===e?this.tooltipShowDelayOverride??this.getGridOptionsTooltipDelay("tooltipShowDelay"):this.tooltipHideDelayOverride??this.getGridOptionsTooltipDelay("tooltipHideDelay")}destroy(){this.setToDoNothing(),super.destroy()}getTooltipTrigger(){const e=this.gos.get("tooltipTrigger");return e&&"hover"!==e?1:0}onMouseEnter(t){this.interactionEnabled&&this.interactiveTooltipTimeoutId&&(this.unlockService(),this.startHideTimeout()),_a()||(e.isLocked?this.showTooltipTimeoutId=window.setTimeout((()=>{this.prepareToShowTooltip(t)}),100):this.prepareToShowTooltip(t))}onMouseMove(e){this.lastMouseEvent&&(this.lastMouseEvent=e),this.tooltipMouseTrack&&2===this.state&&this.tooltipComp&&this.positionTooltip()}onMouseDown(){this.setToDoNothing()}onMouseLeave(){this.interactionEnabled?this.lockService():this.setToDoNothing()}onFocusIn(){this.prepareToShowTooltip()}onFocusOut(e){const t=e.relatedTarget,a=this.parentComp.getGui(),r=this.tooltipComp?.getGui();this.isInteractingWithTooltip||a.contains(t)||this.interactionEnabled&&r?.contains(t)||this.setToDoNothing()}onKeyDown(){this.isInteractingWithTooltip&&(this.isInteractingWithTooltip=!1),this.setToDoNothing()}prepareToShowTooltip(t){if(0!=this.state||e.isLocked)return;let a=0;t&&(a=this.isLastTooltipHiddenRecently()?200:this.getTooltipDelay("show")),this.lastMouseEvent=t||null,this.showTooltipTimeoutId=window.setTimeout(this.showTooltip.bind(this),a),this.state=1}isLastTooltipHiddenRecently(){return(new Date).getTime()-e.lastTooltipHideTime<1e3}setToDoNothing(e){e||2!==this.state||this.hideTooltip(),this.onBodyScrollEventCallback&&(this.onBodyScrollEventCallback(),this.onBodyScrollEventCallback=void 0),this.onColumnMovedEventCallback&&(this.onColumnMovedEventCallback(),this.onColumnMovedEventCallback=void 0),this.onDocumentKeyDownCallback&&(this.onDocumentKeyDownCallback(),this.onDocumentKeyDownCallback=void 0),this.clearTimeouts(),this.state=0,this.lastMouseEvent=null}showTooltip(){const e={...this.parentComp.getTooltipParams()};if(!E(e.value)||this.shouldDisplayTooltip&&!this.shouldDisplayTooltip())return void this.setToDoNothing();this.state=2,this.tooltipInstanceCount++;const t=this.newTooltipComponentCallback.bind(this,this.tooltipInstanceCount);this.userComponentFactory.getTooltipCompDetails(e).newAgStackInstance().then(t)}hideTooltip(t){!t&&this.isInteractingWithTooltip||(this.tooltipComp&&(this.destroyTooltipComp(),e.lastTooltipHideTime=(new Date).getTime()),this.eventService.dispatchEvent({type:"tooltipHide",parentGui:this.parentComp.getGui()}),t&&(this.isInteractingWithTooltip=!1),this.setToDoNothing(!0))}newTooltipComponentCallback(e,t){if(2!==this.state||this.tooltipInstanceCount!==e)return void this.destroyBean(t);const a=t.getGui();this.tooltipComp=t,a.classList.contains("ag-tooltip")||a.classList.add("ag-tooltip-custom"),0===this.tooltipTrigger&&a.classList.add("ag-tooltip-animate"),this.interactionEnabled&&a.classList.add("ag-tooltip-interactive");const r=this.localeService.getLocaleTextFunc(),n=this.popupService.addPopup({eChild:a,ariaLabel:r("ariaLabelTooltip","Tooltip")});if(n&&(this.tooltipPopupDestroyFunc=n.hideFunc),this.positionTooltip(),1===this.tooltipTrigger){const e=()=>this.setToDoNothing();[this.onBodyScrollEventCallback,this.onColumnMovedEventCallback]=this.addManagedEventListeners({bodyScroll:e,columnMoved:e})}this.interactionEnabled&&([this.tooltipMouseEnterListener,this.tooltipMouseLeaveListener]=this.addManagedElementListeners(a,{mouseenter:this.onTooltipMouseEnter.bind(this),mouseleave:this.onTooltipMouseLeave.bind(this)}),[this.onDocumentKeyDownCallback]=this.addManagedElementListeners(Q(this.gos),{keydown:e=>{a.contains(e?.target)||this.onKeyDown()}}),1===this.tooltipTrigger&&([this.tooltipFocusInListener,this.tooltipFocusOutListener]=this.addManagedElementListeners(a,{focusin:this.onTooltipFocusIn.bind(this),focusout:this.onTooltipFocusOut.bind(this)}))),this.eventService.dispatchEvent({type:"tooltipShow",tooltipGui:a,parentGui:this.parentComp.getGui()}),this.startHideTimeout()}onTooltipMouseEnter(){this.isInteractingWithTooltip=!0,this.unlockService()}onTooltipMouseLeave(){this.isTooltipFocused()||(this.isInteractingWithTooltip=!1,this.lockService())}onTooltipFocusIn(){this.isInteractingWithTooltip=!0}isTooltipFocused(){const e=this.tooltipComp?.getGui(),t=J(this.gos);return!!e&&e.contains(t)}onTooltipFocusOut(e){const t=this.parentComp.getGui();this.isTooltipFocused()||(this.isInteractingWithTooltip=!1,t.contains(e.relatedTarget)?this.startHideTimeout():this.hideTooltip())}positionTooltip(){const e={type:"tooltip",ePopup:this.tooltipComp.getGui(),nudgeY:18,skipObserver:this.tooltipMouseTrack};this.lastMouseEvent?this.popupService.positionPopupUnderMouseEvent({...e,mouseEvent:this.lastMouseEvent}):this.popupService.positionPopupByComponent({...e,eventSource:this.parentComp.getGui(),position:"under",keepWithinBounds:!0,nudgeY:5})}destroyTooltipComp(){this.tooltipComp.getGui().classList.add("ag-tooltip-hiding");const e=this.tooltipPopupDestroyFunc,t=this.tooltipComp,a=0===this.tooltipTrigger?1e3:0;window.setTimeout((()=>{e(),this.destroyBean(t)}),a),this.clearTooltipListeners(),this.tooltipPopupDestroyFunc=void 0,this.tooltipComp=void 0}clearTooltipListeners(){[this.tooltipMouseEnterListener,this.tooltipMouseLeaveListener,this.tooltipFocusInListener,this.tooltipFocusOutListener].forEach((e=>{e&&e()})),this.tooltipMouseEnterListener=this.tooltipMouseLeaveListener=this.tooltipFocusInListener=this.tooltipFocusOutListener=null}lockService(){e.isLocked=!0,this.interactiveTooltipTimeoutId=window.setTimeout((()=>{this.unlockService(),this.setToDoNothing()}),100)}unlockService(){e.isLocked=!1,this.clearInteractiveTimeout()}startHideTimeout(){this.clearHideTimeout(),this.hideTooltipTimeoutId=window.setTimeout(this.hideTooltip.bind(this),this.getTooltipDelay("hide"))}clearShowTimeout(){this.showTooltipTimeoutId&&(window.clearTimeout(this.showTooltipTimeoutId),this.showTooltipTimeoutId=void 0)}clearHideTimeout(){this.hideTooltipTimeoutId&&(window.clearTimeout(this.hideTooltipTimeoutId),this.hideTooltipTimeoutId=void 0)}clearInteractiveTimeout(){this.interactiveTooltipTimeoutId&&(window.clearTimeout(this.interactiveTooltipTimeoutId),this.interactiveTooltipTimeoutId=void 0)}clearTimeouts(){this.clearShowTimeout(),this.clearHideTimeout(),this.clearInteractiveTimeout()}};Tr.isLocked=!1;var Ir=Tr,Lr=class extends Ke{constructor(e,t){super(),this.ctrl=e,t&&(this.beans=t)}wireBeans(e){this.beans=e}postConstruct(){this.refreshToolTip()}setBrowserTooltip(e){const t="title",a=this.ctrl.getGui();a&&(null!=e&&""!=e?a.setAttribute(t,e):a.removeAttribute(t))}updateTooltipText(){this.tooltip=this.ctrl.getTooltipValue()}createTooltipFeatureIfNeeded(){if(null!=this.tooltipManager)return;const e={getTooltipParams:()=>this.getTooltipParams(),getGui:()=>this.ctrl.getGui()};this.tooltipManager=this.createBean(new Ir(e,this.ctrl.getTooltipShowDelayOverride?.(),this.ctrl.getTooltipHideDelayOverride?.(),this.ctrl.shouldDisplayTooltip),this.beans.context)}refreshToolTip(){this.browserTooltips=this.beans.gos.get("enableBrowserTooltips"),this.updateTooltipText(),this.browserTooltips?(this.setBrowserTooltip(this.tooltip),this.tooltipManager&&(this.tooltipManager=this.destroyBean(this.tooltipManager,this.beans.context))):(this.setBrowserTooltip(null),this.createTooltipFeatureIfNeeded())}getTooltipParams(){const e=this.ctrl,t=e.getColumn?.(),a=e.getColDef?.(),r=e.getRowNode?.();return{location:e.getLocation(),colDef:a,column:t,rowIndex:e.getRowIndex?.(),node:r,data:r?.data,value:this.getTooltipText(),valueFormatted:e.getValueFormatted?.(),hideTooltipCallback:()=>this.tooltipManager?.hideTooltip(!0)}}getTooltipText(){return this.tooltip}destroy(){this.tooltipManager&&(this.tooltipManager=this.destroyBean(this.tooltipManager,this.beans.context)),super.destroy()}},Or=new Pr,Nr=null,Gr=class e extends Ke{constructor(e,t){super(),this.suppressDataRefValidation=!1,this.displayed=!0,this.visible=!0,this.compId=Or.next(),this.cssClassManager=new pa((()=>this.eGui)),this.componentSelectors=new Map((t??[]).map((e=>[e.selector,e]))),e&&this.setTemplate(e)}preWireBeans(e){super.preWireBeans(e)}preConstruct(){this.usingBrowserTooltips=this.gos.get("enableBrowserTooltips"),this.wireTemplate(this.getGui())}wireTemplate(e,t){e&&this.gos&&(this.applyElementsToComponent(e),this.createChildComponentsFromTags(e,t))}getCompId(){return this.compId}getTooltipParams(){return{value:this.tooltipText,location:"UNKNOWN"}}setTooltip(e){const{newTooltipText:t,showDelayOverride:a,hideDelayOverride:r,location:n,shouldDisplayTooltip:o}=e||{};this.tooltipFeature&&(this.tooltipFeature=this.destroyBean(this.tooltipFeature)),this.tooltipText!==t&&(this.tooltipText=t);const i=()=>this.tooltipText;null!=t&&(this.tooltipFeature=this.createBean(new Lr({getTooltipValue:i,getGui:()=>this.getGui(),getLocation:()=>n??"UNKNOWN",getColDef:e?.getColDef,getColumn:e?.getColumn,getTooltipShowDelayOverride:null!=a?()=>a:void 0,getTooltipHideDelayOverride:null!=r?()=>r:void 0,shouldDisplayTooltip:o})))}getDataRefAttribute(e){return e.getAttribute?e.getAttribute("data-ref"):null}applyElementsToComponent(e,t,a,r=null){if(void 0===t&&(t=this.getDataRefAttribute(e)),t){const n=this[t];if(n===Nr)this[t]=r??e;else{const e=a&&a[t];this.suppressDataRefValidation||e||p(`Issue with data-ref: ${t} on ${this.constructor.name} with ${n}`)}}}createChildComponentsFromTags(e,t){(function(e){if(null==e)return[];const t=[];return Dr(e,(e=>t.push(e))),t})(e.childNodes).forEach((a=>{if(!(a instanceof HTMLElement))return;const r=this.createComponentFromElement(a,(e=>{e.getGui()&&this.copyAttributesFromNode(a,e.getGui())}),t);if(r){if(r.addItems&&a.children.length){this.createChildComponentsFromTags(a,t);const e=Array.prototype.slice.call(a.children);r.addItems(e)}this.swapComponentForNode(r,e,a)}else a.childNodes&&this.createChildComponentsFromTags(a,t)}))}createComponentFromElement(t,a,r){const n=t.nodeName,o=this.getDataRefAttribute(t),i=0===n.indexOf("AG-"),s=i?this.componentSelectors.get(n):null;let l=null;if(s){e.elementGettingCreated=t;const n=r&&o?r[o]:void 0;l=new s.component(n),l.setParentComponent(this),this.createBean(l,null,a)}else i&&p(`Missing selector: ${n}`);return this.applyElementsToComponent(t,o,r,l),l}copyAttributesFromNode(e,t){!function(e,t){if(e)for(let a=0;at.setAttribute(e,a)))}swapComponentForNode(e,t,a){const r=e.getGui();t.replaceChild(r,a),t.insertBefore(document.createComment(a.nodeName),r),this.addDestroyFunc(this.destroyBean.bind(this,e))}activateTabIndex(e){const t=this.gos.get("tabIndex");e||(e=[]),e.length||e.push(this.getGui()),e.forEach((e=>e.setAttribute("tabindex",t.toString())))}setTemplate(e,t,a){const r=Cr(e);this.setTemplateFromElement(r,t,a)}setTemplateFromElement(e,t,a,r=!1){if(this.eGui=e,this.suppressDataRefValidation=r,t)for(let e=0;ethis.eGui.removeEventListener(e,t)))}addCssClass(e){this.cssClassManager.addCssClass(e)}removeCssClass(e){this.cssClassManager.removeCssClass(e)}containsCssClass(e){return this.cssClassManager.containsCssClass(e)}addOrRemoveCssClass(e,t){this.cssClassManager.addOrRemoveCssClass(e,t)}},Br={columnGroupOpened:"expanded",columnGroupClosed:"contracted",columnSelectClosed:"tree-closed",columnSelectOpen:"tree-open",columnSelectIndeterminate:"tree-indeterminate",columnMovePin:"pin",columnMoveHide:"eye-slash",columnMoveMove:"arrows",columnMoveLeft:"left",columnMoveRight:"right",columnMoveGroup:"group",columnMoveValue:"aggregation",columnMovePivot:"pivot",dropNotAllowed:"not-allowed",groupContracted:"tree-closed",groupExpanded:"tree-open",setFilterGroupClosed:"tree-closed",setFilterGroupOpen:"tree-open",setFilterGroupIndeterminate:"tree-indeterminate",chart:"chart",close:"cross",cancel:"cancel",check:"tick",first:"first",previous:"previous",next:"next",last:"last",linked:"linked",unlinked:"unlinked",colorPicker:"color-picker",groupLoading:"loading",menu:"menu",menuAlt:"menu-alt",filter:"filter",columns:"columns",maximize:"maximize",minimize:"minimize",menuPin:"pin",menuValue:"aggregation",menuAddRowGroup:"group",menuRemoveRowGroup:"group",clipboardCopy:"copy",clipboardCut:"cut",clipboardPaste:"paste",pivotPanel:"pivot",rowGroupPanel:"group",valuePanel:"aggregation",columnDrag:"grip",rowDrag:"grip",save:"save",csvExport:"csv",excelExport:"excel",smallDown:"small-down",smallLeft:"small-left",smallRight:"small-right",smallUp:"small-up",sortAscending:"asc",sortDescending:"desc",sortUnSort:"none",advancedFilterBuilder:"group",advancedFilterBuilderDrag:"grip",advancedFilterBuilderInvalid:"not-allowed",advancedFilterBuilderMoveUp:"up",advancedFilterBuilderMoveDown:"down",advancedFilterBuilderAdd:"plus",advancedFilterBuilderRemove:"minus",chartsMenuEdit:"chart",chartsMenuAdvancedSettings:"settings",chartsMenuAdd:"plus",checkboxChecked:"checkbox-checked",checkboxIndeterminate:"checkbox-indeterminate",checkboxUnchecked:"checkbox-unchecked",radioButtonOn:"radio-button-on",radioButtonOff:"radio-button-off"},Hr=(()=>{const e=new Set(Object.values(Br));return e.add("eye"),e})();function qr(e,t,a){const r=Vr(e,t,a);if(r){const{className:e}=r;if("string"==typeof e&&e.indexOf("ag-icon")>-1||"object"==typeof e&&e["ag-icon"])return r}const n=document.createElement("span");return n.appendChild(r),n}function Vr(e,t,a,r){let n=null;const o=a&&a.getColDef().icons;if(o&&(n=o[e]),t&&!n){const a=t.get("icons");a&&(n=a[e])}if(!n){const t=document.createElement("span");let a=Br[e]??(Hr.has(e)?e:void 0);return a||(r?a=e:(p(`Did not find icon ${e}`),a="")),t.setAttribute("class",`ag-icon ag-icon-${a}`),t.setAttribute("unselectable","on"),wa(t,"presentation"),t}{let e;if("function"==typeof n)e=n();else{if("string"!=typeof n)throw new Error("icon from grid options needs to be a string or a function");e=n}if("string"==typeof e)return Cr(e);if(Mr(e))return e;p("iconRenderer should return back a string or a dom object")}}var Wr=class extends Gr{constructor(){super(...arguments),this.dragSource=null,this.eIcon=Nr,this.eLabel=Nr}postConstruct(){const e=e=>qr(e,this.gos,null);this.dropIconMap={pinned:e("columnMovePin"),hide:e("columnMoveHide"),move:e("columnMoveMove"),left:e("columnMoveLeft"),right:e("columnMoveRight"),group:e("columnMoveGroup"),aggregate:e("columnMoveValue"),pivot:e("columnMovePivot"),notAllowed:e("dropNotAllowed")}}init(e){this.dragSource=e.dragSource,this.setTemplate('
\n \n
\n
')}destroy(){this.dragSource=null,super.destroy()}setIcon(e,t=!1){vr(this.eIcon);let a=null;e||(e=this.dragSource?.getDefaultIconName?this.dragSource?.getDefaultIconName():"notAllowed"),a=this.dropIconMap[e],this.eIcon.classList.toggle("ag-shake-left-to-right",t),a===this.dropIconMap.hide&&this.gos.get("suppressDragLeaveHidesColumns")||a&&this.eIcon.appendChild(a)}setLabel(e){this.eLabel.textContent=Ot(e)}};function jr(e,t,a){if(0===a)return!1;const r=Math.abs(e.clientX-t.clientX),n=Math.abs(e.clientY-t.clientY);return Math.max(r,n)<=a}var Ur=class{constructor(e,t=!1){this.DOUBLE_TAP_MILLIS=500,this.destroyFuncs=[],this.touching=!1,this.localEventService=new g,this.eElement=e,this.preventMouseClick=t;const a=this.onTouchStart.bind(this),r=this.onTouchMove.bind(this),n=this.onTouchEnd.bind(this);this.eElement.addEventListener("touchstart",a,{passive:!0}),this.eElement.addEventListener("touchmove",r,{passive:!0}),this.eElement.addEventListener("touchend",n,{passive:!1}),this.destroyFuncs.push((()=>{this.eElement.removeEventListener("touchstart",a,{passive:!0}),this.eElement.removeEventListener("touchmove",r,{passive:!0}),this.eElement.removeEventListener("touchend",n,{passive:!1})}))}getActiveTouch(e){for(let t=0;t{const a=this.touchStart===t;if(this.touching&&a&&!this.moved){this.moved=!0;const t={type:"longTap",touchStart:this.touchStart,touchEvent:e};this.localEventService.dispatchEvent(t)}}),500)}onTouchMove(e){if(!this.touching)return;const t=this.getActiveTouch(e.touches);if(!t)return;!jr(t,this.touchStart,4)&&(this.moved=!0)}onTouchEnd(e){if(this.touching){if(!this.moved){const e={type:"tap",touchStart:this.touchStart};this.localEventService.dispatchEvent(e),this.checkForDoubleTap()}this.preventMouseClick&&e.cancelable&&e.preventDefault(),this.touching=!1}}checkForDoubleTap(){const e=(new Date).getTime();if(this.lastTapTime&&this.lastTapTime>0){if(e-this.lastTapTime>this.DOUBLE_TAP_MILLIS){const e={type:"doubleTap",touchStart:this.touchStart};this.localEventService.dispatchEvent(e),this.lastTapTime=null}else this.lastTapTime=e}else this.lastTapTime=e}destroy(){this.destroyFuncs.forEach((e=>e()))}};function Kr(e,t){return``}var Yr=`\n ${Kr("Order","order")}\n ${Kr("Asc","ascending-icon")}\n ${Kr("Desc","descending-icon")}\n ${Kr("Mixed","mixed-icon")}\n ${Kr("None","none-icon")}\n `,Qr=class extends Gr{constructor(e){super(),this.eSortOrder=Nr,this.eSortAsc=Nr,this.eSortDesc=Nr,this.eSortMixed=Nr,this.eSortNone=Nr,e||this.setTemplate(Yr)}wireBeans(e){this.sortController=e.sortController}attachCustomElements(e,t,a,r,n){this.eSortOrder=e,this.eSortAsc=t,this.eSortDesc=a,this.eSortMixed=r,this.eSortNone=n}setupSort(e,t=!1){if(this.column=e,this.suppressOrder=t,this.setupMultiSortIndicator(),!this.column.isSortable()&&!this.column.getColDef().showRowGroup)return;this.addInIcon("sortAscending",this.eSortAsc,e),this.addInIcon("sortDescending",this.eSortDesc,e),this.addInIcon("sortUnSort",this.eSortNone,e);const a=this.updateIcons.bind(this),r=this.onSortChanged.bind(this);this.addManagedPropertyListener("unSortIcon",a),this.addManagedEventListeners({newColumnsLoaded:a,sortChanged:r,columnRowGroupChanged:r}),this.onSortChanged()}addInIcon(e,t,a){if(null==t)return;const r=Vr(e,this.gos,a);r&&t.appendChild(r)}onSortChanged(){this.updateIcons(),this.suppressOrder||this.updateSortOrder()}updateIcons(){const e=this.sortController.getDisplaySortForColumn(this.column);if(this.eSortAsc){const t="asc"===e;rr(this.eSortAsc,t,{skipAriaHidden:!0})}if(this.eSortDesc){const t="desc"===e;rr(this.eSortDesc,t,{skipAriaHidden:!0})}if(this.eSortNone){const t=!this.column.getColDef().unSortIcon&&!this.gos.get("unSortIcon"),a=null==e;rr(this.eSortNone,!t&&a,{skipAriaHidden:!0})}}setupMultiSortIndicator(){this.addInIcon("sortUnSort",this.eSortMixed,this.column);const e=this.column.getColDef().showRowGroup;te(this.gos)&&e&&(this.addManagedEventListeners({sortChanged:this.updateMultiSortIndicator.bind(this),columnRowGroupChanged:this.updateMultiSortIndicator.bind(this)}),this.updateMultiSortIndicator())}updateMultiSortIndicator(){if(this.eSortMixed){const e="mixed"===this.sortController.getDisplaySortForColumn(this.column);rr(this.eSortMixed,e,{skipAriaHidden:!0})}}updateSortOrder(){if(!this.eSortOrder)return;const e=this.sortController.getColumnsWithSortingOrdered(),t=this.sortController.getDisplaySortIndexForColumn(this.column)??-1,a=e.some((e=>this.sortController.getDisplaySortIndexForColumn(e)??!1)),r=t>=0&&a;rr(this.eSortOrder,r,{skipAriaHidden:!0}),t>=0?this.eSortOrder.textContent=(t+1).toString():vr(this.eSortOrder)}},_r={selector:"AG-SORT-INDICATOR",component:Qr},Zr=class extends Gr{constructor(){super(...arguments),this.eFilter=Nr,this.eFilterButton=Nr,this.eSortIndicator=Nr,this.eMenu=Nr,this.eLabel=Nr,this.eText=Nr,this.eSortOrder=Nr,this.eSortAsc=Nr,this.eSortDesc=Nr,this.eSortMixed=Nr,this.eSortNone=Nr,this.lastMovingChanged=0}wireBeans(e){this.sortController=e.sortController,this.menuService=e.menuService,this.funcColsService=e.funcColsService}destroy(){super.destroy()}refresh(e){const t=this.params;return this.params=e,this.workOutTemplate()==this.currentTemplate&&this.workOutShowMenu()==this.currentShowMenu&&this.workOutSort()==this.currentSort&&this.shouldSuppressMenuHide()==this.currentSuppressMenuHide&&t.enableFilterButton==e.enableFilterButton&&t.enableFilterIcon==e.enableFilterIcon&&(this.setDisplayName(e),!0)}workOutTemplate(){let e=this.params.template??'';return e=e&&e.trim?e.trim():e,e}init(e){this.params=e,this.currentTemplate=this.workOutTemplate(),this.setTemplate(this.currentTemplate,[_r]),this.setupTap(),this.setMenu(),this.setupSort(),this.setupFilterIcon(),this.setupFilterButton(),this.setDisplayName(e)}setDisplayName(e){if(this.currentDisplayName!=e.displayName){this.currentDisplayName=e.displayName;const t=Ot(this.currentDisplayName,!0);this.eText&&(this.eText.textContent=t)}}addInIcon(e,t,a){if(null==t)return;const r=Vr(e,this.gos,a);r&&t.appendChild(r)}setupTap(){const{gos:e}=this;if(e.get("suppressTouch"))return;const t=new Ur(this.getGui(),!0),a=this.shouldSuppressMenuHide(),r=a&&E(this.eMenu),n=r?new Ur(this.eMenu,!0):t;if(this.params.enableMenu){const e=r?"tap":"longTap",t=e=>this.params.showColumnMenuAfterMouseClick(e.touchStart);this.addManagedListeners(n,{[e]:t})}if(this.params.enableSorting){const e=e=>{const t=e.touchStart.target;a&&(this.eMenu?.contains(t)||this.eFilterButton?.contains(t))||this.sortController.progressSort(this.params.column,!1,"uiColumnSorted")};this.addManagedListeners(t,{tap:e})}if(this.params.enableFilterButton){const e=new Ur(this.eFilterButton,!0);this.addManagedListeners(e,{tap:()=>this.params.showFilter(this.eFilterButton)}),this.addDestroyFunc((()=>e.destroy()))}this.addDestroyFunc((()=>t.destroy())),r&&this.addDestroyFunc((()=>n.destroy()))}workOutShowMenu(){return this.params.enableMenu&&this.menuService.isHeaderMenuButtonEnabled()}shouldSuppressMenuHide(){return this.menuService.isHeaderMenuButtonAlwaysShowEnabled()}setMenu(){if(!this.eMenu)return;if(this.currentShowMenu=this.workOutShowMenu(),!this.currentShowMenu)return fr(this.eMenu),void(this.eMenu=void 0);const e=this.menuService.isLegacyMenuEnabled();this.addInIcon(e?"menu":"menuAlt",this.eMenu,this.params.column),this.eMenu.classList.toggle("ag-header-menu-icon",!e),this.currentSuppressMenuHide=this.shouldSuppressMenuHide(),this.addManagedElementListeners(this.eMenu,{click:()=>this.params.showColumnMenu(this.eMenu)}),this.eMenu.classList.toggle("ag-header-menu-always-show",this.currentSuppressMenuHide)}onMenuKeyboardShortcut(e){const t=this.params.column,a=this.menuService.isLegacyMenuEnabled();if(e&&!a){if(this.menuService.isFilterMenuInHeaderEnabled(t))return this.params.showFilter(this.eFilterButton??this.eMenu??this.getGui()),!0}else if(this.params.enableMenu)return this.params.showColumnMenu(this.eMenu??this.eFilterButton??this.getGui()),!0;return!1}workOutSort(){return this.params.enableSorting}setupSort(){if(this.currentSort=this.params.enableSorting,this.eSortIndicator||(this.eSortIndicator=this.createBean(new Qr(!0)),this.eSortIndicator.attachCustomElements(this.eSortOrder,this.eSortAsc,this.eSortDesc,this.eSortMixed,this.eSortNone)),this.eSortIndicator.setupSort(this.params.column),!this.currentSort)return;this.addManagedListeners(this.params.column,{movingChanged:()=>{this.lastMovingChanged=(new Date).getTime()}}),this.eLabel&&this.addManagedElementListeners(this.eLabel,{click:e=>{const t=this.params.column.isMoving(),a=(new Date).getTime()-this.lastMovingChanged<50;if(!(t||a)){const t="ctrl"===this.gos.get("multiSortKey")?e.ctrlKey||e.metaKey:e.shiftKey;this.params.progressSort(t)}}});const e=()=>{const e=this.params.column.getSort();if(this.addOrRemoveCssClass("ag-header-cell-sorted-asc","asc"===e),this.addOrRemoveCssClass("ag-header-cell-sorted-desc","desc"===e),this.addOrRemoveCssClass("ag-header-cell-sorted-none",!e),this.params.column.getColDef().showRowGroup){const e=this.funcColsService.getSourceColumnsForGroupColumn(this.params.column),t=e?.every((e=>this.params.column.getSort()==e.getSort())),a=!t;this.addOrRemoveCssClass("ag-header-cell-sorted-mixed",a)}};this.addManagedEventListeners({sortChanged:e,columnRowGroupChanged:e})}setupFilterIcon(){this.eFilter&&this.configureFilter(this.params.enableFilterIcon,this.eFilter,this.onFilterChangedIcon.bind(this))}setupFilterButton(){if(!this.eFilterButton)return;this.configureFilter(this.params.enableFilterButton,this.eFilterButton,this.onFilterChangedButton.bind(this))?this.addManagedElementListeners(this.eFilterButton,{click:()=>this.params.showFilter(this.eFilterButton)}):this.eFilterButton=void 0}configureFilter(e,t,a){if(!e)return fr(t),!1;const r=this.params.column;return this.addInIcon("filter",t,r),this.addManagedListeners(r,{filterChanged:a}),a(),!0}onFilterChangedIcon(){const e=this.params.column.isFilterActive();rr(this.eFilter,e,{skipAriaHidden:!0})}onFilterChangedButton(){const e=this.params.column.isFilterActive();this.eFilterButton.classList.toggle("ag-filter-active",e)}getAnchorElementForMenu(e){return e?this.eFilterButton??this.eMenu??this.getGui():this.eMenu??this.eFilterButton??this.getGui()}},Jr=class extends Gr{constructor(){super(''),this.agOpened=Nr,this.agClosed=Nr,this.agLabel=Nr}wireBeans(e){this.columnModel=e.columnModel}destroy(){super.destroy()}init(e){this.params=e,this.checkWarnings(),this.setupLabel(),this.addGroupExpandIcon(),this.setupExpandIcons()}checkWarnings(){this.params.template&&p("A template was provided for Header Group Comp - templates are only supported for Header Comps (not groups)")}setupExpandIcons(){this.addInIcon("columnGroupOpened",this.agOpened),this.addInIcon("columnGroupClosed",this.agClosed);const e=e=>{if(Ve(e))return;const t=!this.params.columnGroup.isExpanded();this.columnModel.setColumnGroupOpened(this.params.columnGroup.getProvidedColumnGroup(),t,"uiColumnExpanded")};this.addTouchAndClickListeners(this.agClosed,e),this.addTouchAndClickListeners(this.agOpened,e);const t=e=>{qe(e)};this.addManagedElementListeners(this.agClosed,{dblclick:t}),this.addManagedElementListeners(this.agOpened,{dblclick:t}),this.addManagedElementListeners(this.getGui(),{dblclick:e}),this.updateIconVisibility();const a=this.params.columnGroup.getProvidedColumnGroup(),r=this.updateIconVisibility.bind(this);this.addManagedListeners(a,{expandedChanged:r,expandableChanged:r})}addTouchAndClickListeners(e,t){const a=new Ur(e,!0);this.addManagedListeners(a,{tap:t}),this.addDestroyFunc((()=>a.destroy())),this.addManagedElementListeners(e,{click:t})}updateIconVisibility(){if(this.params.columnGroup.isExpandable()){const e=this.params.columnGroup.isExpanded();rr(this.agOpened,e),rr(this.agClosed,!e)}else rr(this.agOpened,!1),rr(this.agClosed,!1)}addInIcon(e,t){const a=Vr(e,this.gos,null);a&&t.appendChild(a)}addGroupExpandIcon(){if(!this.params.columnGroup.isExpandable())return rr(this.agOpened,!1),void rr(this.agClosed,!1)}setupLabel(){const{displayName:e,columnGroup:t}=this.params;if(E(e)){const t=Ot(e,!0);this.agLabel.textContent=t}this.addOrRemoveCssClass("ag-sticky-label",!t.getColGroupDef()?.suppressStickyLabel)}},Xr=class extends Gr{constructor(){super(),this.refreshCount=0;const e=document.createElement("span"),t=document.createElement("span");t.setAttribute("class","ag-value-change-delta");const a=document.createElement("span");a.setAttribute("class","ag-value-change-value"),e.appendChild(t),e.appendChild(a),this.setTemplateFromElement(e)}wireBeans(e){this.filterManager=e.filterManager}init(e){this.eValue=this.queryForHtmlElement(".ag-value-change-value"),this.eDelta=this.queryForHtmlElement(".ag-value-change-delta"),this.refresh(e,!0)}showDelta(e,t){const a=Math.abs(t),r=e.formatValue(a),n=E(r)?r:a,o=t>=0;this.eDelta.textContent=o?"↑"+n:"↓"+n,this.eDelta.classList.toggle("ag-value-change-delta-up",o),this.eDelta.classList.toggle("ag-value-change-delta-down",!o)}setTimerToRemoveDelta(){this.refreshCount++;const e=this.refreshCount;this.getFrameworkOverrides().wrapIncoming((()=>{window.setTimeout((()=>{e===this.refreshCount&&this.hideDeltaValue()}),2e3)}))}hideDeltaValue(){this.eValue.classList.remove("ag-value-change-value-highlight"),vr(this.eDelta)}refresh(e,t=!1){const a=e.value;if(a===this.lastValue)return!1;if(E(e.valueFormatted)?this.eValue.textContent=e.valueFormatted:E(e.value)?this.eValue.textContent=a:vr(this.eValue),this.filterManager?.isSuppressFlashingCellsBecauseFiltering())return!1;if("number"==typeof a&&"number"==typeof this.lastValue){const t=a-this.lastValue;this.showDelta(e,t)}return this.lastValue&&this.eValue.classList.add("ag-value-change-value-highlight"),t||this.setTimerToRemoveDelta(),this.lastValue=a,!0}},$r=class extends Gr{constructor(){super(),this.refreshCount=0;const e=document.createElement("span"),t=document.createElement("span");t.setAttribute("class","ag-value-slide-current"),e.appendChild(t),this.setTemplateFromElement(e),this.eCurrent=this.queryForHtmlElement(".ag-value-slide-current")}wireBeans(e){this.filterManager=e.filterManager}init(e){this.refresh(e,!0)}addSlideAnimation(){this.refreshCount++;const e=this.refreshCount;this.ePrevious&&this.getGui().removeChild(this.ePrevious);const t=document.createElement("span");t.setAttribute("class","ag-value-slide-previous ag-value-slide-out"),this.ePrevious=t,this.ePrevious.textContent=this.eCurrent.textContent,this.getGui().insertBefore(this.ePrevious,this.eCurrent),this.getFrameworkOverrides().wrapIncoming((()=>{window.setTimeout((()=>{e===this.refreshCount&&this.ePrevious.classList.add("ag-value-slide-out-end")}),50),window.setTimeout((()=>{e===this.refreshCount&&(this.getGui().removeChild(this.ePrevious),this.ePrevious=null)}),3e3)}))}refresh(e,t=!1){let a=e.value;return R(a)&&(a=""),a!==this.lastValue&&(!this.filterManager?.isSuppressFlashingCellsBecauseFiltering()&&(t||this.addSlideAnimation(),this.lastValue=a,E(e.valueFormatted)?this.eCurrent.textContent=e.valueFormatted:E(e.value)?this.eCurrent.textContent=a:vr(this.eCurrent),!0))}},en=class{};en.BACKSPACE="Backspace",en.TAB="Tab",en.ENTER="Enter",en.ESCAPE="Escape",en.SPACE=" ",en.LEFT="ArrowLeft",en.UP="ArrowUp",en.RIGHT="ArrowRight",en.DOWN="ArrowDown",en.DELETE="Delete",en.F2="F2",en.PAGE_UP="PageUp",en.PAGE_DOWN="PageDown",en.PAGE_HOME="Home",en.PAGE_END="End",en.A="KeyA",en.C="KeyC",en.D="KeyD",en.V="KeyV",en.X="KeyX",en.Y="KeyY",en.Z="KeyZ";var tn=class extends Gr{constructor(e,t,a){super(t,a),this.labelSeparator="",this.labelAlignment="left",this.disabled=!1,this.label="",this.config=e||{}}postConstruct(){this.addCssClass("ag-labeled"),this.eLabel.classList.add("ag-label");const{labelSeparator:e,label:t,labelWidth:a,labelAlignment:r,disabled:n}=this.config;null!=n&&this.setDisabled(n),null!=e&&this.setLabelSeparator(e),null!=t&&this.setLabel(t),null!=a&&this.setLabelWidth(a),this.setLabelAlignment(r||this.labelAlignment),this.refreshLabel()}refreshLabel(){vr(this.eLabel),"string"==typeof this.label?this.eLabel.innerText=this.label+this.labelSeparator:this.label&&this.eLabel.appendChild(this.label),""===this.label?(rr(this.eLabel,!1),wa(this.eLabel,"presentation")):(rr(this.eLabel,!0),wa(this.eLabel,null))}setLabelSeparator(e){return this.labelSeparator===e||(this.labelSeparator=e,null!=this.label&&this.refreshLabel()),this}getLabelId(){return this.eLabel.id=this.eLabel.id||`ag-${this.getCompId()}-label`,this.eLabel.id}getLabel(){return this.label}setLabel(e){return this.label===e||(this.label=e,this.refreshLabel()),this}setLabelAlignment(e){const t=this.getGui().classList;return t.toggle("ag-label-align-left","left"===e),t.toggle("ag-label-align-right","right"===e),t.toggle("ag-label-align-top","top"===e),this}setLabelEllipsis(e){return this.eLabel.classList.toggle("ag-label-ellipsis",e),this}setLabelWidth(e){return null==this.label||xr(this.eLabel,e),this}setDisabled(e){e=!!e;const t=this.getGui();return or(t,e),t.classList.toggle("ag-disabled",e),this.disabled=e,this}isDisabled(){return!!this.disabled}},an=class extends tn{constructor(e,t,a,r){super(e,t,a),this.className=r}postConstruct(){super.postConstruct();const{width:e,value:t,onValueChange:a}=this.config;null!=e&&this.setWidth(e),null!=t&&this.setValue(t),null!=a&&this.onValueChange(a),this.className&&this.addCssClass(this.className),this.refreshAriaLabelledBy()}setLabel(e){return super.setLabel(e),this.refreshAriaLabelledBy(),this}refreshAriaLabelledBy(){const e=this.getAriaElement(),t=this.getLabelId(),a=this.getLabel();null==a||""==a||null!==e.getAttribute("aria-label")?ka(e,""):ka(e,t??"")}setAriaLabel(e){return ya(this.getAriaElement(),e),this.refreshAriaLabelledBy(),this}onValueChange(e){return this.addManagedListeners(this,{fieldValueChanged:()=>e(this.getValue())}),this}getWidth(){return this.getGui().clientWidth}setWidth(e){return zr(this.getGui(),e),this}getPreviousValue(){return this.previousValue}getValue(){return this.value}setValue(e,t){return this.value===e||(this.previousValue=this.value,this.value=e,t||this.dispatchLocalEvent({type:"fieldValueChanged"})),this}},rn=class extends an{constructor(e,t,a="text",r="input"){super(e,e?.template??`\n
\n
\n \n
`,[],t),this.inputType=a,this.displayFieldTag=r,this.eLabel=Nr,this.eWrapper=Nr,this.eInput=Nr}postConstruct(){super.postConstruct(),this.setInputType(),this.eLabel.classList.add(`${this.className}-label`),this.eWrapper.classList.add(`${this.className}-input-wrapper`),this.eInput.classList.add(`${this.className}-input`),this.addCssClass("ag-input-field"),this.eInput.id=this.eInput.id||`ag-${this.getCompId()}-input`;const{inputName:e,inputWidth:t}=this.config;null!=e&&this.setInputName(e),null!=t&&this.setInputWidth(t),this.addInputListeners(),this.activateTabIndex([this.eInput])}addInputListeners(){this.addManagedElementListeners(this.eInput,{input:e=>this.setValue(e.target.value)})}setInputType(){"input"===this.displayFieldTag&&this.eInput.setAttribute("type",this.inputType)}getInputElement(){return this.eInput}setInputWidth(e){return xr(this.eWrapper,e),this}setInputName(e){return this.getInputElement().setAttribute("name",e),this}getFocusableElement(){return this.eInput}setMaxLength(e){return this.eInput.maxLength=e,this}setInputPlaceholder(e){return Fr(this.eInput,"placeholder",e),this}setInputAriaLabel(e){return ya(this.eInput,e),this.refreshAriaLabelledBy(),this}setDisabled(e){return or(this.eInput,e),super.setDisabled(e)}setAutoComplete(e){if(!0===e)Fr(this.eInput,"autocomplete",null);else{const t="string"==typeof e?e:"off";Fr(this.eInput,"autocomplete",t)}return this}},nn=class extends rn{constructor(e,t="ag-checkbox",a="checkbox"){super(e,t,a),this.labelAlignment="right",this.selected=!1,this.readOnly=!1,this.passive=!1}postConstruct(){super.postConstruct();const{readOnly:e,passive:t}=this.config;"boolean"==typeof e&&this.setReadOnly(e),"boolean"==typeof t&&this.setPassive(t)}addInputListeners(){this.addManagedElementListeners(this.eInput,{click:this.onCheckboxClick.bind(this)}),this.addManagedElementListeners(this.eLabel,{click:this.toggle.bind(this)})}getNextValue(){return void 0===this.selected||!this.selected}setPassive(e){this.passive=e}isReadOnly(){return this.readOnly}setReadOnly(e){this.eWrapper.classList.toggle("ag-disabled",e),this.eInput.disabled=e,this.readOnly=e}setDisabled(e){return this.eWrapper.classList.toggle("ag-disabled",e),super.setDisabled(e)}toggle(){if(this.eInput.disabled)return;const e=this.isSelected(),t=this.getNextValue();this.passive?this.dispatchChange(t,e):this.setValue(t)}getValue(){return this.isSelected()}setValue(e,t){return this.refreshSelectedClass(e),this.setSelected(e,t),this}setName(e){return this.getInputElement().name=e,this}isSelected(){return this.selected}setSelected(e,t){this.isSelected()!==e&&(this.previousValue=this.isSelected(),e=this.selected="boolean"==typeof e?e:void 0,this.eInput.checked=e,this.eInput.indeterminate=void 0===e,t||this.dispatchChange(this.selected,this.previousValue))}dispatchChange(e,t,a){this.dispatchLocalEvent({type:"fieldValueChanged",selected:e,previousValue:t,event:a});const r=this.getInputElement();this.eventService.dispatchEvent({type:"checkboxChanged",id:r.id,name:r.name,selected:e,previousValue:t})}onCheckboxClick(e){if(this.passive||this.eInput.disabled)return;const t=this.isSelected(),a=this.selected=e.target.checked;this.refreshSelectedClass(a),this.dispatchChange(a,t,e)}refreshSelectedClass(e){this.eWrapper.classList.toggle("ag-checked",!0===e),this.eWrapper.classList.toggle("ag-indeterminate",null==e)}},on={selector:"AG-CHECKBOX",component:nn},sn=class extends Gr{constructor(){super('\n ',[on]),this.eCheckbox=Nr}init(e){this.refresh(e);const t=this.eCheckbox.getInputElement();t.setAttribute("tabindex","-1"),xa(t,"polite"),this.addManagedListeners(t,{click:e=>{if(qe(e),this.eCheckbox.isDisabled())return;const t=this.eCheckbox.getValue();this.onCheckboxChanged(t)},dblclick:e=>{qe(e)}}),this.addManagedElementListeners(this.params.eGridCell,{keydown:e=>{if(e.key===en.SPACE&&!this.eCheckbox.isDisabled()){this.params.eGridCell===J(this.gos)&&this.eCheckbox.toggle();const t=this.eCheckbox.getValue();this.onCheckboxChanged(t),e.preventDefault()}}})}refresh(e){return this.params=e,this.updateCheckbox(e),!0}updateCheckbox(e){let t,a=!0;if(e.node.group&&e.column)if("boolean"==typeof e.value)t=e.value;else{const r=e.column.getColId();r.startsWith(Je)?t=null==e.value||""===e.value?void 0:"true"===e.value:e.node.aggData&&void 0!==e.node.aggData[r]?t=e.value??void 0:a=!1}else t=e.value??void 0;if(!a)return void this.eCheckbox.setDisplayed(!1);this.eCheckbox.setValue(t);const r=null!=e.disabled?e.disabled:!e.column?.isCellEditable(e.node);this.eCheckbox.setDisabled(r);const n=this.localeService.getLocaleTextFunc(),o=Wa(n,t),i=r?o:`${n("ariaToggleCellValue","Press SPACE to toggle cell value")} (${o})`;this.eCheckbox.setInputAriaLabel(i)}onCheckboxChanged(e){const{column:t,node:a,value:r}=this.params;this.eventService.dispatchEvent({type:"cellEditingStarted",column:t,colDef:t?.getColDef(),data:a.data,node:a,rowIndex:a.rowIndex,rowPinned:a.rowPinned,value:r});const n=this.params.node.setDataValue(this.params.column,e,"edit");this.eventService.dispatchEvent({type:"cellEditingStopped",column:t,colDef:t?.getColDef(),data:a.data,node:a,rowIndex:a.rowIndex,rowPinned:a.rowPinned,value:r,oldValue:r,newValue:e,valueChanged:n}),n||this.updateCheckbox(this.params)}},ln=class extends Gr{constructor(){super('
\n \n \n
'),this.eLoadingIcon=Nr,this.eLoadingText=Nr}init(e){e.node.failedLoad?this.setupFailed():this.setupLoading()}setupFailed(){const e=this.localeService.getLocaleTextFunc();this.eLoadingText.innerText=e("loadingError","ERR")}setupLoading(){const e=Vr("groupLoading",this.gos,null);e&&this.eLoadingIcon.appendChild(e);const t=this.localeService.getLocaleTextFunc();this.eLoadingText.innerText=t("loadingOoo","Loading")}refresh(e){return!1}destroy(){super.destroy()}},gn=class extends Gr{constructor(){super('
')}init(e){const t=`ag-cell-skeleton-renderer-${this.getCompId()}`;this.getGui().setAttribute("id",t),this.addDestroyFunc((()=>ka(e.eParentOfValue))),ka(e.eParentOfValue,t),e.node.failedLoad?this.setupFailed():this.setupLoading(e)}setupFailed(){const e=this.localeService.getLocaleTextFunc();this.getGui().innerText=e("loadingError","ERR");const t=e("ariaSkeletonCellLoadingFailed","Row failed to load");ya(this.getGui(),t)}setupLoading(e){const t=Q(this.gos).createElement("div");t.classList.add("ag-skeleton-effect");const a=e.node.rowIndex;if(null!=a){const e=75+25*(a%2==0?Math.sin(a):Math.cos(a));t.style.width=`${e}%`}this.getGui().appendChild(t);const r=this.localeService.getLocaleTextFunc()("ariaSkeletonCellLoading","Row data is loading");ya(this.getGui(),r)}refresh(e){return!1}destroy(){super.destroy()}},cn=class extends Gr{constructor(){super()}destroy(){super.destroy()}},dn=class extends cn{init(){const e=z(this.gos.get("overlayLoadingTemplate")?.trim());if(this.setTemplate(e??''),!e){const e=this.localeService.getLocaleTextFunc();setTimeout((()=>{this.getGui().textContent=e("loadingOoo","Loading...")}))}}},hn=class extends cn{init(){const e=z(this.gos.get("overlayNoRowsTemplate")?.trim());if(this.setTemplate(e??''),!e){const e=this.localeService.getLocaleTextFunc();setTimeout((()=>{this.getGui().textContent=e("noRowsToShow","No Rows To Show")}))}}},un=class extends Gr{isPopup(){return!0}setParentComponent(e){e.addCssClass("ag-has-popup"),super.setParentComponent(e)}destroy(){const e=this.parentComponent;e&&e.isAlive()&&e.getGui().classList.remove("ag-has-popup"),super.destroy()}},pn=class extends un{constructor(){super('
')}init(e){const{value:t}=e;this.getGui().textContent=Ot(t,!0)}};function mn(e){const{inputValue:t,allSuggestions:a,hideIrrelevant:r,filterByPercentageOfBestMatch:n,addSequentialWeight:o}=e;let i=a.map(((e,a)=>({value:e,relevance:vn(t.toLowerCase(),e.toLocaleLowerCase(),o),idx:a})));if(i.sort(((e,t)=>t.relevance-e.relevance)),r&&(i=i.filter((e=>0!==e.relevance))),i.length>0&&n&&n>0){const e=i[0].relevance*n;i=i.filter((t=>e-t.relevance<0))}const s=[],l=[];for(const e of i)s.push(e.value),l.push(e.idx);return{values:s,indices:l}}function vn(e,t,a=!1){const r=e.replace(/\s/g,""),n=t.replace(/\s/g,""),o=r.length,i=n.length,s=new Array(o+1).fill(null).map((()=>new Array(i+1).fill(0)));for(let e=0;e<=o;e+=1)s[e][0]=e;for(let e=0;e<=i;e+=1)s[0][e]=e;for(let e=1;e<=o;e++)for(let t=1;t<=i;t++)r[e-1]===n[t-1]?s[e][t]=s[e-1][t-1]:s[e][t]=1+Math.min(s[e][t-1],Math.min(s[e-1][t],s[e-1][t-1]));const l=s[o][i];let g=Math.max(o,i)-l;if(a){const e=function(e){const t=[],a=e.length;for(let r=1;r<=a;r++)for(let n=0;n<=a-r;n++){const a=n+r-1;t.push(e.slice(n,a+1))}return t}(r);for(let t=0;tthis.registerJsComponent(e,t)))}registerDefaultComponent(e,t,a){this.agGridDefaults[e]=t,a&&(this.agGridDefaultParams[e]=a)}registerJsComponent(e,t){this.jsComps[e]=t}retrieve(e,t){const a=(e,t,a)=>({componentFromFramework:t,component:e,params:a}),r=this.getFrameworkOverrides().frameworkComponent(t,this.gos.get("components"));if(null!=r)return a(r,!0);const n=this.jsComps[t];if(n){return a(n,this.getFrameworkOverrides().isFrameworkComponent(n))}const o=this.agGridDefaults[t];if(o)return a(o,!1,this.agGridDefaultParams[t]);const i=this.enterpriseAgDefaultCompsModule[t];return i?this.gos.assertModuleRegistered(i,`AG Grid '${e}' component: ${t}`):h((()=>{this.warnAboutMissingComponent(e,t)}),"MissingComp"+t),null}warnAboutMissingComponent(e,t){const a=mn({inputValue:t,allSuggestions:[...Object.keys(this.agGridDefaults).filter((e=>!["agCellEditor","agGroupRowRenderer","agSortIndicator"].includes(e))),...Object.keys(this.jsComps)],hideIrrelevant:!0,filterByPercentageOfBestMatch:.8}).values;p(`Could not find '${t}' component. It was configured as "${e}: '${t}'" but it wasn't found in the list of registered components.`),a.length>0&&p(` Did you mean: [${a.slice(0,3)}]?`),p(`If using a custom component check it has been registered as described in: ${this.getFrameworkOverrides().getDocLink("components/")}`)}},bn=class e{constructor(e){this.status=0,this.resolution=null,this.waiters=[],e((e=>this.onDone(e)),(e=>this.onReject(e)))}static all(t){return t.length?new e((e=>{let a=t.length;const r=new Array(a);t.forEach(((t,n)=>{t.then((t=>{r[n]=t,a--,0===a&&e(r)}))}))})):e.resolve()}static resolve(t=null){return new e((e=>e(t)))}then(t){return new e((e=>{1===this.status?e(t(this.resolution)):this.waiters.push((a=>e(t(a))))}))}onDone(e){this.status=1,this.resolution=e,this.waiters.forEach((t=>t(e)))}onReject(e){}},wn={propertyName:"dateComponent",cellRenderer:!1},Cn={propertyName:"dragAndDropImageComponent",cellRenderer:!1},yn={propertyName:"headerComponent",cellRenderer:!1},kn={propertyName:"headerGroupComponent",cellRenderer:!1},Sn={propertyName:"cellRenderer",cellRenderer:!0},xn={propertyName:"cellRenderer",cellRenderer:!1},zn={propertyName:"loadingCellRenderer",cellRenderer:!0},En={propertyName:"cellEditor",cellRenderer:!1},Rn={propertyName:"innerRenderer",cellRenderer:!0},Mn={propertyName:"loadingOverlayComponent",cellRenderer:!1},Fn={propertyName:"noRowsOverlayComponent",cellRenderer:!1},Dn={propertyName:"tooltipComponent",cellRenderer:!1},An={propertyName:"filter",cellRenderer:!1},Pn={propertyName:"floatingFilterComponent",cellRenderer:!1},Tn={propertyName:"toolPanel",cellRenderer:!1},In={propertyName:"statusPanel",cellRenderer:!1},Ln={propertyName:"fullWidthCellRenderer",cellRenderer:!0},On={propertyName:"loadingCellRenderer",cellRenderer:!0},Nn={propertyName:"groupRowRenderer",cellRenderer:!0},Gn={propertyName:"detailCellRenderer",cellRenderer:!0},Bn={propertyName:"menuItem",cellRenderer:!1},Hn=class e extends Ke{constructor(){super(...arguments),this.beanName="userComponentFactory"}wireBeans(e){this.agComponentUtils=e.agComponentUtils,this.componentMetadataProvider=e.componentMetadataProvider,this.userComponentRegistry=e.userComponentRegistry,this.frameworkComponentWrapper=e.frameworkComponentWrapper,this.gridOptions=e.gridOptions}getDragAndDropImageCompDetails(e){return this.getCompDetails(this.gridOptions,Cn,"agDragAndDropImage",e,!0)}getHeaderCompDetails(e,t){return this.getCompDetails(e,yn,"agColumnHeader",t)}getHeaderGroupCompDetails(e){const t=e.columnGroup.getColGroupDef();return this.getCompDetails(t,kn,"agColumnGroupHeader",e)}getFullWidthCellRendererDetails(e){return this.getCompDetails(this.gridOptions,Ln,null,e,!0)}getFullWidthLoadingCellRendererDetails(e){return this.getCompDetails(this.gridOptions,On,"agLoadingCellRenderer",e,!0)}getFullWidthGroupCellRendererDetails(e){return this.getCompDetails(this.gridOptions,Nn,"agGroupRowRenderer",e,!0)}getFullWidthDetailCellRendererDetails(e){return this.getCompDetails(this.gridOptions,Gn,"agDetailCellRenderer",e,!0)}getInnerRendererDetails(e,t){return this.getCompDetails(e,Rn,null,t)}getFullWidthGroupRowInnerCellRenderer(e,t){return this.getCompDetails(e,Rn,null,t)}getCellRendererDetails(e,t){return this.getCompDetails(e,Sn,null,t)}getEditorRendererDetails(e,t){return this.getCompDetails(e,xn,null,t)}getLoadingCellRendererDetails(e,t){return this.getCompDetails(e,zn,"agSkeletonCellRenderer",t,!0)}getCellEditorDetails(e,t){return this.getCompDetails(e,En,"agCellEditor",t,!0)}getFilterDetails(e,t,a){return this.getCompDetails(e,An,a,t,!0)}getDateCompDetails(e){return this.getCompDetails(this.gridOptions,wn,"agDateInput",e,!0)}getLoadingOverlayCompDetails(e){return this.getCompDetails(this.gridOptions,Mn,"agLoadingOverlay",e,!0)}getNoRowsOverlayCompDetails(e){return this.getCompDetails(this.gridOptions,Fn,"agNoRowsOverlay",e,!0)}getTooltipCompDetails(e){return this.getCompDetails(e.colDef,Dn,"agTooltipComponent",e,!0)}getSetFilterCellRendererDetails(e,t){return this.getCompDetails(e,Sn,null,t)}getFloatingFilterCompDetails(e,t,a){return this.getCompDetails(e,Pn,a,t)}getToolPanelCompDetails(e,t){return this.getCompDetails(e,Tn,null,t,!0)}getStatusPanelCompDetails(e,t){return this.getCompDetails(e,In,null,t,!0)}getMenuItemCompDetails(e,t){return this.getCompDetails(e,Bn,"agMenuItem",t,!0)}getCompDetails(t,a,r,n,o=!1){const{propertyName:i,cellRenderer:s}=a;let l,{compName:g,jsComp:c,fwComp:d,paramsFromSelector:h,popupFromSelector:u,popupPositionFromSelector:p}=e.getCompKeys(this.frameworkOverrides,t,a,n);const v=e=>{const t=this.userComponentRegistry.retrieve(i,e);t&&(c=t.componentFromFramework?void 0:t.component,d=t.componentFromFramework?t.component:void 0,l=t.params)};if(null!=g&&v(g),null==c&&null==d&&null!=r&&v(r),c&&s&&!this.agComponentUtils.doesImplementIComponent(c)&&(c=this.agComponentUtils.adaptFunction(i,c)),!c&&!d)return void(o&&m(`Could not find component ${g}, did you forget to configure this component?`));const f=this.mergeParamsWithApplicationProvidedParams(t,a,n,h,l),b=null==c,w=c||d;return{componentFromFramework:b,componentClass:w,params:f,type:a,popupFromSelector:u,popupPositionFromSelector:p,newAgStackInstance:()=>this.newAgStackInstance(w,b,f,a)}}static getCompKeys(e,t,a,r){const{propertyName:n}=a;let o,i,s,l,g,c;if(t){const a=t,d=a[n+"Selector"],h=d?d(r):null,u=t=>{if("string"==typeof t)o=t;else if(null!=t&&!0!==t){e.isFrameworkComponent(t)?s=t:i=t}};h?(u(h.component),l=h.params,g=h.popup,c=h.popupPosition):u(a[n])}return{compName:o,jsComp:i,fwComp:s,paramsFromSelector:l,popupFromSelector:g,popupPositionFromSelector:c}}newAgStackInstance(e,t,a,r){const n=r.propertyName;let o;if(!t)o=new e;else{const t=this.componentMetadataProvider.retrieve(n);o=this.frameworkComponentWrapper.wrap(e,t.mandatoryMethodList,t.optionalMethodList,r)}const i=this.initComponent(o,a);return null==i?bn.resolve(o):i.then((()=>o))}mergeParamsWithApplicationProvidedParams(e,t,a,r=null,n){const o=this.gos.getGridCommonParams();ct(o,a),n&&ct(o,n);const i=e&&e[t.propertyName+"Params"];if("function"==typeof i){ct(o,i(a))}else"object"==typeof i&&ct(o,i);return ct(o,r),o}initComponent(e,t){if(this.createBean(e),null!=e.init)return e.init(t)}};function qn(e){const t=e;return null!=t&&null!=t.getFrameworkComponentInstance?t.getFrameworkComponentInstance():e}var Vn=class e{static register(t){e.__register(t,!0,void 0)}static registerModules(t){e.__registerModules(t,!0,void 0)}static __register(t,a,r){e.runVersionChecks(t),void 0!==r?(e.areGridScopedModules=!0,void 0===e.gridModulesMap[r]&&(e.gridModulesMap[r]={}),e.gridModulesMap[r][t.moduleName]=t):e.globalModulesMap[t.moduleName]=t,e.setModuleBased(a)}static __unRegisterGridModules(t){delete e.gridModulesMap[t]}static __registerModules(t,a,r){e.setModuleBased(a),t&&t.forEach((t=>e.__register(t,a,r)))}static isValidModuleVersion(t){const[a,r]=t.version.split(".")||[],[n,o]=e.currentModuleVersion.split(".")||[];return a===n&&r===o}static runVersionChecks(t){e.currentModuleVersion||(e.currentModuleVersion=t.version);const a=e=>`You are using incompatible versions of AG Grid modules. Major and minor versions should always match across modules. ${e} Please update all modules to the same version.`;if(t.version?e.isValidModuleVersion(t)||m(a(`'${t.moduleName}' is version ${t.version} but the other modules are version ${e.currentModuleVersion}.`)):m(a(`'${t.moduleName}' is incompatible.`)),t.validate){const e=t.validate();if(!e.isValid){m(`${e.message}`)}}}static setModuleBased(t){void 0===e.moduleBased?e.moduleBased=t:e.moduleBased!==t&&(m("AG Grid: You are mixing modules (i.e. @ag-grid-community/core) and packages (ag-grid-community) - you can only use one or the other of these mechanisms."),m("Please see https://www.ag-grid.com/javascript-grid/modules/ for more information."))}static __setIsBundled(){e.isBundled=!0}static __assertRegistered(t,a,r){if(this.__isRegistered(t,r))return!0;let n;if(e.isBundled)n=`AG Grid: unable to use ${a} as 'ag-grid-enterprise' has not been loaded. Check you are using the Enterprise bundle:\n \n + + + + + + + + + + + + diff --git a/WebContent/jsp/common/include/script.jsp b/WebContent/jsp/common/include/script.jsp index 80ed3ec..1e96177 100644 --- a/WebContent/jsp/common/include/script.jsp +++ b/WebContent/jsp/common/include/script.jsp @@ -56,9 +56,12 @@ + + +"/> + + + diff --git a/WebContent/jsp/dashboard-modern/components/dashboard-adapter.jsp b/WebContent/jsp/dashboard-modern/components/dashboard-adapter.jsp index 7f7f86f..bc3761d 100644 --- a/WebContent/jsp/dashboard-modern/components/dashboard-adapter.jsp +++ b/WebContent/jsp/dashboard-modern/components/dashboard-adapter.jsp @@ -105,7 +105,7 @@ /* 어댑터 섹션 스타일 조정 */ .adapters-container .card { height: 100%; - min-height: 249px; + min-height: 293px; } .dashboard-adapter-summary { @@ -115,10 +115,15 @@ .adapter-summary-card { padding: 0; overflow: hidden; + display: flex; + flex-direction: column; } .adapter-summary-container { padding: 0.5rem 1rem; + flex-grow: 1; + display: flex; + flex-direction: column; } .adapter-summary-header { @@ -130,8 +135,10 @@ } .adapter-summary-body { - max-height: 300px; - overflow-y: auto; + flex-grow: 1; + display: flex; + flex-direction: column; + justify-content: space-around; /* 내용을 수직으로 균등 배분 */ } .adapter-summary-row { diff --git a/WebContent/jsp/dashboard-modern/components/dashboard-servers.jsp b/WebContent/jsp/dashboard-modern/components/dashboard-servers.jsp index 7edb471..f7d704e 100644 --- a/WebContent/jsp/dashboard-modern/components/dashboard-servers.jsp +++ b/WebContent/jsp/dashboard-modern/components/dashboard-servers.jsp @@ -481,10 +481,10 @@ border-top: 2px solid #fd7e14; } - /* 서버 그리드 - 한 줄에 3개씩 표시 */ + /* 서버 그리드 - 한 줄에 2개씩 표시 */ .server-grid { display: grid; - grid-template-columns: repeat(3, 1fr); + grid-template-columns: repeat(2, 1fr); gap: 0.75rem; } @@ -521,83 +521,130 @@ /* 메트릭 스타일 조정 */ .server-metrics { - padding: 0.3rem 0; + padding: 0.5rem 0; display: flex; flex-direction: column; gap: 0.0rem; + border-top: 1px solid var(--color-gray-200); + border-bottom: 1px solid var(--color-gray-200); } - .metrics-row { + .metrics-row, + .sparklines-row { display: flex; - justify-content: space-between; - gap: 0.5rem; + justify-content: space-around; + gap: 0; } .sparklines-row { - display: flex; - justify-content: space-between; - gap: 0.5rem; + padding-top: 0.5rem; } .server-metric { flex: 1; min-width: 0; /* 오버플로우 방지 */ + text-align: center; + padding: 0 0.5rem; + border-right: 1px solid var(--color-gray-200); + } + + .server-metric:last-child { + border-right: none; } .metric-header { margin-bottom: 0.2rem; display: flex; - justify-content: flex-start; - align-items: center; + flex-direction: column; /* 아이콘과 값을 수직으로 정렬 */ + justify-content: center; /* 수직 중앙 정렬 */ + align-items: center; /* 수평 중앙 정렬 */ white-space: nowrap; overflow: hidden; } .sparkline { flex: 1; - min-width: 0; /* 오버플로우 방지 */ + min-width: 0; + padding: 0.25rem 0.5rem; + border-right: 1px solid var(--color-gray-200); + } + + .sparkline:last-child { + border-right: none; } .metric-name { display: flex; align-items: center; + margin-bottom: 0.2rem; /* 아이콘과 값 사이 간격 조정 */ } .metric-value { - margin-left: 0.5rem; + margin-left: 0; /* 이전 margin-left 제거 */ + font-size: 1.1rem; /* 값 폰트 크기 약간 키움 */ + font-weight: 600; /* 값 폰트 굵기 */ } /* 인스턴스 테이블 스타일 */ .server-instances { - padding: 0.3rem 0; + padding: 0.5rem 0.5rem 0.5rem 0.5rem; /* 패딩 조정 */ max-height: 120px; } .instance-table { - font-size: 0.8rem; + width: 100%; /* 테이블 전체 너비 */ + font-size: 0.85rem; /* 폰트 크기 약간 키움 */ + border-collapse: collapse; + table-layout: fixed; + } + + .instance-table th { + background-color: var(--color-gray-100); /* 헤더 배경색 */ + font-weight: 600; /* 헤더 폰트 굵기 */ + padding: 0.4rem 0.5rem; /* 헤더 패딩 조정 */ + border-bottom: 1px solid var(--color-gray-200); /* 헤더 아래 보더 */ + text-align: center; /* 모든 헤더 가운데 정렬 */ + + width: 33.3333%; /* 인스턴스 th 각 영역 균등하게, 위쪽 요소와 맞춤 */ + box-sizing: border-box; } - .instance-table th, .instance-table td { - padding: 0.2rem 0.3rem; + padding: 0.4rem 0.5rem; /* 셀 패딩 조정 */ white-space: nowrap; + border-bottom: 1px solid var(--color-gray-100); /* 셀 아래 보더 */ + vertical-align: middle; + text-align: center; /* 모든 셀 내용 가운데 정렬 */ + white-space: nowrap; /* 줄바꿈 방지 */ + overflow: hidden; /* 칸을 넘어가는 글자 숨김 */ + text-overflow: ellipsis; /* 넘어가는 글자를 ...으로 표시 */ + } + + .instance-table tbody tr:nth-child(odd) { + background-color: var(--color-gray-50); /* 홀수 행 배경색 */ + } + + .instance-table tbody tr:hover { + background-color: var(--color-gray-100); /* 호버 효과 */ } /* 인스턴스 상태 점 스타일 */ .instance-status { display: inline-block; - width: 8px; - height: 8px; + width: 10px; /* 크기 키움 */ + height: 10px; /* 크기 키움 */ border-radius: 50%; - margin-right: 4px; + margin-right: 6px; /* 간격 조정 */ + vertical-align: middle; /* 텍스트와 세로 정렬 */ } - /* 오류 상태일 때 인스턴스 ID 색상 변경 */ + /* 오류 상태일 때 인스턴스 ID 색상 변경 - :has()는 구형 브라우저에서 문제를 일으킬 수 있어 주석 처리 tr:has(.status-error) .instance-id, tr:has(.status-inactive) .instance-id { color: #dc3545; font-weight: 600; } + */ + + + + }); + } +
diff --git a/WebContent/jsp/onl/admin/authserver/clientManDetail.jsp b/WebContent/jsp/onl/admin/authserver/clientManDetail.jsp index bd7b237..33dd4d8 100644 --- a/WebContent/jsp/onl/admin/authserver/clientManDetail.jsp +++ b/WebContent/jsp/onl/admin/authserver/clientManDetail.jsp @@ -133,14 +133,17 @@ var selectedApiGrid = $("#selectedApiGrid"); selectedApiGrid.jqGrid({ datatype: "local", + gridview: true, // 성능 향상 + rownumbers: true, // 가상 인덱스 + rownumWidth: 40, colNames: ['API ID', 'API 이름'], colModel: [ {name: 'apiId', index: 'apiId', width: 100, align: 'center'}, {name: 'apiDesc', index: 'apiDesc', width: 100, align: 'center'} ], - rowNum: 10, - rowList: [10, 20, 30], - pager: '#selectedApiGridPager', + rowNum: -1, +// rowList: [10, 20, 30], +// pager: '#selectedApiGridPager', sortname: 'apiId', viewrecords: true, sortorder: "asc", @@ -152,6 +155,9 @@ // selectedApiGrid.jqGrid('delRowData', rowid); } }); + + selectedApiGrid.jqGrid('setLabel', 'rn', 'No'); // 가상 인덱스 ColNames + $.ajax({ type: "POST", url: url, @@ -293,21 +299,30 @@ } var rows = $("#selectedApiGrid").jqGrid('getRowData'); - console.log(returnValue); - const apiId = returnValue.apiId; - var apiIdExists = rows.some(function (row) { - return row.apiId === apiId; - }); + var existingApiIds = new Set(rows.map(function(row) { + return String(row.apiId); + })); + + var newItems = Array.isArray(returnValue) ? returnValue : [returnValue]; - if (apiIdExists) { - return; - } var ids = $("#selectedApiGrid").jqGrid('getDataIDs'); - var lastId = ids.length > 0 ? Math.max.apply(Math, ids) : 0; - var newId = lastId + 1; - $("#selectedApiGrid").jqGrid('addRowData', newId, returnValue, "last"); - }); + var currentIdSeq = ids.length > 0 ? Math.max(...ids.map(Number)) : 0; + + $.each(newItems, function(index, item) { + var checkId = String(item.apiId); + + if (!existingApiIds.has(checkId)) { + currentIdSeq++; + $("#selectedApiGrid").jqGrid('addRowData', currentIdSeq, item, "last"); + + existingApiIds.add(checkId); + } else { + //console.log("중복 제외됨: " + item.apiId); + } + }); + alert("중복건 수를 제외한 API가 추가되었습니다."); + }); }); $("#btn_popup_api_group").click(function () { @@ -470,17 +485,17 @@ REDIRECT URI - + <%= localeMessage.getString("clntManDtl.athrt") %> (<%= localeMessage.getString("clntManDtl.sprt") %> ,) - + - + <%= localeMessage.getString("clntManDtl.rsrc") %> (<%= localeMessage.getString("clntManDtl.sprt") %> ,) - + <%= localeMessage.getString("clntManDtl.tknExprtnDt") %> @@ -492,11 +507,11 @@ - + <%= localeMessage.getString("clntManDtl.atmtcApprvlScp") %> (<%= localeMessage.getString("clntManDtl.sprt") %> ,) - + <%= localeMessage.getString("clntManDtl.allwIp") %> @@ -528,11 +543,11 @@
- + 토큰 발급 제한건수(24시간)
- +
diff --git a/WebContent/jsp/onl/admin/authserver/scopeManDetail.jsp b/WebContent/jsp/onl/admin/authserver/scopeManDetail.jsp index 1de52b6..505483e 100644 --- a/WebContent/jsp/onl/admin/authserver/scopeManDetail.jsp +++ b/WebContent/jsp/onl/admin/authserver/scopeManDetail.jsp @@ -18,6 +18,8 @@ @@ -161,7 +332,25 @@ $(document).ready(function() { - + +
+
+
API
+
+ + +
+
+
+
+
+
+
+
diff --git a/WebContent/jsp/onl/admin/common/propertyManDetail.jsp b/WebContent/jsp/onl/admin/common/propertyManDetail.jsp index 8dff400..582d220 100644 --- a/WebContent/jsp/onl/admin/common/propertyManDetail.jsp +++ b/WebContent/jsp/onl/admin/common/propertyManDetail.jsp @@ -193,6 +193,10 @@ var url_view = ''; }); }); $("#btn_delete").click(function() { + if(!confirm("삭제하시겠습니까?")){ + return; + } + var postData = $('#ajaxForm').serializeArray(); postData.push({ name : "cmd", diff --git a/WebContent/jsp/onl/admin/httpouttlsinfo/httpOutTlsInfoManDetail.jsp b/WebContent/jsp/onl/admin/httpouttlsinfo/httpOutTlsInfoManDetail.jsp index 5376062..187304e 100644 --- a/WebContent/jsp/onl/admin/httpouttlsinfo/httpOutTlsInfoManDetail.jsp +++ b/WebContent/jsp/onl/admin/httpouttlsinfo/httpOutTlsInfoManDetail.jsp @@ -234,11 +234,6 @@ var postData = $('#ajaxForm').serializeArray(); $clientId.prop('disabled', true); - - var key = "${param.clientId}"; - - //client 를 먼저 삭제 한 경우를 위해 mTls info 삭제 가능하도록 함. - postData.push({name: "clientId", value: key}); postData.push({name: "cmd", value: "DELETE"}); diff --git a/WebContent/jsp/onl/admin/inflow/fragments/inflowGroupDetailStyle.jsp b/WebContent/jsp/onl/admin/inflow/fragments/inflowGroupDetailStyle.jsp new file mode 100644 index 0000000..ef69886 --- /dev/null +++ b/WebContent/jsp/onl/admin/inflow/fragments/inflowGroupDetailStyle.jsp @@ -0,0 +1,191 @@ +<%@ page language="java" contentType="text/html; charset=utf-8"%> + + diff --git a/WebContent/jsp/onl/admin/inflow/fragments/inflowGroupHelpModal.jsp b/WebContent/jsp/onl/admin/inflow/fragments/inflowGroupHelpModal.jsp new file mode 100644 index 0000000..e7415b3 --- /dev/null +++ b/WebContent/jsp/onl/admin/inflow/fragments/inflowGroupHelpModal.jsp @@ -0,0 +1,89 @@ +<%@ page language="java" contentType="text/html; charset=utf-8"%> + +
+
+
+

유량제어 임계치 설명

+ +
+
+

개요

+

유량제어는 Token Bucket 알고리즘 기반으로 동작합니다. + 설정된 임계치만큼 토큰이 균등하게 리필되며, 요청 1건당 토큰 1개를 소비합니다. + 토큰이 부족하면 요청이 거부됩니다.

+ +

토큰 리필 방식 - Greedy (균등 리필)

+

토큰은 설정된 시간이 지난 후 한꺼번에 리필되는 것이 아니라, 매 순간 균등하게 리필됩니다.

+
+ 왜 균등 리필인가?
+ 한꺼번에 리필하면 리필 직후 트래픽이 몰려 백엔드에 부하가 집중됩니다. + 균등 리필은 트래픽을 시간축에 고르게 분산시켜 시스템 안정성을 높입니다. + AWS, Google Cloud, Nginx 등 대부분의 Rate Limiter가 이 방식을 채택합니다. +
+
+
예시: 분당 120건 설정 시 리필 속도
+
    +
  • 120건 ÷ 60초 = 초당 2건씩 균등 리필
  • +
  • 1초마다 2개의 토큰이 자동으로 채워짐
  • +
+
+
+
동작 예시: 분당 120건, 10건 요청 처리 후
+ + + + + + + + + +
시간상황토큰 수
10:00:00초기 상태 (만땅)120개
10:00:01요청 10건 처리110개
10:00:021초 경과 → 2개 리필112개
10:00:031초 경과 → 2개 리필114개
10:00:041초 경과 → 2개 리필116개
10:00:051초 경과 → 2개 리필118개
10:00:061초 경과 → 2개 리필 (복구 완료)120개
+

+ ※ 10건 소비 후 5초만에 복구됨 (10건 ÷ 초당2건 = 5초) +

+
+
+ 주의: "분당 120건"은 "1분 후에 120개가 한꺼번에 채워진다"는 의미가 아닙니다. + 매 초마다 2개씩 균등하게 리필되어, 결과적으로 1분 동안 최대 120건을 처리할 수 있다는 의미입니다. +
+ +

초당 임계치 (thresholdPerSecond)

+

1초 단위의 순간적인 트래픽 급증(spike)을 방어합니다.

+
+
예시: 초당 임계치 = 100
+
    +
  • 매 1초마다 토큰 100개 리필
  • +
  • 1초 내 100건까지 처리 가능
  • +
  • 101번째 요청부터 차단
  • +
+
+ +

추가 임계치 (threshold) + 시간단위

+

장시간 누적 트래픽을 제어합니다. 시간단위에 따라 리필 속도가 결정됩니다.

+ + + + + + +
시간단위설명리필 속도 예시 (120건 설정 시)
MIN초당 2건 (120÷60)
HOU초당 0.033건 (120÷3600)
DAY초당 0.00139건 (120÷86400)
MON초당 0.0000463건 (120÷2592000)
+ +

두 임계치 조합 (권장)

+

두 조건을 함께 설정하면 이중 보호가 적용됩니다.

+
+
예시: 초당 100건 + 시간당 50,000건
+
    +
  • 초당 임계치: 순간 폭주 방지 → 1초에 101건 이상 요청 시 차단
  • +
  • 추가 임계치: 장기 누적 제한 → 시간당 50,001건 이상 요청 시 차단
  • +
  • 두 조건 중 하나라도 초과 시 차단 (둘 다 충족해야 통과)
  • +
+
+ +
+ 참고: 그룹에 매핑된 인터페이스들은 개별 유량제어 대신 그룹 유량제어가 적용됩니다. + 그룹 내 모든 인터페이스의 호출이 합산되어 임계치와 비교됩니다. +
+
+
+
diff --git a/WebContent/jsp/onl/admin/inflow/fragments/inflowGroupInterfaceModal.jsp b/WebContent/jsp/onl/admin/inflow/fragments/inflowGroupInterfaceModal.jsp new file mode 100644 index 0000000..98aea33 --- /dev/null +++ b/WebContent/jsp/onl/admin/inflow/fragments/inflowGroupInterfaceModal.jsp @@ -0,0 +1,41 @@ +<%@ page language="java" contentType="text/html; charset=utf-8"%> + +
+
+
+

API 선택

+ +
+
+ +
+ + + + + + + + + + +
API ID설명
검색 결과가 없습니다
+
+
+ 0 +
+ +
+ +
+
+
+ +
+
diff --git a/WebContent/jsp/onl/admin/inflow/inflowControlHistoryMan.jsp b/WebContent/jsp/onl/admin/inflow/inflowControlHistoryMan.jsp index 9744828..c9a402c 100644 --- a/WebContent/jsp/onl/admin/inflow/inflowControlHistoryMan.jsp +++ b/WebContent/jsp/onl/admin/inflow/inflowControlHistoryMan.jsp @@ -31,6 +31,22 @@ function timeStampFormat2(cellvalue, options, rowObject){ } +function groupNameFormat(cellvalue, options, rowObject) { + if (!cellvalue) return ""; + + var strVal = cellvalue.toString().trim(); + + if (strVal.length === 36) { + // 그룹ID의 경우 GROUPNAME 으로 보여줌 + var groupName = rowObject.GROUPNAME; + + // 비어있다면 원래 값을 반환 + return groupName ? groupName : strVal; + } + + return strVal; +} + $(document).ready(function() { $("input[name=searchStartYYYYMMDD],input[name=searchEndYYYYMMDD]").inputmask("yyyy-mm-dd",{'autoUnmask':true}); @@ -66,10 +82,11 @@ $(document).ready(function() { '<%= localeMessage.getString("infConHstMan.eaiSvcSrlNum") %>', '<%= localeMessage.getString("infConHstMan.instNm") %>', '<%= localeMessage.getString("infConHstMan.eaiSvcCd") %>', - '<%= localeMessage.getString("infConHstMan.adpGrpNm") %>', + '그룹별 / <%= localeMessage.getString("infConHstMan.adpGrpNm") %>', '<%= localeMessage.getString("infAdpConMan.thrPerSecond") %>', '<%= localeMessage.getString("infAdpConMan.thr") %>', - '<%= localeMessage.getString("infAdpConMan.thrTimeUnit") %>' + '<%= localeMessage.getString("infAdpConMan.thrTimeUnit") %>', + '그룹별' ], colModel:[ { name : 'EAIBZWKDSTCD' , width:'50' , align:'center' , sortable:false }, @@ -77,10 +94,11 @@ $(document).ready(function() { { name : 'EAISVCSERNO' , width:'150', align:'left' }, { name : 'EAISEVRINSTNCNAME' , width:'50' , align:'center' }, { name : 'EAISVCNAME' , width:'70' , align:'center' }, - { name : 'ADPTRBZWKGROUPNAME' , width:'70' , align:'center' }, - { name : 'THRESHOLDPERSECOND' , width:'40' , align:'right' }, - { name : 'THRESHOLD' , width:'40' , align:'right' }, - { name : 'THRESHOLDTIMEUNIT' , width:'70' , align:'center' } + { name : 'ADPTRBZWKGROUPNAME' , width:'100' , align:'center' , formatter: groupNameFormat }, + { name : 'THRESHOLDPERSECOND' , width:'30' , align:'right' }, + { name : 'THRESHOLD' , width:'30' , align:'right' }, + { name : 'THRESHOLDTIMEUNIT' , width:'50' , align:'center' }, + { name : 'GROUPNAME' , width:'70' , hidden:true } ], jsonReader: { repeatitems:false diff --git a/WebContent/jsp/onl/admin/inflow/inflowGroupControlMan.jsp b/WebContent/jsp/onl/admin/inflow/inflowGroupControlMan.jsp new file mode 100644 index 0000000..04bf391 --- /dev/null +++ b/WebContent/jsp/onl/admin/inflow/inflowGroupControlMan.jsp @@ -0,0 +1,201 @@ +<%@ page language="java" contentType="text/html; charset=utf-8"%> +<%@ page import="java.io.*"%> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> +<%@ taglib uri="http://www.springframework.org/tags" prefix="spring"%> +<%@ include file="/jsp/common/include/localemessage.jsp" %> +<% + response.setHeader("Pragma", "No-cache"); + response.setHeader("Cache-Control", "no-cache"); + response.setHeader("Expires", "0"); + +%> + + + + + + + + + + +
+
+ +
+
+
+ + +
+
유량제어 그룹 관리유량제어 그룹 관리
+ + + + + + + + +
그룹명
+ +
+
+ +
+
+ + diff --git a/WebContent/jsp/onl/admin/inflow/inflowGroupControlManDetail.jsp b/WebContent/jsp/onl/admin/inflow/inflowGroupControlManDetail.jsp new file mode 100644 index 0000000..ea2ef28 --- /dev/null +++ b/WebContent/jsp/onl/admin/inflow/inflowGroupControlManDetail.jsp @@ -0,0 +1,932 @@ +<%@ page language="java" contentType="text/html; charset=utf-8"%> +<%@ page import="java.io.*"%> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> +<%@ taglib uri="http://www.springframework.org/tags" prefix="spring"%> +<%@ include file="/jsp/common/include/localemessage.jsp" %> +<% + response.setHeader("Pragma", "No-cache"); + response.setHeader("Cache-Control", "no-cache"); + response.setHeader("Expires", "0"); +%> + + + + + + + + + + + + + + +
+
+ +
+
+
+ + + + +
+
+
유량제어 그룹 관리유량제어 그룹 관리
+ +
+ + +
+ + +
+ + +
+
+ + + + + + + + + + + + + + + + + + + + + + + +
그룹명 *
사용여부 +
+ + 사용 +
+
+ + + + + + + + + + + + + + + + + + +
+ 초당 임계치 + help_outline + 1초 단위의 순간 트래픽 제한입니다. 매 초마다 설정한 건수만큼 토큰이 리필되며, 초과 시 요청이 거부됩니다. + + +
+ + req/sec +
+
+ 추가 임계치 + help_outline + 장시간 누적 트래픽 제한입니다. 시간단위를 선택하면 해당 주기마다 토큰이 리필됩니다. + + +
+ + + requests +
+
+ + + +
+ 매핑된 API 0개 (활성 0) + +
+
+ + + + + + + + + + + + + + + + + + +
API ID설명관리
inbox 매핑된 API가 없습니다
+
+
+
+ + +
+
+
+ 마지막 갱신: - + +
+ + + + +
+
+ hourglass_empty + 버킷 현황을 조회하려면 새로고침 버튼을 클릭하세요 +
+
+
+
+ +
+
+ + + + + + + + diff --git a/WebContent/jsp/onl/admin/rule/layoutMan.jsp b/WebContent/jsp/onl/admin/rule/layoutMan.jsp index 49cd45c..28eaa9e 100644 --- a/WebContent/jsp/onl/admin/rule/layoutMan.jsp +++ b/WebContent/jsp/onl/admin/rule/layoutMan.jsp @@ -313,6 +313,10 @@ $('#btn_import').click(onclickExcelImport); $("#btn_delete").click(function () { + if(!confirm("삭제하시겠습니까?")){ + return; + } + var id = "grid"; var delArr = new Array(); var ids = $("#grid").jqGrid('getDataIDs'); diff --git a/WebContent/jsp/onl/admin/rule/transform2/transform2Man.jsp b/WebContent/jsp/onl/admin/rule/transform2/transform2Man.jsp index 17b8eee..b0243a0 100644 --- a/WebContent/jsp/onl/admin/rule/transform2/transform2Man.jsp +++ b/WebContent/jsp/onl/admin/rule/transform2/transform2Man.jsp @@ -238,6 +238,10 @@ $(document).ready(function() { $('#btn_clone').click(onclickBtnClone); $("#btn_delete").click(function(){ + if(!confirm("삭제하시겠습니까?")){ + return; + } + var id = "grid"; var cnvsnNameArr = new Array(); var eaiSvcNameArr = new Array(); diff --git a/WebContent/jsp/onl/admin/rule/transform2/transform2ManDetail.jsp b/WebContent/jsp/onl/admin/rule/transform2/transform2ManDetail.jsp index aa0d607..ed96fd4 100644 --- a/WebContent/jsp/onl/admin/rule/transform2/transform2ManDetail.jsp +++ b/WebContent/jsp/onl/admin/rule/transform2/transform2ManDetail.jsp @@ -703,7 +703,15 @@ transformRows = json.transformRows; //eaiSvcName combo $("input[name=eaiSvcName]").val('${param.eaiSvcName }'); - $("input[name=eaiSvcDesc]").val('${param.eaiSvcDesc }'); + + var rawDesc = '${param.eaiSvcDesc}'; + + if (rawDesc && rawDesc !== "") { + $("input[name=eaiSvcDesc]").val(decodeURIComponent(rawDesc)); + } else { + $("input[name=eaiSvcDesc]").val(""); + } + //$("input[name=cnvsnDesc]").val('${param.cnvsnDesc }'); new makeOptions("CODE","NAME").setObj($("#functionCombo")).setData(json.functionRows).setNoValueInclude(true).setNoValue("", "<%=localeMessage.getString("transformManDetail.none")%>").setFormat(codeName3OptionFormat).rendering(); @@ -717,6 +725,8 @@ reloadSourceTargetGrid('${param.cnvsnName}'); reloadCnvsnDesc(); + + setSearchable("functionCombo"); }, error:function(e){ alert(e.responseText); @@ -1063,6 +1073,10 @@ }); $("#btn_delete").click(function(){ + if(!confirm("삭제하시겠습니까?")){ + return; + } + var postData = []; const cnvsnName = $("select[name=transformForEaiSvcNameCombo]").val(); const cnvsnType = cnvsnName.slice(-3); @@ -1604,7 +1618,7 @@ // 연결이 존재 하는 지 확인 if (jsPlumbInstance.getConnections({source:sourceEndpointElementId, target:targetEndpointElementId}).length > 0) { - alert('<%=localeMessage.getString("transformManDetail.alert10")%>'); +<%-- alert('<%=localeMessage.getString("transformManDetail.alert10")%>'); --%> return; } diff --git a/WebContent/jsp/onl/admin/service/jmsQueueManDetail.jsp b/WebContent/jsp/onl/admin/service/jmsQueueManDetail.jsp index ea6d7be..04e240f 100644 --- a/WebContent/jsp/onl/admin/service/jmsQueueManDetail.jsp +++ b/WebContent/jsp/onl/admin/service/jmsQueueManDetail.jsp @@ -154,6 +154,10 @@ $(document).ready(function() { }); }); $("#btn_delete").click(function(){ + if(!confirm("삭제하시겠습니까?")){ + return; + } + var disabled = $('#ajaxForm').find(':input:disabled').removeAttr('disabled'); var postData = $('#ajaxForm').serializeArray(); disabled.attr('disabled','disabled'); diff --git a/WebContent/jsp/onl/apim/apigroup/apiGroupManPopup.jsp b/WebContent/jsp/onl/apim/apigroup/apiGroupManPopup.jsp index 23a9e28..f5f6d14 100644 --- a/WebContent/jsp/onl/apim/apigroup/apiGroupManPopup.jsp +++ b/WebContent/jsp/onl/apim/apigroup/apiGroupManPopup.jsp @@ -27,9 +27,11 @@ for(var i = 0; i < selectedRowIds.length; i++) { var rowData = grid.jqGrid('getRowData', selectedRowIds[i]); selectedData.push({ - bizCode: rowData.eaiBzwkDstcd, + bizCode: rowData.eaiBzwkDstcd, // CLINET apiId: rowData.eaiSvcName, - apiDesc: rowData.eaiSvcDesc + apiDesc: rowData.eaiSvcDesc, // SCOPE-API + APIFULLPATH: rowData.apiFullPath, + BZWKSVCKEYNAME: rowData.bzwksvckeyname }); } @@ -105,8 +107,10 @@ url : url, postData : gridPostData, colNames : [ '업무구분', + 'API ID', 'API 명', - 'API 설명', + 'API FULL PATH', + '업무서비스명', '요청', '응답', '작성자', @@ -116,11 +120,13 @@ colModel : [ { name : 'eaiBzwkDstcd' , align : 'center' , width:'40', sortable:false}, { name : 'eaiSvcName' , align : 'left' , width:'100'}, { name : 'eaiSvcDesc' , align : 'left' }, + { name : 'apiFullPath' , align : 'left' , width:'100'}, + { name : 'bzwksvckeyname' , align : 'left' , width:'100', hidden: true}, { name : 'fromAdapter' , align : 'center' , width:'40', formatter:adapterNameShortFormatter }, { name : 'toAdapter' , align : 'center' , width:'40', formatter:adapterNameShortFormatter }, { name : 'author' , align : 'center' , width:'60' }, { name : 'simYn' , align : 'center' , width:'40' }, - { name : 'syncAsyncType' , align : 'center' , width:'40', hidden: true }, + { name : 'syncAsyncType' , align : 'center' , width:'40', hidden: true}, ], jsonReader : { repeatitems : false @@ -231,6 +237,16 @@ + + API 명 + + + + API FULL PATH + + + +
diff --git a/WebContent/jsp/onl/apim/approval/portalAppApprovalManPopup.jsp b/WebContent/jsp/onl/apim/approval/portalAppApprovalManPopup.jsp index dd62cc2..ad28250 100644 --- a/WebContent/jsp/onl/apim/approval/portalAppApprovalManPopup.jsp +++ b/WebContent/jsp/onl/apim/approval/portalAppApprovalManPopup.jsp @@ -54,7 +54,11 @@ success: function (args) { <%--alert("<%= localeMessage.getString("approval.approveSuccess") %>");--%> alert("승인되었습니다."); - window.opener.location.reload(); + if (window.dialogArguments && window.dialogArguments.self) { + window.dialogArguments.self.location.reload(); + } else if (window.opener) { + window.opener.location.reload(); + } window.close(); }, error: function (e) { @@ -73,7 +77,11 @@ data: {cmd: "REJECT", id: key, rejectReason: rejectReason}, success: function (args) { alert("반려되었습니다."); - window.opener.location.reload(); + if (window.dialogArguments && window.dialogArguments.self) { + window.dialogArguments.self.location.reload(); + } else if (window.opener) { + window.opener.location.reload(); + } window.close(); }, error: function (e) { @@ -91,7 +99,11 @@ data: {cmd: "REDEPLOY", id: key}, success: function (args) { alert("요청되었습니다."); - window.opener.location.reload(); + if (window.dialogArguments && window.dialogArguments.self) { + window.dialogArguments.self.location.reload(); + } else if (window.opener) { + window.opener.location.reload(); + } window.close(); }, error: function (e) { diff --git a/WebContent/jsp/onl/apim/approval/portalUserApprovalManPopup.jsp b/WebContent/jsp/onl/apim/approval/portalUserApprovalManPopup.jsp index 6ee758c..24427b3 100644 --- a/WebContent/jsp/onl/apim/approval/portalUserApprovalManPopup.jsp +++ b/WebContent/jsp/onl/apim/approval/portalUserApprovalManPopup.jsp @@ -113,7 +113,11 @@ data: {cmd: "APPROVE", id: key}, success: function(args) { alert("승인되었습니다."); - window.opener.location.reload(); + if (window.dialogArguments && window.dialogArguments.self) { + window.dialogArguments.self.location.reload(); + } else if (window.opener) { + window.opener.location.reload(); + } window.close(); }, error: function(e) { @@ -132,7 +136,11 @@ data: {cmd: "REJECT", id: key, rejectReason: rejectReason}, success: function(args) { alert("반려되었습니다."); - window.opener.location.reload(); + if (window.dialogArguments && window.dialogArguments.self) { + window.dialogArguments.self.location.reload(); + } else if (window.opener) { + window.opener.location.reload(); + } window.close(); }, error: function(e) { @@ -150,7 +158,11 @@ data: {cmd: "REDEPLOY", id: key}, success: function(args) { alert("요청되었습니다."); - window.opener.location.reload(); + if (window.dialogArguments && window.dialogArguments.self) { + window.dialogArguments.self.location.reload(); + } else if (window.opener) { + window.opener.location.reload(); + } window.close(); }, error: function(e) { diff --git a/WebContent/jsp/onl/apim/approval/prodclient/clientManDetail.jsp b/WebContent/jsp/onl/apim/approval/prodclient/clientManDetail.jsp index 9bf2b7f..566b37c 100644 --- a/WebContent/jsp/onl/apim/approval/prodclient/clientManDetail.jsp +++ b/WebContent/jsp/onl/apim/approval/prodclient/clientManDetail.jsp @@ -83,7 +83,7 @@ var selectedApiGrid = $("#selectedApiGrid"); selectedApiGrid.jqGrid({ datatype: "local", - colNames: ['API ID', 'API 이름'], + colNames: ['API ID', 'API 명'], colModel: [ {name: 'apiId', index: 'apiId', width: 100, align: 'center'}, {name: 'apiName', index: 'apiName', width: 100, align: 'center'} diff --git a/WebContent/jsp/onl/apim/portalfaq/portalFaqManDetail.jsp b/WebContent/jsp/onl/apim/portalfaq/portalFaqManDetail.jsp index 5bc469a..4dda432 100644 --- a/WebContent/jsp/onl/apim/portalfaq/portalFaqManDetail.jsp +++ b/WebContent/jsp/onl/apim/portalfaq/portalFaqManDetail.jsp @@ -13,6 +13,7 @@ FAQ 상세 + "/> ", Pattern.CASE_INSENSITIVE), - Pattern.compile("src[\r\n]*=[\r\n]*\\\'(.*?)\\\'", - Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL), - Pattern.compile("src[\r\n]*=[\r\n]*\\\"(.*?)\\\"", - Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL), Pattern.compile("", Pattern.CASE_INSENSITIVE), Pattern.compile("", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL), @@ -46,6 +43,22 @@ class RequestWrapper extends HttpServletRequestWrapper { Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL) }; + // src 속성 화이트리스트 패턴 - data:image/만 허용 + private static final Pattern SRC_ATTR_PATTERN = Pattern.compile( + "(src\\s*=\\s*)([\"'])([^\"']*?)\\2", + Pattern.CASE_INSENSITIVE); + + // 허용되는 src 값 패턴 (화이트리스트) - data:image/만 허용 + private static final Pattern ALLOWED_SRC_PATTERN = Pattern.compile( + "^data:image/.*", + Pattern.CASE_INSENSITIVE); + + // XSS 문자 변환을 건너뛸 파라미터 목록 (리치 텍스트 에디터 콘텐츠 등) + // XSS 패턴 필터링은 적용하지만 <, > 등으로 변환하지 않음 + private static final String[] SKIP_CHAR_CONVERT_PARAMS = {"contents", "content", "description"}; + + // 현재 처리 중인 파라미터명 (cleanXSS에서 문자 변환 여부 결정에 사용) + private ThreadLocal currentParameter = new ThreadLocal<>(); public RequestWrapper(HttpServletRequest servletRequest) { super(servletRequest); @@ -60,8 +73,13 @@ class RequestWrapper extends HttpServletRequestWrapper { int count = values.length; String[] encodedValues = new String[count]; - for (int i = 0; i < count; i++) { - encodedValues[i] = cleanXSS(values[i]); + currentParameter.set(parameter); + try { + for (int i = 0; i < count; i++) { + encodedValues[i] = cleanXSS(values[i]); + } + } finally { + currentParameter.remove(); } return encodedValues; @@ -74,13 +92,19 @@ class RequestWrapper extends HttpServletRequestWrapper { for (Entry entry : map.entrySet()) { String[] values = entry.getValue(); + String paramName = entry.getKey(); if (values != null && values.length > 0) { String[] cloneValues = new String[values.length]; - for (int idx = 0; idx < values.length; idx++) { - cloneValues[idx] = cleanXSS(values[idx]); + currentParameter.set(paramName); + try { + for (int idx = 0; idx < values.length; idx++) { + cloneValues[idx] = cleanXSS(values[idx]); + } + } finally { + currentParameter.remove(); } - clonedMap.put(entry.getKey(), cloneValues); + clonedMap.put(paramName, cloneValues); } } @@ -95,7 +119,12 @@ class RequestWrapper extends HttpServletRequestWrapper { return value; } - return cleanXSS(value); + currentParameter.set(parameter); + try { + return cleanXSS(value); + } finally { + currentParameter.remove(); + } } @Override @@ -123,9 +152,15 @@ class RequestWrapper extends HttpServletRequestWrapper { // script 문자열을 완전히 제거 value = value.replaceAll("(?i)\\bscript\\b", ""); - StringBuilder sb = new StringBuilder(); - value = convertChars(value, sb); + // src 속성 화이트리스트 필터링 - 허용된 프로토콜만 통과 + value = filterSrcAttribute(value); + + // 리치 텍스트 파라미터는 문자 변환을 건너뛰고 XSS 패턴 필터링만 적용 + if (!shouldSkipCharConvert()) { + StringBuilder sb = new StringBuilder(); + value = convertChars(value, sb); + } // eval, javascript 등 스크립트 관련 문자열 제거 value = value.replaceAll("(?i)eval\\((.*?)\\)", ""); @@ -133,6 +168,54 @@ class RequestWrapper extends HttpServletRequestWrapper { return value; } + /** + * 현재 파라미터가 문자 변환을 건너뛸지 여부 확인 + * 리치 텍스트 에디터 콘텐츠 등은 HTML 태그가 필요하므로 < > 변환을 하지 않음 + */ + private boolean shouldSkipCharConvert() { + String param = currentParameter.get(); + if (param == null) { + return false; + } + for (String skipParam : SKIP_CHAR_CONVERT_PARAMS) { + if (StringUtils.equalsIgnoreCase(param, skipParam)) { + return true; + } + } + return false; + } + + /** + * src 속성을 화이트리스트 방식으로 필터링 + * 허용: data:image/ (Base64 이미지) + * 비허용: 그 외 모든 프로토콜 + */ + private String filterSrcAttribute(String value) { + if (value == null || !value.toLowerCase().contains("src")) { + return value; + } + + Matcher matcher = SRC_ATTR_PATTERN.matcher(value); + StringBuffer result = new StringBuffer(); + + while (matcher.find()) { + String srcPrefix = matcher.group(1); // src= + String quote = matcher.group(2); // " 또는 ' + String srcValue = matcher.group(3); // src 값 + + // 빈 값이거나 화이트리스트에 매칭되면 유지 + if (srcValue.isEmpty() || ALLOWED_SRC_PATTERN.matcher(srcValue).matches()) { + matcher.appendReplacement(result, Matcher.quoteReplacement(srcPrefix + quote + srcValue + quote)); + } else { + // 허용되지 않는 프로토콜은 src="" 로 대체 + matcher.appendReplacement(result, Matcher.quoteReplacement(srcPrefix + quote + quote)); + } + } + matcher.appendTail(result); + + return result.toString(); + } + // '(', ')', '"' 문자는 getMethod()가 "get"일 때만 변환됨. // 이는 gridData에서 사용되는 큰따옴표(")와 변환 함수에서 사용하는 괄호('(', ')')의 처리를 위함. private String convertChars(String value, StringBuilder sb) { diff --git a/src/main/java/com/eactive/eai/rms/common/interceptor/AuthorizeInterceptor.java b/src/main/java/com/eactive/eai/rms/common/interceptor/AuthorizeInterceptor.java index 51e25f0..963652e 100644 --- a/src/main/java/com/eactive/eai/rms/common/interceptor/AuthorizeInterceptor.java +++ b/src/main/java/com/eactive/eai/rms/common/interceptor/AuthorizeInterceptor.java @@ -7,6 +7,7 @@ import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.apache.commons.lang3.ClassUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.HandlerInterceptor; @@ -32,6 +33,14 @@ public class AuthorizeInterceptor implements HandlerInterceptor { public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + // InterceptorSkipController 구현 컨트롤러는 권한 체크 스킵 + if (handler instanceof HandlerMethod) { + Class clazz = ((HandlerMethod) handler).getBeanType(); + if (ClassUtils.isAssignable(clazz, InterceptorSkipController.class)) { + return true; + } + } + // 요청 URI, 사용자 ID, 서비스 타입, cmd 추출 String uri = request.getRequestURI(); String userId = SessionManager.getUserId(request); diff --git a/src/main/java/com/eactive/eai/rms/common/interceptor/SessionCheckInterceptor.java b/src/main/java/com/eactive/eai/rms/common/interceptor/SessionCheckInterceptor.java index a339126..1d9d9d2 100644 --- a/src/main/java/com/eactive/eai/rms/common/interceptor/SessionCheckInterceptor.java +++ b/src/main/java/com/eactive/eai/rms/common/interceptor/SessionCheckInterceptor.java @@ -67,9 +67,9 @@ public class SessionCheckInterceptor extends HandlerInterceptorAdapter { } else { //request.getRequestDispatcher("/monitoring/loginForm.do").forward(request, response); //request.getSession().setAttribute("redirectInProgress", true); - response.sendRedirect("/monitoring/rms/logout.do"); + response.sendRedirect(request.getContextPath() + "/rms/logout.do"); //response.sendRedirect("/monitoring/loginForm.do"); - + } } diff --git a/src/main/java/com/eactive/eai/rms/common/login/SessionDestructionListener.java b/src/main/java/com/eactive/eai/rms/common/login/SessionDestructionListener.java new file mode 100644 index 0000000..1fb4d46 --- /dev/null +++ b/src/main/java/com/eactive/eai/rms/common/login/SessionDestructionListener.java @@ -0,0 +1,46 @@ +package com.eactive.eai.rms.common.login; + +import javax.servlet.annotation.WebListener; +import javax.servlet.http.HttpSession; +import javax.servlet.http.HttpSessionEvent; +import javax.servlet.http.HttpSessionListener; + +import org.apache.log4j.Logger; + +@WebListener +public class SessionDestructionListener implements HttpSessionListener { + + private static final Logger logger = Logger.getLogger(SessionDestructionListener.class); + + @Override + public void sessionCreated(HttpSessionEvent se) { + + } + + /** + * 세션 타임아웃 등으로 소멸 시 세션정보 삭제 + */ + @Override + public void sessionDestroyed(HttpSessionEvent se) { + + try { + HttpSession session = se.getSession(); + + String userId = (String) session.getAttribute("userId"); + + // 로그인 한 사용자의 세션정보 정리 + if (userId != null) { + if (logger.isDebugEnabled()) { + logger.debug("[SessionListener] 세션만료 감지. 사용자 제거: " + userId); + } + + SessionManager.removeUserSession(userId); + } + + } catch (Exception e) { + + + logger.error("[SessionListener] 세션 정리 중 오류 발생", e); + } + } +} diff --git a/src/main/java/com/eactive/eai/rms/common/util/EndpointLogger.java b/src/main/java/com/eactive/eai/rms/common/util/EndpointLogger.java new file mode 100644 index 0000000..7b35075 --- /dev/null +++ b/src/main/java/com/eactive/eai/rms/common/util/EndpointLogger.java @@ -0,0 +1,58 @@ +package com.eactive.eai.rms.common.util; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationListener; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.stereotype.Component; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.mvc.method.RequestMappingInfo; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; + +import java.util.Map; + +/** + * 서버 시작 시 등록된 HTTP 엔드포인트 목록을 로그로 출력 + */ +@Component +public class EndpointLogger implements ApplicationListener { + + private static final Logger log = LoggerFactory.getLogger(EndpointLogger.class); + + private boolean logged = false; + + @Override + public void onApplicationEvent(ContextRefreshedEvent event) { + // 중복 출력 방지 (parent/child context) + if (logged) { + return; + } + + ApplicationContext ctx = event.getApplicationContext(); + + try { + RequestMappingHandlerMapping mapping = ctx.getBean(RequestMappingHandlerMapping.class); + Map methods = mapping.getHandlerMethods(); + + log.debug("========== Registered HTTP Endpoints ({}) ==========", methods.size()); + + methods.entrySet().stream() + .sorted((e1, e2) -> e1.getKey().toString().compareTo(e2.getKey().toString())) + .forEach(entry -> { + RequestMappingInfo info = entry.getKey(); + HandlerMethod method = entry.getValue(); + log.debug("{} -> {}.{}", + info.getPatternsCondition(), + method.getBeanType().getSimpleName(), + method.getMethod().getName()); + }); + + log.debug("========== End of Endpoints =========="); + logged = true; + + } catch (Exception e) { + log.warn("Failed to log endpoints: {}", e.getMessage()); + } + } +} diff --git a/src/main/java/com/eactive/eai/rms/data/entity/man/menu/MenuService.java b/src/main/java/com/eactive/eai/rms/data/entity/man/menu/MenuService.java index 17350ee..73dc650 100644 --- a/src/main/java/com/eactive/eai/rms/data/entity/man/menu/MenuService.java +++ b/src/main/java/com/eactive/eai/rms/data/entity/man/menu/MenuService.java @@ -90,7 +90,7 @@ public class MenuService extends AbstractEMSDataSerivce { public String findPrpty2ValById(String prptyGroupName, String key) { MonitoringPropertyId id = new MonitoringPropertyId(); id.setPrptyGroupName(prptyGroupName); - id.setPrptyGroupName(key); + id.setPrptyName(key); Optional property = repository.findById(id); return property.map(MonitoringProperty::getPrpty2Val).orElse(null); @@ -41,12 +43,16 @@ public class MonitoringPropertyService id.setPrptyName(prptyName); Optional property = repository.findById(id); - if (property.isPresent() && !property.get().getPrpty2Val().isEmpty()) { - return property.get().getPrpty2Val(); - } else { - return defaultValue; + if (property.isPresent()) { + String value = property.get().getPrpty2Val(); + if (value != null && !value.isEmpty()) { + log.debug("getPropertyValue - {} : {}", prptyName, value); + return value; + } } + log.debug("getPropertyValue - {} : {} (defaultValue)", prptyName, defaultValue); + return defaultValue; } public Iterable findAllByIdPrptyName(String prptyName) { diff --git a/src/main/java/com/eactive/eai/rms/data/entity/onl/apim/apigroup/ApiGroupService.java b/src/main/java/com/eactive/eai/rms/data/entity/onl/apim/apigroup/ApiGroupService.java index 7ad1282..85bf102 100644 --- a/src/main/java/com/eactive/eai/rms/data/entity/onl/apim/apigroup/ApiGroupService.java +++ b/src/main/java/com/eactive/eai/rms/data/entity/onl/apim/apigroup/ApiGroupService.java @@ -8,6 +8,7 @@ import com.eactive.eai.data.jpa.AbstractDataService; import com.eactive.eai.rms.onl.apim.apigroup.ui.ApiGroupUISearch; import com.querydsl.core.types.dsl.BooleanExpression; +import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -58,11 +59,20 @@ public class ApiGroupService extends AbstractDataService apiGroupIds = getJPAQueryFactory() + .select(qApiGroupApi.id.apiGroupId) + .from(qApiGroupApi) .where(qApiGroupApi.id.apiId.eq(apiId)) - .distinct() .fetch(); + if (apiGroupIds.isEmpty()) { + return new ArrayList<>(); + } + + return getJPAQueryFactory().selectFrom(qApiGroup) + .where(qApiGroup.id.in(apiGroupIds)) + .fetch(); } } diff --git a/src/main/java/com/eactive/eai/rms/data/entity/onl/apim/obp/ObpGwMetricJpaDAO.java b/src/main/java/com/eactive/eai/rms/data/entity/onl/apim/obp/ObpGwMetricJpaDAO.java new file mode 100644 index 0000000..c734adc --- /dev/null +++ b/src/main/java/com/eactive/eai/rms/data/entity/onl/apim/obp/ObpGwMetricJpaDAO.java @@ -0,0 +1,255 @@ +package com.eactive.eai.rms.data.entity.onl.apim.obp; + +import com.eactive.apim.portal.apprequest.entity.AppRequest; +import com.eactive.apim.portal.apprequest.entity.QAppRequest; +import com.eactive.eai.data.entity.onl.message.EAIMessageEntity; +import com.eactive.eai.data.entity.onl.message.QEAIMessageEntity; +import com.eactive.eai.data.entity.onl.unifbwk.QUnifBwkTp; +import com.querydsl.core.Tuple; +import com.querydsl.jpa.impl.JPAQueryFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Repository; + +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import javax.persistence.Query; +import java.math.BigDecimal; +import java.util.*; +import java.util.stream.Collectors; + +/** + * ObpGwMetric JPA DAO - JPA/QueryDSL을 사용한 로그 테이블 집계 + * + *

Multi-tenancy 지원: 호출 전 DataSourceContextHolder 설정 필요

+ */ +@Repository +public class ObpGwMetricJpaDAO { + + private static final Logger log = LoggerFactory.getLogger(ObpGwMetricJpaDAO.class); + + @PersistenceContext + private EntityManager entityManager; + + private JPAQueryFactory getQueryFactory() { + return new JPAQueryFactory(entityManager); + } + + /** + * EAIBZWKDSTCD 코드 목록 조회 (TSEAICM01) + * + * @return 업무구분코드 목록 + */ + public List selectAllBzwkCodes() { + QUnifBwkTp cm01 = QUnifBwkTp.unifBwkTp; + + return getQueryFactory() + .select(cm01.eaibzwkdstcd) + .from(cm01) + .orderBy(cm01.eaibzwkdstcd.asc()) + .fetch(); + } + + /** + * EAIBZWKDSTCD별 시간대 로그 집계 (Native Query) + * + *

동적 테이블명(TSEAILG1X)을 사용하므로 Native Query 필요

+ * + * @param tableName 동적 테이블명 (예: TSEAILG14) + * @param bzwkCode 업무구분코드 + * @param startTime 시작시간 (yyyyMMddHHmmssSSS) + * @param endTime 종료시간 (yyyyMMddHHmmssSSS) + * @param timeslice 타임슬라이스 (yyyyMMddHH0000000) + * @return 집계 결과 목록 + */ + @SuppressWarnings("unchecked") + public List selectHourlyMetricsByBzwkCode( + String tableName, String bzwkCode, String startTime, String endTime, String timeslice) { + + String sql = "SELECT " + + "EAISVCNAME AS API_ID, " + + "CLIENTID AS CLIENT_ID, " + + "EAISEVRINSTNCNAME AS HOSTNAME, " + + "ORGID AS ORG_ID, " + + ":timeslice AS TIMESLICE, " + + "SUM(CASE WHEN LOGPRCSSSERNO = '100' THEN 1 ELSE 0 END) AS ATTEMPTED_COUNT, " + + "SUM(CASE WHEN LOGPRCSSSERNO = '400' " + + " AND (RSPNSERRCDNAME IS NULL OR RSPNSERRCDNAME LIKE 'ESB%') " + + " THEN 1 ELSE 0 END) AS COMPLETED_COUNT, " + + "MAX(TRIM(EAISVCSERNO)) AS EAI_SVC_SERNO " + + "FROM " + tableName + " " + + "WHERE EAIBZWKDSTCD = :bzwkCode " + + " AND MSGDPSTYMS BETWEEN :startTime AND :endTime " + + "GROUP BY EAISVCNAME, CLIENTID, EAISEVRINSTNCNAME, ORGID " + + "HAVING SUM(CASE WHEN LOGPRCSSSERNO = '100' THEN 1 ELSE 0 END) > 0 " + + " OR SUM(CASE WHEN LOGPRCSSSERNO = '400' THEN 1 ELSE 0 END) > 0"; + + Query query = entityManager.createNativeQuery(sql); + query.setParameter("timeslice", timeslice); + query.setParameter("bzwkCode", bzwkCode); + query.setParameter("startTime", startTime); + query.setParameter("endTime", endTime); + + List results = query.getResultList(); + + return results.stream() + .map(this::mapToObpGwMetricVO) + .collect(Collectors.toList()); + } + + /** + * Native Query 결과를 ObpGwMetricVO로 변환 + */ + private ObpGwMetricVO mapToObpGwMetricVO(Object[] row) { + ObpGwMetricVO vo = new ObpGwMetricVO(); + vo.setApiId((String) row[0]); + vo.setClientId((String) row[1]); + vo.setHostname((String) row[2]); + vo.setOrgId((String) row[3]); + vo.setTimeslice((String) row[4]); + vo.setAttemptedCount(toLong(row[5])); + vo.setCompletedCount(toLong(row[6])); + vo.setEaiSvcSerno((String) row[7]); + return vo; + } + + /** + * Number 타입을 Long으로 변환 (Oracle은 BigDecimal 반환) + */ + private Long toLong(Object value) { + if (value == null) return 0L; + if (value instanceof BigDecimal) { + return ((BigDecimal) value).longValue(); + } + if (value instanceof Number) { + return ((Number) value).longValue(); + } + return 0L; + } + + /** + * API 이름 조회 (TSEAIHE01) + * + * @param apiIds API ID 목록 (EAISVCNAME) + * @return API ID → API 이름 맵 + */ + public Map selectApiNames(Set apiIds) { + if (apiIds == null || apiIds.isEmpty()) { + return new HashMap<>(); + } + + QEAIMessageEntity he01 = QEAIMessageEntity.eAIMessageEntity; + + List results = getQueryFactory() + .select(he01.eaisvcname, he01.eaisvcdesc) + .from(he01) + .where(he01.eaisvcname.in(apiIds)) + .fetch(); + + Map result = new HashMap<>(); + for (Tuple tuple : results) { + String apiId = tuple.get(he01.eaisvcname); + String apiName = tuple.get(he01.eaisvcdesc); + if (apiId != null && apiName != null) { + result.put(apiId, apiName); + } + } + return result; + } + + /** + * URI 및 HTTP Method 조회 결과 + */ + public static class UriMethodResult { + private final Map uriMap; + private final Map methodMap; + + public UriMethodResult(Map uriMap, Map methodMap) { + this.uriMap = uriMap; + this.methodMap = methodMap; + } + + public Map getUriMap() { + return uriMap; + } + + public Map getMethodMap() { + return methodMap; + } + } + + /** + * URI 및 HTTP Method 조회 (HTTP_ADAPTER_EXTRA_LOG) + * + *

SERVICE_PROCESS_NUMBER = 100 (인바운드 요청) 조건으로 조회

+ * + * @param eaiSvcSernos EAI 서비스 일련번호 목록 (GUID) + * @return UriMethodResult (eaiSvcSerno → URI 맵, eaiSvcSerno → METHOD 맵) + */ + @SuppressWarnings("unchecked") + public UriMethodResult selectUrisAndMethods(Set eaiSvcSernos) { + if (eaiSvcSernos == null || eaiSvcSernos.isEmpty()) { + return new UriMethodResult(new HashMap<>(), new HashMap<>()); + } + + // IN절 파라미터 바인딩을 위한 처리 + String sql = "SELECT GUID AS EAI_SVC_SERNO, URL AS URI, HTTP_METHOD AS METHOD " + + "FROM HTTP_ADAPTER_EXTRA_LOG " + + "WHERE SERVICE_PROCESS_NUMBER = 100 " + + " AND GUID IN (:eaiSvcSernos)"; + + Query query = entityManager.createNativeQuery(sql); + query.setParameter("eaiSvcSernos", new ArrayList<>(eaiSvcSernos)); + + List results = query.getResultList(); + + Map uriMap = new HashMap<>(); + Map methodMap = new HashMap<>(); + + for (Object[] row : results) { + String eaiSvcSerno = (String) row[0]; + String uri = (String) row[1]; + String method = (String) row[2]; + if (eaiSvcSerno != null) { + if (uri != null) { + uriMap.put(eaiSvcSerno, uri); + } + if (method != null) { + methodMap.put(eaiSvcSerno, method); + } + } + } + + return new UriMethodResult(uriMap, methodMap); + } + + /** + * AppId 조회 (PTL_APP_REQUEST) - org_id로 조회 + * + * @param orgIds 기관 ID 목록 + * @return 기관 ID → 앱 ID 맵 + */ + public Map selectAppIdsByOrgIds(Set orgIds) { + if (orgIds == null || orgIds.isEmpty()) { + return new HashMap<>(); + } + + QAppRequest appRequest = QAppRequest.appRequest; + + List results = getQueryFactory() + .select(appRequest.org.id, appRequest.id) + .from(appRequest) + .where(appRequest.org.id.in(orgIds)) + .fetch(); + + Map result = new HashMap<>(); + for (Tuple tuple : results) { + String orgId = tuple.get(appRequest.org.id); + String appId = tuple.get(appRequest.id); + if (orgId != null && appId != null) { + result.put(orgId, appId); + } + } + return result; + } +} diff --git a/src/main/java/com/eactive/eai/rms/data/entity/onl/apim/obp/ObpGwMetricService.java b/src/main/java/com/eactive/eai/rms/data/entity/onl/apim/obp/ObpGwMetricService.java new file mode 100644 index 0000000..da1d6ef --- /dev/null +++ b/src/main/java/com/eactive/eai/rms/data/entity/onl/apim/obp/ObpGwMetricService.java @@ -0,0 +1,66 @@ +package com.eactive.eai.rms.data.entity.onl.apim.obp; + +import com.eactive.apim.portal.obp.entity.ObpGwMetric; +import com.eactive.apim.portal.obp.repository.ObpGwMetricRepository; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +/** + * ObpGwMetric Service - 엔티티 CRUD 서비스 + */ +@Service +@Transactional(transactionManager = "transactionManagerForEMS") +public class ObpGwMetricService { + + private final ObpGwMetricRepository repository; + + public ObpGwMetricService(ObpGwMetricRepository repository) { + this.repository = repository; + } + + /** + * UPSERT 판단을 위한 기존 데이터 조회 (NULL 안전) + */ + public Optional findByUniqueKey(LocalDateTime timeslice, String apiId, String clientId, String hostname, String orgId) { + return repository.findByUniqueKeyNullSafe(timeslice, apiId, clientId, hostname, orgId); + } + + /** + * 특정 시간대의 모든 GwMetric 데이터 조회 + */ + public List findByTimeslice(LocalDateTime timeslice) { + return repository.findByTimeslice(timeslice); + } + + /** + * 특정 시간 범위의 GwMetric 데이터 조회 + */ + public List findByTimesliceBetween(LocalDateTime startTime, LocalDateTime endTime) { + return repository.findByTimesliceBetween(startTime, endTime); + } + + /** + * 특정 시간대 이후의 모든 GwMetric 데이터 조회 + */ + public List findByTimesliceGreaterThanEqual(LocalDateTime timeslice) { + return repository.findByTimesliceGreaterThanEqual(timeslice); + } + + /** + * 엔티티 저장 (INSERT 또는 UPDATE) + */ + public ObpGwMetric save(ObpGwMetric entity) { + return repository.save(entity); + } + + /** + * 엔티티 목록 저장 + */ + public List saveAll(List entities) { + return repository.saveAll(entities); + } +} diff --git a/src/main/java/com/eactive/eai/rms/data/entity/onl/apim/obp/ObpGwMetricVO.java b/src/main/java/com/eactive/eai/rms/data/entity/onl/apim/obp/ObpGwMetricVO.java new file mode 100644 index 0000000..ae65a76 --- /dev/null +++ b/src/main/java/com/eactive/eai/rms/data/entity/onl/apim/obp/ObpGwMetricVO.java @@ -0,0 +1,71 @@ +package com.eactive.eai.rms.data.entity.onl.apim.obp; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +/** + * ObpGwMetric VO - 로그 집계 결과 매핑용 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class ObpGwMetricVO { + + private static final DateTimeFormatter TIMESLICE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHH0000000"); + + /** API ID (EAISVCNAME) */ + private String apiId; + + /** 클라이언트 ID (CLIENTID) */ + private String clientId; + + /** 호스트 이름 (EAISEVRINSTNCNAME) */ + private String hostname; + + /** 기관 ID (ORGID) */ + private String orgId; + + /** 타임슬라이스 문자열 (yyyyMMddHH0000000) */ + private String timeslice; + + /** 시도 횟수 (LOGPRCSSSERNO='100' 건수) */ + private Long attemptedCount; + + /** 완료 횟수 (LOGPRCSSSERNO='400' AND 성공 조건 건수) */ + private Long completedCount; + + /** EAI 서비스 일련번호 (URI 조회용 - HTTP_ADAPTER_EXTRA_LOG.GUID와 매핑) */ + private String eaiSvcSerno; + + /** + * timeslice 문자열을 LocalDateTime으로 변환 + */ + public LocalDateTime getTimesliceAsLocalDateTime() { + if (timeslice == null || timeslice.isEmpty()) { + return null; + } + // "yyyyMMddHH0000000" 형식에서 앞 10자리(yyyyMMddHH)만 사용 + String dateTimeStr = timeslice.substring(0, 10) + "0000"; + return LocalDateTime.parse(dateTimeStr, DateTimeFormatter.ofPattern("yyyyMMddHHmmss")); + } + + /** + * 집계 키 생성 (apiId + clientId + hostname + orgId) + */ + public String getAggregationKey() { + return String.join("|", + nullSafe(apiId), + nullSafe(clientId), + nullSafe(hostname), + nullSafe(orgId) + ); + } + + private String nullSafe(String value) { + return value != null ? value : ""; + } +} diff --git a/src/main/java/com/eactive/eai/rms/data/entity/onl/authserver/ScopeEntityService.java b/src/main/java/com/eactive/eai/rms/data/entity/onl/authserver/ScopeEntityService.java index 2f42d64..89b2a1e 100644 --- a/src/main/java/com/eactive/eai/rms/data/entity/onl/authserver/ScopeEntityService.java +++ b/src/main/java/com/eactive/eai/rms/data/entity/onl/authserver/ScopeEntityService.java @@ -1,21 +1,31 @@ package com.eactive.eai.rms.data.entity.onl.authserver; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; import com.eactive.eai.data.entity.onl.authserver.QScopeEntity; import com.eactive.eai.data.entity.onl.authserver.ScopeEntity; import com.eactive.eai.data.entity.onl.authserver.ScopeEntityId; import com.eactive.eai.data.jpa.AbstractDataService; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + @Service @Transactional public class ScopeEntityService extends AbstractDataService { - + + @PersistenceContext + private EntityManager entityManager; // AbstractDataService 의 entityManager가 private이라 접근 불가하여 별도 주입 + public void deleteByIdScopeId(String scopeId) { getJPAQueryFactory().delete(QScopeEntity.scopeEntity) .where(QScopeEntity.scopeEntity.id.scopeid.eq(scopeId)) .execute(); + + // 동일 트랜잭션 내에서 같은 ID로 재저장 시 충돌(EntityExistsException)을 방지 + entityManager.flush(); + entityManager.clear(); } } diff --git a/src/main/java/com/eactive/eai/rms/data/entity/onl/eaimsg/ApiScopeDto.java b/src/main/java/com/eactive/eai/rms/data/entity/onl/eaimsg/ApiScopeDto.java index 2fe0ff2..9916ce4 100644 --- a/src/main/java/com/eactive/eai/rms/data/entity/onl/eaimsg/ApiScopeDto.java +++ b/src/main/java/com/eactive/eai/rms/data/entity/onl/eaimsg/ApiScopeDto.java @@ -9,6 +9,9 @@ import lombok.Data; @AllArgsConstructor public class ApiScopeDto { + @JsonProperty("EAISVCNAME") + private String eaiSvcName; + @JsonProperty("EAISVCDESC") private String eaiSvcDesc; @@ -20,5 +23,8 @@ public class ApiScopeDto { @JsonProperty("SCOPENAME") private String scopeName; + + @JsonProperty("APIFULLPATH") + private String apiFullPath; } diff --git a/src/main/java/com/eactive/eai/rms/data/entity/onl/eaimsg/EAIMessageService.java b/src/main/java/com/eactive/eai/rms/data/entity/onl/eaimsg/EAIMessageService.java index 7ab9d79..2aeb0e3 100644 --- a/src/main/java/com/eactive/eai/rms/data/entity/onl/eaimsg/EAIMessageService.java +++ b/src/main/java/com/eactive/eai/rms/data/entity/onl/eaimsg/EAIMessageService.java @@ -44,6 +44,7 @@ public class EAIMessageService extends AbstractDataService jpaQuery = createBaseQuery(eaiMessageUISearch); @@ -53,7 +54,14 @@ public class EAIMessageService extends AbstractDataService orderSpecifier = getOrderSpecifier(qeaiMessageEntity, sortname, sortorder); List eaiMessages = jpaQuery - .select(qeaiMessageEntity, qServiceMessageEntity.psvintfacdsticname, qServiceMessageEntity.psvsysadptrbzwkgroupname, qRouting.nonmotivrouturiname) + .select(qeaiMessageEntity + , qServiceMessageEntity.psvintfacdsticname + , qServiceMessageEntity.psvsysadptrbzwkgroupname + , qRouting.nonmotivrouturiname + , qStdInfo.apifullpath + , qStdInfo.bzwksvckeyname + + ) .limit(pageable.getPageSize()) .offset(pageable.getOffset()) .orderBy(orderSpecifier) @@ -96,16 +104,24 @@ public class EAIMessageService extends AbstractDataService result = getJPAQueryFactory() .select(Projections.constructor(ApiScopeDto.class , + qEaiMessageEntity.eaisvcname , qEaiMessageEntity.eaisvcdesc , qScopeEntity.id.scopeid , qScopeEntity.id.bzwksvckeyname , - qScopeInfo.scopeName )) + qScopeInfo.scopeName , + qStandardMessageInfo.apifullpath + ) + ) .from(qEaiMessageEntity) .join(qStandardMessageInfo) .on(qEaiMessageEntity.eaisvcname.eq(qStandardMessageInfo.eaisvcname)) @@ -283,8 +310,6 @@ public class EAIMessageService extends AbstractDataService(result, pageable, result.size()); @@ -293,19 +318,20 @@ public class EAIMessageService extends AbstractDataService { + + List findByGroupId(String groupId); + + @Modifying + @Query("DELETE FROM InflowControlGroupMapping m WHERE m.groupId = :groupId") + void deleteByGroupId(@Param("groupId") String groupId); + + /** + * 인터페이스 ID로 매핑 조회 (중복 체크용) + */ + Optional findByInterfaceId(String interfaceId); +} diff --git a/src/main/java/com/eactive/eai/rms/data/entity/onl/inflow/InflowControlGroupRepository.java b/src/main/java/com/eactive/eai/rms/data/entity/onl/inflow/InflowControlGroupRepository.java new file mode 100644 index 0000000..d267d5c --- /dev/null +++ b/src/main/java/com/eactive/eai/rms/data/entity/onl/inflow/InflowControlGroupRepository.java @@ -0,0 +1,8 @@ +package com.eactive.eai.rms.data.entity.onl.inflow; + +import com.eactive.eai.data.entity.onl.inflow.InflowControlGroup; +import com.eactive.eai.data.jpa.BaseRepository; + +public interface InflowControlGroupRepository extends BaseRepository { + +} diff --git a/src/main/java/com/eactive/eai/rms/data/entity/onl/inflow/InflowControlLogService.java b/src/main/java/com/eactive/eai/rms/data/entity/onl/inflow/InflowControlLogService.java index 458a5b0..eccb2ef 100644 --- a/src/main/java/com/eactive/eai/rms/data/entity/onl/inflow/InflowControlLogService.java +++ b/src/main/java/com/eactive/eai/rms/data/entity/onl/inflow/InflowControlLogService.java @@ -1,19 +1,33 @@ package com.eactive.eai.rms.data.entity.onl.inflow; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import com.eactive.eai.data.entity.onl.inflow.InflowControlLog; import com.eactive.eai.data.entity.onl.inflow.InflowControlLogId; +import com.eactive.eai.data.entity.onl.inflow.QInflowControlGroup; import com.eactive.eai.data.entity.onl.inflow.QInflowControlLog; import com.eactive.eai.data.jpa.AbstractDataService; +import com.eactive.eai.rms.onl.manage.inflow.history.InflowControlHistoryManMapper; +import com.eactive.eai.rms.onl.manage.inflow.history.InflowControlHistoryManUI; import com.eactive.eai.rms.onl.manage.inflow.history.InflowControlHistoryManUISearch; import com.querydsl.core.BooleanBuilder; +import com.querydsl.core.Tuple; @Service public class InflowControlLogService extends AbstractDataService { + + private final InflowControlService inflowControlService; + private final InflowControlHistoryManMapper inflowControlHistoryManMapper; + + @Autowired + public InflowControlLogService(InflowControlService inflowControlService, InflowControlHistoryManMapper inflowControlHistoryManMapper) { + this.inflowControlService = inflowControlService; + this.inflowControlHistoryManMapper = inflowControlHistoryManMapper; + } public Page findAll(Pageable pageable, InflowControlHistoryManUISearch uiSearch) { QInflowControlLog qInflowConfrolLog = QInflowControlLog.inflowControlLog; @@ -25,6 +39,28 @@ public class InflowControlLogService return super.repository.findAll(booleanBuilder, pageable); } + public Page selectList(Pageable pageable, InflowControlHistoryManUISearch uiSearch) { + + Page tuples = inflowControlService.selectLogList(pageable, uiSearch); + + return tuples.map(tuple -> { + InflowControlLog inflowControlLog = tuple.get(QInflowControlLog.inflowControlLog); + + String groupName = tuple.get(QInflowControlGroup.inflowControlGroup.groupName); + + InflowControlHistoryManUI inflowControlHistoryManUI; + + inflowControlHistoryManUI = inflowControlHistoryManMapper.toVo(inflowControlLog); + + inflowControlHistoryManUI.setGroupname(groupName); + + return inflowControlHistoryManUI; + + }); + + + } + public Iterable findAll(InflowControlHistoryManUISearch uiSearch) { QInflowControlLog qInflowConfrolLog = QInflowControlLog.inflowControlLog; BooleanBuilder booleanBuilder = new BooleanBuilder(); diff --git a/src/main/java/com/eactive/eai/rms/data/entity/onl/inflow/InflowControlService.java b/src/main/java/com/eactive/eai/rms/data/entity/onl/inflow/InflowControlService.java index 7c08a0a..15adc96 100644 --- a/src/main/java/com/eactive/eai/rms/data/entity/onl/inflow/InflowControlService.java +++ b/src/main/java/com/eactive/eai/rms/data/entity/onl/inflow/InflowControlService.java @@ -17,11 +17,16 @@ import com.eactive.eai.data.entity.onl.adapter.QAdapterGroup; import com.eactive.eai.data.entity.onl.inflow.InflowControl; import com.eactive.eai.data.entity.onl.inflow.InflowControlId; import com.eactive.eai.data.entity.onl.inflow.QInflowControl; +import com.eactive.eai.data.entity.onl.inflow.QInflowControlGroup; +import com.eactive.eai.data.entity.onl.inflow.QInflowControlLog; import com.eactive.eai.data.entity.onl.message.QEAIMessageEntity; import com.eactive.eai.data.jpa.AbstractDataService; +import com.eactive.eai.rms.onl.manage.inflow.history.InflowControlHistoryManUISearch; import com.eactive.eai.rms.onl.manage.inflow.inflow.InflowControlManMapper; import com.querydsl.core.BooleanBuilder; import com.querydsl.core.Tuple; +import com.querydsl.core.types.OrderSpecifier; +import com.querydsl.jpa.impl.JPAQuery; import com.querydsl.jpa.impl.JPAQueryFactory; @Service @@ -168,5 +173,45 @@ public class InflowControlService extends AbstractDataService selectLogList(Pageable pageable, InflowControlHistoryManUISearch uiSearch) { + QInflowControlLog qlog = QInflowControlLog.inflowControlLog; + QInflowControlGroup qgroup = QInflowControlGroup.inflowControlGroup; + + JPAQuery jpaQuery = createBaseQuery(uiSearch); + + long totalCount = jpaQuery.select(QInflowControlLog.inflowControlLog.count()).fetchOne(); + + List inflowControlLog = jpaQuery + .select(qlog, qgroup.groupName) + .limit(pageable.getPageSize()) + .offset(pageable.getOffset()) + .orderBy(qlog.id.msgdpstyms.desc()) + .fetch(); + + return new PageImpl<>(inflowControlLog, pageable, totalCount); + } + + private JPAQuery createBaseQuery(InflowControlHistoryManUISearch uiSearch) { + QInflowControlLog qInflowConfrolLog = QInflowControlLog.inflowControlLog; + QInflowControlGroup qinflowControlGroup = QInflowControlGroup.inflowControlGroup; + + BooleanBuilder predicate = new BooleanBuilder(); + predicate + .and(qInflowConfrolLog.id.msgdpstyms + .between(uiSearch.getSearchStartDate() + "000000000", + uiSearch.getSearchEndDate() + "235959999")); + + return getJPAQueryFactory() + .select( + qInflowConfrolLog, + qinflowControlGroup.groupName + ) + .from(qInflowConfrolLog) + .leftJoin(qinflowControlGroup) + .on(qInflowConfrolLog.adptrbzwkgroupname.eq(qinflowControlGroup.groupId)) + .where(predicate); + + } } diff --git a/src/main/java/com/eactive/eai/rms/data/entity/onl/logger/ApiLogDTO.java b/src/main/java/com/eactive/eai/rms/data/entity/onl/logger/ApiLogDTO.java new file mode 100644 index 0000000..618a124 --- /dev/null +++ b/src/main/java/com/eactive/eai/rms/data/entity/onl/logger/ApiLogDTO.java @@ -0,0 +1,17 @@ +package com.eactive.eai.rms.data.entity.onl.logger; + +import java.util.List; + +import lombok.Data; + +@Data +public class ApiLogDTO { + + private String apiServiceNumber; + + private String txDate; + + private List logs; + + private Long totalProcessingTime; +} diff --git a/src/main/java/com/eactive/eai/rms/data/entity/onl/logger/ApiMessageLogDTO.java b/src/main/java/com/eactive/eai/rms/data/entity/onl/logger/ApiMessageLogDTO.java new file mode 100644 index 0000000..793da46 --- /dev/null +++ b/src/main/java/com/eactive/eai/rms/data/entity/onl/logger/ApiMessageLogDTO.java @@ -0,0 +1,36 @@ +package com.eactive.eai.rms.data.entity.onl.logger; + +import java.util.List; +import java.util.Map; + +import lombok.Data; + +@Data +public class ApiMessageLogDTO { + + private String apiServiceNumber; + + private int processNumber; + + private String txDate; + + private String url; + + private String queryString; + + private String clientId; + + private String method; + + private Integer HttpStatus; + + private String header; + + private String body; + + private String exdata; + + private Long processingTime; + + private String keymgtmsgctnt; +} diff --git a/src/main/java/com/eactive/eai/rms/data/entity/onl/logger/EAILogRepository.java b/src/main/java/com/eactive/eai/rms/data/entity/onl/logger/EAILogRepository.java index 2b10fb0..b89448c 100644 --- a/src/main/java/com/eactive/eai/rms/data/entity/onl/logger/EAILogRepository.java +++ b/src/main/java/com/eactive/eai/rms/data/entity/onl/logger/EAILogRepository.java @@ -6,6 +6,7 @@ import org.springframework.data.domain.Pageable; import com.eactive.eai.data.entity.onl.logger.EAILog; import com.eactive.eai.data.entity.onl.logger.EAILogId; +import java.util.List; import java.util.Map; public interface EAILogRepository { @@ -16,4 +17,6 @@ public interface EAILogRepository { Map countDetailedTransactions(Class clazz, EAILogSearch eaiLogSearch); + List getApiLog(Class class1, String apiServiceNumber); + } diff --git a/src/main/java/com/eactive/eai/rms/data/entity/onl/logger/EAILogRepositoryImpl.java b/src/main/java/com/eactive/eai/rms/data/entity/onl/logger/EAILogRepositoryImpl.java index fc78b9c..aad5b34 100644 --- a/src/main/java/com/eactive/eai/rms/data/entity/onl/logger/EAILogRepositoryImpl.java +++ b/src/main/java/com/eactive/eai/rms/data/entity/onl/logger/EAILogRepositoryImpl.java @@ -1,5 +1,19 @@ package com.eactive.eai.rms.data.entity.onl.logger; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Repository; + import com.eactive.eai.data.entity.onl.logger.EAILog; import com.eactive.eai.data.entity.onl.logger.EAILogId; import com.eactive.eai.data.entity.onl.logger.QEAILog; @@ -11,18 +25,6 @@ import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.core.types.dsl.EntityPathBase; import com.querydsl.jpa.impl.JPAQuery; import com.querydsl.jpa.impl.JPAQueryFactory; -import org.apache.commons.lang3.StringUtils; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; -import org.springframework.data.domain.Pageable; -import org.springframework.stereotype.Repository; - -import javax.persistence.EntityManager; -import javax.persistence.PersistenceContext; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; @Repository public class EAILogRepositoryImpl implements EAILogRepository { @@ -217,4 +219,14 @@ public class EAILogRepositoryImpl implements EAILogRepository { .fetchOne(); } + @Override + public List getApiLog(Class clazz, String apiServiceNumber) { + QEAILog qeaiLog = QEAILog.eAILog; + EntityPathBase entityPathBase = new EntityPathBase<>(clazz, qeaiLog.getMetadata().getName()); + + return new JPAQueryFactory(em).select(qeaiLog).from(entityPathBase) + .where(qeaiLog.id.eaisvcserno.trim().eq(apiServiceNumber)).orderBy(qeaiLog.id.logprcssserno.asc()) + .fetch(); + } + } diff --git a/src/main/java/com/eactive/eai/rms/data/entity/onl/logger/EAILogService.java b/src/main/java/com/eactive/eai/rms/data/entity/onl/logger/EAILogService.java index 5742260..215687b 100644 --- a/src/main/java/com/eactive/eai/rms/data/entity/onl/logger/EAILogService.java +++ b/src/main/java/com/eactive/eai/rms/data/entity/onl/logger/EAILogService.java @@ -19,4 +19,6 @@ public interface EAILogService { List selectLogList(HashMap paramMap); Map getTransactionCounts(EAILogSearch eaiLogSearch); + + ApiLogDTO getApiLog(String txDate, String apiServiceNumber); } diff --git a/src/main/java/com/eactive/eai/rms/data/entity/onl/logger/EAILogServiceImpl.java b/src/main/java/com/eactive/eai/rms/data/entity/onl/logger/EAILogServiceImpl.java index 30a8d95..1c86c8d 100644 --- a/src/main/java/com/eactive/eai/rms/data/entity/onl/logger/EAILogServiceImpl.java +++ b/src/main/java/com/eactive/eai/rms/data/entity/onl/logger/EAILogServiceImpl.java @@ -1,55 +1,132 @@ package com.eactive.eai.rms.data.entity.onl.logger; -import com.eactive.eai.data.RollingTable; -import com.eactive.eai.data.entity.onl.logger.EAILog; -import com.eactive.eai.data.entity.onl.logger.EAILogId; -import com.eactive.eai.rms.common.util.CommonUtil; -import com.eactive.eai.rms.data.entity.onl.apim.portalstatistics.PortalStatisticsVO; -import lombok.RequiredArgsConstructor; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import org.apache.commons.lang3.StringUtils; import org.springframework.context.ApplicationContext; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import com.eactive.eai.data.RollingTable; +import com.eactive.eai.data.entity.onl.logger.EAILog; +import com.eactive.eai.data.entity.onl.logger.EAILogId; +import com.eactive.eai.data.entity.onl.logger.HttpAdapterExtraLog; +import com.eactive.eai.rms.common.util.CommonUtil; +import com.eactive.eai.rms.data.entity.onl.apim.portalstatistics.PortalStatisticsVO; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j @Service @Transactional @RequiredArgsConstructor public class EAILogServiceImpl implements EAILogService { - private final EAILogRepository eaiLogRepository; - private final ApplicationContext applicationContext; - private final EAILogDAOForEMS eaiLogDAOForEMS; + private final EAILogRepository eaiLogRepository; + private final ApplicationContext applicationContext; + private final EAILogDAOForEMS eaiLogDAOForEMS; + private final HttpAdapterExtraLogService httpAdapterExtraLogService; - @Override - public Page findAll(Pageable pageable, EAILogSearch eaiLogSearch) { - long time = CommonUtil.getCalendar(eaiLogSearch.getSearchStartTime()).getTimeInMillis(); - EAILog eaiLog = (EAILog) applicationContext.getBean(RollingTable.class, EAILog.class, time); - Page eaiLogDtos = eaiLogRepository.findAll(eaiLog.getClass(), pageable, eaiLogSearch); - return eaiLogDtos; - } + @Override + public Page findAll(Pageable pageable, EAILogSearch eaiLogSearch) { + long time = CommonUtil.getCalendar(eaiLogSearch.getSearchStartTime()).getTimeInMillis(); + EAILog eaiLog = (EAILog) applicationContext.getBean(RollingTable.class, EAILog.class, time); + Page eaiLogDtos = eaiLogRepository.findAll(eaiLog.getClass(), pageable, eaiLogSearch); + return eaiLogDtos; + } - public Map getTransactionCounts(EAILogSearch eaiLogSearch){ - long time = CommonUtil.getCalendar(eaiLogSearch.getSearchStartTime()).getTimeInMillis(); - EAILog eaiLog = (EAILog) applicationContext.getBean(RollingTable.class, EAILog.class, time); - Map detailedCounts = eaiLogRepository.countDetailedTransactions(eaiLog.getClass(), eaiLogSearch); - return detailedCounts; - } + public Map getTransactionCounts(EAILogSearch eaiLogSearch) { + long time = CommonUtil.getCalendar(eaiLogSearch.getSearchStartTime()).getTimeInMillis(); + EAILog eaiLog = (EAILog) applicationContext.getBean(RollingTable.class, EAILog.class, time); + Map detailedCounts = eaiLogRepository.countDetailedTransactions(eaiLog.getClass(), eaiLogSearch); + return detailedCounts; + } - @Override - public EAILogDto getById(EAILogId eaiLogId) { - long time = CommonUtil.getCalendar(eaiLogId.getMsgdpstyms()).getTimeInMillis(); - EAILog eaiLog = (EAILog) applicationContext.getBean(RollingTable.class, EAILog.class, time); - return eaiLogRepository.getById(eaiLog.getClass(), eaiLogId); - } + @Override + public EAILogDto getById(EAILogId eaiLogId) { + long time = CommonUtil.getCalendar(eaiLogId.getMsgdpstyms()).getTimeInMillis(); + EAILog eaiLog = (EAILog) applicationContext.getBean(RollingTable.class, EAILog.class, time); + return eaiLogRepository.getById(eaiLog.getClass(), eaiLogId); + } + public List selectLogList(HashMap paramMap) { + return eaiLogDAOForEMS.selectLogList(paramMap); + } - public List selectLogList(HashMap paramMap) { - return eaiLogDAOForEMS.selectLogList(paramMap); - } + @Override + public ApiLogDTO getApiLog(String txDate, String apiServiceNumber) { + + long time = CommonUtil.getCalendar(txDate).getTimeInMillis(); + EAILog eaiLogTargetObject = (EAILog) applicationContext.getBean(RollingTable.class, EAILog.class, time); + List eaiLogs = eaiLogRepository.getApiLog(eaiLogTargetObject.getClass(), apiServiceNumber); + eaiLogs.sort((l1, l2) -> l1.getId().getEaisvcserno().compareTo(l2.getId().getEaisvcserno())); + + ApiLogDTO apiLogDTO = new ApiLogDTO(); + apiLogDTO.setApiServiceNumber(apiServiceNumber); + eaiLogs.stream().findFirst().ifPresent(log -> apiLogDTO.setTxDate(log.getId().getMsgdpstyms())); + + List apiMessageLogDTOs = new LinkedList<>(); + + for (EAILog eaiLog : eaiLogs) { + ApiMessageLogDTO apiMessageLogDTO = new ApiMessageLogDTO(); + + apiMessageLogDTO.setApiServiceNumber(apiServiceNumber); + apiMessageLogDTO.setTxDate(eaiLog.getMsgprcssyms()); + apiMessageLogDTO.setProcessNumber(Integer.parseInt(eaiLog.getId().getLogprcssserno())); + apiMessageLogDTO.setQueryString(""); + apiMessageLogDTO.setClientId(eaiLog.getClientid()); + apiMessageLogDTO.setKeymgtmsgctnt(eaiLog.getKeymgtmsgctnt()); + + String body = StringUtils.substringAfter(eaiLog.getBzwkdatactnt(), '|'); + body = StringUtils.substringBeforeLast(body, "|"); + + apiMessageLogDTO.setBody(body); + apiMessageLogDTOs.add(apiMessageLogDTO); + } + + Map extraLogs = httpAdapterExtraLogService.findByGuid(apiServiceNumber); + for (ApiMessageLogDTO apiMessageLogDTO : apiMessageLogDTOs) { + HttpAdapterExtraLog httpAdapterExtraLog = extraLogs.get(apiMessageLogDTO.getProcessNumber()); + + if (httpAdapterExtraLog != null) { + apiMessageLogDTO.setHeader(httpAdapterExtraLog.getHeaderContent()); + apiMessageLogDTO.setMethod(httpAdapterExtraLog.getHttpMethod()); + apiMessageLogDTO.setHttpStatus(httpAdapterExtraLog.getHttpStatus()); + apiMessageLogDTO.setUrl(httpAdapterExtraLog.getUrl()); + } + } + + // 처리 시간 계산 + for (int i = 0; i < apiMessageLogDTOs.size(); i++) { + ApiMessageLogDTO dto = apiMessageLogDTOs.get(i); + EAILog eaiLog = eaiLogs.get(i); + + // msgprcssyms - msgdpstyms + long receivedTime = CommonUtil.getCalendar(eaiLog.getId().getMsgdpstyms()).getTimeInMillis(); + long processedTime = CommonUtil.getCalendar(eaiLog.getMsgprcssyms()).getTimeInMillis(); + long processingTime = processedTime - receivedTime; + dto.setProcessingTime(processingTime); + } + + // 전체 처리 시간 계산 (첫 수신 시각 ~ 마지막 처리 시각) + if (!eaiLogs.isEmpty()) { + long firstReceivedTime = CommonUtil.getCalendar(eaiLogs.get(0).getId().getMsgdpstyms()).getTimeInMillis(); + long lastProcessedTime = CommonUtil.getCalendar(eaiLogs.get(eaiLogs.size() - 1).getMsgprcssyms()).getTimeInMillis(); + long totalProcessingTime = lastProcessedTime - firstReceivedTime; + apiLogDTO.setTotalProcessingTime(totalProcessingTime); + } + + apiLogDTO.setLogs(apiMessageLogDTOs); + return apiLogDTO; + } } diff --git a/src/main/java/com/eactive/eai/rms/data/entity/onl/logger/HttpAdapterExtraLogService.java b/src/main/java/com/eactive/eai/rms/data/entity/onl/logger/HttpAdapterExtraLogService.java index 2fa2c86..6441b7c 100644 --- a/src/main/java/com/eactive/eai/rms/data/entity/onl/logger/HttpAdapterExtraLogService.java +++ b/src/main/java/com/eactive/eai/rms/data/entity/onl/logger/HttpAdapterExtraLogService.java @@ -1,16 +1,28 @@ package com.eactive.eai.rms.data.entity.onl.logger; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + import javax.transaction.Transactional; import org.springframework.stereotype.Service; import com.eactive.eai.data.entity.onl.logger.HttpAdapterExtraLog; import com.eactive.eai.data.entity.onl.logger.HttpAdapterExtraLogId; +import com.eactive.eai.data.entity.onl.logger.QHttpAdapterExtraLog; import com.eactive.eai.data.jpa.AbstractDataService; +import com.querydsl.core.types.dsl.BooleanExpression; @Service @Transactional public class HttpAdapterExtraLogService extends AbstractDataService { + public Map findByGuid(String guid) { + BooleanExpression predicate = QHttpAdapterExtraLog.httpAdapterExtraLog.id.guid.eq(guid); + return StreamSupport.stream(repository.findAll(predicate).spliterator(), false) + .collect(Collectors.toMap(log -> log.getId().getServiceProcessNumber(), log -> log)); + } + } diff --git a/src/main/java/com/eactive/eai/rms/ext/kakaocard/cm/TempController.java b/src/main/java/com/eactive/eai/rms/ext/kakaocard/cm/TempController.java index 915cf5e..a93d396 100644 --- a/src/main/java/com/eactive/eai/rms/ext/kakaocard/cm/TempController.java +++ b/src/main/java/com/eactive/eai/rms/ext/kakaocard/cm/TempController.java @@ -199,7 +199,7 @@ public class TempController implements InterceptorSkipController { result = "success"; } catch (Exception e) { - System.out.println(e); +// System.out.println(e); result = e.getMessage(); } diff --git a/src/main/java/com/eactive/eai/rms/onl/adapter/info/AdapterInfosManager.java b/src/main/java/com/eactive/eai/rms/onl/adapter/info/AdapterInfosManager.java index 223c151..5822d5c 100644 --- a/src/main/java/com/eactive/eai/rms/onl/adapter/info/AdapterInfosManager.java +++ b/src/main/java/com/eactive/eai/rms/onl/adapter/info/AdapterInfosManager.java @@ -191,13 +191,13 @@ public class AdapterInfosManager { public String toString() { - System.out.println("---------------- Display START -------------------"); +// System.out.println("---------------- Display START -------------------"); Collection coll = AdapterInfosManager.adaptersMap.values(); Iterator iter = coll.iterator(); while (iter.hasNext()) { AdapterInfosVO tempVO = iter.next(); - System.out.println(tempVO.toString()); +// System.out.println(tempVO.toString()); } return new String("---------------- Display END -------------------" ); @@ -254,7 +254,7 @@ public class AdapterInfosManager { + "\n AND B.PRPTYGROUPNAME = TAB5.PRPTYGROUPNAME " + "\n ORDER BY 1, 3, 4 " + "\n WITH UR"; - System.out.println(SqlString); +// System.out.println(SqlString); return SqlString; } diff --git a/src/main/java/com/eactive/eai/rms/onl/apim/apigroup/ui/ApiGroupUISearch.java b/src/main/java/com/eactive/eai/rms/onl/apim/apigroup/ui/ApiGroupUISearch.java index c2c4716..47bf7e5 100644 --- a/src/main/java/com/eactive/eai/rms/onl/apim/apigroup/ui/ApiGroupUISearch.java +++ b/src/main/java/com/eactive/eai/rms/onl/apim/apigroup/ui/ApiGroupUISearch.java @@ -7,4 +7,5 @@ public class ApiGroupUISearch { private String searchGroupName; private String searchGroupDesc; private String searchDisplayYn; + private String searchApiFullPath; } diff --git a/src/main/java/com/eactive/eai/rms/onl/apim/approval/PortalApprovalManService.java b/src/main/java/com/eactive/eai/rms/onl/apim/approval/PortalApprovalManService.java index 19c4fe8..ddaf85b 100644 --- a/src/main/java/com/eactive/eai/rms/onl/apim/approval/PortalApprovalManService.java +++ b/src/main/java/com/eactive/eai/rms/onl/apim/approval/PortalApprovalManService.java @@ -61,14 +61,14 @@ public class PortalApprovalManService extends BaseService { public Page selectList(Pageable pageable, PortalApprovalUISearch portalApprovalUISearch) { Page approval = approvalService.findAll(pageable, portalApprovalUISearch); - return approval.map(entity -> getDetailWithMaskOption(entity.getId(), true)); + return approval.map(entity -> getDetailWithMaskOption(entity.getId(), false)); } /** * 마스킹된 상세 정보 조회 */ public PortalApprovalUI selectDetail(String id) { - return getDetailWithMaskOption(id, true); + return getDetailWithMaskOption(id, false); } /** diff --git a/src/main/java/com/eactive/eai/rms/onl/apim/approval/credential/CredentialUIMapper.java b/src/main/java/com/eactive/eai/rms/onl/apim/approval/credential/CredentialUIMapper.java index e3c7ff8..208dfa8 100644 --- a/src/main/java/com/eactive/eai/rms/onl/apim/approval/credential/CredentialUIMapper.java +++ b/src/main/java/com/eactive/eai/rms/onl/apim/approval/credential/CredentialUIMapper.java @@ -28,7 +28,7 @@ public abstract class CredentialUIMapper implements GenericMapper기능:

+ *
    + *
  • 세션 체크 우회 (InterceptorSkipController)
  • + *
  • JSON 변환 유틸리티 (pretty print 지원)
  • + *
  • 응답 크기 포맷팅
  • + *
+ * + *

사용 예:

+ *
+ * {@code
+ * @Controller
+ * public class MyApiController extends BaseRestController {
+ *     @RequestMapping(value = "/api/something.json", produces = JSON_CONTENT_TYPE)
+ *     @ResponseBody
+ *     public String getSomething(@RequestParam(value = "pretty", required = false) Boolean pretty) {
+ *         List data = service.getData();
+ *         return toJson(data, pretty);
+ *     }
+ * }
+ * }
+ * 
+ */ +public abstract class BaseRestController implements InterceptorSkipController { + + /** + * JSON 응답 Content-Type + */ + protected static final String JSON_CONTENT_TYPE = "application/json;charset=utf-8"; + + /** + * 객체를 JSON 문자열로 변환 + * + * @param object 변환할 객체 + * @param pretty true면 들여쓰기 적용 (null이면 false로 처리) + * @return JSON 문자열 + */ + protected String toJson(Object object, Boolean pretty) { + GsonBuilder builder = new GsonBuilder().serializeNulls(); + if (Boolean.TRUE.equals(pretty)) { + builder.setPrettyPrinting(); + } + return builder.create().toJson(object); + } + + /** + * 객체를 JSON 문자열로 변환 (기본: 한 줄 출력) + * + * @param object 변환할 객체 + * @return JSON 문자열 + */ + protected String toJson(Object object) { + return toJson(object, false); + } + + /** + * 바이트 크기를 읽기 쉬운 형식으로 변환 + * 예: 1024 -> "1.0 KB", 1048576 -> "1.0 MB" + * + * @param bytes 바이트 수 + * @return 포맷된 문자열 + */ + protected String formatByteSize(long bytes) { + if (bytes < 1024) { + return bytes + " B"; + } else if (bytes < 1024 * 1024) { + return String.format("%.1f KB", bytes / 1024.0); + } else { + return String.format("%.1f MB", bytes / (1024.0 * 1024.0)); + } + } + + /** + * JSON 문자열의 바이트 크기 계산 (UTF-8 기준) + * + * @param json JSON 문자열 + * @return 바이트 수 + */ + protected int getJsonByteSize(String json) { + return json.getBytes(StandardCharsets.UTF_8).length; + } + + /** + * JSON 문자열의 포맷된 바이트 크기 반환 + * + * @param json JSON 문자열 + * @return 포맷된 크기 문자열 + */ + protected String getFormattedJsonSize(String json) { + return formatByteSize(getJsonByteSize(json)); + } +} diff --git a/src/main/java/com/eactive/eai/rms/onl/apim/obp/ObpApiController.java b/src/main/java/com/eactive/eai/rms/onl/apim/obp/ObpApiController.java new file mode 100644 index 0000000..6faca83 --- /dev/null +++ b/src/main/java/com/eactive/eai/rms/onl/apim/obp/ObpApiController.java @@ -0,0 +1,69 @@ +package com.eactive.eai.rms.onl.apim.obp; + +import com.eactive.eai.rms.common.datasource.DataSourceContextHolder; +import com.eactive.eai.rms.common.datasource.DataSourceTypeManager; +import com.eactive.eai.rms.onl.apim.common.BaseRestController; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; + +import java.util.List; + +/** + * OBP API 목록 조회 REST Controller + * + *

TSEAIHE01과 TSEAIHS04를 조인하여 API 정보를 JSON으로 제공

+ * + * @see BaseRestController 세션 체크 우회 및 JSON 유틸리티 제공 + */ +@Controller +public class ObpApiController extends BaseRestController { + + private static final Logger log = LoggerFactory.getLogger(ObpApiController.class); + + private final ObpApiService obpApiService; + + public ObpApiController(ObpApiService obpApiService) { + this.obpApiService = obpApiService; + } + + /** + * 전체 API 목록 조회 + * + * @param pretty true면 JSON 들여쓰기 적용 + * @return JSON Array + */ + @RequestMapping( + value = "/kjb/apis.json", + produces = JSON_CONTENT_TYPE + ) + @ResponseBody + public String getApis( + @RequestParam(value = "pretty", required = false) Boolean pretty) { + log.debug("OBP API 목록 조회 요청: pretty={}", pretty); + long startTime = System.currentTimeMillis(); + + // APIGW 데이터소스 강제 설정 + DataSourceContextHolder.setDataSourceType( + DataSourceTypeManager.getDataSourceType(DataSourceTypeManager.APIGW)); + + try { + List apis = obpApiService.findAllApis(); + + String json = toJson(apis, pretty); + + long elapsed = System.currentTimeMillis() - startTime; + log.info("OBP API 목록 조회 완료: count={}, size={}, elapsed={}ms", + apis.size(), getFormattedJsonSize(json), elapsed); + + return json; + + } catch (Exception e) { + log.error("OBP API 목록 조회 실패", e); + throw e; + } + } +} diff --git a/src/main/java/com/eactive/eai/rms/onl/apim/obp/ObpApiDTO.java b/src/main/java/com/eactive/eai/rms/onl/apim/obp/ObpApiDTO.java new file mode 100644 index 0000000..e978927 --- /dev/null +++ b/src/main/java/com/eactive/eai/rms/onl/apim/obp/ObpApiDTO.java @@ -0,0 +1,44 @@ +package com.eactive.eai.rms.onl.apim.obp; + +import lombok.Builder; +import lombok.Getter; + +/** + * OBP API 목록 조회 DTO + * + *

TSEAIHE01과 TSEAIHS04 테이블을 조인하여 API 정보를 제공

+ */ +@Getter +@Builder +public class ObpApiDTO { + + /** + * API ID (TSEAIHE01.EAISVCNAME) + */ + private final String id; + + /** + * API 이름 (TSEAIHE01.EAISVCDESC) + */ + private final String name; + + /** + * 활성화 여부 (TSEAIHE01.USEYN = '1' -> true) + */ + private final boolean enabled; + + /** + * API URL (TSEAIHS04.APIFULLPATH에서 '|' 이후 부분) + */ + private final String url; + + /** + * HTTP Method (TSEAIHS04.APIFULLPATH에서 '|' 이전 부분) + */ + private final String method; + + /** + * 인증 타입 (TSEAIHE01.AUTHTYPE) + */ + private final String authType; +} diff --git a/src/main/java/com/eactive/eai/rms/onl/apim/obp/ObpApiService.java b/src/main/java/com/eactive/eai/rms/onl/apim/obp/ObpApiService.java new file mode 100644 index 0000000..54b8b8e --- /dev/null +++ b/src/main/java/com/eactive/eai/rms/onl/apim/obp/ObpApiService.java @@ -0,0 +1,91 @@ +package com.eactive.eai.rms.onl.apim.obp; + +import com.eactive.eai.data.entity.onl.message.QEAIMessageEntity; +import com.eactive.eai.data.entity.onl.stdmessage.QStandardMessageInfo; +import com.querydsl.core.Tuple; +import com.querydsl.jpa.impl.JPAQueryFactory; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; + +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import java.util.List; +import java.util.stream.Collectors; + +/** + * OBP API 목록 조회 Service + * + *

APIGW 데이터소스의 TSEAIHE01, TSEAIHS04 테이블에서 API 정보 조회

+ */ +@Service +public class ObpApiService { + + @PersistenceContext + private EntityManager entityManager; + + private JPAQueryFactory getQueryFactory() { + return new JPAQueryFactory(entityManager); + } + + /** + * 전체 API 목록 조회 + * + * @return API 목록 + */ + public List findAllApis() { + QEAIMessageEntity he01 = QEAIMessageEntity.eAIMessageEntity; + QStandardMessageInfo hs04 = QStandardMessageInfo.standardMessageInfo; + + List results = getQueryFactory() + .select( + he01.eaisvcname, + he01.eaisvcdesc, + he01.useyn, + he01.authtype, + hs04.apifullpath + ) + .from(he01) + .leftJoin(hs04).on(he01.eaisvcname.eq(hs04.eaisvcname)) + .orderBy(he01.eaisvcname.asc()) + .fetch(); + + return results.stream() + .map(this::toDto) + .collect(Collectors.toList()); + } + + /** + * Tuple을 DTO로 변환 + */ + private ObpApiDTO toDto(Tuple tuple) { + QEAIMessageEntity he01 = QEAIMessageEntity.eAIMessageEntity; + QStandardMessageInfo hs04 = QStandardMessageInfo.standardMessageInfo; + + String eaisvcname = tuple.get(he01.eaisvcname); + String eaisvcdesc = tuple.get(he01.eaisvcdesc); + String useyn = tuple.get(he01.useyn); + String authtype = tuple.get(he01.authtype); + String apifullpath = tuple.get(hs04.apifullpath); + + // APIFULLPATH 파싱: "POST|/mapi/kjbank/..." -> method="POST", url="/mapi/kjbank/..." + String method = null; + String url = null; + if (StringUtils.isNotBlank(apifullpath) && apifullpath.contains("|")) { + int pipeIndex = apifullpath.indexOf('|'); + method = apifullpath.substring(0, pipeIndex); + url = apifullpath.substring(pipeIndex + 1); + } + + // USEYN이 "1"이면 enabled = true + boolean enabled = "1".equals(useyn); + + return ObpApiDTO.builder() + .id(eaisvcname) + .name(eaisvcdesc) + .enabled(enabled) + .url(url) + .method(method) + .authType(authtype) + .build(); + } +} diff --git a/src/main/java/com/eactive/eai/rms/onl/apim/obp/ObpGwMetricAfterDTO.java b/src/main/java/com/eactive/eai/rms/onl/apim/obp/ObpGwMetricAfterDTO.java new file mode 100644 index 0000000..c7c2626 --- /dev/null +++ b/src/main/java/com/eactive/eai/rms/onl/apim/obp/ObpGwMetricAfterDTO.java @@ -0,0 +1,101 @@ +package com.eactive.eai.rms.onl.apim.obp; + +import com.eactive.apim.portal.obp.entity.ObpGwMetric; +import com.google.gson.annotations.SerializedName; +import lombok.Data; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +/** + * ObpGwMetric JSON 응답 DTO (after-timeslice 엔드포인트용) + * + *

예시 출력 형식에 맞춤:

+ *
+ * {
+ *   "apiId": "1ba56b82-a9c9-41e2-b3ab-ff9377667d56",
+ *   "apiName": "[해외송금] 가상계좌 입금실패 정보조회",
+ *   "apiRoutingUri": "/api/obs/remittance/remittancefail*",
+ *   "httpMethod": "POST",
+ *   "orgId": "ORG001",
+ *   "partnerCode": "000008-01",
+ *   "appId": "APP001",
+ *   "appKey": "l7xx2fd7efc523794be5a1b126873a5de613",
+ *   "requestUri": "-1",
+ *   "timeslice": "2025-12-02T03:00:00.000+0000",
+ *   "attemptedCount": 3,
+ *   "completedCount": 3
+ * }
+ * 
+ */ +@Data +public class ObpGwMetricAfterDTO { + + private static final DateTimeFormatter ISO8601_MILLIS_FORMATTER = + DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'+0000'"); + + @SerializedName("apiId") + private String apiId; + + @SerializedName("apiName") + private String apiName; + + @SerializedName("apiRoutingUri") + private String apiRoutingUri; + + @SerializedName("httpMethod") + private String httpMethod; + + @SerializedName("orgId") + private String orgId; + + @SerializedName("partnerCode") + private String partnerCode; + + @SerializedName("appId") + private String appId; + + @SerializedName("appKey") + private String appKey; + + @SerializedName("requestUri") + private String requestUri; + + @SerializedName("timeslice") + private String timeslice; + + @SerializedName("attemptedCount") + private Long attemptedCount; + + @SerializedName("completedCount") + private Long completedCount; + + /** + * Entity → DTO 변환 + */ + public static ObpGwMetricAfterDTO from(ObpGwMetric entity) { + ObpGwMetricAfterDTO dto = new ObpGwMetricAfterDTO(); + dto.setApiId(entity.getApiId()); + dto.setApiName(entity.getApiName()); + dto.setApiRoutingUri(entity.getUri()); + dto.setHttpMethod(entity.getMethod()); + dto.setOrgId(entity.getOrgId()); + dto.setPartnerCode(entity.getPartnerCode()); + dto.setAppId(entity.getAppId()); + dto.setAppKey(entity.getClientId()); + dto.setRequestUri(entity.getRequestUri()); + dto.setTimeslice(formatTimeslice(entity.getTimeslice())); + dto.setAttemptedCount(entity.getAttemptedCount()); + dto.setCompletedCount(entity.getCompletedCount()); + return dto; + } + + /** + * timeslice를 ISO8601 형식 (밀리초 포함) + UTC 시간대로 변환 + * 예: 2025-12-02T03:00:00.000+0000 + */ + private static String formatTimeslice(LocalDateTime timeslice) { + if (timeslice == null) return null; + return timeslice.format(ISO8601_MILLIS_FORMATTER); + } +} diff --git a/src/main/java/com/eactive/eai/rms/onl/apim/obp/ObpGwMetricController.java b/src/main/java/com/eactive/eai/rms/onl/apim/obp/ObpGwMetricController.java new file mode 100644 index 0000000..81dc55b --- /dev/null +++ b/src/main/java/com/eactive/eai/rms/onl/apim/obp/ObpGwMetricController.java @@ -0,0 +1,117 @@ +package com.eactive.eai.rms.onl.apim.obp; + +import com.eactive.eai.rms.onl.apim.common.BaseRestController; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.List; + +/** + * ObpGwMetric REST Controller + * API Gateway 시간별 거래량 지표 조회 API + * + * @see BaseRestController 세션 체크 우회 및 JSON 유틸리티 제공 + */ +@Controller +public class ObpGwMetricController extends BaseRestController { + + private static final Logger log = LoggerFactory.getLogger(ObpGwMetricController.class); + + private static final DateTimeFormatter PATH_FORMATTER = + DateTimeFormatter.ofPattern("yyyy-MM-dd_HH:mm:ss"); + + private final ObpGwMetricManService obpGwMetricManService; + + public ObpGwMetricController(ObpGwMetricManService obpGwMetricManService) { + this.obpGwMetricManService = obpGwMetricManService; + } + + /** + * 특정 시간대의 GwMetric 데이터 조회 + * + * @param datetime 조회 시간대 (형식: yyyy-MM-dd_HH:mm:ss) + * @param pretty true면 JSON 들여쓰기 적용 + * @return JSON Array + */ + @RequestMapping( + value = "/kjb/gw-metrics/{datetime}.json", + produces = JSON_CONTENT_TYPE + ) + @ResponseBody + public String getGwMetrics( + @PathVariable("datetime") String datetime, + @RequestParam(value = "pretty", required = false) Boolean pretty) { + log.debug("GwMetric 조회 요청: datetime={}, pretty={}", datetime, pretty); + long startTime = System.currentTimeMillis(); + + try { + // Path 파라미터 파싱 + LocalDateTime targetTime = LocalDateTime.parse(datetime, PATH_FORMATTER); + LocalDateTime timeslice = targetTime.withMinute(0).withSecond(0).withNano(0); + + // 조회 + List metrics = obpGwMetricManService.findByTimeslice(timeslice); + + // JSON 변환 (GSON) + String json = toJson(metrics, pretty); + + long elapsed = System.currentTimeMillis() - startTime; + log.info("GwMetric 조회 완료: datetime={}, count={}, size={}, elapsed={}ms", + datetime, metrics.size(), getFormattedJsonSize(json), elapsed); + + return json; + + } catch (Exception e) { + log.error("GwMetric 조회 실패: datetime={}", datetime, e); + throw e; + } + } + + /** + * 특정 시간대 이후의 GwMetric 데이터 조회 + * + * @param datetime 조회 시작 시간대 (형식: yyyy-MM-dd_HH:mm:ss) + * @param pretty true면 JSON 들여쓰기 적용 + * @return JSON Array (해당 시간대 이후의 모든 지표) + */ + @RequestMapping( + value = "/kjb/gw-metrics/after-timeslice/{datetime}.json", + produces = JSON_CONTENT_TYPE + ) + @ResponseBody + public String getGwMetricsAfterTimeslice( + @PathVariable("datetime") String datetime, + @RequestParam(value = "pretty", required = false) Boolean pretty) { + log.debug("GwMetric after-timeslice 조회 요청: datetime={}, pretty={}", datetime, pretty); + long startTime = System.currentTimeMillis(); + + try { + // Path 파라미터 파싱 + LocalDateTime targetTime = LocalDateTime.parse(datetime, PATH_FORMATTER); + LocalDateTime timeslice = targetTime.withMinute(0).withSecond(0).withNano(0); + + // 조회 + List metrics = obpGwMetricManService.findByTimesliceAfter(timeslice); + + // JSON 변환 (GSON) + String json = toJson(metrics, pretty); + + long elapsed = System.currentTimeMillis() - startTime; + log.info("GwMetric after-timeslice 조회 완료: datetime={}, count={}, size={}, elapsed={}ms", + datetime, metrics.size(), getFormattedJsonSize(json), elapsed); + + return json; + + } catch (Exception e) { + log.error("GwMetric after-timeslice 조회 실패: datetime={}", datetime, e); + throw e; + } + } +} diff --git a/src/main/java/com/eactive/eai/rms/onl/apim/obp/ObpGwMetricDTO.java b/src/main/java/com/eactive/eai/rms/onl/apim/obp/ObpGwMetricDTO.java new file mode 100644 index 0000000..c0d0a0a --- /dev/null +++ b/src/main/java/com/eactive/eai/rms/onl/apim/obp/ObpGwMetricDTO.java @@ -0,0 +1,102 @@ +package com.eactive.eai.rms.onl.apim.obp; + +import com.eactive.apim.portal.obp.entity.ObpGwMetric; +import com.google.gson.annotations.SerializedName; +import lombok.Data; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +/** + * ObpGwMetric JSON 응답 DTO + * Entity 주석에 명시된 JSON Key 사용 + */ +@Data +public class ObpGwMetricDTO { + + private static final DateTimeFormatter ISO8601_FORMATTER = + DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'+0000'"); + + @SerializedName("appKey") + private String clientId; + + @SerializedName("clusterHostname") + private String hostname; + + @SerializedName("timeslice") + private String timeslice; + + @SerializedName("apiName") + private String apiName; + + @SerializedName("apiRoutingUri") + private String uri; + + @SerializedName("httpMethod") + private String method; + + @SerializedName("attemptedCount") + private Long attemptedCount; + + @SerializedName("completedCount") + private Long completedCount; + + // EAPIM ID + @SerializedName("apiIdByEapim") + private String apiId; + + @SerializedName("appIdByEapim") + private String appId; + + @SerializedName("orgIdByEapim") + private String orgId; + + // AS-IS 호환용 필드 + @SerializedName("appId") + private String appIdByCaPortal; + + @SerializedName("portalOrgId") + private String orgIdByCaPortal; + + @SerializedName("portalApiId") + private String apiIdByCaPortal; + + @SerializedName("partnerCode") + private String partnerCode; + + @SerializedName("apiId") + private String apiIdByCa; + + /** + * Entity → DTO 변환 + */ + public static ObpGwMetricDTO from(ObpGwMetric entity) { + ObpGwMetricDTO dto = new ObpGwMetricDTO(); + dto.setClientId(entity.getClientId()); + dto.setHostname(entity.getHostname()); + dto.setTimeslice(formatTimeslice(entity.getTimeslice())); + dto.setApiName(entity.getApiName()); + dto.setUri(entity.getUri()); + dto.setMethod(entity.getMethod()); + dto.setAttemptedCount(entity.getAttemptedCount()); + dto.setCompletedCount(entity.getCompletedCount()); + dto.setApiId(entity.getApiId()); + dto.setAppId(entity.getAppId()); + dto.setOrgId(entity.getOrgId()); + dto.setAppIdByCaPortal(entity.getAppIdByCaPortal()); + dto.setOrgIdByCaPortal(entity.getOrgIdByCaPortal()); + dto.setApiIdByCaPortal(entity.getApiIdByCaPortal()); + dto.setPartnerCode(entity.getPartnerCode()); + dto.setApiIdByCa(entity.getApiIdByCa()); + return dto; + } + + /** + * timeslice를 ISO8601 형식 + UTC 시간대로 변환 + * 예: 2025-12-02T01:00:00+0000 + */ + private static String formatTimeslice(LocalDateTime timeslice) { + if (timeslice == null) return null; + return timeslice.format(ISO8601_FORMATTER); + } +} diff --git a/src/main/java/com/eactive/eai/rms/onl/apim/obp/ObpGwMetricManService.java b/src/main/java/com/eactive/eai/rms/onl/apim/obp/ObpGwMetricManService.java new file mode 100644 index 0000000..61fc417 --- /dev/null +++ b/src/main/java/com/eactive/eai/rms/onl/apim/obp/ObpGwMetricManService.java @@ -0,0 +1,293 @@ +package com.eactive.eai.rms.onl.apim.obp; + +import com.eactive.apim.portal.obp.entity.ObpGwMetric; +import com.eactive.eai.rms.common.base.BaseService; +import com.eactive.eai.rms.common.datasource.DataSourceContextHolder; +import com.eactive.eai.rms.common.datasource.DataSourceTypeManager; +import com.eactive.eai.rms.common.util.CommonUtil; +import com.eactive.eai.rms.data.entity.onl.apim.obp.ObpGwMetricJpaDAO; +import com.eactive.eai.rms.data.entity.onl.apim.obp.ObpGwMetricService; +import com.eactive.eai.rms.data.entity.onl.apim.obp.ObpGwMetricVO; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; +import java.util.*; +import java.util.stream.Collectors; + +/** + * ObpGwMetric Man Service - 비즈니스 로직 + * API Gateway 로그 데이터를 집계하여 ObpGwMetric 엔티티로 저장 + */ +@Service +@Transactional(transactionManager = "transactionManagerForEMS") +public class ObpGwMetricManService extends BaseService { + + private static final Logger log = LoggerFactory.getLogger(ObpGwMetricManService.class); + + /** + * 기본 집계 범위 시간 (단위: 시간) + * - 1: 직전 1시간만 집계 + * - 5: 직전 5시간까지 각각 1시간 단위로 집계 + * + *

Job 파라미터로 오버라이드 가능 (aggregation.hour.range)

+ */ + public static final int DEFAULT_AGGREGATION_HOUR_RANGE = 1; + + private static final DateTimeFormatter TIMESLICE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHH0000000"); + private static final DateTimeFormatter TIMESTAMP_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS"); + private static final DateTimeFormatter YYYYMMDD_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd"); + + private final ObpGwMetricJpaDAO obpGwMetricJpaDAO; + private final ObpGwMetricService obpGwMetricService; + + public ObpGwMetricManService(ObpGwMetricJpaDAO obpGwMetricJpaDAO, ObpGwMetricService obpGwMetricService) { + this.obpGwMetricJpaDAO = obpGwMetricJpaDAO; + this.obpGwMetricService = obpGwMetricService; + } + + /** + * 다중 시간대 집계 실행 (Job에서 호출) - 기본 집계 범위 사용 + */ + public void executeHourlyAggregation() { + executeHourlyAggregation(DEFAULT_AGGREGATION_HOUR_RANGE); + } + + /** + * 다중 시간대 집계 실행 (Job에서 호출) + * + * @param aggregationHourRange 집계 범위 시간 (단위: 시간) + * 예: 1=직전 1시간, 3=직전 3시간 각각 집계 + */ + public void executeHourlyAggregation(int aggregationHourRange) { + long totalStartTime = System.currentTimeMillis(); + LocalDateTime now = LocalDateTime.now(); + LocalDateTime baseHour = now.truncatedTo(ChronoUnit.HOURS); // 현재 시간의 정각 + + log.info("========== ObpGwMetric 집계 시작 =========="); + log.info("실행 시간: {}, aggregationHourRange: {}", now, aggregationHourRange); + + for (int i = 1; i <= aggregationHourRange; i++) { + LocalDateTime targetHour = baseHour.minusHours(i); + log.info("집계 대상 시간대: {}", targetHour); + createHourlyData(targetHour); + } + + long totalElapsed = System.currentTimeMillis() - totalStartTime; + log.info("========== ObpGwMetric 집계 완료 =========="); + log.info("전체 집계 소요시간: {}ms ({}초)", totalElapsed, String.format("%.2f", totalElapsed / 1000.0)); + } + + /** + * 1시간 단위 ObpGwMetric 데이터 생성 (UPSERT) + */ + public void createHourlyData(LocalDateTime targetHour) { + long jobStartTime = System.currentTimeMillis(); + log.info("ObpGwMetric 집계 시작: targetHour={}", targetHour); + + try { + String timeslice = targetHour.format(TIMESLICE_FORMATTER); + String yyyymmdd = targetHour.format(YYYYMMDD_FORMATTER); + String tableName = CommonUtil.getLogTable(yyyymmdd, true); + String startTime = targetHour.format(TIMESTAMP_FORMATTER); + String endTime = targetHour.plusHours(1).minusNanos(1).format(TIMESTAMP_FORMATTER); + + // ═══════════════════════════════════════════════════════════════════ + // 1단계: GW DB에서 EAIBZWKDSTCD 코드 목록 조회 + // ═══════════════════════════════════════════════════════════════════ + long step1Start = System.currentTimeMillis(); + DataSourceContextHolder.setDataSourceType( + DataSourceTypeManager.getDataSourceType(DataSourceTypeManager.APIGW)); + + List bzwkCodes = obpGwMetricJpaDAO.selectAllBzwkCodes(); + log.info("[1단계] EAIBZWKDSTCD 코드 조회 완료: count={}, elapsed={}ms", + bzwkCodes.size(), System.currentTimeMillis() - step1Start); + + // ═══════════════════════════════════════════════════════════════════ + // 2단계: 코드별 개별 쿼리 실행 후 결과 병합 + // ═══════════════════════════════════════════════════════════════════ + long step2Start = System.currentTimeMillis(); + Map aggregatedMap = new HashMap<>(); + int totalLogCount = 0; + + for (String bzwkCode : bzwkCodes) { + long codeStart = System.currentTimeMillis(); + + List partialResult = obpGwMetricJpaDAO.selectHourlyMetricsByBzwkCode( + tableName, bzwkCode, startTime, endTime, timeslice); + + for (ObpGwMetricVO vo : partialResult) { + String key = vo.getAggregationKey(); + aggregatedMap.merge(key, vo, this::mergeMetricVO); + } + + if (!partialResult.isEmpty()) { + log.debug("[2단계] bzwkCode={}, 집계건수={}, elapsed={}ms", + bzwkCode, partialResult.size(), System.currentTimeMillis() - codeStart); + } + totalLogCount += partialResult.size(); + } + + List aggregatedList = new ArrayList<>(aggregatedMap.values()); + log.info("[2단계] 로그 집계 완료: 원본건수={}, 병합후건수={}, elapsed={}ms", + totalLogCount, aggregatedList.size(), System.currentTimeMillis() - step2Start); + + if (aggregatedList.isEmpty()) { + log.warn("집계 대상 데이터 없음: targetHour={}", targetHour); + return; + } + + // ═══════════════════════════════════════════════════════════════════ + // 3단계: 부가 정보 조회 (apiName, uri) - GW DB + // ═══════════════════════════════════════════════════════════════════ + long step3Start = System.currentTimeMillis(); + Set apiIds = aggregatedList.stream() + .map(ObpGwMetricVO::getApiId) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + + // URI 조회용 EAI 서비스 일련번호 (HTTP_ADAPTER_EXTRA_LOG.GUID와 매핑) + Set eaiSvcSernos = aggregatedList.stream() + .map(ObpGwMetricVO::getEaiSvcSerno) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + + Map apiNameMap = obpGwMetricJpaDAO.selectApiNames(apiIds); + ObpGwMetricJpaDAO.UriMethodResult uriMethodResult = obpGwMetricJpaDAO.selectUrisAndMethods(eaiSvcSernos); + Map uriMap = uriMethodResult.getUriMap(); + Map methodMap = uriMethodResult.getMethodMap(); + log.info("[3단계] 부가정보 조회 완료: apiCount={}, eaiSvcSernoCount={}, elapsed={}ms", + apiIds.size(), eaiSvcSernos.size(), System.currentTimeMillis() - step3Start); + + // ═══════════════════════════════════════════════════════════════════ + // 4단계: 지표 DB로 전환 후 appId 조회 및 UPSERT + // ═══════════════════════════════════════════════════════════════════ + long step4Start = System.currentTimeMillis(); + DataSourceContextHolder.setDataSourceType( + DataSourceTypeManager.getDataSourceType(DataSourceTypeManager.MONITORING)); + + Set orgIds = aggregatedList.stream() + .map(ObpGwMetricVO::getOrgId) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + Map appIdMap = obpGwMetricJpaDAO.selectAppIdsByOrgIds(orgIds); + + int insertCount = 0; + int updateCount = 0; + + for (ObpGwMetricVO vo : aggregatedList) { + LocalDateTime timesliceDateTime = vo.getTimesliceAsLocalDateTime(); + + Optional existing = obpGwMetricService.findByUniqueKey( + timesliceDateTime, + vo.getApiId(), + vo.getClientId(), + vo.getHostname(), + vo.getOrgId() + ); + + if (existing.isPresent()) { + // UPDATE: 기존 데이터 갱신 + ObpGwMetric entity = existing.get(); + entity.setAttemptedCount(vo.getAttemptedCount()); + entity.setCompletedCount(vo.getCompletedCount()); + entity.setApiName(apiNameMap.get(vo.getApiId())); + entity.setUri(uriMap.get(vo.getEaiSvcSerno())); // eaiSvcSerno로 URI 조회 + entity.setMethod(methodMap.get(vo.getEaiSvcSerno())); // eaiSvcSerno로 HTTP Method 조회 + entity.setAppId(appIdMap.get(vo.getOrgId())); + entity.setUpdateCount(entity.getUpdateCount() + 1); // 보정 횟수 증가 + entity.setUpdatedAt(LocalDateTime.now()); + entity.setUpdatedBy("GwMetricHourJob"); + obpGwMetricService.save(entity); + updateCount++; + } else { + // INSERT: 신규 데이터 생성 + ObpGwMetric entity = convertToEntity(vo, apiNameMap, uriMap, methodMap, appIdMap); + obpGwMetricService.save(entity); + insertCount++; + } + } + + log.info("[4단계] UPSERT 완료: INSERT={}, UPDATE={}, elapsed={}ms", + insertCount, updateCount, System.currentTimeMillis() - step4Start); + + long totalElapsed = System.currentTimeMillis() - jobStartTime; + log.info("ObpGwMetric 집계 완료: targetHour={}, 총 소요시간={}ms ({}초)", + targetHour, totalElapsed, totalElapsed / 1000.0); + + } catch (Exception e) { + log.error("ObpGwMetric 집계 실패: targetHour={}", targetHour, e); + throw e; + } + } + + /** + * ObpGwMetricVO 병합 (count 누적) + */ + private ObpGwMetricVO mergeMetricVO(ObpGwMetricVO existing, ObpGwMetricVO incoming) { + existing.setAttemptedCount( + (existing.getAttemptedCount() != null ? existing.getAttemptedCount() : 0L) + + (incoming.getAttemptedCount() != null ? incoming.getAttemptedCount() : 0L) + ); + existing.setCompletedCount( + (existing.getCompletedCount() != null ? existing.getCompletedCount() : 0L) + + (incoming.getCompletedCount() != null ? incoming.getCompletedCount() : 0L) + ); + return existing; + } + + /** + * VO → Entity 변환 + */ + private ObpGwMetric convertToEntity(ObpGwMetricVO vo, Map apiNameMap, + Map uriMap, Map methodMap, + Map appIdMap) { + ObpGwMetric entity = new ObpGwMetric(); + entity.setClientId(vo.getClientId()); + entity.setHostname(vo.getHostname()); + entity.setTimeslice(vo.getTimesliceAsLocalDateTime()); + entity.setApiId(vo.getApiId()); + entity.setOrgId(vo.getOrgId()); + entity.setAttemptedCount(vo.getAttemptedCount()); + entity.setCompletedCount(vo.getCompletedCount()); + entity.setApiName(apiNameMap.get(vo.getApiId())); + entity.setUri(uriMap.get(vo.getEaiSvcSerno())); // eaiSvcSerno로 URI 조회 + entity.setMethod(methodMap.get(vo.getEaiSvcSerno())); // eaiSvcSerno로 HTTP Method 조회 + entity.setAppId(appIdMap.get(vo.getOrgId())); + entity.setUpdateCount(0); + entity.setCreatedAt(LocalDateTime.now()); + entity.setCreatedBy("GwMetricHourJob"); + return entity; + } + + /** + * 특정 시간대의 GwMetric 데이터 조회 (REST API용) + */ + public List findByTimeslice(LocalDateTime timeslice) { + DataSourceContextHolder.setDataSourceType( + DataSourceTypeManager.getDataSourceType(DataSourceTypeManager.MONITORING)); + + List entities = obpGwMetricService.findByTimeslice(timeslice); + return entities.stream() + .map(ObpGwMetricDTO::from) + .collect(Collectors.toList()); + } + + /** + * 특정 시간대 이후의 GwMetric 데이터 조회 (REST API용) + * after-timeslice 엔드포인트에서 사용 + */ + public List findByTimesliceAfter(LocalDateTime timeslice) { + DataSourceContextHolder.setDataSourceType( + DataSourceTypeManager.getDataSourceType(DataSourceTypeManager.MONITORING)); + + List entities = obpGwMetricService.findByTimesliceGreaterThanEqual(timeslice); + return entities.stream() + .map(ObpGwMetricAfterDTO::from) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/com/eactive/eai/rms/onl/apim/portalorg/ObpPartnerCodeService.java b/src/main/java/com/eactive/eai/rms/onl/apim/portalorg/ObpPartnerCodeService.java new file mode 100644 index 0000000..38d186c --- /dev/null +++ b/src/main/java/com/eactive/eai/rms/onl/apim/portalorg/ObpPartnerCodeService.java @@ -0,0 +1,66 @@ +package com.eactive.eai.rms.onl.apim.portalorg; + +import com.eactive.eai.rms.data.entity.man.monitoringProperty.service.MonitoringPropertyService; +import com.eactive.ext.kjb.common.KjbObpException; +import com.eactive.ext.kjb.common.KjbProperty; +import com.eactive.ext.kjb.obp.KjbObpModule; +import com.eactive.ext.kjb.util.KjbPropertyInjector; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.HashMap; +import java.util.Map; + +/** + * OBP 파트너코드 조회 서비스 + */ +@Slf4j +@Service +public class ObpPartnerCodeService { + private static final String PROP_GROUP_ID = "Monitoring"; + + private final KjbPropertyInjector kjbPropertyInjector; + + @Autowired + public ObpPartnerCodeService(MonitoringPropertyService monitoringPropertyService) { + this.kjbPropertyInjector = new KjbPropertyInjector(monitoringPropertyService, PROP_GROUP_ID); + } + + /** + * 사업자등록번호로 OBP 파트너코드 조회 + * @param compRegNo 사업자등록번호 + * @return 조회 결과 (success, partnerCode 또는 message) + */ + public Map queryPartnerCode(String compRegNo) { + Map result = new HashMap<>(); + + try { + KjbProperty prop = kjbPropertyInjector.inject(new KjbProperty()); + KjbObpModule obpModule = KjbObpModule.getInstance(prop); + + String partnerCode = obpModule.queryPartnerCode(compRegNo); + + result.put("success", true); + result.put("partnerCode", partnerCode); + log.info("[OBP] 파트너코드 조회 성공 - compRegNo: {}, partnerCode: {}", compRegNo, partnerCode); + + } catch (IllegalArgumentException e) { + log.warn("[OBP] 파트너코드 조회 실패 - 잘못된 입력: {}", e.getMessage()); + result.put("success", false); + result.put("message", e.getMessage()); + + } catch (KjbObpException e) { + log.warn("[OBP] 파트너코드 조회 실패 - OBP 오류: {}", e.getMessage()); + result.put("success", false); + result.put("message", "OBP 조회 실패: " + e.getMessage()); + + } catch (Exception e) { + log.error("[OBP] 파트너코드 조회 중 오류 발생", e); + result.put("success", false); + result.put("message", "시스템 오류가 발생했습니다."); + } + + return result; + } +} diff --git a/src/main/java/com/eactive/eai/rms/onl/apim/portalorg/PortalOrgManController.java b/src/main/java/com/eactive/eai/rms/onl/apim/portalorg/PortalOrgManController.java index 809e32b..2783e36 100644 --- a/src/main/java/com/eactive/eai/rms/onl/apim/portalorg/PortalOrgManController.java +++ b/src/main/java/com/eactive/eai/rms/onl/apim/portalorg/PortalOrgManController.java @@ -13,6 +13,7 @@ import java.util.stream.Collectors; import com.eactive.eai.rms.onl.common.exception.BizException; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -25,13 +26,16 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestParam; +@Slf4j @Controller @RequiredArgsConstructor public class PortalOrgManController extends BaseAnnotationController { private final PortalOrgManService portalOrgManService; private final ComboService comboService; + private final ObpPartnerCodeService obpPartnerCodeService; @GetMapping(value = "/onl/apim/portalorg/portalOrgMan.view") public void view() { @@ -86,7 +90,7 @@ public class PortalOrgManController extends BaseAnnotationController { } @PostMapping(value = "/onl/apim/portalorg/portalOrgMan.json", params = "cmd=UPDATE") - public ResponseEntity update(PortalOrgUI portalOrgUI) throws IOException { + public ResponseEntity update(PortalOrgUI portalOrgUI) throws IOException { portalOrgManService.update(portalOrgUI); return ResponseEntity.ok().build(); } @@ -104,4 +108,15 @@ public class PortalOrgManController extends BaseAnnotationController { return ResponseEntity.ok(new GridResponse<>(page)); } + /** + * OBP 파트너코드 조회 + * @param compRegNo 사업자등록번호 + * @return partnerCode + */ + @PostMapping(value = "/onl/apim/portalorg/portalOrgMan.json", params = "cmd=QUERY_OBP_PARTNER_CODE") + public ResponseEntity> queryObpPartnerCode(@RequestParam("compRegNo") String compRegNo) { + Map result = obpPartnerCodeService.queryPartnerCode(compRegNo); + return ResponseEntity.ok(result); + } + } diff --git a/src/main/java/com/eactive/eai/rms/onl/apim/portalterms/EditorImageController.java b/src/main/java/com/eactive/eai/rms/onl/apim/portalterms/EditorImageController.java new file mode 100644 index 0000000..bc64478 --- /dev/null +++ b/src/main/java/com/eactive/eai/rms/onl/apim/portalterms/EditorImageController.java @@ -0,0 +1,155 @@ +package com.eactive.eai.rms.onl.apim.portalterms; + +import com.eactive.eai.rms.common.base.BaseAnnotationController; +import com.eactive.eai.rms.common.interceptor.InterceptorSkipController; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.util.HashMap; +import java.util.Map; + +/** + * 에디터 이미지 컨트롤러 + * - summernote 에디터에서 사용하는 이미지 업로드/서빙 API + * - InterceptorSkipController: 인터셉터의 상세 검증 우회 (필터에서 기본 로그인 체크는 수행됨) + */ +@Controller +@RequestMapping("/onl/apim/editor/image") +public class EditorImageController extends BaseAnnotationController implements InterceptorSkipController { + + private final EditorImageService editorImageService; + + @Autowired + public EditorImageController(EditorImageService editorImageService) { + this.editorImageService = editorImageService; + } + + /** + * 이미지 업로드 API + * - summernote에서 이미지 선택/붙여넣기 시 호출 + * - 즉시 DB에 저장하고 플레이스홀더 정보 반환 + * + * @param file MultipartFile 이미지 파일 + * @return { success: true, fileId: "uuid", fileSn: 1, placeholder: "{{IMG:uuid:1}}" } + */ + @PostMapping("/upload.json") + @ResponseBody + public ResponseEntity> uploadImage(@RequestParam("file") MultipartFile file) { + Map result = new HashMap<>(); + + try { + // 파일 검증 + if (file.isEmpty()) { + result.put("success", false); + result.put("error", "파일이 비어있습니다."); + return ResponseEntity.badRequest().body(result); + } + + // 이미지 타입 검증 + String contentType = file.getContentType(); + if (contentType == null || !contentType.startsWith("image/")) { + result.put("success", false); + result.put("error", "이미지 파일만 업로드 가능합니다."); + return ResponseEntity.badRequest().body(result); + } + + // 파일 크기 검증 (10MB) + long maxSize = 10 * 1024 * 1024; + if (file.getSize() > maxSize) { + result.put("success", false); + result.put("error", "파일 크기는 10MB를 초과할 수 없습니다."); + return ResponseEntity.badRequest().body(result); + } + + // 이미지 저장 + EditorImageDTO saved = editorImageService.saveImage(file); + + // 성공 응답 + result.put("success", true); + result.put("fileId", saved.getFileId()); + result.put("fileSn", saved.getFileSn()); + result.put("placeholder", "{{IMG:" + saved.getFileId() + ":" + saved.getFileSn() + "}}"); + + return ResponseEntity.ok(result); + + } catch (Exception e) { + logger.error("이미지 업로드 중 오류 발생", e); + result.put("success", false); + result.put("error", "이미지 업로드 중 오류가 발생했습니다: " + e.getMessage()); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result); + } + } + + /** + * 이미지 서빙 API + * - 브라우저에서 이미지 표시용 (관리자포탈) + * - 캐시 설정으로 성능 최적화 + * + * @param fileId 파일 ID (PTL_FILE.FILE_ID) + * @param fileSn 파일 순번 (PTL_FILE_DETAIL.FILE_SN) + * @return 이미지 바이너리 데이터 + */ + @GetMapping("/view.do") + public ResponseEntity viewImage( + @RequestParam String fileId, + @RequestParam(defaultValue = "1") int fileSn) { + try { + EditorImageDTO image = editorImageService.getImage(fileId, fileSn); + + if (image == null || image.getImageData() == null) { + return ResponseEntity.notFound().build(); + } + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.parseMediaType(image.getContentType())); + headers.setContentLength(image.getFileSize()); + headers.setCacheControl("public, max-age=86400"); // 1일 캐시 + headers.set("Content-Disposition", "inline"); + + return new ResponseEntity<>(image.getImageData(), headers, HttpStatus.OK); + + } catch (Exception e) { + logger.error("이미지 조회 중 오류 발생: fileId=" + fileId + ", fileSn=" + fileSn, e); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); + } + } + + /** + * 이미지 삭제 API + * - 에디터에서 이미지 삭제 시 호출 + * - DB에서 파일 삭제 + * + * @param fileId 파일 ID (PTL_FILE.FILE_ID) + * @return { success: true } + */ + @PostMapping("/delete.json") + @ResponseBody + public ResponseEntity> deleteImage(@RequestParam String fileId) { + Map result = new HashMap<>(); + + try { + if (fileId == null || fileId.isEmpty()) { + result.put("success", false); + result.put("error", "파일 ID가 필요합니다."); + return ResponseEntity.badRequest().body(result); + } + + editorImageService.deleteImage(fileId); + + result.put("success", true); + return ResponseEntity.ok(result); + + } catch (Exception e) { + logger.error("이미지 삭제 중 오류 발생: fileId=" + fileId, e); + result.put("success", false); + result.put("error", "이미지 삭제 중 오류가 발생했습니다: " + e.getMessage()); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result); + } + } +} diff --git a/src/main/java/com/eactive/eai/rms/onl/apim/portalterms/EditorImageDTO.java b/src/main/java/com/eactive/eai/rms/onl/apim/portalterms/EditorImageDTO.java new file mode 100644 index 0000000..584e3fe --- /dev/null +++ b/src/main/java/com/eactive/eai/rms/onl/apim/portalterms/EditorImageDTO.java @@ -0,0 +1,46 @@ +package com.eactive.eai.rms.onl.apim.portalterms; + +import lombok.Data; + +/** + * 에디터 이미지 DTO + * - summernote 에디터에서 업로드된 이미지 정보를 담는 DTO + */ +@Data +public class EditorImageDTO { + + /** + * 파일 ID (PTL_FILE.FILE_ID) + */ + private String fileId; + + /** + * 파일 순번 (PTL_FILE_DETAIL.FILE_SN) + */ + private int fileSn; + + /** + * 원본 파일명 + */ + private String originalFileName; + + /** + * 파일 확장자 + */ + private String fileExtension; + + /** + * 파일 크기 (bytes) + */ + private int fileSize; + + /** + * Content-Type (MIME 타입) + */ + private String contentType; + + /** + * 이미지 바이너리 데이터 (조회 시에만 사용) + */ + private byte[] imageData; +} diff --git a/src/main/java/com/eactive/eai/rms/onl/apim/portalterms/EditorImageService.java b/src/main/java/com/eactive/eai/rms/onl/apim/portalterms/EditorImageService.java new file mode 100644 index 0000000..2ffade4 --- /dev/null +++ b/src/main/java/com/eactive/eai/rms/onl/apim/portalterms/EditorImageService.java @@ -0,0 +1,231 @@ +package com.eactive.eai.rms.onl.apim.portalterms; + +import com.eactive.apim.portal.file.entity.FileDetail; +import com.eactive.apim.portal.file.entity.FileInfo; +import com.eactive.apim.portal.file.service.FileService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.util.HashSet; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * 에디터 이미지 서비스 + * - summernote 에디터에서 사용하는 이미지 업로드/조회/삭제 서비스 + * - FileService를 래핑하여 이미지 전용 기능 제공 + */ +@Service +@Transactional(transactionManager = "transactionManagerForEMS") +@Slf4j +public class EditorImageService { + + /** + * 플레이스홀더 패턴: {{IMG:fileId:fileSn}} 또는 {{IMG:fileId:fileSn|속성}} + * - fileId: UUID 형식 (예: 550e8400-e29b-41d4-a716-446655440000) + * - fileSn: 정수 (예: 1) + * - 속성: 선택적 (예: |width=300|height=200|style=max-width:100%) + */ + private static final Pattern PLACEHOLDER_PATTERN = + Pattern.compile("\\{\\{IMG:([a-f0-9\\-]+):(\\d+)(\\|[^}]+)?\\}\\}"); + + private final FileService fileService; + + @Autowired + public EditorImageService(FileService fileService) { + this.fileService = fileService; + } + + /** + * 이미지 저장 + * + * @param file MultipartFile 이미지 파일 + * @return EditorImageDTO (fileId, fileSn 포함) + * @throws IOException 파일 처리 중 오류 발생 시 + */ + public EditorImageDTO saveImage(MultipartFile file) throws IOException { + String fileName = file.getOriginalFilename(); + String contentType = file.getContentType(); + byte[] data = file.getBytes(); + + // FileService를 통해 저장 + FileInfo fileInfo = fileService.createSingleFileFromBytes(fileName, contentType, data); + FileDetail detail = fileInfo.getFileDetails().get(0); + + // DTO로 변환하여 반환 + EditorImageDTO dto = new EditorImageDTO(); + dto.setFileId(fileInfo.getFileId()); + dto.setFileSn(detail.getFileSn()); + dto.setOriginalFileName(detail.getOriginalFileName()); + dto.setFileExtension(detail.getFileExtension()); + dto.setFileSize(detail.getFileSize()); + + return dto; + } + + /** + * 이미지 조회 + * + * @param fileId 파일 ID + * @param fileSn 파일 순번 + * @return EditorImageDTO (imageData 포함) + * @throws IOException 파일 조회 중 오류 발생 시 + */ + @Transactional(readOnly = true) + public EditorImageDTO getImage(String fileId, int fileSn) throws IOException { + FileDetail detail = fileService.getFileDetailByIds(fileId, fileSn); + byte[] data = fileService.retrieveFileContent(detail); + + EditorImageDTO dto = new EditorImageDTO(); + dto.setFileId(fileId); + dto.setFileSn(fileSn); + dto.setOriginalFileName(detail.getOriginalFileName()); + dto.setFileExtension(detail.getFileExtension()); + dto.setFileSize(detail.getFileSize()); + dto.setContentType(getContentType(detail.getFileExtension())); + dto.setImageData(data); + + return dto; + } + + /** + * 본문에서 사용 중인 파일 ID 추출 + * + * @param contents 본문 내용 (플레이스홀더 포함) + * @return 사용 중인 파일 ID Set + */ + public Set extractFileIds(String contents) { + Set fileIds = new HashSet<>(); + if (contents == null || contents.isEmpty()) { + return fileIds; + } + + Matcher matcher = PLACEHOLDER_PATTERN.matcher(contents); + while (matcher.find()) { + fileIds.add(matcher.group(1)); // fileId + } + return fileIds; + } + + /** + * 이미지 삭제 + * + * @param fileId 삭제할 파일 ID + */ + public void deleteImage(String fileId) { + try { + fileService.deleteFile(fileId); + log.info("Deleted image: {}", fileId); + } catch (Exception e) { + log.warn("Failed to delete image: {}", fileId, e); + throw e; + } + } + + /** + * 미사용 이미지 정리 + * - 원래 저장된 이미지 중 현재 본문에서 사용되지 않는 이미지 삭제 + * + * @param usedFileIds 본문에서 사용 중인 파일 ID 목록 + * @param originalFileIds 원래 저장된 모든 파일 ID 목록 + */ + public void cleanupUnusedImages(Set usedFileIds, Set originalFileIds) { + if (originalFileIds == null || originalFileIds.isEmpty()) { + return; + } + + for (String fileId : originalFileIds) { + if (!usedFileIds.contains(fileId)) { + try { + fileService.deleteFile(fileId); + log.info("Deleted unused image: {}", fileId); + } catch (Exception e) { + // 삭제 실패 시 로그만 남기고 계속 진행 + log.warn("Failed to delete unused image: {}", fileId, e); + } + } + } + } + + /** + * 플레이스홀더를 실제 이미지 태그로 변환 + * + * @param contents 본문 내용 (플레이스홀더 포함) + * @param baseUrl 이미지 서빙 베이스 URL (예: /onl/apim/editor/image/view/) + * @return 이미지 태그가 포함된 HTML + */ + public String convertPlaceholdersToHtml(String contents, String baseUrl) { + if (contents == null || contents.isEmpty()) { + return contents; + } + + StringBuffer result = new StringBuffer(); + Matcher matcher = PLACEHOLDER_PATTERN.matcher(contents); + + while (matcher.find()) { + String fileId = matcher.group(1); + String fileSn = matcher.group(2); + String attrs = matcher.group(3); // |width=300|height=200 형태 + + StringBuilder img = new StringBuilder(); + img.append(""); + matcher.appendReplacement(result, Matcher.quoteReplacement(img.toString())); + } + matcher.appendTail(result); + + return result.toString(); + } + + /** + * 파일 확장자를 Content-Type으로 변환 + * + * @param extension 파일 확장자 + * @return MIME 타입 + */ + private String getContentType(String extension) { + if (extension == null) { + return "application/octet-stream"; + } + + switch (extension.toLowerCase()) { + case "jpg": + case "jpeg": + return "image/jpeg"; + case "png": + return "image/png"; + case "gif": + return "image/gif"; + case "bmp": + return "image/bmp"; + case "webp": + return "image/webp"; + case "svg": + return "image/svg+xml"; + default: + return "application/octet-stream"; + } + } +} diff --git a/src/main/java/com/eactive/eai/rms/onl/apim/portalterms/PortalTermsManController.java b/src/main/java/com/eactive/eai/rms/onl/apim/portalterms/PortalTermsManController.java index 143c876..2582606 100644 --- a/src/main/java/com/eactive/eai/rms/onl/apim/portalterms/PortalTermsManController.java +++ b/src/main/java/com/eactive/eai/rms/onl/apim/portalterms/PortalTermsManController.java @@ -13,12 +13,16 @@ import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.multipart.MultipartFile; import java.io.IOException; import java.util.Arrays; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; @Controller @@ -72,14 +76,22 @@ public class PortalTermsManController extends BaseAnnotationController { } @PostMapping(value = "/onl/apim/portalterms/portalTermsMan.json", params = "cmd=INSERT") - public ResponseEntity insert(PortalTermsUI portalTermsUI) throws IOException { - portalTermsManService.insert(portalTermsUI); + public ResponseEntity insert(PortalTermsUI portalTermsUI, + @RequestParam(value = "attachedFile", required = false) MultipartFile attachedFile) throws IOException { + portalTermsManService.insert(portalTermsUI, attachedFile); return ResponseEntity.ok().build(); } @PostMapping(value = "/onl/apim/portalterms/portalTermsMan.json", params = "cmd=UPDATE") - public ResponseEntity update(PortalTermsUI portalTermsUI) throws IOException { - portalTermsManService.update(portalTermsUI); + public ResponseEntity update(PortalTermsUI portalTermsUI, String usedFileIds, + @RequestParam(value = "attachedFile", required = false) MultipartFile attachedFile, + @RequestParam(value = "deleteAttachment", defaultValue = "false") boolean deleteAttachment) throws IOException { + // 사용 중인 파일 ID 목록 파싱 + Set usedFileIdSet = new HashSet<>(); + if (usedFileIds != null && !usedFileIds.isEmpty()) { + usedFileIdSet.addAll(Arrays.asList(usedFileIds.split(","))); + } + portalTermsManService.update(portalTermsUI, usedFileIdSet, attachedFile, deleteAttachment); return ResponseEntity.ok().build(); } diff --git a/src/main/java/com/eactive/eai/rms/onl/apim/portalterms/PortalTermsManService.java b/src/main/java/com/eactive/eai/rms/onl/apim/portalterms/PortalTermsManService.java index f75dda4..1c969ef 100644 --- a/src/main/java/com/eactive/eai/rms/onl/apim/portalterms/PortalTermsManService.java +++ b/src/main/java/com/eactive/eai/rms/onl/apim/portalterms/PortalTermsManService.java @@ -4,15 +4,24 @@ import com.eactive.apim.portal.agreements.entity.AgreementType; import com.eactive.apim.portal.agreements.entity.Agreements; import com.eactive.apim.portal.agreements.entity.AgreementsId; import com.eactive.apim.portal.agreements.service.AgreementsService; +import com.eactive.apim.portal.file.entity.FileDetail; +import com.eactive.apim.portal.file.entity.FileInfo; +import com.eactive.apim.portal.file.service.FileService; import com.eactive.eai.rms.common.base.BaseService; import com.eactive.eai.rms.data.entity.man.user.UserInfoService; import com.eactive.eai.rms.data.entity.onl.apim.portalterms.PortalTermsService; import org.apache.commons.lang.StringEscapeUtils; +import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.util.HashSet; +import java.util.Set; @Service @Transactional(transactionManager = "transactionManagerForEMS") @@ -21,16 +30,22 @@ public class PortalTermsManService extends BaseService { private final PortalTermsUIMapper portalTermsUIMapper; private final AgreementsService agreementsService; private final UserInfoService userInfoService; + private final EditorImageService editorImageService; + private final FileService fileService; @Autowired public PortalTermsManService(PortalTermsService portalTermsService, PortalTermsUIMapper portalTermsUIMapper, AgreementsService agreementsService, - UserInfoService userInfoService){ + UserInfoService userInfoService, + EditorImageService editorImageService, + FileService fileService){ this.portalTermsService = portalTermsService; this.portalTermsUIMapper = portalTermsUIMapper; this.agreementsService = agreementsService; this.userInfoService = userInfoService; + this.editorImageService = editorImageService; + this.fileService = fileService; } private String findName(String userId) { @@ -55,25 +70,90 @@ public class PortalTermsManService extends BaseService { portalTermsUI.setCreatedByName(findName(agreements.getCreatedBy())); portalTermsUI.setLastModifiedByName(findName(agreements.getLastModifiedBy())); portalTermsUI.setName(agreements.getAgreementsType().getDescription()); + + // 첨부파일 정보 조회 + if (StringUtils.isNotBlank(agreements.getAttachedFileId())) { + try { + FileInfo fileInfo = fileService.findById(agreements.getAttachedFileId()); + if (fileInfo != null && !fileInfo.getFileDetails().isEmpty()) { + FileDetail fileDetail = fileInfo.getFileDetails().get(0); + portalTermsUI.setAttachedFileName(fileDetail.getOriginalFileName() + "." + fileDetail.getFileExtension()); + portalTermsUI.setAttachedFileSize((long) fileDetail.getFileSize()); + } + } catch (Exception e) { + // 파일 조회 실패 시 무시 + logger.warn("첨부파일 조회 실패: {}", agreements.getAttachedFileId(), e); + } + } + return portalTermsUI; } - public void insert(PortalTermsUI portalTermsUI) { + public void insert(PortalTermsUI portalTermsUI, MultipartFile attachedFile) throws IOException { portalTermsUI.setName(AgreementType.fromCode(portalTermsUI.getAgreementsType()).getDescription()); portalTermsUI.setContents(StringEscapeUtils.unescapeHtml(portalTermsUI.getContents())); Agreements agreements = portalTermsUIMapper.toEntity(portalTermsUI); + + // 첨부파일 저장 + if (attachedFile != null && !attachedFile.isEmpty()) { + FileInfo fileInfo = fileService.createOrUpdateSingleFile(null, attachedFile, attachedFile.getOriginalFilename()); + agreements.setAttachedFileId(fileInfo.getFileId()); + } + agreementsService.save(agreements); } - public void update(PortalTermsUI portalTermsUI) { + public void update(PortalTermsUI portalTermsUI, Set usedFileIds, + MultipartFile attachedFile, boolean deleteAttachment) throws IOException { Agreements agreements = agreementsService.findById(portalTermsUI.getAgreementsType(), portalTermsUI.getRevision()); + + // 원래 내용에서 사용 중이던 파일 ID 추출 + Set originalFileIds = editorImageService.extractFileIds(agreements.getContents()); + + // 내용 업데이트 agreements.setName(AgreementType.fromCode(portalTermsUI.getAgreementsType()).getDescription()); agreements.setContents(StringEscapeUtils.unescapeHtml(portalTermsUI.getContents())); agreements.setPublishedOn(portalTermsUI.getPublishedOn()); + + // 첨부파일 처리 + String oldAttachedFileId = agreements.getAttachedFileId(); + + if (deleteAttachment && StringUtils.isNotBlank(oldAttachedFileId)) { + // 첨부파일 삭제 요청 + fileService.deleteFile(oldAttachedFileId); + agreements.setAttachedFileId(null); + } else if (attachedFile != null && !attachedFile.isEmpty()) { + // 새 첨부파일 업로드 + FileInfo fileInfo = fileService.createOrUpdateSingleFile( + oldAttachedFileId, attachedFile, attachedFile.getOriginalFilename()); + agreements.setAttachedFileId(fileInfo.getFileId()); + } + agreementsService.save(agreements); + + // 미사용 이미지 정리 (저장 후 비동기적으로 처리) + editorImageService.cleanupUnusedImages(usedFileIds, originalFileIds); } public void delete(String agreementsType, Integer revision) { + // 삭제 전 사용 중인 파일 ID 추출 + Agreements agreements = agreementsService.findById(agreementsType, revision); + Set fileIdsToDelete = editorImageService.extractFileIds(agreements.getContents()); + String attachedFileId = agreements.getAttachedFileId(); + + // 약관 삭제 agreementsService.delete(agreementsType, revision); + + // 관련 이미지 모두 삭제 + editorImageService.cleanupUnusedImages(new HashSet<>(), fileIdsToDelete); + + // 첨부파일 삭제 + if (StringUtils.isNotBlank(attachedFileId)) { + try { + fileService.deleteFile(attachedFileId); + } catch (Exception e) { + logger.warn("첨부파일 삭제 실패: {}", attachedFileId, e); + } + } } } diff --git a/src/main/java/com/eactive/eai/rms/onl/apim/portalterms/PortalTermsUI.java b/src/main/java/com/eactive/eai/rms/onl/apim/portalterms/PortalTermsUI.java index eced443..af0fe4f 100644 --- a/src/main/java/com/eactive/eai/rms/onl/apim/portalterms/PortalTermsUI.java +++ b/src/main/java/com/eactive/eai/rms/onl/apim/portalterms/PortalTermsUI.java @@ -52,4 +52,13 @@ public class PortalTermsUI { private String searchAgreementsType; + /* 첨부파일 ID */ + private String attachedFileId; + + /* 첨부파일명 (조회용) */ + private String attachedFileName; + + /* 첨부파일 크기 (조회용) */ + private Long attachedFileSize; + } diff --git a/src/main/java/com/eactive/eai/rms/onl/audit/adapter/map/AuditUIMapper.java b/src/main/java/com/eactive/eai/rms/onl/audit/adapter/map/AuditUIMapper.java index fcb681d..282bfbd 100644 --- a/src/main/java/com/eactive/eai/rms/onl/audit/adapter/map/AuditUIMapper.java +++ b/src/main/java/com/eactive/eai/rms/onl/audit/adapter/map/AuditUIMapper.java @@ -16,7 +16,7 @@ import com.eactive.eai.rms.onl.audit.adapter.ui.AuditUI; @Mapper(config = BaseMapperConfig.class, uses = {}) public interface AuditUIMapper { - @Mapping(target = "revTstmp", expression = "java(mapLongToString(entity.getRevTstmp()))") + @Mapping(target = "revTstmp", source = "revTstmp") @Mapping(target = "revNo", source = "rev") @Mapping(target = "revUserId", source = "userId") void map(CustomRevisionEntity entity, @MappingTarget AuditUI auditUI); diff --git a/src/main/java/com/eactive/eai/rms/onl/common/service/ObpGwMetricHourJob.java b/src/main/java/com/eactive/eai/rms/onl/common/service/ObpGwMetricHourJob.java new file mode 100644 index 0000000..7b93297 --- /dev/null +++ b/src/main/java/com/eactive/eai/rms/onl/common/service/ObpGwMetricHourJob.java @@ -0,0 +1,147 @@ +package com.eactive.eai.rms.onl.common.service; + +import com.eactive.eai.rms.common.context.MonitoringContext; +import com.eactive.eai.rms.common.util.CommonUtil; +import com.eactive.eai.rms.onl.apim.obp.ObpGwMetricManService; +import com.eactive.eai.rms.onl.common.util.DateUtil; +import org.quartz.Job; +import org.quartz.JobDataMap; +import org.quartz.JobExecutionContext; +import org.quartz.JobExecutionException; +import org.quartz.SchedulerException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationContext; + +/** + * ObpGwMetric Hour Job - Quartz Job + * API Gateway 로그 데이터를 1시간 단위로 집계하여 ObpGwMetric 테이블에 저장 + * + *

Cron Schedule 권장:

+ *
    + *
  • 기본: 0 5 * * * ? (매시 05분 1회 실행)
  • + *
  • 보정 (15분): 0 5,20,35,50 * * * ? (15분 간격 4회 실행)
  • + *
  • 보정 (10분): 0 5,15,25,35,45,55 * * * ? (10분 간격 6회 실행)
  • + *
+ * + *

Job 파라미터 (JobDataMap):

+ *
    + *
  • aggregation.hour.range: 집계 범위 시간 (단위: 시간, 기본값: 1) + *
      + *
    • 1: 직전 1시간만 집계
    • + *
    • 3: 직전 3시간 각각 집계
    • + *
    • 5: 직전 5시간 각각 집계
    • + *
    + *
  • + *
+ * + *

예시 (Quartz 스케줄 등록 시):

+ *
+ * JobDataMap jobDataMap = new JobDataMap();
+ * jobDataMap.put("aggregation.hour.range", "3");
+ * 
+ */ +public class ObpGwMetricHourJob implements Job { + + private static final Logger log = LoggerFactory.getLogger(ObpGwMetricHourJob.class); + + /** Job 파라미터 키: 집계 범위 시간 */ + public static final String PARAM_AGGREGATION_HOUR_RANGE = "aggregation.hour.range"; + + /** 기본 집계 범위 시간 (시간 단위) */ + public static final int DEFAULT_AGGREGATION_HOUR_RANGE = 1; + + private transient MonitoringContext monitoringContext; + + @Override + public void execute(JobExecutionContext context) throws JobExecutionException { + log.info("*** START ObpGwMetricHourJob run({})", CommonUtil.getToday("yyyy-MM-dd HH:mm")); + + // Job 파라미터 로깅 + JobDataMap jobDataMap = context.getJobDetail().getJobDataMap(); + logJobParameters(jobDataMap); + + ApplicationContext appContext; + try { + appContext = (ApplicationContext) context.getScheduler().getContext().get("applicationContext"); + } catch (SchedulerException e) { + log.error("applicationContext get module error", e); + return; + } + + monitoringContext = (MonitoringContext) appContext.getBean("monitoringContext"); + ObpGwMetricManService obpGwMetricManService = appContext.getBean(ObpGwMetricManService.class); + + // 집계 범위 시간 파라미터 파싱 + int aggregationHourRange = parseAggregationHourRange(jobDataMap); + + try { + obpGwMetricManService.executeHourlyAggregation(aggregationHourRange); + } catch (Exception e) { + log.error("ObpGwMetricHourJob execution failed", e); + throw new JobExecutionException(e); + } + + log.info("*** END ObpGwMetricHourJob run({})", DateUtil.getDateTime("yyyy-MM-dd HH:mm")); + } + + /** + * Job 파라미터 로깅 + */ + private void logJobParameters(JobDataMap jobDataMap) { + if (jobDataMap == null || jobDataMap.isEmpty()) { + log.debug("Job 파라미터 없음 (기본값 사용)"); + return; + } + + StringBuilder sb = new StringBuilder("Job 파라미터: "); + for (String key : jobDataMap.getKeys()) { + sb.append(key).append("=").append(jobDataMap.getString(key)).append(", "); + } + log.info(sb.toString()); + } + + /** + * 집계 범위 시간 파라미터 파싱 + * + * @param jobDataMap Job 파라미터 맵 + * @return 집계 범위 시간 (파싱 실패 시 기본값 반환) + */ + private int parseAggregationHourRange(JobDataMap jobDataMap) { + if (jobDataMap == null) { + return DEFAULT_AGGREGATION_HOUR_RANGE; + } + + try { + String value = jobDataMap.getString(PARAM_AGGREGATION_HOUR_RANGE); + if (value != null && !value.trim().isEmpty()) { + int parsedValue = Integer.parseInt(value.trim()); + if (parsedValue < 1) { + log.warn("aggregation.hour.range 값이 1 미만입니다. 기본값({}) 사용: input={}", + DEFAULT_AGGREGATION_HOUR_RANGE, parsedValue); + return DEFAULT_AGGREGATION_HOUR_RANGE; + } + // 개발 모드(D)가 아닌 경우에만 24시간 제한 적용 + if (parsedValue > 24 && !isDevMode()) { + log.warn("aggregation.hour.range 값이 24 초과입니다. 24로 제한: input={}", parsedValue); + return 24; + } + return parsedValue; + } + } catch (NumberFormatException e) { + log.warn("aggregation.hour.range 파싱 실패, 기본값({}) 사용: {}", + DEFAULT_AGGREGATION_HOUR_RANGE, e.getMessage()); + } + + return DEFAULT_AGGREGATION_HOUR_RANGE; + } + + /** + * 개발 모드 여부 확인 + * @return eai.systemmode=D 이면 true + */ + private boolean isDevMode() { + String systemMode = System.getProperty("eai.systemmode", ""); + return "D".equalsIgnoreCase(systemMode); + } +} diff --git a/src/main/java/com/eactive/eai/rms/onl/common/service/TransactionReportHourJob.java b/src/main/java/com/eactive/eai/rms/onl/common/service/TransactionReportHourJob.java index 72e81a3..83fd045 100644 --- a/src/main/java/com/eactive/eai/rms/onl/common/service/TransactionReportHourJob.java +++ b/src/main/java/com/eactive/eai/rms/onl/common/service/TransactionReportHourJob.java @@ -9,6 +9,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import com.eactive.eai.rms.onl.common.util.FileSystemUtil; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import org.apache.poi.hssf.usermodel.HSSFCell; @@ -75,7 +76,7 @@ public class TransactionReportHourJob implements Job { } String trTime = monitoringContext.getStringProperty(MonitoringContext.RMS_TRANSACTION_LOG_TIME, ""); - if( trTime.indexOf("EVERY_HOUR") > -1 ) { + if (trTime.contains("EVERY_HOUR")) { // go on } else if (!StringUtils.contains(trTime, hour)) { return; @@ -93,9 +94,11 @@ public class TransactionReportHourJob implements Job { + CommonUtil.getToday("yyyy-MM-dd HH:mm") + ")"); } - String pathDir = monitoringContext.getStringProperty( MonitoringContext.RMS_TRANSACTION_LOG_PATH,"/fslog/eai/") - + monitoringContext.getStringProperty( Keys.SERVER_KEY) - + "/trlog"; + String pathDir = FileSystemUtil.pathNormalize( + monitoringContext.getStringProperty( MonitoringContext.RMS_TRANSACTION_LOG_PATH,"/fslog/eai/") + , monitoringContext.getStringProperty( Keys.SERVER_KEY) + , "trlog" + ); /////////////////////////////////////////////// // 로컬테스트용 diff --git a/src/main/java/com/eactive/eai/rms/onl/common/service/ums/UmsDispatchService.java b/src/main/java/com/eactive/eai/rms/onl/common/service/ums/UmsDispatchService.java index 549ce34..4a42c9c 100644 --- a/src/main/java/com/eactive/eai/rms/onl/common/service/ums/UmsDispatchService.java +++ b/src/main/java/com/eactive/eai/rms/onl/common/service/ums/UmsDispatchService.java @@ -17,13 +17,14 @@ import org.springframework.transaction.support.TransactionTemplate; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; -import java.io.IOException; import java.time.LocalDateTime; import java.util.List; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; +import javax.annotation.PostConstruct; + @Slf4j @Service @@ -43,9 +44,10 @@ public class UmsDispatchService { private EntityManager entityManager; private final MessageRequestRepository messageRequestRepository; - private final KjbUmsService kjbUmsService; - private final KjbEmailSendModule kjbEmailSendModule; + private KjbUmsService kjbUmsService; + private KjbEmailSendModule kjbEmailSendModule; private final KjbPropertyInjector kjbPropertyInjector; + private KjbProperty kjbProperty; @Autowired @@ -55,11 +57,7 @@ public class UmsDispatchService { MessageRequestRepository messageRequestRepository ) { this.kjbPropertyInjector = new KjbPropertyInjector(monitoringPropertyService, PROP_GORUP_ID); - this.messageRequestRepository = messageRequestRepository; -// this.kjbEmailService = kjbEmailService; -// this.kjbSafedbWrapper = kjbSafedbWrapper; -// this.monitoringPropertyService = monitoringPropertyService; this.executorService = new ThreadPoolExecutor( CORE_POOL_SIZE, @@ -72,20 +70,40 @@ public class UmsDispatchService { this.transactionTemplate = new TransactionTemplate(transactionManager); log.debug("UmsDispatchService initialized with thread pool - core: {}, max: {}, queue: {}", CORE_POOL_SIZE, MAX_POOL_SIZE, QUEUE_CAPACITY); + } + @PostConstruct + public void init() { + this.kjbProperty = kjbPropertyInjector.inject(new KjbProperty()); - KjbProperty prop = new KjbProperty(); -// prop.setEaiBatchUrl(monitoringPropertyService.getPrpty2ValById(PROP_GORUP_ID, "kjb.ums.eai_batch.url")); -// prop.setUmsEaiBatchSendDir(monitoringPropertyService.getPrpty2ValById(PROP_GORUP_ID, "kjb.ums.eai_batch.send_dir")); -// prop.setUmsEaiBatchBackupDir(monitoringPropertyService.getPrpty2ValById(PROP_GORUP_ID, "kjb.ums.eai_batch.backup_dir")); -// prop.setUmsEaiBatchInterfaceId(monitoringPropertyService.getPrpty2ValById(PROP_GORUP_ID, "kjb.ums.eai_batch.interface_id")); -// prop.setUmsHostUrl(monitoringPropertyService.getPrpty2ValById(PROP_GORUP_ID, "kjb.ums.host_url")); -// prop.setUmsApiKey(monitoringPropertyService.getPrpty2ValById(PROP_GORUP_ID, "kjb.ums.api_key")); - prop = kjbPropertyInjector.inject(prop); + try { + this.kjbUmsService = new KjbUmsService(kjbProperty); + this.kjbEmailSendModule = KjbEmailSendModule.getInstance(kjbProperty, messageRequestRepository); + log.info("KjbUmsService and KjbEmailSendModule initialized successfully"); + } catch (Exception e) { + log.error("Failed to initialize KjbUmsService or KjbEmailSendModule: {}", e.getMessage(), e); + } + } - this.kjbUmsService = new KjbUmsService(prop); + /** + * URL 설정값이 비어있을 경우 DB에서 재로딩 후 서비스 재초기화 + */ + private void reloadPropertyIfNeeded() { + if (!kjbPropertyInjector.isUrlConfigured(kjbProperty)) { + log.info("KjbProperty URL 값이 비어있어 재로딩 시도"); + KjbProperty reloaded = kjbPropertyInjector.reloadIfUrlEmpty(kjbProperty); - this.kjbEmailSendModule = KjbEmailSendModule.getInstance(prop, messageRequestRepository); + if (reloaded != kjbProperty && kjbPropertyInjector.isUrlConfigured(reloaded)) { + this.kjbProperty = reloaded; + try { + this.kjbUmsService = new KjbUmsService(kjbProperty); + this.kjbEmailSendModule = KjbEmailSendModule.getInstance(kjbProperty, messageRequestRepository); + log.info("KjbProperty 재로딩 후 서비스 재초기화 완료"); + } catch (Exception e) { + log.error("KjbProperty 재로딩 후 서비스 재초기화 실패: {}", e.getMessage(), e); + } + } + } } @SuppressWarnings("unchecked") @@ -93,7 +111,10 @@ public class UmsDispatchService { public void processMessages() { log.info("Starting to process pending messages"); - // 상태가 PENDING인 메시지들을 조회하고 바로 PROCESSING으로 변경 + // URL 설정값이 비어있을 경우 DB에서 재로딩 시도 + reloadPropertyIfNeeded(); + + // 상태가 PENDING인 메시지들을 조회하고 바로 PROCESSING으로 변경 List pendingMessages = entityManager.createQuery( "SELECT m FROM MessageRequest m WHERE m.requestStatus = :status " + "ORDER BY m.requestDate ASC") diff --git a/src/main/java/com/eactive/eai/rms/onl/common/util/FileSystemUtil.java b/src/main/java/com/eactive/eai/rms/onl/common/util/FileSystemUtil.java new file mode 100644 index 0000000..7026ed7 --- /dev/null +++ b/src/main/java/com/eactive/eai/rms/onl/common/util/FileSystemUtil.java @@ -0,0 +1,73 @@ +package com.eactive.eai.rms.onl.common.util; + +import java.io.File; +import java.util.StringJoiner; + +public class FileSystemUtil { + + /** + * Normalizes a path by joining segments and applying the correct file separator for the current operating system. + * It handles redundant separators between segments. + * + *

Examples for Linux/macOS:

+ *
    + *
  • {@code pathNormalize("/asd1/asd2", "asd3")} returns {@code "/asd1/asd2/asd3"}
  • + *
  • {@code pathNormalize("/asd1/asd2/", "/asd3")} returns {@code "/asd1/asd2/asd3"}
  • + *
+ * + *

Examples for Windows:

+ *
    + *
  • {@code pathNormalize("C:/Users/Test", "Documents")} returns {@code "C:\\Users\\Test\\Documents"}
  • + *
  • {@code pathNormalize("C:/Users/Test/", "/Documents")} returns {@code "C:\\Users\\Test\\Documents"}
  • + *
+ * + * @param args A variable number of path segments to join. + * @return A normalized path string for the current operating system. + */ + public static String pathNormalize(String... args) { + if (args == null || args.length == 0) { + return ""; + } + + StringJoiner joiner = new StringJoiner(File.separator); + for (String arg : args) { + if (arg != null && !arg.isEmpty()) { + String segment = arg; + // Remove leading separators from the segment + while (segment.startsWith("/") || segment.startsWith("\\")) { + segment = segment.substring(1); + } + // Remove trailing separators from the segment + while (segment.endsWith("/") || segment.endsWith("\\")) { + segment = segment.substring(0, segment.length() - 1); + } + if (!segment.isEmpty()) { + joiner.add(segment); + } + } + } + + String joinedPath = joiner.toString(); + + // Handle the root path for the first argument + String firstArg = args[0]; + if (firstArg != null && !firstArg.isEmpty()) { + if (firstArg.startsWith("/")) { + joinedPath = File.separator + joinedPath; + } else if (firstArg.matches("^[a-zA-Z]:\\\\") || firstArg.matches("^[a-zA-Z]:/.*")) { // Handle Windows drive letters, e.g., "C:\" or "C:/" + String drive = firstArg.substring(0, 2); + return drive + File.separator + joinedPath.substring(joinedPath.indexOf(File.separator) + 1); + } + } + + // Final normalization for Windows if the path starts with a separator (e.g. from a UNC path) + if (System.getProperty("os.name").toLowerCase().contains("win")) { + if (args[0] != null && args[0].startsWith("//") || args[0].startsWith("\\\\")) { + return "\\\\" + joinedPath; + } + return joinedPath.replace("/", "\\"); + } + + return joinedPath.replace("\\", "/"); + } +} diff --git a/src/main/java/com/eactive/eai/rms/onl/dashboard/modern/controller/ModernDashboardController.java b/src/main/java/com/eactive/eai/rms/onl/dashboard/modern/controller/ModernDashboardController.java index 3b17bb6..79b7f09 100644 --- a/src/main/java/com/eactive/eai/rms/onl/dashboard/modern/controller/ModernDashboardController.java +++ b/src/main/java/com/eactive/eai/rms/onl/dashboard/modern/controller/ModernDashboardController.java @@ -223,10 +223,6 @@ public class ModernDashboardController implements InterceptorSkipController { @RequestMapping(path = "/modern-dashboard-api.do", params = "type=adapter-status") @ResponseBody public Map getAdapterStatus(@RequestParam(required = false) String serverName) { - // TODO: 디버그 완료 후 삭제 - System.out.println("========================================"); - System.out.println("[Dashboard Debug] getAdapterStatus API CALLED!"); - System.out.println("========================================"); log.error("[Dashboard Debug] getAdapterStatus API CALLED - serverName: " + serverName); Map response = new HashMap<>(); @@ -254,26 +250,26 @@ public class ModernDashboardController implements InterceptorSkipController { // TODO: 디버그 완료 후 아래 디버그 로그 전체 삭제 (라인 254-299) // [Dashboard Debug] Check server list size - System.out.println("[Dashboard Debug] getAdapterStatus: Found " + (servers == null ? 0 : servers.size()) + " servers to check."); +// System.out.println("[Dashboard Debug] getAdapterStatus: Found " + (servers == null ? 0 : servers.size()) + " servers to check."); log.error("[Dashboard Debug] getAdapterStatus: Found " + (servers == null ? 0 : servers.size()) + " servers to check."); List> serverAdapters = new ArrayList<>(); for (DataSourceType server : servers) { - System.out.println("[Dashboard Debug] Loop for server: " + server.getName()); +// System.out.println("[Dashboard Debug] Loop for server: " + server.getName()); log.error("[Dashboard Debug] Loop for server: " + server.getName()); // 서버 컨텍스트 설정 DataSourceContextHolder.setDataSourceType(server); // 서버에 대한 인스턴스 목록 조회 - System.out.println("[Dashboard Debug] Calling getEaiServerIpListDB..."); +// System.out.println("[Dashboard Debug] Calling getEaiServerIpListDB..."); List> ipList = eaiServerInfoService.getEaiServerIpListDB(); - System.out.println("[Dashboard Debug] Call to getEaiServerIpListDB finished. IP list size: " + (ipList == null ? 0 : ipList.size())); +// System.out.println("[Dashboard Debug] Call to getEaiServerIpListDB finished. IP list size: " + (ipList == null ? 0 : ipList.size())); log.error("[Dashboard Debug] Call to getEaiServerIpListDB finished. IP list size: " + (ipList == null ? 0 : ipList.size())); // 어댑터 상태 목록 조회 (개별 인스턴스별 상태) List adaptersList = dashboardService.findAdapterStatusList(ipList); - System.out.println("[Dashboard Debug] adaptersList size: " + (adaptersList == null ? "null" : adaptersList.size())); +// System.out.println("[Dashboard Debug] adaptersList size: " + (adaptersList == null ? "null" : adaptersList.size())); log.error("[Dashboard Debug] adaptersList size: " + (adaptersList == null ? "null" : adaptersList.size())); // 어댑터별 상태 코드 로깅 @@ -288,7 +284,7 @@ public class ModernDashboardController implements InterceptorSkipController { ", Socket counts: [" + vo.getSocket(0) + "," + vo.getSocket(1) + "," + vo.getSocket(2) + "]" + ", Socket2 counts: [" + vo.getSocket2(0) + "," + vo.getSocket2(1) + "," + vo.getSocket2(2) + "]" + ", Http counts: [" + vo.getHttp(0) + "," + vo.getHttp(1) + "," + vo.getHttp(2) + "]"; - System.out.println(debugMsg); +// System.out.println(debugMsg); log.error(debugMsg); } } @@ -305,8 +301,8 @@ public class ModernDashboardController implements InterceptorSkipController { ", normal=" + summaryContainer.getAdapterSummary("HTTP").getNormalCount() + ", inactive=" + summaryContainer.getAdapterSummary("HTTP").getInactiveCount() + ", overall=" + summaryContainer.getAdapterSummary("HTTP").getOverallStatus(); - System.out.println(socketSummary); - System.out.println(httpSummary); +// System.out.println(socketSummary); +// System.out.println(httpSummary); log.error(socketSummary); log.error(httpSummary); // TODO: 여기까지 디버그 로그 삭제 diff --git a/src/main/java/com/eactive/eai/rms/onl/dashboard/modern/mapper/SmsNotificationMapper.java b/src/main/java/com/eactive/eai/rms/onl/dashboard/modern/mapper/SmsNotificationMapper.java index 34ad9df..e65ce4f 100644 --- a/src/main/java/com/eactive/eai/rms/onl/dashboard/modern/mapper/SmsNotificationMapper.java +++ b/src/main/java/com/eactive/eai/rms/onl/dashboard/modern/mapper/SmsNotificationMapper.java @@ -2,6 +2,8 @@ package com.eactive.eai.rms.onl.dashboard.modern.mapper; import com.eactive.eai.rms.onl.dashboard.modern.dto.SmsNotificationDto; import com.eactive.eai.rms.onl.vo.SmsVO; + +import org.apache.log4j.Logger; import org.springframework.stereotype.Component; import java.time.LocalDate; @@ -21,6 +23,9 @@ public class SmsNotificationMapper { private static final Pattern DATE_TIME_PATTERN = Pattern.compile("(\\d{4}) (\\d{2}:\\d{2}:\\d{2}\\.\\d{3})"); private static final Pattern COUNT_PATTERN = Pattern.compile("\\[(\\d+)\\]"); + + // Sysout --> logger + private static final Logger logger = Logger.getLogger(SmsNotificationMapper.class); /** * SmsVO 목록을 SmsNotificationDto 목록으로 변환합니다. @@ -148,22 +153,37 @@ public class SmsNotificationMapper { smsVO.setMessage(messages); List results = mapper.mapToSmsNotificationDtoList(Arrays.asList(smsVO)); - System.out.println("=== SMS 메시지 파싱 결과 ==="); +// System.out.println("=== SMS 메시지 파싱 결과 ==="); + logger.debug("=== SMS 메시지 파싱 결과 ==="); for (SmsNotificationDto dto : results) { - System.out.println("\n[메시지 정보]"); - System.out.println("시간: " + dto.getDateTime()); - System.out.println("건수: " + dto.getCount()); - System.out.println("인스턴스: " + dto.getInstanceName()); - System.out.println("에러내용: " + dto.getErrorContent()); - System.out.println("에러유형: " + dto.getErrorType()); +// System.out.println("\n[메시지 정보]"); +// System.out.println("시간: " + dto.getDateTime()); +// System.out.println("건수: " + dto.getCount()); +// System.out.println("인스턴스: " + dto.getInstanceName()); +// System.out.println("에러내용: " + dto.getErrorContent()); +// System.out.println("에러유형: " + dto.getErrorType()); + + logger.debug("\n[메시지 정보]"); + logger.debug("시간: " + dto.getDateTime()); + logger.debug("건수: " + dto.getCount()); + logger.debug("인스턴스: " + dto.getInstanceName()); + logger.debug("에러내용: " + dto.getErrorContent()); + logger.debug("에러유형: " + dto.getErrorType()); if ("ADAPTER".equals(dto.getErrorType())) { - System.out.println("어댑터설명: " + dto.getAdapterDesc()); - System.out.println("어댑터ID: " + dto.getAdapterId()); +// System.out.println("어댑터설명: " + dto.getAdapterDesc()); +// System.out.println("어댑터ID: " + dto.getAdapterId()); + + logger.debug("어댑터설명: " + dto.getAdapterDesc()); + logger.debug("어댑터ID: " + dto.getAdapterId()); } else { - System.out.println("인터페이스ID: " + dto.getInterfaceId()); +// System.out.println("인터페이스ID: " + dto.getInterfaceId()); + + logger.debug("인터페이스ID: " + dto.getInterfaceId()); } - System.out.println("------------------------"); +// System.out.println("------------------------"); + + logger.debug("------------------------"); } } } \ No newline at end of file diff --git a/src/main/java/com/eactive/eai/rms/onl/manage/adapter/adapter/AdapterController.java b/src/main/java/com/eactive/eai/rms/onl/manage/adapter/adapter/AdapterController.java index 3897bfb..64129af 100644 --- a/src/main/java/com/eactive/eai/rms/onl/manage/adapter/adapter/AdapterController.java +++ b/src/main/java/com/eactive/eai/rms/onl/manage/adapter/adapter/AdapterController.java @@ -18,6 +18,7 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.web.SortDefault; import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; @@ -432,7 +433,7 @@ public class AdapterController extends OnlBaseAnnotationController { @PostMapping(value = "/onl/admin/adapter/adapterMan.file", params = "cmd=LIST_IMPORT_FILE") public ResponseEntity listImportFromFile( - @RequestParam("file") MultipartFile file) { + @RequestParam("file") MultipartFile file, boolean importDupliCheck) { try { @@ -445,7 +446,7 @@ public class AdapterController extends OnlBaseAnnotationController { synchronized (obj) { AdapterGroupUI importedUI = service - .importAdapterFromJson(file); + .importAdapterFromJson(file, importDupliCheck); Map resultMap = service .reloadSync(importedUI); @@ -456,6 +457,10 @@ public class AdapterController extends OnlBaseAnnotationController { } + } catch (IllegalStateException dupEx) { + return ResponseEntity + .status(HttpStatus.CONFLICT) + .body(dupEx.getMessage()); } catch (Exception e) { logger.error(e); return ResponseEntity.internalServerError() diff --git a/src/main/java/com/eactive/eai/rms/onl/manage/adapter/adapter/AdapterManService.java b/src/main/java/com/eactive/eai/rms/onl/manage/adapter/adapter/AdapterManService.java index 15d0885..75c4f22 100644 --- a/src/main/java/com/eactive/eai/rms/onl/manage/adapter/adapter/AdapterManService.java +++ b/src/main/java/com/eactive/eai/rms/onl/manage/adapter/adapter/AdapterManService.java @@ -565,7 +565,7 @@ public class AdapterManService extends OnlBaseService { return adapterPropLoader.findSnaAppcodeProps((String) map.get("searchPrptygroupName")); } - public AdapterGroupUI importAdapterFromJson(MultipartFile file) throws Exception { + public AdapterGroupUI importAdapterFromJson(MultipartFile file, boolean importDupliCheck) throws Exception { if (file.isEmpty()) { throw new IOException("File is empty"); } @@ -574,13 +574,28 @@ public class AdapterManService extends OnlBaseService { ObjectMapper objectMapper = new ObjectMapper(); objectMapper.addMixIn(AdapterGroupUI.class, AdapterGroupUIMixIn.class); AdapterGroupUI adapterGroupUI = objectMapper.readValue(inputStream, AdapterGroupUI.class); - - if(!adapterGroupService.existsById(adapterGroupUI.getAdptrbzwkgroupname())){ + + String adapterGrpupName = adapterGroupUI.getAdptrbzwkgroupname(); + boolean adapterGroupExists = adapterGroupService.existsById(adapterGroupUI.getAdptrbzwkgroupname()); + + if (adapterGroupExists) { + + if (importDupliCheck) { + throw new IllegalStateException("AdapterGroup already exists [ " + adapterGrpupName + " ]" ); + } else { + update(adapterGroupUI); + } + + } else { insert(adapterGroupUI); - }else{ - update(adapterGroupUI); } +// if(!adapterGroupService.existsById(adapterGroupUI.getAdptrbzwkgroupname())){ +// insert(adapterGroupUI); +// }else{ +// update(adapterGroupUI); +// } + return adapterGroupUI; } } diff --git a/src/main/java/com/eactive/eai/rms/onl/manage/authserver/scope/ApiScopeController.java b/src/main/java/com/eactive/eai/rms/onl/manage/authserver/scope/ApiScopeController.java index 48f832f..7f25816 100644 --- a/src/main/java/com/eactive/eai/rms/onl/manage/authserver/scope/ApiScopeController.java +++ b/src/main/java/com/eactive/eai/rms/onl/manage/authserver/scope/ApiScopeController.java @@ -4,6 +4,15 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import com.eactive.eai.agent.authserver.ReloadApiScopeCommand; +import com.eactive.eai.agent.command.CommonCommand; +import com.eactive.eai.rms.common.base.OnlBaseAnnotationController; +import com.eactive.eai.rms.common.vo.GridResponse; +import com.eactive.eai.rms.data.entity.onl.eaimsg.ApiScopeSearch; +import com.eactive.eai.rms.onl.common.exception.BizException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -11,6 +20,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; import com.eactive.eai.agent.authserver.ReloadApiScopeCommand; import com.eactive.eai.agent.command.CommonCommand; @@ -53,6 +63,25 @@ public class ApiScopeController extends OnlBaseAnnotationController { return ResponseEntity.ok().build(); } + @PostMapping(value = "/onl/admin/authserver/apiScopeMan.json", params = "cmd=INSERT_APILIST") + public ResponseEntity insertApiScopeList(@RequestParam("apiList") String apiListJson, @RequestParam("scopeId") String scopeId) { + + ObjectMapper mapper = new ObjectMapper(); + List apiScopeList = null; + + try { + apiScopeList = mapper.readValue(apiListJson, new TypeReference>(){}); + + } catch (Exception e) { + throw new BizException("데이터 변환 중 오류가 발생했습니다."); + } + + apiScopeManService.insertApiScopeRelationList(apiScopeList, scopeId); + + return ResponseEntity.ok().build(); + } + + @PostMapping(value = "/onl/admin/authserver/apiScopeMan.json", params = "cmd=DELETE") public ResponseEntity deleteApiScope(ApiScopeUI apiScopeUI) { apiScopeManService.deleteApiScopeRelation(apiScopeUI); diff --git a/src/main/java/com/eactive/eai/rms/onl/manage/authserver/scope/ApiScopeManService.java b/src/main/java/com/eactive/eai/rms/onl/manage/authserver/scope/ApiScopeManService.java index b1caa62..fe68165 100644 --- a/src/main/java/com/eactive/eai/rms/onl/manage/authserver/scope/ApiScopeManService.java +++ b/src/main/java/com/eactive/eai/rms/onl/manage/authserver/scope/ApiScopeManService.java @@ -11,6 +11,8 @@ import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import com.eactive.eai.agent.authserver.ReloadApiScopeCommand; +import com.eactive.eai.agent.command.CommonCommand; import com.eactive.eai.rms.common.base.OnlBaseService; import com.eactive.eai.rms.common.spring.LocaleMessage; import com.eactive.eai.rms.data.entity.onl.authserver.ScopeEntityService; @@ -56,10 +58,12 @@ public class ApiScopeManService extends OnlBaseService { private ApiScopeUI convertToApiScopeUI(ApiScopeDto dto) { ApiScopeUI apiScopeUI = new ApiScopeUI(); - apiScopeUI.setEaiSvcDesc(dto.getEaiSvcDesc()); + apiScopeUI.setApiDesc(dto.getEaiSvcDesc()); apiScopeUI.setScopeId(dto.getScopeId()); apiScopeUI.setBzwkSvcKeyName(dto.getBzwkSvcKeyName()); apiScopeUI.setScopeName(dto.getScopeName()); + apiScopeUI.setApiFullPath(dto.getApiFullPath()); + apiScopeUI.setApiId(dto.getEaiSvcName()); return apiScopeUI; } @@ -72,6 +76,32 @@ public class ApiScopeManService extends OnlBaseService { scopeEntityService.save(apiScopeUIMapper.toEntity(apiScopeUI)); } + public void insertApiScopeRelationList(List apiScopeList, String scopeId) { + // ScopeId 기준 전체 삭제 후 LIST 기준으로 저장. + if(scopeId == null || scopeId.isEmpty()) { + throw new BizException("ScopeId 가 존재하지 않습니다."); + } + + scopeEntityService.deleteByIdScopeId(scopeId); + + if(apiScopeList == null || apiScopeList.isEmpty()) { + return; + } + + for (ApiScopeUI ui : apiScopeList) { + ui.setScopeId(scopeId); + + scopeEntityService.save(apiScopeUIMapper.toEntity(ui)); + + CommonCommand.builder() + .name(CommonCommand.RELOAD_API_SCOPE_COMMAND) + .args(new String[] {ReloadApiScopeCommand.COMMAND_TYPE_API_SCOPE, + ui.getBzwkSvcKeyName(), ui.getScopeId()}) + .build() + .broadcast(agentUtilService); + } + } + public void deleteApiScopeRelation(ApiScopeUI apiScopeUI) { scopeEntityService.deleteById(apiScopeUIMapper.toId(apiScopeUI)); } diff --git a/src/main/java/com/eactive/eai/rms/onl/manage/authserver/scope/ApiScopeUI.java b/src/main/java/com/eactive/eai/rms/onl/manage/authserver/scope/ApiScopeUI.java index f1407fd..a213032 100644 --- a/src/main/java/com/eactive/eai/rms/onl/manage/authserver/scope/ApiScopeUI.java +++ b/src/main/java/com/eactive/eai/rms/onl/manage/authserver/scope/ApiScopeUI.java @@ -1,10 +1,14 @@ package com.eactive.eai.rms.onl.manage.authserver.scope; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; +import lombok.NoArgsConstructor; @Data +@NoArgsConstructor // jackson 사용을 위해 추가. +@JsonIgnoreProperties(ignoreUnknown = true) public class ApiScopeUI { @JsonProperty("SCOPEID") @@ -16,7 +20,13 @@ public class ApiScopeUI { @JsonProperty("SCOPENAME") private String scopeName; - @JsonProperty("EAISVCDESC") - private String eaiSvcDesc; + @JsonProperty("apiDesc") + private String apiDesc; + + @JsonProperty("APIFULLPATH") + private String apiFullPath; + + @JsonProperty("apiId") + private String apiId; } diff --git a/src/main/java/com/eactive/eai/rms/onl/manage/inflow/group/InflowGroupControlManController.java b/src/main/java/com/eactive/eai/rms/onl/manage/inflow/group/InflowGroupControlManController.java new file mode 100644 index 0000000..c3e31bf --- /dev/null +++ b/src/main/java/com/eactive/eai/rms/onl/manage/inflow/group/InflowGroupControlManController.java @@ -0,0 +1,199 @@ +package com.eactive.eai.rms.onl.manage.inflow.group; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import com.eactive.eai.agent.command.CommonCommand; +import com.eactive.eai.rms.common.base.OnlBaseAnnotationController; +import com.eactive.eai.rms.common.combo.ComboService; +import com.eactive.eai.rms.common.combo.ComboVo; +import com.eactive.eai.rms.common.vo.GridResponse; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.servlet.ModelAndView; + +@Controller +public class InflowGroupControlManController extends OnlBaseAnnotationController { + + @Autowired + private InflowGroupControlManService service; + + @Autowired + private ComboService comboService; + + private ObjectMapper objectMapper = new ObjectMapper(); + + // ========== View Mappings ========== + + @RequestMapping(value = "/onl/admin/inflow/inflowGroupControlMan.view") + public String viewList() { + return "/onl/admin/inflow/inflowGroupControlMan"; + } + + @RequestMapping(value = "/onl/admin/inflow/inflowGroupControlMan.view", params = "cmd=DETAIL") + public String viewDetail() { + return "/onl/admin/inflow/inflowGroupControlManDetail"; + } + + @RequestMapping(value = "/onl/admin/inflow/inflowGroupControlMan.view", params = "cmd=INTERFACE_POPUP") + public String viewInterfacePopup() { + return "/onl/admin/inflow/inflowGroupInterfacePopup"; + } + + // ========== JSON API Mappings ========== + + /** + * 그룹 목록 조회 + */ + @RequestMapping(value = "/onl/admin/inflow/inflowGroupControlMan.json", params = "cmd=LIST") + public ResponseEntity> selectList(HttpServletRequest request, + Pageable pageVo, String searchGroupName) { + Page uiPage = service.selectGroupList(pageVo, searchGroupName); + return ResponseEntity.ok(new GridResponse<>(uiPage)); + } + + /** + * 그룹 상세 조회 + */ + @RequestMapping(value = "/onl/admin/inflow/inflowGroupControlMan.json", params = "cmd=DETAIL") + public ResponseEntity selectDetail(HttpServletRequest request, + HttpServletResponse response, String groupId) { + InflowGroupControlManUI ui = service.selectGroupDetail(groupId); + return ResponseEntity.ok(ui); + } + + /** + * 그룹 저장 (INSERT) + */ + @RequestMapping(value = "/onl/admin/inflow/inflowGroupControlMan.json", params = "cmd=INSERT") + public ResponseEntity insert(HttpServletRequest request, HttpServletResponse response, InflowGroupControlManUI ui, + @RequestParam(value = "interfaceListJson", required = false) String interfaceListJson) throws Exception { + parseInterfaceList(ui, interfaceListJson); + String modifiedBy = getSessionUserId(request); + service.mergeGroup(ui, modifiedBy); + + // Agent Command 전송 + CommonCommand command = new CommonCommand("com.eactive.eai.agent.inflow.ReloadInflowGroupControlCommand", + ui.getGroupId()); + agentUtilService.broadcast(command); + return ResponseEntity.ok().build(); + } + + /** + * 그룹 저장 (UPDATE) + */ + @RequestMapping(value = "/onl/admin/inflow/inflowGroupControlMan.json", params = "cmd=UPDATE") + public ResponseEntity update(HttpServletRequest request, HttpServletResponse response, InflowGroupControlManUI ui, + @RequestParam(value = "interfaceListJson", required = false) String interfaceListJson) throws Exception { + parseInterfaceList(ui, interfaceListJson); + String modifiedBy = getSessionUserId(request); + service.mergeGroup(ui, modifiedBy); + + // Agent Command 전송 + CommonCommand command = new CommonCommand("com.eactive.eai.agent.inflow.ReloadInflowGroupControlCommand", + ui.getGroupId()); + agentUtilService.broadcast(command); + return ResponseEntity.ok().build(); + } + + /** + * 그룹 삭제 + */ + @RequestMapping(value = "/onl/admin/inflow/inflowGroupControlMan.json", params = "cmd=DELETE") + public ResponseEntity delete(HttpServletRequest request, HttpServletResponse response, String groupId) throws Exception { + service.deleteGroup(groupId); + + // Agent Command 전송 + CommonCommand command = new CommonCommand("com.eactive.eai.agent.inflow.RemoveInflowGroupControlCommand", + groupId); + agentUtilService.broadcast(command); + return ResponseEntity.ok().build(); + } + + /** + * 콤보박스 초기화 데이터 + */ + @RequestMapping(value = "/onl/admin/inflow/inflowGroupControlMan.json", params = "cmd=LIST_INIT_COMBO") + public ModelAndView initCombo(HttpServletRequest request, HttpServletResponse response) { + List useYnRows = comboService.getFromCode("USE_YN"); + List timeUnitRows = comboService.getFromCode("INFLOW_CTRL_TIME_UNIT"); + + Map> resultMap = new HashMap<>(); + resultMap.put("useYnRows", useYnRows); + resultMap.put("timeUnitRows", timeUnitRows); + + return new ModelAndView("jsonView", resultMap); + } + + /** + * 인터페이스 목록 조회 (팝업용) + */ + @RequestMapping(value = "/onl/admin/inflow/inflowGroupControlMan.json", params = "cmd=INTERFACE_LIST") + public ResponseEntity> selectInterfaceList(HttpServletRequest request, + Pageable pageVo, String searchName) { + Page uiPage = service.selectInterfaceListForPopup(pageVo, searchName); + return ResponseEntity.ok(new GridResponse<>(uiPage)); + } + + /** + * 인터페이스 중복 등록 체크 + */ + @RequestMapping(value = "/onl/admin/inflow/inflowGroupControlMan.json", params = "cmd=LIST_CHECK_DUPLICATE") + public ModelAndView checkDuplicate(HttpServletRequest request, HttpServletResponse response, + @RequestParam("interfaceId") String interfaceId, + @RequestParam(value = "groupId", required = false) String groupId) { + String existingGroupName = service.checkInterfaceDuplicate(interfaceId, groupId); + + Map resultMap = new HashMap<>(); + resultMap.put("duplicate", existingGroupName != null); + resultMap.put("groupName", existingGroupName); + + return new ModelAndView("jsonView", resultMap); + } + + /** + * 그룹 버킷 상태 조회 (GW 서버별 현황) + */ + @RequestMapping(value = "/onl/admin/inflow/inflowGroupControlMan.json", params = "cmd=LIST_BUCKET_STATUS") + public ModelAndView getBucketStatus(HttpServletRequest request, HttpServletResponse response, + @RequestParam("groupId") String groupId) { + List> statusList = service.getGroupBucketStatus(groupId); + + Map resultMap = new HashMap<>(); + resultMap.put("servers", statusList); + + return new ModelAndView("jsonView", resultMap); + } + + /** + * JSON 문자열을 인터페이스 목록으로 변환 + */ + private void parseInterfaceList(InflowGroupControlManUI ui, String interfaceListJson) throws Exception { + if (interfaceListJson != null && !interfaceListJson.isEmpty()) { + List interfaceList = objectMapper.readValue(interfaceListJson, + new TypeReference>() { + }); + ui.setInterfaceList(interfaceList); + } + } + + /** + * 세션에서 사용자 ID 조회 + */ + private String getSessionUserId(HttpServletRequest request) { + Object userId = request.getSession().getAttribute("userId"); + return userId != null ? userId.toString() : "SYSTEM"; + } +} diff --git a/src/main/java/com/eactive/eai/rms/onl/manage/inflow/group/InflowGroupControlManMapper.java b/src/main/java/com/eactive/eai/rms/onl/manage/inflow/group/InflowGroupControlManMapper.java new file mode 100644 index 0000000..83cf138 --- /dev/null +++ b/src/main/java/com/eactive/eai/rms/onl/manage/inflow/group/InflowGroupControlManMapper.java @@ -0,0 +1,29 @@ +package com.eactive.eai.rms.onl.manage.inflow.group; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.NullValuePropertyMappingStrategy; +import org.mapstruct.ReportingPolicy; + +import com.eactive.eai.data.entity.onl.inflow.InflowControlGroup; +import com.eactive.eai.data.entity.onl.inflow.InflowControlGroupMapping; + +@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE) +public interface InflowGroupControlManMapper { + + InflowGroupControlManUI toVo(InflowControlGroup entity); + + InflowControlGroup toEntity(InflowGroupControlManUI vo); + + @BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE) + void updateToEntity(InflowGroupControlManUI vo, @MappingTarget InflowControlGroup entity); + + // Mapping Entity <-> UI + InflowGroupMappingUI toMappingVo(InflowControlGroupMapping entity); + + InflowControlGroupMapping toMappingEntity(InflowGroupMappingUI vo); + + @BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE) + void updateToMappingEntity(InflowGroupMappingUI vo, @MappingTarget InflowControlGroupMapping entity); +} diff --git a/src/main/java/com/eactive/eai/rms/onl/manage/inflow/group/InflowGroupControlManService.java b/src/main/java/com/eactive/eai/rms/onl/manage/inflow/group/InflowGroupControlManService.java new file mode 100644 index 0000000..29f7b34 --- /dev/null +++ b/src/main/java/com/eactive/eai/rms/onl/manage/inflow/group/InflowGroupControlManService.java @@ -0,0 +1,316 @@ +package com.eactive.eai.rms.onl.manage.inflow.group; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.client.RestTemplate; + +import com.eactive.eai.data.entity.onl.inflow.InflowControlGroup; +import com.eactive.eai.data.entity.onl.inflow.InflowControlGroupMapping; +import com.eactive.eai.data.entity.onl.inflow.QInflowControlGroup; +import com.eactive.eai.data.entity.onl.inflow.QInflowControlGroupMapping; +import com.eactive.eai.data.entity.onl.message.QEAIMessageEntity; +import com.eactive.eai.rms.common.base.BaseService; +import com.eactive.eai.rms.data.entity.onl.inflow.InflowControlGroupMappingRepository; +import com.eactive.eai.rms.data.entity.onl.inflow.InflowControlGroupRepository; +import com.eactive.eai.rms.onl.common.service.EaiServerInfoService; +import com.querydsl.core.BooleanBuilder; +import com.querydsl.core.Tuple; +import com.querydsl.jpa.impl.JPAQueryFactory; + +@Service +public class InflowGroupControlManService extends BaseService { + + private static final Logger logger = LoggerFactory.getLogger(InflowGroupControlManService.class); + + @PersistenceContext + EntityManager entityManager; + + @Autowired + private InflowControlGroupRepository groupRepository; + + @Autowired + private InflowControlGroupMappingRepository mappingRepository; + + @Autowired + private InflowGroupControlManMapper mapper; + + @Autowired + private EaiServerInfoService eaiServerInfoService; + + private RestTemplate restTemplate = new RestTemplate(); + + /** + * 그룹 목록 조회 + */ + public Page selectGroupList(Pageable pageable, String searchGroupName) { + JPAQueryFactory jpaQueryFactory = new JPAQueryFactory(entityManager); + QInflowControlGroup qGroup = QInflowControlGroup.inflowControlGroup; + + BooleanBuilder boolBuilder = new BooleanBuilder(); + if (!StringUtils.isEmpty(searchGroupName)) { + boolBuilder.and(qGroup.groupName.contains(searchGroupName)); + } + + List groupList = jpaQueryFactory + .selectFrom(qGroup) + .where(boolBuilder) + .orderBy(qGroup.groupId.asc()) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .fetch(); + + List uiList = groupList.stream() + .map(mapper::toVo) + .collect(Collectors.toList()); + + long totalCount = jpaQueryFactory + .select(qGroup.groupId.count()) + .from(qGroup) + .where(boolBuilder) + .fetchOne(); + + return new PageImpl<>(uiList, pageable, totalCount); + } + + /** + * 그룹 상세 조회 (매핑된 인터페이스 포함) + */ + public InflowGroupControlManUI selectGroupDetail(String groupId) { + Optional groupOpt = groupRepository.findById(groupId); + if (!groupOpt.isPresent()) { + return null; + } + + InflowGroupControlManUI ui = mapper.toVo(groupOpt.get()); + + // 매핑된 인터페이스 목록 조회 (인터페이스 설명 포함) + List mappingList = selectMappingListWithDesc(groupId); + ui.setInterfaceList(mappingList); + + return ui; + } + + /** + * 그룹에 매핑된 인터페이스 목록 조회 (인터페이스 설명 포함) + */ + private List selectMappingListWithDesc(String groupId) { + JPAQueryFactory jpaQueryFactory = new JPAQueryFactory(entityManager); + QInflowControlGroupMapping qMapping = QInflowControlGroupMapping.inflowControlGroupMapping; + QEAIMessageEntity qMessage = QEAIMessageEntity.eAIMessageEntity; + + List tupleList = jpaQueryFactory + .select(qMapping, qMessage.eaisvcdesc) + .from(qMapping) + .leftJoin(qMessage).on(qMapping.interfaceId.eq(qMessage.eaisvcname)) + .where(qMapping.groupId.eq(groupId)) + .orderBy(qMapping.interfaceId.asc()) + .fetch(); + + return tupleList.stream() + .map(tuple -> { + InflowGroupMappingUI mappingUI = mapper.toMappingVo(tuple.get(qMapping)); + mappingUI.setInterfaceDesc(tuple.get(qMessage.eaisvcdesc)); + return mappingUI; + }) + .collect(Collectors.toList()); + } + + /** + * 그룹 저장 (INSERT/UPDATE) + */ + @Transactional + public void mergeGroup(InflowGroupControlManUI ui, String modifiedBy) { + String groupId = ui.getGroupId(); + boolean isNew = StringUtils.isEmpty(groupId); + + InflowControlGroup entity; + if (!isNew) { + Optional groupOpt = groupRepository.findById(groupId); + if (groupOpt.isPresent()) { + entity = groupOpt.get(); + mapper.updateToEntity(ui, entity); + } else { + entity = mapper.toEntity(ui); + } + } else { + entity = mapper.toEntity(ui); + } + entity.setModifiedBy(modifiedBy); + entity.setModifiedAt(LocalDateTime.now()); + InflowControlGroup savedEntity = groupRepository.saveAndFlush(entity); + + // 저장된 엔티티의 groupId 사용 (신규 생성 시 시퀀스에서 생성된 ID) + String savedGroupId = savedEntity.getGroupId(); + + // 매핑 정보 저장 + saveMappings(savedGroupId, ui.getInterfaceList(), modifiedBy); + } + + /** + * 그룹-인터페이스 매핑 저장 + */ + @Transactional + public void saveMappings(String groupId, List interfaceList, String modifiedBy) { + // 기존 매핑 삭제 + mappingRepository.deleteByGroupId(groupId); + + // 새로운 매핑 저장 + if (interfaceList != null && !interfaceList.isEmpty()) { + for (InflowGroupMappingUI mappingUI : interfaceList) { + InflowControlGroupMapping mapping = new InflowControlGroupMapping(); + mapping.setInterfaceId(mappingUI.getInterfaceId()); + mapping.setGroupId(groupId); + // 화면에서 전달된 useYn 사용, 없으면 기본값 "1" + String useYn = StringUtils.isNotEmpty(mappingUI.getUseYn()) ? mappingUI.getUseYn() : "1"; + mapping.setUseYn(useYn); + mapping.setModifiedBy(modifiedBy); + mapping.setModifiedAt(LocalDateTime.now()); + mappingRepository.save(mapping); + } + } + } + + /** + * 그룹 삭제 + */ + @Transactional + public void deleteGroup(String groupId) { + // 매핑 먼저 삭제 + mappingRepository.deleteByGroupId(groupId); + // 그룹 삭제 + groupRepository.deleteById(groupId); + } + + /** + * 인터페이스 중복 등록 체크 (다른 그룹에 등록 여부) + * @param interfaceId 체크할 인터페이스 ID + * @param currentGroupId 현재 그룹 ID (수정 시 자기 그룹 제외, 신규 시 null) + * @return 등록된 그룹명 (미등록 시 null) + */ + public String checkInterfaceDuplicate(String interfaceId, String currentGroupId) { + Optional mappingOpt = mappingRepository.findByInterfaceId(interfaceId); + if (!mappingOpt.isPresent()) { + return null; + } + + InflowControlGroupMapping mapping = mappingOpt.get(); + String existingGroupId = mapping.getGroupId(); + + // 현재 그룹과 동일하면 중복 아님 (수정 시) + if (existingGroupId.equals(currentGroupId)) { + return null; + } + + // 그룹명 조회 + Optional groupOpt = groupRepository.findById(existingGroupId); + if (groupOpt.isPresent()) { + return groupOpt.get().getGroupName(); + } + return existingGroupId; // 그룹명 없으면 ID 반환 + } + + /** + * 인터페이스 목록 조회 (팝업용) + */ + public Page selectInterfaceListForPopup(Pageable pageable, String searchName) { + JPAQueryFactory jpaQueryFactory = new JPAQueryFactory(entityManager); + QEAIMessageEntity qMessage = QEAIMessageEntity.eAIMessageEntity; + + BooleanBuilder boolBuilder = new BooleanBuilder(); + if (!StringUtils.isEmpty(searchName)) { + boolBuilder.and(qMessage.eaisvcname.containsIgnoreCase(searchName) + .or(qMessage.eaisvcdesc.containsIgnoreCase(searchName))); + } + + List tupleList = jpaQueryFactory + .select(qMessage.eaisvcname, qMessage.eaisvcdesc) + .from(qMessage) + .where(boolBuilder) + .orderBy(qMessage.eaisvcname.asc()) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .fetch(); + + List uiList = tupleList.stream() + .map(tuple -> { + InflowGroupMappingUI ui = new InflowGroupMappingUI(); + ui.setInterfaceId(tuple.get(qMessage.eaisvcname)); + ui.setInterfaceDesc(tuple.get(qMessage.eaisvcdesc)); + return ui; + }) + .collect(Collectors.toList()); + + long totalCount = jpaQueryFactory + .select(qMessage.eaisvcname.count()) + .from(qMessage) + .where(boolBuilder) + .fetchOne(); + + return new PageImpl<>(uiList, pageable, totalCount); + } + + /** + * 그룹 버킷 상태 조회 (모든 GW 서버에서 수집) + */ + @SuppressWarnings("unchecked") + public List> getGroupBucketStatus(String groupId) { + List> result = new ArrayList<>(); + List> serverList = eaiServerInfoService.getEaiServerIpList(); + + for (Map server : serverList) { + String serverName = server.get("EAISVRINSTNM"); + String serverIp = server.get("EAISVRIP"); + String serverPort = server.get("WEBSERVERPORT"); + + if (StringUtils.isEmpty(serverPort)) { + serverPort = server.get("EAISVRLSNPORT"); + } + + Map serverStatus = new HashMap<>(); + serverStatus.put("serverName", serverName); + serverStatus.put("serverIp", serverIp); + serverStatus.put("serverPort", serverPort); + + try { + String url = String.format("http://%s:%s/manage/inflow/group/%s/bucket-status", + serverIp, serverPort, groupId); + ResponseEntity response = restTemplate.getForEntity(url, Map.class); + + if (response.getBody() != null && Boolean.TRUE.equals(response.getBody().get("success"))) { + serverStatus.put("status", "online"); + serverStatus.put("data", response.getBody().get("data")); + } else { + serverStatus.put("status", "no_data"); + serverStatus.put("message", "그룹이 로드되지 않음"); + } + } catch (Exception e) { + logger.warn("버킷 상태 조회 실패 - server: {}, error: {}", serverName, e.getMessage()); + serverStatus.put("status", "offline"); + serverStatus.put("message", "서버 연결 실패"); + } + + result.add(serverStatus); + } + + return result; + } +} diff --git a/src/main/java/com/eactive/eai/rms/onl/manage/inflow/group/InflowGroupControlManUI.java b/src/main/java/com/eactive/eai/rms/onl/manage/inflow/group/InflowGroupControlManUI.java new file mode 100644 index 0000000..2dac454 --- /dev/null +++ b/src/main/java/com/eactive/eai/rms/onl/manage/inflow/group/InflowGroupControlManUI.java @@ -0,0 +1,60 @@ +package com.eactive.eai.rms.onl.manage.inflow.group; + +import java.time.LocalDateTime; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonProperty; + +import lombok.Data; + +@Data +public class InflowGroupControlManUI { + /** + * 그룹 ID + */ + @JsonProperty("GROUPID") + private String groupId; + + /** + * 그룹명 + */ + @JsonProperty("GROUPNAME") + private String groupName; + + /** + * 임계치 + */ + @JsonProperty("THRESHOLD") + private Integer threshold; + + /** + * 초당 임계치 + */ + @JsonProperty("THRESHOLDPERSECOND") + private Integer thresholdPerSecond; + + /** + * 임계치 TimeUnit + */ + @JsonProperty("THRESHOLDTIMEUNIT") + private String thresholdTimeUnit; + + /** + * 사용여부 + */ + @JsonProperty("USEYN") + private String useYn; + + /** + * 수정일시 + */ + @JsonProperty("MODIFIEDAT") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime modifiedAt; + + /** + * 매핑된 인터페이스 목록 (저장용) + */ + private List interfaceList; +} diff --git a/src/main/java/com/eactive/eai/rms/onl/manage/inflow/group/InflowGroupMappingUI.java b/src/main/java/com/eactive/eai/rms/onl/manage/inflow/group/InflowGroupMappingUI.java new file mode 100644 index 0000000..1fdc8a4 --- /dev/null +++ b/src/main/java/com/eactive/eai/rms/onl/manage/inflow/group/InflowGroupMappingUI.java @@ -0,0 +1,37 @@ +package com.eactive.eai.rms.onl.manage.inflow.group; + +import com.fasterxml.jackson.annotation.JsonAlias; +import com.fasterxml.jackson.annotation.JsonProperty; + +import lombok.Data; + +@Data +public class InflowGroupMappingUI { + /** + * 인터페이스 ID + */ + @JsonProperty("INTERFACEID") + @JsonAlias("interfaceId") + private String interfaceId; + + /** + * 인터페이스 설명 (조회용) + */ + @JsonProperty("INTERFACEDESC") + @JsonAlias("interfaceDesc") + private String interfaceDesc; + + /** + * 그룹 ID + */ + @JsonProperty("GROUPID") + @JsonAlias("groupId") + private String groupId; + + /** + * 사용여부 + */ + @JsonProperty("USEYN") + @JsonAlias("useYn") + private String useYn; +} diff --git a/src/main/java/com/eactive/eai/rms/onl/manage/inflow/history/InflowControlHistoryManController.java b/src/main/java/com/eactive/eai/rms/onl/manage/inflow/history/InflowControlHistoryManController.java index da9d0b3..4473bb2 100644 --- a/src/main/java/com/eactive/eai/rms/onl/manage/inflow/history/InflowControlHistoryManController.java +++ b/src/main/java/com/eactive/eai/rms/onl/manage/inflow/history/InflowControlHistoryManController.java @@ -31,6 +31,13 @@ public class InflowControlHistoryManController extends OnlBaseAnnotationControll return "/onl/admin/inflow/inflowControlHistoryMan"; } +// @RequestMapping(value = "/onl/admin/inflow/inflowControlHistoryMan.json", params = "cmd=LIST") +// public ResponseEntity> selectList(HttpServletRequest request, +// Pageable pageable, InflowControlHistoryManUISearch uiSearch) { +// Page uiPage = service.selectList(pageable, uiSearch); +// return ResponseEntity.ok(new GridResponse<>(uiPage)); +// } + @RequestMapping(value = "/onl/admin/inflow/inflowControlHistoryMan.json", params = "cmd=LIST") public ResponseEntity> selectList(HttpServletRequest request, Pageable pageable, InflowControlHistoryManUISearch uiSearch) { diff --git a/src/main/java/com/eactive/eai/rms/onl/manage/inflow/history/InflowControlHistoryManService.java b/src/main/java/com/eactive/eai/rms/onl/manage/inflow/history/InflowControlHistoryManService.java index 2aa632b..8ee1410 100644 --- a/src/main/java/com/eactive/eai/rms/onl/manage/inflow/history/InflowControlHistoryManService.java +++ b/src/main/java/com/eactive/eai/rms/onl/manage/inflow/history/InflowControlHistoryManService.java @@ -23,8 +23,13 @@ public class InflowControlHistoryManService extends BaseService { @Autowired InflowControlHistoryManMapper mapper; +// public Page selectList(Pageable pageable, InflowControlHistoryManUISearch uiSearch) { +// return service.findAll(pageable, uiSearch).map(mapper::toVo); +// } + + public Page selectList(Pageable pageable, InflowControlHistoryManUISearch uiSearch) { - return service.findAll(pageable, uiSearch).map(mapper::toVo); + return service.selectList(pageable, uiSearch); } public List> selectListToExcel(InflowControlHistoryManUISearch uiSearch) { diff --git a/src/main/java/com/eactive/eai/rms/onl/manage/inflow/history/InflowControlHistoryManUI.java b/src/main/java/com/eactive/eai/rms/onl/manage/inflow/history/InflowControlHistoryManUI.java index 9b1bfb6..d63736c 100644 --- a/src/main/java/com/eactive/eai/rms/onl/manage/inflow/history/InflowControlHistoryManUI.java +++ b/src/main/java/com/eactive/eai/rms/onl/manage/inflow/history/InflowControlHistoryManUI.java @@ -66,6 +66,12 @@ public class InflowControlHistoryManUI { */ @JsonProperty("THRESHOLDTIMEUNIT") private String thresholdtimeunit; + + /** + * 그룹명 + */ + @JsonProperty("GROUPNAME") + private String groupname; } diff --git a/src/main/java/com/eactive/eai/rms/onl/manage/rule/transform2/Transform2Controller.java b/src/main/java/com/eactive/eai/rms/onl/manage/rule/transform2/Transform2Controller.java index 28cb813..0915282 100644 --- a/src/main/java/com/eactive/eai/rms/onl/manage/rule/transform2/Transform2Controller.java +++ b/src/main/java/com/eactive/eai/rms/onl/manage/rule/transform2/Transform2Controller.java @@ -15,6 +15,7 @@ import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; diff --git a/src/main/java/com/eactive/eai/rms/onl/manage/rule/transform2/Transform2Service.java b/src/main/java/com/eactive/eai/rms/onl/manage/rule/transform2/Transform2Service.java index a667814..cc2e444 100644 --- a/src/main/java/com/eactive/eai/rms/onl/manage/rule/transform2/Transform2Service.java +++ b/src/main/java/com/eactive/eai/rms/onl/manage/rule/transform2/Transform2Service.java @@ -171,7 +171,8 @@ public class Transform2Service extends BaseService { private String genConversionSourceItems(String srcLayoutName, String cnvsncmdname) { - Pattern pattern = Pattern.compile(srcLayoutName + "(\\w|\\[|\\]|\\*|\\.|-|:)*"); + // \uAC00-\uD7A3 : 한글 전체 ('가' ~ '힣') + Pattern pattern = Pattern.compile(srcLayoutName + "[\\w\\.\\[\\]\\*\\-:\\uAC00-\\uD7A3]*"); Matcher matcher = pattern.matcher(cnvsncmdname); StringBuilder result = new StringBuilder(); diff --git a/src/main/java/com/eactive/eai/rms/onl/manage/serviceinfo/stdmessage/StdMessageManController.java b/src/main/java/com/eactive/eai/rms/onl/manage/serviceinfo/stdmessage/StdMessageManController.java index ec5f5ce..6482828 100644 --- a/src/main/java/com/eactive/eai/rms/onl/manage/serviceinfo/stdmessage/StdMessageManController.java +++ b/src/main/java/com/eactive/eai/rms/onl/manage/serviceinfo/stdmessage/StdMessageManController.java @@ -9,6 +9,7 @@ import javax.servlet.http.HttpServletResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; @@ -52,7 +53,7 @@ public class StdMessageManController extends BaseController { @PostMapping(value = "/onl/admin/service/stdMessageMan.json", params = "cmd=LIST") public ResponseEntity> selectList(Pageable pageable, String searchBzwkSvcKeyName, String searchEaiSvcCode, String searchEaiSendRecv, String searchEaiDirection) { - List map = service + Page map = service .selectList(pageable, searchBzwkSvcKeyName, searchEaiSvcCode, searchEaiSendRecv, searchEaiDirection); return ResponseEntity.ok(new GridResponse<>(map)); diff --git a/src/main/java/com/eactive/eai/rms/onl/manage/serviceinfo/stdmessage/StdMessageManService.java b/src/main/java/com/eactive/eai/rms/onl/manage/serviceinfo/stdmessage/StdMessageManService.java index 15d84a6..6ecbf58 100644 --- a/src/main/java/com/eactive/eai/rms/onl/manage/serviceinfo/stdmessage/StdMessageManService.java +++ b/src/main/java/com/eactive/eai/rms/onl/manage/serviceinfo/stdmessage/StdMessageManService.java @@ -16,6 +16,7 @@ import org.apache.commons.lang3.StringUtils; import org.apache.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -59,14 +60,13 @@ public class StdMessageManService { @Autowired private ExtendedColumnDefinitionUIMapper extendedColumnDefinitionUIMapper; - public List selectList(Pageable pageable, String searchBzwkSvcKeyName, String searchEaiSvcCode, + public Page selectList(Pageable pageable, String searchBzwkSvcKeyName, String searchEaiSvcCode, String searchEaiSendRecv, String searchEaiDirection) { - return standardMessageInfoService - .findAll(pageable, searchBzwkSvcKeyName, searchEaiSvcCode, searchEaiSendRecv, searchEaiDirection) - .stream() - .map(stdMessageUIMapper::toVo) - .collect(Collectors.toList()); + Page pageResult = standardMessageInfoService + .findAll(pageable, searchBzwkSvcKeyName, searchEaiSvcCode, searchEaiSendRecv, searchEaiDirection); + + return pageResult.map(stdMessageUIMapper::toVo); } diff --git a/src/main/java/com/eactive/eai/rms/onl/transaction/apim/ApiInterfaceController.java b/src/main/java/com/eactive/eai/rms/onl/transaction/apim/ApiInterfaceController.java index 0cfa70f..0f21a97 100644 --- a/src/main/java/com/eactive/eai/rms/onl/transaction/apim/ApiInterfaceController.java +++ b/src/main/java/com/eactive/eai/rms/onl/transaction/apim/ApiInterfaceController.java @@ -271,10 +271,12 @@ public class ApiInterfaceController extends OnlBaseAnnotationController { } @PostMapping(value = "/onl/transaction/apim/apiInterfaceMan.json", params = "cmd=CLONE") - public ResponseEntity cloneApiInterface(String orgApiInterfaceId, String newBizCode, String newApiInterfaceId, String newInboundHttpMethod, String newInboundRestPath, boolean importDupliCheck) throws BizException { + public ResponseEntity cloneApiInterface(String orgApiInterfaceId, String newBizCode, + String newApiInterfaceId, String newInboundHttpMethod, + String newInboundRestPath, String newSvcDesc, boolean importDupliCheck) throws BizException { try { ApiInterfaceUI orgApiInterfaceUI = service.selectDetail(orgApiInterfaceId); - ApiInterfaceUI apiInterfaceUI = service.cloneApiInterface(orgApiInterfaceUI, newBizCode, newApiInterfaceId, newInboundHttpMethod, newInboundRestPath, importDupliCheck); + ApiInterfaceUI apiInterfaceUI = service.cloneApiInterface(orgApiInterfaceUI, newBizCode, newApiInterfaceId, newInboundHttpMethod, newInboundRestPath, newSvcDesc, importDupliCheck); Map resultMap = reloadSync(apiInterfaceUI); return ResponseEntity.ok(resultMap); } catch(Exception e) { diff --git a/src/main/java/com/eactive/eai/rms/onl/transaction/apim/ApiInterfaceService.java b/src/main/java/com/eactive/eai/rms/onl/transaction/apim/ApiInterfaceService.java index 416553c..0558a4e 100644 --- a/src/main/java/com/eactive/eai/rms/onl/transaction/apim/ApiInterfaceService.java +++ b/src/main/java/com/eactive/eai/rms/onl/transaction/apim/ApiInterfaceService.java @@ -6,6 +6,7 @@ import com.eactive.eai.data.entity.onl.message.QEAIMessageEntity; import com.eactive.eai.data.entity.onl.message.ServiceMessageEntity; import com.eactive.eai.data.entity.onl.message.ServiceMessageEntityId; import com.eactive.eai.data.entity.onl.messagekey.MessageKey; +import com.eactive.eai.data.entity.onl.stdmessage.QStandardMessageInfo; import com.eactive.eai.data.entity.onl.stdmessage.StandardMessageInfo; import com.eactive.eai.rms.common.base.OnlBaseService; import com.eactive.eai.rms.common.combo.ComboService; @@ -131,10 +132,14 @@ public class ApiInterfaceService extends OnlBaseService { } public Page selectList(EAIMessageUISearch eaiMessageUISearch, Pageable pageable,String sortname, String sortorder) { - Page tuples = eaiMessageQueryService.selectList(eaiMessageUISearch, pageable, sortname, sortorder ); + + Page tuples = eaiMessageQueryService.selectList(eaiMessageUISearch, pageable, sortname, sortorder ); return tuples.map(tuple -> { EAIMessageEntity eaiMessageEntity = tuple.get(QEAIMessageEntity.eAIMessageEntity); + + String apiFullPath = tuple.get(QStandardMessageInfo.standardMessageInfo.apifullpath); + String bzwksvckeyname = tuple.get(QStandardMessageInfo.standardMessageInfo.bzwksvckeyname); ApiInterfaceUI apiInterfaceUI; if (eaiMessageEntity.getServiceMessages() != null && eaiMessageEntity.getServiceMessages().size() > 0) { @@ -142,7 +147,10 @@ public class ApiInterfaceService extends OnlBaseService { } else { apiInterfaceUI = apiInterfaceUIMapper.toVo(eaiMessageEntity); } - + + apiInterfaceUI.setApiFullPath(apiFullPath); + apiInterfaceUI.setBzwksvckeyname(bzwksvckeyname); + return apiInterfaceUI; }); } @@ -242,6 +250,7 @@ public class ApiInterfaceService extends OnlBaseService { String stdKey = stdMessageUI.getBzwkSvcKeyName(); String inboundRestPath = StringUtils.substringAfter(stdKey, STEMSG_SERVICE_URL_DELIMITER); apiInterfaceUI.setInboundRestPath(inboundRestPath); + apiInterfaceUI.setApiFullPath(standardMessageInfo.getApifullpath()); apiInterfaceUI.setStandardMessageItems(stdMessageUI.getStandardMessageItems()); @@ -420,6 +429,9 @@ public class ApiInterfaceService extends OnlBaseService { case "NET": outbndRoutName = "SOCKETOUT"; break; + case "APP": + outbndRoutName = "APPOUT"; + break; default: outbndRoutName = "RESTOUT"; break; @@ -541,7 +553,7 @@ public class ApiInterfaceService extends OnlBaseService { restOption.setMethod(outboundHttpMethod); restOption.setExtraPath(outboundRestPath); - String pathType = outboundRestPath.matches(".*\\{[a-zA-Z]+\\}.*") ? "variableUrlRequest" : "simpleRequest"; + String pathType = outboundRestPath.matches(".*\\{[_a-zA-Z]+\\}.*") ? "variableUrlRequest" : "simpleRequest"; restOption.setType(pathType); if (StringUtils.isNotBlank(contentType)) { @@ -591,6 +603,7 @@ public class ApiInterfaceService extends OnlBaseService { // 25.11.25 HS04 APIFULLPATH 추가. List adapters = adapterGroup.getAdapters(); + String inboundHttpMethod = vo.getInboundHttpMethod().toUpperCase(); for (AdapterUI adapter : adapters) { @@ -603,19 +616,21 @@ public class ApiInterfaceService extends OnlBaseService { for (AdapterPropUI prop : props) { if("API_PATH".equals(prop.getPrptyname())) { - String fullPath = prop.getPrpty2val() + "/" + vo.getInboundRestPath(); + String fullPath = inboundHttpMethod + "|" + prop.getPrpty2val() + "/" + vo.getInboundRestPath(); vo.setApiFullPath(fullPath); + // HS04 AndEaiSvcNameNot : 업데이트일 때 자신의 fullpath 는 제외함 + standardMessageInfoQueryService.existsByApiFullPathAndEaiSvcNameNot(vo.getApiFullPath(), eaiServiceName); + break; } } } - } - - if (!"RST".equals(adapterGroup.getAdptrcd())) { - throw new RuntimeException("인바운드 어댑터가 타입이 REST가 아닙니다. 타입을 확인하세요"); + // HTTP 어댑터도 등록 가능하도록 수정. + if (!"RST".equals(adapterGroup.getAdptrcd())&&!"HTT".equals(adapterGroup.getAdptrcd())) { + throw new RuntimeException("인바운드 어댑터가 타입이 REST/HTTP가 아닙니다. 타입을 확인하세요"); } StandardMessageInfo standardMessageInfo = apiInterfaceUIMapper.toStandardMessageInfo(vo); @@ -1148,7 +1163,7 @@ public class ApiInterfaceService extends OnlBaseService { public ApiInterfaceUI cloneApiInterface(ApiInterfaceUI orgApiInterfaceUI, String newBizCd, String newApiInterfaceId, String newInboundHttpMethod, - String newInboundRestPath, boolean importDupliCheck) throws Exception { + String newInboundRestPath, String newSvcDesc, boolean importDupliCheck) throws Exception { Optional optional = eaiMessageQueryService.findById(newApiInterfaceId); if (optional.isPresent()) { @@ -1162,21 +1177,59 @@ public class ApiInterfaceService extends OnlBaseService { apiInterfaceUI.setEaiBzwkDstcd(newBizCd); settingInterfaceId(apiInterfaceUI, newApiInterfaceId); apiInterfaceUI.setEaiSvcName(newApiInterfaceId); + apiInterfaceUI.setEaiSvcDesc(newSvcDesc); apiInterfaceUI.setInboundHttpMethod(newInboundHttpMethod); apiInterfaceUI.setInboundRestPath(newInboundRestPath); if ("Y".equals(apiInterfaceUI.getTransformYn())) { - apiInterfaceUI.setInboundRequestLayout(changeToTargetLayoutUI(layoutManService.getLayoutUiToExport(apiInterfaceUI.getInboundRequestLayout()), newBizCd, newApiInterfaceId)); - apiInterfaceUI.setOutboundRequestLayout(changeToTargetLayoutUI(layoutManService.getLayoutUiToExport(apiInterfaceUI.getOutboundRequestLayout()), newBizCd, newApiInterfaceId)); - apiInterfaceUI.setOutboundResponseLayout(changeToTargetLayoutUI(layoutManService.getLayoutUiToExport(apiInterfaceUI.getOutboundResponseLayout()), newBizCd, newApiInterfaceId)); - apiInterfaceUI.setInboundResponseLayout(changeToTargetLayoutUI(layoutManService.getLayoutUiToExport(apiInterfaceUI.getInboundResponseLayout()), newBizCd, newApiInterfaceId)); - - apiInterfaceUI.setRequestTransform(changeToTargetTransformUI(transform2Service.getTransformUiToExport(apiInterfaceUI.getRequestTransform()), newBizCd, newApiInterfaceId, apiInterfaceUI.getInboundRequestLayout(), apiInterfaceUI.getOutboundRequestLayout())); - apiInterfaceUI.setResponseTransform(changeToTargetTransformUI(transform2Service.getTransformUiToExport(apiInterfaceUI.getResponseTransform()), newBizCd, newApiInterfaceId, apiInterfaceUI.getOutboundResponseLayout(), apiInterfaceUI.getInboundResponseLayout())); + + String newInReq = processLayoutIfExist(apiInterfaceUI.getInboundRequestLayout(), newBizCd, newApiInterfaceId); + String newOutReq = processLayoutIfExist(apiInterfaceUI.getOutboundRequestLayout(), newBizCd, newApiInterfaceId); + String newOutRes = processLayoutIfExist(apiInterfaceUI.getOutboundResponseLayout(), newBizCd, newApiInterfaceId); + String newInRes = processLayoutIfExist(apiInterfaceUI.getInboundResponseLayout(), newBizCd, newApiInterfaceId); + + apiInterfaceUI.setInboundRequestLayout(newInReq); + apiInterfaceUI.setOutboundRequestLayout(newOutReq); + apiInterfaceUI.setOutboundResponseLayout(newOutRes); + apiInterfaceUI.setInboundResponseLayout(newInRes); + + if (newInReq != null && newOutReq != null) { + apiInterfaceUI.setRequestTransform( + changeToTargetTransformUI( + transform2Service.getTransformUiToExport(apiInterfaceUI.getRequestTransform()), + newBizCd, newApiInterfaceId, newInReq, newOutReq + ) + ); + } + + if (newOutRes != null && newInRes != null) { + apiInterfaceUI.setResponseTransform( + changeToTargetTransformUI( + transform2Service.getTransformUiToExport(apiInterfaceUI.getResponseTransform()), + newBizCd, newApiInterfaceId, newOutRes, newInRes + ) + ); + } + } importApiInterfaceUi(apiInterfaceUI, importDupliCheck); return apiInterfaceUI; } + + /* + * 복제시 레이아웃 정보가 있는 경우에만 변환 로직을 수행 + */ + private String processLayoutIfExist(String layoutId, String newBizCd, String newApiInterfaceId) { + + if (layoutId == null || layoutId.isEmpty()) { + return null; + } + + LayoutUI layoutEntity = layoutManService.getLayoutUiToExport(layoutId); + + return changeToTargetLayoutUI(layoutEntity, newBizCd, newApiInterfaceId); + + } } diff --git a/src/main/java/com/eactive/eai/rms/onl/transaction/apim/StandardMessageInfoQueryServiceForApi.java b/src/main/java/com/eactive/eai/rms/onl/transaction/apim/StandardMessageInfoQueryServiceForApi.java index cf5300d..af98df0 100644 --- a/src/main/java/com/eactive/eai/rms/onl/transaction/apim/StandardMessageInfoQueryServiceForApi.java +++ b/src/main/java/com/eactive/eai/rms/onl/transaction/apim/StandardMessageInfoQueryServiceForApi.java @@ -21,6 +21,15 @@ public class StandardMessageInfoQueryServiceForApi extends AbstractDataService findByEaisvcname(String eaiSvcName); void deleteByEaisvcname(String eaiSvcName); + + boolean existsByApifullpathAndEaisvcnameNot(String fullPath, String eaiServiceName); } diff --git a/src/main/java/com/eactive/eai/rms/onl/transaction/apim/ui/ApiInterfaceUI.java b/src/main/java/com/eactive/eai/rms/onl/transaction/apim/ui/ApiInterfaceUI.java index a464377..9f34cc0 100644 --- a/src/main/java/com/eactive/eai/rms/onl/transaction/apim/ui/ApiInterfaceUI.java +++ b/src/main/java/com/eactive/eai/rms/onl/transaction/apim/ui/ApiInterfaceUI.java @@ -51,6 +51,7 @@ public class ApiInterfaceUI { private List inboundResponseStandardMessageItems ; private String apiFullPath; + private String bzwksvckeyname; //ASYNC-SYNC 용 private String inboundResponseHttpMethod; diff --git a/src/main/java/com/eactive/eai/rms/onl/transaction/tracking/DbTrackingController.java b/src/main/java/com/eactive/eai/rms/onl/transaction/tracking/DbTrackingController.java index 4267116..7cb03c2 100644 --- a/src/main/java/com/eactive/eai/rms/onl/transaction/tracking/DbTrackingController.java +++ b/src/main/java/com/eactive/eai/rms/onl/transaction/tracking/DbTrackingController.java @@ -454,11 +454,11 @@ public class DbTrackingController { @RequestMapping(value = "/onl/transaction/tracking/trackingMan.json", params = "cmd=DETAIL_HTTP_LOG") public ResponseEntity selectHttpAdapterExtraLog( - @RequestParam int dayOfWeekNumber, + @RequestParam String prcsDate, @RequestParam String guid, @RequestParam int serviceProcessNumber) { return ResponseEntity.ok(service.selectHttpAdapterExtraLog( - dayOfWeekNumber, guid, serviceProcessNumber)); + prcsDate, guid, serviceProcessNumber)); } @RequestMapping(value = "/onl/transaction/tracking/trackingMan.json", params = "cmd=DETAIL_8583") diff --git a/src/main/java/com/eactive/eai/rms/onl/transaction/tracking/DbTrackingService.java b/src/main/java/com/eactive/eai/rms/onl/transaction/tracking/DbTrackingService.java index 26b0f6c..ad9aaa0 100644 --- a/src/main/java/com/eactive/eai/rms/onl/transaction/tracking/DbTrackingService.java +++ b/src/main/java/com/eactive/eai/rms/onl/transaction/tracking/DbTrackingService.java @@ -439,18 +439,23 @@ public class DbTrackingService extends BaseService { return map; } - public HttpAdapterExtraLogVo selectHttpAdapterExtraLog(int dayOfWeekNumber, + public HttpAdapterExtraLogVo selectHttpAdapterExtraLog(String prcsDate, String guid, int serviceProcessNumber) { - Optional optional = httpAdapterExtraLogService - .findById(new HttpAdapterExtraLogId( - dayOfWeekNumber, guid, - serviceProcessNumber)); + HttpAdapterExtraLogId httpAdapterExtraLogId = new HttpAdapterExtraLogId(); + httpAdapterExtraLogId.setPrcsDate(prcsDate); + httpAdapterExtraLogId.setGuid(guid); + httpAdapterExtraLogId.setServiceProcessNumber(serviceProcessNumber); - if (optional.isPresent()) { - return httpAdapterExtraLogMapper.toVo(optional.get()); - } + HttpAdapterExtraLog httpAdapterExtraLog = httpAdapterExtraLogService.getById(httpAdapterExtraLogId); +// Optional optional = httpAdapterExtraLogService +// .findById(new HttpAdapterExtraLogId( +// prcsDate, guid, serviceProcessNumber)); +// +// if (optional.isPresent()) { +// return httpAdapterExtraLogMapper.toVo(optional.get()); +// } - return null; + return httpAdapterExtraLogMapper.toVo(httpAdapterExtraLog); } public String resultByMessage(EAILogDetailSearch paramVo, diff --git a/src/main/java/com/eactive/ext/kjb/statistics/통계테이블_설계.md b/src/main/java/com/eactive/ext/kjb/statistics/통계테이블_설계.md new file mode 100644 index 0000000..3fb40b5 --- /dev/null +++ b/src/main/java/com/eactive/ext/kjb/statistics/통계테이블_설계.md @@ -0,0 +1,83 @@ +# API Gateway 통계 테이블 설계 + +## 요구사항 정리 + +### 수집 차원 +- **시간 단위**: 분 단위 +- **분류 차원**: API명, API Gateway Instance, ClientId, Scope, 업무구분코드 (값이 없을 경우 'NONE' 으로 대체) + +### 메트릭 +- **처리 건수**: + - 총 건수 + - 성공 건수 + - 오류 건수 (Timeout / System Error / Business Error) +- **응답시간 통계**: + - 평균 응답시간 + - 최소 응답시간 + - 최대 응답시간 +- **추가 검토사항**: + - P50, P95 백분위 응답시간 + +### 집계 방식 +- **집계 주기**: 매분 배치 집계 +- **백분위 계산**: HdrHistogram 라이브러리 사용 (근사값, 메모리 효율적) + +--- + +## 테이블 DDL + +```sql +-- API Gateway 분단위 통계 테이블 +CREATE TABLE API_STATS_MINUTE ( + STAT_TIME TIMESTAMP(0) NOT NULL, -- 통계 시간 (분 단위, 초는 00) + API_NAME VARCHAR2(100) NOT NULL, -- API명 + GW_INSTANCE_ID VARCHAR2(50) NOT NULL, -- API Gateway 인스턴스 ID + CLIENT_ID VARCHAR2(100) NOT NULL, -- 클라이언트 ID + SCOPE VARCHAR2(100) DEFAULT 'NONE' NOT NULL, -- 스코프 + BIZ_DIV_CODE VARCHAR2(50) DEFAULT 'NONE' NOT NULL, -- 업무구분코드 + + -- 건수 메트릭 + TOTAL_CNT NUMBER(12) DEFAULT 0 NOT NULL, + SUCCESS_CNT NUMBER(12) DEFAULT 0 NOT NULL, + TIMEOUT_CNT NUMBER(12) DEFAULT 0 NOT NULL, + SYSTEM_ERR_CNT NUMBER(12) DEFAULT 0 NOT NULL, + BIZ_ERR_CNT NUMBER(12) DEFAULT 0 NOT NULL, + + -- 응답시간 메트릭 (ms, 소수점 3자리) + AVG_RESP_TIME NUMBER(10,3) DEFAULT 0, + MIN_RESP_TIME NUMBER(10,3) DEFAULT 0, + MAX_RESP_TIME NUMBER(10,3) DEFAULT 0, + P50_RESP_TIME NUMBER(10,3) DEFAULT 0, + P95_RESP_TIME NUMBER(10,3) DEFAULT 0, + + -- 관리 정보 + REG_DTIME TIMESTAMP DEFAULT SYSTIMESTAMP NOT NULL, + UPD_DTIME TIMESTAMP DEFAULT SYSTIMESTAMP, + + CONSTRAINT PK_API_STATS_MINUTE PRIMARY KEY ( + STAT_TIME, API_NAME, GW_INSTANCE_ID, CLIENT_ID, SCOPE, BIZ_DIV_CODE + ) +); + +-- 코멘트 +COMMENT ON TABLE API_STATS_MINUTE IS 'API Gateway 분단위 처리 통계'; +COMMENT ON COLUMN API_STATS_MINUTE.STAT_TIME IS '통계시간(분단위, 초는 00초로 절사)'; +COMMENT ON COLUMN API_STATS_MINUTE.API_NAME IS 'API 명칭'; +COMMENT ON COLUMN API_STATS_MINUTE.GW_INSTANCE_ID IS 'API Gateway 인스턴스 ID'; +COMMENT ON COLUMN API_STATS_MINUTE.CLIENT_ID IS '클라이언트 ID'; +COMMENT ON COLUMN API_STATS_MINUTE.SCOPE IS '스코프 (없으면 NONE)'; +COMMENT ON COLUMN API_STATS_MINUTE.BIZ_DIV_CODE IS '업무구분코드 (없으면 NONE)'; +COMMENT ON COLUMN API_STATS_MINUTE.TOTAL_CNT IS '총 처리 건수'; +COMMENT ON COLUMN API_STATS_MINUTE.SUCCESS_CNT IS '성공 건수'; +COMMENT ON COLUMN API_STATS_MINUTE.TIMEOUT_CNT IS 'Timeout 오류 건수'; +COMMENT ON COLUMN API_STATS_MINUTE.SYSTEM_ERR_CNT IS '시스템 오류 건수'; +COMMENT ON COLUMN API_STATS_MINUTE.BIZ_ERR_CNT IS '업무 오류 건수'; +COMMENT ON COLUMN API_STATS_MINUTE.AVG_RESP_TIME IS '평균 응답시간(ms)'; +COMMENT ON COLUMN API_STATS_MINUTE.MIN_RESP_TIME IS '최소 응답시간(ms)'; +COMMENT ON COLUMN API_STATS_MINUTE.MAX_RESP_TIME IS '최대 응답시간(ms)'; +COMMENT ON COLUMN API_STATS_MINUTE.P50_RESP_TIME IS '50 백분위 응답시간(ms)'; +COMMENT ON COLUMN API_STATS_MINUTE.P95_RESP_TIME IS '95 백분위 응답시간(ms)'; +``` + +--- + diff --git a/src/main/java/com/eactive/ext/kjb/util/KjbPropertyInjector.java b/src/main/java/com/eactive/ext/kjb/util/KjbPropertyInjector.java index b87061c..414bd4d 100644 --- a/src/main/java/com/eactive/ext/kjb/util/KjbPropertyInjector.java +++ b/src/main/java/com/eactive/ext/kjb/util/KjbPropertyInjector.java @@ -19,8 +19,46 @@ public class KjbPropertyInjector { this.groupId = groupId; } - public KjbProperty inject() { - return inject(new KjbProperty()); + /** + * KjbProperty의 URL 값들이 설정되어 있는지 확인 + * @param property 검사할 KjbProperty 객체 + * @return URL 값들이 모두 설정되어 있으면 true + */ + public boolean isUrlConfigured(KjbProperty property) { + if (property == null) return false; + + // 주요 URL 값들 중 하나라도 설정되어 있으면 true + return StringUtils.isNotEmpty(property.getEaiBatchUrl()) + || StringUtils.isNotEmpty(property.getUmsHostUrl()) + || StringUtils.isNotEmpty(property.getObpUrl()); + } + + /** + * URL 값이 비어있을 경우 DB에서 재로딩 시도 + * @param property 기존 KjbProperty 객체 + * @return 재로딩된 KjbProperty 객체 (재로딩 실패 시 기존 객체 반환) + */ + public KjbProperty reloadIfUrlEmpty(KjbProperty property) { + if (property == null) { + return inject(new KjbProperty()); + } + + if (!isUrlConfigured(property)) { + log.info("KjbProperty URL 값이 비어있어 DB에서 재로딩 시도"); + try { + KjbProperty reloaded = inject(new KjbProperty()); + if (isUrlConfigured(reloaded)) { + log.info("KjbProperty 재로딩 성공"); + return reloaded; + } else { + log.warn("KjbProperty 재로딩 후에도 URL 값이 비어있음"); + } + } catch (Exception e) { + log.error("KjbProperty 재로딩 실패: {}", e.getMessage(), e); + } + } + + return property; } public T inject(T property) { diff --git a/src/main/java/com/eactive/ext/kjb/web/ApiLogUI.java b/src/main/java/com/eactive/ext/kjb/web/ApiLogUI.java new file mode 100644 index 0000000..7572680 --- /dev/null +++ b/src/main/java/com/eactive/ext/kjb/web/ApiLogUI.java @@ -0,0 +1,17 @@ +package com.eactive.ext.kjb.web; + +import java.util.List; + +import lombok.Data; + +@Data +public class ApiLogUI { + + private String apiServiceNumber; + + private String txDate; + + private List logs; + + private Long totalProcessingTime; +} diff --git a/src/main/java/com/eactive/ext/kjb/web/ApiMessageLogUI.java b/src/main/java/com/eactive/ext/kjb/web/ApiMessageLogUI.java new file mode 100644 index 0000000..7e2989a --- /dev/null +++ b/src/main/java/com/eactive/ext/kjb/web/ApiMessageLogUI.java @@ -0,0 +1,35 @@ +package com.eactive.ext.kjb.web; + +import java.util.Map; + +import lombok.Data; + +@Data +public class ApiMessageLogUI { + + private String apiServiceNumber; + + private int processNumber; + + private String txDate; + + private String url; + + private String queryString; + + private String clientId; + + private String method; + + private Integer HttpStatus; + + private Map header; + + private Object body; + + private String exdata; + + private Long processingTime; + + private String guid; +} diff --git a/src/main/java/com/eactive/ext/kjb/web/ApiTransactionLogController.java b/src/main/java/com/eactive/ext/kjb/web/ApiTransactionLogController.java new file mode 100644 index 0000000..c18c317 --- /dev/null +++ b/src/main/java/com/eactive/ext/kjb/web/ApiTransactionLogController.java @@ -0,0 +1,142 @@ +package com.eactive.ext.kjb.web; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; + +import com.eactive.eai.rms.common.datasource.DataSourceContextHolder; +import com.eactive.eai.rms.common.datasource.DataSourceTypeManager; +import com.eactive.eai.rms.common.interceptor.InterceptorSkipController; +import com.eactive.eai.rms.data.entity.onl.logger.ApiLogDTO; +import com.eactive.eai.rms.data.entity.onl.logger.ApiMessageLogDTO; +import com.eactive.eai.rms.data.entity.onl.logger.EAILogService; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Controller +@RequiredArgsConstructor +public class ApiTransactionLogController implements InterceptorSkipController { + + private final EAILogService eaiLogService; + private final ObjectMapper objectMapper = new ObjectMapper(); + + @GetMapping("/api/transaction/log.do") + @PostMapping("/api/transaction/log.do") + public ResponseEntity getApiLog(String txDate, String apiServiceNumber) { + DataSourceContextHolder.setDataSourceType(DataSourceTypeManager.getDataSourceType(DataSourceTypeManager.APIGW)); + + // txDate가 없으면 오늘 날짜 설정 + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd"); + LocalDate searchDate; + if (StringUtils.hasText(txDate)) { + searchDate = LocalDate.parse(txDate, formatter); + } else { + searchDate = LocalDate.now(); + } + + // 최대 10일 전까지 조회 시도 + ApiLogDTO dto = null; + for (int i = 0; i < 10; i++) { + String searchDateStr = searchDate.format(formatter); + dto = eaiLogService.getApiLog(searchDateStr, apiServiceNumber); + + // 결과가 있으면 종료 + if (dto != null && dto.getLogs() != null && !dto.getLogs().isEmpty()) { + break; + } + + // 하루 전으로 이동 + searchDate = searchDate.minusDays(1); + } + + return ResponseEntity.ok(convertToUI(dto)); + } + + private ApiLogUI convertToUI(ApiLogDTO dto) { + ApiLogUI ui = new ApiLogUI(); + ui.setApiServiceNumber(dto.getApiServiceNumber()); + ui.setTxDate(dto.getTxDate()); + ui.setTotalProcessingTime(dto.getTotalProcessingTime()); + + if (dto.getLogs() != null) { + List uiLogs = dto.getLogs().stream().map(this::convertMessageToUI) + .collect(Collectors.toList()); + ui.setLogs(uiLogs); + } + + return ui; + } + + private ApiMessageLogUI convertMessageToUI(ApiMessageLogDTO dto) { + ApiMessageLogUI ui = new ApiMessageLogUI(); + ui.setApiServiceNumber(dto.getApiServiceNumber()); + ui.setProcessNumber(dto.getProcessNumber()); + ui.setTxDate(dto.getTxDate()); + ui.setUrl(dto.getUrl()); + ui.setQueryString(dto.getQueryString()); + ui.setClientId(dto.getClientId()); + ui.setMethod(dto.getMethod()); + ui.setHttpStatus(dto.getHttpStatus()); + ui.setExdata(dto.getExdata()); + ui.setProcessingTime(dto.getProcessingTime()); + ui.setGuid(dto.getKeymgtmsgctnt()); + + // header: JSON 문자열 → Map + if (dto.getHeader() != null) { + try { + List> headerList = objectMapper.readValue(dto.getHeader(), + new TypeReference>>() { + }); + + Map headerMap = new HashMap<>(); + for (Map headerItem : headerList) { + String name = headerItem.get("name"); + String value = headerItem.get("value"); + if (name != null) { + headerMap.put(name, value); + } + } + + // header에서 client*id 패턴 추출 (정규식, ignoreCase) + for (Map.Entry entry : headerMap.entrySet()) { + if (entry.getKey() != null && entry.getKey().matches("(?i)client.*id")) { + ui.setClientId(entry.getValue()); + break; + } + } + + ui.setHeader(headerMap); + } catch (Exception e) { + log.error("Failed to parse header JSON: {}", dto.getHeader(), e); + } + } + + // body: JSON 문자열 → Object + if (dto.getBody() != null) { + try { + Object bodyObject = objectMapper.readValue(dto.getBody(), Object.class); + ui.setBody(bodyObject); + } catch (Exception e) { + // JSON 파싱 실패 시 원본 문자열 사용 + ui.setBody(dto.getBody()); + } + } + + return ui; + } + +} diff --git a/src/main/java/com/eactive/ext/kjb/web/package-info.java b/src/main/java/com/eactive/ext/kjb/web/package-info.java new file mode 100644 index 0000000..03ec7ba --- /dev/null +++ b/src/main/java/com/eactive/ext/kjb/web/package-info.java @@ -0,0 +1 @@ +package com.eactive.ext.kjb.web; \ No newline at end of file diff --git a/src/main/resources/com/eactive/eai/apim/portalstatistics/EAILog-oracle.xml b/src/main/resources/com/eactive/eai/apim/portalstatistics/EAILog-oracle.xml index ed1d6ea..18b6ca6 100644 --- a/src/main/resources/com/eactive/eai/apim/portalstatistics/EAILog-oracle.xml +++ b/src/main/resources/com/eactive/eai/apim/portalstatistics/EAILog-oracle.xml @@ -32,7 +32,7 @@ FROM ( SELECT CLIENTID AS CLIENT_ID, - RAND() AS REQUEST_URI, + DBMS_RANDOM.VALUE AS REQUEST_URI, '$startTime$' AS STATISTICS_TIME, EAISVCNAME AS API_ID, NULL AS API_NAME, @@ -49,7 +49,7 @@ UNION ALL SELECT CLIENTID AS CLIENT_ID, - RAND() AS REQUEST_URI, + DBMS_RANDOM.VALUE AS REQUEST_URI, '$startTime$' AS STATISTICS_TIME, EAISVCNAME AS API_ID, NULL AS API_NAME, @@ -59,8 +59,8 @@ ORGID AS ORG_ID, ORGNAME AS ORG_NAME, CASE - WHEN SUBSTRING(RSPNSERRCDNAME, 1, 2) = 'RE' THEN 0 - WHEN SUBSTRING(RSPNSERRCDNAME, 1, 2) = 'FE' THEN 0 + WHEN SUBSTR(RSPNSERRCDNAME, 1, 2) = 'RE' THEN 0 + WHEN SUBSTR(RSPNSERRCDNAME, 1, 2) = 'FE' THEN 0 ELSE 1 END AS END_COUNT, 0 AS START_COUNT diff --git a/src/main/resources/com/eactive/eai/rms/onl/admin/jms/JmsQueueMonitor.xml b/src/main/resources/com/eactive/eai/rms/onl/admin/jms/JmsQueueMonitor.xml new file mode 100644 index 0000000..8d49e6e --- /dev/null +++ b/src/main/resources/com/eactive/eai/rms/onl/admin/jms/JmsQueueMonitor.xml @@ -0,0 +1,164 @@ + + + + + + + SELECT + COUNT(*) + FROM $schemaId$.TSEAIQM01 Q1, $schemaId$.TSEAICM01 C1 + WHERE Q1.EAIBZWKDSTCD = C1.EAIBZWKDSTCD + AND Q1.QUEUSEYN = '1' + + Q1.EAIBZWKDSTCD LIKE '%' || #searchEaiBzwkdstcd# || '%' + + + Q1.EAIQUEBZWKCTNT LIKE '%' || #searchEaiQueBzwkCtnt# ||'%' + + + Q1.EAISEVRINSTNCNAME LIKE '%'|| #searchInst# + + + Q1.NONSYNCZTRANQUENAME LIKE '%' || #searchQueName# || '%' + + + + SELECT * FROM ( + SELECT + ROW_NUMBER() OVER (ORDER BY Q1.EAIBZWKDSTCD, Q1.EAISEVRINSTNCNAME) AS "ROWNO", + Q1.EAIBZWKDSTCD "EAIBZWKDSTCD", + C1.BZWKDSTICNAME "BZWKDSTICNAME", + Q1.EAIQUEBZWKDTALSCTNT "EAIQUEBZWKCTNT", + Q1.EAISEVRINSTNCNAME "EAISEVRINSTNCNAME", + Q1.QUEUSAGDSTCD "QUEUSAGDSTCD", + Q1.NONSYNCZTRANQUENAME "NONSYNCZTRANQUENAME", + CASE WHEN QUEUSAGDSTCD = '1' + THEN 0 + END AS "SENDQUEUECOUNT", + CASE WHEN QUEUSAGDSTCD = '2' + THEN 0 + END AS "RECEIVEQUEUECOUNT", + CASE WHEN QUEUSAGDSTCD = '3' + THEN 0 + END AS "SENDERRORQUEUECOUNT", + CASE WHEN QUEUSAGDSTCD = '4' + THEN 0 + END AS "RECEIVEERRORCOUNT" + FROM $schemaId$.TSEAIQM01 Q1, $schemaId$.TSEAICM01 C1 + WHERE Q1.EAIBZWKDSTCD = C1.EAIBZWKDSTCD + AND Q1.QUEUSEYN = '1' + + Q1.EAIBZWKDSTCD LIKE '%' || #searchEaiBzwkdstcd# || '%' + + + Q1.EAIQUEBZWKCTNT LIKE '%' || #searchEaiQueBzwkCtnt# ||'%' + + + Q1.EAISEVRINSTNCNAME LIKE '%'|| #searchInst# + + + Q1.NONSYNCZTRANQUENAME LIKE '%' || #searchQueName# || '%' + + ORDER BY Q1.EAIBZWKDSTCD, Q1.EAIQUEBZWKCTNT, Q1.EAISEVRINSTNCNAME + ) TMP WHERE "ROWNO" BETWEEN #startNum# AND #endNum# + + + SELECT $headersStr$ FROM ( + SELECT + ROW_NUMBER() OVER (ORDER BY Q1.EAIBZWKDSTCD, Q1.EAISEVRINSTNCNAME) AS "ROWNO", + Q1.EAIBZWKDSTCD "EAIBZWKDSTCD", + C1.BZWKDSTICNAME "BZWKDSTICNAME", + Q1.EAIQUEBZWKCTNT "EAIQUEBZWKCTNT", + Q1.EAISEVRINSTNCNAME "EAISEVRINSTNCNAME", + Q1.QUEUSAGDSTCD "QUEUSAGDSTCD", + Q1.NONSYNCZTRANQUENAME "NONSYNCZTRANQUENAME", + CAST (CASE WHEN QUEUSAGDSTCD = '1' + THEN 0 + END AS VARCHAR )AS "SENDQUEUECOUNT", + CAST (CASE WHEN QUEUSAGDSTCD = '2' + THEN 0 + END AS VARCHAR )AS "RECEIVEQUEUECOUNT", + CAST (CASE WHEN QUEUSAGDSTCD = '3' + THEN 0 + END AS VARCHAR )AS "SENDERRORQUEUECOUNT", + CAST (CASE WHEN QUEUSAGDSTCD = '4' + THEN 0 + END AS VARCHAR ) "RECEIVEERRORCOUNT" + FROM $schemaId$.TSEAIQM01 Q1, $schemaId$.TSEAICM01 C1 + WHERE Q1.EAIBZWKDSTCD = C1.EAIBZWKDSTCD + AND Q1.QUEUSEYN = '1' + + Q1.EAIBZWKDSTCD LIKE '%' || #searchEaiBzwkdstcd# || '%' + + + Q1.EAIQUEBZWKCTNT LIKE '%' || #searchEaiQueBzwkCtnt# ||'%' + + + Q1.EAISEVRINSTNCNAME LIKE '%'|| #searchInst# + + + Q1.NONSYNCZTRANQUENAME LIKE '%' || #searchQueName# || '%' + + ORDER BY Q1.EAIBZWKDSTCD, Q1.EAIQUEBZWKCTNT, Q1.EAISEVRINSTNCNAME + ) TMP + + + select + Q1.EAIBzwkDstcd, + C1.BzwkDsticName, + Q1.EAIQueBzwkCtnt, + Q1.EAISevrInstncName, + Q1.QueUsagDstcd, + Q1.NonSynczTranQueName + from $schemaId$.tseaiqm01 Q1, $schemaId$.tseaicm01 C1 + where Q1.EAIBzwkDstcd = C1.EAIBzwkDstcd + and Q1.QueUseYn = '1' + + Q1.NonSynczTranQueName = #nonSynczTranQueName# + + ORDER BY Q1.EAIBzwkDstcd, Q1.EAIQueBzwkCtnt, Q1.EAISevrInstncName + + + + SELECT PrptyName, Prpty2Val + FROM $schemaId$.TSEAICM03 + WHERE PrptyGroupName = #prptyGroupName# + + + + SELECT EAISvcName, EAISvcDesc + FROM $schemaId$.TSEAIHE01 + + + + SELECT MAX(SUBSTR(EAISEVRINSTNCNAME,7,1)) + FROM $schemaId$.TSEAISY02 + WHERE EAISEVRIP != '*' + + + + SELECT MAX(SUBSTR(EAISEVRINSTNCNAME,8,1)) + FROM $schemaId$.TSEAISY02 + WHERE EAISEVRIP != '*' + + + SELECT + Q1.EAIBZWKDSTCD, C1.BZWKDSTICNAME, Q1.EAIQUEBZWKCTNT, Q1.EAISEVRINSTNCNAME, Q1.QUEUSAGDSTCD, Q1.NONSYNCZTRANQUENAME , + CASE WHEN QUEUSAGDSTCD = '1' + THEN 0 + END AS "SENDQUEUECOUNT", + CASE WHEN QUEUSAGDSTCD = '2' + THEN 0 + END AS "RECEIVEQUEUECOUNT", + CASE WHEN QUEUSAGDSTCD = '3' + THEN 0 + END AS "SENDERRORQUEUECOUNT", + CASE WHEN QUEUSAGDSTCD = '4' + THEN 0 + END AS "RECEIVEERRORCOUNT" + FROM $schemaId$.TSEAIQM01 Q1 + INNER JOIN $schemaId$.TSEAICM01 C1 ON Q1.EAIBZWKDSTCD = C1.EAIBZWKDSTCD + WHERE Q1.QUEUSEYN = '1' + ORDER BY Q1.EAIBZWKDSTCD, Q1.EAIQUEBZWKCTNT, Q1.EAISEVRINSTNCNAME + + diff --git a/src/main/resources/hotswap-agent.properties b/src/main/resources/hotswap-agent.properties index 3371a8b..cc2d7ce 100644 --- a/src/main/resources/hotswap-agent.properties +++ b/src/main/resources/hotswap-agent.properties @@ -1,9 +1,81 @@ -# HotSwapAgent 설정 -# Spring 관련 -Spring.reload=true -# Hibernate 관련 -Hibernate.reload=true -# 로그 레벨 -logLevel=INFO -# 자동 Hot Swap 활성화 +# ============================================================ +# HotswapAgent Configuration for eapim-admin (Spring Legacy) +# ============================================================ + +# \uC790\uB3D9 \uD56B\uC2A4\uC651 \uD65C\uC131\uD654 autoHotswap=true + +# \uB85C\uAE45 \uB808\uBCA8 (TRACE, DEBUG, INFO, WARNING, ERROR) +LOGGER.level=INFO + +# ============================================================ +# \uBA85\uC2DC\uC801 \uD50C\uB7EC\uADF8\uC778 \uBE44\uD65C\uC131\uD654 (\uC27C\uD45C \uAD6C\uBD84) +# ============================================================ +disabledPlugins=org.hotswap.agent.plugin.ibatis.IBatisPlugin,org.hotswap.agent.plugin.mybatis.MyBatisPlugin + +# ============================================================ +# Core Plugin \uD65C\uC131\uD654 +# ============================================================ + +# Spring Framework (\uD544\uC218) - Bean \uC7AC\uB4F1\uB85D +spring.enabled=true +spring.basePackagePrefix=com.eactive. + +# Hibernate/JPA - \uC5D4\uD2F0\uD2F0 \uBCC0\uACBD \uAC10\uC9C0 +hibernate.enabled=true + +# Logback - \uB85C\uADF8 \uC124\uC815 \uB9AC\uB85C\uB4DC +logback.enabled=true + +# Proxy - AOP \uD504\uB85D\uC2DC \uC7AC\uC0DD\uC131 +proxy.enabled=true + +# ============================================================ +# \uBE44\uD65C\uC131\uD654 Plugin (\uBBF8\uC0AC\uC6A9 \uB610\uB294 \uCDA9\uB3CC \uBC29\uC9C0) +# ============================================================ + +# iBatis (Spring\uC758 SqlMapClientFactoryBean\uACFC \uCDA9\uB3CC) +ibatis.enabled=false +mybatis.enabled=false +jsf.enabled=false +seam.enabled=false +wicket.enabled=false +mojarra.enabled=false +omnifaces.enabled=false +el.enabled=false +weld.enabled=false +owb.enabled=false +cdi.enabled=false +resteasy.enabled=false +jersey1.enabled=false +jersey2.enabled=false + +# ============================================================ +# Watch Resources (\uB9AC\uC18C\uC2A4 \uBCC0\uACBD \uAC10\uC9C0) +# ============================================================ + +# \uD074\uB798\uC2A4\uD328\uC2A4 \uC678 \uCD94\uAC00 \uAC10\uC2DC \uACBD\uB85C (XML \uC124\uC815 \uB4F1) +watchResources=src/main/resources,WebContent/WEB-INF + +# ============================================================ +# Spring \uC804\uC6A9 \uC124\uC815 +# ============================================================ + +# \uCEF4\uD3EC\uB10C\uD2B8 \uC2A4\uCE94 \uBCA0\uC774\uC2A4 \uD328\uD0A4\uC9C0 +spring.scanBasePackage=com.eactive.eai,com.eactive.apim + +# \uD504\uB85D\uC2DC \uC7AC\uC0DD\uC131 (AOP, @Transactional \uC0AC\uC6A9 \uC2DC \uD544\uC218) +spring.proxyRegeneration=true + +# Bean \uC815\uC758 \uBCC0\uACBD \uC2DC \uB9AC\uB85C\uB4DC +spring.reloadBeanDefinition=true + +# ============================================================ +# \uB514\uBC84\uAE45 (\uBB38\uC81C \uBC1C\uC0DD \uC2DC \uD65C\uC131\uD654) +# ============================================================ + +# \uC0C1\uC138 \uB85C\uADF8 \uCD9C\uB825 (\uD544\uC694\uC2DC DEBUG\uB85C \uBCC0\uACBD) +# LOGGER.level=DEBUG + +# \uD074\uB798\uC2A4 \uBCC0\uACBD \uCD94\uC801 +# LOGGER.org.hotswap.agent.plugin.spring=DEBUG diff --git a/src/test/java/com/eactive/eai/rms/onl/common/util/FileSystemUtilTest.java b/src/test/java/com/eactive/eai/rms/onl/common/util/FileSystemUtilTest.java new file mode 100644 index 0000000..0d2e19b --- /dev/null +++ b/src/test/java/com/eactive/eai/rms/onl/common/util/FileSystemUtilTest.java @@ -0,0 +1,50 @@ +package com.eactive.eai.rms.onl.common.util; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledOnOs; +import org.junit.jupiter.api.condition.OS; + +import java.io.File; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class FileSystemUtilTest { + + @Test + @EnabledOnOs(OS.LINUX) + void testPathNormalize_Linux() { + assertEquals("/asd1/asd2/asd3", FileSystemUtil.pathNormalize("/asd1/asd2", "asd3")); + assertEquals("/asd1/asd2/asd3", FileSystemUtil.pathNormalize("/asd1/asd2/", "/asd3")); + assertEquals("/asd1/asd2/asd3", FileSystemUtil.pathNormalize("/asd1/asd2", "/asd3")); + assertEquals("/asd1/asd2/asd3", FileSystemUtil.pathNormalize("/asd1/asd2/", "asd3/")); + assertEquals("/a/b/c", FileSystemUtil.pathNormalize("/a/", "/b/", "/c/")); + assertEquals("a/b/c", FileSystemUtil.pathNormalize("a", "b", "c")); + assertEquals("/a", FileSystemUtil.pathNormalize("/a")); + } + + @Test + @EnabledOnOs(OS.WINDOWS) + void testPathNormalize_Windows() { + assertEquals("\\asd1\\asd2\\asd3", FileSystemUtil.pathNormalize("/asd1/asd2", "asd3")); + assertEquals("\\asd1\\asd2\\asd3", FileSystemUtil.pathNormalize("/asd1/asd2/", "/asd3")); + assertEquals("\\asd1\\asd2\\asd3", FileSystemUtil.pathNormalize("/asd1/asd2", "/asd3")); + assertEquals("\\asd1\\asd2\\asd3", FileSystemUtil.pathNormalize("/asd1/asd2/", "asd3/")); + assertEquals("C:\\Users\\Test", FileSystemUtil.pathNormalize("C:/Users", "Test")); + assertEquals("C:\\Users\\Test", FileSystemUtil.pathNormalize("C:\\Users", "Test")); + assertEquals("\\a\\b\\c", FileSystemUtil.pathNormalize("/a/", "/b/", "/c/")); + assertEquals("a\\b\\c", FileSystemUtil.pathNormalize("a", "b", "c")); + assertEquals("\\a", FileSystemUtil.pathNormalize("/a")); + } + + @Test + void testPathNormalize_General() { + String expected = "a" + File.separator + "b" + File.separator + "c"; + assertEquals(expected, FileSystemUtil.pathNormalize("a", "b", "c")); + assertEquals("", FileSystemUtil.pathNormalize("")); + assertEquals("", FileSystemUtil.pathNormalize(null)); + assertEquals("", FileSystemUtil.pathNormalize()); + assertEquals("a", FileSystemUtil.pathNormalize("a")); + assertEquals("a", FileSystemUtil.pathNormalize("a/")); + assertEquals("a", FileSystemUtil.pathNormalize("/a")); + } +}