Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| dfb37c18b9 | |||
| 5f5b23026f | |||
| 5c61fc3f24 | |||
| a7aa5bc98c | |||
| bf63701e68 | |||
| f26aae296a | |||
| 8b4bc425ec | |||
| 06dc148515 | |||
| 3cfd6b0b04 | |||
| 7a0f88bfb1 | |||
| 6d7b292b8a | |||
| 20ad6f8394 | |||
| 2bae0e350c | |||
| 45e8fa2903 |
@@ -185,8 +185,9 @@ com.eactive.apim.portal/
|
||||
|
||||
설정: `config/PortalDatasourceConfiguration.java`
|
||||
- 각 데이터베이스별 별도 EntityManager
|
||||
- Atomikos JTA를 통한 분산 트랜잭션
|
||||
- 트랜잭션 로그: `/Log/eapim/portal/`
|
||||
- **JTA/XA 미사용**. EntityManagerFactory 별 로컬 트랜잭션 (`config/PortalConfigTransaction.java`)
|
||||
- `transactionManager` (@Primary) → EMS
|
||||
- `gatewayTransactionManager` → Gateway
|
||||
|
||||
### 설정 프로파일
|
||||
|
||||
@@ -359,7 +360,13 @@ return queryFactory.selectFrom(user)
|
||||
적절한 propagation과 함께 `@Transactional` 사용:
|
||||
- 기본값: `REQUIRED` (기존 트랜잭션에 참여하거나 새로 생성)
|
||||
- 읽기 전용 작업: 최적화를 위해 `@Transactional(readOnly = true)`
|
||||
- 다중 데이터베이스: Atomikos JTA가 자동으로 분산 트랜잭션 처리
|
||||
- **다중 데이터베이스: 분산 트랜잭션(JTA/XA) 없음.** EMS·Gateway 각각 독립 로컬 트랜잭션이다.
|
||||
- 무지정 `@Transactional` = EMS(`transactionManager`)
|
||||
- **Gateway 엔티티(`com.eactive.apim.gateway.*`, `com.eactive.eai.data.entity.onl.*`)를 다루는 서비스는
|
||||
`@Transactional("gatewayTransactionManager")` 를 명시**한다. 안 하면 게이트웨이 EntityManager 가
|
||||
리포지토리 호출 단위로 닫혀 지연 로딩에서 `LazyInitializationException` 이 난다
|
||||
(예: `ApiServiceService` — `ApiGroup.apiGroupApiList`).
|
||||
- 두 DB 를 한 원자 단위로 묶어야 하는 작업은 만들지 않는다. 현재 Gateway 는 조회 전용이다.
|
||||
|
||||
### 에러 처리
|
||||
|
||||
|
||||
@@ -0,0 +1,501 @@
|
||||
// 보안 검사 통합 파이프라인 — OWASP Dependency-Check(SCA) + SonarQube(SAST).
|
||||
//
|
||||
// 두 검사는 보는 대상이 다르다. 하나만 돌리면 절반만 본다.
|
||||
// SonarQube : 우리가 쓴 코드의 결함 (SQL Injection, XSS, 하드코딩 시크릿 …)
|
||||
// Dependency-Check : 우리가 쓰는 라이브러리의 알려진 취약점 (CVE)
|
||||
//
|
||||
// 전제 (Jenkins 쪽 설정 이름이 다르면 environment 블록만 고치면 된다)
|
||||
// 1) Manage Jenkins > System > SonarQube servers 에 서버 등록 (이름: SONAR_ENV_NAME)
|
||||
// 2) Manage Jenkins > Tools > SonarQube Scanner 에 스캐너 등록 (없으면 PATH 탐색)
|
||||
// 3) 노드에 Dependency-Check CLI 설치 (DC_HOME) — 설치 방법은 17-보안 가이드 문서 참고
|
||||
// 4) NVD API Key 를 Secret text credential 로 등록 (기본 ID: nvd-api-key)
|
||||
// 키가 없으면 NVD 갱신이 극심하게 느려진다(수 시간). 폐쇄망은 UPDATE_NVD=false 로 운영.
|
||||
// 5) Quality Gate 대기를 쓰려면 SonarQube 에 <JENKINS_URL>/sonarqube-webhook/ 웹훅 등록
|
||||
//
|
||||
// JDK 분리: 빌드는 JDK 8, 스캐너/Dependency-Check 실행은 JDK 17.
|
||||
// (SonarScanner CLI 5.x 는 JRE 17 필수, Dependency-Check 9.x 이상은 JRE 11 이상 필수)
|
||||
pipeline {
|
||||
agent { label 'djb-vm' }
|
||||
|
||||
triggers {
|
||||
// 코드 변경 감지
|
||||
pollSCM('H/30 * * * *')
|
||||
// 코드가 그대로여도 새 CVE 는 계속 공개된다 → 야간 정기 재검사
|
||||
cron('H 3 * * *')
|
||||
}
|
||||
|
||||
options {
|
||||
timestamps()
|
||||
disableConcurrentBuilds()
|
||||
buildDiscarder(logRotator(numToKeepStr: '20'))
|
||||
}
|
||||
|
||||
parameters {
|
||||
booleanParam(
|
||||
name: 'RUN_DEPENDENCY_CHECK',
|
||||
defaultValue: true,
|
||||
description: 'OWASP Dependency-Check(SCA) 실행'
|
||||
)
|
||||
booleanParam(
|
||||
name: 'RUN_SONAR',
|
||||
defaultValue: true,
|
||||
description: 'SonarQube 정적 분석(SAST) 실행'
|
||||
)
|
||||
booleanParam(
|
||||
name: 'RUN_TESTS',
|
||||
defaultValue: false,
|
||||
description: '단위 테스트를 함께 실행해 JUnit 결과를 Sonar 로 전송한다(분석 시간 증가).'
|
||||
)
|
||||
booleanParam(
|
||||
name: 'UPDATE_NVD',
|
||||
defaultValue: true,
|
||||
description: 'NVD 취약점 DB 갱신. 폐쇄망(외부 인터넷 불가)이면 반드시 끈다 — 캐시된 DB 로만 검사한다.'
|
||||
)
|
||||
string(
|
||||
name: 'FAIL_ON_CVSS',
|
||||
defaultValue: '11',
|
||||
description: '이 CVSS 점수 이상이면 검사 실패로 표시. 11 = 실패시키지 않음(리포트만). 예: 7 = High 이상'
|
||||
)
|
||||
booleanParam(
|
||||
name: 'FAIL_BUILD_ON_FINDING',
|
||||
defaultValue: false,
|
||||
description: 'FAIL_ON_CVSS 위반 시 빌드를 FAILURE 로 만든다. 끄면 UNSTABLE 로만 표시.'
|
||||
)
|
||||
booleanParam(
|
||||
name: 'SCAN_JS',
|
||||
defaultValue: true,
|
||||
description: '정적 JS 라이브러리(static/js, static/plugins)도 RetireJS 로 검사. 폐쇄망에서 오류나면 끈다.'
|
||||
)
|
||||
}
|
||||
|
||||
environment {
|
||||
// --- Jenkins 설정 이름 (환경에 맞게 수정) ---
|
||||
SONAR_ENV_NAME = 'SonarQube-Community'
|
||||
SONAR_SCANNER_TOOL = ''
|
||||
// NVD API Key 를 담은 Secret text credential ID. 없으면 키 없이 진행한다(느림).
|
||||
NVD_API_KEY_CRED = 'nvd-api-key'
|
||||
|
||||
// --- 툴체인 ---
|
||||
JAVA_HOME = '/apps/opts/jdk8' // gradle 컴파일용 (프로젝트는 Java 8)
|
||||
JAVA_HOME_SCANNER = '/apps/opts/jdk17' // sonar-scanner / dependency-check 실행용
|
||||
GRADLE_HOME = '/apps/opts/gradle-8.7'
|
||||
GRADLE_USER_HOME = '/apps/opts/gradle-home'
|
||||
PATH = "/apps/opts/jdk8/bin:/apps/opts/gradle-8.7/bin:/apps/opts/bin:${env.PATH}"
|
||||
GIT_SSH_COMMAND = 'ssh -o StrictHostKeyChecking=accept-new'
|
||||
|
||||
// --- Dependency-Check ---
|
||||
// CLI 설치 경로. 비어 있거나 없으면 Jenkins Tools 등록분 > PATH 순으로 탐색한다.
|
||||
DC_HOME = '/apps/opts/dependency-check'
|
||||
// NVD 캐시(H2 DB). 워크스페이스 밖에 두어야 빌드마다 1GB 이상을 다시 받지 않는다.
|
||||
// 여러 Job 이 공유하므로 disableConcurrentBuilds 를 켠 채로 쓴다.
|
||||
DC_DATA = '/apps/opts/dependency-check-data'
|
||||
DC_TOOL_NAME = ''
|
||||
DC_REPORT_DIR = 'build/reports/dependency-check'
|
||||
}
|
||||
|
||||
stages {
|
||||
stage('Checkout') {
|
||||
steps {
|
||||
checkout scm
|
||||
}
|
||||
}
|
||||
|
||||
// eapim-portal 은 elink-portal-common / elink-online-core-jpa 를 subproject 로 참조한다.
|
||||
// 이 둘이 없으면 컴파일도 의존성 해석도 불가능하다.
|
||||
stage('Checkout dependencies') {
|
||||
steps {
|
||||
sh '''
|
||||
set -eu
|
||||
cd "$WORKSPACE/.."
|
||||
|
||||
if [ ! -d elink-portal-common/.git ]; then
|
||||
rm -rf elink-portal-common
|
||||
git clone --depth=1 --branch master \
|
||||
ssh://git@172.30.1.50:2222/djb-eapim/elink-portal-common.git \
|
||||
elink-portal-common
|
||||
else
|
||||
git -C elink-portal-common fetch --depth=1 origin master
|
||||
git -C elink-portal-common reset --hard origin/master
|
||||
git -C elink-portal-common clean -fdx
|
||||
fi
|
||||
|
||||
mkdir -p eapim-online
|
||||
if [ ! -d eapim-online/elink-online-core-jpa/.git ]; then
|
||||
rm -rf eapim-online/elink-online-core-jpa
|
||||
git clone --depth=1 --branch master \
|
||||
ssh://git@172.30.1.50:2222/djb-eapim/elink-online-core-jpa.git \
|
||||
eapim-online/elink-online-core-jpa
|
||||
else
|
||||
git -C eapim-online/elink-online-core-jpa fetch --depth=1 origin master
|
||||
git -C eapim-online/elink-online-core-jpa reset --hard origin/master
|
||||
git -C eapim-online/elink-online-core-jpa clean -fdx
|
||||
fi
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
stage('Verify toolchain') {
|
||||
steps {
|
||||
sh '''
|
||||
set -eu
|
||||
echo "--- build JDK ---"
|
||||
java -version
|
||||
gradle --version
|
||||
|
||||
echo "--- scanner/dependency-check JDK ---"
|
||||
if [ ! -x "$JAVA_HOME_SCANNER/bin/java" ]; then
|
||||
echo "JDK 17 이 없다: $JAVA_HOME_SCANNER"
|
||||
echo "SonarScanner 는 JRE 17, Dependency-Check 는 JRE 11 이상을 요구한다."
|
||||
exit 1
|
||||
fi
|
||||
"$JAVA_HOME_SCANNER/bin/java" -version
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
// build/generated 잔여 산출물이 남으면 MapStruct Impl / QueryDSL Q클래스가
|
||||
// 중복 생성되어 컴파일이 깨진다. clean 이 파일 잠금 등으로 실패하는 경우가 있어
|
||||
// 생성 소스 디렉터리는 별도로 먼저 지운다.
|
||||
stage('Compile') {
|
||||
steps {
|
||||
sh '''
|
||||
set -eu
|
||||
rm -rf build/generated build/classes
|
||||
gradle clean classes testClasses --no-daemon
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
stage('Test') {
|
||||
when { expression { return params.RUN_TESTS } }
|
||||
steps {
|
||||
sh 'gradle test --no-daemon'
|
||||
}
|
||||
post {
|
||||
always {
|
||||
junit allowEmptyResults: true, testResults: 'build/test-results/test/*.xml'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 배포본에 실리는 것과 같은 목록(runtimeClasspath)만 모은다.
|
||||
// 이게 없으면 Dependency-Check 가 스캔할 jar 가 없어 "취약점 0건" 이라는 거짓 안심을 준다.
|
||||
stage('Export dependency jars') {
|
||||
when { expression { return params.RUN_DEPENDENCY_CHECK } }
|
||||
steps {
|
||||
sh 'gradle -I ci/dependency-check-classpath.gradle exportDependencyJars --no-daemon'
|
||||
sh '''
|
||||
set -eu
|
||||
COUNT=$(find build/dependency-check/libs -name '*.jar' -type f | wc -l | tr -d ' ')
|
||||
echo "scan target jars: $COUNT"
|
||||
if [ "$COUNT" -eq 0 ]; then
|
||||
echo "수집된 jar 가 0개다. 의존성 해석이 실패했는지 확인할 것."
|
||||
exit 1
|
||||
fi
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
stage('OWASP Dependency-Check') {
|
||||
when { expression { return params.RUN_DEPENDENCY_CHECK } }
|
||||
steps {
|
||||
script {
|
||||
// CLI 위치 결정: DC_HOME > Jenkins Tools 등록분 > PATH
|
||||
def dcHome = null
|
||||
if (env.DC_HOME?.trim() && fileExists("${env.DC_HOME}/bin/dependency-check.sh")) {
|
||||
dcHome = env.DC_HOME.trim()
|
||||
echo "Dependency-Check: ${dcHome} (DC_HOME)"
|
||||
} else {
|
||||
def candidates = env.DC_TOOL_NAME?.trim()
|
||||
? [env.DC_TOOL_NAME.trim()]
|
||||
: ['dependency-check', 'Dependency-Check', 'OWASP Dependency-Check', 'dependency-check-cli']
|
||||
for (name in candidates) {
|
||||
try {
|
||||
dcHome = tool name: name,
|
||||
type: 'org.jenkinsci.plugins.DependencyCheck.tools.DependencyCheckInstallation'
|
||||
echo "Dependency-Check tool: '${name}' -> ${dcHome}"
|
||||
break
|
||||
} catch (ignored) {
|
||||
// 등록되지 않은 이름은 건너뛴다
|
||||
}
|
||||
}
|
||||
if (dcHome == null) {
|
||||
echo 'Jenkins Tools 에 등록된 Dependency-Check 가 없다. PATH 의 dependency-check.sh 로 진행한다.'
|
||||
}
|
||||
}
|
||||
|
||||
// NVD API Key credential 이 있는지 먼저 확인한다.
|
||||
// (없는 credential 로 withCredentials 를 감싸면 파이프라인 자체가 죽는다)
|
||||
def hasNvdKey = true
|
||||
try {
|
||||
withCredentials([string(credentialsId: env.NVD_API_KEY_CRED, variable: 'NVD_PROBE')]) {
|
||||
// 존재 확인만 한다
|
||||
}
|
||||
} catch (ignored) {
|
||||
hasNvdKey = false
|
||||
}
|
||||
|
||||
if (!hasNvdKey) {
|
||||
echo "NVD API Key credential('${env.NVD_API_KEY_CRED}') 이 없다. 키 없이 진행한다."
|
||||
echo 'NVD 갱신이 매우 느려진다(수 시간). https://nvd.nist.gov/developers/request-an-api-key 에서 발급 권장.'
|
||||
}
|
||||
|
||||
def dcScript = '''
|
||||
set -eu
|
||||
|
||||
# Dependency-Check 는 JRE 11 이상 필요 (빌드용 JDK 8 과 분리)
|
||||
export JAVA_HOME="$JAVA_HOME_SCANNER"
|
||||
export PATH="$JAVA_HOME/bin:$PATH"
|
||||
|
||||
mkdir -p "$DC_DATA" "$DC_REPORT_DIR"
|
||||
|
||||
if [ -n "${DC_RESOLVED_HOME:-}" ] && [ -x "$DC_RESOLVED_HOME/bin/dependency-check.sh" ]; then
|
||||
DC="$DC_RESOLVED_HOME/bin/dependency-check.sh"
|
||||
elif command -v dependency-check.sh >/dev/null 2>&1; then
|
||||
DC="$(command -v dependency-check.sh)"
|
||||
else
|
||||
echo "dependency-check.sh 를 찾지 못했다."
|
||||
echo "노드에 CLI 를 설치하고 DC_HOME 을 맞추거나, Manage Jenkins > Tools 에 등록할 것."
|
||||
exit 1
|
||||
fi
|
||||
echo "dependency-check: $DC"
|
||||
|
||||
ARGS=""
|
||||
|
||||
# 폐쇄망/오프라인: 캐시된 NVD DB 로만 검사한다.
|
||||
if [ "${UPDATE_NVD_FLAG}" != "true" ]; then
|
||||
ARGS="$ARGS --noupdate"
|
||||
if [ ! -d "$DC_DATA" ] || [ -z "$(ls -A "$DC_DATA" 2>/dev/null)" ]; then
|
||||
echo "NVD 캐시가 비어 있는데 갱신이 꺼져 있다: $DC_DATA"
|
||||
echo "인터넷 되는 곳에서 1회 적재한 data 디렉터리를 복사해 둘 것."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -n "${NVD_API_KEY:-}" ]; then
|
||||
ARGS="$ARGS --nvdApiKey ${NVD_API_KEY}"
|
||||
fi
|
||||
|
||||
# OSS Index 분석기는 외부 서비스(ossindex.sonatype.org)를 호출한다.
|
||||
# 갱신을 끈 환경(=외부 통신 불가)에서는 같이 끈다.
|
||||
if [ "${UPDATE_NVD_FLAG}" != "true" ]; then
|
||||
ARGS="$ARGS --disableOssIndex"
|
||||
fi
|
||||
|
||||
# 자바 프로젝트에 불필요한 분석기 — 실행 시간만 늘린다.
|
||||
ARGS="$ARGS --disableAssembly --disableNodeAudit --disableNodeJS"
|
||||
|
||||
SCAN_ARGS="--scan build/dependency-check/libs"
|
||||
if [ "${SCAN_JS_FLAG}" = "true" ]; then
|
||||
# 번들된 JS 라이브러리(jQuery, summernote 등)의 알려진 취약점 — RetireJS
|
||||
[ -d src/main/resources/static/js ] && SCAN_ARGS="$SCAN_ARGS --scan src/main/resources/static/js"
|
||||
[ -d src/main/resources/static/plugins ] && SCAN_ARGS="$SCAN_ARGS --scan src/main/resources/static/plugins"
|
||||
else
|
||||
ARGS="$ARGS --disableRetireJS"
|
||||
fi
|
||||
|
||||
SUPPRESSION=""
|
||||
if [ -f ci/dependency-check-suppressions.xml ]; then
|
||||
SUPPRESSION="--suppression ci/dependency-check-suppressions.xml"
|
||||
fi
|
||||
|
||||
# 토큰/키가 콘솔에 남지 않도록 실행 명령 자체는 출력하지 않는다.
|
||||
set +x
|
||||
"$DC" \
|
||||
--project "DJB eAPIM Portal" \
|
||||
$SCAN_ARGS \
|
||||
--out "$DC_REPORT_DIR" \
|
||||
--format HTML --format XML --format JSON \
|
||||
--data "$DC_DATA" \
|
||||
--failOnCVSS "${FAIL_ON_CVSS_VALUE}" \
|
||||
$SUPPRESSION \
|
||||
$ARGS
|
||||
'''
|
||||
|
||||
def dcEnv = [
|
||||
"DC_RESOLVED_HOME=${dcHome ?: ''}",
|
||||
"UPDATE_NVD_FLAG=${params.UPDATE_NVD}",
|
||||
"SCAN_JS_FLAG=${params.SCAN_JS}",
|
||||
"FAIL_ON_CVSS_VALUE=${params.FAIL_ON_CVSS}"
|
||||
]
|
||||
|
||||
// 정책: 기본은 UNSTABLE 로만 표시하고 뒤의 Sonar 분석까지 마친다.
|
||||
// FAIL_BUILD_ON_FINDING 을 켜면 임계치 위반이 빌드 실패가 된다.
|
||||
def onFinding = params.FAIL_BUILD_ON_FINDING ? 'FAILURE' : 'UNSTABLE'
|
||||
catchError(buildResult: onFinding, stageResult: 'FAILURE') {
|
||||
withEnv(dcEnv) {
|
||||
if (hasNvdKey) {
|
||||
withCredentials([string(credentialsId: env.NVD_API_KEY_CRED, variable: 'NVD_API_KEY')]) {
|
||||
sh dcScript
|
||||
}
|
||||
} else {
|
||||
sh dcScript
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
post {
|
||||
always {
|
||||
archiveArtifacts allowEmptyArchive: true,
|
||||
artifacts: 'build/reports/dependency-check/dependency-check-report.*'
|
||||
script {
|
||||
// OWASP Dependency-Check 플러그인이 설치돼 있으면 추이 그래프/경고를 남긴다.
|
||||
// 없어도 리포트는 위에서 아카이브되므로 실패로 보지 않는다.
|
||||
try {
|
||||
dependencyCheckPublisher pattern: 'build/reports/dependency-check/dependency-check-report.xml'
|
||||
} catch (ignored) {
|
||||
echo 'OWASP Dependency-Check 플러그인 미설치 — 아카이브된 HTML 리포트로 확인할 것.'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// sonar.java.libraries 용 클래스패스 덤프.
|
||||
// 없으면 타입 해석이 안 돼 보안 룰 다수가 침묵하므로 실패 시 UNSTABLE 로만 넘기고 분석은 계속한다.
|
||||
stage('Export analysis classpath') {
|
||||
when { expression { return params.RUN_SONAR } }
|
||||
steps {
|
||||
catchError(buildResult: 'UNSTABLE', stageResult: 'FAILURE') {
|
||||
sh 'gradle -I ci/sonar-classpath.gradle exportSonarClasspath --no-daemon'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('SonarQube analysis') {
|
||||
when { expression { return params.RUN_SONAR } }
|
||||
steps {
|
||||
script {
|
||||
// 스캐너 위치 결정: 지정 이름 > 흔한 등록 이름 후보 > PATH 의 sonar-scanner
|
||||
def candidates = env.SONAR_SCANNER_TOOL?.trim()
|
||||
? [env.SONAR_SCANNER_TOOL.trim()]
|
||||
: ['SonarScanner', 'SonarQube Scanner', 'sonar-scanner', 'SonarScanner CLI', 'sonarqube-scanner']
|
||||
|
||||
def scannerHome = null
|
||||
for (name in candidates) {
|
||||
try {
|
||||
scannerHome = tool name: name, type: 'hudson.plugins.sonar.SonarRunnerInstallation'
|
||||
echo "SonarScanner tool: '${name}' -> ${scannerHome}"
|
||||
break
|
||||
} catch (ignored) {
|
||||
// 등록되지 않은 이름은 건너뛴다
|
||||
}
|
||||
}
|
||||
|
||||
if (scannerHome == null) {
|
||||
echo 'Jenkins Tools 에 등록된 SonarQube Scanner 를 찾지 못했다. PATH 의 sonar-scanner 로 진행한다.'
|
||||
echo '(Manage Jenkins > Tools 에 등록한 뒤 environment 의 SONAR_SCANNER_TOOL 에 그 이름을 넣으면 확실하다.)'
|
||||
}
|
||||
|
||||
withSonarQubeEnv(env.SONAR_ENV_NAME) {
|
||||
withEnv(["SONAR_SCANNER_HOME=${scannerHome ?: ''}"]) {
|
||||
sh '''
|
||||
set -eu
|
||||
|
||||
# 스캐너는 JDK 17 로 실행한다 (빌드용 JDK 8 과 분리)
|
||||
export JAVA_HOME="$JAVA_HOME_SCANNER"
|
||||
export PATH="$JAVA_HOME/bin:$PATH"
|
||||
|
||||
# 토큰은 커맨드라인(-Dsonar.token)에 노출시키지 않고 환경변수로만 넘긴다.
|
||||
if [ -z "${SONAR_TOKEN:-}" ] && [ -n "${SONAR_AUTH_TOKEN:-}" ]; then
|
||||
export SONAR_TOKEN="$SONAR_AUTH_TOKEN"
|
||||
fi
|
||||
|
||||
LIBS=""
|
||||
if [ -f build/sonar/java-libraries.txt ]; then
|
||||
LIBS=$(cat build/sonar/java-libraries.txt)
|
||||
else
|
||||
echo "WARN: build/sonar/java-libraries.txt 없음 - 타입 해석 정확도 저하"
|
||||
fi
|
||||
|
||||
TEST_LIBS=""
|
||||
if [ -f build/sonar/java-test-libraries.txt ]; then
|
||||
TEST_LIBS=$(cat build/sonar/java-test-libraries.txt)
|
||||
fi
|
||||
|
||||
JUNIT_ARG=""
|
||||
if [ -d build/test-results/test ]; then
|
||||
JUNIT_ARG="-Dsonar.junit.reportPaths=build/test-results/test"
|
||||
fi
|
||||
|
||||
# SonarQube 에 Dependency-Check 플러그인이 설치돼 있으면 CVE 결과도 함께 올린다.
|
||||
# 플러그인이 없으면 스캐너가 모르는 속성으로 무시한다(경고만).
|
||||
DC_ARG=""
|
||||
if [ -f "$DC_REPORT_DIR/dependency-check-report.json" ]; then
|
||||
DC_ARG="-Dsonar.dependencyCheck.jsonReportPath=$DC_REPORT_DIR/dependency-check-report.json"
|
||||
if [ -f "$DC_REPORT_DIR/dependency-check-report.html" ]; then
|
||||
DC_ARG="$DC_ARG -Dsonar.dependencyCheck.htmlReportPath=$DC_REPORT_DIR/dependency-check-report.html"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -n "${SONAR_SCANNER_HOME:-}" ] && [ -x "$SONAR_SCANNER_HOME/bin/sonar-scanner" ]; then
|
||||
SCANNER="$SONAR_SCANNER_HOME/bin/sonar-scanner"
|
||||
elif command -v sonar-scanner >/dev/null 2>&1; then
|
||||
SCANNER="$(command -v sonar-scanner)"
|
||||
else
|
||||
echo "sonar-scanner 실행 파일을 찾지 못했다."
|
||||
echo "Manage Jenkins > Tools > SonarQube Scanner 에 등록하거나 노드 PATH 에 설치할 것."
|
||||
exit 1
|
||||
fi
|
||||
echo "scanner: $SCANNER"
|
||||
|
||||
"$SCANNER" \
|
||||
-Dsonar.projectVersion="${BUILD_NUMBER}" \
|
||||
-Dsonar.java.libraries="$LIBS" \
|
||||
-Dsonar.java.test.libraries="$TEST_LIBS" \
|
||||
$JUNIT_ARG \
|
||||
$DC_ARG
|
||||
'''
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SonarQube 웹훅으로 게이트 결과를 받아 판정한다.
|
||||
// 정책: 게이트 실패는 UNSTABLE 로만 표시하고 빌드는 실패시키지 않는다.
|
||||
stage('Quality Gate') {
|
||||
when { expression { return params.RUN_SONAR } }
|
||||
steps {
|
||||
script {
|
||||
def qg = null
|
||||
try {
|
||||
timeout(time: 15, unit: 'MINUTES') {
|
||||
qg = waitForQualityGate abortPipeline: false
|
||||
}
|
||||
} catch (err) {
|
||||
unstable("Quality Gate 결과 대기 실패/타임아웃: ${err}")
|
||||
return
|
||||
}
|
||||
|
||||
if (qg == null) {
|
||||
unstable('Quality Gate 결과를 받지 못했다. SonarQube 웹훅 설정을 확인할 것.')
|
||||
} else if (qg.status != 'OK') {
|
||||
unstable("Quality Gate ${qg.status}")
|
||||
} else {
|
||||
echo 'Quality Gate OK'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
post {
|
||||
always {
|
||||
archiveArtifacts allowEmptyArchive: true, artifacts: '.scannerwork/report-task.txt'
|
||||
script {
|
||||
def report = '.scannerwork/report-task.txt'
|
||||
if (fileExists(report)) {
|
||||
def url = readFile(report).readLines().find { it.startsWith('dashboardUrl=') }
|
||||
if (url) {
|
||||
echo "SonarQube: ${url.substring('dashboardUrl='.length())}"
|
||||
}
|
||||
}
|
||||
if (fileExists('build/reports/dependency-check/dependency-check-report.html')) {
|
||||
echo "Dependency-Check 리포트: ${env.BUILD_URL}artifact/build/reports/dependency-check/dependency-check-report.html"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+67
-12
@@ -60,7 +60,6 @@ dependencies {
|
||||
// implementation project(':kjb-safedb')
|
||||
|
||||
implementation('org.springframework.boot:spring-boot-starter')
|
||||
implementation 'org.springframework.boot:spring-boot-starter-jta-atomikos'
|
||||
implementation('org.springframework.boot:spring-boot-starter-web')
|
||||
implementation('org.springframework.boot:spring-boot-starter-validation')
|
||||
|
||||
@@ -84,7 +83,10 @@ dependencies {
|
||||
implementation group: 'com.fasterxml.woodstox', name: 'woodstox-core', version: '6.5.1'
|
||||
|
||||
|
||||
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
|
||||
// Thymeleaf 3.1 코어가 #temporals 를 내장 제공 → java8time extras 를 함께 두면 표현식 객체가 중복 등록된다.
|
||||
implementation('org.springframework.boot:spring-boot-starter-thymeleaf') {
|
||||
exclude group: 'org.thymeleaf.extras', module: 'thymeleaf-extras-java8time'
|
||||
}
|
||||
implementation 'org.springframework.boot:spring-boot-starter-security'
|
||||
implementation('org.springframework.boot:spring-boot-starter-cache')
|
||||
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
|
||||
@@ -92,33 +94,40 @@ dependencies {
|
||||
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
|
||||
|
||||
developmentOnly 'org.springframework.boot:spring-boot-devtools'
|
||||
implementation 'org.springframework:spring-expression:5.3.30'
|
||||
// spring-expression 개별 pin 제거: 아래 ext 의 spring-framework.version 이 전 모듈을 일괄 관리한다.
|
||||
// 개별 pin 이 남아 있으면 다른 spring-* 모듈보다 낮은 버전으로 고정되어 버전이 어긋난다.
|
||||
|
||||
implementation group: 'xalan', name: 'xalan', version: '2.7.3'
|
||||
|
||||
implementation 'org.hibernate:hibernate-envers:5.6.15.Final'
|
||||
implementation 'nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect:3.0.0'
|
||||
implementation 'nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect:3.4.0' // 3.0.0 은 thymeleaf 3.0 전용
|
||||
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5'
|
||||
|
||||
implementation 'com.github.ua-parser:uap-java:1.5.3'
|
||||
// uap-java 제거: 소스 전체에 ua_parser 참조 0건이고, 1.5.3 은 snakeyaml 2.x 에서 삭제된
|
||||
// SafeConstructor() no-arg 를 호출해 NoSuchMethodError 를 낸다. 되살릴 경우 1.6.1 이상.
|
||||
implementation 'org.apache.httpcomponents:httpclient:4.5.14'
|
||||
implementation 'com.navercorp.lucy:lucy-xss-servlet:2.0.1'
|
||||
implementation 'javax.servlet:javax.servlet-api:4.0.0'
|
||||
implementation 'org.jasypt:jasypt:1.9.3'
|
||||
implementation 'xerces:xercesImpl:2.12.2'
|
||||
|
||||
implementation 'org.apache.commons:commons-lang3:3.12.0'
|
||||
// Uncontrolled recursion in ClassUtils.getClass(...) on very long inputs. 3.18.0+ 에서 수정.
|
||||
implementation 'org.apache.commons:commons-lang3:3.20.0'
|
||||
implementation 'org.apache.commons:commons-collections4:4.4'
|
||||
|
||||
implementation 'commons-net:commons-net:3.9.0'
|
||||
implementation('commons-beanutils:commons-beanutils:1.9.4') {
|
||||
// CVE-2025-48734 (PropertyUtilsBean 이 enum 의 declaredClass 프로퍼티 노출 → ClassLoader 접근/RCE).
|
||||
// 1.11.0 부터 SuppressPropertiesBeanIntrospector 가 기본 활성이라 declaredClass 접근이 차단된다.
|
||||
// 이 앱의 호출부(PasswordMatchValidator / PasswordRuleValidator / AuthNumberValidator)는
|
||||
// 어노테이션에 박힌 고정 프로퍼티명만 넘기므로 외부 입력 경로는 없지만 버전은 올려 둔다.
|
||||
// 1.11.0 = Java 8 바이트코드(major 52), PropertyUtils.getProperty/getNestedProperty API 동일.
|
||||
implementation('commons-beanutils:commons-beanutils:1.11.0') {
|
||||
// exclude group: 'commons-collections', module: 'commons-collections'
|
||||
}
|
||||
implementation 'org.mapstruct:mapstruct:1.5.5.Final'
|
||||
// WS-2026-0003 (jackson-core async parser DoS, CVSS 7.5) — 2.18.6 에서 수정. JDK8 호환.
|
||||
implementation 'com.fasterxml.jackson.core:jackson-core:2.18.6'
|
||||
implementation 'com.fasterxml.jackson.core:jackson-annotations:2.18.6'
|
||||
implementation 'com.fasterxml.jackson.core:jackson-databind:2.18.6'
|
||||
// jackson 개별 pin 제거: 아래 ext 의 jackson-bom.version 이 전 모듈을 일괄 관리한다.
|
||||
// 개별 pin 은 BOM 보다 우선하므로 남겨 두면 BOM 만 올렸을 때 core/annotations/databind 가
|
||||
// 옛 버전에 고정돼 버전이 어긋난다(실제로 그런 상태였다).
|
||||
|
||||
implementation group: 'org.apache.velocity', name: 'velocity-engine-core', version: '2.3'
|
||||
|
||||
@@ -128,7 +137,10 @@ dependencies {
|
||||
implementation 'net.bytebuddy:byte-buddy:1.14.5'
|
||||
|
||||
// Commons FileUpload (WAS 독립적인 multipart 처리)
|
||||
implementation 'commons-fileupload:commons-fileupload:1.5'
|
||||
// CVE-2025-48976 (멀티파트 파트 헤더 크기 제한 부재 → DoS). 1.6.0 에서 partHeaderSizeMax 도입.
|
||||
// 주의: 1.6 부터 파트 헤더 총량 기본 상한이 10240 → 512 바이트로 줄었다(DEFAULT_PART_HEADER_SIZE_MAX).
|
||||
// 한글 파일명은 UTF-8 로 3바이트/자라 Content-Disposition 이 길어질 수 있어 실측으로 여유를 확인했다.
|
||||
implementation 'commons-fileupload:commons-fileupload:1.6.0'
|
||||
implementation 'commons-io:commons-io:2.15.1'
|
||||
|
||||
|
||||
@@ -146,6 +158,49 @@ ext {
|
||||
springMavenArtifactVersion = '5.3.30'
|
||||
encoding = 'UTF-8'
|
||||
profile = 'local'
|
||||
|
||||
// 내장 Tomcat 버전 상향 (Spring Boot 2.7.18 기본값 9.0.83 → 9.0.120).
|
||||
// Boot BOM 의 tomcat.version 프로퍼티를 덮어써서 tomcat-embed-core/-el/-websocket 이 함께 올라간다.
|
||||
// 9.0.x 계열 유지 = Servlet 4.0 / javax.* 네임스페이스 그대로, JDK8 호환.
|
||||
set('tomcat.version', '9.0.120')
|
||||
|
||||
// CVE-2022-1471 (snakeyaml Constructor 임의 타입 역직렬화 → RCE). Boot 2.7.18 BOM 기본값 1.30 → 2.6.
|
||||
// 앱/프레임워크 실경로는 이미 SafeConstructor 계열(OriginTrackedYamlLoader, spring-beans
|
||||
// FilteringConstructor, swagger-parser DeserializationUtils)이라 익스플로잇 경로는 없었으나
|
||||
// SCA 는 버전으로 판정하므로 2.x 로 올린다. snakeyaml 2.6 = Java 8 바이트코드(major 52).
|
||||
// 2.6 인 이유: swagger-core 2.2.52 / swagger-parser-v3 2.1.45 가 요구하는 버전이라 강등이 없다.
|
||||
// 부수 효과: swagger-parser 가 호출하는 LoaderOptions.setCodePointLimit(1.32+ API) 도 해소.
|
||||
set('snakeyaml.version', '2.6')
|
||||
|
||||
// jackson 전 모듈 버전 통일(Boot 2.7.18 BOM 기본 2.13.5). 2.18.x 는 JDK8 호환 라인이다.
|
||||
// 이유 3가지
|
||||
// 1) snakeyaml 2.x 는 ParserImpl(StreamReader) 를 제거했고 jackson-dataformat-yaml 은
|
||||
// 2.15+ 부터 ParserImpl(StreamReader, LoaderOptions) 를 쓴다 — 위 snakeyaml 상향의 전제.
|
||||
// 2) WS-2026-0003 (jackson-core async parser DoS, CVSS 7.5) — 2.18.6 에서 수정.
|
||||
// 3) jackson-databind PolymorphicTypeValidator 우회(제네릭 타입 인자 미검증) — 2.18.8 에서 수정.
|
||||
// 이 앱은 다형성 역직렬화(activateDefaultTyping/@JsonTypeInfo)를 쓰지 않아 노출 경로는 없다.
|
||||
// 2.18.x 마지막 패치를 쓴다.
|
||||
set('jackson-bom.version', '2.18.10')
|
||||
|
||||
// Thymeleaf SSTI (≤3.1.3.RELEASE: 표현식 접근 객체 제한 우회 → 템플릿 인젝션). 3.0.x 는 EOL 이라
|
||||
// 백포트가 없어 3.1.4 로 올린다. JDK8/Spring5 유지: thymeleaf 3.1.4 / thymeleaf-spring5 3.1.4 /
|
||||
// extras-springsecurity5 3.1.5 / layout-dialect 3.4.0 모두 Java8 바이트코드(major 52), 패키지도
|
||||
// org.thymeleaf.spring5 + javax.servlet 그대로다.
|
||||
// Boot 2.7 ThymeleafAutoConfiguration 이 호출하는 setter 는 3.1.4 에 전부 존재함(확인함).
|
||||
// 주의: 3.1 은 #request/#session/#response/#servletContext 표현식 객체를 제거했다(IllegalArgumentException).
|
||||
set('thymeleaf.version', '3.1.4.RELEASE')
|
||||
set('thymeleaf-extras-springsecurity.version', '3.1.5.RELEASE')
|
||||
|
||||
// Spring Framework 5.3.x OSS 마지막 릴리스로 통일(Boot 2.7.18 BOM 기본 5.3.31, 일부 5.3.30 혼재였음).
|
||||
// 5.3.32~5.3.39 구간 CVE 정리용. 5.3.x 는 OSS EOL 이라 이 위로는 상용(Enterprise) 빌드뿐이다.
|
||||
// 남는 5.3.x 지적(CVE-2026-41855 JMS Jackson 역직렬화, CVE-2016-1000027 HttpInvoker)은
|
||||
// 5.3 계열에 수정본이 없고 앱이 JMS/HttpInvoker 를 쓰지 않으므로 억제 항목으로 따로 관리한다.
|
||||
set('spring-framework.version', '5.3.39')
|
||||
|
||||
// Spring Security 5.7.x OSS 마지막 릴리스(Boot 2.7.18 BOM 기본 5.7.11).
|
||||
// CVE-2026-22732(응답 커밋 후 보안 헤더 미기록)는 수정본이 5.7.22/5.8.24 = Enterprise 전용이라
|
||||
// OSS 로는 올릴 수 없다. 우회책(HeaderWriterFilter.shouldWriteHeadersEagerly=true)은 동작 변경이라 분리 검토.
|
||||
set('spring-security.version', '5.7.14')
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
// OWASP Dependency-Check 가 스캔할 서드파티 jar 를 한 디렉터리로 모은다.
|
||||
//
|
||||
// build.gradle 을 건드리지 않기 위해 init script(-I) 로만 주입한다.
|
||||
// gradle -I ci/dependency-check-classpath.gradle exportDependencyJars --no-daemon
|
||||
//
|
||||
// 출력:
|
||||
// build/dependency-check/libs/*.jar runtimeClasspath 의 외부 의존 jar (원본 파일명 유지)
|
||||
// build/dependency-check/jars.txt 수집 목록(경로 1줄씩) — 진단용
|
||||
//
|
||||
// 왜 디렉터리로 모으는가
|
||||
// Dependency-Check CLI 는 --scan <경로> 로 파일/디렉터리를 받는다. Gradle 캐시를 통째로 스캔하면
|
||||
// 이 프로젝트가 쓰지 않는 버전까지 잡히고, 프로젝트 디렉터리만 스캔하면 의존 jar 가 아예 안 잡힌다.
|
||||
// 실제 배포본에 실리는 것과 같은 목록(runtimeClasspath)만 모아야 결과가 배포 산출물과 일치한다.
|
||||
//
|
||||
// 파일명을 유지해야 하는 이유
|
||||
// Dependency-Check 는 jar 안의 POM/MANIFEST 외에 파일명에서도 CPE(제품/버전)를 추론한다.
|
||||
// 이름을 바꾸면 탐지 정확도가 떨어진다.
|
||||
|
||||
rootProject { project ->
|
||||
project.plugins.withId('java') {
|
||||
project.tasks.register('exportDependencyJars') {
|
||||
group = 'verification'
|
||||
description = 'Dependency-Check 스캔용 런타임 의존 jar 를 build/dependency-check/libs 에 모은다'
|
||||
|
||||
doLast {
|
||||
def outRoot = new File(project.layout.buildDirectory.get().asFile, 'dependency-check')
|
||||
def outDir = new File(outRoot, 'libs')
|
||||
project.delete(outDir)
|
||||
outDir.mkdirs()
|
||||
|
||||
// 컴포지트/서브프로젝트의 build 디렉터리 산출물(우리가 만든 jar)은 제외한다.
|
||||
// 자체 코드는 SonarQube 가 보는 영역이고, SCA 대상은 외부 라이브러리다.
|
||||
def buildDirs = project.allprojects.collect {
|
||||
it.layout.buildDirectory.get().asFile.absolutePath
|
||||
}
|
||||
def isOwnArtifact = { File f ->
|
||||
buildDirs.any { f.absolutePath.startsWith(it + File.separator) }
|
||||
}
|
||||
|
||||
def jars = project.configurations.runtimeClasspath.files
|
||||
.findAll { it.isFile() && it.name.endsWith('.jar') && !isOwnArtifact(it) }
|
||||
.unique()
|
||||
.sort { it.name }
|
||||
|
||||
project.copy {
|
||||
from jars
|
||||
into outDir
|
||||
}
|
||||
|
||||
new File(outRoot, 'jars.txt').text =
|
||||
jars.collect { it.absolutePath }.join(System.lineSeparator()) + System.lineSeparator()
|
||||
|
||||
def totalMb = (jars.sum { it.length() } ?: 0L) / (1024 * 1024)
|
||||
logger.lifecycle("dependency-check scan target: ${jars.size()} jars, ${totalMb as int} MB -> ${outDir}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
OWASP Dependency-Check 오탐(false positive) 억제 목록.
|
||||
|
||||
규칙
|
||||
1. 억제는 "오탐"에만 쓴다. 진짜 취약점을 조용히 숨기는 용도로 쓰지 않는다.
|
||||
실제 취약하지만 당장 못 올리는 경우는 억제 대신 만료일(until)을 넣어 재검토를 강제한다.
|
||||
2. 항목마다 <notes> 에 판단 근거와 판단자/일자를 남긴다. 근거 없는 억제는 리뷰에서 거절한다.
|
||||
3. 범위를 좁게 잡는다. cve 단건 + 특정 파일(sha1/packageUrl)이 기본이고,
|
||||
cpe 나 정규식 filePath 로 넓게 억제하지 않는다.
|
||||
|
||||
작성법
|
||||
Dependency-Check HTML 리포트의 각 취약점 옆 "Suppress" 버튼을 누르면 해당 항목의
|
||||
<suppress> 블록이 그대로 생성된다. 그것을 이 파일에 붙여넣고 <notes> 만 채우면 된다.
|
||||
|
||||
참고: https://dependency-check.github.io/DependencyCheck/general/suppression.html
|
||||
-->
|
||||
<suppressions xmlns="https://jeremylong.github.io/DependencyCheck/dependency-suppression.1.3.xsd">
|
||||
|
||||
<!-- 예시 (실제 억제 시 주석을 풀고 값 교체)
|
||||
<suppress until="2026-12-31Z">
|
||||
<notes><![CDATA[
|
||||
오탐 근거: 해당 CVE 는 X 기능을 사용할 때만 성립하는데 이 앱은 해당 API 를 호출하지 않음.
|
||||
확인: 홍길동 / 2026-08-13 / 호출부 grep 결과 0건.
|
||||
]]></notes>
|
||||
<packageUrl regex="true">^pkg:maven/org\.example/example-lib@.*$</packageUrl>
|
||||
<cve>CVE-2026-00000</cve>
|
||||
</suppress>
|
||||
-->
|
||||
|
||||
<!-- ================================================================================
|
||||
Spring 계열 Critical 5건 (2026-08-18 리포트 기준).
|
||||
|
||||
공통 배경: Spring Framework 5.3.x / Spring Boot 2.7.x / Spring Security 5.7.x 는 모두 OSS EOL 이라
|
||||
남은 수정본이 상용(Enterprise/Tanzu)뿐이다. JDK 8 유지 제약상 Boot 3 + Spring 6 이관 전까지는
|
||||
버전 상향으로 못 없앤다. 아래는 "코드에 트리거 경로가 없음"을 근거로 한 억제이며 만료일을 둔다.
|
||||
버전 범위를 5.3.x / 2.7.x / 5.7.x 로 고정해 두었으므로, 이관 후에는 억제가 자동으로 풀린다.
|
||||
재검토 시 확인할 것: (1) 각 근거 grep 이 여전히 0건인지 (2) OSS 수정본이 나왔는지.
|
||||
================================================================================ -->
|
||||
|
||||
<!-- 1. Spring Boot: Cassandra SSL 호스트명 미검증 -->
|
||||
<suppress until="2027-02-28Z">
|
||||
<notes><![CDATA[
|
||||
CVE-2026-40974 는 Spring Boot 의 Cassandra SSL 자동설정이 SSL 번들의 호스트명 검증 설정을
|
||||
드라이버에 전달하지 않는 문제다. 이 앱은 Cassandra 를 쓰지 않는다
|
||||
(runtimeClasspath 에 cassandra/datastax 계열 jar 0건, spring-boot-starter-data-cassandra 미선언).
|
||||
수정본 2.7.33 은 상용(Enterprise) 릴리스라 OSS 로는 올릴 수 없다.
|
||||
devtools/actuator/spring-boot-admin jar 는 스캔 대상(runtimeClasspath)에는 있으나
|
||||
배포 산출물에서는 build.gradle 의 localOnlyLibPrefixes 로 제외되어 WAR 에 실리지 않는다.
|
||||
확인: Rinjae / 2026-08-18 / grep -ri cassandra 0건, WAR 내 devtools/actuator jar 0건.
|
||||
]]></notes>
|
||||
<packageUrl regex="true">^pkg:maven/org\.springframework\.boot/.*@2\.7\..*$</packageUrl>
|
||||
<cve>CVE-2026-40974</cve>
|
||||
</suppress>
|
||||
|
||||
<!-- 2. Thymeleaf SSTI 2건: starter jar 이름 기준 오탐 -->
|
||||
<suppress>
|
||||
<notes><![CDATA[
|
||||
오탐. CVE-2026-40477 / CVE-2026-40478 은 thymeleaf 본체 3.1.3.RELEASE 이하의 표현식 샌드박스
|
||||
우회 문제이고 3.1.4.RELEASE 에서 수정됐다. 이 프로젝트는 build.gradle 의 ext 에서
|
||||
thymeleaf.version=3.1.4.RELEASE / thymeleaf-extras-springsecurity.version=3.1.5.RELEASE 로
|
||||
올려 두었으므로 실제 실리는 jar 는 thymeleaf-3.1.4.RELEASE.jar 다.
|
||||
Dependency-Check 가 spring-boot-starter-thymeleaf-2.7.18.jar(의존만 선언한 빈 starter)에
|
||||
thymeleaf CPE 를 잘못 매칭한 결과다. thymeleaf 본체 jar 에 대한 탐지는 억제하지 않는다.
|
||||
확인: Rinjae / 2026-08-18 / WAR 내 thymeleaf-3.1.4.RELEASE.jar, thymeleaf-spring5-3.1.4.RELEASE.jar.
|
||||
]]></notes>
|
||||
<packageUrl regex="true">^pkg:maven/org\.springframework\.boot/spring-boot-starter-thymeleaf@.*$</packageUrl>
|
||||
<cve>CVE-2026-40477</cve>
|
||||
<cve>CVE-2026-40478</cve>
|
||||
</suppress>
|
||||
|
||||
<!-- 3. Spring Framework: JMS Jackson 역직렬화 -->
|
||||
<suppress until="2027-02-28Z">
|
||||
<notes><![CDATA[
|
||||
CVE-2026-41855 는 org.springframework.jms.support.converter.MappingJackson2MessageConverter /
|
||||
JacksonJsonMessageConverter 가 임의 클래스 인스턴스화를 허용하는 문제다(신뢰할 수 없는 JMS 환경 전제).
|
||||
이 앱은 JMS 를 쓰지 않는다: runtimeClasspath 에 spring-jms 0건, 소스에 javax.jms /
|
||||
JmsTemplate / MappingJackson2MessageConverter 참조 0건.
|
||||
5.3.x 는 OSS 수정본이 없다(6.2.19 / 7.0.8 에서만 수정). 이미 5.3.x OSS 마지막인 5.3.39 로 올려 둔 상태다.
|
||||
확인: Rinjae / 2026-08-18 / grep -rn "javax.jms|JmsTemplate|MappingJackson2MessageConverter" src 0건.
|
||||
]]></notes>
|
||||
<packageUrl regex="true">^pkg:maven/org\.springframework/spring-.*@5\.3\..*$</packageUrl>
|
||||
<cve>CVE-2026-41855</cve>
|
||||
</suppress>
|
||||
|
||||
<!-- 4. spring-web: HttpInvoker 역직렬화 -->
|
||||
<suppress until="2027-02-28Z">
|
||||
<notes><![CDATA[
|
||||
CVE-2016-1000027 은 HttpInvokerServiceExporter 를 노출했을 때만 성립한다. 해당 클래스는
|
||||
Spring 6.0 에서 제거됐고 5.3.x 에는 수정본이 없다(= 5.3.x 를 쓰는 한 계속 탐지된다).
|
||||
이 앱은 HttpInvoker 계열을 쓰지 않는다: 소스에 HttpInvoker 참조 0건이고 외부 호출은
|
||||
RestTemplate / HttpClient 기반이다.
|
||||
근본 해결은 Spring 6(Boot 3, JDK 17) 이관. 이관 전까지 억제한다.
|
||||
확인: Rinjae / 2026-08-18 / grep -rn HttpInvoker src 0건.
|
||||
]]></notes>
|
||||
<packageUrl regex="true">^pkg:maven/org\.springframework/spring-web@5\.3\..*$</packageUrl>
|
||||
<cve>CVE-2016-1000027</cve>
|
||||
</suppress>
|
||||
|
||||
<!-- 5. Spring Security: 응답 커밋 시 보안 헤더 미기록 -->
|
||||
<suppress until="2027-02-28Z">
|
||||
<notes><![CDATA[
|
||||
CVE-2026-22732 는 응답이 커밋된 뒤 Spring Security 가 보안 헤더를 기록하지 못하는 문제다.
|
||||
트리거는 Content-Length 를 setHeader / setIntHeader / addIntHeader 로 지정하는 경로다
|
||||
(OnCommittedResponseWrapper 는 setContentLength / setContentLengthLong / addHeader 만 추적한다).
|
||||
(1) 이 코드베이스에는 트리거가 없다: portal / elink-portal-common / elink-online-core-jpa 전체에
|
||||
setIntHeader, addIntHeader, setHeader("Content-Length") 0건. 파일 다운로드는
|
||||
response.setContentLength(int) 를 쓰며 실측상 보안 헤더가 정상 기록된다.
|
||||
(2) 그럼에도 안전망으로 PortalConfigSecurity 에서
|
||||
HeaderWriterFilter.setShouldWriteHeadersEagerly(true) 를 적용해(spring.io 권고 우회책)
|
||||
요청 시작 시점에 헤더를 기록하도록 했다.
|
||||
수정본 5.7.22 / 5.8.24 는 Enterprise 전용이라 OSS 로는 올릴 수 없다. 이미 5.7.x OSS 마지막인
|
||||
5.7.14 로 올려 둔 상태다.
|
||||
확인: Rinjae / 2026-08-18 / 동일 스택(Boot 2.7.18 + Security 5.7.14 + Tomcat 9.0.120) 프로브 앱 실측 —
|
||||
우회책 미적용 시 setHeader/setIntHeader/addIntHeader 경로에서 헤더 누락 재현, 적용 후 정상 기록.
|
||||
]]></notes>
|
||||
<packageUrl regex="true">^pkg:maven/org\.springframework\.security/spring-security-.*@5\.7\..*$</packageUrl>
|
||||
<cve>CVE-2026-22732</cve>
|
||||
</suppress>
|
||||
|
||||
</suppressions>
|
||||
@@ -51,6 +51,14 @@ sonar.exclusions=\
|
||||
|
||||
# 자동 생성 코드(MapStruct Impl, QueryDSL Q클래스)는 build/ 아래라 이미 제외된다.
|
||||
|
||||
# Thymeleaf 인라인 표현식([[${...}]], /*[[${...}]]*/)은 JS 파서가 읽지 못해
|
||||
# "Failed to parse file ... Unexpected token" 으로 분석이 중단된다.
|
||||
# JS 분석에서만 빼고 HTML(web) 센서는 그대로 두어 템플릿 XSS 룰은 유지한다.
|
||||
# 기본값이 **/node_modules/** 이므로 재정의 시 함께 명시해야 한다.
|
||||
sonar.javascript.exclusions=\
|
||||
**/node_modules/**,\
|
||||
src/main/resources/templates/**
|
||||
|
||||
# --- SCM -------------------------------------------------------------------
|
||||
# blame 기반 "새 코드" 판정을 위해 Jenkins Job 에서 shallow clone 을 쓰지 않는다.
|
||||
sonar.scm.provider=git
|
||||
|
||||
+12
-1
@@ -22,8 +22,19 @@ import org.springframework.data.domain.Sort;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* API 그룹(전시) 조회 서비스.
|
||||
*
|
||||
* <p>주 조회 대상 {@link ApiGroup} 은 <b>AGW(게이트웨이) 데이터소스</b> 엔티티라
|
||||
* 트랜잭션 매니저를 {@code gatewayTransactionManager} 로 지정한다. 기본
|
||||
* {@code transactionManager}(EMS) 를 쓰면 게이트웨이 EntityManager 가 리포지토리 호출 단위로
|
||||
* 닫혀 {@code ApiGroup.apiGroupApiList} 지연 로딩에서 LazyInitializationException 이 난다.</p>
|
||||
*
|
||||
* <p>내부에서 호출하는 {@code ApiSpecInfoService} 는 EMS 쪽이며 자체 {@code @Transactional}
|
||||
* (기본 매니저)로 독립 트랜잭션을 연다. {@code ApiSpecInfo} 에는 지연 연관이 없어 문제되지 않는다.</p>
|
||||
*/
|
||||
@Service("apiServiceService")
|
||||
@Transactional
|
||||
@Transactional("gatewayTransactionManager")
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class ApiServiceService {
|
||||
|
||||
@@ -36,6 +36,7 @@ import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.Strings;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
@@ -155,10 +156,10 @@ public class ApiSpecService {
|
||||
updateReferences(pathItem, apiId);
|
||||
|
||||
String tmpFullPath = StringUtils.isNotEmpty(basePath)
|
||||
? StringUtils.join(basePath, "/", StringUtils.removeStart(path, "/"))
|
||||
? StringUtils.join(basePath, "/", Strings.CS.removeStart(path, "/"))
|
||||
: path;
|
||||
|
||||
final String fullPath = StringUtils.replacePattern(tmpFullPath, "//+", "/");
|
||||
final String fullPath = tmpFullPath.replaceAll("//+", "/");
|
||||
|
||||
// HTTP 메소드별로 중복 체크
|
||||
pathItem.readOperationsMap().forEach((httpMethod, operation) -> {
|
||||
@@ -294,7 +295,7 @@ public class ApiSpecService {
|
||||
return StringUtils.EMPTY;
|
||||
}
|
||||
Server server = api.getServers().get(0);
|
||||
return StringUtils.removeEnd(server.getUrl(), "/");
|
||||
return Strings.CS.removeEnd(server.getUrl(), "/");
|
||||
}
|
||||
|
||||
private void mergeComponents(OpenAPI currentAPI, String apiId, String apiName, Map<String, Object> mergedComponents, ObjectMapper objectMapper) {
|
||||
|
||||
@@ -37,7 +37,7 @@ public class AdminGatewayClient {
|
||||
String baseUrl = portalPropertyService.getOrCreateProperty(
|
||||
PROP_GROUP, PROP_ADMIN_BASE_URL, DEFAULT_ADMIN_BASE_URL, "admin(관리자포털) 내부 API base URL");
|
||||
|
||||
String url = baseUrl.replaceAll("/+$", "") + CLIENT_BLOCK_PATH;
|
||||
String url = stripTrailingSlashes(baseUrl) + CLIENT_BLOCK_PATH;
|
||||
|
||||
// 네트워크/HTTP 오류는 RestTemplate 이 예외로 던진다.
|
||||
ResponseEntity<Map> response = restTemplate.postForEntity(url, null, Map.class, clientId);
|
||||
@@ -51,4 +51,18 @@ public class AdminGatewayClient {
|
||||
|
||||
log.info("admin GW 차단/리로드 위임 성공 - clientId={}", clientId);
|
||||
}
|
||||
|
||||
/**
|
||||
* base URL 끝의 {@code '/'} 를 모두 걷어낸다.
|
||||
*
|
||||
* <p>{@code replaceAll("/+$", "")} 은 백트래킹으로 super-linear 가 될 수 있어(Sonar S5852)
|
||||
* 선형 스캔으로 대체했다. 동작은 동일하다.</p>
|
||||
*/
|
||||
private static String stripTrailingSlashes(String url) {
|
||||
int end = url.length();
|
||||
while (end > 0 && url.charAt(end - 1) == '/') {
|
||||
end--;
|
||||
}
|
||||
return url.substring(0, end);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,6 @@ package com.eactive.apim.portal.apps.auth;
|
||||
|
||||
import com.eactive.apim.portal.portalproperty.service.PortalPropertyService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.env.Profiles;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
@@ -12,10 +10,18 @@ import org.springframework.stereotype.Component;
|
||||
* <p>그룹 {@code Portal}, 키 {@code auth.test-notice.enabled}(true/false). 값이 참이면 인증 요청
|
||||
* 응답에 인증번호를 실어 화면에 노출한다(실제 발송 대신 테스트 확인 용도). 기존 application.yml
|
||||
* {@code portal.test-auth-notice-enabled} 설정을 DB PTL_PROPERTY 로 이전한 것으로,
|
||||
* {@link TwoFactorProperties} 와 동일한 {@code getOrCreateProperty} 패턴을 따른다.</p>
|
||||
* {@link com.eactive.apim.portal.apps.auth.twofactor.TwoFactorProperties} 와 동일한
|
||||
* {@code getOrCreateProperty} 패턴을 따른다.</p>
|
||||
*
|
||||
* <p><b>prod 프로파일에서는 DB 값과 무관하게 항상 false</b> 를 반환한다(운영 환경 인증번호 노출 금지).
|
||||
* 세션 keepalive 등 다른 비운영 전용 스위치와 동일한 정책이다.</p>
|
||||
* <p><b>운영(prod)에서도 DB 값만으로 켤 수 있다.</b> 대신 종료일
|
||||
* {@code auth.test-notice.prod-until}({@code yyyy-MM-dd})을 함께 지정해야 하며, 날짜가 지나면
|
||||
* {@link TestNoticeWindow} 가 자동으로 닫는다. 종료일 미설정/형식 오류면 운영에서는 열리지 않는다.
|
||||
* 운영 오픈 전 UMS 실발송 미연계 기간을 위한 한시 스위치다.</p>
|
||||
*
|
||||
* <p>이 판정은 {@link com.eactive.apim.portal.common.breadcrumb.GlobalControllerAdvice} 의
|
||||
* {@code @ModelAttribute} 에서 <b>모든 페이지 요청마다</b> 호출된다. PortalPropertyService 에 캐시가
|
||||
* 없어 호출마다 DB 조회가 발생하므로 {@value #CACHE_TTL_MILLIS}ms 짧은 캐시를 둔다.
|
||||
* PTL_PROPERTY 를 바꾸면 최대 그 시간만큼 반영이 늦다.</p>
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
@@ -23,21 +29,45 @@ public class AuthNoticeProperties {
|
||||
|
||||
public static final String GROUP = "Portal";
|
||||
public static final String KEY_TEST_NOTICE_ENABLED = "auth.test-notice.enabled";
|
||||
public static final String KEY_TEST_NOTICE_PROD_UNTIL = "auth.test-notice.prod-until";
|
||||
|
||||
/** 판정 결과 캐시 유효시간(ms) */
|
||||
static final long CACHE_TTL_MILLIS = 30_000L;
|
||||
|
||||
private final PortalPropertyService portalPropertyService;
|
||||
private final Environment environment;
|
||||
private final TestNoticeWindow testNoticeWindow;
|
||||
|
||||
private volatile boolean cachedEnabled;
|
||||
/** 캐시 갱신 시각(ms). 0 이면 미조회 */
|
||||
private volatile long cachedAt;
|
||||
|
||||
/**
|
||||
* 인증 요청 응답에 인증번호를 실어 UI 에 노출할지 여부(개발/테스트 전용).
|
||||
* prod 환경에서는 property 값과 무관하게 항상 false.
|
||||
* 운영에서는 종료일({@link #KEY_TEST_NOTICE_PROD_UNTIL})까지만 참이 된다.
|
||||
*/
|
||||
public boolean isTestNoticeEnabled() {
|
||||
if (environment.acceptsProfiles(Profiles.of("prod"))) {
|
||||
long now = System.currentTimeMillis();
|
||||
long at = cachedAt;
|
||||
if (at != 0L && now - at < CACHE_TTL_MILLIS) {
|
||||
return cachedEnabled;
|
||||
}
|
||||
boolean enabled = resolveEnabled();
|
||||
cachedEnabled = enabled;
|
||||
cachedAt = now;
|
||||
return enabled;
|
||||
}
|
||||
|
||||
private boolean resolveEnabled() {
|
||||
// 기본값 false - row 가 없는 환경(신규 운영 DB 등)에서 자동으로 켜지지 않도록 한다.
|
||||
String value = portalPropertyService.getOrCreateProperty(
|
||||
GROUP, KEY_TEST_NOTICE_ENABLED, "false",
|
||||
"인증(이메일/SMS) 요청 시 인증번호를 화면에 표시할지 여부 (true/false, 테스트 전용)");
|
||||
if (value == null || !"true".equalsIgnoreCase(value.trim())) {
|
||||
return false;
|
||||
}
|
||||
String value = portalPropertyService.getOrCreateProperty(
|
||||
GROUP, KEY_TEST_NOTICE_ENABLED, "true",
|
||||
"인증(이메일/SMS) 요청 시 인증번호를 화면에 표시할지 여부 (true/false, 테스트 전용)");
|
||||
return value != null && "true".equalsIgnoreCase(value.trim());
|
||||
String until = portalPropertyService.getOrCreateProperty(
|
||||
GROUP, KEY_TEST_NOTICE_PROD_UNTIL, TestNoticeWindow.UNSET,
|
||||
"운영(prod)에서 인증번호 화면 표시를 허용할 종료일 (yyyy-MM-dd, 미사용은 none). 경과 시 자동 차단");
|
||||
return testNoticeWindow.isOpen(until, KEY_TEST_NOTICE_ENABLED);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
package com.eactive.apim.portal.apps.auth;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.env.Profiles;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeParseException;
|
||||
|
||||
/**
|
||||
* 인증번호 화면 노출(테스트 안내) 스위치의 <b>운영 환경 사용 기한</b> 판정기.
|
||||
*
|
||||
* <p>운영 오픈 전에는 UMS/EAI 실발송 연계가 끝나지 않아 인증번호가 실제로 도달하지 않을 수 있다.
|
||||
* 이 기간에는 prod 에서도 인증번호를 화면에 노출해야 테스트가 가능하다. 다만 스위치 끄기를 잊으면
|
||||
* 인증번호가 상시 노출되므로, prod 에서는 <b>종료일을 명시한 경우에만</b> 열어 주고 그 날짜가 지나면
|
||||
* PTL_PROPERTY 값과 무관하게 자동으로 닫는다.</p>
|
||||
*
|
||||
* <ul>
|
||||
* <li>비운영(!prod) — 항상 열림. 종료일을 보지 않는다.</li>
|
||||
* <li>운영(prod) — 종료일이 없거나 형식이 잘못되면 <b>닫힘</b>(fail-safe). 종료일 당일까지 열림.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>이메일/휴대폰 인증({@link AuthNoticeProperties})과 2차 인증
|
||||
* ({@code TwoFactorProperties})이 같은 규칙을 쓰도록 판정을 이 클래스로 모은다.
|
||||
* 종료일 프로퍼티 값은 {@code yyyy-MM-dd} 형식이며, 미설정을 뜻하는 기본값은 {@value #UNSET}
|
||||
* ({@code PTL_PROPERTY.property_value} 가 NOT NULL 이라 빈 문자열을 기본값으로 쓸 수 없다).</p>
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class TestNoticeWindow {
|
||||
|
||||
/** 종료일 미설정을 뜻하는 기본값. prod 에서는 이 값이면 닫힘 */
|
||||
public static final String UNSET = "none";
|
||||
|
||||
private final Environment environment;
|
||||
|
||||
public TestNoticeWindow(Environment environment) {
|
||||
this.environment = environment;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param untilValue 종료일 문자열({@code yyyy-MM-dd}). PTL_PROPERTY 값 원본
|
||||
* @param subject 로그 식별용 스위치 이름
|
||||
* @return 지금 인증번호를 화면에 노출해도 되는 기간인지 여부
|
||||
*/
|
||||
public boolean isOpen(String untilValue, String subject) {
|
||||
if (!environment.acceptsProfiles(Profiles.of("prod"))) {
|
||||
return true;
|
||||
}
|
||||
LocalDate until = parseDate(untilValue);
|
||||
if (until == null) {
|
||||
return false;
|
||||
}
|
||||
if (LocalDate.now().isAfter(until)) {
|
||||
return false;
|
||||
}
|
||||
// 운영에서 인증번호가 화면에 노출되는 상태 - 흔적을 남긴다.
|
||||
log.warn("운영 환경 인증번호 노출 활성 상태 - {} (종료일 {})", subject, until);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static LocalDate parseDate(String value) {
|
||||
if (value == null || value.trim().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return LocalDate.parse(value.trim());
|
||||
} catch (DateTimeParseException e) {
|
||||
log.warn("인증번호 노출 종료일 형식 오류(yyyy-MM-dd 필요) - {}", value);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.eactive.apim.portal.apps.auth.twofactor;
|
||||
|
||||
import com.eactive.apim.portal.apps.auth.TestNoticeWindow;
|
||||
import com.eactive.apim.portal.portalproperty.service.PortalPropertyService;
|
||||
import com.eactive.apim.portal.portaluser.entity.PortalUserEnums.RoleCode;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@@ -25,9 +26,11 @@ public class TwoFactorProperties {
|
||||
public static final String KEY_TTL_SECONDS = "two-factor.ttl.seconds";
|
||||
public static final String KEY_ATTEMPT_LIMIT = "two-factor.attempt.limit";
|
||||
public static final String KEY_TEST_NOTICE_ENABLED = "two-factor.test-notice.enabled";
|
||||
public static final String KEY_TEST_NOTICE_PROD_UNTIL = "two-factor.test-notice.prod-until";
|
||||
public static final String KEY_STEPUP_ENABLED = "two-factor.stepup.enabled";
|
||||
|
||||
private final PortalPropertyService portalPropertyService;
|
||||
private final TestNoticeWindow testNoticeWindow;
|
||||
|
||||
/** 로그인 2FA 활성화 여부 */
|
||||
public boolean isLoginEnabled() {
|
||||
@@ -91,9 +94,21 @@ public class TwoFactorProperties {
|
||||
return parseInt(resolve(KEY_ATTEMPT_LIMIT, "5", "2차 인증번호 검증 시도 한도"), 5);
|
||||
}
|
||||
|
||||
/** 팝업에 테스트용 인증번호를 노출할지 여부(개발/테스트 전용) */
|
||||
/**
|
||||
* 팝업에 테스트용 인증번호를 노출할지 여부(개발/테스트 전용).
|
||||
*
|
||||
* <p>운영(prod)에서는 종료일 {@link #KEY_TEST_NOTICE_PROD_UNTIL}({@code yyyy-MM-dd})을 함께
|
||||
* 지정한 경우에만 참이 되고, 날짜가 지나면 {@link TestNoticeWindow} 가 자동으로 닫는다.
|
||||
* 종료일 미설정/형식 오류면 운영에서는 열리지 않는다. 이메일/휴대폰 인증
|
||||
* ({@code auth.test-notice.*})과 동일한 규칙이다.</p>
|
||||
*/
|
||||
public boolean isTestNoticeEnabled() {
|
||||
return parseBool(resolve(KEY_TEST_NOTICE_ENABLED, "false", "2차 인증 팝업에 테스트용 인증번호 표시 여부 (true/false)"));
|
||||
if (!parseBool(resolve(KEY_TEST_NOTICE_ENABLED, "false", "2차 인증 팝업에 테스트용 인증번호 표시 여부 (true/false)"))) {
|
||||
return false;
|
||||
}
|
||||
String until = resolve(KEY_TEST_NOTICE_PROD_UNTIL, TestNoticeWindow.UNSET,
|
||||
"운영(prod)에서 2차 인증번호 화면 표시를 허용할 종료일 (yyyy-MM-dd, 미사용은 none). 경과 시 자동 차단");
|
||||
return testNoticeWindow.isOpen(until, KEY_TEST_NOTICE_ENABLED);
|
||||
}
|
||||
|
||||
private String resolve(String key, String defaultValue, String description) {
|
||||
|
||||
+44
-8
@@ -2,6 +2,7 @@ package com.eactive.apim.portal.common.exception;
|
||||
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
@@ -36,23 +37,27 @@ import org.springframework.web.servlet.mvc.support.RedirectAttributes;
|
||||
@RequiredArgsConstructor
|
||||
public class PortalGlobalExceptionHandler {
|
||||
|
||||
private static final String REDIRECT_PREFIX = "redirect:";
|
||||
private static final String REDIRECT_HOME = REDIRECT_PREFIX + "/";
|
||||
private static final String REDIRECT_LOGIN_AUTH = REDIRECT_PREFIX + "/login?reason=auth";
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(getClass());
|
||||
private final PortalProperties portalProperties;
|
||||
private final Environment environment;
|
||||
|
||||
@ExceptionHandler(value = NotFoundException.class)
|
||||
public ModelAndView handleINotFoundException(HttpServletRequest request, NotFoundException ex) {
|
||||
return new ModelAndView("redirect:/");
|
||||
return new ModelAndView(REDIRECT_HOME);
|
||||
}
|
||||
|
||||
@ExceptionHandler(value = MethodArgumentTypeMismatchException.class)
|
||||
public ModelAndView handleMethodArgumentTypeMismatchException(HttpServletRequest request, MethodArgumentTypeMismatchException ex) {
|
||||
return new ModelAndView("redirect:/");
|
||||
return new ModelAndView(REDIRECT_HOME);
|
||||
}
|
||||
|
||||
@ExceptionHandler(value = UserNotLoginException.class)
|
||||
public ModelAndView handleUserNotLoginException(HttpServletRequest request, UserNotLoginException ex) {
|
||||
return new ModelAndView("redirect:/login?reason=auth");
|
||||
return new ModelAndView(REDIRECT_LOGIN_AUTH);
|
||||
}
|
||||
|
||||
@ExceptionHandler(value = AccessDeniedException.class)
|
||||
@@ -61,7 +66,7 @@ public class PortalGlobalExceptionHandler {
|
||||
if (!SecurityUtil.isAuthenticated()) {
|
||||
// 원래 요청 페이지를 세션에 저장 → 로그인+2FA 완료 후 LoginFinalizer 가 복귀시킨다.
|
||||
savePostLoginRedirect(request);
|
||||
return new ModelAndView("redirect:/login?reason=auth");
|
||||
return new ModelAndView(REDIRECT_LOGIN_AUTH);
|
||||
}
|
||||
log.warn("접근 권한 없음: loginId={}, uri={}", StringMaskingUtil.maskLoginId(SecurityUtil.getCurrentLoginId()), request.getRequestURI());
|
||||
ModelAndView modelAndView = new ModelAndView("error");
|
||||
@@ -122,9 +127,40 @@ public class PortalGlobalExceptionHandler {
|
||||
|| path.startsWith("/favicon"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 리다이렉트/뷰 이름으로 허용할 문자. 스킴 구분자(:)·중괄호·달러를 막아
|
||||
* 외부 URL 이나 Thymeleaf 표현식이 뷰 이름으로 흘러드는 것을 차단한다.
|
||||
*/
|
||||
private static final Pattern SAFE_REDIRECT_TARGET = Pattern.compile("[A-Za-z0-9._/?=&%-]+");
|
||||
|
||||
@ExceptionHandler(value = PortalRedirectException.class)
|
||||
public ModelAndView handlePortalRedirectException(HttpServletRequest request, PortalRedirectException ex) {
|
||||
return new ModelAndView(ex.getMessage());
|
||||
// PortalRedirectException 은 super(message) 를 호출하지 않으므로 getMessage() 는 항상 null 이다.
|
||||
// 이전 구현은 그 null 을 뷰 이름으로 넘겨(= 뷰 미지정) 요청 URL 기준으로 뷰가 추론되게 만들었다.
|
||||
// 실제 대상은 redirectPage 필드다.
|
||||
String target = ex.getRedirectPage();
|
||||
if (!isSafeRedirectTarget(target)) {
|
||||
log.warn("허용되지 않은 리다이렉트 대상 - uri={}, target={}", request.getRequestURI(), target);
|
||||
return new ModelAndView(REDIRECT_HOME);
|
||||
}
|
||||
return new ModelAndView(target);
|
||||
}
|
||||
|
||||
/** 내부 경로/뷰 이름만 허용(외부 URL·프로토콜 상대 URL·표현식 문자 차단) */
|
||||
private boolean isSafeRedirectTarget(String target) {
|
||||
if (target == null) {
|
||||
return false;
|
||||
}
|
||||
String path = target;
|
||||
if (path.startsWith(REDIRECT_PREFIX)) {
|
||||
path = path.substring(REDIRECT_PREFIX.length());
|
||||
} else if (path.startsWith("forward:")) {
|
||||
path = path.substring("forward:".length());
|
||||
}
|
||||
if (path.isEmpty() || path.startsWith("//")) { // //evil.example 형태의 외부 리다이렉트 차단
|
||||
return false;
|
||||
}
|
||||
return SAFE_REDIRECT_TARGET.matcher(path).matches();
|
||||
}
|
||||
|
||||
@ExceptionHandler(value = {IllegalArgumentException.class})
|
||||
@@ -140,7 +176,7 @@ public class PortalGlobalExceptionHandler {
|
||||
public ModelAndView handleHttpRequestMethodNotSupportedException(HttpServletRequest request, HttpRequestMethodNotSupportedException ex) {
|
||||
log.error(ex.getMessage());
|
||||
ModelAndView modelAndView = new ModelAndView();
|
||||
modelAndView.setViewName("redirect:/");
|
||||
modelAndView.setViewName(REDIRECT_HOME);
|
||||
return modelAndView;
|
||||
}
|
||||
|
||||
@@ -184,7 +220,7 @@ public class PortalGlobalExceptionHandler {
|
||||
public ModelAndView handleInvalidFileException(HttpServletRequest request, RedirectAttributes redirectAttributes, InvalidFileException ex) {
|
||||
ModelAndView modelAndView = new ModelAndView();
|
||||
redirectAttributes.addFlashAttribute("error", ex.getMessage());
|
||||
modelAndView.setViewName("redirect:" + request.getRequestURI());
|
||||
modelAndView.setViewName(REDIRECT_PREFIX + request.getRequestURI());
|
||||
return modelAndView;
|
||||
}
|
||||
|
||||
@@ -195,7 +231,7 @@ public class PortalGlobalExceptionHandler {
|
||||
String errorMessage = "파일 크기가 허용된 최대 용량(" + maxSize + ")을 초과했습니다.";
|
||||
redirectAttributes.addFlashAttribute("error", errorMessage);
|
||||
ModelAndView modelAndView = new ModelAndView();
|
||||
modelAndView.setViewName("redirect:" + request.getRequestURI());
|
||||
modelAndView.setViewName(REDIRECT_PREFIX + request.getRequestURI());
|
||||
return modelAndView;
|
||||
}
|
||||
}
|
||||
|
||||
-258
@@ -1,258 +0,0 @@
|
||||
package com.eactive.apim.portal.common.migration;
|
||||
|
||||
import com.eactive.apim.portal.common.util.StringMaskingUtil;
|
||||
import com.eactive.apim.portal.jpa.PersonalDataEncryptConverter;
|
||||
import com.eactive.apim.portal.portalproperty.service.PortalPropertyService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.sql.DataSource;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* [임시] 레거시 평문 데이터를 {@link PersonalDataEncryptConverter} 규칙으로 일괄 정규화(암호화)하는 운영 도구.
|
||||
*
|
||||
* <p>배경: {@code @Convert} 컬럼이 평문으로 저장된 레거시 행은, derived query가 검색값을 암호화하면서
|
||||
* 평문 DB값과 불일치해 검색/로그인이 실패한다. 컨버터는 읽기에서 평문/암호문을 자동 구분하고
|
||||
* 쓰기에서 무조건 인코딩하므로, {@code convertToDatabaseColumn(convertToEntityAttribute(x))}는
|
||||
* 평문→인코딩, 인코딩→동일값(멱등)으로 정규화된다. 이 값이 기존과 다를 때만 UPDATE 한다.</p>
|
||||
*
|
||||
* <p>보안: PTL_PROPERTY {@code Portal / migration.internal.allow-ips} 허용 IP 목록(콤마 구분,
|
||||
* 기본 loopback)에 포함된 IP 의 직접 호출만 허용한다 ({@code MenuInternalController} 모델).
|
||||
* 운영 서버는 bind IP 가 NIC IP 라 loopback 호출이 불가하므로, 실행 전 property 에 호출자 IP 를
|
||||
* 추가하고 작업 완료 후 원복한다. 프록시 경유(X-Forwarded-For 존재) 요청은 거부한다.
|
||||
* 기본은 dry-run(미변경)이며, 실제 실행은 {@code dryRun=false}를 명시해야 한다.
|
||||
* 작업 완료 후 이 클래스는 제거한다.</p>
|
||||
*
|
||||
* <pre>
|
||||
* # 미리보기(변경 안 함)
|
||||
* curl -X POST 'http://127.0.0.1:39130/internal/migration/encrypt-legacy'
|
||||
* # 실제 실행 (PII 컬럼)
|
||||
* curl -X POST 'http://127.0.0.1:39130/internal/migration/encrypt-legacy?dryRun=false'
|
||||
* # audit 컬럼(created_by/last_modified_by, 19개 테이블)까지 포함
|
||||
* curl -X POST 'http://127.0.0.1:39130/internal/migration/encrypt-legacy?dryRun=false&includeAudit=true'
|
||||
* </pre>
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/internal/migration")
|
||||
public class LegacyEncryptionMigrationController {
|
||||
|
||||
/** PII 직접 컬럼 (로그인/검색에 직접 영향). ofctelno 는 admin(UnifbwkManService)이 컨버터를 수동 호출해 암호화하는 컬럼 */
|
||||
private static final List<TargetTable> PII_TARGETS = Arrays.asList(
|
||||
new TargetTable("PTL_USER", Arrays.asList("login_id", "email_addr", "phone_number", "mobile_number")),
|
||||
new TargetTable("PTL_MESSAGE_REQUEST", Arrays.asList("email", "phone")),
|
||||
new TargetTable("tseairm02", Arrays.asList("cphnno", "emad", "ofctelno")),
|
||||
new TargetTable("PTL_USER_LOG", Arrays.asList("login_id")),
|
||||
new TargetTable("PTL_TWO_FACTOR_AUTH", Arrays.asList("recipient")),
|
||||
new TargetTable("PTL_USER_INVITATION", Arrays.asList("INVITATION_MOBILE"))
|
||||
);
|
||||
|
||||
/** Auditable(@MappedSuperclass) 상속 테이블의 감사 컬럼 (옵션) */
|
||||
private static final List<String> AUDIT_TABLES = Arrays.asList(
|
||||
"PTL_USER", "ptl_faq", "ptl_org",
|
||||
"DJB_APISTATUS_INCIDENT", "DJB_APISTATUS_INCIDENT_TIMELINE", "DJB_APISTATUS_INCIDENT_API",
|
||||
"ptl_file", "PTL_MESSAGE_TEMPLATE", "ptl_notice", "ptl_terms",
|
||||
"ptl_user_privacy_policy_agreement", "ptl_approval_line",
|
||||
"PTL_INQUIRY_COMMENT", "ptl_inquiry", "ptl_partnership_application",
|
||||
"PTL_MENU_ITEM", "PTL_MENU_PLACEMENT", "PTL_ROLE", "PTL_ROLE_AUTHORITY"
|
||||
);
|
||||
private static final List<String> AUDIT_COLUMNS = Arrays.asList("created_by", "last_modified_by");
|
||||
|
||||
static final String PROP_GROUP = "Portal";
|
||||
static final String PROP_ALLOW_IPS = "migration.internal.allow-ips";
|
||||
static final String DEFAULT_ALLOW_IPS = "127.0.0.1,::1";
|
||||
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
private final PortalPropertyService portalPropertyService;
|
||||
private final PersonalDataEncryptConverter converter = new PersonalDataEncryptConverter();
|
||||
|
||||
public LegacyEncryptionMigrationController(@Qualifier("portalDataSource") DataSource emsDataSource,
|
||||
PortalPropertyService portalPropertyService) {
|
||||
// EMS(EMSAPP) 스키마 데이터소스. 컨버터 적용 테이블은 모두 EMS에 존재한다.
|
||||
this.jdbcTemplate = new JdbcTemplate(emsDataSource);
|
||||
this.portalPropertyService = portalPropertyService;
|
||||
}
|
||||
|
||||
@PostMapping("/encrypt-legacy")
|
||||
@Transactional("transactionManager")
|
||||
public Map<String, Object> encryptLegacy(HttpServletRequest request,
|
||||
@RequestParam(defaultValue = "true") boolean dryRun,
|
||||
@RequestParam(defaultValue = "false") boolean includeAudit) {
|
||||
assertAllowedIp(request);
|
||||
assertNotBypass();
|
||||
|
||||
List<TargetTable> targets = new ArrayList<>(PII_TARGETS);
|
||||
if (includeAudit) {
|
||||
for (String table : AUDIT_TABLES) {
|
||||
targets.add(new TargetTable(table, AUDIT_COLUMNS));
|
||||
}
|
||||
}
|
||||
|
||||
List<Map<String, Object>> results = new ArrayList<>();
|
||||
int totalChanged = 0;
|
||||
int totalSkipped = 0;
|
||||
for (TargetTable target : targets) {
|
||||
for (String column : target.columns) {
|
||||
Map<String, Object> r = processColumn(target.table, column, dryRun);
|
||||
results.add(r);
|
||||
totalChanged += (int) r.get("changed");
|
||||
totalSkipped += (int) r.get("skipped");
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, Object> response = new LinkedHashMap<>();
|
||||
response.put("mode", dryRun ? "dry-run (변경 없음)" : "executed");
|
||||
response.put("damoMode", resolveDamoMode());
|
||||
response.put("includeAudit", includeAudit);
|
||||
response.put("totalChanged", totalChanged);
|
||||
// 정규화 결과가 빈 값이라 UPDATE 를 생략한 건수. 0 이 아니면 원인 조사 후 진행할 것.
|
||||
response.put("totalSkipped", totalSkipped);
|
||||
response.put("results", results);
|
||||
log.info("[레거시 암호화 마이그레이션] mode={} includeAudit={} totalChanged={} totalSkipped={}",
|
||||
dryRun ? "dry-run" : "executed", includeAudit, totalChanged, totalSkipped);
|
||||
return response;
|
||||
}
|
||||
|
||||
private String resolveDamoMode() {
|
||||
if (converter.isBypassMode()) {
|
||||
return "BYPASS";
|
||||
}
|
||||
return converter.isFakeMode() ? "FAKE" : "REAL";
|
||||
}
|
||||
|
||||
/**
|
||||
* 단일 (테이블, 컬럼)의 고유값을 정규화하고, 값이 바뀌는 경우에만 UPDATE.
|
||||
*/
|
||||
private Map<String, Object> processColumn(String table, String column, boolean dryRun) {
|
||||
Map<String, Object> r = new LinkedHashMap<>();
|
||||
r.put("table", table);
|
||||
r.put("column", column);
|
||||
|
||||
List<String> values;
|
||||
try {
|
||||
values = jdbcTemplate.queryForList(
|
||||
"SELECT DISTINCT " + column + " FROM " + table + " WHERE " + column + " IS NOT NULL",
|
||||
String.class);
|
||||
} catch (Exception e) {
|
||||
log.warn("[마이그레이션] 조회 실패 table={} column={} : {}", table, column, e.toString());
|
||||
r.put("distinct", 0);
|
||||
r.put("changed", 0);
|
||||
r.put("skipped", 0);
|
||||
r.put("error", e.getMessage());
|
||||
return r;
|
||||
}
|
||||
|
||||
int changed = 0;
|
||||
int skipped = 0;
|
||||
for (String value : values) {
|
||||
String normalized;
|
||||
try {
|
||||
// 평문 → 인코딩, 이미 인코딩 → 동일값 (멱등)
|
||||
normalized = converter.convertToDatabaseColumn(converter.convertToEntityAttribute(value));
|
||||
} catch (Exception e) {
|
||||
log.warn("[마이그레이션] 정규화 실패 table={} column={} : {}", table, column, e.toString());
|
||||
continue;
|
||||
}
|
||||
if ((normalized == null || normalized.isEmpty()) && !value.isEmpty()) {
|
||||
// 방어: 원본이 비어있지 않은데 정규화 결과가 빈 값 → 절대 UPDATE 하지 않음 (데이터 소실 방지)
|
||||
skipped++;
|
||||
log.warn("[마이그레이션] 정규화 결과가 빈 값 — UPDATE 생략 table={} column={} valueLen={}",
|
||||
table, column, value.length());
|
||||
continue;
|
||||
}
|
||||
if (normalized != null && !normalized.equals(value)) {
|
||||
if (!dryRun) {
|
||||
jdbcTemplate.update(
|
||||
"UPDATE " + table + " SET " + column + " = ? WHERE " + column + " = ?",
|
||||
normalized, value);
|
||||
}
|
||||
changed++;
|
||||
}
|
||||
}
|
||||
|
||||
r.put("distinct", values.size());
|
||||
r.put("changed", changed);
|
||||
r.put("skipped", skipped);
|
||||
return r;
|
||||
}
|
||||
|
||||
/**
|
||||
* damo-manager 가 bypass 모드면 실행 자체를 거부한다.
|
||||
* bypass 에서는 {@code encrypt} 가 무변환(원문 그대로)이라 정규화가 평문→평문 no-op 이 되어
|
||||
* 마이그레이션이 의미가 없고, "완료"로 오인될 위험이 있다. real/fake 모드로 기동 후 실행해야 한다.
|
||||
*/
|
||||
private void assertNotBypass() {
|
||||
if (converter.isBypassMode()) {
|
||||
log.warn("[마이그레이션] bypass 모드 실행 거부 — 암복호화가 무변환이라 마이그레이션이 무의미함");
|
||||
throw new ResponseStatusException(HttpStatus.CONFLICT,
|
||||
"damo-manager 가 bypass 모드입니다. 암복호화가 무변환(원문 그대로)이라 마이그레이션이 무의미하므로 거부합니다. "
|
||||
+ "real/fake 모드(-Ddamo-manager.enabled=true)로 기동한 뒤 실행하세요.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* PTL_PROPERTY({@code Portal / migration.internal.allow-ips}) 허용 IP 목록 검사.
|
||||
* 프록시 경유(X-Forwarded-For 존재) 요청은 IP 신뢰 불가로 거부한다. ({@code MenuInternalController} 모델)
|
||||
* 운영 서버는 bind IP 가 NIC IP 라 loopback 기본값으로는 호출 불가 — 실행 전 property 에
|
||||
* 호출자 IP 를 추가하고 완료 후 원복한다.
|
||||
*/
|
||||
private void assertAllowedIp(HttpServletRequest request) {
|
||||
String remote = canonicalize(request.getRemoteAddr());
|
||||
boolean viaProxy = request.getHeader("X-Forwarded-For") != null;
|
||||
|
||||
Set<String> allowed = Arrays.stream(resolveAllowIps().split(","))
|
||||
.map(String::trim)
|
||||
.filter(ip -> !ip.isEmpty())
|
||||
.map(LegacyEncryptionMigrationController::canonicalize)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
if (viaProxy || !allowed.contains(remote)) {
|
||||
log.warn("[마이그레이션] 비허용 접근 차단 remoteAddr={} viaProxy={} xff={}",
|
||||
StringMaskingUtil.maskIpAddress(remote), viaProxy,
|
||||
StringMaskingUtil.maskIpAddress(request.getHeader("X-Forwarded-For")));
|
||||
throw new ResponseStatusException(HttpStatus.FORBIDDEN,
|
||||
"허용되지 않은 접근입니다. (PTL_PROPERTY " + PROP_GROUP + "/" + PROP_ALLOW_IPS + " 확인)");
|
||||
}
|
||||
}
|
||||
|
||||
private String resolveAllowIps() {
|
||||
try {
|
||||
return portalPropertyService.getOrCreateProperty(PROP_GROUP, PROP_ALLOW_IPS,
|
||||
DEFAULT_ALLOW_IPS, "레거시 암호화 마이그레이션 내부 API 허용 IP 목록(콤마 구분)");
|
||||
} catch (Exception e) {
|
||||
log.warn("[마이그레이션] 허용 IP 목록 조회 실패 - 기본값({}) 사용", DEFAULT_ALLOW_IPS, e);
|
||||
return DEFAULT_ALLOW_IPS;
|
||||
}
|
||||
}
|
||||
|
||||
/** IPv6 loopback 표기 통일 */
|
||||
private static String canonicalize(String ip) {
|
||||
return "0:0:0:0:0:0:0:1".equals(ip) ? "::1" : ip;
|
||||
}
|
||||
|
||||
private static final class TargetTable {
|
||||
final String table;
|
||||
final List<String> columns;
|
||||
|
||||
TargetTable(String table, List<String> columns) {
|
||||
this.table = table;
|
||||
this.columns = columns;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.eactive.apim.portal.common.validator;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.Strings;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.validation.ConstraintValidator;
|
||||
@@ -39,14 +40,8 @@ public class CellPhoneValidator implements ConstraintValidator<CellPhone, String
|
||||
}
|
||||
|
||||
// 기존의 prefix 체크 로직 유지
|
||||
return StringUtils.startsWith(cellPhone, "+821") ||
|
||||
StringUtils.startsWith(cellPhone, "821") ||
|
||||
StringUtils.startsWith(cellPhone, "010") ||
|
||||
StringUtils.startsWith(cellPhone, "011") ||
|
||||
StringUtils.startsWith(cellPhone, "016") ||
|
||||
StringUtils.startsWith(cellPhone, "017") ||
|
||||
StringUtils.startsWith(cellPhone, "018") ||
|
||||
StringUtils.startsWith(cellPhone, "019");
|
||||
return Strings.CS.startsWithAny(cellPhone,
|
||||
"+821", "821", "010", "011", "016", "017", "018", "019");
|
||||
}
|
||||
|
||||
private boolean isValidPartLength(String prefix, String middle, String last) {
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.eactive.apim.portal.config;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import javax.naming.NamingException;
|
||||
import javax.sql.DataSource;
|
||||
@@ -58,11 +59,11 @@ public class BaseDatasourceConfiguration {
|
||||
//public interface AvailableSettings extends org.hibernate.jpa.AvailableSettings 참고
|
||||
properties.put("hibernate.dialect", prop.getHibernateDialect());
|
||||
properties.put("hibernate.connection.handling_mode", "DELAYED_ACQUISITION_AND_RELEASE_AFTER_TRANSACTION");
|
||||
properties.put("hibernate.transaction.jta.platform", "org.hibernate.engine.transaction.jta.platform.internal.AtomikosJtaPlatform");
|
||||
properties.put("hibernate.physical_naming_strategy", prop.getHibernatePhysicalNamingStrategy());
|
||||
properties.put("hibernate.default_schema", prop.getSchema());
|
||||
properties.put("javax.persistence.validation.mode", "none");
|
||||
properties.put("javax.persistence.transactionType", "JTA");
|
||||
// JTA/XA 미사용. EMF 별 로컬 트랜잭션(JpaTransactionManager)으로 처리한다 - PortalConfigTransaction 참고
|
||||
properties.put("javax.persistence.transactionType", "RESOURCE_LOCAL");
|
||||
properties.put("hibernate.format_sql", "true");
|
||||
|
||||
if (prop instanceof EmsDatasourceProperty) {
|
||||
@@ -110,9 +111,22 @@ public class BaseDatasourceConfiguration {
|
||||
return builder
|
||||
.dataSource(dataSource)
|
||||
// entity-package 는 콤마로 복수 지정 가능 (gateway 는 online-core + 포털 통계 엔티티 패키지)
|
||||
.packages(prop.getEntityPackage().split("\\s*,\\s*"))
|
||||
.packages(splitPackages(prop.getEntityPackage()))
|
||||
.persistenceUnit(persistenceUnit)
|
||||
.properties(properties)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 콤마로 나열된 엔티티 패키지를 분리한다. 앞뒤 공백은 걷어내고 빈 토큰은 버린다.
|
||||
*
|
||||
* <p>구분자 정규식 {@code "\\s*,\\s*"} 은 백트래킹으로 super-linear 가 될 수 있어(Sonar S5852)
|
||||
* 단순 분리 + trim 으로 대체했다.</p>
|
||||
*/
|
||||
private static String[] splitPackages(String entityPackage) {
|
||||
return Arrays.stream(entityPackage.split(","))
|
||||
.map(String::trim)
|
||||
.filter(pkg -> !pkg.isEmpty())
|
||||
.toArray(String[]::new);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,8 @@ import javax.sql.DataSource;
|
||||
|
||||
@Configuration
|
||||
@EntityScan(basePackages = {"com.eactive.apim.gateway"})
|
||||
@EnableJpaRepositories(basePackages = "com.eactive.apim.gateway", repositoryBaseClass = BaseRepositoryImpl.class, entityManagerFactoryRef = "gatewayEntityManagerFactory")
|
||||
@EnableJpaRepositories(basePackages = "com.eactive.apim.gateway", repositoryBaseClass = BaseRepositoryImpl.class,
|
||||
entityManagerFactoryRef = "gatewayEntityManagerFactory", transactionManagerRef = "gatewayTransactionManager")
|
||||
@Slf4j
|
||||
public class GatewayDatasourceConfiguration extends BaseDatasourceConfiguration {
|
||||
private final Environment env;
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.eactive.apim.portal.config;
|
||||
|
||||
import com.eactive.apim.portal.common.internal.InternalApiTokenService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.boot.context.event.ApplicationReadyEvent;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 내부 API 공유 토큰 초기화 — 포탈 기동 시 1회.
|
||||
*
|
||||
* <p>PTL_PROPERTY {@code Portal / internal.api.header-name}, {@code internal.api.token} 이 없으면
|
||||
* 이 시점에 생성된다(토큰은 SecureRandom 난수). eapim-admin 은 이 값을 <b>읽기만</b> 하므로,
|
||||
* 토큰을 처음 만드는 주체는 포탈 한 곳으로 고정된다.</p>
|
||||
*
|
||||
* <p>{@link ApplicationReadyEvent} 를 쓰는 이유: 데이터소스/JTA 초기화가 끝난 뒤여야 프로퍼티를 저장할 수 있다.
|
||||
* WAR 배포(WebLogic)에서도 동일하게 발생한다.</p>
|
||||
*
|
||||
* @see com.eactive.apim.portal.djb.menu.MenuInternalController
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class InternalApiTokenInitializer {
|
||||
|
||||
private final InternalApiTokenService internalApiTokenService;
|
||||
|
||||
@EventListener(ApplicationReadyEvent.class)
|
||||
public void initialize() {
|
||||
internalApiTokenService.initialize();
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,8 @@ package com.eactive.apim.portal.config;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.fileupload.FileItemFactory;
|
||||
import org.apache.commons.fileupload.FileUpload;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.multipart.commons.CommonsMultipartResolver;
|
||||
@@ -18,6 +20,18 @@ import org.springframework.web.multipart.commons.CommonsMultipartResolver;
|
||||
@RequiredArgsConstructor
|
||||
public class MultipartConfig {
|
||||
|
||||
/**
|
||||
* 멀티파트 파트 1개의 헤더 구간 최대 바이트.
|
||||
* <p>
|
||||
* commons-fileupload 1.6.0(CVE-2025-48976 수정)부터 이 상한이 생겼고 기본값이 512 바이트다.
|
||||
* 한글 파일명은 UTF-8 로 3바이트/자라 Content-Disposition 이 금방 커진다 — 실측상 기본값 512 로는
|
||||
* 한글 약 137자 이상 파일명에서 업로드가 실패하고, 예외도 "Maximum upload size exceeded" 로 감싸져
|
||||
* 원인 파악이 어렵다. OS 파일명 상한(255자)이 전부 한글이어도 통과하도록 2048 로 둔다.
|
||||
* (1.5 이전에는 사실상 10240 이었으므로 이 값도 그보다 5배 엄격하다.)
|
||||
* </p>
|
||||
*/
|
||||
private static final int PART_HEADER_SIZE_MAX = 2048;
|
||||
|
||||
private final PortalProperties portalProperties;
|
||||
|
||||
/**
|
||||
@@ -31,7 +45,14 @@ public class MultipartConfig {
|
||||
*/
|
||||
@Bean(name = "filterMultipartResolver")
|
||||
public CommonsMultipartResolver filterMultipartResolver() {
|
||||
CommonsMultipartResolver resolver = new CommonsMultipartResolver();
|
||||
CommonsMultipartResolver resolver = new CommonsMultipartResolver() {
|
||||
@Override
|
||||
protected FileUpload newFileUpload(FileItemFactory fileItemFactory) {
|
||||
FileUpload fileUpload = super.newFileUpload(fileItemFactory);
|
||||
fileUpload.setPartHeaderSizeMax(PART_HEADER_SIZE_MAX);
|
||||
return fileUpload;
|
||||
}
|
||||
};
|
||||
|
||||
long maxSizeBytes = portalProperties.getFile().getMaxSizeBytes();
|
||||
resolver.setMaxUploadSize(maxSizeBytes);
|
||||
@@ -39,8 +60,8 @@ public class MultipartConfig {
|
||||
resolver.setMaxInMemorySize((int) Math.min(maxSizeBytes, Integer.MAX_VALUE));
|
||||
resolver.setDefaultEncoding("UTF-8");
|
||||
|
||||
log.info("CommonsMultipartResolver configured with maxUploadSize: {} bytes ({})",
|
||||
maxSizeBytes, portalProperties.getFile().getMaxSize());
|
||||
log.info("CommonsMultipartResolver configured with maxUploadSize: {} bytes ({}), partHeaderSizeMax: {} bytes",
|
||||
maxSizeBytes, portalProperties.getFile().getMaxSize(), PART_HEADER_SIZE_MAX);
|
||||
|
||||
return resolver;
|
||||
}
|
||||
|
||||
@@ -33,15 +33,13 @@ import org.springframework.web.filter.ForwardedHeaderFilter;
|
||||
* → getHeader("X-Forwarded-For") = null (필터가 제거 → viaProxy 판정이 false)
|
||||
* </pre>
|
||||
*
|
||||
* 즉 {@code MenuInternalController#isAllowed} / {@code LegacyEncryptionMigrationController#assertAllowedIp}
|
||||
* 의 "프록시 경유 거부 + loopback 허용" 가드가 무력화된다. 필터를 제외해 두면 이 경로만은 <b>원 소켓 IP</b> 로
|
||||
* 검사되므로 기존 가드가 설계대로 동작한다.
|
||||
* 즉 {@code MenuInternalController#isAllowedIp} 의 "프록시 경유 거부 + loopback 허용" 가드가 무력화된다.
|
||||
* 필터를 제외해 두면 이 경로만은 <b>원 소켓 IP</b> 로 검사되므로 기존 가드가 설계대로 동작한다.
|
||||
*
|
||||
* <p><b>전제</b>: {@code framework} 는 신뢰 프록시 목록이 없어 헤더를 무조건 신뢰한다. 앞단(OHS)에서 인바운드
|
||||
* {@code X-Forwarded-*} / {@code Forwarded} 를 제거한 뒤 재설정해야 하며, WAS 포트로의 직접 접근 경로도 차단해야 한다.
|
||||
*
|
||||
* @see com.eactive.apim.portal.djb.menu.MenuInternalController
|
||||
* @see com.eactive.apim.portal.common.migration.LegacyEncryptionMigrationController
|
||||
*/
|
||||
@Slf4j
|
||||
@Configuration
|
||||
|
||||
@@ -7,6 +7,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.ObjectPostProcessor;
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
@@ -16,6 +17,7 @@ import org.springframework.security.web.access.AccessDeniedHandler;
|
||||
import org.springframework.security.web.access.AccessDeniedHandlerImpl;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
import org.springframework.security.web.csrf.CsrfException;
|
||||
import org.springframework.security.web.header.HeaderWriterFilter;
|
||||
import org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository;
|
||||
import org.springframework.security.web.session.HttpSessionEventPublisher;
|
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
@@ -92,6 +94,23 @@ public class PortalConfigSecurity {
|
||||
|
||||
http
|
||||
.authenticationManager(portalAuthenticationManager)
|
||||
// CVE-2026-22732 우회책. Spring Security 는 기본적으로 보안 헤더를 "응답 커밋 시점"에
|
||||
// 지연 기록하는데, 응답 래퍼가 Content-Length 를 setHeader/setIntHeader/addIntHeader 로
|
||||
// 지정하는 경로를 추적하지 못해 그 경우 헤더가 통째로 누락된다
|
||||
// (X-Content-Type-Options, X-Frame-Options, Cache-Control, Pragma, Expires, X-XSS-Protection).
|
||||
// 수정본은 5.7.22/5.8.24(Enterprise 전용)뿐이라 OSS 로는 올릴 수 없어 우회책을 적용한다.
|
||||
// 요청 시작 시점에 헤더를 기록하게 만든다. 앱이 나중에 같은 헤더를 지정하면 앱 값이 남는다
|
||||
// (실측: FileDownloadController#viewImage 의 Cache-Control: public, max-age=86400 유지됨).
|
||||
// 현재 앱은 response.setContentLength(int) 만 쓰므로 노출 경로는 없지만,
|
||||
// 새 코드가 위 메서드를 쓰더라도 헤더가 빠지지 않도록 두는 안전망이다.
|
||||
// Spring Security 를 수정본(6.5.9+/7.0.4+ 또는 Enterprise 5.7.22+)으로 올리면 제거 가능.
|
||||
.headers(headers -> headers.addObjectPostProcessor(new ObjectPostProcessor<HeaderWriterFilter>() {
|
||||
@Override
|
||||
public <O extends HeaderWriterFilter> O postProcess(O filter) {
|
||||
filter.setShouldWriteHeadersEagerly(true);
|
||||
return filter;
|
||||
}
|
||||
}))
|
||||
.formLogin(form -> form
|
||||
.loginPage("/login")
|
||||
.usernameParameter("id")
|
||||
@@ -107,8 +126,12 @@ public class PortalConfigSecurity {
|
||||
.logoutSuccessHandler(logoutSuccessHandler))
|
||||
.csrf(csrf -> csrf
|
||||
.csrfTokenRepository(csrfTokenRepository)
|
||||
.ignoringRequestMatchers(new AntPathRequestMatcher("/_proxy/**/*"))
|
||||
.ignoringRequestMatchers(new AntPathRequestMatcher("/internal/migration/**"))
|
||||
// /_proxy 는 대응 핸들러가 없어 예외를 해제했다. 경로가 부활하면 아래를 되살릴 것.
|
||||
// .ignoringRequestMatchers(new AntPathRequestMatcher("/_proxy/**/*"))
|
||||
// /internal/migration 은 LegacyEncryptionMigrationController 제거와 함께 삭제됨.
|
||||
// /internal/menu 는 브라우저 세션이 없는 서버간 호출(admin → portal)이라 CSRF 토큰을 실을 수 없다.
|
||||
// 대신 InternalApiTokenService 의 공유 토큰 헤더 + 허용 IP 목록으로 통제한다.
|
||||
// (커스텀 헤더는 cross-site form POST 로 위조할 수 없어 CSRF 경로가 차단된다)
|
||||
.ignoringRequestMatchers(new AntPathRequestMatcher("/internal/menu/**"))
|
||||
)
|
||||
// 로그인 페이지에 오래 머물러 세션(=CSRF 토큰 저장소)이 타임아웃되면
|
||||
|
||||
@@ -1,39 +1,44 @@
|
||||
package com.eactive.apim.portal.config;
|
||||
|
||||
import com.atomikos.icatch.jta.UserTransactionImp;
|
||||
import com.atomikos.icatch.jta.UserTransactionManager;
|
||||
import javax.persistence.EntityManagerFactory;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.DependsOn;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.orm.jpa.JpaTransactionManager;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
import org.springframework.transaction.jta.JtaTransactionManager;
|
||||
|
||||
import javax.transaction.SystemException;
|
||||
import javax.transaction.UserTransaction;
|
||||
|
||||
/**
|
||||
* 트랜잭션 매니저 설정.
|
||||
*
|
||||
* <p>EMS(포털)·AGW(게이트웨이) 두 데이터소스를 쓰지만 <b>JTA/XA 는 사용하지 않는다</b>.
|
||||
* 두 DB 를 한 트랜잭션으로 묶어 쓰는 지점이 없고(게이트웨이 측은 조회 전용),
|
||||
* 이전 Atomikos 구성도 {@code AtomikosDataSourceBean} 없이 JNDI/Hikari 데이터소스를 그대로 넘겨
|
||||
* XA 리소스가 하나도 enlist 되지 않는 형태였다. 즉 2PC 는 실제로 동작한 적이 없고
|
||||
* {@code com.atomikos.icatch.max_actives} 상한(기본 50)만 병목으로 남았다.</p>
|
||||
*
|
||||
* <p>따라서 EntityManagerFactory 별로 로컬 {@link JpaTransactionManager} 를 둔다.
|
||||
* 게이트웨이 리포지토리는 {@code GatewayDatasourceConfiguration} 의
|
||||
* {@code transactionManagerRef = "gatewayTransactionManager"} 로 연결된다.</p>
|
||||
*/
|
||||
@Configuration
|
||||
@EnableTransactionManagement
|
||||
public class PortalConfigTransaction {
|
||||
|
||||
@Bean(name = "userTransaction")
|
||||
public UserTransaction userTransaction() throws SystemException {
|
||||
UserTransactionImp userTransactionImp = new UserTransactionImp();
|
||||
userTransactionImp.setTransactionTimeout(30000);
|
||||
return userTransactionImp;
|
||||
}
|
||||
|
||||
@Bean(name = "atomikosTransactionManager", initMethod = "init", destroyMethod = "close")
|
||||
public UserTransactionManager atomikosTransactionManager() {
|
||||
UserTransactionManager userTransactionManager = new UserTransactionManager();
|
||||
userTransactionManager.setForceShutdown(false);
|
||||
return userTransactionManager;
|
||||
}
|
||||
|
||||
/** EMS(포털) 기본 트랜잭션 매니저. {@code @Transactional} 무지정 시 이 매니저가 쓰인다. */
|
||||
@Primary
|
||||
@Bean(name = "transactionManager")
|
||||
@DependsOn({"userTransaction", "atomikosTransactionManager"})
|
||||
public PlatformTransactionManager transactionManager(UserTransactionManager atomikosTransactionManager) throws SystemException {
|
||||
UserTransaction userTransaction = userTransaction();
|
||||
return new JtaTransactionManager(userTransaction, atomikosTransactionManager);
|
||||
public PlatformTransactionManager transactionManager(
|
||||
@Qualifier("entityManagerFactory") EntityManagerFactory entityManagerFactory) {
|
||||
return new JpaTransactionManager(entityManagerFactory);
|
||||
}
|
||||
|
||||
/** AGW(게이트웨이) 트랜잭션 매니저. {@code @Transactional("gatewayTransactionManager")} 로 지정해 쓴다. */
|
||||
@Bean(name = "gatewayTransactionManager")
|
||||
public PlatformTransactionManager gatewayTransactionManager(
|
||||
@Qualifier("gatewayEntityManagerFactory") EntityManagerFactory gatewayEntityManagerFactory) {
|
||||
return new JpaTransactionManager(gatewayEntityManagerFactory);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.eactive.apim.portal.djb.menu;
|
||||
|
||||
import com.eactive.apim.portal.common.internal.InternalApiTokenService;
|
||||
import com.eactive.apim.portal.common.util.IpAddressMatcher;
|
||||
import com.eactive.apim.portal.portalproperty.service.PortalPropertyService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@@ -19,14 +20,20 @@ import java.util.Map;
|
||||
/**
|
||||
* 메뉴 캐시 내부 API — eapim-admin 의 reload 명령 수신용.
|
||||
*
|
||||
* <p>가드: PTL_PROPERTY {@code Portal / menu.internal.allow-ips} 허용 IP 목록
|
||||
* (기본 loopback) + X-Forwarded-For 동반 요청 거부
|
||||
* ({@code LegacyEncryptionMigrationController.assertLocalOnly} 모델).
|
||||
* 허용 목록은 {@link IpAddressMatcher} 규칙을 따라 정확 일치 외에
|
||||
* IPv4 CIDR({@code 172.30.1.0/24}) 과 옥텟 와일드카드({@code 172.30.*.*}) 를 지원한다.
|
||||
* CSRF 는 PortalConfigSecurity 에서 {@code /internal/menu/**} 예외 처리.</p>
|
||||
* <p>가드는 두 겹이다.</p>
|
||||
* <ol>
|
||||
* <li><b>공유 토큰 헤더</b> — {@link InternalApiTokenService}. 헤더명/토큰은 PTL_PROPERTY
|
||||
* {@code Portal / internal.api.header-name}, {@code internal.api.token} 으로 관리하며
|
||||
* 토큰은 포탈 최초 기동 시 자동 생성된다. 이 경로는 CSRF 예외 대상
|
||||
* (PortalConfigSecurity {@code /internal/menu/**})이라, 커스텀 헤더 요구가 CSRF 를 대신한다 —
|
||||
* 브라우저의 cross-site form POST 는 커스텀 헤더를 붙일 수 없다.</li>
|
||||
* <li><b>허용 IP 목록</b> — PTL_PROPERTY {@code Portal / menu.internal.allow-ips}(기본 loopback)
|
||||
* + X-Forwarded-For 동반 요청 거부. 허용 목록은 {@link IpAddressMatcher} 규칙을 따라 정확 일치 외에
|
||||
* IPv4 CIDR({@code 172.30.1.0/24}) 과 옥텟 와일드카드({@code 172.30.*.*}) 를 지원한다.</li>
|
||||
* </ol>
|
||||
*
|
||||
* <pre>curl -X POST http://127.0.0.1:39130/internal/menu/reload</pre>
|
||||
* <pre>curl -X POST -H 'X-Internal-Token: <PTL_PROPERTY Portal/internal.api.token>' \
|
||||
* http://127.0.0.1:39130/internal/menu/reload</pre>
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@@ -43,14 +50,18 @@ public class MenuInternalController {
|
||||
|
||||
private final MenuService menuService;
|
||||
private final PortalPropertyService portalPropertyService;
|
||||
private final InternalApiTokenService internalApiTokenService;
|
||||
|
||||
@PostMapping("/reload")
|
||||
public ResponseEntity<Map<String, Object>> reload(HttpServletRequest request) {
|
||||
if (!isAllowed(request)) {
|
||||
Map<String, Object> denied = new LinkedHashMap<>();
|
||||
denied.put("result", "DENIED");
|
||||
denied.put("message", "허용되지 않은 접근입니다. (menu.internal.allow-ips 확인)");
|
||||
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(denied);
|
||||
if (!isAllowedIp(request)) {
|
||||
return denied(HttpStatus.FORBIDDEN,
|
||||
"허용되지 않은 접근입니다. (" + PROP_GROUP + "/" + PROP_ALLOW_IPS + " 확인)");
|
||||
}
|
||||
if (!hasValidToken(request)) {
|
||||
return denied(HttpStatus.UNAUTHORIZED,
|
||||
"내부 API 토큰이 유효하지 않습니다. (" + InternalApiTokenService.PROP_GROUP + "/"
|
||||
+ InternalApiTokenService.PROP_TOKEN + " 확인)");
|
||||
}
|
||||
|
||||
MenuService.MenuSnapshot snapshot = menuService.reload();
|
||||
@@ -63,12 +74,31 @@ public class MenuInternalController {
|
||||
return ResponseEntity.ok(body);
|
||||
}
|
||||
|
||||
private ResponseEntity<Map<String, Object>> denied(HttpStatus status, String message) {
|
||||
Map<String, Object> denied = new LinkedHashMap<>();
|
||||
denied.put("result", "DENIED");
|
||||
denied.put("message", message);
|
||||
return ResponseEntity.status(status).body(denied);
|
||||
}
|
||||
|
||||
/**
|
||||
* 공유 토큰 헤더 검사. 헤더명은 PTL_PROPERTY 로 바뀔 수 있으므로 매 요청 조회한다(호출 빈도가 낮다).
|
||||
*/
|
||||
private boolean hasValidToken(HttpServletRequest request) {
|
||||
String headerName = internalApiTokenService.ensureHeaderName();
|
||||
if (!internalApiTokenService.matches(request.getHeader(headerName))) {
|
||||
log.warn("메뉴 내부 API 차단 - 토큰 불일치, remote: {}, header: {}", request.getRemoteAddr(), headerName);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 허용 IP 검사. 프록시 경유(X-Forwarded-For 존재) 요청은 IP 신뢰 불가로 거부한다.
|
||||
* (embedded Tomcat 은 forward-headers-strategy: native 로 XFF 가 remoteAddr 에 반영될 수 있으나
|
||||
* 그 경우에도 allowlist 검사로 차단된다. WebLogic WAR 배포에서는 원 소켓 IP 로 검사된다.)
|
||||
*/
|
||||
private boolean isAllowed(HttpServletRequest request) {
|
||||
private boolean isAllowedIp(HttpServletRequest request) {
|
||||
String remote = IpAddressMatcher.canonicalize(request.getRemoteAddr());
|
||||
boolean viaProxy = request.getHeader("X-Forwarded-For") != null;
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
spring:
|
||||
jta:
|
||||
enabled: false
|
||||
config:
|
||||
activate:
|
||||
on-profile: dev
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
spring:
|
||||
jta:
|
||||
enabled: false
|
||||
config:
|
||||
activate:
|
||||
on-profile: prod
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
spring:
|
||||
jta:
|
||||
enabled: false
|
||||
web:
|
||||
resources:
|
||||
cache:
|
||||
|
||||
@@ -58,13 +58,7 @@ spring:
|
||||
# 설정으로는 적용되지 않는다. 실제 버전닝은 PortalConfigWebDispatcherServlet 가
|
||||
# 아래 app.resource-versioning.enabled 토글을 읽어 직접 수행한다.
|
||||
|
||||
jta:
|
||||
enabled: false
|
||||
atomikos:
|
||||
properties:
|
||||
# log-base-dir: /logs/eapim/${inst.Name:devSvr11}/atomikos
|
||||
log-base-dir: /dev/null
|
||||
log-base-name: adp_tx
|
||||
# JTA/Atomikos 제거됨. EMS·AGW 각각 로컬 트랜잭션(JpaTransactionManager) 사용 - PortalConfigTransaction 참고
|
||||
|
||||
thymeleaf:
|
||||
prefix: 'classpath:/templates/views/'
|
||||
@@ -125,7 +119,7 @@ portal:
|
||||
allowed-extensions: pdf,doc,docx,xls,xlsx,ppt,pptx,hwp,gif,jpg,jpeg,png
|
||||
|
||||
logging:
|
||||
log-path: /logs/eapim
|
||||
log-path: /logs/prod/eapim
|
||||
|
||||
# 내부 사용자(운영자) 판별 설정
|
||||
# 로그인 계정 이메일의 호스트가 아래 도메인이면 내부 사용자로 분류한다.
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
# application.yml ? ??
|
||||
# com.atomikos.icatch.log_base_dir=/Log/eapim/portal/
|
||||
# com.atomikos.icatch.log_base_name=adp_tx
|
||||
@@ -87,9 +87,6 @@
|
||||
<logger name="org.springframework.orm.jpa" level="DEBUG" additivity="false">
|
||||
<appender-ref ref="FILE_HIBERNATE" />
|
||||
</logger>
|
||||
<logger name="com.atomikos" level="DEBUG" additivity="false">
|
||||
<appender-ref ref="FILE_HIBERNATE" />
|
||||
</logger>
|
||||
|
||||
<logger name="eapim.portal.session" level="INFO" additivity="false">
|
||||
<appender-ref ref="HTTP_SESSION" />
|
||||
|
||||
@@ -1,491 +0,0 @@
|
||||
class ChartEditor {
|
||||
constructor(container, layoutManager, dashboard) {
|
||||
this.rootContainer = container;
|
||||
this.layoutManager = layoutManager;
|
||||
this.dashboard = dashboard;
|
||||
this.targetContainerIndex = -1;
|
||||
this.targetColumnIndex = -1;
|
||||
this.tempChart = null;
|
||||
this.chartPreview = null;
|
||||
this.containerHeight = 0;
|
||||
|
||||
this.chartEditorModel = [
|
||||
{
|
||||
'groupName': 'default',
|
||||
'groupTitle': '기본 정보',
|
||||
'fields': [
|
||||
{
|
||||
'path': 'contentId',
|
||||
'type': 'text',
|
||||
'label': 'Chart ID',
|
||||
'placeholder': 'chartId를 입력하세요'
|
||||
},
|
||||
{
|
||||
'path': 'styles.width',
|
||||
'type': 'text',
|
||||
'label': '길이(px, %)',
|
||||
'placeholder': 'px, % 등 단위를 포함한 길이를 입력하세요'
|
||||
},
|
||||
{
|
||||
'path': 'styles.height',
|
||||
'type': 'text',
|
||||
'label': '높이(px, %)',
|
||||
'placeholder': 'px, % 등 단위를 포함한 높이를 입력하세요'
|
||||
},
|
||||
{
|
||||
'path': 'config.animation',
|
||||
'type': 'boolean',
|
||||
'label': 'Animation 사용'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'groupName': 'title',
|
||||
'groupTitle': '제목',
|
||||
'fields': [
|
||||
{
|
||||
'path': 'config.title.text',
|
||||
'type': 'text',
|
||||
'label': '차트 제목',
|
||||
'placeholder': '차트 제목을 입력하세요'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'groupName': 'tooltip',
|
||||
'groupTitle': 'Tooltip',
|
||||
'fields': [
|
||||
{
|
||||
'path': 'config.tooltip.show',
|
||||
'type': 'boolean',
|
||||
'label': 'Tooltip 표시'
|
||||
},
|
||||
{
|
||||
'path': 'config.tooltip.trigger',
|
||||
'type': 'select',
|
||||
'label': 'Tooltip Trigger',
|
||||
'placeholder': 'Enter the trigger type',
|
||||
'options': ['item', 'axis', 'none']
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'groupName': 'legend',
|
||||
'groupTitle': 'Legend',
|
||||
'fields': [
|
||||
{
|
||||
'path': 'config.legend.show',
|
||||
'type': 'boolean',
|
||||
'label': 'Legend 표시'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'groupName': 'grid',
|
||||
'groupTitle': 'Grid',
|
||||
'fields': [
|
||||
{
|
||||
'path': 'config.grid.show',
|
||||
'type': 'boolean',
|
||||
'label': 'Grid 표시'
|
||||
},
|
||||
{
|
||||
'path': 'config.grid.left',
|
||||
'type': 'text',
|
||||
'label': 'Grid Left',
|
||||
'placeholder': '좌측 여백'
|
||||
},
|
||||
{
|
||||
'path': 'config.grid.right',
|
||||
'type': 'text',
|
||||
'label': 'Grid Right',
|
||||
'placeholder': '우측 여백'
|
||||
},
|
||||
{
|
||||
'path': 'config.grid.top',
|
||||
'type': 'text',
|
||||
'label': 'Grid Top',
|
||||
'placeholder': '위쪽 여백'
|
||||
},
|
||||
{
|
||||
'path': 'config.grid.bottom',
|
||||
'type': 'text',
|
||||
'label': 'Grid Bottom',
|
||||
'placeholder': '아래쪽 여백'
|
||||
},
|
||||
{
|
||||
'path': 'config.grid.containLabel',
|
||||
'type': 'boolean',
|
||||
'label': 'Contain Label'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'groupName': 'xAxis',
|
||||
'groupTitle': 'X축',
|
||||
'fields': [
|
||||
{
|
||||
'path': 'config.xAxis.show',
|
||||
'type': 'boolean',
|
||||
'label': 'X축 표시'
|
||||
},
|
||||
{
|
||||
'path': 'config.xAxis.type',
|
||||
'type': 'select',
|
||||
'label': 'X축 유형',
|
||||
'placeholder': 'X축 유형을 선택하세요',
|
||||
'options': ['category', 'value', 'time']
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'groupName': 'yAxis',
|
||||
'groupTitle': 'Y축',
|
||||
'fields': [
|
||||
{
|
||||
'path': 'config.yAxis[0].show',
|
||||
'type': 'boolean',
|
||||
'label': 'Y축 표시'
|
||||
},
|
||||
{
|
||||
'path': 'config.yAxis[0].name',
|
||||
'type': 'text',
|
||||
'label': 'Y축 이름'
|
||||
},
|
||||
{
|
||||
'path': 'config.yAxis[0].type',
|
||||
'type': 'select',
|
||||
'label': 'Y축 유형',
|
||||
'placeholder': 'Y축 유형을 선택하세요',
|
||||
'options': ['category', 'value', 'time']
|
||||
},
|
||||
{
|
||||
'path': 'config.yAxis[0].position',
|
||||
'type': 'select',
|
||||
'label': 'Y축 위치',
|
||||
'placeholder': 'Y축 위치를 선택하세요',
|
||||
'options': ['left', 'right']
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'groupName': 'toolbox',
|
||||
'groupTitle': 'Toolbox',
|
||||
'fields': [
|
||||
{
|
||||
'path': 'config.toolbox.show',
|
||||
'type': 'boolean',
|
||||
'label': 'Toolbox 표시',
|
||||
'placeholder': 'Check to show toolbox'
|
||||
},
|
||||
{
|
||||
'path': 'config.toolbox.feature.saveAsImage.show',
|
||||
'type': 'boolean',
|
||||
'label': 'Save as Image 사용'
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
this.seriesConfigModel = [
|
||||
{
|
||||
'groupName': 'default',
|
||||
'groupTitle': '시리즈 정보',
|
||||
'fields': [
|
||||
{
|
||||
'path': 'seriesConfig[0].queryType',
|
||||
'type': 'select',
|
||||
'label': 'Query Type',
|
||||
'options': ['elasticsearch']
|
||||
},
|
||||
// {
|
||||
// 'path': 'seriesConfig[0].type',
|
||||
// 'type': 'select',
|
||||
// 'label': '차트 유형',
|
||||
// 'placeholder': '차트 유형을 선택하세요',
|
||||
// 'options': ['bar', 'line', 'pie']
|
||||
// },
|
||||
{
|
||||
'path': 'seriesConfig[0].index',
|
||||
'type': 'text',
|
||||
'label': 'Elasticsearch Index',
|
||||
'placeholder': 'Elasticsearch Index를 입력하세요'
|
||||
},
|
||||
{
|
||||
'path': 'seriesConfig[0].query',
|
||||
'type': 'textarea',
|
||||
'label': 'Elasticsearch Query'
|
||||
},
|
||||
{
|
||||
'path': 'seriesConfig[0].queryResult',
|
||||
'type': 'textarea',
|
||||
'label': 'Elasticsearch Query Result'
|
||||
},
|
||||
{
|
||||
'path': 'seriesConfig[0].dataPath',
|
||||
'type': 'text',
|
||||
'label': '데이터 경로',
|
||||
'placeholder': '데이터 경로를 입력하세요'
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
initChartEditor() {
|
||||
|
||||
const chartEditorPopup = `
|
||||
<div class="modal fade" id="chart_editor_modal" tabindex="-1" aria-labelledby="chart_editor_modal" aria-hidden="true">
|
||||
<div class="modal-dialog modal-fullscreen modal-dialog-scrollable">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">차트 정보</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="editor_container">
|
||||
<input type="hidden" name="type" value="" />
|
||||
<div class="left-panel default-info">
|
||||
${LayoutUtil.createForm(this.chartEditorModel)}
|
||||
</div>
|
||||
<div class="editor-right-panel">
|
||||
<div class="series-config d-flex flex-column">
|
||||
<h6>Data Source</h6>
|
||||
<div class="d-flex flex-row">
|
||||
<div class="form-floating me-2" style="flex: 1">
|
||||
<select class="form-select" name="seriesConfig[0].queryType">
|
||||
<option value="elasticsearch">Elasticsearch</option>
|
||||
</select>
|
||||
<label for="queryType">Query Type</label>
|
||||
</div>
|
||||
<div class="form-floating me-2" style="flex: 1">
|
||||
<input class="form-control" type="text" name="seriesConfig[0].index" value="" />
|
||||
<label for="index">Elasticsearch Index</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex flex-row mt-2" style="flex: 1">
|
||||
<div style="flex: 1">
|
||||
<div class="form-floating me-2" style="height: 100%">
|
||||
<textarea class="form-control" type="text" name="seriesConfig[0].query" id="seriesConfigQuery" placeholder="Code goes here..." style="height: 400px;">
|
||||
</textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div style="flex: 1">
|
||||
<div class="form-floating me-2" style="height: 100%">
|
||||
<textarea class="form-control" type="text" name="seriesConfig[0].queryResult" style="height: 400px;" readonly>
|
||||
</textarea>
|
||||
<label for="query">요청 결과</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex flex-row mt-2" style="flex: 1">
|
||||
<div style="flex: 1">
|
||||
<div class="form-floating me-2" style="height: 100%">
|
||||
<textarea class="form-control" type="text" name="seriesConfig[0].mapperY" style="height: 400px;">
|
||||
</textarea>
|
||||
<label for="mapper">데이터 Mapper</label>
|
||||
</div>
|
||||
</div>
|
||||
<div style="flex: 1">
|
||||
<div class="form-floating me-2" style="height: 100%">
|
||||
<textarea class="form-control" type="text" name="seriesConfig[0].mapperX" style="height: 400px;">
|
||||
</textarea>
|
||||
<label for="mapper">X축 Mapper</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex flex-row" style="height: 400px">
|
||||
<div style="flex: 1">
|
||||
<div class="form-floating me-2" style="height: 100%">
|
||||
<textarea class="form-control" type="text" id="echartOption" style="width: 100%; height: 400px;" readonly>
|
||||
</textarea>
|
||||
<label for="mapper">생성된 Option</label>
|
||||
</div>
|
||||
</div>
|
||||
<div style="flex: 1">
|
||||
<div class="chart-preview form-control" id="chart-preview" style="height: 400px;">
|
||||
chart_preview
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-primary btn-sm" data-bs-dismiss="modal">닫기</button>
|
||||
<button type="button" class="btn btn-primary btn-sm add-chart">추가</button>
|
||||
<button type="button" class="btn btn-primary btn-sm save-chart">저장</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
$(this.rootContainer).parent().append(chartEditorPopup);
|
||||
|
||||
//add event to modal show
|
||||
$('#chart_editor_modal').on('shown.bs.modal', (event) => {
|
||||
let chartContainer = document.getElementById('chart-preview');
|
||||
let rect = chartContainer.getBoundingClientRect();
|
||||
let width = rect.width; // Precise width
|
||||
let height = rect.height; // Precise height
|
||||
this.chartPreview = echarts.init(document.getElementById('chart-preview'), 'dark', {
|
||||
width: width, height: height
|
||||
});
|
||||
this.drawCharts();
|
||||
});
|
||||
}
|
||||
|
||||
showChartEditor(isNew, tempChart, containerIndex, columnIndex) {
|
||||
this.targetContainerIndex = containerIndex;
|
||||
this.targetColumnIndex = columnIndex;
|
||||
this.tempChart = tempChart;
|
||||
if (isNew) {
|
||||
$('.add-chart').show();
|
||||
$('.save-chart').hide();
|
||||
} else {
|
||||
$('.add-chart').hide();
|
||||
$('.save-chart').show();
|
||||
}
|
||||
|
||||
let flatModel = LayoutUtil.flattenObject(tempChart);
|
||||
|
||||
$('#chart_editor_modal').find('input, select, textarea').each((index, element) => {
|
||||
if ($(element).attr('type') === 'checkbox') {
|
||||
$(element).prop('checked', false);
|
||||
}
|
||||
$(element).val('');
|
||||
});
|
||||
|
||||
_.forEach(flatModel, (value, key) => {
|
||||
|
||||
const inputSelector = `input[name="${key}"]`;
|
||||
const selectSelector = `select[name="${key}"]`;
|
||||
const textareaSelector = `textarea[name="${key}"]`;
|
||||
|
||||
const $input = $('#chart_editor_modal').find(inputSelector);
|
||||
const $select = $('#chart_editor_modal').find(selectSelector);
|
||||
const $textarea = $('#chart_editor_modal').find(textareaSelector);
|
||||
|
||||
if ($input.length > 0 && $input.attr('type') === 'checkbox') {
|
||||
// If it's a checkbox, update its 'checked' property
|
||||
$input.prop('checked', value);
|
||||
} else if ($input.length > 0 || $select.length > 0 || $textarea.length > 0) {
|
||||
// If any input, select, or textarea is found, update its value
|
||||
$($input.add($select).add($textarea)).val(value);
|
||||
} else {
|
||||
// If no elements are found, log to the console
|
||||
console.log(`Element not found for name: ${key}`);
|
||||
}
|
||||
});
|
||||
|
||||
$('#chart_editor_modal').modal('show');
|
||||
console.log('chart_editor_modal show');
|
||||
|
||||
this.bindEvents();
|
||||
}
|
||||
|
||||
addChart() {
|
||||
$('#chart_editor_modal').find('input, select, textarea').each((index, element) => {
|
||||
const fieldName = $(element).attr('name');
|
||||
const newValue = LayoutUtil.getFieldValue(element);
|
||||
if (fieldName !== '') {
|
||||
_.set(this.tempChart, fieldName, newValue);
|
||||
}
|
||||
});
|
||||
this.layoutManager.layoutData.children[this.targetContainerIndex].children.push(JSON.parse(JSON.stringify(this.tempChart)));
|
||||
this.tempChart = null;
|
||||
this.targetContainerIndex = -1;
|
||||
this.targetColumnIndex = -1;
|
||||
|
||||
this.layoutManager.resetLayout();
|
||||
$('#chart_editor_modal').modal('hide');
|
||||
}
|
||||
|
||||
cleanUpEvents() {
|
||||
console.log('cleanUpEvents');
|
||||
$('.add-chart').off('click');
|
||||
$('.save-chart').off('click');
|
||||
}
|
||||
|
||||
saveChart() {
|
||||
console.log('saveChart');
|
||||
|
||||
$('#chart_editor_modal').find('input, select, textarea').each((index, element) => {
|
||||
const fieldName = $(element).attr('name');
|
||||
const newValue = LayoutUtil.getFieldValue(element);
|
||||
if (fieldName !== undefined && fieldName !== '') {
|
||||
_.set(this.tempChart, fieldName, newValue);
|
||||
}
|
||||
});
|
||||
const json = JSON.stringify(this.tempChart, LayoutUtil.echartReplacer, 2);
|
||||
console.log(json);
|
||||
this.layoutManager.layoutData.children[this.targetContainerIndex].children[this.targetColumnIndex] = JSON.parse(json);
|
||||
this.targetContainerIndex = -1;
|
||||
this.targetColumnIndex = -1;
|
||||
this.layoutManager.resetLayout();
|
||||
$('#chart_editor_modal').modal('hide');
|
||||
}
|
||||
|
||||
refreshChart(chartInfo) {
|
||||
console.log('refreshChart');
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
let option = JSON.parse(JSON.stringify(chartInfo.config));
|
||||
option.series = [];
|
||||
|
||||
let self = this;
|
||||
Promise.all(this.tempChart.seriesConfig.map(config => this.dashboard.requestQuery(config))).then(async (results) => {
|
||||
await self.dashboard.updateChartSeries(option, results, chartInfo, (index, result) => {
|
||||
$('#chart_editor_modal').find(`textarea[name='seriesConfig[${index}].queryResult']`).val('result = ' + JSON.stringify(result, null, 2));
|
||||
});
|
||||
|
||||
document.getElementById('echartOption').value = JSON.stringify(option, null, 2);
|
||||
self.chartPreview.setOption(option);
|
||||
resolve();
|
||||
}).catch((error) => {
|
||||
console.error('Error fetching data for preview: ', error);
|
||||
reject(error);
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
drawCharts() {
|
||||
console.log('drawCharts');
|
||||
|
||||
const promises = [this.refreshChart(this.tempChart)];
|
||||
|
||||
Promise.all(promises).then(() => console.log('Chart Preview is completed')).catch(error => console.error('An error occurred:', error));
|
||||
}
|
||||
|
||||
bindEvents() {
|
||||
$('.add-chart').off('click').on('click', (event) => {
|
||||
this.addChart();
|
||||
});
|
||||
|
||||
$('.save-chart').off('click').on('click', (event) => {
|
||||
this.saveChart();
|
||||
});
|
||||
|
||||
$('#chart_editor_modal').find('input, select, textarea').each((index, element) => {
|
||||
$(element).off('change').on('change', (event) => {
|
||||
console.log('change event');
|
||||
const fieldName = $(event.currentTarget).attr('name');
|
||||
const newValue = LayoutUtil.getFieldValue(event.currentTarget);
|
||||
if (fieldName !== '') {
|
||||
_.set(this.tempChart, fieldName, newValue);
|
||||
}
|
||||
this.refreshChart(this.tempChart);
|
||||
});
|
||||
});
|
||||
|
||||
$('#chart_editor_modal').on('hidden.bs.modal', (event) => {
|
||||
if (this.chartPreview != null && this.chartPreview.dispose) {
|
||||
this.chartPreview.dispose();
|
||||
this.chartPreview = null;
|
||||
}
|
||||
|
||||
this.cleanUpEvents();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,344 +0,0 @@
|
||||
class Dashboard {
|
||||
|
||||
constructor(layoutData) {
|
||||
this.layoutData = layoutData || {};
|
||||
this.fromDateTarget = 'fromDate';
|
||||
this.toDateTarget = 'toDate';
|
||||
this.charts = [];
|
||||
this.theme = 'dark';
|
||||
this.requestUrl = '/mgmt/dashboard/search.do';
|
||||
|
||||
}
|
||||
|
||||
bindEvents() {
|
||||
const events = [{selector: '.refresh', action: this.refresh.bind(this)}];
|
||||
|
||||
for (let event of events) {
|
||||
$(document).off('click', event.selector).on('click', event.selector, event.action);
|
||||
}
|
||||
}
|
||||
|
||||
getDateTemplate(id, label) {
|
||||
return `
|
||||
<div class="input-group mb-3">
|
||||
<div class="form-floating">
|
||||
<input id="${id}Input" type="text" class="form-control" data-td-target="#${id}"/>
|
||||
<label for="${id}Input">${label}</label>
|
||||
</div>
|
||||
<span class="input-group-text" data-td-target="#${id}" data-td-toggle="datetimepicker">
|
||||
<span class="fa-solid fa-calendar"></span>
|
||||
</span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
init(chartAreaId) {
|
||||
console.log('Dashboard init');
|
||||
this.chartAreaId = chartAreaId;
|
||||
this.initDate();
|
||||
this.initCharts();
|
||||
this.drawCharts();
|
||||
|
||||
window.addEventListener('resize', this.resizeAllCharts.bind(this));
|
||||
}
|
||||
|
||||
initCharts() {
|
||||
console.log('initCharts');
|
||||
this.charts = [];
|
||||
if (this.layoutData.children) {
|
||||
this.layoutData.children.forEach((row) => {
|
||||
row.children.forEach((chartInfo) => {
|
||||
this.charts.push(chartInfo);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
formatDate(isoString) {
|
||||
const date = new Date(isoString);
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0'); // +1 because getMonth() returns 0-11
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
|
||||
return `${year}-${month}-${day}`;
|
||||
}
|
||||
|
||||
formatTime(isoString) {
|
||||
const date = new Date(isoString);
|
||||
const hour = String(date.getHours()).padStart(2, '0');
|
||||
const minute = String(date.getMinutes()).padStart(2, '0');
|
||||
return `${hour}:${minute}:00`;
|
||||
}
|
||||
|
||||
formatDateTime(isoString) {
|
||||
return this.formatDate(isoString) + 'T' + this.formatTime(isoString);
|
||||
}
|
||||
|
||||
startOfDay(isoString) {
|
||||
return this.formatDate(isoString) + 'T00:00:00';
|
||||
}
|
||||
|
||||
initDate() {
|
||||
const ko = tempusDominus.locales.ko.localization;
|
||||
const fromDateElement = document.getElementById(this.fromDateTarget);
|
||||
$(fromDateElement).data('td-target-input', 'nearest');
|
||||
$(fromDateElement).data('td-target-toggle', 'nearest');
|
||||
|
||||
const toDateElement = document.getElementById(this.toDateTarget);
|
||||
$(toDateElement).data('td-target-input', 'nearest');
|
||||
$(toDateElement).data('td-target-toggle', 'nearest');
|
||||
|
||||
$(fromDateElement).append(this.getDateTemplate(this.fromDateTarget, 'From'));
|
||||
$(toDateElement).append(this.getDateTemplate(this.toDateTarget, 'To'));
|
||||
|
||||
this.fromDate = new tempusDominus.TempusDominus(fromDateElement, {
|
||||
localization: ko, defaultDate: this.startOfDay(new Date())
|
||||
});
|
||||
this.toDate = new tempusDominus.TempusDominus(toDateElement, {
|
||||
useCurrent: false, localization: ko, defaultDate: new Date()
|
||||
});
|
||||
|
||||
fromDateElement.addEventListener(tempusDominus.Namespace.events.change, (e) => {
|
||||
this.toDate.updateOptions({
|
||||
restrictions: {
|
||||
minDate: e.detail.date
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const subscription = this.toDate.subscribe(tempusDominus.Namespace.events.change, (e) => {
|
||||
this.fromDate.updateOptions({
|
||||
restrictions: {
|
||||
maxDate: e.date
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
requestPost(url, data, hds) {
|
||||
var deferred = $.Deferred();
|
||||
|
||||
var headers = $.extend({
|
||||
'Content-Type': 'application/json; charset=utf-8'
|
||||
}, hds);
|
||||
|
||||
if (headers['Content-Type'].startsWith('application/json')) {
|
||||
data = JSON.stringify(data);
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: url, async: true, method: 'POST', data: data, headers: headers
|
||||
}).done((response) => {
|
||||
deferred.resolve(response);
|
||||
}).fail((response, textStatus, errorThrown) => {
|
||||
deferred.reject(response, textStatus, errorThrown);
|
||||
});
|
||||
return deferred.promise();
|
||||
};
|
||||
|
||||
requestQuery(seriesConfig) {
|
||||
let request = null;
|
||||
try {
|
||||
if (seriesConfig.queryType === 'elasticsearch') {
|
||||
if (seriesConfig.query && seriesConfig.index !== '') {
|
||||
let requestQuery = JSON.parse(seriesConfig.query);
|
||||
requestQuery.query = requestQuery.query || {};
|
||||
requestQuery.query.bool = requestQuery.query.bool || {};
|
||||
requestQuery.query.bool.filter = requestQuery.query.bool.filter || [];
|
||||
requestQuery.query.bool.filter.push({
|
||||
'range': {
|
||||
'@timestamp': {
|
||||
'gte': this.formatDateTime(this.fromDate.dates.picked[0]),
|
||||
'lte': this.formatDateTime(this.toDate.dates.picked[0]),
|
||||
'time_zone': 'Asia/Seoul'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
request = {
|
||||
query: JSON.stringify(requestQuery), index: seriesConfig.index
|
||||
};
|
||||
return this.requestPost(this.requestUrl, request, {});
|
||||
} else {
|
||||
console.log('query is empty');
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching data for ' + seriesConfig.contentId + ': ', error);
|
||||
return Promise.reject(error);
|
||||
}
|
||||
}
|
||||
|
||||
resizeAllCharts() {
|
||||
this.charts.forEach(chartInfo => {
|
||||
let chartContainer = document.getElementById('chart_' + chartInfo.contentId);
|
||||
let rect = chartContainer.getBoundingClientRect();
|
||||
let width = rect.width; // Precise width
|
||||
let height = rect.height; // Precise height
|
||||
if (chartInfo.echart) {
|
||||
chartInfo.echart.resize({width: width, height: height});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
initializeChart(chartInfo) {
|
||||
console.log('initializeChart: ' + chartInfo.contentId);
|
||||
let chartContainer = document.getElementById('chart_' + chartInfo.contentId);
|
||||
let rect = chartContainer.getBoundingClientRect();
|
||||
let width = rect.width; // Precise width
|
||||
let height = rect.height; // Precise height
|
||||
|
||||
chartInfo.echart = echarts.init(document.getElementById('chart_' + chartInfo.contentId), this.theme, {
|
||||
width: width, height: height
|
||||
});
|
||||
}
|
||||
|
||||
executeScript(result, script) {
|
||||
console.log('executeScript');
|
||||
if (script) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let resultFrame = document.getElementById('mapperScript');
|
||||
window.preRequestComplete = (result, error) => {
|
||||
if (error) {
|
||||
console.log('Error executing script: ', error);
|
||||
reject(error);
|
||||
} else {
|
||||
resolve(result);
|
||||
}
|
||||
};
|
||||
window.result = result;
|
||||
resultFrame.srcdoc = `<script>
|
||||
function get(obj, path, defaultValue = undefined) {
|
||||
const keys = Array.isArray(path) ? path : path.replace(/\\[(\\d+)\\]/g, '.$1').split('.');
|
||||
let result = obj;
|
||||
for (let key of keys) {
|
||||
result = result?.[key];
|
||||
if (result === undefined) {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function formatDate(isoString) {
|
||||
const date = new Date(isoString);
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0'); // +1 because getMonth() returns 0-11
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
return \`\${year}-\${month}-\${day}\`;
|
||||
}
|
||||
|
||||
function formatTime(isoString) {
|
||||
const date = new Date(isoString);
|
||||
const hour = String(date.getHours()).padStart(2, '0');
|
||||
const minute = String(date.getMinutes()).padStart(2, '0');
|
||||
return \`\${hour}:\${minute}:00\`;
|
||||
}
|
||||
|
||||
function formatDateTime(isoString) {
|
||||
return formatDate(isoString) + 'T' + formatTime(isoString);
|
||||
}
|
||||
|
||||
function startOfDay(isoString) {
|
||||
return formatDate(isoString) + 'T00:00:00';
|
||||
}
|
||||
|
||||
window.result = window.parent.result;
|
||||
try {
|
||||
const executeFunction = ${script};
|
||||
const data = executeFunction(window.result);
|
||||
window.parent.preRequestComplete(data, null);
|
||||
} catch (error) {
|
||||
window.parent.preRequestComplete(null, error);
|
||||
}
|
||||
</script>`;
|
||||
});
|
||||
} else {
|
||||
console.log('No script to execute');
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async refresh(event) {
|
||||
console.log('refresh');
|
||||
|
||||
for (let i = 0; i < this.charts.length; i++) {
|
||||
const chartInfo = this.charts[i];
|
||||
try {
|
||||
await this.refreshChart(chartInfo);
|
||||
} catch (error) {
|
||||
console.error(`Error refreshing chart ${chartInfo.contentId}:`, error);
|
||||
}
|
||||
}
|
||||
console.log('All Charts requests are complete');
|
||||
// const promises = this.charts.map(info => this.refreshChart(info));
|
||||
// Promise.all(promises).then(() => console.log('All Charts requests are complete')).catch(error => console.error('An error occurred:', error));
|
||||
}
|
||||
|
||||
drawCharts() {
|
||||
console.log('drawCharts');
|
||||
this.charts.forEach(info => this.initializeChart(info));
|
||||
|
||||
this.refresh();
|
||||
this.bindEvents();
|
||||
}
|
||||
|
||||
async updateChartSeries(option, results, chartInfo, callback) {
|
||||
console.log('updateChartSeries');
|
||||
for (let i = 0; i < results.length; i++) {
|
||||
const result = results[i];
|
||||
if (result){
|
||||
let config = chartInfo.seriesConfig[i];
|
||||
let parsedResult = JSON.parse(result);
|
||||
|
||||
if (callback) {
|
||||
callback(i, parsedResult);
|
||||
}
|
||||
try {
|
||||
console.log('start executeScript');
|
||||
const dataY = await this.executeScript(parsedResult, config.mapperY);
|
||||
|
||||
if (Array.isArray(dataY)) {
|
||||
dataY.forEach(item => {
|
||||
option.series.push(item);
|
||||
});
|
||||
} else {
|
||||
option.series.push(dataY);
|
||||
}
|
||||
const dataX = await this.executeScript(parsedResult, config.mapperX);
|
||||
option.xAxis.data = dataX;
|
||||
|
||||
console.log('stop executeScript');
|
||||
} catch (error) {
|
||||
console.error(`Error updating series for chart ${chartInfo.contentId}:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async refreshChart(chartInfo) {
|
||||
|
||||
try {
|
||||
console.log('refreshChart');
|
||||
|
||||
let option = JSON.parse(JSON.stringify(chartInfo.config));
|
||||
option.series = [];
|
||||
|
||||
const results = await Promise.all(chartInfo.seriesConfig.map(config => this.requestQuery(config)));
|
||||
await this.updateChartSeries(option, results, chartInfo);
|
||||
|
||||
// Clear the chart and set the new option
|
||||
chartInfo.echart.clear();
|
||||
chartInfo.echart.setOption(option);
|
||||
|
||||
console.log('Chart refreshed successfully');
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error fetching data for ' + chartInfo.contentId + ': ', error);
|
||||
throw error; // Rethrow the error if you need to catch it higher up in the call stack
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,643 +0,0 @@
|
||||
class LayoutManager {
|
||||
constructor(container, layoutData) {
|
||||
this.rootContainer = container;
|
||||
this.tempContainer = null;
|
||||
this.editContainerIndex = -1;
|
||||
this.layoutData = layoutData || {
|
||||
type: 'root',
|
||||
styles: {
|
||||
'display': 'flex',
|
||||
'flex-direction': 'column',
|
||||
'justify-content': 'start',
|
||||
'align-items': 'flex-start',
|
||||
'gap': '10px',
|
||||
'width': '100%',
|
||||
'height': '100vh',
|
||||
'position': 'relative',
|
||||
'overflow': 'auto',
|
||||
'background-color': '#100C2A'
|
||||
},
|
||||
children: []
|
||||
};
|
||||
|
||||
this.containerEditorModel = [
|
||||
{
|
||||
'groupName': 'default',
|
||||
'groupTitle': '기본',
|
||||
'fields': [
|
||||
{
|
||||
'path': 'styles.display',
|
||||
'type': 'select',
|
||||
'label': 'Display',
|
||||
'options': ['flex']
|
||||
},
|
||||
{
|
||||
'path': 'styles.flex-direction',
|
||||
'type': 'select',
|
||||
'label': 'Flex 방향',
|
||||
'options': ['row', 'column', 'row-reverse', 'column-reverse']
|
||||
},
|
||||
{
|
||||
'path': 'styles.width',
|
||||
'type': 'text',
|
||||
'label': '길이(px, %)',
|
||||
'placeholder': 'px, % 등 단위를 포함한 길이를 입력하세요'
|
||||
},
|
||||
{
|
||||
'path': 'styles.height',
|
||||
'type': 'text',
|
||||
'label': '높이(px, %)',
|
||||
'placeholder': 'px, % 등 단위를 포함한 높이를 입력하세요'
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
this.dashboardEditorModel = [
|
||||
{
|
||||
'groupName': 'default',
|
||||
'groupTitle': '기본',
|
||||
'fields': [
|
||||
{
|
||||
'path': 'styles.background-color',
|
||||
'type': 'text',
|
||||
'label': '색상',
|
||||
'placeholder': '#로 시작하는 값을 입력하세요'
|
||||
},
|
||||
{
|
||||
'path': 'styles.width',
|
||||
'type': 'text',
|
||||
'label': '길이(px)',
|
||||
'placeholder': 'px 단위를 포함한 길이를 입력하세요'
|
||||
},
|
||||
{
|
||||
'path': 'styles.height',
|
||||
'type': 'text',
|
||||
'label': '높이(px)',
|
||||
'placeholder': 'px 단위를 포함한 높이를 입력하세요'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
}
|
||||
|
||||
// traverse(obj) {
|
||||
// if (obj.type === 'chart') {
|
||||
// $(`#chart_${obj.contentId}`).closest('.dashboard-chart').resizable({
|
||||
// grid: 10,
|
||||
// ghost: true,
|
||||
// stop: (event, ui) => {
|
||||
// const {width, height} = ui.size;
|
||||
// obj.styles.width = `${width}px`;
|
||||
// obj.styles.height = `${height}px`;
|
||||
// this.resetLayout();
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
//
|
||||
// if (obj.children) {
|
||||
// obj.children.forEach(child => {
|
||||
// this.traverse(child);
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
|
||||
init(Dashboard, viewerMode) {
|
||||
this.viewMode = viewerMode || false;
|
||||
this.renderRoot(this.layoutData, this.rootContainer);
|
||||
|
||||
if (Dashboard) {
|
||||
this.dashboard = new Dashboard(this.layoutData);
|
||||
this.dashboard.init('chart_area');
|
||||
}
|
||||
|
||||
if (!this.viewMode) {
|
||||
this.initContainerEditor();
|
||||
this.initDashboardEditor();
|
||||
this.chartEditor = new ChartEditor(this.rootContainer, this, this.dashboard);
|
||||
this.chartEditor.initChartEditor();
|
||||
this.bindEvents();
|
||||
}
|
||||
}
|
||||
|
||||
initContainerEditor() {
|
||||
const containerEditorPopup = `
|
||||
<div class="modal fade" id="container_editor_modal" tabindex="-1" aria-labelledby="container_editor_modal" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">컨테이너 정보</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div id="containerOption">
|
||||
${LayoutUtil.createForm(this.containerEditorModel)}
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-primary btn-sm" data-bs-dismiss="modal">닫기</button>
|
||||
<button type="button" class="btn btn-primary btn-sm add-container">추가</button>
|
||||
<button type="button" class="btn btn-primary btn-sm save-container">저장</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
$(this.rootContainer).parent().append(containerEditorPopup);
|
||||
}
|
||||
|
||||
initDashboardEditor() {
|
||||
const containerEditorPopup = `
|
||||
<div class="modal fade" id="dashboard_editor_modal" tabindex="-1" aria-labelledby="dashboard_editor_modal" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">대시보드 정보</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div id="containerOption">
|
||||
${LayoutUtil.createForm(this.dashboardEditorModel)}
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-primary btn-sm" data-bs-dismiss="modal">닫기</button>
|
||||
<button type="button" class="btn btn-primary btn-sm save-dashboard">적용</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
$(this.rootContainer).parent().append(containerEditorPopup);
|
||||
}
|
||||
|
||||
resetLayout() {
|
||||
console.log('resetLayout');
|
||||
$(this.rootContainer).empty();
|
||||
this.renderRoot(this.layoutData, this.rootContainer);
|
||||
this.dashboard.initCharts();
|
||||
this.dashboard.layoutData = this.layoutData;
|
||||
this.dashboard.drawCharts();
|
||||
this.editContainerIndex = -1;
|
||||
this.bindEvents();
|
||||
}
|
||||
|
||||
addNewChart(containerIndex) {
|
||||
this.editContainerIndex = containerIndex;
|
||||
let tempChart = {
|
||||
'contentId': '',
|
||||
'type': 'chart',
|
||||
'styles': {
|
||||
width: '100%', height: '100%'
|
||||
},
|
||||
'config': {
|
||||
'tooltip': {
|
||||
'trigger': 'axis',
|
||||
'show': true
|
||||
},
|
||||
'legend': {
|
||||
'show': true
|
||||
},
|
||||
'grid': {
|
||||
'show': true,
|
||||
'top': 0,
|
||||
'bottom': 0,
|
||||
'left': 0,
|
||||
'right': 0,
|
||||
'containLabel': true
|
||||
},
|
||||
'title': {},
|
||||
'xAxis': {
|
||||
'show': true,
|
||||
'type': 'category'
|
||||
},
|
||||
'yAxis': [
|
||||
{
|
||||
'show': true,
|
||||
'name': '',
|
||||
'type': 'value',
|
||||
'position': 'left'
|
||||
}],
|
||||
'toolbox': {
|
||||
'show': true
|
||||
}
|
||||
},
|
||||
'seriesConfig': [
|
||||
{
|
||||
'type': 'line',
|
||||
'index': '',
|
||||
'queryType': 'elasticsearch',
|
||||
'query': '',
|
||||
'mapperX': `
|
||||
function(result) {
|
||||
let data = [];
|
||||
return data;
|
||||
}
|
||||
`,
|
||||
'mapperY': `
|
||||
function(result) {
|
||||
let data = [{
|
||||
type : 'line',
|
||||
data: []
|
||||
}];
|
||||
return data;
|
||||
}
|
||||
`,
|
||||
'queryResult': ''
|
||||
}
|
||||
]
|
||||
};
|
||||
this.chartEditor.showChartEditor(true, tempChart, containerIndex);
|
||||
}
|
||||
|
||||
editChart(editContainerIndex, editColumnIndex) {
|
||||
console.log('edit-chart');
|
||||
let tempChart = this.layoutData.children[editContainerIndex].children[editColumnIndex];
|
||||
this.chartEditor.showChartEditor(false, JSON.parse(JSON.stringify(tempChart, LayoutUtil.echartReplacer)), editContainerIndex, editColumnIndex);
|
||||
}
|
||||
|
||||
renderRoot(layoutData, parent) {
|
||||
let classList = 'dashboard-root mt-2' + (!this.viewMode ? ' edit' : '');
|
||||
let children = '';
|
||||
let style = '';
|
||||
if (layoutData.styles) {
|
||||
style = Object.keys(layoutData.styles).map(key => `${key}: ${layoutData.styles[key]};`).join(' ');
|
||||
}
|
||||
console.log({style});
|
||||
if (layoutData.children) {
|
||||
layoutData.children.forEach((child) => {
|
||||
if (child.type === 'container') {
|
||||
children += this.renderContainer(child);
|
||||
} else if (child.type === 'chart') {
|
||||
children += this.renderChart(child);
|
||||
}
|
||||
});
|
||||
}
|
||||
let layoutItem = `<div class="${classList}" style="${style}"> ${children}</div>`;
|
||||
$(parent).append(layoutItem);
|
||||
// if (!this.viewMode) {
|
||||
// this.traverse(this.layoutData);
|
||||
// }
|
||||
};
|
||||
|
||||
renderChart(layoutData) {
|
||||
let style = '';
|
||||
if (layoutData.styles) {
|
||||
style = Object.keys(layoutData.styles).map(key => `${key}: ${layoutData.styles[key]};`).join(' ');
|
||||
}
|
||||
|
||||
let editButton = this.viewMode ? '' : `<div class="chart-menu-icon" data-content-id="chart_${layoutData.contentId}">
|
||||
<div class="dropdown">
|
||||
<span class="" data-bs-toggle="dropdown" aria-expanded="false">⋮</span>
|
||||
<ul class="dropdown-menu">
|
||||
<li><span class="dropdown-item edit-chart">차트 편집</span></li>
|
||||
<li><span class="dropdown-item remove-chart">차트 제거</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>`;
|
||||
return `
|
||||
<div class="dashboard-chart" data-content-id="chart_${layoutData.contentId}" style="${style}">
|
||||
${editButton}
|
||||
<div id="chart_${layoutData.contentId}" style="width: 100%; height: 100%"></div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
renderContainer(layoutData) {
|
||||
console.log('renderContainer');
|
||||
|
||||
let children = '';
|
||||
if (layoutData.children) {
|
||||
layoutData.children.forEach(child => {
|
||||
if (child.type === 'container') {
|
||||
children += this.renderContainer(child);
|
||||
} else if (child.type === 'chart') {
|
||||
children += this.renderChart(child);
|
||||
}
|
||||
});
|
||||
}
|
||||
let width = layoutData.styles.width || '100%';
|
||||
let height = layoutData.styles.height || '100%';
|
||||
|
||||
let editButton = this.viewMode ? '' : `
|
||||
<div class="container-menu-icon">
|
||||
<div class="dropdown">
|
||||
<span><i class="fa-solid fa-arrows-up-down-left-right"></i></span>
|
||||
<span class="" data-bs-toggle="dropdown" aria-expanded="false">⋮</span>
|
||||
<ul class="dropdown-menu">
|
||||
<li><span class="dropdown-item edit-container">컨테이너 편집</span></li>
|
||||
<li><span class="dropdown-item remove-container">컨테이너 제거</span></li>
|
||||
<li><span class="dropdown-item add-new-chart">차트 추가</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
let style = '';
|
||||
if (layoutData.styles) {
|
||||
style = Object.keys(layoutData.styles).map(key =>{
|
||||
if (key === 'width' || key === 'height') return `${key}: 100%;`;
|
||||
return `${key}: ${layoutData.styles[key]};`;
|
||||
} ).join(' ');
|
||||
}
|
||||
|
||||
return `<div class="dashboard-container" style="width: ${width}; height: ${height};">
|
||||
<div class="handle">
|
||||
${editButton}
|
||||
</div>
|
||||
<div class="dashboard-container-children" style="${style}">
|
||||
${children}
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
addNewContainer(event) {
|
||||
this.tempContainer = {
|
||||
type: 'container',
|
||||
styles: {
|
||||
display: 'flex',
|
||||
'flex-direction': 'row',
|
||||
width: '100%',
|
||||
height: '300px'
|
||||
},
|
||||
children: []
|
||||
};
|
||||
this.showContainerEditor(true);
|
||||
}
|
||||
|
||||
editContainer(editContainerIndex) {
|
||||
console.log('edit-container: ' + editContainerIndex);
|
||||
this.tempContainer = JSON.parse(JSON.stringify(this.layoutData.children[editContainerIndex], LayoutUtil.echartReplacer));
|
||||
this.showContainerEditor(false);
|
||||
this.editContainerIndex = editContainerIndex;
|
||||
}
|
||||
|
||||
removeContainer(containerIndex) {
|
||||
console.log('edit-container: ' + containerIndex);
|
||||
this.layoutData.children.splice(containerIndex, 1);
|
||||
this.resetLayout();
|
||||
}
|
||||
|
||||
addContainer(event) {
|
||||
$('#container_editor_modal').find('input, select, textarea').each((index, element) => {
|
||||
const fieldName = $(element).attr('name');
|
||||
const newValue = LayoutUtil.getFieldValue(element);
|
||||
if (fieldName !== '') {
|
||||
_.set(this.tempContainer, fieldName, newValue);
|
||||
}
|
||||
});
|
||||
this.layoutData.children.push(JSON.parse(JSON.stringify(this.tempContainer)));
|
||||
this.tempContainer = null;
|
||||
this.resetLayout();
|
||||
$('#container_editor_modal').modal('hide');
|
||||
}
|
||||
|
||||
saveContainer(event) {
|
||||
console.log('save-container: ' + this.editContainerIndex);
|
||||
$('#container_editor_modal').find('input, select, textarea').each((index, element) => {
|
||||
const fieldName = $(element).attr('name');
|
||||
const newValue = LayoutUtil.getFieldValue(element);
|
||||
if (fieldName !== '') {
|
||||
_.set(this.tempContainer, fieldName, newValue);
|
||||
}
|
||||
});
|
||||
this.layoutData.children[this.editContainerIndex] = JSON.parse(JSON.stringify(this.tempContainer));
|
||||
this.tempContainer = null;
|
||||
this.resetLayout();
|
||||
$('#container_editor_modal').modal('hide');
|
||||
}
|
||||
|
||||
removeChart(containerIndex, columnIndex) {
|
||||
this.layoutData.children[containerIndex].children.splice(columnIndex, 1);
|
||||
this.resetLayout();
|
||||
}
|
||||
|
||||
onRowsSorted(ui) {
|
||||
let originalContainerIndex = ui.item.data('original_row_index');
|
||||
this.updateLayoutDataRows(originalContainerIndex, ui);
|
||||
}
|
||||
|
||||
onColumnsSorted(ui) {
|
||||
let containerIndex = $('.dashboard-root').children('.dashboard-container').index(ui.item.closest('.dashboard-container'));
|
||||
let originalContainerIndex = ui.item.data('original_row_index');
|
||||
if (originalContainerIndex) {
|
||||
if (originalContainerIndex === containerIndex) { // Check if the column moved from another row
|
||||
this.updateLayoutDataWithinRow(ui.item);
|
||||
} else { // The column was sorted within the same row
|
||||
this.updateLayoutDataColumns(ui.item);
|
||||
}
|
||||
this.resetLayout();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
updateLayoutDataRows(originalContainerIndex, ui) {
|
||||
let targetIndex = $('.dashboard-root').children('.dashboard-container').index(ui.item.closest('.dashboard-container'));
|
||||
|
||||
if (targetIndex !== originalContainerIndex) {
|
||||
this.layoutData.children.splice(targetIndex, 0, this.layoutData.children.splice(originalContainerIndex, 1)[0]);
|
||||
}
|
||||
console.log(this.layoutData);
|
||||
}
|
||||
|
||||
updateLayoutDataWithinRow(movedItem) {
|
||||
let oldContainerIndex = movedItem.data('original_row_index');
|
||||
let oldIndex = movedItem.data('original_column_index');
|
||||
|
||||
if (oldContainerIndex !== -1 && oldIndex !== -1) {
|
||||
let $row = movedItem.closest('.dashboard-container');
|
||||
let newIndex = $row.children('.dashboard-chart').index(movedItem);
|
||||
this.layoutData.children[oldContainerIndex].children.splice(newIndex, 0, this.layoutData.children[oldContainerIndex].children.splice(oldIndex, 1)[0]);
|
||||
}
|
||||
}
|
||||
|
||||
save() {
|
||||
const jsonString = JSON.stringify(this.layoutData, LayoutUtil.echartReplacer);
|
||||
return jsonString;
|
||||
}
|
||||
|
||||
updateLayoutDataColumns(movedItem) {
|
||||
// 새로운 행과 이전 행의 jQuery 요소를 가져옵니다
|
||||
let $newRow = movedItem.closest('.dashboard-container');
|
||||
|
||||
// 이전 행의 레이아웃 데이터에서 이동된 행과 열의 인덱스를 찾습니다
|
||||
let oldContainerIndex = movedItem.data('original_row_index');
|
||||
let movedColumnIndex = movedItem.data('original_column_index');
|
||||
|
||||
if (oldContainerIndex !== -1 && movedColumnIndex !== undefined && movedColumnIndex !== -1) {
|
||||
movedItem.data('original_row_index', -1);
|
||||
movedItem.data('original_column_index', -1);
|
||||
|
||||
// 새 행의 인덱스를 찾습니다
|
||||
let newContainerIndex = $('.dashboard-root').children('.dashboard-container').index($newRow);
|
||||
|
||||
// 이전 행에서 열 데이터를 추출합니다
|
||||
let movedColumnData = this.layoutData.children[oldContainerIndex].children.splice(movedColumnIndex, 1)[0];
|
||||
// 아이템이 드롭된 새 열 인덱스를 찾습니다
|
||||
let newColumnIndex = movedItem.closest('.dashboard-container-children').children('.dashboard-chart').index(movedItem);
|
||||
|
||||
// 이동된 열 데이터를 새 행의 열에 새 인덱스에 삽입합니다
|
||||
// 아이템이 마지막 위치로 이동된 경우 인덱스는 -1이 될 것이므로 이 경우를 처리합니다
|
||||
if (newColumnIndex === -1) {
|
||||
this.layoutData.children[newContainerIndex].children.push(movedColumnData);
|
||||
} else {
|
||||
this.layoutData.children[newContainerIndex].children.splice(newColumnIndex, 0, movedColumnData);
|
||||
}
|
||||
} else {
|
||||
console.log('called twice on update');
|
||||
}
|
||||
}
|
||||
|
||||
showContainerEditor(isNew) {
|
||||
if (isNew) {
|
||||
$('.add-container').show();
|
||||
$('.save-container').hide();
|
||||
} else {
|
||||
$('.add-container').hide();
|
||||
$('.save-container').show();
|
||||
}
|
||||
let flatModel = LayoutUtil.flattenObject(JSON.parse(JSON.stringify(this.tempContainer)));
|
||||
|
||||
$('#container_editor_modal').find('input, select, textarea').each((index, element) => {
|
||||
if ($(element).attr('type') === 'checkbox') {
|
||||
$(element).prop('checked', false);
|
||||
}
|
||||
$(element).val('');
|
||||
});
|
||||
|
||||
_.forEach(flatModel, (value, key) => {
|
||||
if ($('#container_editor_modal').find(`input[name="${key}"]`).attr('type') === 'checkbox') {
|
||||
$('#container_editor_modal').find(`input[name="${key}"]`).prop('checked', value);
|
||||
} else {
|
||||
$('#container_editor_modal').find(`input[name="${key}"], select[name="${key}"], textarea[name="${key}"]`).val(value);
|
||||
}
|
||||
});
|
||||
|
||||
$('#container_editor_modal').modal('show');
|
||||
}
|
||||
|
||||
resize() {
|
||||
console.log('resize');
|
||||
this.dashboard.resizeAllCharts();
|
||||
}
|
||||
|
||||
editDashboard(event) {
|
||||
this.showDashboardEditor();
|
||||
}
|
||||
|
||||
showDashboardEditor() {
|
||||
let flatModel = LayoutUtil.flattenObject(JSON.parse(JSON.stringify(this.layoutData, LayoutUtil.echartReplacer)));
|
||||
$('#dashboard_editor_modal').find('input, select, textarea').each((index, element) => {
|
||||
if ($(element).attr('type') === 'checkbox') {
|
||||
$(element).prop('checked', false);
|
||||
}
|
||||
$(element).val('');
|
||||
});
|
||||
|
||||
_.forEach(flatModel, (value, key) => {
|
||||
if ($('#dashboard_editor_modal').find(`input[name="${key}"]`).attr('type') === 'checkbox') {
|
||||
$('#dashboard_editor_modal').find(`input[name="${key}"]`).prop('checked', value);
|
||||
} else {
|
||||
$('#dashboard_editor_modal').find(`input[name="${key}"], select[name="${key}"], textarea[name="${key}"]`).val(value);
|
||||
}
|
||||
});
|
||||
|
||||
$('#dashboard_editor_modal').modal('show');
|
||||
}
|
||||
|
||||
saveDashboard(event) {
|
||||
$('#dashboard_editor_modal').find('input, select, textarea').each((index, element) => {
|
||||
const fieldName = $(element).attr('name');
|
||||
const newValue = LayoutUtil.getFieldValue(element);
|
||||
if (fieldName !== '') {
|
||||
_.set(this.layoutData, fieldName, newValue);
|
||||
}
|
||||
});
|
||||
console.log(this.layoutData);
|
||||
this.resetLayout();
|
||||
$('#dashboard_editor_modal').modal('hide');
|
||||
}
|
||||
|
||||
bindEvents() {
|
||||
$('.add-new-container').off('click').on('click', (event) => {
|
||||
this.addNewContainer(event);
|
||||
});
|
||||
|
||||
$('.edit-dashboard').off('click').on('click', (event) => {
|
||||
this.editDashboard(event);
|
||||
});
|
||||
|
||||
$('.save-dashboard').off('click').on('click', (event) => {
|
||||
this.saveDashboard(event);
|
||||
});
|
||||
|
||||
$('.remove-container').off('click').on('click', (event) => {
|
||||
let containerIndex = $('.dashboard-root').children('.dashboard-container').index(event.target.closest('.dashboard-container'));
|
||||
this.removeContainer(containerIndex);
|
||||
});
|
||||
|
||||
$('.add-container').off('click').on('click', (event) => {
|
||||
this.addContainer(event);
|
||||
});
|
||||
|
||||
$('.save-container').off('click').on('click', (event) => {
|
||||
this.saveContainer(event);
|
||||
});
|
||||
|
||||
$('.edit-container').off('click').on('click', (event) => {
|
||||
let editContainerIndex = $('.dashboard-root').children('.dashboard-container').index(event.target.closest('.dashboard-container'));
|
||||
this.editContainer(editContainerIndex);
|
||||
});
|
||||
|
||||
$('.edit-chart').off('click').on('click', (event) => {
|
||||
let editContainerIndex = $('.dashboard-root').children('.dashboard-container').index(event.target.closest('.dashboard-container'));
|
||||
let column = event.target.closest('.dashboard-chart');
|
||||
let editColumnIndex = $(event.target.closest('.dashboard-container-children')).children('.dashboard-chart').index(column);
|
||||
this.editChart(editContainerIndex, editColumnIndex);
|
||||
});
|
||||
|
||||
$('.add-new-chart').off('click').on('click', (event) => {
|
||||
let containerIndex = $('.dashboard-root').children('.dashboard-container').index(event.target.closest('.dashboard-container'));
|
||||
this.addNewChart(containerIndex);
|
||||
});
|
||||
|
||||
$('.remove-chart').off('click').on('click', (event) => {
|
||||
let column = event.target.closest('.dashboard-chart');
|
||||
let containerIndex = $('.dashboard-root').children('.dashboard-container').index(event.target.closest('.dashboard-container'));
|
||||
let columnIndex = $(event.target.closest('.dashboard-container-children')).children('.dashboard-chart').index(column);
|
||||
this.removeChart(containerIndex, columnIndex);
|
||||
});
|
||||
|
||||
if ($('.dashboard-root').hasClass('ui-sortable')) {
|
||||
$('.dashboard-root').sortable('destroy');
|
||||
}
|
||||
|
||||
$('.dashboard-root').sortable({
|
||||
handle: '.handle',
|
||||
items: '.dashboard-container',
|
||||
placeholder: 'ui-state-highlight',
|
||||
start: (event, ui) => {
|
||||
let originalRowIdx = $('.dashboard-root').children('.dashboard-container').index(ui.item.closest('.dashboard-container'));
|
||||
ui.item.data('original_row_index', originalRowIdx);
|
||||
}, update: (event, ui) => {
|
||||
this.onRowsSorted(ui);
|
||||
}
|
||||
});
|
||||
|
||||
if ($('.dashboard-container').hasClass('ui-sortable')) {
|
||||
$('.dashboard-container').sortable('destroy');
|
||||
}
|
||||
$('.dashboard-container-children').sortable({
|
||||
// handle: ".handle",
|
||||
placeholder: 'ui-state-highlight',
|
||||
items: '.dashboard-chart',
|
||||
connectWith: '.dashboard-container-children',
|
||||
update: (event, ui) => {
|
||||
this.onColumnsSorted(ui);
|
||||
},
|
||||
start: (event, ui) => {
|
||||
let originalRowIdx = $('.dashboard-root').children('.dashboard-container').index(ui.item.closest('.dashboard-container'));
|
||||
let originalColumnIdx = ui.item.closest('.dashboard-container-children').children('.dashboard-chart').index(ui.item);
|
||||
ui.item.data('original_row_index', originalRowIdx);
|
||||
ui.item.data('original_column_index', originalColumnIdx);
|
||||
}
|
||||
}).disableSelection();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -321,6 +321,14 @@ function insertImageAsDataUri(file, $editor) {
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
|
||||
/**
|
||||
* 정규식 기반 정리를 적용할 HTML 최대 길이(문자).
|
||||
* 아래 VML/네임스페이스 정리 정규식은 백트래킹으로 super-linear 가 될 수 있어(Sonar S5852)
|
||||
* 비정상적으로 큰 입력에서는 정규식 단계를 건너뛴다. data-uri 이미지를 포함한 일반적인
|
||||
* Office 붙여넣기는 이 한도 아래다.
|
||||
*/
|
||||
var MAX_REGEX_CLEAN_LENGTH = 500000;
|
||||
|
||||
/**
|
||||
* 붙여넣기된 HTML 정리 (MS Office 등에서 복사한 내용)
|
||||
* - 불필요한 Office 속성 제거
|
||||
@@ -358,6 +366,14 @@ function cleanPastedHtml(html) {
|
||||
|
||||
// VML 태그 제거 (v:, o:, w: 등)
|
||||
var cleanedHtml = $temp.html();
|
||||
|
||||
// 한도 초과 시 정규식 정리를 건너뛴다 — DOM 정리(이미지 속성 제거)는 이미 끝났으므로
|
||||
// 붙여넣기 자체는 동작하고, Office 네임스페이스 잔여물만 남는다. (브라우저 멈춤 방지)
|
||||
if (cleanedHtml.length > MAX_REGEX_CLEAN_LENGTH) {
|
||||
console.warn('붙여넣기 HTML이 너무 커서 Office 태그 정리를 건너뜁니다. length=' + cleanedHtml.length);
|
||||
return cleanedHtml;
|
||||
}
|
||||
|
||||
cleanedHtml = cleanedHtml.replace(/<v:[^>]*>[\s\S]*?<\/v:[^>]*>/gi, '');
|
||||
cleanedHtml = cleanedHtml.replace(/<o:[^>]*>[\s\S]*?<\/o:[^>]*>/gi, '');
|
||||
cleanedHtml = cleanedHtml.replace(/<w:[^>]*>[\s\S]*?<\/w:[^>]*>/gi, '');
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
var undefined;
|
||||
|
||||
/** Used as the semantic version number. */
|
||||
var VERSION = '4.17.21';
|
||||
var VERSION = '4.18.1';
|
||||
|
||||
/** Used as the size to enable large array optimizations. */
|
||||
var LARGE_ARRAY_SIZE = 200;
|
||||
@@ -20,7 +20,8 @@
|
||||
/** Error message constants. */
|
||||
var CORE_ERROR_TEXT = 'Unsupported core-js use. Try https://npms.io/search?q=ponyfill.',
|
||||
FUNC_ERROR_TEXT = 'Expected a function',
|
||||
INVALID_TEMPL_VAR_ERROR_TEXT = 'Invalid `variable` option passed into `_.template`';
|
||||
INVALID_TEMPL_VAR_ERROR_TEXT = 'Invalid `variable` option passed into `_.template`',
|
||||
INVALID_TEMPL_IMPORTS_ERROR_TEXT = 'Invalid `imports` option passed into `_.template`';
|
||||
|
||||
/** Used to stand-in for `undefined` hash values. */
|
||||
var HASH_UNDEFINED = '__lodash_hash_undefined__';
|
||||
@@ -1752,6 +1753,10 @@
|
||||
* embedded Ruby (ERB) as well as ES2015 template strings. Change the
|
||||
* following template settings to use alternative delimiters.
|
||||
*
|
||||
* **Security:** See
|
||||
* [threat model](https://github.com/lodash/lodash/blob/main/threat-model.md)
|
||||
* — `_.template` is insecure and will be removed in v5.
|
||||
*
|
||||
* @static
|
||||
* @memberOf _
|
||||
* @type {Object}
|
||||
@@ -2300,7 +2305,7 @@
|
||||
* @name has
|
||||
* @memberOf SetCache
|
||||
* @param {*} value The value to search for.
|
||||
* @returns {number} Returns `true` if `value` is found, else `false`.
|
||||
* @returns {boolean} Returns `true` if `value` is found, else `false`.
|
||||
*/
|
||||
function setCacheHas(value) {
|
||||
return this.__data__.has(value);
|
||||
@@ -3766,7 +3771,7 @@
|
||||
if (isArray(iteratee)) {
|
||||
return function(value) {
|
||||
return baseGet(value, iteratee.length === 1 ? iteratee[0] : iteratee);
|
||||
}
|
||||
};
|
||||
}
|
||||
return iteratee;
|
||||
});
|
||||
@@ -4370,8 +4375,34 @@
|
||||
*/
|
||||
function baseUnset(object, path) {
|
||||
path = castPath(path, object);
|
||||
object = parent(object, path);
|
||||
return object == null || delete object[toKey(last(path))];
|
||||
|
||||
// Prevent prototype pollution:
|
||||
// https://github.com/lodash/lodash/security/advisories/GHSA-xxjr-mmjv-4gpg
|
||||
// https://github.com/lodash/lodash/security/advisories/GHSA-f23m-r3pf-42rh
|
||||
var index = -1,
|
||||
length = path.length;
|
||||
|
||||
if (!length) {
|
||||
return true;
|
||||
}
|
||||
|
||||
while (++index < length) {
|
||||
var key = toKey(path[index]);
|
||||
|
||||
// Always block "__proto__" anywhere in the path if it's not expected
|
||||
if (key === '__proto__' && !hasOwnProperty.call(object, '__proto__')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Block constructor/prototype as non-terminal traversal keys to prevent
|
||||
// escaping the object graph into built-in constructors and prototypes.
|
||||
if ((key === 'constructor' || key === 'prototype') && index < length - 1) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var obj = parent(object, path);
|
||||
return obj == null || delete obj[toKey(last(path))];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -6922,7 +6953,7 @@
|
||||
|
||||
/**
|
||||
* Creates an array with all falsey values removed. The values `false`, `null`,
|
||||
* `0`, `""`, `undefined`, and `NaN` are falsey.
|
||||
* `0`, `-0`, `0n`, `""`, `undefined`, and `NaN` are falsy.
|
||||
*
|
||||
* @static
|
||||
* @memberOf _
|
||||
@@ -7461,7 +7492,7 @@
|
||||
|
||||
while (++index < length) {
|
||||
var pair = pairs[index];
|
||||
result[pair[0]] = pair[1];
|
||||
baseAssignValue(result, pair[0], pair[1]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -14121,6 +14152,8 @@
|
||||
* **Note:** JavaScript follows the IEEE-754 standard for resolving
|
||||
* floating-point values which can produce unexpected results.
|
||||
*
|
||||
* **Note:** If `lower` is greater than `upper`, the values are swapped.
|
||||
*
|
||||
* @static
|
||||
* @memberOf _
|
||||
* @since 0.7.0
|
||||
@@ -14134,9 +14167,16 @@
|
||||
* _.random(0, 5);
|
||||
* // => an integer between 0 and 5
|
||||
*
|
||||
* // when lower is greater than upper the values are swapped
|
||||
* _.random(5, 0);
|
||||
* // => an integer between 0 and 5
|
||||
*
|
||||
* _.random(5);
|
||||
* // => also an integer between 0 and 5
|
||||
*
|
||||
* _.random(-5);
|
||||
* // => an integer between -5 and 0
|
||||
*
|
||||
* _.random(5, true);
|
||||
* // => a floating-point number between 0 and 5
|
||||
*
|
||||
@@ -14738,6 +14778,10 @@
|
||||
* properties may be accessed as free variables in the template. If a setting
|
||||
* object is given, it takes precedence over `_.templateSettings` values.
|
||||
*
|
||||
* **Security:** `_.template` is insecure and should not be used. It will be
|
||||
* removed in Lodash v5. Avoid untrusted input. See
|
||||
* [threat model](https://github.com/lodash/lodash/blob/main/threat-model.md).
|
||||
*
|
||||
* **Note:** In the development build `_.template` utilizes
|
||||
* [sourceURLs](http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl)
|
||||
* for easier debugging.
|
||||
@@ -14845,12 +14889,18 @@
|
||||
options = undefined;
|
||||
}
|
||||
string = toString(string);
|
||||
options = assignInWith({}, options, settings, customDefaultsAssignIn);
|
||||
options = assignWith({}, options, settings, customDefaultsAssignIn);
|
||||
|
||||
var imports = assignInWith({}, options.imports, settings.imports, customDefaultsAssignIn),
|
||||
var imports = assignWith({}, options.imports, settings.imports, customDefaultsAssignIn),
|
||||
importsKeys = keys(imports),
|
||||
importsValues = baseValues(imports, importsKeys);
|
||||
|
||||
arrayEach(importsKeys, function(key) {
|
||||
if (reForbiddenIdentifierChars.test(key)) {
|
||||
throw new Error(INVALID_TEMPL_IMPORTS_ERROR_TEXT);
|
||||
}
|
||||
});
|
||||
|
||||
var isEscaping,
|
||||
isEvaluating,
|
||||
index = 0,
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,173 +0,0 @@
|
||||
|
||||
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* AUTO-GENERATED FILE. DO NOT MODIFY.
|
||||
*/
|
||||
(function(root, factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
// AMD. Register as an anonymous module.
|
||||
define(['exports'], factory);
|
||||
} else if (
|
||||
typeof exports === 'object' &&
|
||||
typeof exports.nodeName !== 'string'
|
||||
) {
|
||||
// CommonJS
|
||||
factory(exports);
|
||||
} else {
|
||||
// Browser globals
|
||||
factory({});
|
||||
}
|
||||
})(this, function(exports) {
|
||||
|
||||
|
||||
/**
|
||||
* Language: English.
|
||||
*/
|
||||
|
||||
var localeObj = {
|
||||
time: {
|
||||
month: [
|
||||
'January', 'February', 'March', 'April', 'May', 'June',
|
||||
'July', 'August', 'September', 'October', 'November', 'December'
|
||||
],
|
||||
monthAbbr: [
|
||||
'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
|
||||
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'
|
||||
],
|
||||
dayOfWeek: [
|
||||
'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'
|
||||
],
|
||||
dayOfWeekAbbr: [
|
||||
'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'
|
||||
]
|
||||
},
|
||||
legend: {
|
||||
selector: {
|
||||
all: 'All',
|
||||
inverse: 'Inv'
|
||||
}
|
||||
},
|
||||
toolbox: {
|
||||
brush: {
|
||||
title: {
|
||||
rect: 'Box Select',
|
||||
polygon: 'Lasso Select',
|
||||
lineX: 'Horizontally Select',
|
||||
lineY: 'Vertically Select',
|
||||
keep: 'Keep Selections',
|
||||
clear: 'Clear Selections'
|
||||
}
|
||||
},
|
||||
dataView: {
|
||||
title: 'Data View',
|
||||
lang: ['Data View', 'Close', 'Refresh']
|
||||
},
|
||||
dataZoom: {
|
||||
title: {
|
||||
zoom: 'Zoom',
|
||||
back: 'Zoom Reset'
|
||||
}
|
||||
},
|
||||
magicType: {
|
||||
title: {
|
||||
line: 'Switch to Line Chart',
|
||||
bar: 'Switch to Bar Chart',
|
||||
stack: 'Stack',
|
||||
tiled: 'Tile'
|
||||
}
|
||||
},
|
||||
restore: {
|
||||
title: 'Restore'
|
||||
},
|
||||
saveAsImage: {
|
||||
title: 'Save as Image',
|
||||
lang: ['Right Click to Save Image']
|
||||
}
|
||||
},
|
||||
series: {
|
||||
typeNames: {
|
||||
pie: 'Pie chart',
|
||||
bar: 'Bar chart',
|
||||
line: 'Line chart',
|
||||
scatter: 'Scatter plot',
|
||||
effectScatter: 'Ripple scatter plot',
|
||||
radar: 'Radar chart',
|
||||
tree: 'Tree',
|
||||
treemap: 'Treemap',
|
||||
boxplot: 'Boxplot',
|
||||
candlestick: 'Candlestick',
|
||||
k: 'K line chart',
|
||||
heatmap: 'Heat map',
|
||||
map: 'Map',
|
||||
parallel: 'Parallel coordinate map',
|
||||
lines: 'Line graph',
|
||||
graph: 'Relationship graph',
|
||||
sankey: 'Sankey diagram',
|
||||
funnel: 'Funnel chart',
|
||||
gauge: 'Gauge',
|
||||
pictorialBar: 'Pictorial bar',
|
||||
themeRiver: 'Theme River Map',
|
||||
sunburst: 'Sunburst'
|
||||
}
|
||||
},
|
||||
aria: {
|
||||
general: {
|
||||
withTitle: 'This is a chart about "{title}"',
|
||||
withoutTitle: 'This is a chart'
|
||||
},
|
||||
series: {
|
||||
single: {
|
||||
prefix: '',
|
||||
withName: ' with type {seriesType} named {seriesName}.',
|
||||
withoutName: ' with type {seriesType}.'
|
||||
},
|
||||
multiple: {
|
||||
prefix: '. It consists of {seriesCount} series count.',
|
||||
withName: ' The {seriesId} series is a {seriesType} representing {seriesName}.',
|
||||
withoutName: ' The {seriesId} series is a {seriesType}.',
|
||||
separator: {
|
||||
middle: '',
|
||||
end: ''
|
||||
}
|
||||
}
|
||||
},
|
||||
data: {
|
||||
allData: 'The data is as follows: ',
|
||||
partialData: 'The first {displayCnt} items are: ',
|
||||
withName: 'the data for {name} is {value}',
|
||||
withoutName: '{value}',
|
||||
separator: {
|
||||
middle: ', ',
|
||||
end: '. '
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
for (var key in localeObj) {
|
||||
if (localeObj.hasOwnProperty(key)) {
|
||||
exports[key] = localeObj[key];
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
@@ -1,169 +0,0 @@
|
||||
|
||||
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* AUTO-GENERATED FILE. DO NOT MODIFY.
|
||||
*/
|
||||
(function(root, factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
// AMD. Register as an anonymous module.
|
||||
define(['exports', 'echarts'], factory);
|
||||
} else if (
|
||||
typeof exports === 'object' &&
|
||||
typeof exports.nodeName !== 'string'
|
||||
) {
|
||||
// CommonJS
|
||||
factory(exports, require('echarts/lib/echarts'));
|
||||
} else {
|
||||
// Browser globals
|
||||
factory({}, root.echarts);
|
||||
}
|
||||
})(this, function(exports, echarts) {
|
||||
|
||||
|
||||
/**
|
||||
* Language: English.
|
||||
*/
|
||||
|
||||
var localeObj = {
|
||||
time: {
|
||||
month: [
|
||||
'January', 'February', 'March', 'April', 'May', 'June',
|
||||
'July', 'August', 'September', 'October', 'November', 'December'
|
||||
],
|
||||
monthAbbr: [
|
||||
'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
|
||||
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'
|
||||
],
|
||||
dayOfWeek: [
|
||||
'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'
|
||||
],
|
||||
dayOfWeekAbbr: [
|
||||
'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'
|
||||
]
|
||||
},
|
||||
legend: {
|
||||
selector: {
|
||||
all: 'All',
|
||||
inverse: 'Inv'
|
||||
}
|
||||
},
|
||||
toolbox: {
|
||||
brush: {
|
||||
title: {
|
||||
rect: 'Box Select',
|
||||
polygon: 'Lasso Select',
|
||||
lineX: 'Horizontally Select',
|
||||
lineY: 'Vertically Select',
|
||||
keep: 'Keep Selections',
|
||||
clear: 'Clear Selections'
|
||||
}
|
||||
},
|
||||
dataView: {
|
||||
title: 'Data View',
|
||||
lang: ['Data View', 'Close', 'Refresh']
|
||||
},
|
||||
dataZoom: {
|
||||
title: {
|
||||
zoom: 'Zoom',
|
||||
back: 'Zoom Reset'
|
||||
}
|
||||
},
|
||||
magicType: {
|
||||
title: {
|
||||
line: 'Switch to Line Chart',
|
||||
bar: 'Switch to Bar Chart',
|
||||
stack: 'Stack',
|
||||
tiled: 'Tile'
|
||||
}
|
||||
},
|
||||
restore: {
|
||||
title: 'Restore'
|
||||
},
|
||||
saveAsImage: {
|
||||
title: 'Save as Image',
|
||||
lang: ['Right Click to Save Image']
|
||||
}
|
||||
},
|
||||
series: {
|
||||
typeNames: {
|
||||
pie: 'Pie chart',
|
||||
bar: 'Bar chart',
|
||||
line: 'Line chart',
|
||||
scatter: 'Scatter plot',
|
||||
effectScatter: 'Ripple scatter plot',
|
||||
radar: 'Radar chart',
|
||||
tree: 'Tree',
|
||||
treemap: 'Treemap',
|
||||
boxplot: 'Boxplot',
|
||||
candlestick: 'Candlestick',
|
||||
k: 'K line chart',
|
||||
heatmap: 'Heat map',
|
||||
map: 'Map',
|
||||
parallel: 'Parallel coordinate map',
|
||||
lines: 'Line graph',
|
||||
graph: 'Relationship graph',
|
||||
sankey: 'Sankey diagram',
|
||||
funnel: 'Funnel chart',
|
||||
gauge: 'Gauge',
|
||||
pictorialBar: 'Pictorial bar',
|
||||
themeRiver: 'Theme River Map',
|
||||
sunburst: 'Sunburst'
|
||||
}
|
||||
},
|
||||
aria: {
|
||||
general: {
|
||||
withTitle: 'This is a chart about "{title}"',
|
||||
withoutTitle: 'This is a chart'
|
||||
},
|
||||
series: {
|
||||
single: {
|
||||
prefix: '',
|
||||
withName: ' with type {seriesType} named {seriesName}.',
|
||||
withoutName: ' with type {seriesType}.'
|
||||
},
|
||||
multiple: {
|
||||
prefix: '. It consists of {seriesCount} series count.',
|
||||
withName: ' The {seriesId} series is a {seriesType} representing {seriesName}.',
|
||||
withoutName: ' The {seriesId} series is a {seriesType}.',
|
||||
separator: {
|
||||
middle: '',
|
||||
end: ''
|
||||
}
|
||||
}
|
||||
},
|
||||
data: {
|
||||
allData: 'The data is as follows: ',
|
||||
partialData: 'The first {displayCnt} items are: ',
|
||||
withName: 'the data for {name} is {value}',
|
||||
withoutName: '{value}',
|
||||
separator: {
|
||||
middle: ', ',
|
||||
end: '. '
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
echarts.registerLocale('EN', localeObj);
|
||||
|
||||
});
|
||||
@@ -1,173 +0,0 @@
|
||||
|
||||
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* AUTO-GENERATED FILE. DO NOT MODIFY.
|
||||
*/
|
||||
(function(root, factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
// AMD. Register as an anonymous module.
|
||||
define(['exports'], factory);
|
||||
} else if (
|
||||
typeof exports === 'object' &&
|
||||
typeof exports.nodeName !== 'string'
|
||||
) {
|
||||
// CommonJS
|
||||
factory(exports);
|
||||
} else {
|
||||
// Browser globals
|
||||
factory({});
|
||||
}
|
||||
})(this, function(exports) {
|
||||
|
||||
|
||||
/**
|
||||
* Language: Korean.
|
||||
*/
|
||||
|
||||
var localeObj = {
|
||||
time: {
|
||||
month: [
|
||||
'1월', '2월', '3월', '4월', '5월', '6월',
|
||||
'7월', '8월', '9월', '10월', '11월', '12월'
|
||||
],
|
||||
monthAbbr: [
|
||||
'1월', '2월', '3월', '4월', '5월', '6월',
|
||||
'7월', '8월', '9월', '10월', '11월', '12월'
|
||||
],
|
||||
dayOfWeek: [
|
||||
'일요일', '월요일', '화요일', '수요일', '목요일', '금요일', '토요일'
|
||||
],
|
||||
dayOfWeekAbbr: [
|
||||
'일', '월', '화', '수', '목', '금', '토'
|
||||
]
|
||||
},
|
||||
legend: {
|
||||
selector: {
|
||||
all: '모두 선택',
|
||||
inverse: '선택 범위 반전'
|
||||
}
|
||||
},
|
||||
toolbox: {
|
||||
brush: {
|
||||
title: {
|
||||
rect: '사각형 선택',
|
||||
polygon: '올가미 선택',
|
||||
lineX: '수평 선택',
|
||||
lineY: '수직 선택',
|
||||
keep: '선택 유지',
|
||||
clear: '선택 지우기'
|
||||
}
|
||||
},
|
||||
dataView: {
|
||||
title: '날짜 보기',
|
||||
lang: ['날짜 보기', '닫기', '새로 고침']
|
||||
},
|
||||
dataZoom: {
|
||||
title: {
|
||||
zoom: '확대/축소',
|
||||
back: '확대/축소 초기화'
|
||||
}
|
||||
},
|
||||
magicType: {
|
||||
title: {
|
||||
line: '꺽은선 그래프로 변경',
|
||||
bar: '막대 그래프로 변경',
|
||||
stack: '스택',
|
||||
tiled: '타일'
|
||||
}
|
||||
},
|
||||
restore: {
|
||||
title: '복구'
|
||||
},
|
||||
saveAsImage: {
|
||||
title: '이미지로 저장',
|
||||
lang: ['이미지를 저장하려면 마우스 오른쪽 버튼을 클릭하세요.']
|
||||
}
|
||||
},
|
||||
series: {
|
||||
typeNames: {
|
||||
pie: '원 그래프',
|
||||
bar: '막대 그래프',
|
||||
line: '꺽은선 그래프',
|
||||
scatter: '산점도',
|
||||
effectScatter: '물결 효과 산점도',
|
||||
radar: '방사형 그래프',
|
||||
tree: '트리',
|
||||
treemap: '트리맵',
|
||||
boxplot: '상자 수염 그래프',
|
||||
candlestick: '캔들스틱 차트',
|
||||
k: 'K 라인 차트',
|
||||
heatmap: '히트 맵',
|
||||
map: '지도',
|
||||
parallel: '평행 좌표 맵',
|
||||
lines: '선',
|
||||
graph: '관계 그래프',
|
||||
sankey: '산키 다이어그램',
|
||||
funnel: '깔때기형 그래프',
|
||||
gauge: '계기',
|
||||
pictorialBar: '픽토그램 차트',
|
||||
themeRiver: '스트림 그래프',
|
||||
sunburst: '선버스트 차트'
|
||||
}
|
||||
},
|
||||
aria: {
|
||||
general: {
|
||||
withTitle: '"{title}"에 대한 차트입니다.',
|
||||
withoutTitle: '차트입니다.'
|
||||
},
|
||||
series: {
|
||||
single: {
|
||||
prefix: '',
|
||||
withName: ' 차트 유형은 {seriesType}이며 {seriesName}을 표시합니다.',
|
||||
withoutName: ' 차트 유형은 {seriesType}입니다.'
|
||||
},
|
||||
multiple: {
|
||||
prefix: '. {seriesCount} 하나의 차트 시리즈로 구성됩니다.',
|
||||
withName: ' {seriesId}번째 시리즈는 {seriesName}을 나타내는 {seriesType} representing.',
|
||||
withoutName: ' {seriesId}번째 시리즈는 {seriesType}입니다.',
|
||||
separator: {
|
||||
middle: '',
|
||||
end: ''
|
||||
}
|
||||
}
|
||||
},
|
||||
data: {
|
||||
allData: '데이터: ',
|
||||
partialData: '첫번째 {displayCnt} 아이템: ',
|
||||
withName: '{name}의 데이터는 {value}',
|
||||
withoutName: '{value}',
|
||||
separator: {
|
||||
middle: ', ',
|
||||
end: '. '
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
for (var key in localeObj) {
|
||||
if (localeObj.hasOwnProperty(key)) {
|
||||
exports[key] = localeObj[key];
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
@@ -1,169 +0,0 @@
|
||||
|
||||
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* AUTO-GENERATED FILE. DO NOT MODIFY.
|
||||
*/
|
||||
(function(root, factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
// AMD. Register as an anonymous module.
|
||||
define(['exports', 'echarts'], factory);
|
||||
} else if (
|
||||
typeof exports === 'object' &&
|
||||
typeof exports.nodeName !== 'string'
|
||||
) {
|
||||
// CommonJS
|
||||
factory(exports, require('echarts/lib/echarts'));
|
||||
} else {
|
||||
// Browser globals
|
||||
factory({}, root.echarts);
|
||||
}
|
||||
})(this, function(exports, echarts) {
|
||||
|
||||
|
||||
/**
|
||||
* Language: Korean.
|
||||
*/
|
||||
|
||||
var localeObj = {
|
||||
time: {
|
||||
month: [
|
||||
'1월', '2월', '3월', '4월', '5월', '6월',
|
||||
'7월', '8월', '9월', '10월', '11월', '12월'
|
||||
],
|
||||
monthAbbr: [
|
||||
'1월', '2월', '3월', '4월', '5월', '6월',
|
||||
'7월', '8월', '9월', '10월', '11월', '12월'
|
||||
],
|
||||
dayOfWeek: [
|
||||
'일요일', '월요일', '화요일', '수요일', '목요일', '금요일', '토요일'
|
||||
],
|
||||
dayOfWeekAbbr: [
|
||||
'일', '월', '화', '수', '목', '금', '토'
|
||||
]
|
||||
},
|
||||
legend: {
|
||||
selector: {
|
||||
all: '모두 선택',
|
||||
inverse: '선택 범위 반전'
|
||||
}
|
||||
},
|
||||
toolbox: {
|
||||
brush: {
|
||||
title: {
|
||||
rect: '사각형 선택',
|
||||
polygon: '올가미 선택',
|
||||
lineX: '수평 선택',
|
||||
lineY: '수직 선택',
|
||||
keep: '선택 유지',
|
||||
clear: '선택 지우기'
|
||||
}
|
||||
},
|
||||
dataView: {
|
||||
title: '날짜 보기',
|
||||
lang: ['날짜 보기', '닫기', '새로 고침']
|
||||
},
|
||||
dataZoom: {
|
||||
title: {
|
||||
zoom: '확대/축소',
|
||||
back: '확대/축소 초기화'
|
||||
}
|
||||
},
|
||||
magicType: {
|
||||
title: {
|
||||
line: '꺽은선 그래프로 변경',
|
||||
bar: '막대 그래프로 변경',
|
||||
stack: '스택',
|
||||
tiled: '타일'
|
||||
}
|
||||
},
|
||||
restore: {
|
||||
title: '복구'
|
||||
},
|
||||
saveAsImage: {
|
||||
title: '이미지로 저장',
|
||||
lang: ['이미지를 저장하려면 마우스 오른쪽 버튼을 클릭하세요.']
|
||||
}
|
||||
},
|
||||
series: {
|
||||
typeNames: {
|
||||
pie: '원 그래프',
|
||||
bar: '막대 그래프',
|
||||
line: '꺽은선 그래프',
|
||||
scatter: '산점도',
|
||||
effectScatter: '물결 효과 산점도',
|
||||
radar: '방사형 그래프',
|
||||
tree: '트리',
|
||||
treemap: '트리맵',
|
||||
boxplot: '상자 수염 그래프',
|
||||
candlestick: '캔들스틱 차트',
|
||||
k: 'K 라인 차트',
|
||||
heatmap: '히트 맵',
|
||||
map: '지도',
|
||||
parallel: '평행 좌표 맵',
|
||||
lines: '선',
|
||||
graph: '관계 그래프',
|
||||
sankey: '산키 다이어그램',
|
||||
funnel: '깔때기형 그래프',
|
||||
gauge: '계기',
|
||||
pictorialBar: '픽토그램 차트',
|
||||
themeRiver: '스트림 그래프',
|
||||
sunburst: '선버스트 차트'
|
||||
}
|
||||
},
|
||||
aria: {
|
||||
general: {
|
||||
withTitle: '"{title}"에 대한 차트입니다.',
|
||||
withoutTitle: '차트입니다.'
|
||||
},
|
||||
series: {
|
||||
single: {
|
||||
prefix: '',
|
||||
withName: ' 차트 유형은 {seriesType}이며 {seriesName}을 표시합니다.',
|
||||
withoutName: ' 차트 유형은 {seriesType}입니다.'
|
||||
},
|
||||
multiple: {
|
||||
prefix: '. {seriesCount} 하나의 차트 시리즈로 구성됩니다.',
|
||||
withName: ' {seriesId}번째 시리즈는 {seriesName}을 나타내는 {seriesType} representing.',
|
||||
withoutName: ' {seriesId}번째 시리즈는 {seriesType}입니다.',
|
||||
separator: {
|
||||
middle: '',
|
||||
end: ''
|
||||
}
|
||||
}
|
||||
},
|
||||
data: {
|
||||
allData: '데이터: ',
|
||||
partialData: '첫번째 {displayCnt} 아이템: ',
|
||||
withName: '{name}의 데이터는 {value}',
|
||||
withoutName: '{value}',
|
||||
separator: {
|
||||
middle: ', ',
|
||||
end: '. '
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
echarts.registerLocale('KO', localeObj);
|
||||
|
||||
});
|
||||
@@ -1,163 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
(function(root, factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
// AMD. Register as an anonymous module.
|
||||
define(['exports', 'echarts'], factory);
|
||||
} else if (
|
||||
typeof exports === 'object' &&
|
||||
typeof exports.nodeName !== 'string'
|
||||
) {
|
||||
// CommonJS
|
||||
factory(exports, require('echarts/lib/echarts'));
|
||||
} else {
|
||||
// Browser globals
|
||||
factory({}, root.echarts);
|
||||
}
|
||||
})(this, function(exports, echarts) {
|
||||
var log = function(msg) {
|
||||
if (typeof console !== 'undefined') {
|
||||
console && console.error && console.error(msg);
|
||||
}
|
||||
};
|
||||
if (!echarts) {
|
||||
log('ECharts is not Loaded');
|
||||
return;
|
||||
}
|
||||
|
||||
var colorPalette = [
|
||||
'#f2385a',
|
||||
'#f5a503',
|
||||
'#4ad9d9',
|
||||
'#f7879c',
|
||||
'#c1d7a8',
|
||||
'#4dffd2',
|
||||
'#fccfd7',
|
||||
'#d5f6f6'
|
||||
];
|
||||
|
||||
var theme = {
|
||||
color: colorPalette,
|
||||
|
||||
title: {
|
||||
textStyle: {
|
||||
fontWeight: 'normal',
|
||||
color: '#f2385a'
|
||||
}
|
||||
},
|
||||
|
||||
visualMap: {
|
||||
color: ['#f2385a', '#f5a503']
|
||||
},
|
||||
|
||||
toolbox: {
|
||||
color: ['#f2385a', '#f2385a', '#f2385a', '#f2385a']
|
||||
},
|
||||
|
||||
tooltip: {
|
||||
backgroundColor: 'rgba(0,0,0,0.5)',
|
||||
axisPointer: {
|
||||
// Axis indicator, coordinate trigger effective
|
||||
type: 'line', // The default is a straight line: 'line' | 'shadow'
|
||||
lineStyle: {
|
||||
// Straight line indicator style settings
|
||||
color: '#f2385a',
|
||||
type: 'dashed'
|
||||
},
|
||||
crossStyle: {
|
||||
color: '#f2385a'
|
||||
},
|
||||
shadowStyle: {
|
||||
// Shadow indicator style settings
|
||||
color: 'rgba(200,200,200,0.3)'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Area scaling controller
|
||||
dataZoom: {
|
||||
dataBackgroundColor: '#eee', // Data background color
|
||||
fillerColor: 'rgba(200,200,200,0.2)', // Fill the color
|
||||
handleColor: '#f2385a' // Handle color
|
||||
},
|
||||
|
||||
timeline: {
|
||||
lineStyle: {
|
||||
color: '#f2385a'
|
||||
},
|
||||
controlStyle: {
|
||||
color: '#f2385a',
|
||||
borderColor: '#f2385a'
|
||||
}
|
||||
},
|
||||
|
||||
candlestick: {
|
||||
itemStyle: {
|
||||
color: '#f2385a',
|
||||
color0: '#f5a503'
|
||||
},
|
||||
lineStyle: {
|
||||
width: 1,
|
||||
color: '#f2385a',
|
||||
color0: '#f5a503'
|
||||
},
|
||||
areaStyle: {
|
||||
color: '#c1d7a8',
|
||||
color0: '#4ad9d9'
|
||||
}
|
||||
},
|
||||
|
||||
map: {
|
||||
itemStyle: {
|
||||
color: '#f2385a'
|
||||
},
|
||||
areaStyle: {
|
||||
color: '#ddd'
|
||||
},
|
||||
label: {
|
||||
color: '#c12e34'
|
||||
}
|
||||
},
|
||||
|
||||
graph: {
|
||||
itemStyle: {
|
||||
color: '#f2385a'
|
||||
},
|
||||
linkStyle: {
|
||||
color: '#f2385a'
|
||||
}
|
||||
},
|
||||
|
||||
gauge: {
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: [
|
||||
[0.2, '#f5a503'],
|
||||
[0.8, '#f2385a'],
|
||||
[1, '#c1d7a8']
|
||||
],
|
||||
width: 8
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
echarts.registerTheme('azul', theme);
|
||||
});
|
||||
@@ -1,178 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
(function(root, factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
// AMD. Register as an anonymous module.
|
||||
define(['exports', 'echarts'], factory);
|
||||
} else if (
|
||||
typeof exports === 'object' &&
|
||||
typeof exports.nodeName !== 'string'
|
||||
) {
|
||||
// CommonJS
|
||||
factory(exports, require('echarts/lib/echarts'));
|
||||
} else {
|
||||
// Browser globals
|
||||
factory({}, root.echarts);
|
||||
}
|
||||
})(this, function(exports, echarts) {
|
||||
var log = function(msg) {
|
||||
if (typeof console !== 'undefined') {
|
||||
console && console.error && console.error(msg);
|
||||
}
|
||||
};
|
||||
if (!echarts) {
|
||||
log('ECharts is not Loaded');
|
||||
return;
|
||||
}
|
||||
|
||||
var colorPalette = [
|
||||
'#001727',
|
||||
'#805500',
|
||||
'#ffff00',
|
||||
'#ffd11a',
|
||||
'#f2d71f',
|
||||
'#f2be19',
|
||||
'#f3a81a',
|
||||
'#fff5cc'
|
||||
];
|
||||
|
||||
var theme = {
|
||||
color: colorPalette,
|
||||
|
||||
title: {
|
||||
textStyle: {
|
||||
fontWeight: 'normal',
|
||||
color: '#001727'
|
||||
}
|
||||
},
|
||||
|
||||
visualMap: {
|
||||
color: ['#001727', '#805500']
|
||||
},
|
||||
|
||||
toolbox: {
|
||||
color: ['#001727', '#001727', '#001727', '#001727']
|
||||
},
|
||||
|
||||
tooltip: {
|
||||
backgroundColor: 'rgba(0,0,0,0.5)',
|
||||
axisPointer: {
|
||||
// Axis indicator, coordinate trigger effective
|
||||
type: 'line', // The default is a straight line: 'line' | 'shadow'
|
||||
lineStyle: {
|
||||
// Straight line indicator style settings
|
||||
color: '#001727',
|
||||
type: 'dashed'
|
||||
},
|
||||
crossStyle: {
|
||||
color: '#001727'
|
||||
},
|
||||
shadowStyle: {
|
||||
// Shadow indicator style settings
|
||||
color: 'rgba(200,200,200,0.3)'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Area scaling controller
|
||||
dataZoom: {
|
||||
dataBackgroundColor: '#eee', // Data background color
|
||||
fillerColor: 'rgba(200,200,200,0.2)', // Fill the color
|
||||
handleColor: '#001727' // Handle color
|
||||
},
|
||||
|
||||
timeline: {
|
||||
lineStyle: {
|
||||
color: '#001727'
|
||||
},
|
||||
controlStyle: {
|
||||
color: '#001727',
|
||||
borderColor: '#001727'
|
||||
}
|
||||
},
|
||||
|
||||
candlestick: {
|
||||
itemStyle: {
|
||||
color: '#f3a81a',
|
||||
color0: '#ffff00'
|
||||
},
|
||||
lineStyle: {
|
||||
width: 1,
|
||||
color: '#ffff00',
|
||||
color0: '#f3a81a'
|
||||
},
|
||||
areaStyle: {
|
||||
color: '#805500',
|
||||
color0: '#ffff00'
|
||||
}
|
||||
},
|
||||
|
||||
chord: {
|
||||
padding: 4,
|
||||
itemStyle: {
|
||||
color: '#f3a81a',
|
||||
borderWidth: 1,
|
||||
borderColor: 'rgba(128, 128, 128, 0.5)'
|
||||
},
|
||||
lineStyle: {
|
||||
color: 'rgba(128, 128, 128, 0.5)'
|
||||
},
|
||||
areaStyle: {
|
||||
color: '#805500'
|
||||
}
|
||||
},
|
||||
|
||||
map: {
|
||||
itemStyle: {
|
||||
color: '#ffd11a'
|
||||
},
|
||||
areaStyle: {
|
||||
color: '#f2be19'
|
||||
},
|
||||
label: {
|
||||
color: '#ffd11a'
|
||||
}
|
||||
},
|
||||
|
||||
graph: {
|
||||
itemStyle: {
|
||||
color: '#001727'
|
||||
},
|
||||
linkStyle: {
|
||||
color: '#001727'
|
||||
}
|
||||
},
|
||||
|
||||
gauge: {
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: [
|
||||
[0.2, '#f2d71f'],
|
||||
[0.8, '#001727'],
|
||||
[1, '#ffff00']
|
||||
],
|
||||
width: 8
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
echarts.registerTheme('bee-inspired', theme);
|
||||
});
|
||||
@@ -1,178 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
(function(root, factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
// AMD. Register as an anonymous module.
|
||||
define(['exports', 'echarts'], factory);
|
||||
} else if (
|
||||
typeof exports === 'object' &&
|
||||
typeof exports.nodeName !== 'string'
|
||||
) {
|
||||
// CommonJS
|
||||
factory(exports, require('echarts/lib/echarts'));
|
||||
} else {
|
||||
// Browser globals
|
||||
factory({}, root.echarts);
|
||||
}
|
||||
})(this, function(exports, echarts) {
|
||||
var log = function(msg) {
|
||||
if (typeof console !== 'undefined') {
|
||||
console && console.error && console.error(msg);
|
||||
}
|
||||
};
|
||||
if (!echarts) {
|
||||
log('ECharts is not Loaded');
|
||||
return;
|
||||
}
|
||||
|
||||
var colorPalette = [
|
||||
'#1790cf',
|
||||
'#1bb2d8',
|
||||
'#99d2dd',
|
||||
'#88b0bb',
|
||||
'#1c7099',
|
||||
'#038cc4',
|
||||
'#75abd0',
|
||||
'#afd6dd'
|
||||
];
|
||||
|
||||
var theme = {
|
||||
color: colorPalette,
|
||||
|
||||
title: {
|
||||
textStyle: {
|
||||
fontWeight: 'normal',
|
||||
color: '#1790cf'
|
||||
}
|
||||
},
|
||||
|
||||
visualMap: {
|
||||
color: ['#1790cf', '#a2d4e6']
|
||||
},
|
||||
|
||||
toolbox: {
|
||||
color: ['#1790cf', '#1790cf', '#1790cf', '#1790cf']
|
||||
},
|
||||
|
||||
tooltip: {
|
||||
backgroundColor: 'rgba(0,0,0,0.5)',
|
||||
axisPointer: {
|
||||
// Axis indicator, coordinate trigger effective
|
||||
type: 'line', // The default is a straight line: 'line' | 'shadow'
|
||||
lineStyle: {
|
||||
// Straight line indicator style settings
|
||||
color: '#1790cf',
|
||||
type: 'dashed'
|
||||
},
|
||||
crossStyle: {
|
||||
color: '#1790cf'
|
||||
},
|
||||
shadowStyle: {
|
||||
// Shadow indicator style settings
|
||||
color: 'rgba(200,200,200,0.3)'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Area scaling controller
|
||||
dataZoom: {
|
||||
dataBackgroundColor: '#eee', // Data background color
|
||||
fillerColor: 'rgba(144,197,237,0.2)', // Fill the color
|
||||
handleColor: '#1790cf' // Handle color
|
||||
},
|
||||
|
||||
timeline: {
|
||||
lineStyle: {
|
||||
color: '#1790cfa'
|
||||
},
|
||||
controlStyle: {
|
||||
color: '#1790cf',
|
||||
borderColor: '#1790cf'
|
||||
}
|
||||
},
|
||||
|
||||
candlestick: {
|
||||
itemStyle: {
|
||||
color: '#1bb2d8',
|
||||
color0: '#99d2dd'
|
||||
},
|
||||
lineStyle: {
|
||||
width: 1,
|
||||
color: '#1c7099',
|
||||
color0: '#88b0bb'
|
||||
},
|
||||
areaStyle: {
|
||||
color: '#1790cf',
|
||||
color0: '#1bb2d8'
|
||||
}
|
||||
},
|
||||
|
||||
chord: {
|
||||
padding: 4,
|
||||
itemStyle: {
|
||||
color: '#1bb2d8',
|
||||
borderWidth: 1,
|
||||
borderColor: 'rgba(128, 128, 128, 0.5)'
|
||||
},
|
||||
lineStyle: {
|
||||
color: 'rgba(128, 128, 128, 0.5)'
|
||||
},
|
||||
areaStyle: {
|
||||
color: '#1790cf'
|
||||
}
|
||||
},
|
||||
|
||||
graph: {
|
||||
itemStyle: {
|
||||
color: '#1bb2d8'
|
||||
},
|
||||
linkStyle: {
|
||||
color: '#88b0bb'
|
||||
}
|
||||
},
|
||||
|
||||
map: {
|
||||
itemStyle: {
|
||||
color: '#ddd'
|
||||
},
|
||||
areaStyle: {
|
||||
color: '99d2dd'
|
||||
},
|
||||
label: {
|
||||
color: '#c12e34'
|
||||
}
|
||||
},
|
||||
|
||||
gauge: {
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: [
|
||||
[0.2, '#1bb2d8'],
|
||||
[0.8, '#1790cf'],
|
||||
[1, '#1c7099']
|
||||
],
|
||||
width: 8
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
echarts.registerTheme('blue', theme);
|
||||
});
|
||||
@@ -1,178 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
(function(root, factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
// AMD. Register as an anonymous module.
|
||||
define(['exports', 'echarts'], factory);
|
||||
} else if (
|
||||
typeof exports === 'object' &&
|
||||
typeof exports.nodeName !== 'string'
|
||||
) {
|
||||
// CommonJS
|
||||
factory(exports, require('echarts/lib/echarts'));
|
||||
} else {
|
||||
// Browser globals
|
||||
factory({}, root.echarts);
|
||||
}
|
||||
})(this, function(exports, echarts) {
|
||||
var log = function(msg) {
|
||||
if (typeof console !== 'undefined') {
|
||||
console && console.error && console.error(msg);
|
||||
}
|
||||
};
|
||||
if (!echarts) {
|
||||
log('ECharts is not Loaded');
|
||||
return;
|
||||
}
|
||||
|
||||
var colorPalette = [
|
||||
'#fad089',
|
||||
'#ff9c5b',
|
||||
'#f5634a',
|
||||
'#ed303c',
|
||||
'#3b8183',
|
||||
'#f7826e',
|
||||
'#faac9e',
|
||||
'#fcd5cf'
|
||||
];
|
||||
|
||||
var theme = {
|
||||
color: colorPalette,
|
||||
|
||||
title: {
|
||||
textStyle: {
|
||||
fontWeight: 'normal',
|
||||
color: '#fad089'
|
||||
}
|
||||
},
|
||||
|
||||
visualMap: {
|
||||
color: ['#fad089', '#a2d4e6']
|
||||
},
|
||||
|
||||
toolbox: {
|
||||
color: ['#fad089', '#fad089', '#fad089', '#fad089']
|
||||
},
|
||||
|
||||
tooltip: {
|
||||
backgroundColor: 'rgba(0,0,0,0.5)',
|
||||
axisPointer: {
|
||||
// Axis indicator, coordinate trigger effective
|
||||
type: 'line', // The default is a straight line: 'line' | 'shadow'
|
||||
lineStyle: {
|
||||
// Straight line indicator style settings
|
||||
color: '#fad089',
|
||||
type: 'dashed'
|
||||
},
|
||||
crossStyle: {
|
||||
color: '#fad089'
|
||||
},
|
||||
shadowStyle: {
|
||||
// Shadow indicator style settings
|
||||
color: 'rgba(200,200,200,0.3)'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Area scaling controller
|
||||
dataZoom: {
|
||||
dataBackgroundColor: '#eee', // Data background color
|
||||
fillerColor: 'rgba(144,197,237,0.2)', // Fill the color
|
||||
handleColor: '#fad089' // Handle color
|
||||
},
|
||||
|
||||
timeline: {
|
||||
lineStyle: {
|
||||
color: '#fad089'
|
||||
},
|
||||
controlStyle: {
|
||||
color: '#fad089',
|
||||
borderColor: '#fad089'
|
||||
}
|
||||
},
|
||||
|
||||
candlestick: {
|
||||
itemStyle: {
|
||||
color: '#ff9c5b',
|
||||
color0: '#f5634a'
|
||||
},
|
||||
lineStyle: {
|
||||
width: 1,
|
||||
color: '#3b8183',
|
||||
color0: '#ed303c'
|
||||
},
|
||||
areaStyle: {
|
||||
color: '#fad089',
|
||||
color0: '#ed303c'
|
||||
}
|
||||
},
|
||||
|
||||
chord: {
|
||||
padding: 4,
|
||||
itemStyle: {
|
||||
color: '#fad089',
|
||||
borderWidth: 1,
|
||||
borderColor: 'rgba(128, 128, 128, 0.5)'
|
||||
},
|
||||
lineStyle: {
|
||||
color: 'rgba(128, 128, 128, 0.5)'
|
||||
},
|
||||
areaStyle: {
|
||||
color: '#ed303c'
|
||||
}
|
||||
},
|
||||
|
||||
map: {
|
||||
itemStyle: {
|
||||
color: '#ddd'
|
||||
},
|
||||
areaStyle: {
|
||||
color: '#f5634a'
|
||||
},
|
||||
label: {
|
||||
color: '#c12e34'
|
||||
}
|
||||
},
|
||||
|
||||
graph: {
|
||||
itemStyle: {
|
||||
color: '#f5634a'
|
||||
},
|
||||
linkStyle: {
|
||||
color: '#fad089'
|
||||
}
|
||||
},
|
||||
|
||||
gauge: {
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: [
|
||||
[0.2, '#ff9c5b'],
|
||||
[0.8, '#fad089'],
|
||||
[1, '#3b8183']
|
||||
],
|
||||
width: 8
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
echarts.registerTheme('caravan', theme);
|
||||
});
|
||||
@@ -1,163 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
(function(root, factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
// AMD. Register as an anonymous module.
|
||||
define(['exports', 'echarts'], factory);
|
||||
} else if (
|
||||
typeof exports === 'object' &&
|
||||
typeof exports.nodeName !== 'string'
|
||||
) {
|
||||
// CommonJS
|
||||
factory(exports, require('echarts/lib/echarts'));
|
||||
} else {
|
||||
// Browser globals
|
||||
factory({}, root.echarts);
|
||||
}
|
||||
})(this, function(exports, echarts) {
|
||||
var log = function(msg) {
|
||||
if (typeof console !== 'undefined') {
|
||||
console && console.error && console.error(msg);
|
||||
}
|
||||
};
|
||||
if (!echarts) {
|
||||
log('ECharts is not Loaded');
|
||||
return;
|
||||
}
|
||||
|
||||
var colorPalette = [
|
||||
'#f0d8A8',
|
||||
'#3d1c00',
|
||||
'#86b8b1',
|
||||
'#f2d694',
|
||||
'#fa2a00',
|
||||
'#ff8066',
|
||||
'#ffd5cc',
|
||||
'#f9edd2'
|
||||
];
|
||||
|
||||
var theme = {
|
||||
color: colorPalette,
|
||||
|
||||
title: {
|
||||
textStyle: {
|
||||
fontWeight: 'normal',
|
||||
color: '#f0d8A8'
|
||||
}
|
||||
},
|
||||
|
||||
visualMap: {
|
||||
color: ['#f0d8A8', '#3d1c00']
|
||||
},
|
||||
|
||||
toolbox: {
|
||||
color: ['#f0d8A8', '#f0d8A8', '#f0d8A8', '#f0d8A8']
|
||||
},
|
||||
|
||||
tooltip: {
|
||||
backgroundColor: 'rgba(0,0,0,0.5)',
|
||||
axisPointer: {
|
||||
// Axis indicator, coordinate trigger effective
|
||||
type: 'line', // The default is a straight line: 'line' | 'shadow'
|
||||
lineStyle: {
|
||||
// Straight line indicator style settings
|
||||
color: '#f0d8A8',
|
||||
type: 'dashed'
|
||||
},
|
||||
crossStyle: {
|
||||
color: '#f0d8A8'
|
||||
},
|
||||
shadowStyle: {
|
||||
// Shadow indicator style settings
|
||||
color: 'rgba(200,200,200,0.3)'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Area scaling controller
|
||||
dataZoom: {
|
||||
dataBackgroundColor: '#eee', // Data background color
|
||||
fillerColor: 'rgba(200,200,200,0.2)', // Fill the color
|
||||
handleColor: '#f0d8A8' // Handle color
|
||||
},
|
||||
|
||||
timeline: {
|
||||
lineStyle: {
|
||||
color: '#f0dba8'
|
||||
},
|
||||
controlStyle: {
|
||||
color: '#f0dba8',
|
||||
borderColor: '#f0dba8'
|
||||
}
|
||||
},
|
||||
|
||||
candlestick: {
|
||||
itemStyle: {
|
||||
color: '#3d1c00',
|
||||
color0: '#86b8b1'
|
||||
},
|
||||
lineStyle: {
|
||||
width: 1,
|
||||
color: '#fa2a00',
|
||||
color0: '#f2d694'
|
||||
},
|
||||
areaStyle: {
|
||||
color: '#f0d8A8',
|
||||
color0: '#86b8b1'
|
||||
}
|
||||
},
|
||||
|
||||
map: {
|
||||
itemStyle: {
|
||||
color: '#ddd'
|
||||
},
|
||||
areaStyle: {
|
||||
color: '#86b8b1'
|
||||
},
|
||||
label: {
|
||||
color: '#c12e34'
|
||||
}
|
||||
},
|
||||
|
||||
graph: {
|
||||
itemStyle: {
|
||||
color: '#3d1c00'
|
||||
},
|
||||
linkStyle: {
|
||||
color: '#f0d8A8'
|
||||
}
|
||||
},
|
||||
|
||||
gauge: {
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: [
|
||||
[0.2, '#3d1c00'],
|
||||
[0.8, '#f0d8A8'],
|
||||
[1, '#fa2a00']
|
||||
],
|
||||
width: 8
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
echarts.registerTheme('carp', theme);
|
||||
});
|
||||
@@ -1,180 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
(function(root, factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
// AMD. Register as an anonymous module.
|
||||
define(['exports', 'echarts'], factory);
|
||||
} else if (
|
||||
typeof exports === 'object' &&
|
||||
typeof exports.nodeName !== 'string'
|
||||
) {
|
||||
// CommonJS
|
||||
factory(exports, require('echarts/lib/echarts'));
|
||||
} else {
|
||||
// Browser globals
|
||||
factory({}, root.echarts);
|
||||
}
|
||||
})(this, function(exports, echarts) {
|
||||
var log = function(msg) {
|
||||
if (typeof console !== 'undefined') {
|
||||
console && console.error && console.error(msg);
|
||||
}
|
||||
};
|
||||
if (!echarts) {
|
||||
log('ECharts is not Loaded');
|
||||
return;
|
||||
}
|
||||
|
||||
var colorPalette = [
|
||||
'#b21ab4',
|
||||
'#6f0099',
|
||||
'#2a2073',
|
||||
'#0b5ea8',
|
||||
'#17aecc',
|
||||
'#b3b3ff',
|
||||
'#eb99ff',
|
||||
'#fae6ff',
|
||||
'#e6f2ff',
|
||||
'#eeeeee'
|
||||
];
|
||||
|
||||
var theme = {
|
||||
color: colorPalette,
|
||||
|
||||
title: {
|
||||
textStyle: {
|
||||
fontWeight: 'normal',
|
||||
color: '#00aecd'
|
||||
}
|
||||
},
|
||||
|
||||
visualMap: {
|
||||
color: ['#00aecd', '#a2d4e6']
|
||||
},
|
||||
|
||||
toolbox: {
|
||||
color: ['#00aecd', '#00aecd', '#00aecd', '#00aecd']
|
||||
},
|
||||
|
||||
tooltip: {
|
||||
backgroundColor: 'rgba(0,0,0,0.5)',
|
||||
axisPointer: {
|
||||
// Axis indicator, coordinate trigger effective
|
||||
type: 'line', // The default is a straight line: 'line' | 'shadow'
|
||||
lineStyle: {
|
||||
// Straight line indicator style settings
|
||||
color: '#00aecd',
|
||||
type: 'dashed'
|
||||
},
|
||||
crossStyle: {
|
||||
color: '#00aecd'
|
||||
},
|
||||
shadowStyle: {
|
||||
// Shadow indicator style settings
|
||||
color: 'rgba(200,200,200,0.3)'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Area scaling controller
|
||||
dataZoom: {
|
||||
dataBackgroundColor: '#eee', // Data background color
|
||||
fillerColor: 'rgba(144,197,237,0.2)', // Fill the color
|
||||
handleColor: '#00aecd' // Handle color
|
||||
},
|
||||
|
||||
timeline: {
|
||||
lineStyle: {
|
||||
color: '#00aecd'
|
||||
},
|
||||
controlStyle: {
|
||||
color: '#00aecd',
|
||||
borderColor: '00aecd'
|
||||
}
|
||||
},
|
||||
|
||||
candlestick: {
|
||||
itemStyle: {
|
||||
color: '#00aecd',
|
||||
color0: '#a2d4e6'
|
||||
},
|
||||
lineStyle: {
|
||||
width: 1,
|
||||
color: '#00aecd',
|
||||
color0: '#a2d4e6'
|
||||
},
|
||||
areaStyle: {
|
||||
color: '#b21ab4',
|
||||
color0: '#0b5ea8'
|
||||
}
|
||||
},
|
||||
|
||||
chord: {
|
||||
padding: 4,
|
||||
itemStyle: {
|
||||
color: '#b21ab4',
|
||||
borderWidth: 1,
|
||||
borderColor: 'rgba(128, 128, 128, 0.5)'
|
||||
},
|
||||
lineStyle: {
|
||||
color: 'rgba(128, 128, 128, 0.5)'
|
||||
},
|
||||
areaStyle: {
|
||||
color: '#0b5ea8'
|
||||
}
|
||||
},
|
||||
|
||||
graph: {
|
||||
itemStyle: {
|
||||
color: '#b21ab4'
|
||||
},
|
||||
linkStyle: {
|
||||
color: '#2a2073'
|
||||
}
|
||||
},
|
||||
|
||||
map: {
|
||||
itemStyle: {
|
||||
color: '#c12e34'
|
||||
},
|
||||
areaStyle: {
|
||||
color: '#ddd'
|
||||
},
|
||||
label: {
|
||||
color: '#c12e34'
|
||||
}
|
||||
},
|
||||
|
||||
gauge: {
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: [
|
||||
[0.2, '#dddddd'],
|
||||
[0.8, '#00aecd'],
|
||||
[1, '#f5ccff']
|
||||
],
|
||||
width: 8
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
echarts.registerTheme('cool', theme);
|
||||
});
|
||||
@@ -1,164 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
(function(root, factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
// AMD. Register as an anonymous module.
|
||||
define(['exports', 'echarts'], factory);
|
||||
} else if (
|
||||
typeof exports === 'object' &&
|
||||
typeof exports.nodeName !== 'string'
|
||||
) {
|
||||
// CommonJS
|
||||
factory(exports, require('echarts/lib/echarts'));
|
||||
} else {
|
||||
// Browser globals
|
||||
factory({}, root.echarts);
|
||||
}
|
||||
})(this, function(exports, echarts) {
|
||||
var log = function(msg) {
|
||||
if (typeof console !== 'undefined') {
|
||||
console && console.error && console.error(msg);
|
||||
}
|
||||
};
|
||||
if (!echarts) {
|
||||
log('ECharts is not Loaded');
|
||||
return;
|
||||
}
|
||||
var contrastColor = '#eee';
|
||||
var axisCommon = function() {
|
||||
return {
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: contrastColor
|
||||
}
|
||||
},
|
||||
axisTick: {
|
||||
lineStyle: {
|
||||
color: contrastColor
|
||||
}
|
||||
},
|
||||
axisLabel: {
|
||||
color: contrastColor
|
||||
},
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
type: 'dashed',
|
||||
color: '#aaa'
|
||||
}
|
||||
},
|
||||
splitArea: {
|
||||
areaStyle: {
|
||||
color: contrastColor
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
var colorPalette = [
|
||||
'#00305a',
|
||||
'#004b8d',
|
||||
'#0074d9',
|
||||
'#4192d9',
|
||||
'#7abaf2',
|
||||
'#99cce6',
|
||||
'#d6ebf5',
|
||||
'#eeeeee'
|
||||
];
|
||||
var theme = {
|
||||
color: colorPalette,
|
||||
backgroundColor: '#333',
|
||||
tooltip: {
|
||||
axisPointer: {
|
||||
lineStyle: {
|
||||
color: contrastColor
|
||||
},
|
||||
crossStyle: {
|
||||
color: contrastColor
|
||||
}
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
textStyle: {
|
||||
color: contrastColor
|
||||
}
|
||||
},
|
||||
title: {
|
||||
textStyle: {
|
||||
color: contrastColor
|
||||
}
|
||||
},
|
||||
toolbox: {
|
||||
iconStyle: {
|
||||
borderColor: contrastColor
|
||||
}
|
||||
},
|
||||
|
||||
// Area scaling controller
|
||||
dataZoom: {
|
||||
dataBackgroundColor: '#eee', // Data background color
|
||||
fillerColor: 'rgba(200,200,200,0.2)', // Fill the color
|
||||
handleColor: '#00305a' // Handle color
|
||||
},
|
||||
|
||||
timeline: {
|
||||
itemStyle: {
|
||||
color: colorPalette[1]
|
||||
},
|
||||
lineStyle: {
|
||||
color: contrastColor
|
||||
},
|
||||
controlStyle: {
|
||||
color: contrastColor,
|
||||
borderColor: contrastColor
|
||||
},
|
||||
label: {
|
||||
color: contrastColor
|
||||
}
|
||||
},
|
||||
|
||||
timeAxis: axisCommon(),
|
||||
logAxis: axisCommon(),
|
||||
valueAxis: axisCommon(),
|
||||
categoryAxis: axisCommon(),
|
||||
|
||||
line: {
|
||||
symbol: 'circle'
|
||||
},
|
||||
graph: {
|
||||
color: colorPalette
|
||||
},
|
||||
|
||||
gauge: {
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: [
|
||||
[0.2, '#004b8d'],
|
||||
[0.8, '#00305a'],
|
||||
[1, '#7abaf2']
|
||||
],
|
||||
width: 8
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
theme.categoryAxis.splitLine.show = false;
|
||||
echarts.registerTheme('dark-blue', theme);
|
||||
});
|
||||
@@ -1,164 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
(function(root, factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
// AMD. Register as an anonymous module.
|
||||
define(['exports', 'echarts'], factory);
|
||||
} else if (
|
||||
typeof exports === 'object' &&
|
||||
typeof exports.nodeName !== 'string'
|
||||
) {
|
||||
// CommonJS
|
||||
factory(exports, require('echarts/lib/echarts'));
|
||||
} else {
|
||||
// Browser globals
|
||||
factory({}, root.echarts);
|
||||
}
|
||||
})(this, function(exports, echarts) {
|
||||
var log = function(msg) {
|
||||
if (typeof console !== 'undefined') {
|
||||
console && console.error && console.error(msg);
|
||||
}
|
||||
};
|
||||
if (!echarts) {
|
||||
log('ECharts is not Loaded');
|
||||
return;
|
||||
}
|
||||
var contrastColor = '#eee';
|
||||
var axisCommon = function() {
|
||||
return {
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: contrastColor
|
||||
}
|
||||
},
|
||||
axisTick: {
|
||||
lineStyle: {
|
||||
color: contrastColor
|
||||
}
|
||||
},
|
||||
axisLabel: {
|
||||
color: contrastColor
|
||||
},
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
type: 'dashed',
|
||||
color: '#aaa'
|
||||
}
|
||||
},
|
||||
splitArea: {
|
||||
areaStyle: {
|
||||
color: contrastColor
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
var colorPalette = [
|
||||
'#458c6b',
|
||||
'#f2da87',
|
||||
'#d9a86c',
|
||||
'#d94436',
|
||||
'#a62424',
|
||||
'#76bc9b',
|
||||
'#cce6da',
|
||||
'#eeeeee'
|
||||
];
|
||||
var theme = {
|
||||
color: colorPalette,
|
||||
backgroundColor: '#333',
|
||||
tooltip: {
|
||||
axisPointer: {
|
||||
lineStyle: {
|
||||
color: contrastColor
|
||||
},
|
||||
crossStyle: {
|
||||
color: contrastColor
|
||||
}
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
textStyle: {
|
||||
color: contrastColor
|
||||
}
|
||||
},
|
||||
title: {
|
||||
textStyle: {
|
||||
color: contrastColor
|
||||
}
|
||||
},
|
||||
toolbox: {
|
||||
iconStyle: {
|
||||
borderColor: contrastColor
|
||||
}
|
||||
},
|
||||
|
||||
// Area scaling controller
|
||||
dataZoom: {
|
||||
dataBackgroundColor: '#eee', // Data background color
|
||||
fillerColor: 'rgba(200,200,200,0.2)', // Fill the color
|
||||
handleColor: '#458c6b' // Handle color
|
||||
},
|
||||
|
||||
timeline: {
|
||||
itemStyle: {
|
||||
color: colorPalette[1]
|
||||
},
|
||||
lineStyle: {
|
||||
color: contrastColor
|
||||
},
|
||||
controlStyle: {
|
||||
color: contrastColor,
|
||||
borderColor: contrastColor
|
||||
},
|
||||
label: {
|
||||
color: contrastColor
|
||||
}
|
||||
},
|
||||
|
||||
timeAxis: axisCommon(),
|
||||
logAxis: axisCommon(),
|
||||
valueAxis: axisCommon(),
|
||||
categoryAxis: axisCommon(),
|
||||
|
||||
line: {
|
||||
symbol: 'circle'
|
||||
},
|
||||
graph: {
|
||||
color: colorPalette
|
||||
},
|
||||
|
||||
gauge: {
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: [
|
||||
[0.2, '#f2da87'],
|
||||
[0.8, '#458c6b'],
|
||||
[1, '#a62424']
|
||||
],
|
||||
width: 8
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
theme.categoryAxis.splitLine.show = false;
|
||||
echarts.registerTheme('dark-bold', theme);
|
||||
});
|
||||
@@ -1,164 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
(function(root, factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
// AMD. Register as an anonymous module.
|
||||
define(['exports', 'echarts'], factory);
|
||||
} else if (
|
||||
typeof exports === 'object' &&
|
||||
typeof exports.nodeName !== 'string'
|
||||
) {
|
||||
// CommonJS
|
||||
factory(exports, require('echarts/lib/echarts'));
|
||||
} else {
|
||||
// Browser globals
|
||||
factory({}, root.echarts);
|
||||
}
|
||||
})(this, function(exports, echarts) {
|
||||
var log = function(msg) {
|
||||
if (typeof console !== 'undefined') {
|
||||
console && console.error && console.error(msg);
|
||||
}
|
||||
};
|
||||
if (!echarts) {
|
||||
log('ECharts is not Loaded');
|
||||
return;
|
||||
}
|
||||
var contrastColor = '#eee';
|
||||
var axisCommon = function() {
|
||||
return {
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: contrastColor
|
||||
}
|
||||
},
|
||||
axisTick: {
|
||||
lineStyle: {
|
||||
color: contrastColor
|
||||
}
|
||||
},
|
||||
axisLabel: {
|
||||
color: contrastColor
|
||||
},
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
type: 'dashed',
|
||||
color: '#aaa'
|
||||
}
|
||||
},
|
||||
splitArea: {
|
||||
areaStyle: {
|
||||
color: contrastColor
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
var colorPalette = [
|
||||
'#52656b',
|
||||
'#ff3b77',
|
||||
'#a3cc00',
|
||||
'#ffffff',
|
||||
'#b8b89f',
|
||||
'#ffccdb',
|
||||
'#e5ff80',
|
||||
'#f4f4f0'
|
||||
];
|
||||
var theme = {
|
||||
color: colorPalette,
|
||||
backgroundColor: '#333',
|
||||
tooltip: {
|
||||
axisPointer: {
|
||||
lineStyle: {
|
||||
color: contrastColor
|
||||
},
|
||||
crossStyle: {
|
||||
color: contrastColor
|
||||
}
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
textStyle: {
|
||||
color: contrastColor
|
||||
}
|
||||
},
|
||||
title: {
|
||||
textStyle: {
|
||||
color: contrastColor
|
||||
}
|
||||
},
|
||||
toolbox: {
|
||||
iconStyle: {
|
||||
borderColor: contrastColor
|
||||
}
|
||||
},
|
||||
|
||||
// Area scaling controller
|
||||
dataZoom: {
|
||||
dataBackgroundColor: '#eee', // Data background color
|
||||
fillerColor: 'rgba(200,200,200,0.2)', // Fill the color
|
||||
handleColor: '#52656b' // Handle color
|
||||
},
|
||||
|
||||
timeline: {
|
||||
itemStyle: {
|
||||
color: colorPalette[1]
|
||||
},
|
||||
lineStyle: {
|
||||
color: contrastColor
|
||||
},
|
||||
controlStyle: {
|
||||
color: contrastColor,
|
||||
borderColor: contrastColor
|
||||
},
|
||||
label: {
|
||||
color: contrastColor
|
||||
}
|
||||
},
|
||||
|
||||
timeAxis: axisCommon(),
|
||||
logAxis: axisCommon(),
|
||||
valueAxis: axisCommon(),
|
||||
categoryAxis: axisCommon(),
|
||||
|
||||
line: {
|
||||
symbol: 'circle'
|
||||
},
|
||||
graph: {
|
||||
color: colorPalette
|
||||
},
|
||||
|
||||
gauge: {
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: [
|
||||
[0.2, '#ff3b77'],
|
||||
[0.8, '#52656b'],
|
||||
[1, '#b8b89f']
|
||||
],
|
||||
width: 8
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
theme.categoryAxis.splitLine.show = false;
|
||||
echarts.registerTheme('dark-digerati', theme);
|
||||
});
|
||||
@@ -1,164 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
(function(root, factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
// AMD. Register as an anonymous module.
|
||||
define(['exports', 'echarts'], factory);
|
||||
} else if (
|
||||
typeof exports === 'object' &&
|
||||
typeof exports.nodeName !== 'string'
|
||||
) {
|
||||
// CommonJS
|
||||
factory(exports, require('echarts/lib/echarts'));
|
||||
} else {
|
||||
// Browser globals
|
||||
factory({}, root.echarts);
|
||||
}
|
||||
})(this, function(exports, echarts) {
|
||||
var log = function(msg) {
|
||||
if (typeof console !== 'undefined') {
|
||||
console && console.error && console.error(msg);
|
||||
}
|
||||
};
|
||||
if (!echarts) {
|
||||
log('ECharts is not Loaded');
|
||||
return;
|
||||
}
|
||||
var contrastColor = '#eee';
|
||||
var axisCommon = function() {
|
||||
return {
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: contrastColor
|
||||
}
|
||||
},
|
||||
axisTick: {
|
||||
lineStyle: {
|
||||
color: contrastColor
|
||||
}
|
||||
},
|
||||
axisLabel: {
|
||||
color: contrastColor
|
||||
},
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
type: 'dashed',
|
||||
color: '#aaa'
|
||||
}
|
||||
},
|
||||
splitArea: {
|
||||
areaStyle: {
|
||||
color: contrastColor
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
var colorPalette = [
|
||||
'#00a8c6',
|
||||
'#40c0cb',
|
||||
'#ebd3ad',
|
||||
'#aee239',
|
||||
'#8fbe00',
|
||||
'#33e0ff',
|
||||
'#b3f4ff',
|
||||
'#e6ff99'
|
||||
];
|
||||
var theme = {
|
||||
color: colorPalette,
|
||||
backgroundColor: '#333',
|
||||
tooltip: {
|
||||
axisPointer: {
|
||||
lineStyle: {
|
||||
color: contrastColor
|
||||
},
|
||||
crossStyle: {
|
||||
color: contrastColor
|
||||
}
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
textStyle: {
|
||||
color: contrastColor
|
||||
}
|
||||
},
|
||||
title: {
|
||||
textStyle: {
|
||||
color: contrastColor
|
||||
}
|
||||
},
|
||||
toolbox: {
|
||||
iconStyle: {
|
||||
borderColor: contrastColor
|
||||
}
|
||||
},
|
||||
|
||||
// Area scaling controller
|
||||
dataZoom: {
|
||||
dataBackgroundColor: '#eee', // Data background color
|
||||
fillerColor: 'rgba(200,200,200,0.2)', // Fill the color
|
||||
handleColor: '#00a8c6' // Handle color
|
||||
},
|
||||
|
||||
timeline: {
|
||||
itemStyle: {
|
||||
color: colorPalette[1]
|
||||
},
|
||||
lineStyle: {
|
||||
color: contrastColor
|
||||
},
|
||||
controlStyle: {
|
||||
color: contrastColor,
|
||||
borderColor: contrastColor
|
||||
},
|
||||
label: {
|
||||
color: contrastColor
|
||||
}
|
||||
},
|
||||
|
||||
timeAxis: axisCommon(),
|
||||
logAxis: axisCommon(),
|
||||
valueAxis: axisCommon(),
|
||||
categoryAxis: axisCommon(),
|
||||
|
||||
line: {
|
||||
symbol: 'circle'
|
||||
},
|
||||
graph: {
|
||||
color: colorPalette
|
||||
},
|
||||
|
||||
gauge: {
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: [
|
||||
[0.2, '#40c0cb'],
|
||||
[0.8, '#00a8c6'],
|
||||
[1, '#8fbe00']
|
||||
],
|
||||
width: 8
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
theme.categoryAxis.splitLine.show = false;
|
||||
echarts.registerTheme('dark-fresh-cut', theme);
|
||||
});
|
||||
@@ -1,164 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
(function(root, factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
// AMD. Register as an anonymous module.
|
||||
define(['exports', 'echarts'], factory);
|
||||
} else if (
|
||||
typeof exports === 'object' &&
|
||||
typeof exports.nodeName !== 'string'
|
||||
) {
|
||||
// CommonJS
|
||||
factory(exports, require('echarts/lib/echarts'));
|
||||
} else {
|
||||
// Browser globals
|
||||
factory({}, root.echarts);
|
||||
}
|
||||
})(this, function(exports, echarts) {
|
||||
var log = function(msg) {
|
||||
if (typeof console !== 'undefined') {
|
||||
console && console.error && console.error(msg);
|
||||
}
|
||||
};
|
||||
if (!echarts) {
|
||||
log('ECharts is not Loaded');
|
||||
return;
|
||||
}
|
||||
var contrastColor = '#eee';
|
||||
var axisCommon = function() {
|
||||
return {
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: contrastColor
|
||||
}
|
||||
},
|
||||
axisTick: {
|
||||
lineStyle: {
|
||||
color: contrastColor
|
||||
}
|
||||
},
|
||||
axisLabel: {
|
||||
color: contrastColor
|
||||
},
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
type: 'dashed',
|
||||
color: '#aaa'
|
||||
}
|
||||
},
|
||||
splitArea: {
|
||||
areaStyle: {
|
||||
color: contrastColor
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
var colorPalette = [
|
||||
'#cc0e00',
|
||||
'#ff1a0a',
|
||||
'#ff8880',
|
||||
'#ffc180',
|
||||
'#ffc2b0',
|
||||
'#ffffff',
|
||||
'#ff8880',
|
||||
'#ffe6e6'
|
||||
];
|
||||
var theme = {
|
||||
color: colorPalette,
|
||||
backgroundColor: '#333',
|
||||
tooltip: {
|
||||
axisPointer: {
|
||||
lineStyle: {
|
||||
color: contrastColor
|
||||
},
|
||||
crossStyle: {
|
||||
color: contrastColor
|
||||
}
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
textStyle: {
|
||||
color: contrastColor
|
||||
}
|
||||
},
|
||||
title: {
|
||||
textStyle: {
|
||||
color: contrastColor
|
||||
}
|
||||
},
|
||||
toolbox: {
|
||||
iconStyle: {
|
||||
borderColor: contrastColor
|
||||
}
|
||||
},
|
||||
|
||||
// Area scaling controller
|
||||
dataZoom: {
|
||||
dataBackgroundColor: '#eee', // Data background color
|
||||
fillerColor: 'rgba(200,200,200,0.2)', // Fill the color
|
||||
handleColor: '#cc0e00' // Handle color
|
||||
},
|
||||
|
||||
timeline: {
|
||||
itemStyle: {
|
||||
color: colorPalette[1]
|
||||
},
|
||||
lineStyle: {
|
||||
color: contrastColor
|
||||
},
|
||||
controlStyle: {
|
||||
color: contrastColor,
|
||||
borderColor: contrastColor
|
||||
},
|
||||
label: {
|
||||
color: contrastColor
|
||||
}
|
||||
},
|
||||
|
||||
timeAxis: axisCommon(),
|
||||
logAxis: axisCommon(),
|
||||
valueAxis: axisCommon(),
|
||||
categoryAxis: axisCommon(),
|
||||
|
||||
line: {
|
||||
symbol: 'circle'
|
||||
},
|
||||
graph: {
|
||||
color: colorPalette
|
||||
},
|
||||
|
||||
gauge: {
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: [
|
||||
[0.2, '#ff1a0a'],
|
||||
[0.8, '#cc0e00'],
|
||||
[1, '#ffc2b0']
|
||||
],
|
||||
width: 8
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
theme.categoryAxis.splitLine.show = false;
|
||||
echarts.registerTheme('dark-mushroom', theme);
|
||||
});
|
||||
@@ -1,224 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
(function(root, factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
// AMD. Register as an anonymous module.
|
||||
define(['exports', 'echarts'], factory);
|
||||
} else if (
|
||||
typeof exports === 'object' &&
|
||||
typeof exports.nodeName !== 'string'
|
||||
) {
|
||||
// CommonJS
|
||||
factory(exports, require('echarts/lib/echarts'));
|
||||
} else {
|
||||
// Browser globals
|
||||
factory({}, root.echarts);
|
||||
}
|
||||
})(this, function(exports, echarts) {
|
||||
var log = function(msg) {
|
||||
if (typeof console !== 'undefined') {
|
||||
console && console.error && console.error(msg);
|
||||
}
|
||||
};
|
||||
if (!echarts) {
|
||||
log('ECharts is not Loaded');
|
||||
return;
|
||||
}
|
||||
var contrastColor = '#B9B8CE';
|
||||
var backgroundColor = '#100C2A';
|
||||
var axisCommon = function () {
|
||||
return {
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: contrastColor
|
||||
}
|
||||
},
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
color: '#484753'
|
||||
}
|
||||
},
|
||||
splitArea: {
|
||||
areaStyle: {
|
||||
color: ['rgba(255,255,255,0.02)', 'rgba(255,255,255,0.05)']
|
||||
}
|
||||
},
|
||||
minorSplitLine: {
|
||||
lineStyle: {
|
||||
color: '#20203B'
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
var colorPalette = [
|
||||
'#4992ff',
|
||||
'#7cffb2',
|
||||
'#fddd60',
|
||||
'#ff6e76',
|
||||
'#58d9f9',
|
||||
'#05c091',
|
||||
'#ff8a45',
|
||||
'#8d48e3',
|
||||
'#dd79ff'
|
||||
];
|
||||
var theme = {
|
||||
darkMode: true,
|
||||
|
||||
color: colorPalette,
|
||||
backgroundColor: backgroundColor,
|
||||
axisPointer: {
|
||||
lineStyle: {
|
||||
color: '#817f91'
|
||||
},
|
||||
crossStyle: {
|
||||
color: '#817f91'
|
||||
},
|
||||
label: {
|
||||
// TODO Contrast of label backgorundColor
|
||||
color: '#fff'
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
textStyle: {
|
||||
color: contrastColor
|
||||
}
|
||||
},
|
||||
textStyle: {
|
||||
color: contrastColor
|
||||
},
|
||||
title: {
|
||||
textStyle: {
|
||||
color: '#EEF1FA'
|
||||
},
|
||||
subtextStyle: {
|
||||
color: '#B9B8CE'
|
||||
}
|
||||
},
|
||||
toolbox: {
|
||||
iconStyle: {
|
||||
borderColor: contrastColor
|
||||
}
|
||||
},
|
||||
dataZoom: {
|
||||
borderColor: '#71708A',
|
||||
textStyle: {
|
||||
color: contrastColor
|
||||
},
|
||||
brushStyle: {
|
||||
color: 'rgba(135,163,206,0.3)'
|
||||
},
|
||||
handleStyle: {
|
||||
color: '#353450',
|
||||
borderColor: '#C5CBE3'
|
||||
},
|
||||
moveHandleStyle: {
|
||||
color: '#B0B6C3',
|
||||
opacity: 0.3
|
||||
},
|
||||
fillerColor: 'rgba(135,163,206,0.2)',
|
||||
emphasis: {
|
||||
handleStyle: {
|
||||
borderColor: '#91B7F2',
|
||||
color: '#4D587D'
|
||||
},
|
||||
moveHandleStyle: {
|
||||
color: '#636D9A',
|
||||
opacity: 0.7
|
||||
}
|
||||
},
|
||||
dataBackground: {
|
||||
lineStyle: {
|
||||
color: '#71708A',
|
||||
width: 1
|
||||
},
|
||||
areaStyle: {
|
||||
color: '#71708A'
|
||||
}
|
||||
},
|
||||
selectedDataBackground: {
|
||||
lineStyle: {
|
||||
color: '#87A3CE'
|
||||
},
|
||||
areaStyle: {
|
||||
color: '#87A3CE'
|
||||
}
|
||||
}
|
||||
},
|
||||
visualMap: {
|
||||
textStyle: {
|
||||
color: contrastColor
|
||||
}
|
||||
},
|
||||
timeline: {
|
||||
lineStyle: {
|
||||
color: contrastColor
|
||||
},
|
||||
label: {
|
||||
color: contrastColor
|
||||
},
|
||||
controlStyle: {
|
||||
color: contrastColor,
|
||||
borderColor: contrastColor
|
||||
}
|
||||
},
|
||||
calendar: {
|
||||
itemStyle: {
|
||||
color: backgroundColor
|
||||
},
|
||||
dayLabel: {
|
||||
color: contrastColor
|
||||
},
|
||||
monthLabel: {
|
||||
color: contrastColor
|
||||
},
|
||||
yearLabel: {
|
||||
color: contrastColor
|
||||
}
|
||||
},
|
||||
timeAxis: axisCommon(),
|
||||
logAxis: axisCommon(),
|
||||
valueAxis: axisCommon(),
|
||||
categoryAxis: axisCommon(),
|
||||
|
||||
line: {
|
||||
symbol: 'circle'
|
||||
},
|
||||
graph: {
|
||||
color: colorPalette
|
||||
},
|
||||
gauge: {
|
||||
title: {
|
||||
color: contrastColor
|
||||
}
|
||||
},
|
||||
candlestick: {
|
||||
itemStyle: {
|
||||
color: '#FD1050',
|
||||
color0: '#0CF49B',
|
||||
borderColor: '#FD1050',
|
||||
borderColor0: '#0CF49B'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
theme.categoryAxis.splitLine.show = false;
|
||||
echarts.registerTheme('dark', theme);
|
||||
});
|
||||
@@ -1,178 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
(function(root, factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
// AMD. Register as an anonymous module.
|
||||
define(['exports', 'echarts'], factory);
|
||||
} else if (
|
||||
typeof exports === 'object' &&
|
||||
typeof exports.nodeName !== 'string'
|
||||
) {
|
||||
// CommonJS
|
||||
factory(exports, require('echarts/lib/echarts'));
|
||||
} else {
|
||||
// Browser globals
|
||||
factory({}, root.echarts);
|
||||
}
|
||||
})(this, function(exports, echarts) {
|
||||
var log = function(msg) {
|
||||
if (typeof console !== 'undefined') {
|
||||
console && console.error && console.error(msg);
|
||||
}
|
||||
};
|
||||
if (!echarts) {
|
||||
log('ECharts is not Loaded');
|
||||
return;
|
||||
}
|
||||
|
||||
var colorPalette = [
|
||||
'#59535e',
|
||||
'#e7dcef',
|
||||
'#f1baf3',
|
||||
'#5d4970',
|
||||
'#372049',
|
||||
'#c0b2cd',
|
||||
'#ffccff',
|
||||
'#f2f0f5'
|
||||
];
|
||||
|
||||
var theme = {
|
||||
color: colorPalette,
|
||||
|
||||
title: {
|
||||
textStyle: {
|
||||
fontWeight: 'normal',
|
||||
color: '#59535e'
|
||||
}
|
||||
},
|
||||
|
||||
visualMap: {
|
||||
color: ['#59535e', '#e7dcef']
|
||||
},
|
||||
|
||||
toolbox: {
|
||||
color: ['#59535e', '#59535e', '#59535e', '#59535e']
|
||||
},
|
||||
|
||||
tooltip: {
|
||||
backgroundColor: 'rgba(0,0,0,0.5)',
|
||||
axisPointer: {
|
||||
// Axis indicator, coordinate trigger effective
|
||||
type: 'line', // The default is a straight line: 'line' | 'shadow'
|
||||
lineStyle: {
|
||||
// Straight line indicator style settings
|
||||
color: '#59535e',
|
||||
type: 'dashed'
|
||||
},
|
||||
crossStyle: {
|
||||
color: '#59535e'
|
||||
},
|
||||
shadowStyle: {
|
||||
// Shadow indicator style settings
|
||||
color: 'rgba(200,200,200,0.3)'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Area scaling controller
|
||||
dataZoom: {
|
||||
dataBackgroundColor: '#eee', // Data background color
|
||||
fillerColor: 'rgba(200,200,200,0.2)', // Fill the color
|
||||
handleColor: '#59535e' // Handle color
|
||||
},
|
||||
|
||||
timeline: {
|
||||
lineStyle: {
|
||||
color: '#59535e'
|
||||
},
|
||||
controlStyle: {
|
||||
color: '#59535e',
|
||||
borderColor: '#59535e'
|
||||
}
|
||||
},
|
||||
|
||||
candlestick: {
|
||||
itemStyle: {
|
||||
color: '#e7dcef',
|
||||
color0: '#f1baf3'
|
||||
},
|
||||
lineStyle: {
|
||||
width: 1,
|
||||
color: '#372049',
|
||||
color0: '#5d4970'
|
||||
},
|
||||
areaStyle: {
|
||||
color: '#59535e',
|
||||
color0: '#e7dcef'
|
||||
}
|
||||
},
|
||||
|
||||
chord: {
|
||||
padding: 4,
|
||||
itemStyle: {
|
||||
color: '#59535e',
|
||||
borderWidth: 1,
|
||||
borderColor: 'rgba(128, 128, 128, 0.5)'
|
||||
},
|
||||
lineStyle: {
|
||||
color: 'rgba(128, 128, 128, 0.5)'
|
||||
},
|
||||
areaStyle: {
|
||||
color: '#e7dcef'
|
||||
}
|
||||
},
|
||||
|
||||
map: {
|
||||
itemStyle: {
|
||||
color: '#ddd'
|
||||
},
|
||||
areaStyle: {
|
||||
color: '#f1baf3'
|
||||
},
|
||||
label: {
|
||||
color: '#c12e34'
|
||||
}
|
||||
},
|
||||
|
||||
graph: {
|
||||
itemStyle: {
|
||||
color: '#59535e'
|
||||
},
|
||||
linkStyle: {
|
||||
color: '#59535e'
|
||||
}
|
||||
},
|
||||
|
||||
gauge: {
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: [
|
||||
[0.2, '#e7dcef'],
|
||||
[0.8, '#59535e'],
|
||||
[1, '#372049']
|
||||
],
|
||||
width: 8
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
echarts.registerTheme('eduardo', theme);
|
||||
});
|
||||
@@ -1,163 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
(function(root, factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
// AMD. Register as an anonymous module.
|
||||
define(['exports', 'echarts'], factory);
|
||||
} else if (
|
||||
typeof exports === 'object' &&
|
||||
typeof exports.nodeName !== 'string'
|
||||
) {
|
||||
// CommonJS
|
||||
factory(exports, require('echarts/lib/echarts'));
|
||||
} else {
|
||||
// Browser globals
|
||||
factory({}, root.echarts);
|
||||
}
|
||||
})(this, function(exports, echarts) {
|
||||
var log = function(msg) {
|
||||
if (typeof console !== 'undefined') {
|
||||
console && console.error && console.error(msg);
|
||||
}
|
||||
};
|
||||
if (!echarts) {
|
||||
log('ECharts is not Loaded');
|
||||
return;
|
||||
}
|
||||
|
||||
var colorPalette = [
|
||||
'#313b23',
|
||||
'#494f2b',
|
||||
'#606233',
|
||||
'#d6b77b',
|
||||
'#0e0e0e',
|
||||
'#076278',
|
||||
'#808080',
|
||||
'#e7d5b1'
|
||||
];
|
||||
|
||||
var theme = {
|
||||
color: colorPalette,
|
||||
|
||||
title: {
|
||||
textStyle: {
|
||||
fontWeight: 'normal',
|
||||
color: '#313b23'
|
||||
}
|
||||
},
|
||||
|
||||
visualMap: {
|
||||
color: ['#313b23', '#494f2b']
|
||||
},
|
||||
|
||||
toolbox: {
|
||||
color: ['#313b23', '#313b23', '#313b23', '#313b23']
|
||||
},
|
||||
|
||||
tooltip: {
|
||||
backgroundColor: 'rgba(0,0,0,0.5)',
|
||||
axisPointer: {
|
||||
// Axis indicator, coordinate trigger effective
|
||||
type: 'line', // The default is a straight line: 'line' | 'shadow'
|
||||
lineStyle: {
|
||||
// Straight line indicator style settings
|
||||
color: '#313b23',
|
||||
type: 'dashed'
|
||||
},
|
||||
crossStyle: {
|
||||
color: '#313b23'
|
||||
},
|
||||
shadowStyle: {
|
||||
// Shadow indicator style settings
|
||||
color: 'rgba(200,200,200,0.3)'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Area scaling controller
|
||||
dataZoom: {
|
||||
dataBackgroundColor: '#eee', // Data background color
|
||||
fillerColor: 'rgba(200,200,200,0.2)', // Fill the color
|
||||
handleColor: '#313b23' // Handle color
|
||||
},
|
||||
|
||||
timeline: {
|
||||
lineStyle: {
|
||||
color: '#313b23'
|
||||
},
|
||||
controlStyle: {
|
||||
color: '#313b23',
|
||||
borderColor: '#313b23'
|
||||
}
|
||||
},
|
||||
|
||||
candlestick: {
|
||||
itemStyle: {
|
||||
color: '#494f2b',
|
||||
color0: '#606233'
|
||||
},
|
||||
lineStyle: {
|
||||
width: 1,
|
||||
color: '#0e0e0e',
|
||||
color0: '#d6b77b'
|
||||
},
|
||||
areaStyle: {
|
||||
color: '#494f2b',
|
||||
color0: '#d6b77b'
|
||||
}
|
||||
},
|
||||
|
||||
map: {
|
||||
itemStyle: {
|
||||
color: '#606233'
|
||||
},
|
||||
areaStyle: {
|
||||
color: '#ddd'
|
||||
},
|
||||
label: {
|
||||
color: '#c12e34'
|
||||
}
|
||||
},
|
||||
|
||||
graph: {
|
||||
itemStyle: {
|
||||
color: '#494f2b'
|
||||
},
|
||||
linkStyle: {
|
||||
color: '#313b23'
|
||||
}
|
||||
},
|
||||
|
||||
gauge: {
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: [
|
||||
[0.2, '#494f2b'],
|
||||
[0.8, '#313b23'],
|
||||
[1, '0e0e0e']
|
||||
],
|
||||
width: 8
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
echarts.registerTheme('forest', theme);
|
||||
});
|
||||
@@ -1,163 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
(function(root, factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
// AMD. Register as an anonymous module.
|
||||
define(['exports', 'echarts'], factory);
|
||||
} else if (
|
||||
typeof exports === 'object' &&
|
||||
typeof exports.nodeName !== 'string'
|
||||
) {
|
||||
// CommonJS
|
||||
factory(exports, require('echarts/lib/echarts'));
|
||||
} else {
|
||||
// Browser globals
|
||||
factory({}, root.echarts);
|
||||
}
|
||||
})(this, function(exports, echarts) {
|
||||
var log = function(msg) {
|
||||
if (typeof console !== 'undefined') {
|
||||
console && console.error && console.error(msg);
|
||||
}
|
||||
};
|
||||
if (!echarts) {
|
||||
log('ECharts is not Loaded');
|
||||
return;
|
||||
}
|
||||
|
||||
var colorPalette = [
|
||||
'#00a8c6',
|
||||
'#40c0cb',
|
||||
'#f0dec2',
|
||||
'#aee239',
|
||||
'#8fbe00',
|
||||
'#33e0ff',
|
||||
'#b3f4ff',
|
||||
'#e6ff99'
|
||||
];
|
||||
|
||||
var theme = {
|
||||
color: colorPalette,
|
||||
|
||||
title: {
|
||||
textStyle: {
|
||||
fontWeight: 'normal',
|
||||
color: '#00a8c6'
|
||||
}
|
||||
},
|
||||
|
||||
visualMap: {
|
||||
color: ['#00a8c6', '#a2d4e6']
|
||||
},
|
||||
|
||||
toolbox: {
|
||||
color: ['#00a8c6', '#00a8c6', '#00a8c6', '#00a8c6']
|
||||
},
|
||||
|
||||
tooltip: {
|
||||
backgroundColor: 'rgba(0,0,0,0.5)',
|
||||
axisPointer: {
|
||||
// Axis indicator, coordinate trigger effective
|
||||
type: 'line', // The default is a straight line: 'line' | 'shadow'
|
||||
lineStyle: {
|
||||
// Straight line indicator style settings
|
||||
color: '#00a8c6',
|
||||
type: 'dashed'
|
||||
},
|
||||
crossStyle: {
|
||||
color: '#00a8c6'
|
||||
},
|
||||
shadowStyle: {
|
||||
// Shadow indicator style settings
|
||||
color: 'rgba(200,200,200,0.3)'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Area scaling controller
|
||||
dataZoom: {
|
||||
dataBackgroundColor: '#eee', // Data background color
|
||||
fillerColor: 'rgba(144,197,237,0.2)', // Fill the color
|
||||
handleColor: '#00a8c6' // Handle color
|
||||
},
|
||||
|
||||
timeline: {
|
||||
lineStyle: {
|
||||
color: '#00a8c6'
|
||||
},
|
||||
controlStyle: {
|
||||
color: '#00a8c6',
|
||||
borderColor: '#00a8c6'
|
||||
}
|
||||
},
|
||||
|
||||
candlestick: {
|
||||
itemStyle: {
|
||||
color: '#40c0cb',
|
||||
color0: '#f0dec2'
|
||||
},
|
||||
lineStyle: {
|
||||
width: 1,
|
||||
color: '#8fbe00',
|
||||
color0: '#aee239'
|
||||
},
|
||||
areaStyle: {
|
||||
color: '#00a8c6',
|
||||
color0: '#aee239'
|
||||
}
|
||||
},
|
||||
|
||||
map: {
|
||||
itemStyle: {
|
||||
color: '#ddd'
|
||||
},
|
||||
areaStyle: {
|
||||
color: '#f0dec2'
|
||||
},
|
||||
label: {
|
||||
color: '#c12e34'
|
||||
}
|
||||
},
|
||||
|
||||
graph: {
|
||||
itemStyle: {
|
||||
color: '#f0dec2'
|
||||
},
|
||||
linkStyle: {
|
||||
color: '#00a8c6'
|
||||
}
|
||||
},
|
||||
|
||||
gauge: {
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: [
|
||||
[0.2, '#40c0cb'],
|
||||
[0.8, '#00a8c6'],
|
||||
[1, '#8fbe00']
|
||||
],
|
||||
width: 8
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
echarts.registerTheme('fresh-cut', theme);
|
||||
});
|
||||
@@ -1,178 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
(function(root, factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
// AMD. Register as an anonymous module.
|
||||
define(['exports', 'echarts'], factory);
|
||||
} else if (
|
||||
typeof exports === 'object' &&
|
||||
typeof exports.nodeName !== 'string'
|
||||
) {
|
||||
// CommonJS
|
||||
factory(exports, require('echarts/lib/echarts'));
|
||||
} else {
|
||||
// Browser globals
|
||||
factory({}, root.echarts);
|
||||
}
|
||||
})(this, function(exports, echarts) {
|
||||
var log = function(msg) {
|
||||
if (typeof console !== 'undefined') {
|
||||
console && console.error && console.error(msg);
|
||||
}
|
||||
};
|
||||
if (!echarts) {
|
||||
log('ECharts is not Loaded');
|
||||
return;
|
||||
}
|
||||
|
||||
var colorPalette = [
|
||||
'#ffcb6a',
|
||||
'#ffa850',
|
||||
'#ffe2c4',
|
||||
'#e5834e',
|
||||
'#ffb081',
|
||||
'#f7826e',
|
||||
'#faac9e',
|
||||
'#fcd5cf'
|
||||
];
|
||||
|
||||
var theme = {
|
||||
color: colorPalette,
|
||||
|
||||
title: {
|
||||
textStyle: {
|
||||
fontWeight: 'normal',
|
||||
color: '#ffcb6a'
|
||||
}
|
||||
},
|
||||
|
||||
visualMap: {
|
||||
color: ['#ffcb6a', '#ffa850']
|
||||
},
|
||||
|
||||
toolbox: {
|
||||
color: ['#ffcb6a', '#ffcb6a', '#ffcb6a', '#ffcb6a']
|
||||
},
|
||||
|
||||
tooltip: {
|
||||
backgroundColor: 'rgba(0,0,0,0.5)',
|
||||
axisPointer: {
|
||||
// Axis indicator, coordinate trigger effective
|
||||
type: 'line', // The default is a straight line: 'line' | 'shadow'
|
||||
lineStyle: {
|
||||
// Straight line indicator style settings
|
||||
color: '#ffcb6a',
|
||||
type: 'dashed'
|
||||
},
|
||||
crossStyle: {
|
||||
color: '#ffcb6a'
|
||||
},
|
||||
shadowStyle: {
|
||||
// Shadow indicator style settings
|
||||
color: 'rgba(200,200,200,0.3)'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Area scaling controller
|
||||
dataZoom: {
|
||||
dataBackgroundColor: '#eee', // Data background color
|
||||
fillerColor: 'rgba(200,200,200,0.2)', // Fill the color
|
||||
handleColor: '#ffcb6a' // Handle color
|
||||
},
|
||||
|
||||
timeline: {
|
||||
lineStyle: {
|
||||
color: '#ffcb6a'
|
||||
},
|
||||
controlStyle: {
|
||||
color: '#ffcb6a',
|
||||
borderColor: '#ffcb6a'
|
||||
}
|
||||
},
|
||||
|
||||
candlestick: {
|
||||
itemStyle: {
|
||||
color: '#ffa850',
|
||||
color0: '#ffe2c4'
|
||||
},
|
||||
lineStyle: {
|
||||
width: 1,
|
||||
color: '#ffb081',
|
||||
color0: '#e5834e'
|
||||
},
|
||||
areaStyle: {
|
||||
color: '#e5834e',
|
||||
color0: '#fcd5cf'
|
||||
}
|
||||
},
|
||||
|
||||
chord: {
|
||||
padding: 4,
|
||||
itemStyle: {
|
||||
color: '#fcd5cf',
|
||||
borderWidth: 1,
|
||||
borderColor: 'rgba(128, 128, 128, 0.5)'
|
||||
},
|
||||
lineStyle: {
|
||||
color: 'rgba(128, 128, 128, 0.5)'
|
||||
},
|
||||
areaStyle: {
|
||||
color: '#e5834e'
|
||||
}
|
||||
},
|
||||
|
||||
map: {
|
||||
itemStyle: {
|
||||
color: '#ffe2c4'
|
||||
},
|
||||
areaStyle: {
|
||||
color: '#ddd'
|
||||
},
|
||||
label: {
|
||||
color: '#c12e34'
|
||||
}
|
||||
},
|
||||
|
||||
graph: {
|
||||
itemStyle: {
|
||||
color: '#f2385a'
|
||||
},
|
||||
linkStyle: {
|
||||
color: '#ffcb6a'
|
||||
}
|
||||
},
|
||||
|
||||
gauge: {
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: [
|
||||
[0.2, '#ffa850'],
|
||||
[0.8, '#ffcb6a'],
|
||||
[1, '#ffb081']
|
||||
],
|
||||
width: 8
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
echarts.registerTheme('fruit', theme);
|
||||
});
|
||||
@@ -1,220 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
(function(root, factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
// AMD. Register as an anonymous module.
|
||||
define(['exports', 'echarts'], factory);
|
||||
} else if (
|
||||
typeof exports === 'object' &&
|
||||
typeof exports.nodeName !== 'string'
|
||||
) {
|
||||
// CommonJS
|
||||
factory(exports, require('echarts/lib/echarts'));
|
||||
} else {
|
||||
// Browser globals
|
||||
factory({}, root.echarts);
|
||||
}
|
||||
})(this, function(exports, echarts) {
|
||||
var log = function(msg) {
|
||||
if (typeof console !== 'undefined') {
|
||||
console && console.error && console.error(msg);
|
||||
}
|
||||
};
|
||||
if (!echarts) {
|
||||
log('ECharts is not Loaded');
|
||||
return;
|
||||
}
|
||||
|
||||
var colorPalette = [
|
||||
'#757575',
|
||||
'#c7c7c7',
|
||||
'#dadada',
|
||||
'#8b8b8b',
|
||||
'#b5b5b5',
|
||||
'#e9e9e9'
|
||||
];
|
||||
|
||||
var theme = {
|
||||
color: colorPalette,
|
||||
|
||||
title: {
|
||||
textStyle: {
|
||||
fontWeight: 'normal',
|
||||
color: '#757575'
|
||||
}
|
||||
},
|
||||
|
||||
dataRange: {
|
||||
color: ['#636363', '#dcdcdc']
|
||||
},
|
||||
|
||||
toolbox: {
|
||||
color: ['#757575', '#757575', '#757575', '#757575']
|
||||
},
|
||||
|
||||
tooltip: {
|
||||
backgroundColor: 'rgba(0,0,0,0.5)',
|
||||
axisPointer: {
|
||||
// Axis indicator, coordinate trigger effective
|
||||
type: 'line', // The default is a straight line: 'line' | 'shadow'
|
||||
lineStyle: {
|
||||
// Straight line indicator style settings
|
||||
color: '#757575',
|
||||
type: 'dashed'
|
||||
},
|
||||
crossStyle: {
|
||||
color: '#757575'
|
||||
},
|
||||
shadowStyle: {
|
||||
// Shadow indicator style settings
|
||||
color: 'rgba(200,200,200,0.3)'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Area scaling controller
|
||||
dataZoom: {
|
||||
dataBackgroundColor: '#eee', // Data background color
|
||||
fillerColor: 'rgba(117,117,117,0.2)', // Fill the color
|
||||
handleColor: '#757575' // Handle color
|
||||
},
|
||||
|
||||
grid: {
|
||||
borderWidth: 0
|
||||
},
|
||||
|
||||
categoryAxis: {
|
||||
axisLine: {
|
||||
// Coordinate axis
|
||||
lineStyle: {
|
||||
// Property 'lineStyle' controls line styles
|
||||
color: '#757575'
|
||||
}
|
||||
},
|
||||
splitLine: {
|
||||
// Separation line
|
||||
lineStyle: {
|
||||
// Property 'lineStyle' (see lineStyle) controls line styles
|
||||
color: ['#eee']
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
valueAxis: {
|
||||
axisLine: {
|
||||
// Coordinate axis
|
||||
lineStyle: {
|
||||
// Property 'lineStyle' controls line styles
|
||||
color: '#757575'
|
||||
}
|
||||
},
|
||||
splitArea: {
|
||||
show: true,
|
||||
areaStyle: {
|
||||
color: ['rgba(250,250,250,0.1)', 'rgba(200,200,200,0.1)']
|
||||
}
|
||||
},
|
||||
splitLine: {
|
||||
// Separation line
|
||||
lineStyle: {
|
||||
// Property 'lineStyle' (see lineStyle) controls line styles
|
||||
color: ['#eee']
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
timeline: {
|
||||
lineStyle: {
|
||||
color: '#757575'
|
||||
},
|
||||
controlStyle: {
|
||||
color: '#757575',
|
||||
borderColor: '#757575'
|
||||
}
|
||||
},
|
||||
|
||||
candlestick: {
|
||||
itemStyle: {
|
||||
color: '#8b8b8b',
|
||||
color0: '#dadada'
|
||||
},
|
||||
lineStyle: {
|
||||
width: 1,
|
||||
color: '#757575',
|
||||
color0: '#c7c7c7'
|
||||
},
|
||||
areaStyle: {
|
||||
color: '#757575',
|
||||
color0: '#e9e9e9'
|
||||
}
|
||||
},
|
||||
|
||||
map: {
|
||||
itemStyle: {
|
||||
color: '#c7c7c7'
|
||||
},
|
||||
areaStyle: {
|
||||
color: 'ddd'
|
||||
},
|
||||
label: {
|
||||
color: '#c12e34'
|
||||
}
|
||||
},
|
||||
|
||||
graph: {
|
||||
itemStyle: {
|
||||
color: '#e9e9e9'
|
||||
},
|
||||
linkStyle: {
|
||||
color: '#757575'
|
||||
}
|
||||
},
|
||||
|
||||
chord: {
|
||||
padding: 4,
|
||||
itemStyle: {
|
||||
color: '#e9e9e9',
|
||||
borderWidth: 1,
|
||||
borderColor: 'rgba(128, 128, 128, 0.5)'
|
||||
},
|
||||
lineStyle: {
|
||||
color: 'rgba(128, 128, 128, 0.5)'
|
||||
},
|
||||
areaStyle: {
|
||||
color: '#757575'
|
||||
}
|
||||
},
|
||||
|
||||
gauge: {
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: [
|
||||
[0.2, '#b5b5b5'],
|
||||
[0.8, '#757575'],
|
||||
[1, '#5c5c5c']
|
||||
],
|
||||
width: 8
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
echarts.registerTheme('gray', theme);
|
||||
});
|
||||
@@ -1,222 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
(function(root, factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
// AMD. Register as an anonymous module.
|
||||
define(['exports', 'echarts'], factory);
|
||||
} else if (
|
||||
typeof exports === 'object' &&
|
||||
typeof exports.nodeName !== 'string'
|
||||
) {
|
||||
// CommonJS
|
||||
factory(exports, require('echarts/lib/echarts'));
|
||||
} else {
|
||||
// Browser globals
|
||||
factory({}, root.echarts);
|
||||
}
|
||||
})(this, function(exports, echarts) {
|
||||
var log = function(msg) {
|
||||
if (typeof console !== 'undefined') {
|
||||
console && console.error && console.error(msg);
|
||||
}
|
||||
};
|
||||
if (!echarts) {
|
||||
log('ECharts is not Loaded');
|
||||
return;
|
||||
}
|
||||
|
||||
var colorPalette = [
|
||||
'#408829',
|
||||
'#68a54a',
|
||||
'#a9cba2',
|
||||
'#86b379',
|
||||
'#397b29',
|
||||
'#8abb6f',
|
||||
'#759c6a',
|
||||
'#bfd3b7'
|
||||
];
|
||||
|
||||
var theme = {
|
||||
color: colorPalette,
|
||||
|
||||
title: {
|
||||
textStyle: {
|
||||
fontWeight: 'normal',
|
||||
color: '#408829'
|
||||
}
|
||||
},
|
||||
|
||||
visualMap: {
|
||||
color: ['408829', '#a9cba2']
|
||||
},
|
||||
|
||||
toolbox: {
|
||||
color: ['#408829', '#408829', '#408829', '#408829']
|
||||
},
|
||||
|
||||
tooltip: {
|
||||
backgroundColor: 'rgba(0,0,0,0.5)',
|
||||
axisPointer: {
|
||||
// Axis indicator, coordinate trigger effective
|
||||
type: 'line', // The default is a straight line: 'line' | 'shadow'
|
||||
lineStyle: {
|
||||
// Straight line indicator style settings
|
||||
color: '#408829',
|
||||
type: 'dashed'
|
||||
},
|
||||
crossStyle: {
|
||||
color: '#408829'
|
||||
},
|
||||
shadowStyle: {
|
||||
// Shadow indicator style settings
|
||||
color: 'rgba(200,200,200,0.3)'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Area scaling controller
|
||||
dataZoom: {
|
||||
dataBackgroundColor: '#eee', // Data background color
|
||||
fillerColor: 'rgba(64,136,41,0.2)', // Fill the color
|
||||
handleColor: '#408829' // Handle color
|
||||
},
|
||||
|
||||
grid: {
|
||||
borderWidth: 0
|
||||
},
|
||||
|
||||
categoryAxis: {
|
||||
axisLine: {
|
||||
// Coordinate axis
|
||||
lineStyle: {
|
||||
// Property 'lineStyle' controls line styles
|
||||
color: '#408829'
|
||||
}
|
||||
},
|
||||
splitLine: {
|
||||
// Separation line
|
||||
lineStyle: {
|
||||
// Property 'lineStyle' (see lineStyle) controls line styles
|
||||
color: ['#eee']
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
valueAxis: {
|
||||
axisLine: {
|
||||
// Coordinate axis
|
||||
lineStyle: {
|
||||
// Property 'lineStyle' controls line styles
|
||||
color: '#408829'
|
||||
}
|
||||
},
|
||||
splitArea: {
|
||||
show: true,
|
||||
areaStyle: {
|
||||
color: ['rgba(250,250,250,0.1)', 'rgba(200,200,200,0.1)']
|
||||
}
|
||||
},
|
||||
splitLine: {
|
||||
// Separation line
|
||||
lineStyle: {
|
||||
// Property 'lineStyle' (see lineStyle) controls line styles
|
||||
color: ['#eee']
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
timeline: {
|
||||
lineStyle: {
|
||||
color: '#408829'
|
||||
},
|
||||
controlStyle: {
|
||||
color: '#408829',
|
||||
borderColor: '#408829'
|
||||
}
|
||||
},
|
||||
|
||||
candlestick: {
|
||||
itemStyle: {
|
||||
color: '#68a54a',
|
||||
color0: '#a9cba2'
|
||||
},
|
||||
lineStyle: {
|
||||
width: 1,
|
||||
color: '#408829',
|
||||
color0: '#86b379'
|
||||
},
|
||||
areaStyle: {
|
||||
color: '#408829',
|
||||
color0: '#bfd3b7'
|
||||
}
|
||||
},
|
||||
|
||||
graph: {
|
||||
itemStyle: {
|
||||
color: '#bfd3b7'
|
||||
},
|
||||
linkStyle: {
|
||||
color: '#408829'
|
||||
}
|
||||
},
|
||||
|
||||
chord: {
|
||||
padding: 4,
|
||||
itemStyle: {
|
||||
color: '#bfd3b7',
|
||||
borderWidth: 1,
|
||||
borderColor: 'rgba(128, 128, 128, 0.5)'
|
||||
},
|
||||
lineStyle: {
|
||||
color: 'rgba(128, 128, 128, 0.5)'
|
||||
},
|
||||
areaStyle: {
|
||||
color: '#408829'
|
||||
}
|
||||
},
|
||||
|
||||
map: {
|
||||
itemStyle: {
|
||||
color: '#ddd'
|
||||
},
|
||||
areaStyle: {
|
||||
color: '#408829'
|
||||
},
|
||||
label: {
|
||||
color: '#000'
|
||||
}
|
||||
},
|
||||
|
||||
gauge: {
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: [
|
||||
[0.2, '#86b379'],
|
||||
[0.8, '#68a54a'],
|
||||
[1, '#408829']
|
||||
],
|
||||
width: 8
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
echarts.registerTheme('green', theme);
|
||||
});
|
||||
@@ -1,263 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
(function(root, factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
// AMD. Register as an anonymous module.
|
||||
define(['exports', 'echarts'], factory);
|
||||
} else if (
|
||||
typeof exports === 'object' &&
|
||||
typeof exports.nodeName !== 'string'
|
||||
) {
|
||||
// CommonJS
|
||||
factory(exports, require('echarts/lib/echarts'));
|
||||
} else {
|
||||
// Browser globals
|
||||
factory({}, root.echarts);
|
||||
}
|
||||
})(this, function(exports, echarts) {
|
||||
var log = function(msg) {
|
||||
if (typeof console !== 'undefined') {
|
||||
console && console.error && console.error(msg);
|
||||
}
|
||||
};
|
||||
if (!echarts) {
|
||||
log('ECharts is not Loaded');
|
||||
return;
|
||||
}
|
||||
|
||||
var colorPalette = [
|
||||
'#44B7D3',
|
||||
'#E42B6D',
|
||||
'#F4E24E',
|
||||
'#FE9616',
|
||||
'#8AED35',
|
||||
'#ff69b4',
|
||||
'#ba55d3',
|
||||
'#cd5c5c',
|
||||
'#ffa500',
|
||||
'#40e0d0',
|
||||
'#E95569',
|
||||
'#ff6347',
|
||||
'#7b68ee',
|
||||
'#00fa9a',
|
||||
'#ffd700',
|
||||
'#6699FF',
|
||||
'#ff6666',
|
||||
'#3cb371',
|
||||
'#b8860b',
|
||||
'#30e0e0'
|
||||
];
|
||||
|
||||
var theme = {
|
||||
color: colorPalette,
|
||||
|
||||
title: {
|
||||
textStyle: {
|
||||
fontWeight: 'normal',
|
||||
color: '#8A826D'
|
||||
}
|
||||
},
|
||||
|
||||
dataRange: {
|
||||
x: 'right',
|
||||
y: 'center',
|
||||
itemWidth: 5,
|
||||
itemHeight: 25,
|
||||
color: ['#E42B6D', '#F9AD96'],
|
||||
text: ['High', 'Low'], // Text, default is numeric text
|
||||
textStyle: {
|
||||
color: '#8A826D' // Range text color
|
||||
}
|
||||
},
|
||||
|
||||
toolbox: {
|
||||
color: ['#E95569', '#E95569', '#E95569', '#E95569'],
|
||||
effectiveColor: '#ff4500',
|
||||
itemGap: 8
|
||||
},
|
||||
|
||||
tooltip: {
|
||||
backgroundColor: 'rgba(138,130,109,0.7)', // Prompt background color, default is black with a transparency of 0.7
|
||||
axisPointer: {
|
||||
// Axis indicator, coordinate trigger effective
|
||||
type: 'line', // The default is a straight line: 'line' | 'shadow'
|
||||
lineStyle: {
|
||||
// Straight line indicator style settings
|
||||
color: '#6B6455',
|
||||
type: 'dashed'
|
||||
},
|
||||
crossStyle: {
|
||||
color: '#A6A299'
|
||||
},
|
||||
shadowStyle: {
|
||||
// Shadow indicator style settings
|
||||
color: 'rgba(200,200,200,0.3)'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Area scaling controller
|
||||
dataZoom: {
|
||||
dataBackgroundColor: 'rgba(130,197,209,0.6)', // Data background color
|
||||
fillerColor: 'rgba(233,84,105,0.1)', // Fill the color
|
||||
handleColor: 'rgba(107,99,84,0.8)' // Handle color
|
||||
},
|
||||
|
||||
grid: {
|
||||
borderWidth: 0
|
||||
},
|
||||
|
||||
categoryAxis: {
|
||||
axisLine: {
|
||||
// Coordinate axis
|
||||
lineStyle: {
|
||||
// Property 'lineStyle' controls line styles
|
||||
color: '#6B6455'
|
||||
}
|
||||
},
|
||||
splitLine: {
|
||||
// separate line
|
||||
show: false
|
||||
}
|
||||
},
|
||||
|
||||
valueAxis: {
|
||||
axisLine: {
|
||||
// Coordinate axis
|
||||
show: true
|
||||
},
|
||||
splitArea: {
|
||||
show: false
|
||||
},
|
||||
splitLine: {
|
||||
// separate line
|
||||
lineStyle: {
|
||||
// Property 'lineStyle' controls line styles
|
||||
color: ['#FFF'],
|
||||
type: 'dashed'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
polar: {
|
||||
axisLine: {
|
||||
// Coordinate axis
|
||||
lineStyle: {
|
||||
// // Property 'lineStyle' controls line styles
|
||||
color: '#ddd'
|
||||
}
|
||||
},
|
||||
splitArea: {
|
||||
show: true,
|
||||
areaStyle: {
|
||||
color: ['rgba(250,250,250,0.2)', 'rgba(200,200,200,0.2)']
|
||||
}
|
||||
},
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
color: '#ddd'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
timeline: {
|
||||
lineStyle: {
|
||||
color: '#6B6455'
|
||||
},
|
||||
controlStyle: {
|
||||
color: '#6B6455',
|
||||
borderColor: '#6B6455'
|
||||
}
|
||||
},
|
||||
|
||||
line: {
|
||||
smooth: true,
|
||||
symbol: 'emptyCircle', // Inflection point graphic type
|
||||
symbolSize: 3 // Inflection point graphic size
|
||||
},
|
||||
|
||||
candlestick: {
|
||||
itemStyle: {
|
||||
color: '#e42B6d',
|
||||
color0: '#44B7d3'
|
||||
},
|
||||
lineStyle: {
|
||||
width: 1,
|
||||
color: '#e42B6d',
|
||||
color0: '#44B7d3'
|
||||
},
|
||||
areaStyle: {
|
||||
color: '#fe994e',
|
||||
color0: '#e42B6d'
|
||||
}
|
||||
},
|
||||
|
||||
map: {
|
||||
itemStyle: {
|
||||
color: '#6b6455'
|
||||
},
|
||||
areaStyle: {
|
||||
color: '#ddd'
|
||||
},
|
||||
label: {
|
||||
color: '#e42B6d'
|
||||
}
|
||||
},
|
||||
|
||||
graph: {
|
||||
itemStyle: {
|
||||
color: '#e42B6d'
|
||||
},
|
||||
linkStyle: {
|
||||
color: '#6b6455'
|
||||
}
|
||||
},
|
||||
|
||||
chord: {
|
||||
padding: 4,
|
||||
itemStyle: {
|
||||
color: '#e42B6d',
|
||||
borderWidth: 1,
|
||||
borderColor: 'rgba(128, 128, 128, 0.5)'
|
||||
},
|
||||
lineStyle: {
|
||||
color: 'rgba(128, 128, 128, 0.5)'
|
||||
},
|
||||
areaStyle: {
|
||||
color: '#6b6455'
|
||||
}
|
||||
},
|
||||
|
||||
gauge: {
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: [
|
||||
[0.2, '#44B7D3'],
|
||||
[0.8, '#6B6455'],
|
||||
[1, '#E42B6D']
|
||||
],
|
||||
width: 8
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
echarts.registerTheme('helianthus', theme);
|
||||
});
|
||||
@@ -1,236 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
(function(root, factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
// AMD. Register as an anonymous module.
|
||||
define(['exports', 'echarts'], factory);
|
||||
} else if (
|
||||
typeof exports === 'object' &&
|
||||
typeof exports.nodeName !== 'string'
|
||||
) {
|
||||
// CommonJS
|
||||
factory(exports, require('echarts/lib/echarts'));
|
||||
} else {
|
||||
// Browser globals
|
||||
factory({}, root.echarts);
|
||||
}
|
||||
})(this, function(exports, echarts) {
|
||||
var log = function(msg) {
|
||||
if (typeof console !== 'undefined') {
|
||||
console && console.error && console.error(msg);
|
||||
}
|
||||
};
|
||||
if (!echarts) {
|
||||
log('ECharts is not Loaded');
|
||||
return;
|
||||
}
|
||||
|
||||
var colorPalette = [
|
||||
'#C1232B',
|
||||
'#27727B',
|
||||
'#FCCE10',
|
||||
'#E87C25',
|
||||
'#B5C334',
|
||||
'#FE8463',
|
||||
'#9BCA63',
|
||||
'#FAD860',
|
||||
'#F3A43B',
|
||||
'#60C0DD',
|
||||
'#D7504B',
|
||||
'#C6E579',
|
||||
'#F4E001',
|
||||
'#F0805A',
|
||||
'#26C0C0'
|
||||
];
|
||||
|
||||
var theme = {
|
||||
color: colorPalette,
|
||||
|
||||
title: {
|
||||
textStyle: {
|
||||
fontWeight: 'normal',
|
||||
color: '#27727B'
|
||||
}
|
||||
},
|
||||
|
||||
visualMap: {
|
||||
color: ['#C1232B', '#FCCE10']
|
||||
},
|
||||
|
||||
toolbox: {
|
||||
iconStyle: {
|
||||
borderColor: colorPalette[0]
|
||||
}
|
||||
},
|
||||
|
||||
tooltip: {
|
||||
backgroundColor: 'rgba(50,50,50,0.5)',
|
||||
axisPointer: {
|
||||
type: 'line',
|
||||
lineStyle: {
|
||||
color: '#27727B',
|
||||
type: 'dashed'
|
||||
},
|
||||
crossStyle: {
|
||||
color: '#27727B'
|
||||
},
|
||||
shadowStyle: {
|
||||
color: 'rgba(200,200,200,0.3)'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
dataZoom: {
|
||||
dataBackgroundColor: 'rgba(181,195,52,0.3)',
|
||||
fillerColor: 'rgba(181,195,52,0.2)',
|
||||
handleColor: '#27727B'
|
||||
},
|
||||
|
||||
categoryAxis: {
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#27727B'
|
||||
}
|
||||
},
|
||||
splitLine: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
|
||||
valueAxis: {
|
||||
axisLine: {
|
||||
show: false
|
||||
},
|
||||
splitArea: {
|
||||
show: false
|
||||
},
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
color: ['#ccc'],
|
||||
type: 'dashed'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
timeline: {
|
||||
itemStyle: {
|
||||
color: '#27727B'
|
||||
},
|
||||
lineStyle: {
|
||||
color: '#27727B'
|
||||
},
|
||||
controlStyle: {
|
||||
color: '#27727B',
|
||||
borderColor: '#27727B'
|
||||
},
|
||||
symbol: 'emptyCircle',
|
||||
symbolSize: 3
|
||||
},
|
||||
|
||||
line: {
|
||||
itemStyle: {
|
||||
borderWidth: 2,
|
||||
borderColor: '#fff',
|
||||
lineStyle: {
|
||||
width: 3
|
||||
}
|
||||
},
|
||||
emphasis: {
|
||||
itemStyle: {
|
||||
borderWidth: 0
|
||||
}
|
||||
},
|
||||
symbol: 'circle',
|
||||
symbolSize: 3.5
|
||||
},
|
||||
|
||||
candlestick: {
|
||||
itemStyle: {
|
||||
color: '#c1232b',
|
||||
color0: '#b5c334'
|
||||
},
|
||||
lineStyle: {
|
||||
width: 1,
|
||||
color: '#c1232b',
|
||||
color0: '#b5c334'
|
||||
},
|
||||
areaStyle: {
|
||||
color: '#c1232b',
|
||||
color0: '#27727b'
|
||||
}
|
||||
},
|
||||
|
||||
graph: {
|
||||
itemStyle: {
|
||||
color: '#c1232b'
|
||||
},
|
||||
linkStyle: {
|
||||
color: '#b5c334'
|
||||
}
|
||||
},
|
||||
|
||||
map: {
|
||||
itemStyle: {
|
||||
color: '#f2385a',
|
||||
areaColor: '#ddd',
|
||||
borderColor: '#eee'
|
||||
},
|
||||
areaStyle: {
|
||||
color: '#fe994e'
|
||||
},
|
||||
label: {
|
||||
color: '#c1232b'
|
||||
}
|
||||
},
|
||||
|
||||
gauge: {
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: [
|
||||
[0.2, '#B5C334'],
|
||||
[0.8, '#27727B'],
|
||||
[1, '#C1232B']
|
||||
]
|
||||
}
|
||||
},
|
||||
axisTick: {
|
||||
splitNumber: 2,
|
||||
length: 5,
|
||||
lineStyle: {
|
||||
color: '#fff'
|
||||
}
|
||||
},
|
||||
axisLabel: {
|
||||
color: '#fff'
|
||||
},
|
||||
splitLine: {
|
||||
length: '5%',
|
||||
lineStyle: {
|
||||
color: '#fff'
|
||||
}
|
||||
},
|
||||
title: {
|
||||
offsetCenter: [0, -20]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
echarts.registerTheme('infographic', theme);
|
||||
});
|
||||
@@ -1,163 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
(function(root, factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
// AMD. Register as an anonymous module.
|
||||
define(['exports', 'echarts'], factory);
|
||||
} else if (
|
||||
typeof exports === 'object' &&
|
||||
typeof exports.nodeName !== 'string'
|
||||
) {
|
||||
// CommonJS
|
||||
factory(exports, require('echarts/lib/echarts'));
|
||||
} else {
|
||||
// Browser globals
|
||||
factory({}, root.echarts);
|
||||
}
|
||||
})(this, function(exports, echarts) {
|
||||
var log = function(msg) {
|
||||
if (typeof console !== 'undefined') {
|
||||
console && console.error && console.error(msg);
|
||||
}
|
||||
};
|
||||
if (!echarts) {
|
||||
log('ECharts is not Loaded');
|
||||
return;
|
||||
}
|
||||
|
||||
var colorPalette = [
|
||||
'#cc0000',
|
||||
'#002266',
|
||||
'#ff9900',
|
||||
'#006600',
|
||||
'#8a150f',
|
||||
'#076278',
|
||||
'#808080',
|
||||
'#f07b75'
|
||||
];
|
||||
|
||||
var theme = {
|
||||
color: colorPalette,
|
||||
|
||||
title: {
|
||||
textStyle: {
|
||||
fontWeight: 'normal',
|
||||
color: '#cc0000'
|
||||
}
|
||||
},
|
||||
|
||||
visualMap: {
|
||||
color: ['#cc0000', '#002266']
|
||||
},
|
||||
|
||||
toolbox: {
|
||||
color: ['#cc0000', '#cc0000', '#cc0000', '#cc0000']
|
||||
},
|
||||
|
||||
tooltip: {
|
||||
backgroundColor: 'rgba(0,0,0,0.5)',
|
||||
axisPointer: {
|
||||
// Axis indicator, coordinate trigger effective
|
||||
type: 'line', // The default is a straight line: 'line' | 'shadow'
|
||||
lineStyle: {
|
||||
// Straight line indicator style settings
|
||||
color: '#cc0000',
|
||||
type: 'dashed'
|
||||
},
|
||||
crossStyle: {
|
||||
color: '#cc0000'
|
||||
},
|
||||
shadowStyle: {
|
||||
// Shadow indicator style settings
|
||||
color: 'rgba(200,200,200,0.3)'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Area scaling controller
|
||||
dataZoom: {
|
||||
dataBackgroundColor: '#eee', // Data background color
|
||||
fillerColor: 'rgba(200,200,200,0.2)', // Fill the color
|
||||
handleColor: '#cc0000' // Handle color
|
||||
},
|
||||
|
||||
timeline: {
|
||||
lineStyle: {
|
||||
color: '#cc0000'
|
||||
},
|
||||
controlStyle: {
|
||||
color: '#cc0000',
|
||||
borderColor: '#cc0000'
|
||||
}
|
||||
},
|
||||
|
||||
candlestick: {
|
||||
itemStyle: {
|
||||
color: '#002266',
|
||||
color0: '#ff9900'
|
||||
},
|
||||
lineStyle: {
|
||||
width: 1,
|
||||
color: '#8a150f',
|
||||
color0: '#006600'
|
||||
},
|
||||
areaStyle: {
|
||||
color: '#cc0000',
|
||||
color0: '#ff9900'
|
||||
}
|
||||
},
|
||||
|
||||
map: {
|
||||
itemStyle: {
|
||||
color: '#ff9900'
|
||||
},
|
||||
areaStyle: {
|
||||
color: '#ddd'
|
||||
},
|
||||
label: {
|
||||
color: '#c12e34'
|
||||
}
|
||||
},
|
||||
|
||||
graph: {
|
||||
itemStyle: {
|
||||
color: '#ff9900'
|
||||
},
|
||||
linkStyle: {
|
||||
color: '#cc0000'
|
||||
}
|
||||
},
|
||||
|
||||
gauge: {
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: [
|
||||
[0.2, '#002266'],
|
||||
[0.8, '#cc0000'],
|
||||
[1, '8a150f']
|
||||
],
|
||||
width: 8
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
echarts.registerTheme('inspired', theme);
|
||||
});
|
||||
@@ -1,163 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
(function(root, factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
// AMD. Register as an anonymous module.
|
||||
define(['exports', 'echarts'], factory);
|
||||
} else if (
|
||||
typeof exports === 'object' &&
|
||||
typeof exports.nodeName !== 'string'
|
||||
) {
|
||||
// CommonJS
|
||||
factory(exports, require('echarts/lib/echarts'));
|
||||
} else {
|
||||
// Browser globals
|
||||
factory({}, root.echarts);
|
||||
}
|
||||
})(this, function(exports, echarts) {
|
||||
var log = function(msg) {
|
||||
if (typeof console !== 'undefined') {
|
||||
console && console.error && console.error(msg);
|
||||
}
|
||||
};
|
||||
if (!echarts) {
|
||||
log('ECharts is not Loaded');
|
||||
return;
|
||||
}
|
||||
|
||||
var colorPalette = [
|
||||
'#e9e0d1',
|
||||
'#91a398',
|
||||
'#33605a',
|
||||
'#070001',
|
||||
'#68462b',
|
||||
'#58a79c',
|
||||
'#abd3ce',
|
||||
'#eef6f5'
|
||||
];
|
||||
|
||||
var theme = {
|
||||
color: colorPalette,
|
||||
|
||||
title: {
|
||||
textStyle: {
|
||||
fontWeight: 'normal',
|
||||
color: '#e9e0d1'
|
||||
}
|
||||
},
|
||||
|
||||
visualMap: {
|
||||
color: ['#e9e0d1', '#91a398']
|
||||
},
|
||||
|
||||
toolbox: {
|
||||
color: ['#e9e0d1', '#e9e0d1', '#e9e0d1', '#e9e0d1']
|
||||
},
|
||||
|
||||
tooltip: {
|
||||
backgroundColor: 'rgba(0,0,0,0.5)',
|
||||
axisPointer: {
|
||||
// Axis indicator, coordinate trigger effective
|
||||
type: 'line', // The default is a straight line: 'line' | 'shadow'
|
||||
lineStyle: {
|
||||
// Straight line indicator style settings
|
||||
color: '#e9e0d1',
|
||||
type: 'dashed'
|
||||
},
|
||||
crossStyle: {
|
||||
color: '#e9e0d1'
|
||||
},
|
||||
shadowStyle: {
|
||||
// Shadow indicator style settings
|
||||
color: 'rgba(200,200,200,0.3)'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Area scaling controller
|
||||
dataZoom: {
|
||||
dataBackgroundColor: '#eee', // Data background color
|
||||
fillerColor: 'rgba(200,200,200,0.2)', // Fill the color
|
||||
handleColor: '#e9e0d1' // Handle color
|
||||
},
|
||||
|
||||
timeline: {
|
||||
lineStyle: {
|
||||
color: '#e9e0d1'
|
||||
},
|
||||
controlStyle: {
|
||||
color: '#e9e0d1',
|
||||
borderColor: '#e9e0d1'
|
||||
}
|
||||
},
|
||||
|
||||
candlestick: {
|
||||
itemStyle: {
|
||||
color: '#91a398',
|
||||
color0: '#33605a'
|
||||
},
|
||||
lineStyle: {
|
||||
width: 1,
|
||||
color: '#68462b',
|
||||
color0: '#070001'
|
||||
},
|
||||
areaStyle: {
|
||||
color: '#91a398',
|
||||
color0: '#abd3ce'
|
||||
}
|
||||
},
|
||||
|
||||
map: {
|
||||
itemStyle: {
|
||||
color: '#c12e34'
|
||||
},
|
||||
areaStyle: {
|
||||
color: '#ddd'
|
||||
},
|
||||
label: {
|
||||
color: '#c12e34'
|
||||
}
|
||||
},
|
||||
|
||||
graph: {
|
||||
itemStyle: {
|
||||
color: '#33605a'
|
||||
},
|
||||
linkStyle: {
|
||||
color: '#e9e0d1'
|
||||
}
|
||||
},
|
||||
|
||||
gauge: {
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: [
|
||||
[0.2, '#91a398'],
|
||||
[0.8, '#e9e0d1'],
|
||||
[1, '#68462b']
|
||||
],
|
||||
width: 8
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
echarts.registerTheme('jazz', theme);
|
||||
});
|
||||
@@ -1,163 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
(function(root, factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
// AMD. Register as an anonymous module.
|
||||
define(['exports', 'echarts'], factory);
|
||||
} else if (
|
||||
typeof exports === 'object' &&
|
||||
typeof exports.nodeName !== 'string'
|
||||
) {
|
||||
// CommonJS
|
||||
factory(exports, require('echarts/lib/echarts'));
|
||||
} else {
|
||||
// Browser globals
|
||||
factory({}, root.echarts);
|
||||
}
|
||||
})(this, function(exports, echarts) {
|
||||
var log = function(msg) {
|
||||
if (typeof console !== 'undefined') {
|
||||
console && console.error && console.error(msg);
|
||||
}
|
||||
};
|
||||
if (!echarts) {
|
||||
log('ECharts is not Loaded');
|
||||
return;
|
||||
}
|
||||
|
||||
var colorPalette = [
|
||||
'#02151a',
|
||||
'#043a47',
|
||||
'#087891',
|
||||
'#c8c8c8',
|
||||
'#b31d14',
|
||||
'#0b9cc1',
|
||||
'#f2f2f2',
|
||||
'#f07b75'
|
||||
];
|
||||
|
||||
var theme = {
|
||||
color: colorPalette,
|
||||
|
||||
title: {
|
||||
textStyle: {
|
||||
fontWeight: 'normal',
|
||||
color: '#02151a'
|
||||
}
|
||||
},
|
||||
|
||||
visualMap: {
|
||||
color: ['#02151a', '#a2d4e6']
|
||||
},
|
||||
|
||||
toolbox: {
|
||||
color: ['#02151a', '#02151a', '#02151a', '#02151a']
|
||||
},
|
||||
|
||||
tooltip: {
|
||||
backgroundColor: 'rgba(0,0,0,0.5)',
|
||||
axisPointer: {
|
||||
// Axis indicator, coordinate trigger effective
|
||||
type: 'line', // The default is a straight line: 'line' | 'shadow'
|
||||
lineStyle: {
|
||||
// Straight line indicator style settings
|
||||
color: '#02151a',
|
||||
type: 'dashed'
|
||||
},
|
||||
crossStyle: {
|
||||
color: '#02151a'
|
||||
},
|
||||
shadowStyle: {
|
||||
// Shadow indicator style settings
|
||||
color: 'rgba(200,200,200,0.3)'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Area scaling controller
|
||||
dataZoom: {
|
||||
dataBackgroundColor: '#eee', // Data background color
|
||||
fillerColor: 'rgba(144,197,237,0.2)', // Fill the color
|
||||
handleColor: '#02151a' // Handle color
|
||||
},
|
||||
|
||||
timeline: {
|
||||
lineStyle: {
|
||||
color: '#02151a'
|
||||
},
|
||||
controlStyle: {
|
||||
color: '#02151a',
|
||||
borderColor: '#02151a'
|
||||
}
|
||||
},
|
||||
|
||||
candlestick: {
|
||||
itemStyle: {
|
||||
color: '#043a47',
|
||||
color0: '#087891'
|
||||
},
|
||||
lineStyle: {
|
||||
width: 1,
|
||||
color: '#b31d14',
|
||||
color0: '#c8c8c8'
|
||||
},
|
||||
areaStyle: {
|
||||
color: '#087891',
|
||||
color0: '#c8c8c8'
|
||||
}
|
||||
},
|
||||
|
||||
map: {
|
||||
itemStyle: {
|
||||
color: '#ddd'
|
||||
},
|
||||
areaStyle: {
|
||||
color: '#087891'
|
||||
},
|
||||
label: {
|
||||
color: '#c12e34'
|
||||
}
|
||||
},
|
||||
|
||||
graph: {
|
||||
itemStyle: {
|
||||
color: '#c12e34'
|
||||
},
|
||||
linkStyle: {
|
||||
color: '#02151a'
|
||||
}
|
||||
},
|
||||
|
||||
gauge: {
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: [
|
||||
[0.2, '#043a47'],
|
||||
[0.8, '#02151a'],
|
||||
[1, '#b31d14']
|
||||
],
|
||||
width: 8
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
echarts.registerTheme('london', theme);
|
||||
});
|
||||
@@ -1,242 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
(function(root, factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
// AMD. Register as an anonymous module.
|
||||
define(['exports', 'echarts'], factory);
|
||||
} else if (
|
||||
typeof exports === 'object' &&
|
||||
typeof exports.nodeName !== 'string'
|
||||
) {
|
||||
// CommonJS
|
||||
factory(exports, require('echarts/lib/echarts'));
|
||||
} else {
|
||||
// Browser globals
|
||||
factory({}, root.echarts);
|
||||
}
|
||||
})(this, function(exports, echarts) {
|
||||
var log = function(msg) {
|
||||
if (typeof console !== 'undefined') {
|
||||
console && console.error && console.error(msg);
|
||||
}
|
||||
};
|
||||
if (!echarts) {
|
||||
log('ECharts is not Loaded');
|
||||
return;
|
||||
}
|
||||
|
||||
var colorPalette = [
|
||||
'#2ec7c9',
|
||||
'#b6a2de',
|
||||
'#5ab1ef',
|
||||
'#ffb980',
|
||||
'#d87a80',
|
||||
'#8d98b3',
|
||||
'#e5cf0d',
|
||||
'#97b552',
|
||||
'#95706d',
|
||||
'#dc69aa',
|
||||
'#07a2a4',
|
||||
'#9a7fd1',
|
||||
'#588dd5',
|
||||
'#f5994e',
|
||||
'#c05050',
|
||||
'#59678c',
|
||||
'#c9ab00',
|
||||
'#7eb00a',
|
||||
'#6f5553',
|
||||
'#c14089'
|
||||
];
|
||||
|
||||
var theme = {
|
||||
color: colorPalette,
|
||||
|
||||
title: {
|
||||
textStyle: {
|
||||
fontWeight: 'normal',
|
||||
color: '#008acd'
|
||||
}
|
||||
},
|
||||
|
||||
visualMap: {
|
||||
itemWidth: 15,
|
||||
color: ['#5ab1ef', '#e0ffff']
|
||||
},
|
||||
|
||||
toolbox: {
|
||||
iconStyle: {
|
||||
borderColor: colorPalette[0]
|
||||
}
|
||||
},
|
||||
|
||||
tooltip: {
|
||||
borderWidth: 0,
|
||||
backgroundColor: 'rgba(50,50,50,0.5)',
|
||||
textStyle: {
|
||||
color: '#FFF'
|
||||
},
|
||||
axisPointer: {
|
||||
type: 'line',
|
||||
lineStyle: {
|
||||
color: '#008acd'
|
||||
},
|
||||
crossStyle: {
|
||||
color: '#008acd'
|
||||
},
|
||||
shadowStyle: {
|
||||
color: 'rgba(200,200,200,0.2)'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
dataZoom: {
|
||||
dataBackgroundColor: '#efefff',
|
||||
fillerColor: 'rgba(182,162,222,0.2)',
|
||||
handleColor: '#008acd'
|
||||
},
|
||||
|
||||
grid: {
|
||||
borderColor: '#eee'
|
||||
},
|
||||
|
||||
categoryAxis: {
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#008acd'
|
||||
}
|
||||
},
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
color: ['#eee']
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
valueAxis: {
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#008acd'
|
||||
}
|
||||
},
|
||||
splitArea: {
|
||||
show: true,
|
||||
areaStyle: {
|
||||
color: ['rgba(250,250,250,0.1)', 'rgba(200,200,200,0.1)']
|
||||
}
|
||||
},
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
color: ['#eee']
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
timeline: {
|
||||
lineStyle: {
|
||||
color: '#008acd'
|
||||
},
|
||||
controlStyle: {
|
||||
color: '#008acd',
|
||||
borderColor: '#008acd'
|
||||
},
|
||||
symbol: 'emptyCircle',
|
||||
symbolSize: 3
|
||||
},
|
||||
|
||||
line: {
|
||||
smooth: true,
|
||||
symbol: 'emptyCircle',
|
||||
symbolSize: 3
|
||||
},
|
||||
|
||||
candlestick: {
|
||||
itemStyle: {
|
||||
color: '#d87a80',
|
||||
color0: '#2ec7c9'
|
||||
},
|
||||
lineStyle: {
|
||||
width: 1,
|
||||
color: '#d87a80',
|
||||
color0: '#2ec7c9'
|
||||
},
|
||||
areaStyle: {
|
||||
color: '#2ec7c9',
|
||||
color0: '#b6a2de'
|
||||
}
|
||||
},
|
||||
|
||||
scatter: {
|
||||
symbol: 'circle',
|
||||
symbolSize: 4
|
||||
},
|
||||
|
||||
map: {
|
||||
itemStyle: {
|
||||
color: '#ddd'
|
||||
},
|
||||
areaStyle: {
|
||||
color: '#fe994e'
|
||||
},
|
||||
label: {
|
||||
color: '#d87a80'
|
||||
}
|
||||
},
|
||||
|
||||
graph: {
|
||||
itemStyle: {
|
||||
color: '#d87a80'
|
||||
},
|
||||
linkStyle: {
|
||||
color: '#2ec7c9'
|
||||
}
|
||||
},
|
||||
|
||||
gauge: {
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: [
|
||||
[0.2, '#2ec7c9'],
|
||||
[0.8, '#5ab1ef'],
|
||||
[1, '#d87a80']
|
||||
],
|
||||
width: 10
|
||||
}
|
||||
},
|
||||
axisTick: {
|
||||
splitNumber: 10,
|
||||
length: 15,
|
||||
lineStyle: {
|
||||
color: 'auto'
|
||||
}
|
||||
},
|
||||
splitLine: {
|
||||
length: 22,
|
||||
lineStyle: {
|
||||
color: 'auto'
|
||||
}
|
||||
},
|
||||
pointer: {
|
||||
width: 5
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
echarts.registerTheme('macarons', theme);
|
||||
});
|
||||
@@ -1,246 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
(function(root, factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
// AMD. Register as an anonymous module.
|
||||
define(['exports', 'echarts'], factory);
|
||||
} else if (
|
||||
typeof exports === 'object' &&
|
||||
typeof exports.nodeName !== 'string'
|
||||
) {
|
||||
// CommonJS
|
||||
factory(exports, require('echarts/lib/echarts'));
|
||||
} else {
|
||||
// Browser globals
|
||||
factory({}, root.echarts);
|
||||
}
|
||||
})(this, function(exports, echarts) {
|
||||
var log = function(msg) {
|
||||
if (typeof console !== 'undefined') {
|
||||
console && console.error && console.error(msg);
|
||||
}
|
||||
};
|
||||
if (!echarts) {
|
||||
log('ECharts is not Loaded');
|
||||
return;
|
||||
}
|
||||
|
||||
var colorPalette = [
|
||||
'#ed9678',
|
||||
'#e7dac9',
|
||||
'#cb8e85',
|
||||
'#f3f39d',
|
||||
'#c8e49c',
|
||||
'#f16d7a',
|
||||
'#f3d999',
|
||||
'#d3758f',
|
||||
'#dcc392',
|
||||
'#2e4783',
|
||||
'#82b6e9',
|
||||
'#ff6347',
|
||||
'#a092f1',
|
||||
'#0a915d',
|
||||
'#eaf889',
|
||||
'#6699FF',
|
||||
'#ff6666',
|
||||
'#3cb371',
|
||||
'#d5b158',
|
||||
'#38b6b6'
|
||||
];
|
||||
|
||||
var theme = {
|
||||
color: colorPalette,
|
||||
|
||||
title: {
|
||||
textStyle: {
|
||||
fontWeight: 'normal',
|
||||
color: '#cb8e85'
|
||||
}
|
||||
},
|
||||
|
||||
dataRange: {
|
||||
color: ['#cb8e85', '#e7dac9'], //颜色
|
||||
//text:['高','低'], // 文本,默认为数值文本
|
||||
textStyle: {
|
||||
color: '#333' // 值域文字颜色
|
||||
}
|
||||
},
|
||||
|
||||
bar: {
|
||||
barMinHeight: 0, // 最小高度改为0
|
||||
// barWidth: null, // 默认自适应
|
||||
barGap: '30%', // 柱间距离,默认为柱形宽度的30%,可设固定值
|
||||
barCategoryGap: '20%', // 类目间柱形距离,默认为类目间距的20%,可设固定值
|
||||
label: {
|
||||
show: false
|
||||
// position: 默认自适应,水平布局为'top',垂直布局为'right',可选为
|
||||
// 'inside'|'left'|'right'|'top'|'bottom'
|
||||
// textStyle: null // 默认使用全局文本样式,详见TEXTSTYLE
|
||||
},
|
||||
itemStyle: {
|
||||
// color: '各异',
|
||||
barBorderColor: '#fff', // 柱条边线
|
||||
barBorderRadius: 0, // 柱条边线圆角,单位px,默认为0
|
||||
barBorderWidth: 1 // 柱条边线线宽,单位px,默认为1
|
||||
},
|
||||
emphasis: {
|
||||
itemStyle: {
|
||||
// color: '各异',
|
||||
barBorderColor: 'rgba(0,0,0,0)', // 柱条边线
|
||||
barBorderRadius: 0, // 柱条边线圆角,单位px,默认为0
|
||||
barBorderWidth: 1, // 柱条边线线宽,单位px,默认为1
|
||||
},
|
||||
label: {
|
||||
show: false
|
||||
// position: 默认自适应,水平布局为'top',垂直布局为'right',可选为
|
||||
// 'inside'|'left'|'right'|'top'|'bottom'
|
||||
// textStyle: null // 默认使用全局文本样式,详见TEXTSTYLE
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
line: {
|
||||
label: {
|
||||
show: false
|
||||
// position: 默认自适应,水平布局为'top',垂直布局为'right',可选为
|
||||
// 'inside'|'left'|'right'|'top'|'bottom'
|
||||
// textStyle: null // 默认使用全局文本样式,详见TEXTSTYLE
|
||||
},
|
||||
itemStyle: {
|
||||
// color: 各异,
|
||||
},
|
||||
emphasis: {
|
||||
// color: 各异,
|
||||
label: {
|
||||
show: false
|
||||
// position: 默认自适应,水平布局为'top',垂直布局为'right',可选为
|
||||
// 'inside'|'left'|'right'|'top'|'bottom'
|
||||
// textStyle: null // 默认使用全局文本样式,详见TEXTSTYLE
|
||||
}
|
||||
},
|
||||
lineStyle: {
|
||||
width: 2,
|
||||
type: 'solid',
|
||||
shadowColor: 'rgba(0,0,0,0)', //默认透明
|
||||
shadowBlur: 5,
|
||||
shadowOffsetX: 3,
|
||||
shadowOffsetY: 3
|
||||
},
|
||||
//smooth : false,
|
||||
//symbol: null, // 拐点图形类型
|
||||
symbolSize: 2, // 拐点图形大小
|
||||
//symbolRotate : null, // 拐点图形旋转控制
|
||||
showAllSymbol: false // 标志图形默认只有主轴显示(随主轴标签间隔隐藏策略)
|
||||
},
|
||||
candlestick: {
|
||||
itemStyle: {
|
||||
color: '#fe9778',
|
||||
color0: '#e7dac9'
|
||||
},
|
||||
lineStyle: {
|
||||
width: 1,
|
||||
color: '#f78766',
|
||||
color0: '#f1ccb8'
|
||||
},
|
||||
areaStyle: {
|
||||
color: '#e7dac9',
|
||||
color0: '#c8e49c'
|
||||
}
|
||||
},
|
||||
|
||||
// 饼图默认参数
|
||||
pie: {
|
||||
center: ['50%', '50%'], // 默认全局居中
|
||||
radius: [0, '75%'],
|
||||
clockWise: false, // 默认逆时针
|
||||
startAngle: 90,
|
||||
minAngle: 0, // 最小角度改为0
|
||||
selectedOffset: 10, // 选中是扇区偏移量
|
||||
label: {
|
||||
show: true,
|
||||
position: 'outer',
|
||||
color: '#1b1b1b',
|
||||
lineStyle: { color: '#1b1b1b' }
|
||||
// textStyle: null // 默认使用全局文本样式,详见TEXTSTYLE
|
||||
},
|
||||
itemStyle: {
|
||||
// color: 各异,
|
||||
borderColor: '#fff',
|
||||
borderWidth: 1
|
||||
},
|
||||
labelLine: {
|
||||
show: true,
|
||||
length: 20,
|
||||
lineStyle: {
|
||||
// color: 各异,
|
||||
width: 1,
|
||||
type: 'solid'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
map: {
|
||||
itemStyle: {
|
||||
color: '#ddd',
|
||||
borderColor: '#fff',
|
||||
borderWidth: 1
|
||||
},
|
||||
areaStyle: {
|
||||
color: '#f3f39d'
|
||||
},
|
||||
label: {
|
||||
show: false,
|
||||
color: 'rgba(139,69,19,1)'
|
||||
},
|
||||
showLegendSymbol: true
|
||||
},
|
||||
|
||||
graph: {
|
||||
itemStyle: {
|
||||
color: '#d87a80'
|
||||
},
|
||||
linkStyle: {
|
||||
strokeColor: '#a17e6e'
|
||||
},
|
||||
nodeStyle: {
|
||||
brushType: 'both',
|
||||
strokeColor: '#a17e6e'
|
||||
},
|
||||
label: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
|
||||
gauge: {
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: [
|
||||
[0.2, '#ed9678'],
|
||||
[0.8, '#e7dac9'],
|
||||
[1, '#cb8e85']
|
||||
],
|
||||
width: 8
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
echarts.registerTheme('macarons2', theme);
|
||||
});
|
||||
@@ -1,155 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
(function(root, factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
// AMD. Register as an anonymous module.
|
||||
define(['exports', 'echarts'], factory);
|
||||
} else if (
|
||||
typeof exports === 'object' &&
|
||||
typeof exports.nodeName !== 'string'
|
||||
) {
|
||||
// CommonJS
|
||||
factory(exports, require('echarts/lib/echarts'));
|
||||
} else {
|
||||
// Browser globals
|
||||
factory({}, root.echarts);
|
||||
}
|
||||
})(this, function(exports, echarts) {
|
||||
var log = function(msg) {
|
||||
if (typeof console !== 'undefined') {
|
||||
console && console.error && console.error(msg);
|
||||
}
|
||||
};
|
||||
if (!echarts) {
|
||||
log('ECharts is not Loaded');
|
||||
return;
|
||||
}
|
||||
|
||||
var colorPalette = [
|
||||
'#8aedd5',
|
||||
'#93bc9e',
|
||||
'#cef1db',
|
||||
'#7fe579',
|
||||
'#a6d7c2',
|
||||
'#bef0bb',
|
||||
'#99e2vb',
|
||||
'#94f8a8',
|
||||
'#7de5b8',
|
||||
'#4dfb70'
|
||||
];
|
||||
|
||||
var theme = {
|
||||
color: colorPalette,
|
||||
|
||||
title: {
|
||||
textStyle: {
|
||||
fontWeight: 'normal',
|
||||
color: '#8aedd5'
|
||||
}
|
||||
},
|
||||
|
||||
toolbox: {
|
||||
color: ['#8aedd5', '#8aedd5', '#8aedd5', '#8aedd5']
|
||||
},
|
||||
|
||||
tooltip: {
|
||||
backgroundColor: 'rgba(0,0,0,0.5)',
|
||||
axisPointer: {
|
||||
// Axis indicator, coordinate trigger effective
|
||||
type: 'line', // The default is a straight line: 'line' | 'shadow'
|
||||
lineStyle: {
|
||||
// Straight line indicator style settings
|
||||
color: '#8aedd5',
|
||||
type: 'dashed'
|
||||
},
|
||||
crossStyle: {
|
||||
color: '#8aedd5'
|
||||
},
|
||||
shadowStyle: {
|
||||
// Shadow indicator style settings
|
||||
color: 'rgba(200,200,200,0.3)'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Area scaling controller
|
||||
dataZoom: {
|
||||
dataBackgroundColor: '#eee', // Data background color
|
||||
fillerColor: 'rgba(64,136,41,0.2)', // Fill the color
|
||||
handleColor: '#408829' // Handle color
|
||||
},
|
||||
|
||||
dataRange: {
|
||||
color: ['#93bc92', '#bef0bb']
|
||||
},
|
||||
|
||||
candlestick: {
|
||||
itemStyle: {
|
||||
color: '#8aedd5',
|
||||
color0: '#7fe579'
|
||||
},
|
||||
lineStyle: {
|
||||
width: 1,
|
||||
color: '#8aedd5',
|
||||
color0: '#7fe579'
|
||||
},
|
||||
areaStyle: {
|
||||
color: '#8aedd5',
|
||||
color0: '#93bc9e'
|
||||
}
|
||||
},
|
||||
|
||||
graph: {
|
||||
itemStyle: {
|
||||
color: '#8aedd5'
|
||||
},
|
||||
linkStyle: {
|
||||
color: '#93bc9e'
|
||||
}
|
||||
},
|
||||
|
||||
map: {
|
||||
itemStyle: {
|
||||
color: '#8aedd5'
|
||||
},
|
||||
areaStyle: {
|
||||
color: '#93bc9e'
|
||||
},
|
||||
label: {
|
||||
color: '#cef1db'
|
||||
}
|
||||
},
|
||||
|
||||
gauge: {
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: [
|
||||
[0.2, '#93bc9e'],
|
||||
[0.8, '#8aedd5'],
|
||||
[1, '#a6d7c2']
|
||||
],
|
||||
width: 8
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
echarts.registerTheme('mint', theme);
|
||||
});
|
||||
@@ -1,163 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
(function(root, factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
// AMD. Register as an anonymous module.
|
||||
define(['exports', 'echarts'], factory);
|
||||
} else if (
|
||||
typeof exports === 'object' &&
|
||||
typeof exports.nodeName !== 'string'
|
||||
) {
|
||||
// CommonJS
|
||||
factory(exports, require('echarts/lib/echarts'));
|
||||
} else {
|
||||
// Browser globals
|
||||
factory({}, root.echarts);
|
||||
}
|
||||
})(this, function(exports, echarts) {
|
||||
var log = function(msg) {
|
||||
if (typeof console !== 'undefined') {
|
||||
console && console.error && console.error(msg);
|
||||
}
|
||||
};
|
||||
if (!echarts) {
|
||||
log('ECharts is not Loaded');
|
||||
return;
|
||||
}
|
||||
|
||||
var colorPalette = [
|
||||
'#8b1a2d',
|
||||
'#a7314b',
|
||||
'#e6004c',
|
||||
'#ff8066',
|
||||
'#8e5c4e',
|
||||
'#ff1a66',
|
||||
'#d6c582',
|
||||
'#f0d4af'
|
||||
];
|
||||
|
||||
var theme = {
|
||||
color: colorPalette,
|
||||
|
||||
title: {
|
||||
textStyle: {
|
||||
fontWeight: 'normal',
|
||||
color: '#8b1a2d'
|
||||
}
|
||||
},
|
||||
|
||||
visualMap: {
|
||||
color: ['#8b1a2d', '#a7314b']
|
||||
},
|
||||
|
||||
toolbox: {
|
||||
color: ['#8b1a2d', '#8b1a2d', '#8b1a2d', '#8b1a2d']
|
||||
},
|
||||
|
||||
tooltip: {
|
||||
backgroundColor: 'rgba(0,0,0,0.5)',
|
||||
axisPointer: {
|
||||
// Axis indicator, coordinate trigger effective
|
||||
type: 'line', // The default is a straight line: 'line' | 'shadow'
|
||||
lineStyle: {
|
||||
// Straight line indicator style settings
|
||||
color: '#8b1a2d',
|
||||
type: 'dashed'
|
||||
},
|
||||
crossStyle: {
|
||||
color: '#8b1a2d'
|
||||
},
|
||||
shadowStyle: {
|
||||
// Shadow indicator style settings
|
||||
color: 'rgba(200,200,200,0.3)'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Area scaling controller
|
||||
dataZoom: {
|
||||
dataBackgroundColor: '#eee', // Data background color
|
||||
fillerColor: 'rgba(200,200,200,0.2)', // Fill the color
|
||||
handleColor: '#8b1a2d' // Handle color
|
||||
},
|
||||
|
||||
timeline: {
|
||||
lineStyle: {
|
||||
color: '#8b1a2d'
|
||||
},
|
||||
controlStyle: {
|
||||
color: '#8b1a2d',
|
||||
borderColor: '#8b1a2d'
|
||||
}
|
||||
},
|
||||
|
||||
candlestick: {
|
||||
itemStyle: {
|
||||
color: '#a7314b',
|
||||
color0: '#d6c582'
|
||||
},
|
||||
lineStyle: {
|
||||
width: 1,
|
||||
color: '#8e5c4e',
|
||||
color0: '#f0d4af'
|
||||
},
|
||||
areaStyle: {
|
||||
color: '#8b1a2d',
|
||||
color0: '#ff8066'
|
||||
}
|
||||
},
|
||||
|
||||
map: {
|
||||
itemStyle: {
|
||||
color: '#8b1a2d'
|
||||
},
|
||||
areaStyle: {
|
||||
color: '#ff8066'
|
||||
},
|
||||
label: {
|
||||
color: '#c12e34'
|
||||
}
|
||||
},
|
||||
|
||||
graph: {
|
||||
itemStyle: {
|
||||
color: '#ff8066'
|
||||
},
|
||||
linkStyle: {
|
||||
color: '#8b1a2d'
|
||||
}
|
||||
},
|
||||
|
||||
gauge: {
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: [
|
||||
[0.2, '#a7314b'],
|
||||
[0.8, '#8b1a2d'],
|
||||
[1, '#8e5c4e']
|
||||
],
|
||||
width: 8
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
echarts.registerTheme('red-velvet', theme);
|
||||
});
|
||||
@@ -1,225 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
(function(root, factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
// AMD. Register as an anonymous module.
|
||||
define(['exports', 'echarts'], factory);
|
||||
} else if (
|
||||
typeof exports === 'object' &&
|
||||
typeof exports.nodeName !== 'string'
|
||||
) {
|
||||
// CommonJS
|
||||
factory(exports, require('echarts/lib/echarts'));
|
||||
} else {
|
||||
// Browser globals
|
||||
factory({}, root.echarts);
|
||||
}
|
||||
})(this, function(exports, echarts) {
|
||||
var log = function(msg) {
|
||||
if (typeof console !== 'undefined') {
|
||||
console && console.error && console.error(msg);
|
||||
}
|
||||
};
|
||||
if (!echarts) {
|
||||
log('ECharts is not Loaded');
|
||||
return;
|
||||
}
|
||||
|
||||
var colorPalette = [
|
||||
'#d8361b',
|
||||
'#f16b4c',
|
||||
'#f7b4a9',
|
||||
'#d26666',
|
||||
'#99311c',
|
||||
'#c42703',
|
||||
'#d07e75'
|
||||
];
|
||||
|
||||
var theme = {
|
||||
color: colorPalette,
|
||||
|
||||
title: {
|
||||
textStyle: {
|
||||
fontWeight: 'normal',
|
||||
color: '#d8361b'
|
||||
}
|
||||
},
|
||||
|
||||
visualMap: {
|
||||
color: ['#d8361b', '#ffd2d2']
|
||||
},
|
||||
|
||||
dataRange: {
|
||||
color: ['#bd0707', '#ffd2d2']
|
||||
},
|
||||
|
||||
toolbox: {
|
||||
color: ['#d8361b', '#d8361b', '#d8361b', '#d8361b']
|
||||
},
|
||||
|
||||
tooltip: {
|
||||
backgroundColor: 'rgba(0,0,0,0.5)',
|
||||
axisPointer: {
|
||||
// Axis indicator, coordinate trigger effective
|
||||
type: 'line', // The default is a straight line: 'line' | 'shadow'
|
||||
lineStyle: {
|
||||
// Straight line indicator style settings
|
||||
color: '#d8361b',
|
||||
type: 'dashed'
|
||||
},
|
||||
crossStyle: {
|
||||
color: '#d8361b'
|
||||
},
|
||||
shadowStyle: {
|
||||
// Shadow indicator style settings
|
||||
color: 'rgba(200,200,200,0.3)'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Area scaling controller
|
||||
dataZoom: {
|
||||
dataBackgroundColor: '#eee', // Data background color
|
||||
fillerColor: 'rgba(216,54,27,0.2)', // Fill the color
|
||||
handleColor: '#d8361b' // Handle color
|
||||
},
|
||||
|
||||
grid: {
|
||||
borderWidth: 0
|
||||
},
|
||||
|
||||
categoryAxis: {
|
||||
axisLine: {
|
||||
// Coordinate axis
|
||||
lineStyle: {
|
||||
// Property 'lineStyle' controls line styles
|
||||
color: '#d8361b'
|
||||
}
|
||||
},
|
||||
splitLine: {
|
||||
// Separation line
|
||||
lineStyle: {
|
||||
// Property 'lineStyle' (see lineStyle) controls line styles
|
||||
color: ['#eee']
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
valueAxis: {
|
||||
axisLine: {
|
||||
// Coordinate axis
|
||||
lineStyle: {
|
||||
// Property 'lineStyle' controls line styles
|
||||
color: '#d8361b'
|
||||
}
|
||||
},
|
||||
splitArea: {
|
||||
show: true,
|
||||
areaStyle: {
|
||||
color: ['rgba(250,250,250,0.1)', 'rgba(200,200,200,0.1)']
|
||||
}
|
||||
},
|
||||
splitLine: {
|
||||
// Separation line
|
||||
lineStyle: {
|
||||
// Property 'lineStyle' (see lineStyle) controls line styles
|
||||
color: ['#eee']
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
timeline: {
|
||||
lineStyle: {
|
||||
color: '#d8361b'
|
||||
},
|
||||
controlStyle: {
|
||||
color: '#d8361b',
|
||||
borderColor: '#d8361b'
|
||||
}
|
||||
},
|
||||
|
||||
candlestick: {
|
||||
itemStyle: {
|
||||
color: '#f16b4c',
|
||||
color0: '#f7b4a9'
|
||||
},
|
||||
lineStyle: {
|
||||
width: 1,
|
||||
color: '#d8361b',
|
||||
color0: '#d26666'
|
||||
},
|
||||
areaStyle: {
|
||||
color: '#d8361b',
|
||||
color0: '#d07e75'
|
||||
}
|
||||
},
|
||||
|
||||
graph: {
|
||||
itemStyle: {
|
||||
color: '#d07e75'
|
||||
},
|
||||
linkStyle: {
|
||||
color: '#d8361b'
|
||||
}
|
||||
},
|
||||
|
||||
chord: {
|
||||
padding: 4,
|
||||
itemStyle: {
|
||||
color: '#d07e75',
|
||||
borderWidth: 1,
|
||||
borderColor: 'rgba(128, 128, 128, 0.5)'
|
||||
},
|
||||
lineStyle: {
|
||||
color: 'rgba(128, 128, 128, 0.5)'
|
||||
},
|
||||
areaStyle: {
|
||||
color: '#d8361b'
|
||||
}
|
||||
},
|
||||
|
||||
map: {
|
||||
itemStyle: {
|
||||
color: '#d8361b'
|
||||
},
|
||||
areaStyle: {
|
||||
color: '#d07e75'
|
||||
},
|
||||
label: {
|
||||
color: '#c12e34'
|
||||
}
|
||||
},
|
||||
|
||||
gauge: {
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: [
|
||||
[0.2, '#f16b4c'],
|
||||
[0.8, '#d8361b'],
|
||||
[1, '#99311c']
|
||||
],
|
||||
width: 8
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
echarts.registerTheme('red', theme);
|
||||
});
|
||||
@@ -1,119 +0,0 @@
|
||||
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
(function(root, factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
// AMD. Register as an anonymous module.
|
||||
define(['exports', 'echarts'], factory);
|
||||
} else if (
|
||||
typeof exports === 'object' &&
|
||||
typeof exports.nodeName !== 'string'
|
||||
) {
|
||||
// CommonJS
|
||||
factory(exports, require('echarts/lib/echarts'));
|
||||
} else {
|
||||
// Browser globals
|
||||
factory({}, root.echarts);
|
||||
}
|
||||
})(this, function(exports, echarts) {
|
||||
var log = function(msg) {
|
||||
if (typeof console !== 'undefined') {
|
||||
console && console.error && console.error(msg);
|
||||
}
|
||||
};
|
||||
if (!echarts) {
|
||||
log('ECharts is not Loaded');
|
||||
return;
|
||||
}
|
||||
|
||||
var colorPalette = [
|
||||
'#E01F54',
|
||||
'#001852',
|
||||
'#f5e8c8',
|
||||
'#b8d2c7',
|
||||
'#c6b38e',
|
||||
'#a4d8c2',
|
||||
'#f3d999',
|
||||
'#d3758f',
|
||||
'#dcc392',
|
||||
'#2e4783',
|
||||
'#82b6e9',
|
||||
'#ff6347',
|
||||
'#a092f1',
|
||||
'#0a915d',
|
||||
'#eaf889',
|
||||
'#6699FF',
|
||||
'#ff6666',
|
||||
'#3cb371',
|
||||
'#d5b158',
|
||||
'#38b6b6'
|
||||
];
|
||||
|
||||
var theme = {
|
||||
color: colorPalette,
|
||||
|
||||
visualMap: {
|
||||
color: ['#e01f54', '#e7dbc3'],
|
||||
textStyle: {
|
||||
color: '#333'
|
||||
}
|
||||
},
|
||||
|
||||
candlestick: {
|
||||
itemStyle: {
|
||||
color: '#e01f54',
|
||||
color0: '#001852'
|
||||
},
|
||||
lineStyle: {
|
||||
width: 1,
|
||||
color: '#f5e8c8',
|
||||
color0: '#b8d2c7'
|
||||
},
|
||||
areaStyle: {
|
||||
color: '#a4d8c2',
|
||||
color0: '#f3d999'
|
||||
}
|
||||
},
|
||||
|
||||
graph: {
|
||||
itemStyle: {
|
||||
color: '#a4d8c2'
|
||||
},
|
||||
linkStyle: {
|
||||
color: '#f3d999'
|
||||
}
|
||||
},
|
||||
|
||||
gauge: {
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: [
|
||||
[0.2, '#E01F54'],
|
||||
[0.8, '#b8d2c7'],
|
||||
[1, '#001852']
|
||||
],
|
||||
width: 8
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
echarts.registerTheme('roma', theme);
|
||||
});
|
||||
@@ -1,163 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
(function(root, factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
// AMD. Register as an anonymous module.
|
||||
define(['exports', 'echarts'], factory);
|
||||
} else if (
|
||||
typeof exports === 'object' &&
|
||||
typeof exports.nodeName !== 'string'
|
||||
) {
|
||||
// CommonJS
|
||||
factory(exports, require('echarts/lib/echarts'));
|
||||
} else {
|
||||
// Browser globals
|
||||
factory({}, root.echarts);
|
||||
}
|
||||
})(this, function(exports, echarts) {
|
||||
var log = function(msg) {
|
||||
if (typeof console !== 'undefined') {
|
||||
console && console.error && console.error(msg);
|
||||
}
|
||||
};
|
||||
if (!echarts) {
|
||||
log('ECharts is not Loaded');
|
||||
return;
|
||||
}
|
||||
|
||||
var colorPalette = [
|
||||
'#3f7ea6',
|
||||
'#993366',
|
||||
'#408000',
|
||||
'#8c6f56',
|
||||
'#a65149',
|
||||
'#731f17',
|
||||
'#adc2eb',
|
||||
'#d9c3b0'
|
||||
];
|
||||
|
||||
var theme = {
|
||||
color: colorPalette,
|
||||
|
||||
title: {
|
||||
textStyle: {
|
||||
fontWeight: 'normal',
|
||||
color: '#3f7ea6'
|
||||
}
|
||||
},
|
||||
|
||||
visualMap: {
|
||||
color: ['#3f7ea6', '#993366']
|
||||
},
|
||||
|
||||
toolbox: {
|
||||
color: ['#3f7ea6', '#3f7ea6', '#3f7ea6', '#3f7ea6']
|
||||
},
|
||||
|
||||
tooltip: {
|
||||
backgroundColor: 'rgba(0,0,0,0.5)',
|
||||
axisPointer: {
|
||||
// Axis indicator, coordinate trigger effective
|
||||
type: 'line', // The default is a straight line: 'line' | 'shadow'
|
||||
lineStyle: {
|
||||
// Straight line indicator style settings
|
||||
color: '#3f7ea6',
|
||||
type: 'dashed'
|
||||
},
|
||||
crossStyle: {
|
||||
color: '#3f7ea6'
|
||||
},
|
||||
shadowStyle: {
|
||||
// Shadow indicator style settings
|
||||
color: 'rgba(200,200,200,0.3)'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Area scaling controller
|
||||
dataZoom: {
|
||||
dataBackgroundColor: '#eee', // Data background color
|
||||
fillerColor: 'rgba(200,200,200,0.2)', // Fill the color
|
||||
handleColor: '#3f7ea6' // Handle color
|
||||
},
|
||||
|
||||
timeline: {
|
||||
lineStyle: {
|
||||
color: '#3f7ea6'
|
||||
},
|
||||
controlStyle: {
|
||||
color: '#3f7ea6',
|
||||
borderColor: '#3f7ea6'
|
||||
}
|
||||
},
|
||||
|
||||
candlestick: {
|
||||
itemStyle: {
|
||||
color: '#d9c3b0',
|
||||
color0: '#8c6f56'
|
||||
},
|
||||
lineStyle: {
|
||||
width: 1,
|
||||
color: '#731f17',
|
||||
color0: '#a65149'
|
||||
},
|
||||
areaStyle: {
|
||||
color: '#3f7ea6',
|
||||
color0: '#993366'
|
||||
}
|
||||
},
|
||||
|
||||
map: {
|
||||
itemStyle: {
|
||||
color: '#d9c3b0'
|
||||
},
|
||||
areaStyle: {
|
||||
color: '#ddd'
|
||||
},
|
||||
label: {
|
||||
color: '#c12e34'
|
||||
}
|
||||
},
|
||||
|
||||
graph: {
|
||||
itemStyle: {
|
||||
color: '#993366'
|
||||
},
|
||||
linkStyle: {
|
||||
color: '#3f7ea6'
|
||||
}
|
||||
},
|
||||
|
||||
gauge: {
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: [
|
||||
[0.2, '#d9c3b0'],
|
||||
[0.8, '#3f7ea6'],
|
||||
[1, '#731f17']
|
||||
],
|
||||
width: 8
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
echarts.registerTheme('royal', theme);
|
||||
});
|
||||
@@ -1,140 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
(function(root, factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
// AMD. Register as an anonymous module.
|
||||
define(['exports', 'echarts'], factory);
|
||||
} else if (
|
||||
typeof exports === 'object' &&
|
||||
typeof exports.nodeName !== 'string'
|
||||
) {
|
||||
// CommonJS
|
||||
factory(exports, require('echarts/lib/echarts'));
|
||||
} else {
|
||||
// Browser globals
|
||||
factory({}, root.echarts);
|
||||
}
|
||||
})(this, function(exports, echarts) {
|
||||
var log = function(msg) {
|
||||
if (typeof console !== 'undefined') {
|
||||
console && console.error && console.error(msg);
|
||||
}
|
||||
};
|
||||
if (!echarts) {
|
||||
log('ECharts is not Loaded');
|
||||
return;
|
||||
}
|
||||
|
||||
var colorPalette = [
|
||||
'#e52c3c',
|
||||
'#f7b1ab',
|
||||
'#fa506c',
|
||||
'#f59288',
|
||||
'#f8c4d8',
|
||||
'#e54f5c',
|
||||
'#f06d5c',
|
||||
'#e54f80',
|
||||
'#f29c9f',
|
||||
'#eeb5b7'
|
||||
];
|
||||
|
||||
var theme = {
|
||||
color: colorPalette,
|
||||
|
||||
title: {
|
||||
textStyle: {
|
||||
fontWeight: 'normal',
|
||||
color: '#e52c3c'
|
||||
}
|
||||
},
|
||||
|
||||
visualMap: {
|
||||
color: ['#e52c3c', '#f7b1ab']
|
||||
},
|
||||
|
||||
dataRange: {
|
||||
color: ['#e52c3c', '#f7b1ab']
|
||||
},
|
||||
|
||||
candlestick: {
|
||||
itemStyle: {
|
||||
color: '#e52c3c',
|
||||
color0: '#f59288'
|
||||
},
|
||||
lineStyle: {
|
||||
width: 1,
|
||||
color: '#e52c3c',
|
||||
color0: '#f59288'
|
||||
},
|
||||
areaStyle: {
|
||||
color: '#fa506c',
|
||||
color0: '#f8c4d8'
|
||||
}
|
||||
},
|
||||
|
||||
map: {
|
||||
itemStyle: {
|
||||
color: '#e52c3c',
|
||||
borderColor: '#fff',
|
||||
borderWidth: 1
|
||||
},
|
||||
areaStyle: {
|
||||
color: '#ccc'
|
||||
},
|
||||
label: {
|
||||
color: 'rgba(139,69,19,1)',
|
||||
show: false
|
||||
}
|
||||
},
|
||||
|
||||
graph: {
|
||||
itemStyle: {
|
||||
color: '#f2385a'
|
||||
},
|
||||
nodeStyle: {
|
||||
brushType: 'both',
|
||||
strokeColor: '#e54f5c'
|
||||
},
|
||||
linkStyle: {
|
||||
color: '#f2385a',
|
||||
strokeColor: '#e54f5c'
|
||||
},
|
||||
label: {
|
||||
color: '#f2385a',
|
||||
show: false
|
||||
}
|
||||
},
|
||||
|
||||
gauge: {
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: [
|
||||
[0.2, '#e52c3c'],
|
||||
[0.8, '#f7b1ab'],
|
||||
[1, '#fa506c']
|
||||
],
|
||||
width: 8
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
echarts.registerTheme('sakura', theme);
|
||||
});
|
||||
@@ -1,175 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
(function(root, factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
// AMD. Register as an anonymous module.
|
||||
define(['exports', 'echarts'], factory);
|
||||
} else if (
|
||||
typeof exports === 'object' &&
|
||||
typeof exports.nodeName !== 'string'
|
||||
) {
|
||||
// CommonJS
|
||||
factory(exports, require('echarts/lib/echarts'));
|
||||
} else {
|
||||
// Browser globals
|
||||
factory({}, root.echarts);
|
||||
}
|
||||
})(this, function(exports, echarts) {
|
||||
var log = function(msg) {
|
||||
if (typeof console !== 'undefined') {
|
||||
console && console.error && console.error(msg);
|
||||
}
|
||||
};
|
||||
if (!echarts) {
|
||||
log('ECharts is not Loaded');
|
||||
return;
|
||||
}
|
||||
|
||||
var colorPalette = [
|
||||
'#c12e34',
|
||||
'#e6b600',
|
||||
'#0098d9',
|
||||
'#2b821d',
|
||||
'#005eaa',
|
||||
'#339ca8',
|
||||
'#cda819',
|
||||
'#32a487'
|
||||
];
|
||||
|
||||
var theme = {
|
||||
color: colorPalette,
|
||||
|
||||
title: {
|
||||
textStyle: {
|
||||
fontWeight: 'normal'
|
||||
}
|
||||
},
|
||||
|
||||
visualMap: {
|
||||
color: ['#1790cf', '#a2d4e6']
|
||||
},
|
||||
|
||||
toolbox: {
|
||||
iconStyle: {
|
||||
borderColor: '#06467c'
|
||||
}
|
||||
},
|
||||
|
||||
tooltip: {
|
||||
backgroundColor: 'rgba(0,0,0,0.6)'
|
||||
},
|
||||
|
||||
dataZoom: {
|
||||
dataBackgroundColor: '#dedede',
|
||||
fillerColor: 'rgba(154,217,247,0.2)',
|
||||
handleColor: '#005eaa'
|
||||
},
|
||||
|
||||
timeline: {
|
||||
lineStyle: {
|
||||
color: '#005eaa'
|
||||
},
|
||||
controlStyle: {
|
||||
color: '#005eaa',
|
||||
borderColor: '#005eaa'
|
||||
}
|
||||
},
|
||||
|
||||
candlestick: {
|
||||
itemStyle: {
|
||||
color: '#c12e34',
|
||||
color0: '#2b821d'
|
||||
},
|
||||
lineStyle: {
|
||||
width: 1,
|
||||
color: '#c12e34',
|
||||
color0: '#2b821d'
|
||||
},
|
||||
areaStyle: {
|
||||
color: '#e6b600',
|
||||
color0: '#005eaa'
|
||||
}
|
||||
},
|
||||
|
||||
graph: {
|
||||
itemStyle: {
|
||||
color: '#e6b600'
|
||||
},
|
||||
linkStyle: {
|
||||
color: '#005eaa'
|
||||
}
|
||||
},
|
||||
|
||||
map: {
|
||||
itemStyle: {
|
||||
color: '#f2385a',
|
||||
borderColor: '#eee',
|
||||
areaColor: '#ddd'
|
||||
},
|
||||
areaStyle: {
|
||||
color: '#ddd'
|
||||
},
|
||||
label: {
|
||||
color: '#c12e34'
|
||||
}
|
||||
},
|
||||
|
||||
gauge: {
|
||||
axisLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
color: [
|
||||
[0.2, '#2b821d'],
|
||||
[0.8, '#005eaa'],
|
||||
[1, '#c12e34']
|
||||
],
|
||||
width: 5
|
||||
}
|
||||
},
|
||||
axisTick: {
|
||||
splitNumber: 10,
|
||||
length: 8,
|
||||
lineStyle: {
|
||||
color: 'auto'
|
||||
}
|
||||
},
|
||||
axisLabel: {
|
||||
color: 'auto'
|
||||
},
|
||||
splitLine: {
|
||||
length: 12,
|
||||
lineStyle: {
|
||||
color: 'auto'
|
||||
}
|
||||
},
|
||||
pointer: {
|
||||
length: '90%',
|
||||
width: 3,
|
||||
color: 'auto'
|
||||
},
|
||||
title: {
|
||||
color: '#333'
|
||||
},
|
||||
detail: {
|
||||
color: 'auto'
|
||||
}
|
||||
}
|
||||
};
|
||||
echarts.registerTheme('shine', theme);
|
||||
});
|
||||
@@ -1,184 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
(function(root, factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
// AMD. Register as an anonymous module.
|
||||
define(['exports', 'echarts'], factory);
|
||||
} else if (
|
||||
typeof exports === 'object' &&
|
||||
typeof exports.nodeName !== 'string'
|
||||
) {
|
||||
// CommonJS
|
||||
factory(exports, require('echarts/lib/echarts'));
|
||||
} else {
|
||||
// Browser globals
|
||||
factory({}, root.echarts);
|
||||
}
|
||||
})(this, function(exports, echarts) {
|
||||
var log = function(msg) {
|
||||
if (typeof console !== 'undefined') {
|
||||
console && console.error && console.error(msg);
|
||||
}
|
||||
};
|
||||
if (!echarts) {
|
||||
log('ECharts is not Loaded');
|
||||
return;
|
||||
}
|
||||
|
||||
var colorPalette = [
|
||||
'#4d4d4d',
|
||||
'#3a5897',
|
||||
'#007bb6',
|
||||
'#7094db',
|
||||
'#0080ff',
|
||||
'#b3b3ff',
|
||||
'#00bdec',
|
||||
'#33ccff',
|
||||
'#ccddff',
|
||||
'#eeeeee'
|
||||
];
|
||||
|
||||
var theme = {
|
||||
color: colorPalette,
|
||||
|
||||
title: {
|
||||
textStyle: {
|
||||
fontWeight: 'normal',
|
||||
color: '#00aecd'
|
||||
}
|
||||
},
|
||||
|
||||
visualMap: {
|
||||
color: ['#00aecd', '#a2d4e6']
|
||||
},
|
||||
|
||||
toolbox: {
|
||||
color: ['#00aecd', '#00aecd', '#00aecd', '#00aecd']
|
||||
},
|
||||
|
||||
tooltip: {
|
||||
backgroundColor: 'rgba(0,0,0,0.5)',
|
||||
axisPointer: {
|
||||
// Axis indicator, coordinate trigger effective
|
||||
type: 'line', // The default is a straight line: 'line' | 'shadow'
|
||||
lineStyle: {
|
||||
// Straight line indicator style settings
|
||||
color: '#00aecd',
|
||||
type: 'dashed'
|
||||
},
|
||||
crossStyle: {
|
||||
color: '#00aecd'
|
||||
},
|
||||
shadowStyle: {
|
||||
// Shadow indicator style settings
|
||||
color: 'rgba(200,200,200,0.3)'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Area scaling controller
|
||||
dataZoom: {
|
||||
dataBackgroundColor: '#eee', // Data background color
|
||||
fillerColor: 'rgba(144,197,237,0.2)', // Fill the color
|
||||
handleColor: '#00aecd' // Handle color
|
||||
},
|
||||
|
||||
timeline: {
|
||||
lineStyle: {
|
||||
color: '#00aecd'
|
||||
},
|
||||
controlStyle: {
|
||||
color: '#00aecd',
|
||||
},
|
||||
emphasis: {
|
||||
controlStyle: {
|
||||
color: '#00aecd'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
candlestick: {
|
||||
itemStyle: {
|
||||
color: '#ddd',
|
||||
color0: '#eee'
|
||||
},
|
||||
lineStyle: {
|
||||
width: 1,
|
||||
color: '#33ccff',
|
||||
color0: '#1bb4cf'
|
||||
},
|
||||
areaStyle: {
|
||||
color: '#7094db',
|
||||
color0: '#33ccff'
|
||||
}
|
||||
},
|
||||
|
||||
chord: {
|
||||
padding: 4,
|
||||
itemStyle: {
|
||||
color: '#7094db',
|
||||
borderWidth: 1,
|
||||
borderColor: 'rgba(128, 128, 128, 0.5)'
|
||||
},
|
||||
lineStyle: {
|
||||
color: 'rgba(128, 128, 128, 0.5)'
|
||||
},
|
||||
areaStyle: {
|
||||
color: '#33ccff'
|
||||
}
|
||||
},
|
||||
|
||||
graph: {
|
||||
itemStyle: {
|
||||
color: '#7094db'
|
||||
},
|
||||
linkStyle: {
|
||||
color: '#33ccff'
|
||||
}
|
||||
},
|
||||
|
||||
map: {
|
||||
itemStyle: {
|
||||
color: '#7094db'
|
||||
},
|
||||
areaStyle: {
|
||||
color: '#33ccff'
|
||||
},
|
||||
label: {
|
||||
color: '#ddd'
|
||||
}
|
||||
},
|
||||
|
||||
gauge: {
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: [
|
||||
[0.2, '#dddddd'],
|
||||
[0.8, '#00aecd'],
|
||||
[1, '#33ccff']
|
||||
],
|
||||
width: 8
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
echarts.registerTheme('tech-blue', theme);
|
||||
});
|
||||
@@ -1,88 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
export default {
|
||||
title: {
|
||||
text: 'Area Chart',
|
||||
left: 'center',
|
||||
top: '3%',
|
||||
textStyle: {
|
||||
fontWeight: 'normal'
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '12%',
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday','Sunday']
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
splitNumber: 3
|
||||
},
|
||||
dataZoom: {
|
||||
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name:'Email',
|
||||
type:'line',
|
||||
stack: '总量',
|
||||
areaStyle: {},
|
||||
data:[120, 132, 101, 134, 90, 230, 210]
|
||||
},
|
||||
{
|
||||
name:'联盟广告',
|
||||
type:'line',
|
||||
stack: '总量',
|
||||
areaStyle: {},
|
||||
data:[220, 182, 191, 234, 290, 330, 310]
|
||||
},
|
||||
{
|
||||
name:'视频广告',
|
||||
type:'line',
|
||||
stack: '总量',
|
||||
areaStyle: {},
|
||||
data:[150, 232, 201, 154, 190, 330, 410]
|
||||
},
|
||||
{
|
||||
name:'直接访问',
|
||||
type:'line',
|
||||
stack: '总量',
|
||||
areaStyle: {},
|
||||
data:[320, 332, 301, 334, 390, 330, 320]
|
||||
},
|
||||
{
|
||||
name:'搜索引擎',
|
||||
type:'line',
|
||||
stack: '总量',
|
||||
label: {
|
||||
show: true,
|
||||
position: 'top'
|
||||
},
|
||||
areaStyle: {},
|
||||
data:[820, 932, 901, 934, 1290, 1330, 1320]
|
||||
}
|
||||
]
|
||||
};
|
||||
@@ -1,107 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
export default {
|
||||
title: {
|
||||
text: 'Bar Chart',
|
||||
left: 'center',
|
||||
top: '3%',
|
||||
textStyle: {
|
||||
fontWeight: 'normal'
|
||||
}
|
||||
},
|
||||
toolbox: {
|
||||
top: '3%',
|
||||
feature: {
|
||||
magicType: {
|
||||
type: ['line', 'bar', 'stack', 'tiled']
|
||||
},
|
||||
restore: {},
|
||||
dataZoom: {},
|
||||
saveAsImage: {}
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
left: '13%',
|
||||
right: '5%',
|
||||
bottom: '5%',
|
||||
textStyle: {
|
||||
fontWeight: 'normal'
|
||||
}
|
||||
},
|
||||
xAxis: {
|
||||
type: 'value'
|
||||
},
|
||||
yAxis: {
|
||||
type: 'category',
|
||||
data: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday','Sunday']
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name:'直接访问',
|
||||
type:'bar',
|
||||
stack: '总量',
|
||||
label: {
|
||||
show: true,
|
||||
position: 'insideRight'
|
||||
},
|
||||
data:[320, 302, 301, 334, 390, 330, 320]
|
||||
},
|
||||
{
|
||||
name:'邮件营销',
|
||||
type:'bar',
|
||||
stack: '总量',
|
||||
label: {
|
||||
show: true,
|
||||
position: 'insideRight'
|
||||
},
|
||||
data:[120, 132, 101, 134, 90, 230, 210]
|
||||
},
|
||||
{
|
||||
name:'联盟广告',
|
||||
type:'bar',
|
||||
stack: '总量',
|
||||
label: {
|
||||
show: true,
|
||||
position: 'insideRight'
|
||||
},
|
||||
data:[220, 182, 191, 234, 290, 330, 310]
|
||||
},
|
||||
{
|
||||
name:'视频广告',
|
||||
type:'bar',
|
||||
stack: '总量',
|
||||
label: {
|
||||
show: true,
|
||||
position: 'insideRight'
|
||||
},
|
||||
data:[150, 212, 201, 154, 190, 330, 410]
|
||||
},
|
||||
{
|
||||
name:'搜索引擎',
|
||||
type:'bar',
|
||||
stack: '总量',
|
||||
label: {
|
||||
show: true,
|
||||
position: 'insideRight'
|
||||
},
|
||||
data:[820, 832, 901, 934, 1290, 1330, 1320]
|
||||
}
|
||||
]
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,141 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
export default {
|
||||
visualMap: {
|
||||
show: true,
|
||||
min: 0,
|
||||
max: 1500,
|
||||
right: 50,
|
||||
top: 'middle',
|
||||
text:['高','低']
|
||||
// orient: 'horizontal'
|
||||
},
|
||||
selectedMode: 'single',
|
||||
series : [
|
||||
{
|
||||
name: 'iphone3',
|
||||
type: 'map',
|
||||
map: 'china',
|
||||
showLegendSymbol: true,
|
||||
label: {
|
||||
show: false,
|
||||
},
|
||||
emphasis: {
|
||||
label: {
|
||||
show: false,
|
||||
}
|
||||
},
|
||||
data:[
|
||||
{name: '北京',value: 500},
|
||||
{name: '天津',value: 500},
|
||||
{name: '上海',value: 500},
|
||||
{name: '重庆',value: 500},
|
||||
{name: '河北',value: 500},
|
||||
{name: '河南',value: 500},
|
||||
{name: '云南',value: 500},
|
||||
{name: '辽宁',value: 500},
|
||||
{name: '黑龙江',value: 500},
|
||||
{name: '湖南',value: 500},
|
||||
{name: '安徽',value: 500},
|
||||
{name: '山东',value: 500},
|
||||
{name: '新疆',value: 500},
|
||||
{name: '江苏',value: 500},
|
||||
{name: '浙江',value: 500},
|
||||
{name: '江西',value: 500},
|
||||
{name: '湖北',value: 500},
|
||||
{name: '广西',value: 500},
|
||||
{name: '甘肃',value: 500},
|
||||
{name: '山西',value: 500},
|
||||
{name: '内蒙古',value: 500},
|
||||
{name: '陕西',value: 500},
|
||||
{name: '吉林',value: 500},
|
||||
{name: '福建',value: 500},
|
||||
{name: '贵州',value: 500},
|
||||
{name: '广东',value: 500},
|
||||
{name: '青海',value: 500},
|
||||
{name: '西藏',value: 500},
|
||||
{name: '四川',value: 500},
|
||||
{name: '宁夏',value: 500},
|
||||
{name: '海南',value: 500},
|
||||
{name: '台湾',value: 500},
|
||||
{name: '香港',value: 500},
|
||||
{name: '澳门',value: 500}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'iphone4',
|
||||
type: 'map',
|
||||
mapType: 'china',
|
||||
showLegendSymbol: true,
|
||||
label: {
|
||||
show: false,
|
||||
},
|
||||
emphasis: {
|
||||
label: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
data:[
|
||||
{name: '北京',value: 500},
|
||||
{name: '天津',value: 500},
|
||||
{name: '上海',value: 500},
|
||||
{name: '重庆',value: 500},
|
||||
{name: '河北',value: 500},
|
||||
{name: '安徽',value: 500},
|
||||
{name: '新疆',value: 500},
|
||||
{name: '浙江',value: 500},
|
||||
{name: '江西',value: 500},
|
||||
{name: '山西',value: 500},
|
||||
{name: '内蒙古',value: 500},
|
||||
{name: '吉林',value: 500},
|
||||
{name: '福建',value: 500},
|
||||
{name: '广东',value: 500},
|
||||
{name: '西藏',value: 500},
|
||||
{name: '四川',value: 500},
|
||||
{name: '宁夏',value: 500},
|
||||
{name: '香港',value: 500},
|
||||
{name: '澳门',value: 500}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'iphone5',
|
||||
type: 'map',
|
||||
mapType: 'china',
|
||||
showLegendSymbol: true,
|
||||
label: {
|
||||
show: false,
|
||||
},
|
||||
emphasis: {
|
||||
label: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
data:[
|
||||
{name: '北京',value: 500},
|
||||
{name: '天津',value: 500},
|
||||
{name: '上海',value: 500},
|
||||
{name: '广东',value: 500},
|
||||
{name: '台湾',value: 500},
|
||||
{name: '香港',value: 500},
|
||||
{name: '澳门',value: 500}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
@@ -1,87 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
export default {
|
||||
legend: {
|
||||
bottom: '5%',
|
||||
data: ['rose1', 'rose2', 'rose3', 'rose4']
|
||||
},
|
||||
series : [
|
||||
{
|
||||
name:'半径模式',
|
||||
type:'pie',
|
||||
radius : [20, 80],
|
||||
center : ['25%', 110],
|
||||
label: {
|
||||
show: false,
|
||||
},
|
||||
lableLine: {
|
||||
show: false,
|
||||
},
|
||||
emphasis: {
|
||||
label: {
|
||||
show: true
|
||||
},
|
||||
lableLine: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
data:[
|
||||
{value:10, name:'rose1'},
|
||||
{value:5, name:'rose2'},
|
||||
{value:15, name:'rose3'},
|
||||
{value:25, name:'rose4'},
|
||||
{value:20, name:'rose5'},
|
||||
{value:35, name:'rose6'},
|
||||
{value:30, name:'rose7'},
|
||||
{value:40, name:'rose8'}
|
||||
]
|
||||
},
|
||||
{
|
||||
name:'面积模式',
|
||||
type:'pie',
|
||||
radius : [30, 80],
|
||||
center : ['75%', 110],
|
||||
roseType : 'area',
|
||||
labelLine: {
|
||||
length: 5
|
||||
},
|
||||
data:[
|
||||
{value:10, name:'rose1'},
|
||||
{value:5, name:'rose2'},
|
||||
{value:15, name:'rose3'},
|
||||
{value:25, name:'rose4'},
|
||||
{value:20, name:'rose5'},
|
||||
{value:35, name:'rose6'},
|
||||
{value:30, name:'rose7'},
|
||||
{value:40, name:'rose8'}
|
||||
]
|
||||
},
|
||||
{
|
||||
name:'仪表盘',
|
||||
type:'gauge',
|
||||
radius : 100,
|
||||
center : ['50%', 280],
|
||||
detail : {formatter:'{value}%'},
|
||||
data:[
|
||||
{value:50, name:'Gauge'}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
@@ -1,201 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
export default {
|
||||
timeline: {
|
||||
left: '2%',
|
||||
right: '2%',
|
||||
data: [
|
||||
'2002-01-01','2003-01-01','2004-01-01',
|
||||
{
|
||||
value: '2005-01-01',
|
||||
symbol: 'diamond',
|
||||
symbolSize: 16
|
||||
},
|
||||
'2006-01-01', '2007-01-01','2008-01-01','2009-01-01','2010-01-01',
|
||||
{
|
||||
value: '2011-01-01',
|
||||
symbol: 'diamond',
|
||||
symbolSize: 18
|
||||
}
|
||||
],
|
||||
label: {
|
||||
formatter : function(s) {
|
||||
return (new Date(s)).getFullYear();
|
||||
}
|
||||
}
|
||||
},
|
||||
options: [{
|
||||
grid: {
|
||||
left: '13%',
|
||||
right: '5%',
|
||||
bottom: '20%'
|
||||
},
|
||||
xAxis: {
|
||||
type : 'value',
|
||||
scale:true,
|
||||
axisLabel : {
|
||||
formatter: '{value} cm'
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
type : 'value',
|
||||
scale:true,
|
||||
axisLabel : {
|
||||
formatter: '{value} kg'
|
||||
}
|
||||
},
|
||||
series : [
|
||||
{
|
||||
name:'女性',
|
||||
type:'scatter',
|
||||
data: [[161.2, 51.6], [167.5, 59.0], [159.5, 49.2], [157.0, 63.0], [155.8, 53.6],
|
||||
[170.0, 59.0], [159.1, 47.6], [166.0, 69.8], [176.2, 66.8], [160.2, 75.2],
|
||||
[172.5, 55.2], [170.9, 54.2], [172.9, 62.5], [153.4, 42.0], [160.0, 50.0],
|
||||
[147.2, 49.8], [168.2, 49.2], [175.0, 73.2], [157.0, 47.8], [167.6, 68.8],
|
||||
[159.5, 50.6], [175.0, 82.5], [166.8, 57.2], [176.5, 87.8], [170.2, 72.8],
|
||||
[174.0, 54.5], [173.0, 59.8], [179.9, 67.3], [170.5, 67.8], [160.0, 47.0],
|
||||
[154.4, 46.2], [162.0, 55.0], [176.5, 83.0], [160.0, 54.4], [152.0, 45.8],
|
||||
[162.1, 53.6], [170.0, 73.2], [160.2, 52.1], [161.3, 67.9], [166.4, 56.6],
|
||||
[168.9, 62.3], [163.8, 58.5], [167.6, 54.5], [160.0, 50.2], [161.3, 60.3],
|
||||
[167.6, 58.3], [165.1, 56.2], [160.0, 50.2], [170.0, 72.9], [157.5, 59.8],
|
||||
[167.6, 61.0], [160.7, 69.1], [163.2, 55.9], [152.4, 46.5], [157.5, 54.3],
|
||||
[168.3, 54.8], [180.3, 60.7], [165.5, 60.0], [165.0, 62.0], [164.5, 60.3],
|
||||
[156.0, 52.7], [160.0, 74.3], [163.0, 62.0], [165.7, 73.1], [161.0, 80.0],
|
||||
[162.0, 54.7], [166.0, 53.2], [174.0, 75.7], [172.7, 61.1], [167.6, 55.7],
|
||||
[151.1, 48.7], [164.5, 52.3], [163.5, 50.0], [152.0, 59.3], [169.0, 62.5],
|
||||
[164.0, 55.7], [161.2, 54.8], [155.0, 45.9], [170.0, 70.6], [176.2, 67.2],
|
||||
[170.0, 69.4], [162.5, 58.2], [170.3, 64.8], [164.1, 71.6], [169.5, 52.8],
|
||||
[163.2, 59.8], [154.5, 49.0], [159.8, 50.0], [173.2, 69.2], [170.0, 55.9],
|
||||
[161.4, 63.4], [169.0, 58.2], [166.2, 58.6], [159.4, 45.7], [162.5, 52.2],
|
||||
[159.0, 48.6], [162.8, 57.8], [159.0, 55.6], [179.8, 66.8], [162.9, 59.4],
|
||||
[161.0, 53.6], [151.1, 73.2], [168.2, 53.4], [168.9, 69.0], [173.2, 58.4],
|
||||
[171.8, 56.2], [178.0, 70.6], [164.3, 59.8], [163.0, 72.0], [168.5, 65.2],
|
||||
[166.8, 56.6], [172.7, 105.2], [163.5, 51.8], [169.4, 63.4], [167.8, 59.0],
|
||||
[159.5, 47.6], [167.6, 63.0], [161.2, 55.2], [160.0, 45.0], [163.2, 54.0],
|
||||
[162.2, 50.2], [161.3, 60.2], [149.5, 44.8], [157.5, 58.8], [163.2, 56.4],
|
||||
[172.7, 62.0], [155.0, 49.2], [156.5, 67.2], [164.0, 53.8], [160.9, 54.4],
|
||||
[162.8, 58.0], [167.0, 59.8], [160.0, 54.8], [160.0, 43.2], [168.9, 60.5],
|
||||
[158.2, 46.4], [156.0, 64.4], [160.0, 48.8], [167.1, 62.2], [158.0, 55.5],
|
||||
[167.6, 57.8], [156.0, 54.6], [162.1, 59.2], [173.4, 52.7], [159.8, 53.2],
|
||||
[170.5, 64.5], [159.2, 51.8], [157.5, 56.0], [161.3, 63.6], [162.6, 63.2],
|
||||
[160.0, 59.5], [168.9, 56.8], [165.1, 64.1], [162.6, 50.0], [165.1, 72.3],
|
||||
[166.4, 55.0], [160.0, 55.9], [152.4, 60.4], [170.2, 69.1], [162.6, 84.5],
|
||||
[170.2, 55.9], [158.8, 55.5], [172.7, 69.5], [167.6, 76.4], [162.6, 61.4],
|
||||
[167.6, 65.9], [156.2, 58.6], [175.2, 66.8], [172.1, 56.6], [162.6, 58.6],
|
||||
[160.0, 55.9], [165.1, 59.1], [182.9, 81.8], [166.4, 70.7], [165.1, 56.8],
|
||||
[177.8, 60.0], [165.1, 58.2], [175.3, 72.7], [154.9, 54.1], [158.8, 49.1],
|
||||
[172.7, 75.9], [168.9, 55.0], [161.3, 57.3], [167.6, 55.0], [165.1, 65.5],
|
||||
[175.3, 65.5], [157.5, 48.6], [163.8, 58.6], [167.6, 63.6], [165.1, 55.2],
|
||||
[165.1, 62.7], [168.9, 56.6], [162.6, 53.9], [164.5, 63.2], [176.5, 73.6],
|
||||
[168.9, 62.0], [175.3, 63.6], [159.4, 53.2], [160.0, 53.4], [170.2, 55.0],
|
||||
[162.6, 70.5], [167.6, 54.5], [162.6, 54.5], [160.7, 55.9], [160.0, 59.0],
|
||||
[157.5, 63.6], [162.6, 54.5], [152.4, 47.3], [170.2, 67.7], [165.1, 80.9],
|
||||
[172.7, 70.5], [165.1, 60.9], [170.2, 63.6], [170.2, 54.5], [170.2, 59.1],
|
||||
[161.3, 70.5], [167.6, 52.7], [167.6, 62.7], [165.1, 86.3], [162.6, 66.4],
|
||||
[152.4, 67.3], [168.9, 63.0], [170.2, 73.6], [175.2, 62.3], [175.2, 57.7],
|
||||
[160.0, 55.4], [165.1, 104.1], [174.0, 55.5], [170.2, 77.3], [160.0, 80.5],
|
||||
[167.6, 64.5], [167.6, 72.3], [167.6, 61.4], [154.9, 58.2], [162.6, 81.8],
|
||||
[175.3, 63.6], [171.4, 53.4], [157.5, 54.5], [165.1, 53.6], [160.0, 60.0],
|
||||
[174.0, 73.6], [162.6, 61.4], [174.0, 55.5], [162.6, 63.6], [161.3, 60.9],
|
||||
[156.2, 60.0], [149.9, 46.8], [169.5, 57.3], [160.0, 64.1], [175.3, 63.6],
|
||||
[169.5, 67.3], [160.0, 75.5], [172.7, 68.2], [162.6, 61.4], [157.5, 76.8],
|
||||
[176.5, 71.8], [164.4, 55.5], [160.7, 48.6], [174.0, 66.4], [163.8, 67.3]
|
||||
],
|
||||
markPoint : {
|
||||
data : [
|
||||
{type : 'max', name: '最大值'},
|
||||
{type : 'min', name: '最小值'}
|
||||
]
|
||||
},
|
||||
markLine : {
|
||||
data : [
|
||||
{type : 'average', name: '平均值'}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
name:'男性',
|
||||
type:'scatter',
|
||||
data: [[174.0, 65.6], [175.3, 71.8], [193.5, 80.7], [186.5, 72.6], [187.2, 78.8],
|
||||
[181.5, 74.8], [184.0, 86.4], [184.5, 78.4], [175.0, 62.0], [184.0, 81.6],
|
||||
[180.0, 76.6], [177.8, 83.6], [192.0, 90.0], [176.0, 74.6], [174.0, 71.0],
|
||||
[184.0, 79.6], [192.7, 93.8], [171.5, 70.0], [173.0, 72.4], [176.0, 85.9],
|
||||
[176.0, 78.8], [180.5, 77.8], [172.7, 66.2], [176.0, 86.4], [173.5, 81.8],
|
||||
[178.0, 89.6], [180.3, 82.8], [180.3, 76.4], [164.5, 63.2], [173.0, 60.9],
|
||||
[183.5, 74.8], [175.5, 70.0], [188.0, 72.4], [189.2, 84.1], [172.8, 69.1],
|
||||
[170.0, 59.5], [182.0, 67.2], [170.0, 61.3], [177.8, 68.6], [184.2, 80.1],
|
||||
[186.7, 87.8], [171.4, 84.7], [172.7, 73.4], [175.3, 72.1], [180.3, 82.6],
|
||||
[182.9, 88.7], [188.0, 84.1], [177.2, 94.1], [172.1, 74.9], [167.0, 59.1],
|
||||
[169.5, 75.6], [174.0, 86.2], [172.7, 75.3], [182.2, 87.1], [164.1, 55.2],
|
||||
[163.0, 57.0], [171.5, 61.4], [184.2, 76.8], [174.0, 86.8], [174.0, 72.2],
|
||||
[177.0, 71.6], [186.0, 84.8], [167.0, 68.2], [171.8, 66.1], [182.0, 72.0],
|
||||
[167.0, 64.6], [177.8, 74.8], [164.5, 70.0], [192.0, 101.6], [175.5, 63.2],
|
||||
[171.2, 79.1], [181.6, 78.9], [167.4, 67.7], [181.1, 66.0], [177.0, 68.2],
|
||||
[174.5, 63.9], [177.5, 72.0], [170.5, 56.8], [182.4, 74.5], [197.1, 90.9],
|
||||
[180.1, 93.0], [175.5, 80.9], [180.6, 72.7], [184.4, 68.0], [175.5, 70.9],
|
||||
[180.6, 72.5], [177.0, 72.5], [177.1, 83.4], [181.6, 75.5], [176.5, 73.0],
|
||||
[175.0, 70.2], [174.0, 73.4], [165.1, 70.5], [177.0, 68.9], [192.0, 102.3],
|
||||
[176.5, 68.4], [169.4, 65.9], [182.1, 75.7], [179.8, 84.5], [175.3, 87.7],
|
||||
[184.9, 86.4], [177.3, 73.2], [167.4, 53.9], [178.1, 72.0], [168.9, 55.5],
|
||||
[157.2, 58.4], [180.3, 83.2], [170.2, 72.7], [177.8, 64.1], [172.7, 72.3],
|
||||
[165.1, 65.0], [186.7, 86.4], [165.1, 65.0], [174.0, 88.6], [175.3, 84.1],
|
||||
[185.4, 66.8], [177.8, 75.5], [180.3, 93.2], [180.3, 82.7], [177.8, 58.0],
|
||||
[177.8, 79.5], [177.8, 78.6], [177.8, 71.8], [177.8, 116.4], [163.8, 72.2],
|
||||
[188.0, 83.6], [198.1, 85.5], [175.3, 90.9], [166.4, 85.9], [190.5, 89.1],
|
||||
[166.4, 75.0], [177.8, 77.7], [179.7, 86.4], [172.7, 90.9], [190.5, 73.6],
|
||||
[185.4, 76.4], [168.9, 69.1], [167.6, 84.5], [175.3, 64.5], [170.2, 69.1],
|
||||
[190.5, 108.6], [177.8, 86.4], [190.5, 80.9], [177.8, 87.7], [184.2, 94.5],
|
||||
[176.5, 80.2], [177.8, 72.0], [180.3, 71.4], [171.4, 72.7], [172.7, 84.1],
|
||||
[172.7, 76.8], [177.8, 63.6], [177.8, 80.9], [182.9, 80.9], [170.2, 85.5],
|
||||
[167.6, 68.6], [175.3, 67.7], [165.1, 66.4], [185.4, 102.3], [181.6, 70.5],
|
||||
[172.7, 95.9], [190.5, 84.1], [179.1, 87.3], [175.3, 71.8], [170.2, 65.9],
|
||||
[193.0, 95.9], [171.4, 91.4], [177.8, 81.8], [177.8, 96.8], [167.6, 69.1],
|
||||
[167.6, 82.7], [180.3, 75.5], [182.9, 79.5], [176.5, 73.6], [186.7, 91.8],
|
||||
[188.0, 84.1], [188.0, 85.9], [177.8, 81.8], [174.0, 82.5], [177.8, 80.5],
|
||||
[171.4, 70.0], [185.4, 81.8], [185.4, 84.1], [188.0, 90.5], [188.0, 91.4],
|
||||
[182.9, 89.1], [176.5, 85.0], [175.3, 69.1], [175.3, 73.6], [188.0, 80.5],
|
||||
[188.0, 82.7], [175.3, 86.4], [170.5, 67.7], [179.1, 92.7], [177.8, 93.6],
|
||||
[175.3, 70.9], [182.9, 75.0], [170.8, 93.2], [188.0, 93.2], [180.3, 77.7],
|
||||
[177.8, 61.4], [185.4, 94.1], [168.9, 75.0], [185.4, 83.6], [180.3, 85.5],
|
||||
[174.0, 73.9], [167.6, 66.8], [182.9, 87.3], [160.0, 72.3], [180.3, 88.6],
|
||||
[167.6, 75.5], [186.7, 101.4], [175.3, 91.1], [175.3, 67.3], [175.9, 77.7],
|
||||
[175.3, 81.8], [179.1, 75.5], [181.6, 84.5], [177.8, 76.6], [182.9, 85.0],
|
||||
[177.8, 102.5], [184.2, 77.3], [179.1, 71.8], [176.5, 87.9], [188.0, 94.3],
|
||||
[174.0, 70.9], [167.6, 64.5], [170.2, 77.3], [167.6, 72.3], [188.0, 87.3],
|
||||
[174.0, 80.0], [176.5, 82.3], [180.3, 73.6], [167.6, 74.1], [188.0, 85.9],
|
||||
[180.3, 73.2], [167.6, 76.3], [183.0, 65.9], [183.0, 90.9], [179.1, 89.1],
|
||||
[170.2, 62.3], [177.8, 82.7], [179.1, 79.1], [190.5, 98.2], [177.8, 84.1],
|
||||
[180.3, 83.2], [180.3, 83.2]
|
||||
],
|
||||
markPoint : {
|
||||
data : [
|
||||
{type : 'max', name: '最大值'},
|
||||
{type : 'min', name: '最小值'}
|
||||
]
|
||||
},
|
||||
markLine : {
|
||||
data : [
|
||||
{type : 'average', name: '平均值'}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}]
|
||||
};
|
||||
@@ -1,65 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<!--
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
-->
|
||||
|
||||
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<script src="../../dist/echarts.js"></script>
|
||||
<script src="../../map/js/china.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
<div id="main"></div>
|
||||
<script type="module">
|
||||
import bar from './option/bar';
|
||||
import area from './option/area';
|
||||
import scatter from './option/scatter';
|
||||
import pie from './option/pie';
|
||||
import graph from './option/graph';
|
||||
import map from './option/map';
|
||||
|
||||
let options = [bar, area, scatter, pie, graph, map];
|
||||
let mainDiv = document.querySelector('#main');
|
||||
|
||||
let theme = window.location.hash.slice(1);
|
||||
|
||||
let scriptTag = document.createElement('script');
|
||||
scriptTag.src = '../' + theme + '.js';
|
||||
scriptTag.onload = function () {
|
||||
options.forEach(option => {
|
||||
let div = document.createElement('div');
|
||||
mainDiv.appendChild(div);
|
||||
div.style.cssText = 'float:left;width:50%;height:400px';
|
||||
let chart = echarts.init(div, theme);
|
||||
option.animation = false;
|
||||
chart.setOption(option);
|
||||
});
|
||||
}
|
||||
document.body.appendChild(scriptTag);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,63 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
const glob = require('glob');
|
||||
const puppeteer = require('puppeteer');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
async function wait(time) {
|
||||
return new Promise(resolve => {
|
||||
setTimeout(resolve, time);
|
||||
});
|
||||
}
|
||||
|
||||
async function snapshot(browser, themePath) {
|
||||
let themeName = path.basename(themePath, '.js');
|
||||
let code = fs.readFileSync(themePath, 'utf-8');
|
||||
|
||||
let page = await browser.newPage();
|
||||
await page.evaluateOnNewDocument(code);
|
||||
await page.setViewport({ width: 1200, height: 1200 });
|
||||
try {
|
||||
await page.goto('http://localhost/echarts/theme/tool/thumb.html#' + themeName);
|
||||
await wait(200);
|
||||
await page.screenshot({ path: __dirname + '/../thumb/' + themeName + '.png' });
|
||||
}
|
||||
catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
await page.close();
|
||||
|
||||
console.log('Updated ' + themeName);
|
||||
}
|
||||
|
||||
glob('../*.js', async function (err, themePathList) {
|
||||
|
||||
let browser = await puppeteer.launch();
|
||||
for (let themePath of themePathList) {
|
||||
try {
|
||||
await snapshot(browser, themePath);
|
||||
}
|
||||
catch(e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
await browser.close();
|
||||
});
|
||||
@@ -1,63 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
(function(root, factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
// AMD. Register as an anonymous module.
|
||||
define(['exports', 'echarts'], factory);
|
||||
} else if (
|
||||
typeof exports === 'object' &&
|
||||
typeof exports.nodeName !== 'string'
|
||||
) {
|
||||
// CommonJS
|
||||
factory(exports, require('echarts/lib/echarts'));
|
||||
} else {
|
||||
// Browser globals
|
||||
factory({}, root.echarts);
|
||||
}
|
||||
})(this, function(exports, echarts) {
|
||||
var log = function(msg) {
|
||||
if (typeof console !== 'undefined') {
|
||||
console && console.error && console.error(msg);
|
||||
}
|
||||
};
|
||||
if (!echarts) {
|
||||
log('ECharts is not Loaded');
|
||||
return;
|
||||
}
|
||||
var colorPalette = [
|
||||
'#d87c7c',
|
||||
'#919e8b',
|
||||
'#d7ab82',
|
||||
'#6e7074',
|
||||
'#61a0a8',
|
||||
'#efa18d',
|
||||
'#787464',
|
||||
'#cc7e63',
|
||||
'#724e58',
|
||||
'#4b565b'
|
||||
];
|
||||
echarts.registerTheme('vintage', {
|
||||
color: colorPalette,
|
||||
backgroundColor: '#fef8ef',
|
||||
graph: {
|
||||
color: colorPalette
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -20,14 +20,14 @@
|
||||
<nav class="service-nav">
|
||||
<!-- All APIs -->
|
||||
<a class="service-nav__item"
|
||||
th:classappend="${selected == '-1' || #request.getParameter('selected') == '-1'} ? 'service-nav__item--active' : ''"
|
||||
th:classappend="${selected == '-1' or (param.selected != null and param.selected[0] == '-1')} ? 'service-nav__item--active' : ''"
|
||||
th:href="@{/apis}">
|
||||
전체
|
||||
</a>
|
||||
|
||||
<!-- Service Categories -->
|
||||
<a class="service-nav__item" th:each="service : ${services}"
|
||||
th:classappend="${(#request.getParameter('selected') != null ? #request.getParameter('selected') : (selected != null ? selected : apiSpecInfo.service)) == service.id} ? 'service-nav__item--active' : ''"
|
||||
th:classappend="${(param.selected != null ? param.selected[0] : (selected != null ? selected : apiSpecInfo.service)) == service.id} ? 'service-nav__item--active' : ''"
|
||||
th:href="@{/apis(groupIds=${service.id})}">
|
||||
[[${service.groupName}]]
|
||||
</a>
|
||||
|
||||
@@ -122,8 +122,14 @@
|
||||
<!--/* LiveReload (개발 전용): DevTools 가 정적 리소스/템플릿 변경을 감지하면 브라우저를 자동 새로고침한다.
|
||||
localhost 외 IP(예: 172.30.1.14)로 접속해도 동작하도록 접속 호스트 기준으로 livereload.js 를 로드한다.
|
||||
prod/stage 에는 DevTools 자체가 없으므로(developmentOnly) 개발 프로파일에서만 주입한다. */-->
|
||||
<script th:if="${@environment.acceptsProfiles('local_rinjaemac','local')}"
|
||||
th:src="|//${#request.serverName}:35729/livereload.js|"></script>
|
||||
<script th:if="${@environment.acceptsProfiles('local_rinjaemac','local')}">
|
||||
// Thymeleaf 3.1 은 #request 표현식 객체를 제거했으므로 호스트는 브라우저에서 읽는다.
|
||||
(function () {
|
||||
var s = document.createElement('script');
|
||||
s.src = '//' + location.hostname + ':35729/livereload.js';
|
||||
document.head.appendChild(s);
|
||||
})();
|
||||
</script>
|
||||
</head>
|
||||
|
||||
</html>
|
||||
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
package com.eactive.apim.portal.common.compatibility;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertAll;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import com.eactive.apim.portal.common.util.UserTypeUtil;
|
||||
import com.eactive.apim.portal.common.validator.CellPhoneValidator;
|
||||
import org.apache.commons.lang3.StringEscapeUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.Strings;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Commons Lang 업그레이드 시 포털에서 사용하는 문자열 처리 결과가 유지되는지 검증한다.
|
||||
*/
|
||||
class CommonsLangUpgradeCompatibilityTest {
|
||||
|
||||
@AfterEach
|
||||
void resetUserTypeConfiguration() {
|
||||
UserTypeUtil.setInternalEmailDomains(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("빈 문자열과 공백 판정이 기존 분기 조건을 유지한다")
|
||||
void keepsEmptyAndBlankBranchingBehavior() {
|
||||
assertAll(
|
||||
() -> assertTrue(StringUtils.isEmpty(null)),
|
||||
() -> assertTrue(StringUtils.isEmpty("")),
|
||||
() -> assertFalse(StringUtils.isEmpty(" ")),
|
||||
() -> assertTrue(StringUtils.isBlank(" \t")),
|
||||
() -> assertFalse(StringUtils.isNotBlank(" \t")),
|
||||
() -> assertTrue(StringUtils.isNotEmpty(" "))
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("폼 입력과 상태 설정의 trim 및 기본값 처리가 유지된다")
|
||||
void keepsTrimmingAndDefaultingBehavior() {
|
||||
assertAll(
|
||||
() -> assertEquals("서비스명", StringUtils.trimToEmpty(" 서비스명 ")),
|
||||
() -> assertEquals("", StringUtils.trimToEmpty(null)),
|
||||
() -> assertEquals("api-001", StringUtils.defaultIfBlank(" ", "api-001")),
|
||||
() -> assertEquals("API 이름", StringUtils.defaultIfBlank("API 이름", "api-001")),
|
||||
() -> assertTrue(Strings.CI.equalsAny(" Y ".trim(), "true", "y", "1"))
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("API 경로와 메시지 리소스명 문자열 처리가 유지된다")
|
||||
void keepsPathAndResourceNameBehavior() {
|
||||
String fullPath = StringUtils.join("/openapi/", "/", Strings.CS.removeStart("/users", "/"));
|
||||
String normalizedPath = fullPath.replaceAll("//+", "/");
|
||||
String resource = "/WEB-INF/messages/messages_ko.properties";
|
||||
String baseName = StringUtils.substringBeforeLast(resource, ".properties");
|
||||
|
||||
assertAll(
|
||||
() -> assertEquals("/openapi/users", normalizedPath),
|
||||
() -> assertEquals("/openapi", Strings.CS.removeEnd("/openapi/", "/")),
|
||||
() -> assertEquals("/WEB-INF/messages", StringUtils.substringBeforeLast(baseName, "/")),
|
||||
() -> assertEquals("messages_ko", StringUtils.substringAfterLast(baseName, "/")),
|
||||
() -> assertEquals("messages", StringUtils.substringBeforeLast("messages_ko", "_"))
|
||||
);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Test
|
||||
@DisplayName("API와 약관 본문의 HTML 엔티티 디코딩 결과가 유지된다")
|
||||
void keepsHtmlUnescapeBehavior() {
|
||||
assertEquals("<p>이용약관 & 안내</p>",
|
||||
StringEscapeUtils.unescapeHtml4("<p>이용약관 & 안내</p>"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("휴대전화 검증의 기존 허용 및 거부 결과가 유지된다")
|
||||
void keepsCellPhoneValidationBehavior() {
|
||||
CellPhoneValidator validator = new CellPhoneValidator();
|
||||
|
||||
assertAll(
|
||||
() -> assertTrue(validator.isValid("010-1234-5678", null)),
|
||||
() -> assertTrue(validator.isValid("821012345678", null)),
|
||||
() -> assertFalse(validator.isValid("", null)),
|
||||
() -> assertFalse(validator.isValid("02-1234-5678", null)),
|
||||
() -> assertFalse(validator.isValid("010-12-5678", null))
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("내부 사용자 이메일 도메인 정규화와 판별 결과가 유지된다")
|
||||
void keepsInternalUserDomainBehavior() {
|
||||
UserTypeUtil.setInternalEmailDomains(Arrays.asList(" DJBANK.com ", "@Partner.COM", " "));
|
||||
|
||||
assertAll(
|
||||
() -> assertEquals(Arrays.asList("@djbank.com", "@partner.com"),
|
||||
UserTypeUtil.getInternalEmailDomains()),
|
||||
() -> assertTrue(UserTypeUtil.isInternalEmail(" USER@DJBANK.COM ")),
|
||||
() -> assertTrue(UserTypeUtil.isInternalEmail("user@partner.com")),
|
||||
() -> assertFalse(UserTypeUtil.isInternalEmail("user@example.com")),
|
||||
() -> assertFalse(UserTypeUtil.isInternalEmail(null))
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
package com.eactive.apim.portal.common.json;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import com.eactive.apim.portal.djb.apistatus.dto.DailyStatDTO;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
|
||||
/**
|
||||
* jackson 2.13.5 → 2.18.x 상향에 따른 JSON 직렬화 회귀 테스트.
|
||||
* <p>
|
||||
* Spring Boot 의 {@link JacksonAutoConfiguration} 이 만들어 주는 ObjectMapper(= MVC 응답에 실제로 쓰이는 것)로
|
||||
* 화면/AJAX 응답의 날짜 표현이 그대로인지 고정한다. 직접 new 한 ObjectMapper 나 Jackson2ObjectMapperBuilder 는
|
||||
* WRITE_DATES_AS_TIMESTAMPS 가 켜져 있어 실제 응답과 다르므로 쓰지 않는다.
|
||||
* </p>
|
||||
*/
|
||||
class JacksonDateSerializationTest {
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(JacksonAutoConfiguration.class));
|
||||
|
||||
/** Boot 가 구성한 ObjectMapper 로 검증 로직을 실행한다. */
|
||||
private void withBootObjectMapper(MapperAssertion assertion) {
|
||||
contextRunner.run(context -> assertion.accept(context.getBean(ObjectMapper.class)));
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
interface MapperAssertion {
|
||||
void accept(ObjectMapper objectMapper) throws Exception;
|
||||
}
|
||||
|
||||
/** 테스트 소스에는 lombok annotation processor 가 걸려 있지 않아 접근자를 직접 둔다. */
|
||||
public static class SampleDTO {
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDate baseDate;
|
||||
private String name;
|
||||
|
||||
public LocalDateTime getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
public void setCreatedAt(LocalDateTime createdAt) {
|
||||
this.createdAt = createdAt;
|
||||
}
|
||||
|
||||
public LocalDate getBaseDate() {
|
||||
return baseDate;
|
||||
}
|
||||
|
||||
public void setBaseDate(LocalDate baseDate) {
|
||||
this.baseDate = baseDate;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("@JsonFormat 지정 필드는 지정 패턴 문자열로 직렬화된다")
|
||||
void jsonFormatPatternIsHonored() {
|
||||
withBootObjectMapper(objectMapper -> {
|
||||
DailyStatDTO dto = new DailyStatDTO();
|
||||
dto.setStatDate(LocalDate.of(2026, 8, 18));
|
||||
dto.setUptimeRatio(0.9987);
|
||||
dto.setStatus("NORMAL");
|
||||
|
||||
String json = objectMapper.writeValueAsString(dto);
|
||||
|
||||
assertTrue(json.contains("\"statDate\":\"2026-08-18\""), json);
|
||||
assertTrue(json.contains("\"status\":\"NORMAL\""), json);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("어노테이션 없는 날짜 필드는 타임스탬프 숫자가 아니라 ISO-8601 문자열이다")
|
||||
void plainDatesAreIsoStrings() {
|
||||
withBootObjectMapper(objectMapper -> {
|
||||
SampleDTO dto = new SampleDTO();
|
||||
dto.setCreatedAt(LocalDateTime.of(2026, 8, 18, 9, 30, 0));
|
||||
dto.setBaseDate(LocalDate.of(2026, 8, 18));
|
||||
dto.setName("포털");
|
||||
|
||||
String json = objectMapper.writeValueAsString(dto);
|
||||
|
||||
assertTrue(json.contains("\"createdAt\":\"2026-08-18T09:30:00\""), json);
|
||||
assertTrue(json.contains("\"baseDate\":\"2026-08-18\""), json);
|
||||
assertTrue(json.contains("\"name\":\"포털\""), json);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("ISO-8601 문자열은 다시 날짜 타입으로 역직렬화된다")
|
||||
void isoStringsDeserializeBack() {
|
||||
withBootObjectMapper(objectMapper -> {
|
||||
String json = "{\"createdAt\":\"2026-08-18T09:30:00\",\"baseDate\":\"2026-08-18\",\"name\":\"포털\"}";
|
||||
|
||||
SampleDTO dto = objectMapper.readValue(json, SampleDTO.class);
|
||||
|
||||
assertEquals(LocalDateTime.of(2026, 8, 18, 9, 30, 0), dto.getCreatedAt());
|
||||
assertEquals(LocalDate.of(2026, 8, 18), dto.getBaseDate());
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("모르는 필드가 있어도 역직렬화가 실패하지 않는다 - Boot 기본 동작 유지")
|
||||
void unknownPropertiesAreIgnored() {
|
||||
withBootObjectMapper(objectMapper -> {
|
||||
String json = "{\"name\":\"포털\",\"unknownField\":123}";
|
||||
|
||||
SampleDTO dto = objectMapper.readValue(json, SampleDTO.class);
|
||||
|
||||
assertEquals("포털", dto.getName());
|
||||
});
|
||||
}
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
package com.eactive.apim.portal.common.security;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
import org.apache.commons.beanutils.PropertyUtils;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* commons-beanutils 1.11.0(CVE-2025-48734) 상향 회귀 테스트.
|
||||
* <p>
|
||||
* 1.11.0 부터 SuppressPropertiesBeanIntrospector 가 기본 활성이라 enum 의 declaredClass 를 통해
|
||||
* ClassLoader 로 내려가는 경로가 막힌다. 검증기들이 쓰는 일반 프로퍼티 접근은 그대로 동작해야 한다.
|
||||
* </p>
|
||||
*/
|
||||
class BeanUtilsPropertyAccessTest {
|
||||
|
||||
enum Kind {
|
||||
A, B
|
||||
}
|
||||
|
||||
public static class Form {
|
||||
private String password = "pw1234";
|
||||
private Kind kind = Kind.A;
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public Kind getKind() {
|
||||
return kind;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("일반 프로퍼티 접근은 정상 - PasswordRuleValidator 등이 쓰는 경로")
|
||||
void plainPropertyAccessWorks() throws Exception {
|
||||
assertEquals("pw1234", PropertyUtils.getProperty(new Form(), "password"));
|
||||
assertEquals(Kind.A, PropertyUtils.getProperty(new Form(), "kind"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("enum 의 declaredClass 접근은 차단된다")
|
||||
void declaredClassIsSuppressed() {
|
||||
assertThrows(NoSuchMethodException.class,
|
||||
() -> PropertyUtils.getProperty(new Form(), "kind.declaredClass"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("declaredClass 를 거쳐 ClassLoader 로 내려가는 경로도 차단된다")
|
||||
void classLoaderIsUnreachable() {
|
||||
assertThrows(NoSuchMethodException.class,
|
||||
() -> PropertyUtils.getNestedProperty(new Form(), "kind.declaredClass.classLoader"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.eactive.apim.portal.common.security;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.env.YamlPropertySourceLoader;
|
||||
import org.springframework.core.env.PropertySource;
|
||||
import org.springframework.core.io.ByteArrayResource;
|
||||
|
||||
/**
|
||||
* snakeyaml 2.6(CVE-2022-1471) 상향 회귀 테스트.
|
||||
* <p>
|
||||
* application.yml 로딩 경로가 정상 동작하는지, 그리고 임의 타입을 지정하는 글로벌 태그(!!java...)가
|
||||
* 거부되는지 확인한다.
|
||||
* </p>
|
||||
*/
|
||||
class YamlLoaderTagTest {
|
||||
|
||||
private final YamlPropertySourceLoader loader = new YamlPropertySourceLoader();
|
||||
|
||||
private List<PropertySource<?>> load(String yaml) throws IOException {
|
||||
return loader.load("test", new ByteArrayResource(yaml.getBytes("UTF-8")));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("일반 yml 은 그대로 로딩된다")
|
||||
void plainYamlLoads() throws Exception {
|
||||
List<PropertySource<?>> sources = load("server:\n port: 39130\nportal:\n auth-ttl: 300\n");
|
||||
|
||||
assertEquals(1, sources.size());
|
||||
assertEquals(39130, sources.get(0).getProperty("server.port"));
|
||||
assertEquals(300, sources.get(0).getProperty("portal.auth-ttl"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("임의 타입을 지정하는 글로벌 태그는 거부된다")
|
||||
void globalTagIsRejected() {
|
||||
String payload = "key: !!javax.script.ScriptEngineManager [!!java.net.URL [\"http://127.0.0.1/\"]]\n";
|
||||
|
||||
Exception thrown = assertThrows(Exception.class, () -> load(payload));
|
||||
|
||||
assertNotNull(thrown.getMessage());
|
||||
assertEquals(true, thrown.getMessage().contains("Global tag is not allowed"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package com.eactive.apim.portal.config;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.web.multipart.MultipartException;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.multipart.MultipartHttpServletRequest;
|
||||
import org.springframework.web.multipart.commons.CommonsMultipartResolver;
|
||||
|
||||
/**
|
||||
* commons-fileupload 1.6.0(CVE-2025-48976) 상향에 따른 파트 헤더 상한 회귀 테스트.
|
||||
* <p>
|
||||
* 1.6 부터 파트 헤더 총량 기본 상한이 512 바이트라 한글 파일명(UTF-8 3바이트/자)이 길면 업로드가 깨진다.
|
||||
* {@link MultipartConfig} 가 상한을 2048 로 올려 두는지 파싱 결과로 확인한다.
|
||||
* </p>
|
||||
*/
|
||||
class MultipartConfigTest {
|
||||
|
||||
private static final String BOUNDARY = "----portalTestBoundary";
|
||||
|
||||
private CommonsMultipartResolver resolver() {
|
||||
PortalProperties properties = new PortalProperties();
|
||||
properties.getFile().setMaxSize("8MB");
|
||||
return new MultipartConfig(properties).filterMultipartResolver();
|
||||
}
|
||||
|
||||
private MockHttpServletRequest multipartRequest(String filename) throws IOException {
|
||||
String head = "--" + BOUNDARY + "\r\n"
|
||||
+ "Content-Disposition: form-data; name=\"file\"; filename=\"" + filename + "\"\r\n"
|
||||
+ "Content-Type: application/octet-stream\r\n\r\n";
|
||||
ByteArrayOutputStream body = new ByteArrayOutputStream();
|
||||
body.write(head.getBytes("UTF-8"));
|
||||
body.write("hello".getBytes("UTF-8"));
|
||||
body.write(("\r\n--" + BOUNDARY + "--\r\n").getBytes("UTF-8"));
|
||||
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("POST", "/file/upload");
|
||||
request.setContentType("multipart/form-data; boundary=" + BOUNDARY);
|
||||
request.setCharacterEncoding("UTF-8");
|
||||
request.setContent(body.toByteArray());
|
||||
return request;
|
||||
}
|
||||
|
||||
private String korean(int length) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < length; i++) {
|
||||
sb.append('한');
|
||||
}
|
||||
return sb.append(".pdf").toString();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("영문 파일명 업로드는 그대로 파싱된다")
|
||||
void asciiFilename() throws Exception {
|
||||
MultipartHttpServletRequest parsed = resolver().resolveMultipart(multipartRequest("report.pdf"));
|
||||
MultipartFile file = parsed.getFile("file");
|
||||
|
||||
assertNotNull(file);
|
||||
assertEquals("report.pdf", file.getOriginalFilename());
|
||||
assertEquals(5L, file.getSize());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("한글 파일명 137자 이상도 파싱된다 - fileupload 1.6 기본 상한 512 였다면 실패할 길이")
|
||||
void longKoreanFilenameOverDefaultLimit() throws Exception {
|
||||
String filename = korean(150);
|
||||
|
||||
MultipartHttpServletRequest parsed = resolver().resolveMultipart(multipartRequest(filename));
|
||||
MultipartFile file = parsed.getFile("file");
|
||||
|
||||
assertNotNull(file);
|
||||
assertEquals(filename, file.getOriginalFilename());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("OS 파일명 상한(255자)이 전부 한글이어도 파싱된다")
|
||||
void koreanFilenameAtOsLimit() throws Exception {
|
||||
String filename = korean(255);
|
||||
|
||||
MultipartHttpServletRequest parsed = resolver().resolveMultipart(multipartRequest(filename));
|
||||
MultipartFile file = parsed.getFile("file");
|
||||
|
||||
assertNotNull(file);
|
||||
assertEquals(filename, file.getOriginalFilename());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("파트 헤더가 상한(2048바이트)을 넘으면 거부한다 - DoS 방어가 살아 있음")
|
||||
void partHeaderOverConfiguredLimitIsRejected() {
|
||||
assertThrows(MultipartException.class,
|
||||
() -> resolver().resolveMultipart(multipartRequest(korean(700))));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package com.eactive.apim.portal.config;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.mock.web.MockFilterChain;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.security.web.header.HeaderWriterFilter;
|
||||
import org.springframework.security.web.header.writers.CacheControlHeadersWriter;
|
||||
import org.springframework.security.web.header.writers.XContentTypeOptionsHeaderWriter;
|
||||
|
||||
/**
|
||||
* CVE-2026-22732 우회책 회귀 테스트.
|
||||
* <p>
|
||||
* {@code PortalConfigSecurity} 는 HeaderWriterFilter 를 eager 모드로 후처리한다. eager 모드에서는
|
||||
* 컨트롤러가 실행되기 전에 보안 헤더가 이미 기록돼 있어야 하며, 응답이 먼저 커밋되더라도 헤더가 누락되지 않는다.
|
||||
* </p>
|
||||
*/
|
||||
class SecurityHeaderEagerWriteTest {
|
||||
|
||||
private HeaderWriterFilter filter(boolean eager) {
|
||||
HeaderWriterFilter filter = new HeaderWriterFilter(Arrays.asList(
|
||||
new XContentTypeOptionsHeaderWriter(),
|
||||
new CacheControlHeadersWriter()));
|
||||
filter.setShouldWriteHeadersEagerly(eager);
|
||||
return filter;
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("eager 모드에서는 체인(컨트롤러) 실행 시점에 이미 보안 헤더가 기록돼 있다")
|
||||
void eagerWritesHeadersBeforeChain() throws Exception {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/file/download");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
AtomicReference<String> seenInChain = new AtomicReference<>();
|
||||
|
||||
filter(true).doFilter(request, response, (req, res) -> {
|
||||
seenInChain.set(((HttpServletResponse) res).getHeader("X-Content-Type-Options"));
|
||||
new MockFilterChain().doFilter(req, res);
|
||||
});
|
||||
|
||||
assertEquals("nosniff", seenInChain.get(), "체인 진입 시점에 헤더가 있어야 한다");
|
||||
assertEquals("nosniff", response.getHeader("X-Content-Type-Options"));
|
||||
assertNotNull(response.getHeader("Cache-Control"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("기본(lazy) 모드에서는 체인 실행 시점에 헤더가 아직 없다 - eager 설정이 실제로 의미 있음을 고정")
|
||||
void lazyDoesNotWriteHeadersBeforeChain() throws Exception {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/file/download");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
AtomicReference<String> seenInChain = new AtomicReference<>();
|
||||
|
||||
filter(false).doFilter(request, response, (req, res) -> {
|
||||
seenInChain.set(((HttpServletResponse) res).getHeader("X-Content-Type-Options"));
|
||||
new MockFilterChain().doFilter(req, res);
|
||||
});
|
||||
|
||||
assertNull(seenInChain.get(), "lazy 모드는 체인 이후에 헤더를 쓴다");
|
||||
assertEquals("nosniff", response.getHeader("X-Content-Type-Options"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user