Initial commit
@@ -0,0 +1,35 @@
|
||||
HELP.md
|
||||
target/
|
||||
!.mvn/wrapper/maven-wrapper.jar
|
||||
!**/src/main/**/target/
|
||||
!**/src/test/**/target/
|
||||
|
||||
### STS ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
build/
|
||||
!**/src/main/**/build/
|
||||
!**/src/test/**/build/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
/src/main/resources/public/
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* Copyright 2007-present the original author or authors.
|
||||
*
|
||||
* Licensed 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
|
||||
*
|
||||
* https://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.
|
||||
*/
|
||||
|
||||
import java.net.*;
|
||||
import java.io.*;
|
||||
import java.nio.channels.*;
|
||||
import java.util.Properties;
|
||||
|
||||
public class MavenWrapperDownloader {
|
||||
|
||||
private static final String WRAPPER_VERSION = "0.5.6";
|
||||
/**
|
||||
* Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
|
||||
*/
|
||||
private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/"
|
||||
+ WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar";
|
||||
|
||||
/**
|
||||
* Path to the maven-wrapper.properties file, which might contain a downloadUrl property to
|
||||
* use instead of the default one.
|
||||
*/
|
||||
private static final String MAVEN_WRAPPER_PROPERTIES_PATH =
|
||||
".mvn/wrapper/maven-wrapper.properties";
|
||||
|
||||
/**
|
||||
* Path where the maven-wrapper.jar will be saved to.
|
||||
*/
|
||||
private static final String MAVEN_WRAPPER_JAR_PATH =
|
||||
".mvn/wrapper/maven-wrapper.jar";
|
||||
|
||||
/**
|
||||
* Name of the property which should be used to override the default download url for the wrapper.
|
||||
*/
|
||||
private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";
|
||||
|
||||
public static void main(String args[]) {
|
||||
System.out.println("- Downloader started");
|
||||
File baseDirectory = new File(args[0]);
|
||||
System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath());
|
||||
|
||||
// If the maven-wrapper.properties exists, read it and check if it contains a custom
|
||||
// wrapperUrl parameter.
|
||||
File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
|
||||
String url = DEFAULT_DOWNLOAD_URL;
|
||||
if (mavenWrapperPropertyFile.exists()) {
|
||||
FileInputStream mavenWrapperPropertyFileInputStream = null;
|
||||
try {
|
||||
mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
|
||||
Properties mavenWrapperProperties = new Properties();
|
||||
mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
|
||||
url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
|
||||
} catch (IOException e) {
|
||||
System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
|
||||
} finally {
|
||||
try {
|
||||
if (mavenWrapperPropertyFileInputStream != null) {
|
||||
mavenWrapperPropertyFileInputStream.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// Ignore ...
|
||||
}
|
||||
}
|
||||
}
|
||||
System.out.println("- Downloading from: " + url);
|
||||
|
||||
File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
|
||||
if (!outputFile.getParentFile().exists()) {
|
||||
if (!outputFile.getParentFile().mkdirs()) {
|
||||
System.out.println(
|
||||
"- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'");
|
||||
}
|
||||
}
|
||||
System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
|
||||
try {
|
||||
downloadFileFromURL(url, outputFile);
|
||||
System.out.println("Done");
|
||||
System.exit(0);
|
||||
} catch (Throwable e) {
|
||||
System.out.println("- Error downloading");
|
||||
e.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
private static void downloadFileFromURL(String urlString, File destination) throws Exception {
|
||||
if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) {
|
||||
String username = System.getenv("MVNW_USERNAME");
|
||||
char[] password = System.getenv("MVNW_PASSWORD").toCharArray();
|
||||
Authenticator.setDefault(new Authenticator() {
|
||||
@Override
|
||||
protected PasswordAuthentication getPasswordAuthentication() {
|
||||
return new PasswordAuthentication(username, password);
|
||||
}
|
||||
});
|
||||
}
|
||||
URL website = new URL(urlString);
|
||||
ReadableByteChannel rbc;
|
||||
rbc = Channels.newChannel(website.openStream());
|
||||
FileOutputStream fos = new FileOutputStream(destination);
|
||||
fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
|
||||
fos.close();
|
||||
rbc.close();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.1/apache-maven-3.8.1-bin.zip
|
||||
wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar
|
||||
@@ -0,0 +1,65 @@
|
||||
# CLAUDE.md
|
||||
|
||||
Behavioral guidelines to reduce common LLM coding mistakes. Merge with project-specific instructions as needed.
|
||||
|
||||
**Tradeoff:** These guidelines bias toward caution over speed. For trivial tasks, use judgment.
|
||||
|
||||
## 1. Think Before Coding
|
||||
|
||||
**Don't assume. Don't hide confusion. Surface tradeoffs.**
|
||||
|
||||
Before implementing:
|
||||
- State your assumptions explicitly. If uncertain, ask.
|
||||
- If multiple interpretations exist, present them - don't pick silently.
|
||||
- If a simpler approach exists, say so. Push back when warranted.
|
||||
- If something is unclear, stop. Name what's confusing. Ask.
|
||||
|
||||
## 2. Simplicity First
|
||||
|
||||
**Minimum code that solves the problem. Nothing speculative.**
|
||||
|
||||
- No features beyond what was asked.
|
||||
- No abstractions for single-use code.
|
||||
- No "flexibility" or "configurability" that wasn't requested.
|
||||
- No error handling for impossible scenarios.
|
||||
- If you write 200 lines and it could be 50, rewrite it.
|
||||
|
||||
Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify.
|
||||
|
||||
## 3. Surgical Changes
|
||||
|
||||
**Touch only what you must. Clean up only your own mess.**
|
||||
|
||||
When editing existing code:
|
||||
- Don't "improve" adjacent code, comments, or formatting.
|
||||
- Don't refactor things that aren't broken.
|
||||
- Match existing style, even if you'd do it differently.
|
||||
- If you notice unrelated dead code, mention it - don't delete it.
|
||||
|
||||
When your changes create orphans:
|
||||
- Remove imports/variables/functions that YOUR changes made unused.
|
||||
- Don't remove pre-existing dead code unless asked.
|
||||
|
||||
The test: Every changed line should trace directly to the user's request.
|
||||
|
||||
## 4. Goal-Driven Execution
|
||||
|
||||
**Define success criteria. Loop until verified.**
|
||||
|
||||
Transform tasks into verifiable goals:
|
||||
- "Add validation" → "Write tests for invalid inputs, then make them pass"
|
||||
- "Fix the bug" → "Write a test that reproduces it, then make it pass"
|
||||
- "Refactor X" → "Ensure tests pass before and after"
|
||||
|
||||
For multi-step tasks, state a brief plan:
|
||||
```
|
||||
1. [Step] → verify: [check]
|
||||
2. [Step] → verify: [check]
|
||||
3. [Step] → verify: [check]
|
||||
```
|
||||
|
||||
Strong success criteria let you loop independently. Weak criteria ("make it work") require constant clarification.
|
||||
|
||||
---
|
||||
|
||||
**These guidelines are working if:** fewer unnecessary changes in diffs, fewer rewrites due to overcomplication, and clarifying questions come before implementation rather than after mistakes.
|
||||
@@ -0,0 +1,58 @@
|
||||
import java.text.SimpleDateFormat
|
||||
|
||||
node {
|
||||
def mvnHome
|
||||
stage('Preparation') { // for display purposes
|
||||
git([url: 'https://code.sdsdev.co.kr/SDL/Nonsan.git', credentialsId: 'sdlsupport', branch: '5.2.1'])
|
||||
mvnHome = tool 'M3'
|
||||
def dateFormat = new SimpleDateFormat("yyyy-MM-dd")
|
||||
def date = new Date()
|
||||
today = dateFormat.format(date)
|
||||
pomVersion = bat(script: 'mvn help:evaluate -Dexpression=project.version -q -DforceStdout', returnStdout: true).trim()
|
||||
pomVersion = pomVersion.readLines().drop(1).join(" ")
|
||||
|
||||
echo 'project.version=' + pomVersion
|
||||
echo 'today=' + today
|
||||
}
|
||||
environment {
|
||||
DATE = today
|
||||
}
|
||||
stage('Directory Clean') {
|
||||
bat("start cmd.exe /c rmdir /s/q %USERPROFILE%\\.m2\\repository\\sdl")
|
||||
bat("start cmd.exe /c rmdir /s/q D:\\deploy\\sdl-5.0.0\\sdl-base")
|
||||
bat("start cmd.exe /c rmdir /s/q D:\\deploy\\sdl-5.0.0\\sdl-sql")
|
||||
bat('start cmd.exe /c xcopy sdl-base D:\\deploy\\sdl-5.0.0\\sdl-base\\ /s /e /d /y')
|
||||
bat('start cmd.exe /c xcopy sdl-sql D:\\deploy\\sdl-5.0.0\\sdl-sql\\ /s /e /d /y')
|
||||
}
|
||||
|
||||
stage('lib deploy') {
|
||||
|
||||
bat('mvn clean deploy -DaltDeploymentRepository=sdl-release::default::http://admin:admin123@70.118.68.230:8081/repository/sdl-release')
|
||||
|
||||
}
|
||||
|
||||
stage('Build') {
|
||||
dir('D:\\deploy\\sdl-5.0.0\\sdl-base'){
|
||||
bat('mvn compile dependency:sources -DincludeGroupIds=sdl')
|
||||
bat('start cmd.exe /c rmdir /s/q %USERPROFILE%\\.m2\\repository\\sdl\\sdl-base')
|
||||
bat('start cmd.exe /c xcopy %USERPROFILE%\\.m2\\repository\\sdl D:\\deploy\\sdl-5.0.0\\sdl-base\\source\\sdl\\ /s /e /d /y')
|
||||
bat('mvn clean compile dependency:sources -DincludeGroupIds=sdl')
|
||||
bat("start cmd.exe /c rmdir /s/q D:\\deploy\\sdl-5.0.0\\sdl-base\\target")
|
||||
}
|
||||
}
|
||||
stage('Packaging') {
|
||||
dir('D:\\deploy\\sdl-5.0.0'){
|
||||
bat('start cmd.exe /c bc c D:\\deploy\\sdl-5.0.0\\sdl-base-'+pomVersion+'-'+today+'.zip sdl-base')
|
||||
bat('start cmd.exe /c bc c D:\\deploy\\sdl-5.0.0\\sdl-ddl-'+pomVersion+'-'+today+'.zip sdl-sql')
|
||||
}
|
||||
}
|
||||
stage('Maven Package') {
|
||||
dir('D:\\deploy\\sdl-5.0.0\\sdl-base'){
|
||||
bat('mvn clean package')
|
||||
}
|
||||
}
|
||||
stage('Success') {
|
||||
echo 'project.version=' + pomVersion
|
||||
echo 'today=' + today
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// PDF
|
||||
:pdf-fontsdir: {asciidoctorconfigdir}/fonts
|
||||
:pdf-themesdir: {asciidoctorconfigdir}/themes
|
||||
:pdf-theme: ko_KR
|
||||
|
||||
// DOCTYPE, TOC
|
||||
:doctype: book
|
||||
:chapter-signifier:
|
||||
:sectnums:
|
||||
:sectanchors:
|
||||
:toc: left
|
||||
:toclevels: 3
|
||||
|
||||
:source-highlighter: highlight.js
|
||||
|
||||
// ICON, IMAGE
|
||||
:icons: font
|
||||
:stylesheet: css/sdl-manual.css
|
||||
:linkcss:
|
||||
:imagesdir: {asciidoctorconfigdir}/img
|
||||
:data-uri:
|
||||
@@ -0,0 +1,13 @@
|
||||
= Appendix
|
||||
|
||||
:leveloffset: +1
|
||||
|
||||
include::QuartzClustering.adoc[leveloffset]
|
||||
|
||||
include::Properties암호화툴사용방법.adoc[leveloffset]
|
||||
|
||||
include::uiSDL.adoc[leveloffset]
|
||||
|
||||
include::RedisConfig.adoc[leveloffset]
|
||||
|
||||
include::D-KMS 암호화 적용.adoc[leveloffset]
|
||||
@@ -0,0 +1,90 @@
|
||||
= D-KMS 암호화 적용
|
||||
|
||||
SDL(표준개발라이브러리)에 D-KMS 암호화를 적용하는 방법에 대해 설명한다.
|
||||
|
||||
== 사전 준비
|
||||
|
||||
=== KMS 포털에서 암호화 권한 신청
|
||||
|
||||
. KMS 포털에서 계정을 인증하고 암호화 권한 신청, 결재
|
||||
. 암호화 권한 결재 완료되면 D-KMS SDK 및 가이드 전송됨
|
||||
. 전달 받은 SDK 설치 및 코드 적용
|
||||
|
||||
== 코드 적용
|
||||
|
||||
=== Credential 생성 및 환경변수 추가
|
||||
|
||||
. D-KMS 가이드를 참고하여 Credential을 생성하고 시스템 환경변수에 추가한다.
|
||||
|
||||
=== SDL 수정
|
||||
|
||||
. D-KMS 암호화 라이브러리 추가
|
||||
- https://code.sec.samsung.net/confluence/pages/viewpage.action?pageId=343332410[D-KMS 가이드]를 참고하여 라이브러리를 추가한다.
|
||||
|
||||
. src/main/resources-{local|dev|prod} 에 dkms.properties 파일을 추가한다. (배포된 sdl-appendix 내 참조)
|
||||
|
||||
IMPORTANT: - dkms.task-id 프로퍼티 값을 D-KMS 신청시 부여받은 값으로 수정해야한다. +
|
||||
- 기타 다른 프로퍼티 값도 제대로 설정되었는지 확인한다.
|
||||
|
||||
[start=3]
|
||||
. mybatis typehandler 추가
|
||||
- 암호화 대상이 되는 컬럼에 대해 typehandler 를 적용한다.
|
||||
- com.samsung.dkms.handler 패키지 생성
|
||||
- EncryptionTypeHandler.java, NameEncryptionTypeHandler.java, EmailEncryptionTypeHandler.java, PhoneEncryptionTypeHandler.java, AddressEncryptionTypeHandler.java, BirthdayEncryptionTypeHandler.java 파일을 복사/붙여넣기 한다. (배포된 sdl-appendix 내 참조)
|
||||
|
||||
. mybatis-config.xml 수정
|
||||
- mapper xml 에 적용이 용이하도록 typehandler의 type alias 를 추가한다.
|
||||
+
|
||||
image::dkms_typehandler_alias.png[]
|
||||
|
||||
[start=5]
|
||||
. mapper xml 에 mybatis typehandler 적용
|
||||
- 암호화가 필요한 컬럼에 해당하는 핸들러를 선택 적용한다.
|
||||
- mapper xml에 typehandler 적용 방법은 샘플파일(mapper-mybatis-user.xml, 배포된 sdl-appendix 내 참조)을 참고한다. (`*""유무에 유의*`)
|
||||
* select resultMap 적용 예
|
||||
+
|
||||
image::dkms_typehandler_select_resultmap.png[]
|
||||
|
||||
* insert, update 적용 예
|
||||
+
|
||||
image::dkms_typehandler_insert.png[]
|
||||
image::dkms_typehandler_update.png[]
|
||||
|
||||
* 수정대상 mapper xml (배포판 기준)
|
||||
+
|
||||
----
|
||||
mapper-mybatis-common.xml
|
||||
mapper-mybatis-sample-approval-internal.xml
|
||||
mapper-mybatis-sample-approval-knox.xml
|
||||
mapper-mybatis-sample.xml
|
||||
mapper-mybatis-sys-resource.xml
|
||||
mapper-mybatis-user-sync.xml
|
||||
mapper-mybatis-approval.xml
|
||||
mapper-mybatis-user-role.xml
|
||||
mapper-mybatis-user.xml
|
||||
mapper-mybatis-workgroup-role.xml
|
||||
mapper-mybatis-comment.xml
|
||||
mapper-mybatis-post.xml
|
||||
mapper-mybatis-email.xml
|
||||
mapper-mybatis-mail-group.xml
|
||||
mapper-mybatis-history.xml
|
||||
mapper-mybatis-access-log.xml
|
||||
mapper-mybatis-admin-address.xml
|
||||
mapper-mybatis-knox-department-sync.xml
|
||||
mapper-mybatis-knox-department.xml
|
||||
mapper-mybatis-terms-use.xml
|
||||
----
|
||||
|
||||
=== 테이블 수정 및 데이터 마이그레이션
|
||||
. 테이블 컬럼 사이즈 변경
|
||||
- 암호화 대상 컬럼의 사이즈를 최소 255byte 가 되도록 변경한다.
|
||||
. 기존 데이터 암호화를 위한 마이그레이션은 D-KMS 가이드를 참고한다.
|
||||
|
||||
CAUTION: *SQL기반의 Database의 경우, 개인정보가 암호화됨으로써 아래와 같은 SQL문에 영향이 발생* (D-KMS 소개자료 중 발췌) +
|
||||
Nondeterministic Encryption으로 인해 Equality 비교 불가 +
|
||||
: WHERE, GROUP BY, JOIN (ON), DISTINCT, HAVING, ORDER BY +
|
||||
-> 일반적으로 Application Level에서 Filtering, Grouping, Joining 구현 필요 +
|
||||
+
|
||||
*대안: 별도 Column에 Hash 데이터를 추가 저장* +
|
||||
Hash로 인해 영향 받는 SQL문: WHERE (비교에 사용하는 데이터 역시 Hash 필요. Equality 비교는 가능하나 LIKE 또는 대소비교 불가), ORDER BY +
|
||||
-> 단, 모든 데이터가 Hash되어있지 않은 경우 GROUP BY에 영향을 줄 수 있음
|
||||
@@ -0,0 +1,170 @@
|
||||
= Properties 암호화 툴 사용 방법
|
||||
|
||||
sdl-encrypt는 Spring Framework에서 사용하는 properties를 http://www.jasypt.org/[Jasypt]를 이용해 쉽게 암호화 하기 위한 모듈이다.
|
||||
|
||||
== 사용 방법
|
||||
|
||||
=== 파일다운로드
|
||||
|
||||
sdl-encrypt-1.0.0.jar 파일을 다운로드 받아서 특정 디렉토리에 복사한다. (배포된 sdl-appendix 내 참조)
|
||||
|
||||
sdl-encypt-1.0.0.jar 파일을 실행하기 위해서는 JDK 8 이상의 버전이 설치 되어 있어야 한다.
|
||||
|
||||
----
|
||||
C:\encrypt>dir
|
||||
C 드라이브의 볼륨에는 이름이 없습니다.
|
||||
볼륨 일련 번호: 1841-973A
|
||||
|
||||
C:\encrypt 디렉터리
|
||||
|
||||
2022-08-09 오후 02:17 <DIR> .
|
||||
2022-08-09 오후 02:17 <DIR> ..
|
||||
2022-08-09 오후 02:15 9,114,144 sdl-encrypt-1.0.0.jar
|
||||
|
||||
----
|
||||
|
||||
'''
|
||||
|
||||
=== 암호화 key 파일 생성
|
||||
|
||||
같은 폴더에 암호화를 위한 key 파일을 sdl-encrypt-1.0.0.jar 파일을 있는 폴더에 생성한다.
|
||||
|
||||
key파일의 이름은 *jasypt.key* 로 한다. jasypt.key 파일은 어플리케이션 실행 시 서버에서 읽어 복호화 할때 필요하다.
|
||||
|
||||
*jasypt.key 파일의 내용이 유출 될 경우 문제가 생길 수 있으니 형상관리에 저장하지 않도록 한다.*
|
||||
|
||||
*jasypt.key 샘플*
|
||||
|
||||
----
|
||||
fhasiuk23dfjhasoijcnaoijfoase0342ruq0
|
||||
|
||||
----
|
||||
|
||||
jasypt.key 파일의 내용은 프로젝트에서 랜덤한 문자열로 생성한다.
|
||||
|
||||
'''
|
||||
|
||||
=== Properties 파일 생성
|
||||
|
||||
config.properties 파일에 암호화가 필요한 properties를 작성해 sdl-encrypt-1.0.0.jar 파일이 있는 폴더에 생성한다.
|
||||
|
||||
*config.properties 파일 샘플*
|
||||
|
||||
----
|
||||
# Datasource(Oracle)
|
||||
datasource.driver=net.sf.log4jdbc.sql.jdbcapi.DriverSpy
|
||||
datasource.url=jdbc:log4jdbc:oracle:thin:@10.127.72.226:1521:XE
|
||||
datasource.username=sdl5
|
||||
datasource.password=dlatl#00
|
||||
|
||||
----
|
||||
|
||||
'''
|
||||
|
||||
=== 암호화 하기
|
||||
|
||||
실행 전에 위의 모든 파일들이 같은 폴더에 위치하는지 확인한다.
|
||||
|
||||
----
|
||||
C:\encrypt>dir
|
||||
C 드라이브의 볼륨에는 이름이 없습니다.
|
||||
볼륨 일련 번호: 1841-973A
|
||||
|
||||
C:\encrypt 디렉터리
|
||||
|
||||
2022-08-09 오후 02:29 <DIR> .
|
||||
2022-08-09 오후 02:29 <DIR> ..
|
||||
2022-08-08 오후 05:14 407 config.properties
|
||||
2022-08-09 오후 02:29 37 jasypt.key
|
||||
2022-08-09 오후 02:15 9,114,144 sdl-encrypt-1.0.0.jar
|
||||
3개 파일 9,114,588 바이트
|
||||
|
||||
----
|
||||
|
||||
*java -jar sdl-encrypt-1.0.0.jar* 실행
|
||||
|
||||
----
|
||||
C:\encrypt>java -jar sdl-encrypt-1.0.0.jar
|
||||
|
||||
. ____ _ __ _ _
|
||||
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
|
||||
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
|
||||
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
|
||||
' |____| .__|_| |_|_| |_\__, | / / / /
|
||||
=========|_|==============|___/=/_/_/_/
|
||||
:: Spring Boot :: (v2.7.2)
|
||||
|
||||
2022-08-09 14:30:33.152 INFO 47544 --- [ main] com.samsung.EncryptApplication : Starting EncryptApplication v1.0.0 using Java 1.8.0_341 on DESKTOP-BBNYDORY with PID 47544 (C:\encrypt\sdl-encrypt-1.0.0.jar started by bbnydory in C:\encrypt)
|
||||
2022-08-09 14:30:33.157 INFO 47544 --- [ main] com.samsung.EncryptApplication : No active profile set, falling back to 1 default profile: "default"
|
||||
2022-08-09 14:30:33.456 INFO 47544 --- [ main] ptablePropertiesBeanFactoryPostProcessor : Post-processing PropertySource instances
|
||||
2022-08-09 14:30:33.458 INFO 47544 --- [ main] c.u.j.EncryptablePropertySourceConverter : Skipping PropertySource configurationProperties [class org.springframework.boot.context.properties.source.ConfigurationPropertySourcesPropertySource
|
||||
2022-08-09 14:30:33.461 INFO 47544 --- [ main] c.u.j.EncryptablePropertySourceConverter : Converting PropertySource systemProperties [org.springframework.core.env.PropertiesPropertySource] to EncryptableMapPropertySourceWrapper
|
||||
2022-08-09 14:30:33.461 INFO 47544 --- [ main] c.u.j.EncryptablePropertySourceConverter : Converting PropertySource systemEnvironment [org.springframework.boot.env.SystemEnvironmentPropertySourceEnvironmentPostProcessor$OriginAwareSystemEnvironmentPropertySource] to EncryptableSystemEnvironmentPropertySourceWrapper
|
||||
2022-08-09 14:30:33.463 INFO 47544 --- [ main] c.u.j.EncryptablePropertySourceConverter : Converting PropertySource random [org.springframework.boot.env.RandomValuePropertySource] to EncryptablePropertySourceWrapper
|
||||
2022-08-09 14:30:33.464 INFO 47544 --- [ main] c.u.j.EncryptablePropertySourceConverter : Converting PropertySource Config resource 'class path resource [application.properties]' via location 'optional:classpath:/' [org.springframework.boot.env.OriginTrackedMapPropertySource] to EncryptableMapPropertySourceWrapper
|
||||
2022-08-09 14:30:33.500 INFO 47544 --- [ main] c.u.j.filter.DefaultLazyPropertyFilter : Property Filter custom Bean not found with name 'encryptablePropertyFilter'. Initializing Default Property Filter
|
||||
2022-08-09 14:30:33.551 INFO 47544 --- [ main] c.u.j.r.DefaultLazyPropertyResolver : Property Resolver custom Bean not found with name 'encryptablePropertyResolver'. Initializing Default Property Resolver
|
||||
2022-08-09 14:30:33.553 INFO 47544 --- [ main] c.u.j.d.DefaultLazyPropertyDetector : Property Detector custom Bean not found with name 'encryptablePropertyDetector'. Initializing Default Property Detector
|
||||
2022-08-09 14:30:33.611 INFO 47544 --- [ main] com.samsung.EncryptApplication : Started EncryptApplication in 0.765 seconds (JVM running for 1.669)
|
||||
2022-08-09 14:30:34.114 INFO 47544 --- [ main] com.samsung.EncryptService : datasource.username=ENC(fOdjJKVr6fkmdS46JlsCRw==)
|
||||
2022-08-09 14:30:34.115 INFO 47544 --- [ main] com.samsung.EncryptService : datasource.password=ENC(xfWl/qeA7tIMQ10UyU7IInz+SyY6ovX9)
|
||||
2022-08-09 14:30:34.117 INFO 47544 --- [ main] com.samsung.EncryptService : datasource.url=ENC(Ocp24DAK1cq0f8TezLyMpcedSu6nLdTACSIYyMoCnuanSJT5x76lw8yM0reAeu4qBrBF+yvItVyCBmtPlIv70A==)
|
||||
2022-08-09 14:30:34.119 INFO 47544 --- [ main] com.samsung.EncryptService : datasource.driver=ENC(GJ527jBO/7Xjilg8WbQHk55GwVUAvc6fIa1Yai1o68WeimiE5BpHNj3e7YnjhtQR)
|
||||
|
||||
----
|
||||
|
||||
'''
|
||||
|
||||
=== 확인하기
|
||||
|
||||
암호화가 정상적으로 실행 됐다면 같은 폴더에 config-enc.properties 파일이 생성된 것을 확인 할 수 있다.
|
||||
|
||||
----
|
||||
C:\encrypt>dir
|
||||
C 드라이브의 볼륨에는 이름이 없습니다.
|
||||
볼륨 일련 번호: 1841-973A
|
||||
|
||||
C:\encrypt 디렉터리
|
||||
|
||||
2022-08-09 오후 02:30 <DIR> .
|
||||
2022-08-09 오후 02:30 <DIR> ..
|
||||
2022-08-09 오후 02:30 305 config-enc.properties
|
||||
2022-08-08 오후 05:14 407 config.properties
|
||||
2022-08-09 오후 02:29 37 jasypt.key
|
||||
2022-08-09 오후 02:15 9,114,144 sdl-encrypt-1.0.0.jar
|
||||
4개 파일 9,114,893 바이트
|
||||
|
||||
----
|
||||
|
||||
config-enc.properties 파일내용
|
||||
|
||||
----
|
||||
datasource.username=ENC(fOdjJKVr6fkmdS46JlsCRw==)
|
||||
datasource.password=ENC(xfWl/qeA7tIMQ10UyU7IInz+SyY6ovX9)
|
||||
datasource.url=ENC(Ocp24DAK1cq0f8TezLyMpcedSu6nLdTACSIYyMoCnuanSJT5x76lw8yM0reAeu4qBrBF+yvItVyCBmtPlIv70A==)
|
||||
datasource.driver=ENC(GJ527jBO/7Xjilg8WbQHk55GwVUAvc6fIa1Yai1o68WeimiE5BpHNj3e7YnjhtQR)
|
||||
|
||||
----
|
||||
|
||||
'''
|
||||
|
||||
== 고급 사용
|
||||
|
||||
sdl-encrypt 모듈을 Spring Boot 프로젝트로 되어 있고 application.properties 파일에 암호화 key파일, Properties 파일, 출력파일에 대한 경로가 작성되어 있다.
|
||||
|
||||
application.properties 내용
|
||||
|
||||
[source,properties]
|
||||
----
|
||||
algorithm=PBEWithMD5AndDES
|
||||
keyObtentionIterations=1000
|
||||
pollSize=1
|
||||
stringOutputType=base64
|
||||
keyFilePath=./jasypt.key
|
||||
propertiesFilePath=./config.properties
|
||||
outputPropertiesFilePath=./config-enc.properties
|
||||
----
|
||||
|
||||
위 내용의 변경이 필요하면
|
||||
|
||||
sdl-encrypt-1.0.0.jar 파일이 있는 위치에 application.properties 파일을 새로 작성하고 위의 값들을 오버라이딩해서 사용할 수 있다.
|
||||
@@ -0,0 +1,221 @@
|
||||
= Quartz Clustering with JDBC-JobStore
|
||||
|
||||
== JDBC-JobStore DB 초기화
|
||||
quartz db schema (배포된 sdl-appendix 내 참조) 를 사용하는 DBMS에 맞는 SQL을 실행해 JDBC-JobStore를 사용하기 위한 Table을 생성한다.
|
||||
|
||||
CAUTION: Tibero DB는 미지원
|
||||
|
||||
== application.properties
|
||||
properties 파일 내용 중 Quartz JDBC-JobStore 관련 설정이다.
|
||||
[source, properties]
|
||||
----
|
||||
# Spring Quartz 설정
|
||||
spring.quartz.job-store-type=jdbc
|
||||
spring.quartz.jdbc.initialize-schema=always
|
||||
|
||||
spring.quartz.datasource.driver-class-name=org.postgresql.Driver
|
||||
spring.quartz.datasource.jdbcUrl=jdbc:postgresql://10.40.87.189:5445/SDL
|
||||
spring.quartz.datasource.username=sdl5
|
||||
spring.quartz.datasource.password=dlatl#123
|
||||
|
||||
spring.quartz.properties.org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.PostgreSQLDelegate <1>
|
||||
spring.quartz.properties.org.quartz.jobStore.isClustered=true
|
||||
spring.quartz.properties.org.quartz.jobStore.misfireThreshold=60000
|
||||
spring.quartz.properties.org.quartz.jobStore.clusterCheckinInterval=15000
|
||||
spring.quartz.properties.org.quartz.scheduler.instanceName=sdl-prod <2>
|
||||
spring.quartz.properties.org.quartz.scheduler.instanceId=AUTO <3>
|
||||
spring.quartz.properties.org.quartz.scheduler.rmi.export=false
|
||||
spring.quartz.properties.org.quartz.scheduler.rmi.proxy=false
|
||||
spring.quartz.properties.org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
|
||||
spring.quartz.properties.org.quartz.threadPool.threadCount=10 <4>
|
||||
spring.quartz.properties.org.quartz.threadPool.threadPriority=5
|
||||
spring.quartz.properties.org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread=true
|
||||
spring.quartz.properties.org.quartz.scheduler-name=QuartzScheduler
|
||||
----
|
||||
|
||||
<1> DB 에 따라 Delegate 를 변경한다.
|
||||
+
|
||||
org.quartz.impl.jdbcjobstore.oracle.OracleDelegate, +
|
||||
org.quartz.impl.jdbcjobstore.MSSQLDelegate, +
|
||||
org.quartz.impl.jdbcjobstore.PostgreSQLDelegate
|
||||
<2> Quartz Scheduler 를 구별하는 Property 값으로 Clustering 할 서버에서는 같은 값을 세팅한다.
|
||||
<3> Unique 한 값이어야 하며 AUTO 일 경우 자동 생성된다.
|
||||
<4> Job 을 실행하기 위한 Thread 수
|
||||
|
||||
기타 자세한 Property 값은 http://www.quartz-scheduler.org/documentation/quartz-2.3.0/configuration/ConfigJDBCJobStoreClustering.html[Quartz 메뉴얼, window=_blank]을 참고 한다.
|
||||
|
||||
== QuartzClusteringConfig
|
||||
|
||||
QuartzClusteringConfig 파일은 Spring 에 등록된 CronTriggerFactoryBean 을 실행 하도록 SchedulerFactoryBean이 정의되어 있다.
|
||||
|
||||
[source, java]
|
||||
----
|
||||
@Bean
|
||||
public SchedulerFactoryBean setSchedulerFactoryBean(ApplicationContext applicationContext) {
|
||||
SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
|
||||
|
||||
AutoWiringSpringBeanJobFactory jobFactory = new AutoWiringSpringBeanJobFactory();
|
||||
jobFactory.setApplicationContext(applicationContext);
|
||||
|
||||
schedulerFactoryBean.setJobFactory(jobFactory); <1>
|
||||
schedulerFactoryBean.setDataSource(quartzDataSource()); <2>
|
||||
schedulerFactoryBean.setApplicationContext(applicationContext);
|
||||
schedulerFactoryBean.setTransactionManager(quartzTransactionManager()); <3>
|
||||
schedulerFactoryBean.setWaitForJobsToCompleteOnShutdown(true);
|
||||
schedulerFactoryBean.setStartupDelay(60);
|
||||
schedulerFactoryBean.setOverwriteExistingJobs(true);
|
||||
|
||||
Properties properties = new Properties();
|
||||
properties.putAll(quartzProperties.getProperties()); <4>
|
||||
schedulerFactoryBean.setQuartzProperties(properties);
|
||||
|
||||
schedulerFactoryBean.setTriggers(
|
||||
knoxSyncBatchConfig.knoxApprovalSyncTrigger().getObject(),
|
||||
userBatchConfig.batchUserLongTermCheckTrigger().getObject(),
|
||||
userBatchConfig.batchUserAuthExpiredTrigger().getObject(),
|
||||
sysUseLogBatchConfig.batchSysUseLogTrigger().getObject(),
|
||||
sysUseLogBatchConfig.batchMenuUseHistoryTrigger().getObject()
|
||||
); <5>
|
||||
|
||||
return schedulerFactoryBean;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConfigurationProperties(prefix = "spring.quartz.datasource")
|
||||
@QuartzDataSource <2>
|
||||
public DataSource quartzDataSource() {
|
||||
return DataSourceBuilder.create().type(HikariDataSource.class).build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@QuartzTransactionManager <3>
|
||||
public DataSourceTransactionManager quartzTransactionManager() {
|
||||
return new DataSourceTransactionManager(quartzDataSource());
|
||||
}
|
||||
----
|
||||
|
||||
<1> Spring Bean을 AutoWiring
|
||||
<2> Job 정보를 조회, 저회 저장할 때 사용하는 Datasource (resources-dev, resources-prod/application.properties 참조)
|
||||
<3> Quartz 전용 TransactionManager
|
||||
<4> Quartz 설정 파일 세팅
|
||||
<5> SchedulerFactoryBean 에서 실행 할 Trigger
|
||||
|
||||
== JobDetail 및 Trigger Bean 등록
|
||||
아래는 KnoxSyncBatchConfig에 수행할 Job 클래스를 설정한 JobDetail Bean과 Trigger Bean을 등록한 예이다.
|
||||
|
||||
.KnoxSyncBatchConfig.java
|
||||
[source, java]
|
||||
----
|
||||
@Value("${knox.approval.sync.cron}")
|
||||
private String knoxApprovalSyncCron;
|
||||
|
||||
@Bean
|
||||
public JobDetail knoxApprovalSyncJobDetail() {
|
||||
return JobBuilder
|
||||
.newJob(KnoxSyncBatchExecutor.class) <1>
|
||||
.withIdentity("knoxApprovalSyncJob")
|
||||
.withDescription("Knox Approval Sync Batch")
|
||||
.storeDurably(true)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public CronTriggerFactoryBean knoxApprovalSyncTrigger() {
|
||||
CronTriggerFactoryBean approvalTrigger = new CronTriggerFactoryBean();
|
||||
approvalTrigger.setJobDetail(knoxApprovalSyncJobDetail());
|
||||
approvalTrigger.setCronExpression(knoxApprovalSyncCron);
|
||||
return approvalTrigger;
|
||||
}
|
||||
----
|
||||
|
||||
<1> 배치잡 실행시 QuartzJobBean을 상속 받은 KnoxSyncBatchExecutor의 executeInternal 메소드가 실행된다.
|
||||
|
||||
== Job 구현
|
||||
QuartzJobBean은 Spring Bean을 DI Job의 구현체에서 사용할 수 있도록 Job을 구현한 추상 클래스이며,
|
||||
QuartzJobBean을 상속 받아 executeInternal 메소드를 오버라이드하여 실행할 배치 Service의 메소드를 호출한다.
|
||||
|
||||
.KnoxSyncBatchExecutor.java
|
||||
[source,java]
|
||||
----
|
||||
@Component
|
||||
@Log4j2
|
||||
public class KnoxSyncBatchExecutor extends QuartzJobBean {
|
||||
|
||||
@Value("${node-id}")
|
||||
private String nodeId;
|
||||
|
||||
@Autowired
|
||||
private KnoxApprovalSyncService knoxApprovalSyncService;
|
||||
|
||||
@Override
|
||||
protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
|
||||
JobDetail jobDetail = jobExecutionContext.getJobDetail();
|
||||
log.info(new StringBuilder()
|
||||
.append("nodeId : ").append(nodeId).append(", ")
|
||||
.append("jobName : ").append(jobDetail.getKey().getName()).append(", ")
|
||||
.append("description : ").append(jobDetail.getDescription())
|
||||
);
|
||||
knoxApprovalSyncService.synchronizeKnox();
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
CAUTION: UserBatchExecutor의 경우 JobKey(JobBuilder.withIdentity 설정 값)를 이용해 분기 처리 하고 있으므로 Job 등록시 Name 명 지정에 주의하도록 한다.
|
||||
|
||||
.UserBatchConfig.java
|
||||
[source, java]
|
||||
----
|
||||
/**
|
||||
* 장기 미사용자 관리 Job
|
||||
*/
|
||||
@Bean
|
||||
public JobDetail batchUserLongTermCheckJob() {
|
||||
return JobBuilder
|
||||
.newJob(UserBatchExecutor.class)
|
||||
.withIdentity("batchUserLongTermCheck")
|
||||
.withDescription("User LongTerm Check Batch")
|
||||
.storeDurably(true)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 만료 권한 삭제 Job
|
||||
*/
|
||||
@Bean
|
||||
public JobDetail batchUserAuthExpiredJob() {
|
||||
return JobBuilder
|
||||
.newJob(UserBatchExecutor.class)
|
||||
.withIdentity("batchUserAuthExpired")
|
||||
.withDescription("User Auth Expired Batch")
|
||||
.storeDurably(true)
|
||||
.build();
|
||||
}
|
||||
----
|
||||
|
||||
.UserBatchExecutor.java
|
||||
[source, java]
|
||||
----
|
||||
@Override
|
||||
protected void executeInternal(JobExecutionContext jobExecutionContext) {
|
||||
JobDetail jobDetail = jobExecutionContext.getJobDetail();
|
||||
String jobName = jobDetail.getKey().getName();
|
||||
log.info(new StringBuilder()
|
||||
.append("nodeId : ").append(nodeId).append(", ")
|
||||
.append("jobName : ").append(jobName).append(", ")
|
||||
.append("description : ").append(jobDetail.getDescription())
|
||||
);
|
||||
switch (jobName) {
|
||||
case "batchUserLongTermCheck" -> userBatchService.execUserLongTermCheck();
|
||||
case "batchUserAuthExpired" -> userBatchService.execUserAuthValidCheck();
|
||||
case "batchUserAuthExpiredMailing" -> {
|
||||
try {
|
||||
userBatchService.execUserAuthValidAlarmMail();
|
||||
} catch (IOException | MessagingException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
default -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
@@ -0,0 +1,46 @@
|
||||
= Redis 설정
|
||||
|
||||
== RedisConfig
|
||||
SDL은 어플리케이션 속도 향상을 위해 자주 사용하는 Data를 Cache하고 있다.
|
||||
기본적으로 Local, Dev 환경에서는 Spring에서 제공하고 있는 ConcurrentMapCacheManager를 사용해
|
||||
Data를 Cache하고 있지만 Clustering 환경에서 Cache Replicated를 위해서
|
||||
Redis를 사용한다.
|
||||
|
||||
IMPORTANT: Ehcache 3부터 RMI, JMS 방식이 Cache 동기화 지원이 중단 됐으니 Cache 복제가 필요한 경우 Redis를 반드시 사용해야 한다.
|
||||
|
||||
application.properties
|
||||
----
|
||||
spring.cache.type=redis
|
||||
spring.data.redis.host=redis 서버 주소
|
||||
spring.data.redis.port=redis 포트
|
||||
----
|
||||
|
||||
RedisConfig
|
||||
----
|
||||
@Bean
|
||||
public RedisCacheManagerBuilderCustomizer redisCacheManagerBuilderCustomizer() {
|
||||
return (builder) -> builder
|
||||
.withCacheConfiguration("message-all",
|
||||
RedisCacheConfiguration.defaultCacheConfig())
|
||||
.withCacheConfiguration("message",
|
||||
RedisCacheConfiguration.defaultCacheConfig())
|
||||
.withCacheConfiguration("menu-all",
|
||||
RedisCacheConfiguration.defaultCacheConfig())
|
||||
.withCacheConfiguration("menu-label-all",
|
||||
RedisCacheConfiguration.defaultCacheConfig())
|
||||
.withCacheConfiguration("menu-full-path-all",
|
||||
RedisCacheConfiguration.defaultCacheConfig())
|
||||
.withCacheConfiguration("page-full-path-all",
|
||||
RedisCacheConfiguration.defaultCacheConfig())
|
||||
.withCacheConfiguration("api-full-path-all",
|
||||
RedisCacheConfiguration.defaultCacheConfig())
|
||||
.withCacheConfiguration("api-user",
|
||||
RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofSeconds(300)))
|
||||
.withCacheConfiguration("api-user-menu",
|
||||
RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofSeconds(300)))
|
||||
.withCacheConfiguration("page-all-by-menu-auth",
|
||||
RedisCacheConfiguration.defaultCacheConfig());
|
||||
}
|
||||
----
|
||||
|
||||
각 Cache별 필요한 세팅은 redisCacheManagerBuilderCustomizer 메소드에서 할수 있으니 시스템 환경에 맞춰 적절하게 수정한다.
|
||||
@@ -0,0 +1,272 @@
|
||||
= UI 라이브러리(uiSDL)
|
||||
|
||||
== UI 라이브러리(uiSDL)란?
|
||||
UI라이브러리(uiSDL)은 웹 UI 개발 리드타임 단축을 위하여 실무에 유용한 컴포넌트들을 담은 UI라이브러리입니다. +
|
||||
Vue2 및 Vue3 용 라이브러리가 나뉘어져 있습니다.
|
||||
|
||||
NOTE: UI라이브러리 시스템 접속 +
|
||||
vue2 : http://uisdl.scp.samsung.net/v2/[window=_blnak] +
|
||||
vue3 : http://uisdl.scp.samsung.net/v3/[window=_blnak]
|
||||
|
||||
IMPORTANT: 접속이 안되는 경우 window host 파일에 아래와 같이 추가해 주시기 바랍니다 +
|
||||
10.195.53.147 uisdl.scp.samsung.net
|
||||
|
||||
|
||||
== 컴포넌트 주요 특징
|
||||
|
||||
- 삼성전자 웹 시스템 개발 시 빠르게 웹 화면을 개발할 수 있도록 사용빈도가 높은 UI 환경 구성 요소들을 오픈 소스 Framework인 Vue.js와 Bootstrap 기반으로 제작하여 제공합니다.
|
||||
|
||||
- 시스템의 특성에 맞게 제작이 가능하도록 화면의 구조인 레이아웃 6종과 단일 UI구성요소인 컴포넌트 37종으로 구성되어 있습니다
|
||||
+
|
||||
image::uisdl_1.png[]
|
||||
|
||||
- 각 페이지 화면의 우측 상단에 개발과 디자인 탭으로 구분되어 있습니다.
|
||||
+
|
||||
개발 탭에서는 개발 가이드와 간단한 기능 테스트를 해볼 수 있는 Code Demo 가 있고, 디자인 탭에서는 전사 UX 표준을 바탕으로 디자인 원칙과 구성요소들을 확인할 수 있습니다.
|
||||
+
|
||||
image::uisdl_2.png[]
|
||||
|
||||
== 컴포넌트 개발 환경
|
||||
* VueJS 버전 3
|
||||
* Used Open-source
|
||||
** bootstrap: 5.3.0 or Higher
|
||||
** vue-datepicker-next: 1.0.3 or Higher
|
||||
|
||||
== 컴포넌트 적용 방법
|
||||
|
||||
. Install from Local file
|
||||
+
|
||||
로컬 파일로부터 컴포넌트를 설치할 수 있습니다. 아래의 경로에서 파일을 다운로드 받습니다.
|
||||
+
|
||||
http://uisdl.scp.samsung.net/v3/download/sdl-component-3.0.0.tgz[sdl-component-3.0.0.tgz 다운로드]
|
||||
+
|
||||
해당 파일을 로컬의 임의의 위치로 복사한 다음, NPM 명령어로 컴포넌트를 설치합니다. 설치하실 프로젝트 루트 폴더로 이동한 다음, 아래 명령어를 커맨드 창에서 입력합니다.
|
||||
+
|
||||
[source, shell]
|
||||
----
|
||||
npm install --save ./{your-path}/sdl-component-{version}.tgz
|
||||
----
|
||||
|
||||
. Import bootstrap
|
||||
+
|
||||
'bootstrap' 관련 스타일과 스크립트를 Entry Point 파일에 Import 합니다.
|
||||
+
|
||||
[source, javascript]
|
||||
----
|
||||
// bootstrap css등록 및 popper가 포함된 bootstrap bundle js 임포트
|
||||
import 'bootstrap/dist/css/bootstrap.min.css';
|
||||
import 'bootstrap/dist/js/bootstrap.bundle.min';
|
||||
----
|
||||
|
||||
. Import sdl-component
|
||||
+
|
||||
'sdl-component' 관련 스타일과 스크립트를 Entry Point 파일에 Import 하고
|
||||
+
|
||||
[source, javascript]
|
||||
----
|
||||
import 'sdl-component/src/assets/css/custom.css';
|
||||
import SdlComponent from 'sdl-component';
|
||||
----
|
||||
+
|
||||
전역 Vue(app)에서 사용할 수 있도록 Plugin을 등록합니다.
|
||||
+
|
||||
[source, javascript]
|
||||
----
|
||||
app.use(SdlComponent);
|
||||
----
|
||||
|
||||
. Complete
|
||||
+
|
||||
이제 시스템 전역에서 'sdl-component'를 사용하실 수 있습니다
|
||||
|
||||
== UI 라이브러리 목록
|
||||
활용빈도, 사용성을 고려하여 사내시스템 개발에 필요한 Library를 제공합니다.
|
||||
|
||||
* 레이아웃
|
||||
+
|
||||
화면의 구조를 정의하는 레이아웃 유형
|
||||
+
|
||||
[cols="7,33,60",options="header"]
|
||||
|===
|
||||
|No |Library |설명
|
||||
|
||||
|1
|
||||
|GNB + Contents
|
||||
|GNB와 콘텐츠로 구성된 화면 형태
|
||||
|
||||
|2
|
||||
|GNB + LNB + Contents
|
||||
|GNB, LNB, 콘텐츠로 구성된 화면 형태
|
||||
|
||||
|3
|
||||
|GNB + LNB + Contents + Footer
|
||||
|GNB, LNB, Footer, 콘텐츠로 구성된 화면 형태
|
||||
|
||||
|4
|
||||
|GNB + LNB + Aside + Contents + Footer
|
||||
|GNB, LNB, Footer, Aside, 콘텐츠로 구성된 화면 형태
|
||||
|
||||
|5
|
||||
|Popup
|
||||
|현재 화면에서 추가적으로 띄우는 레이어 형태의 창으로, 사용 유형에 따라 구별하여 사용
|
||||
|
||||
|6
|
||||
|Center Frame
|
||||
|본 화면의 상하좌우 중앙에 콘텐츠 박스가 위치
|
||||
|===
|
||||
|
||||
* 컴포넌트
|
||||
+
|
||||
화면을 구성하는 요소 단위
|
||||
+
|
||||
[cols="7,33,60",options="header"]
|
||||
|===
|
||||
|No |Library |설명
|
||||
|1
|
||||
|GNB
|
||||
|(Global Navigation Bar) 시스템 명과 메뉴를 포함한 네비게이션으로 화면의 최 상단에 구성
|
||||
|
||||
|2
|
||||
|LNB
|
||||
|(Left Navigation Bar) 서브메뉴라고 불리며 화면의 좌측에 구성
|
||||
|
||||
|3
|
||||
|Aside
|
||||
|자주 사용하는 기능을 빠르게 접근(Quick Access)할 수 있도록 제공하는 영역
|
||||
|
||||
|4
|
||||
|Footer
|
||||
|Copyright, 사이트 맵, 이용약관 등의 정보를 포함하며, 화면의 하단에 구성
|
||||
|
||||
|5
|
||||
|Card List
|
||||
|한 레이아웃에서 여러 개의 카드를 함께 사용할 때 사용
|
||||
|
||||
|6
|
||||
|Search Grid
|
||||
|검색조건을 입력/선택하는 조회 영역과 데이터결과를 호출하는 리스트영역으로 구성할 때 사용
|
||||
|
||||
|7
|
||||
|Form
|
||||
|여러 개의 컨트롤(Input Box, Radio Button, Dropdown Box 등)를 활용하여 하나의 입력 폼으로 구성할 때 사용
|
||||
|
||||
|8
|
||||
|Form Detail
|
||||
|폼 화면에서 입력한 정보를 화면에 표시할 때 사용
|
||||
|
||||
|9
|
||||
|Board Detail
|
||||
|게시판 게시물의 기본적인 상세 화면
|
||||
|
||||
|10
|
||||
|Tree Detail
|
||||
|계층 구조를 나타내는 트리 요소와 트리에서 선택한 정보를 페이지 전환 없이 즉각적으로 보여줄 때 사용
|
||||
|
||||
|11
|
||||
|Create Board
|
||||
|게시판 게시물의 기본적인 입력 화면
|
||||
|
||||
|12
|
||||
|Approval
|
||||
|시스템에서 Knox 결재나 시스템 내부 결재시 사용
|
||||
|
||||
|13
|
||||
|Reply
|
||||
|간단하게 사용자 간의 의사소통을 할 수 있는 폼 형태로 구성
|
||||
|
||||
|14
|
||||
|Notice
|
||||
|사용자에게 시스템 내 서비스 관련 정보들을 공지 시 사용
|
||||
|
||||
|15
|
||||
|Alert
|
||||
|공지나 안내 사항 같은 부가적인 정보, 경고 상태 혹은 사용자의 행동에 대한 즉각적인 피드백이 필요할 때 사용
|
||||
|
||||
|16
|
||||
|Badge
|
||||
|알림 목적으로 사용하거나, 데이터를 시각적으로 강조하고 싶을 때 사용
|
||||
|
||||
|17
|
||||
|Button
|
||||
|사용자가 클릭하여 기능을 수행할 수 있는 그래픽 요소
|
||||
|
||||
|18
|
||||
|Card
|
||||
|다양한 정보들을 그룹화하여 하나의 카드 모양 안에 구성할 때 사용
|
||||
|
||||
|19
|
||||
|Collapse(Toggle)
|
||||
|사용자가 두개의 반대되는 상태 중에서 선택할 수 있는 온오프 스위치
|
||||
|
||||
|20
|
||||
|Tooltip
|
||||
|페이지 요소 또는 기능에 대한 추가 정보를 제공하는 간단하고 유용한 메시지
|
||||
|
||||
|21
|
||||
|Text Input
|
||||
|입력 폼 요소 중 한 줄의 비교적 짧은 데이터를 입력할 경우 사용
|
||||
|
||||
|22
|
||||
|Textarea
|
||||
|입력 폼 요소 중 데이터의 내용이 일정하지 않거나, 여러 줄의 데이터를 입력할 경우 사용
|
||||
|
||||
|23
|
||||
|Radio
|
||||
|두개 이상의 항목 중 하나를 선택할 때 사용
|
||||
|
||||
|24
|
||||
|Select
|
||||
|사용자가 옵션 중 하나 또는 다중으로 선택할 수 있을 때 사용
|
||||
|
||||
|25
|
||||
|Checkbox
|
||||
|여러 개의 항목 중 하나 이상을 다중 선택할 때 사용하거나 On/Off의 Toggle 경우에 사용
|
||||
|
||||
|26
|
||||
|Table
|
||||
|데이터/정보를 열과 행으로 구분하여 사용자가 쉽게 읽고 이해할 수 있도록 제공
|
||||
|
||||
|27
|
||||
|Progress
|
||||
|사용자 액션에 대한 즉각 데이터가 표시가 어려운 경우(데이터의 로딩 처리 시간이 긴 경우) 작업의 진행 상태에 대한 정확한 피드백을 제공하기 위해 사용
|
||||
|
||||
|28
|
||||
|Spinner
|
||||
|사용자 액션에 대한 즉각 데이터가 표시가 어려운 경우(데이터의 로딩 처리 시간이 긴 경우) 작업의 진행 상태에 대한 정확한 피드백을 제공하지 못할 경우 사용
|
||||
|
||||
|29
|
||||
|Navbar
|
||||
|시스템 명과 메뉴를 포함한 네비게이션 영역으로 주로 상단(GNB)에 제공
|
||||
|
||||
|30
|
||||
|File Attachment
|
||||
|사용자가 첨부파일을 다운받거나 업로드할 경우에 사용
|
||||
|
||||
|31
|
||||
|Tree
|
||||
|목록의 계층 구조를 표현하기 위해 사용하는 요소
|
||||
|
||||
|32
|
||||
|Datepicker
|
||||
|날짜를 선택하고 그에 해당하는 데이터를 얻을 수 있도록 위젯 형태로 제공
|
||||
|
||||
|33
|
||||
|Breadcrumb
|
||||
|시스템 내 사용자의 메뉴의 이동경로를 의미하며 현재 사용자의 위치를 보여줄 때 사용
|
||||
|
||||
|34
|
||||
|Pagination
|
||||
|많은 양의 데이터를 여러 페이지로 나누고, 이전, 다음 페이지 혹은 특정 페이지로 이동할 수 있는 일련의 링크를 목록 하단에 배치하여 제공
|
||||
|
||||
|35
|
||||
|Tab(Basic, Progress)
|
||||
|페이지 이동 및 시점 전환 없이 그룹화된 콘텐츠를 제공할 때 사용
|
||||
|
||||
|36
|
||||
|Jumbotron
|
||||
|어떤 특별한 내용이나 정보를 눈에 띄게 보여주기 위한 박스 형태의 공간
|
||||
|
||||
|37
|
||||
|Carousel
|
||||
|한 공간에 여러 개의 콘텐츠를 슬라이드 형태로 제공
|
||||
|===
|
||||
@@ -0,0 +1,460 @@
|
||||
/* @import "https://fonts.googleapis.com/css?family=Karla:400,700|Montserrat:400,700"; !normalize.css v2.1.2 | MIT License | git.io/normalize*/
|
||||
@font-face {
|
||||
font-family: 'D2 coding';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url('../fonts/D2Coding.eot');
|
||||
src: local('※'), local('D2Coding'),
|
||||
url('../fonts/D2Coding.eot?#iefix') format('embedded-opentype'),
|
||||
url('../fonts/D2Coding.woff') format('woff');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'D2 coding';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: url('../fonts/D2CodingBold.eot');
|
||||
src: local('※'), local('D2Coding Bold'),
|
||||
url('../fonts/D2CodingBold.eot?#iefix') format('embedded-opentype'),
|
||||
url('../fonts/D2CodingBold.woff') format('woff');
|
||||
}
|
||||
article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}
|
||||
audio,video{display:inline-block}
|
||||
audio:not([controls]){display:none;height:0}
|
||||
html{font-family:"D2 coding";-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}
|
||||
a{background:none}
|
||||
a:focus{outline:thin dotted}
|
||||
a:active,a:hover{outline:0}
|
||||
h1{font-size:2em;margin:.67em 0}
|
||||
abbr[title]{border-bottom:1px dotted}
|
||||
b,strong{font-weight:bold}
|
||||
dfn{font-style:italic}
|
||||
hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}
|
||||
mark{background:#ff0;color:#000}
|
||||
code,kbd,pre,samp{font-family:"D2 coding";font-size:1em}
|
||||
pre{white-space:pre-wrap}
|
||||
q{quotes:"\201C" "\201D" "\2018" "\2019"}
|
||||
small{font-size:80%}
|
||||
sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}
|
||||
sup{top:-.5em}
|
||||
sub{bottom:-.25em}
|
||||
img{border:0}
|
||||
svg:not(:root){overflow:hidden}
|
||||
figure{margin:0}
|
||||
fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}
|
||||
legend{border:0;padding:0}
|
||||
button,input,select,textarea{font-family:inherit;font-size:100%;margin:0}
|
||||
button,input{line-height:normal}
|
||||
button,select{text-transform:none}
|
||||
button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}
|
||||
button[disabled],html input[disabled]{cursor:default}
|
||||
input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0}
|
||||
button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}
|
||||
textarea{overflow:auto;vertical-align:top}
|
||||
table{border-collapse:collapse;border-spacing:0}
|
||||
*,*::before,*::after{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}
|
||||
html,body{font-size:14px}
|
||||
body{background:#fff;color:rgba(0,0,0,.8);padding:0;margin:0;font-family:"D2 coding";font-weight:400;font-style:normal;line-height:1;position:relative;cursor:auto;tab-size:4;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased}
|
||||
a:hover{cursor:pointer}
|
||||
img,object,embed{max-width:100%;height:auto}
|
||||
object,embed{height:100%}
|
||||
img{-ms-interpolation-mode:bicubic}
|
||||
.left{float:left!important}
|
||||
.right{float:right!important}
|
||||
.text-left{text-align:left!important}
|
||||
.text-right{text-align:right!important}
|
||||
.text-center{text-align:center!important}
|
||||
.text-justify{text-align:justify!important}
|
||||
.hide{display:none}
|
||||
img,object,svg{display:inline-block;vertical-align:middle}
|
||||
textarea{height:auto;min-height:50px}
|
||||
select{width:100%}
|
||||
.center{margin-left:auto;margin-right:auto}
|
||||
.stretch{width:100%}
|
||||
.subheader,.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{line-height:1.45;font-weight:800;margin-top:0;margin-bottom:.25em}
|
||||
div,dl,dt,dd,ul,ol,li,h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6,pre,form,p,blockquote,th,td{margin:0;padding:0;direction:ltr}
|
||||
a{color:#2156a5;text-decoration:underline;line-height:inherit}
|
||||
a:hover,a:focus{color:#1d4b8f}
|
||||
a img{border:0}
|
||||
p{font-family:inherit;font-weight:400;font-size:1em;line-height:1.6;margin-bottom:1.25em;text-rendering:optimizeLegibility}
|
||||
p aside{font-size:.875em;line-height:1.35;font-style:italic}
|
||||
h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{font-weight:300;font-style:normal;color:inherit;text-rendering:optimizeLegibility;margin-top:1em;margin-bottom:.5em;line-height:1.0125em}
|
||||
h1 small,h2 small,h3 small,#toctitle small,.sidebarblock>.content>.title small,h4 small,h5 small,h6 small{font-size:60%;color:#e99b8f;line-height:0}
|
||||
h1{font-size:2.125em}
|
||||
h2{font-size:1.6875em}
|
||||
h3,#toctitle,.sidebarblock>.content>.title{font-size:1.375em}
|
||||
h4,h5{font-size:1.125em}
|
||||
h6{font-size:1em}
|
||||
hr{border:solid #dddddf;border-width:1px 0 0;clear:both;margin:1.25em 0 1.1875em;height:0}
|
||||
em,i{font-style:italic;line-height:inherit}
|
||||
strong,b{font-weight:bold;line-height:inherit}
|
||||
small{font-size:60%;line-height:inherit}
|
||||
code{font-family:D2 coding,Monaco,Menlo,Consolas,courier new,monospace;font-weight:400;color:rgba(0,0,0,.9)}
|
||||
ul,ol,dl{font-size:1em;line-height:1.6;margin-bottom:1.25em;list-style-position:outside;font-family:inherit}
|
||||
ul,ol{margin-left:1.5em}
|
||||
ul li ul,ul li ol{margin-left:1.25em;margin-bottom:0;font-size:1em}
|
||||
ul.square li ul,ul.circle li ul,ul.disc li ul{list-style:inherit}
|
||||
ul.square{list-style-type:square}
|
||||
ul.circle{list-style-type:circle}
|
||||
ul.disc{list-style-type:disc}
|
||||
ol li ul,ol li ol{margin-left:1.25em;margin-bottom:0}
|
||||
dl dt{margin-bottom:.3125em;font-weight:bold}
|
||||
dl dd{margin-bottom:1.25em}
|
||||
abbr,acronym{text-transform:uppercase;font-size:90%;color:rgba(0,0,0,.8);border-bottom:1px dotted #ddd;cursor:help}
|
||||
abbr{text-transform:none}
|
||||
blockquote{margin:0 0 1.25em;padding:.5625em 1.25em 0 1.1875em;border-left:1px solid #ddd}
|
||||
blockquote cite{display:block;font-size:.9375em;color:rgba(0,0,0,.6)}
|
||||
blockquote cite::before{content:"\2014 \0020"}
|
||||
blockquote cite a,blockquote cite a:visited{color:rgba(0,0,0,.6)}
|
||||
blockquote,blockquote p{line-height:1.6;color:rgba(0,0,0,.85)}
|
||||
table{background:#fff;margin-bottom:1.25em;border:solid 1px #cacaca;border-spacing:0}
|
||||
table thead,table tfoot{background:#f7f8f7;font-weight:700}
|
||||
table thead tr th,table thead tr td,table tfoot tr th,table tfoot tr td{padding:.5em .625em .625em;font-size:inherit;color:#000;text-align:left}
|
||||
table tr th,table tr td{padding:.5625em .625em;font-size:inherit;color:#000}
|
||||
table tr td{word-break:break-all}
|
||||
table tr.even,table tr.alt{background:#fafafa}
|
||||
table thead tr th,table tfoot tr th,table tbody tr td,table tr td,table tfoot tr td{display:table-cell;line-height:1.6}
|
||||
body{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;tab-size:4}
|
||||
h1,h2,h3,#toctitle,.sidebarblock > .content > .title,h4,h5,h6{line-height:1.2;word-spacing:-.05em}
|
||||
.clearfix:before,.clearfix:after,.float-group:before,.float-group:after{content:" ";display:table}
|
||||
.clearfix:after,.float-group:after{clear:both}
|
||||
:not(pre) > code{font-size:.8525em;font-style:normal!important;letter-spacing:0;padding:.1em .3em .2em;background-color:rgba(27,31,35,.05);border-radius:4px;text-rendering:optimizeSpeed}
|
||||
pre,pre > code{line-height:1.85;color:rgba(0,0,0,.9);font-family:D2 coding,Monaco,Menlo,Consolas,courier new,monospace;font-weight:400;text-rendering:optimizeSpeed;word-break:normal}
|
||||
pre{overflow:auto}
|
||||
em em{font-style:normal}
|
||||
strong strong{font-weight:400}
|
||||
.keyseq{color:#6b625c}
|
||||
kbd{font-family:D2 coding,Monaco,Menlo,Consolas,courier new,monospace;display:inline-block;color:#000;font-size:.65em;line-height:1.45;background-color:#f7f7f7;border:1px solid #ccc;-webkit-border-radius:3px;border-radius:3px;-webkit-box-shadow:0 1px 0 rgba(0,0,0,.2),0 0 0 .1em #fff inset;box-shadow:0 1px 0 rgba(0,0,0,.2),0 0 0 .1em #fff inset;margin:0 .15em;padding:.2em .5em;vertical-align:middle;position:relative;top:-.1em;white-space:nowrap}
|
||||
.keyseq kbd:first-child{margin-left:0}
|
||||
.keyseq kbd:last-child{margin-right:0}
|
||||
.menuseq,.menu{color:#191715}
|
||||
b.button:before,b.button:after{position:relative;top:-1px;font-weight:400}
|
||||
b.button:before{content:"[";padding:0 3px 0 2px}
|
||||
b.button:after{content:"]";padding:0 2px 0 3px}
|
||||
p a > code:hover{color:rgba(0,0,0,.9)}
|
||||
#toc{border-bottom:1px solid #ddddd8;padding-bottom:.5em}
|
||||
#toc > ul{margin-left:.125em}
|
||||
#toc ul.sectlevel0 > li > a{font-style:italic}
|
||||
#toc ul.sectlevel0 ul.sectlevel1{margin:.5em 0}
|
||||
#toc ul{list-style-type:none}
|
||||
#toc li{line-height:1.3334}
|
||||
#toc a{text-decoration:none}
|
||||
#toc a:active{text-decoration:underline}
|
||||
#toctitle{color:#0b0a0a;font-size:1.2em}
|
||||
body.toc2{padding-top:90px;text-rendering:optimizeLegibility}
|
||||
#content #toc{border-style:solid;border-width:1px;border-color:#d7d7d7;margin-bottom:1.25em;padding:1.25em;background:#f1f1f1;-webkit-border-radius:4px;border-radius:4px}
|
||||
#content #toc > :first-child{margin-top:0}
|
||||
#content #toc > :last-child{margin-bottom:0}
|
||||
#footer{padding-bottom:2rem}
|
||||
#footer #footer-text{padding:2rem 0;border-top:1px solid #efefed}
|
||||
#footer-text{color:rgba(0,0,0,.6);line-height:1.44}
|
||||
.sect1{padding-bottom:.625em}
|
||||
.sect1 + .sect1{border-top:1px solid #efefed}
|
||||
#content h1 > a.anchor,h2 > a.anchor,h3 > a.anchor,#toctitle > a.anchor,.sidebarblock > .content > .title > a.anchor,h4 > a.anchor,h5 > a.anchor,h6 > a.anchor{position:absolute;z-index:1001;width:1.5ex;margin-left:-1.5ex;margin-top:.1rem;display:block;visibility:hidden;text-align:center;font-weight:400;color:rgba(0,0,0,.2)}
|
||||
#content h1 > a.anchor:hover,h2 > a.anchor:hover,h3 > a.anchor:hover,#toctitle > a.anchor:hover,.sidebarblock > .content > .title > a.anchor:hover,h4 > a.anchor:hover,h5 > a.anchor:hover,h6 > a.anchor:hover{color:#097dff;text-decoration:none}
|
||||
#content h1 > a.anchor:before,h2 > a.anchor:before,h3 > a.anchor:before,#toctitle > a.anchor:before,.sidebarblock > .content > .title > a.anchor:before,h4 > a.anchor:before,h5 > a.anchor:before,h6 > a.anchor:before{content:"\0023";font-size:.85em;display:block;padding-top:.1em}
|
||||
#content h1:hover > a.anchor,#content h1 > a.anchor:hover,h2:hover > a.anchor,h2 > a.anchor:hover,h3:hover > a.anchor,#toctitle:hover > a.anchor,.sidebarblock > .content > .title:hover > a.anchor,h3 > a.anchor:hover,#toctitle > a.anchor:hover,.sidebarblock > .content > .title > a.anchor:hover,h4:hover > a.anchor,h4 > a.anchor:hover,h5:hover > a.anchor,h5 > a.anchor:hover,h6:hover > a.anchor,h6 > a.anchor:hover{visibility:visible}
|
||||
#content h1 > a.link,h2 > a.link,h3 > a.link,#toctitle > a.link,.sidebarblock > .content > .title > a.link,h4 > a.link,h5 > a.link,h6 > a.link{color:#000;text-decoration:none}
|
||||
#content h1 > a.link:hover,h2 > a.link:hover,h3 > a.link:hover,#toctitle > a.link:hover,.sidebarblock > .content > .title > a.link:hover,h4 > a.link:hover,h5 > a.link:hover,h6 > a.link:hover{color:#262321}
|
||||
.audioblock,.imageblock,.literalblock,.listingblock,.stemblock,.videoblock{margin-bottom:1.25em}
|
||||
.admonitionblock td.content > .title,.audioblock > .title,.exampleblock > .title,.imageblock > .title,.listingblock > .title,.literalblock > .title,.stemblock > .title,.openblock > .title,.paragraph > .title,.quoteblock > .title,table.tableblock > .title,.verseblock > .title,.videoblock > .title,.dlist > .title,.olist > .title,.ulist > .title,.qlist > .title,.hdlist > .title{text-rendering:optimizeLegibility;text-align:left;font-size:1rem}
|
||||
table.tableblock > caption.title{white-space:nowrap;overflow:visible;max-width:0;padding:.6rem 0}
|
||||
table.tableblock #preamble > .sectionbody > .paragraph:first-of-type p{font-size:inherit}
|
||||
.admonitionblock > table{border-collapse:separate;border:0;background:0 0;width:100%}
|
||||
.admonitionblock > table td.icon{text-align:center;vertical-align:top;padding-top:.8em;width:80px}
|
||||
.admonitionblock > table td.icon img{max-width:initial}
|
||||
.admonitionblock > table td.icon .title{font-weight:700;text-transform:uppercase}
|
||||
.admonitionblock > table td.content{padding-left:0;padding-right:1.25em;border-left:1px solid #ddddd8}
|
||||
.admonitionblock > table td.content > :last-child > :last-child{margin-bottom:0}
|
||||
.exampleblock > .content{border-style:solid;border-width:0;border-color:#e6e6e6;margin-bottom:1.25em;padding:1.25em;background:#f1f1f1;border-radius:4px}
|
||||
.exampleblock > .content > :first-child{margin-top:0}
|
||||
.exampleblock > .content > :last-child{margin-bottom:0}
|
||||
.sidebarblock{border-style:solid;border-width:0;border-color:#d7d7d7;margin-bottom:1.25em;padding:1.25em;background:#f1f1f1;border-radius:4px;overflow:scroll}
|
||||
.sidebarblock > :first-child{margin-top:0}
|
||||
.sidebarblock > :last-child{margin-bottom:0}
|
||||
.sidebarblock > .content > .title{color:#0b0a0a;margin-top:0;text-align:center}
|
||||
.exampleblock > .content > :last-child > :last-child,.exampleblock > .content .olist > ol > li:last-child > :last-child,.exampleblock > .content .ulist > ul > li:last-child > :last-child,.exampleblock > .content .qlist > ol > li:last-child > :last-child,.sidebarblock > .content > :last-child > :last-child,.sidebarblock > .content .olist > ol > li:last-child > :last-child,.sidebarblock > .content .ulist > ul > li:last-child > :last-child,.sidebarblock > .content .qlist > ol > li:last-child > :last-child{margin-bottom:0}
|
||||
.literalblock pre,.listingblock pre:not(.highlight),.listingblock pre[class=highlight],.listingblock pre[class^="highlight "],.listingblock pre.CodeRay,.listingblock pre.prettyprint{background:#f2f2f2;color:#222;border-radius:4px}
|
||||
.sidebarblock .literalblock pre,.sidebarblock .listingblock pre:not(.highlight),.sidebarblock .listingblock pre[class=highlight],.sidebarblock .listingblock pre[class^="highlight "],.sidebarblock .listingblock pre.CodeRay,.sidebarblock .listingblock pre.prettyprint{background:#f2f2f2;color:#222}
|
||||
.literalblock pre,.literalblock pre[class],.listingblock pre,.listingblock pre[class],.listingblock pre:not(.highlight){padding:1em 1.5rem;font-size:.9285em}
|
||||
.literalblock pre.nowrap,.literalblock pre[class].nowrap,.listingblock pre.nowrap,.listingblock pre[class].nowrap{overflow-x:auto}
|
||||
.literalblock.output pre{color:#f5f5f5;background-color:rgba(0,0,0,.9)}
|
||||
.listingblock{white-space:nowrap}
|
||||
.listingblock pre.highlightjs{padding:.2rem 0}
|
||||
.listingblock pre.highlightjs > code{padding:1em 1.5rem;border-radius:4px}
|
||||
.listingblock > .content{position:relative}
|
||||
.listingblock code[data-lang]:before{content:attr(data-lang);position:absolute;font-size:.8em;font-weight:700;top:.425rem;right:.5rem;line-height:1;text-transform:uppercase;color:#999;display:block}
|
||||
.listingblock.terminal pre .command:before{content:attr(data-prompt);padding-right:.5em;color:#999}
|
||||
.listingblock.terminal pre .command:not([data-prompt]):before{content:"$"}
|
||||
table.pyhltable{border-collapse:separate;border:0;margin-bottom:0;background:0 0}
|
||||
table.pyhltable td{vertical-align:top;padding-top:0;padding-bottom:0;line-height:1.45}
|
||||
table.pyhltable td.code{padding-left:.75em;padding-right:0}
|
||||
pre.pygments .lineno,table.pyhltable td:not(.code){color:#999;padding-left:0;padding-right:.5em;border-right:1px solid #ddddd8}
|
||||
pre.pygments .lineno{display:block;margin-right:.25em}
|
||||
table.pyhltable .linenodiv{background:0 0!important;padding-right:0!important}
|
||||
.quoteblock{margin:0 1em 1.25em 1.5em;display:block;text-align:left;padding-left:20px}
|
||||
.quoteblock blockquote,.quoteblock blockquote p{color:rgba(0,0,0,.85);line-height:1.75;letter-spacing:0}
|
||||
.quoteblock blockquote{margin:0;padding:0;border:0;position:relative}
|
||||
.quoteblock blockquote:before{content:"\201c";font-size:2.75em;font-weight:700;line-height:.6em;margin-left:0;margin-right:1rem;margin-top:.8rem;color:rgba(0,0,0,.1);position:absolute;top:0;left:-30px}
|
||||
.quoteblock blockquote > .paragraph:last-child p{margin-bottom:0}
|
||||
.quoteblock .attribution{margin-right:.5ex}
|
||||
.quoteblock .quoteblock{margin-left:0;margin-right:0;padding:.5em 0;border-left:3px solid rgba(0,0,0,.6)}
|
||||
.quoteblock .quoteblock blockquote{padding:0 0 0 .75em}
|
||||
.quoteblock .quoteblock blockquote:before{display:none}
|
||||
.verseblock{margin:0 1em 1.25em 0;background-color:#f1f1f1;padding:1rem 1.4rem;border-radius:4px}
|
||||
.verseblock pre{font-family:D2 coding,Monaco,Menlo,Consolas,courier new,monospace;font-size:.9rem;color:rgba(0,0,0,.85);font-weight:300;text-rendering:optimizeLegibility}
|
||||
.verseblock pre strong{font-weight:400}
|
||||
.verseblock .attribution{margin-top:1.25rem;margin-left:.5ex}
|
||||
.quoteblock .attribution,.verseblock .attribution{font-size:.9375em;line-height:1.45;font-style:italic}
|
||||
.quoteblock .attribution br,.verseblock .attribution br{display:none}
|
||||
.quoteblock .attribution cite,.verseblock .attribution cite{display:block;letter-spacing:-.025em;color:rgba(0,0,0,.6)}
|
||||
.quoteblock.abstract{margin:0 0 1.25em;display:block}
|
||||
.quoteblock.abstract blockquote,.quoteblock.abstract blockquote p{text-align:left;word-spacing:0}
|
||||
.quoteblock.abstract blockquote:before,.quoteblock.abstract blockquote p:first-of-type:before{display:none}
|
||||
table.tableblock{max-width:100%;border-collapse:separate;overflow-x:scroll}
|
||||
table.tableblock td > .paragraph:last-child p > p:last-child,table.tableblock th > p:last-child,table.tableblock td > p:last-child{margin-bottom:0}
|
||||
table.tableblock,th.tableblock,td.tableblock{border:0 solid #cacaca}
|
||||
table.grid-all th.tableblock,table.grid-all td.tableblock{border-width:0 1px 1px 0}
|
||||
table.grid-all tfoot > tr > th.tableblock,table.grid-all tfoot > tr > td.tableblock{border-width:1px 1px 0 0}
|
||||
table.grid-cols th.tableblock,table.grid-cols td.tableblock{border-width:0 1px 0 0}
|
||||
table.grid-all * > tr > .tableblock:last-child,table.grid-cols * > tr > .tableblock:last-child{border-right-width:0}
|
||||
table.grid-rows th.tableblock,table.grid-rows td.tableblock{border-width:0 0 1px}
|
||||
table.grid-all tbody > tr:last-child > th.tableblock,table.grid-all tbody > tr:last-child > td.tableblock,table.grid-all thead:last-child > tr > th.tableblock,table.grid-rows tbody > tr:last-child > th.tableblock,table.grid-rows tbody > tr:last-child > td.tableblock,table.grid-rows thead:last-child > tr > th.tableblock{border-bottom-width:0}
|
||||
table.grid-rows tfoot > tr > th.tableblock,table.grid-rows tfoot > tr > td.tableblock{border-width:1px 0 0}
|
||||
table.frame-all{border-width:1px}
|
||||
table.frame-sides{border-width:0 1px}
|
||||
table.frame-topbot{border-width:1px 0}
|
||||
th.halign-left,td.halign-left{text-align:left}
|
||||
th.halign-right,td.halign-right{text-align:right}
|
||||
th.halign-center,td.halign-center{text-align:center}
|
||||
th.valign-top,td.valign-top{vertical-align:top}
|
||||
th.valign-bottom,td.valign-bottom{vertical-align:bottom}
|
||||
th.valign-middle,td.valign-middle{vertical-align:middle}
|
||||
table thead th,table tfoot th{font-weight:700}
|
||||
tbody tr th{display:table-cell;line-height:1.6;background:#f7f8f7}
|
||||
tbody tr th,tbody tr th p,tfoot tr th,tfoot tr th p{color:#34302d;font-weight:700}
|
||||
p.tableblock{font-size:1em}
|
||||
td > div.verse{white-space:pre}
|
||||
ol{margin-left:1.75em}
|
||||
ul li ol{margin-left:1.5em}
|
||||
dl dd{margin-left:1.125em}
|
||||
dl dd:last-child,dl dd:last-child > :last-child{margin-bottom:0}
|
||||
ol > li p,ul > li p,ul dd,ol dd,.olist .olist,.ulist .ulist,.ulist .olist,.olist .ulist{margin-bottom:.625em}
|
||||
ul.unstyled,ol.unnumbered,ul.checklist,ul.none{list-style-type:none}
|
||||
ul.unstyled,ol.unnumbered,ul.checklist{margin-left:.625em}
|
||||
ul.checklist li > p:first-child > .fa-square-o:first-child,ul.checklist li > p:first-child > .fa-check-square-o:first-child{width:1em;font-size:.85em}
|
||||
ul.checklist li > p:first-child > input[type=checkbox]:first-child{width:1em;position:relative;top:1px}
|
||||
ul.inline{margin:0 auto .625em;margin-left:-1.375em;margin-right:0;padding:0;list-style:none;overflow:hidden}
|
||||
ul.inline > li{list-style:none;float:left;margin-left:1.375em;display:block}
|
||||
ul.inline > li > *{display:block}
|
||||
.unstyled dl dt{font-weight:400;font-style:normal}
|
||||
ol.arabic{list-style-type:decimal}
|
||||
ol.decimal{list-style-type:decimal-leading-zero}
|
||||
ol.loweralpha{list-style-type:lower-alpha}
|
||||
ol.upperalpha{list-style-type:upper-alpha}
|
||||
ol.lowerroman{list-style-type:lower-roman}
|
||||
ol.upperroman{list-style-type:upper-roman}
|
||||
ol.lowergreek{list-style-type:lower-greek}
|
||||
.hdlist > table,.colist > table{border:0;background:0 0}
|
||||
.hdlist > table > tbody > tr,.colist > table > tbody > tr{background:0 0}
|
||||
td.hdlist1,td.hdlist2{vertical-align:top;padding:0 .625em}
|
||||
td.hdlist1{font-weight:700;padding-bottom:1.25em}
|
||||
.literalblock + .colist,.listingblock + .colist{margin-top:-.5em}
|
||||
.colist > table tr > td:first-of-type{padding:0 .75em;line-height:1}
|
||||
.colist > table tr > td:first-of-type img{max-width:initial}
|
||||
.colist > table tr > td:last-of-type{padding:.25em 0}
|
||||
.thumb,.th{line-height:0;display:inline-block;border:solid 4px #fff;-webkit-box-shadow:0 0 0 1px #ddd;box-shadow:0 0 0 1px #ddd}
|
||||
.imageblock.left,.imageblock[style*="float: left"]{margin:.25em .625em 1.25em 0}
|
||||
.imageblock.right,.imageblock[style*="float: right"]{margin:.25em 0 1.25em .625em}
|
||||
.imageblock > .title{margin-bottom:0}
|
||||
.imageblock.thumb,.imageblock.th{border-width:6px}
|
||||
.imageblock.thumb > .title,.imageblock.th > .title{padding:0 .125em}
|
||||
.image.left,.image.right{margin-top:.25em;margin-bottom:.25em;display:inline-block;line-height:0}
|
||||
.image.left{margin-right:.625em}
|
||||
.image.right{margin-left:.625em}
|
||||
a.image{text-decoration:none;display:inline-block}
|
||||
a.image object{pointer-events:none}
|
||||
sup.footnote,sup.footnoteref{font-size:.875em;position:static;vertical-align:super}
|
||||
sup.footnote a,sup.footnoteref a{text-decoration:none}
|
||||
sup.footnote a:active,sup.footnoteref a:active{text-decoration:underline}
|
||||
#footnotes{padding-top:.75em;padding-bottom:.75em;margin-bottom:.625em}
|
||||
#footnotes hr{width:20%;min-width:6.25em;margin:-.25em 0 .75em;border-width:1px 0 0}
|
||||
#footnotes .footnote{padding:0 .375em 0 .225em;line-height:1.3334;font-size:.875em;margin-left:1.2em;text-indent:-1.05em;margin-bottom:.2em}
|
||||
#footnotes .footnote a:first-of-type{font-weight:700;text-decoration:none}
|
||||
#footnotes .footnote:last-of-type{margin-bottom:0}
|
||||
#content #footnotes{margin-top:-.625em;margin-bottom:0;padding:.75em 0}
|
||||
.gist .file-data > table{border:0;background:#fff;width:100%;margin-bottom:0}
|
||||
.gist .file-data > table td.line-data{width:99%}
|
||||
div.unbreakable{page-break-inside:avoid}
|
||||
.big{font-size:larger}
|
||||
.small{font-size:smaller}
|
||||
.underline{text-decoration:underline}
|
||||
.overline{text-decoration:overline}
|
||||
.line-through{text-decoration:line-through}
|
||||
.aqua{color:#00bfbf}
|
||||
.aqua-background{background-color:#00fafa}
|
||||
.black{color:#000}
|
||||
.black-background{background-color:#000}
|
||||
.blue{color:#0000bf}
|
||||
.blue-background{background-color:#0000fa}
|
||||
.fuchsia{color:#bf00bf}
|
||||
.fuchsia-background{background-color:#fa00fa}
|
||||
.gray{color:#606060}
|
||||
.gray-background{background-color:#7d7d7d}
|
||||
.green{color:#006000}
|
||||
.green-background{background-color:#007d00}
|
||||
.lime{color:#00bf00}
|
||||
.lime-background{background-color:#00fa00}
|
||||
.maroon{color:#600000}
|
||||
.maroon-background{background-color:#7d0000}
|
||||
.navy{color:#000060}
|
||||
.navy-background{background-color:#00007d}
|
||||
.olive{color:#606000}
|
||||
.olive-background{background-color:#7d7d00}
|
||||
.purple{color:#600060}
|
||||
.purple-background{background-color:#7d007d}
|
||||
.red{color:#bf0000}
|
||||
.red-background{background-color:#fa0000}
|
||||
.silver{color:#909090}
|
||||
.silver-background{background-color:#bcbcbc}
|
||||
.teal{color:#006060}
|
||||
.teal-background{background-color:#007d7d}
|
||||
.white{color:#bfbfbf}
|
||||
.white-background{background-color:#fafafa}
|
||||
.yellow{color:#bfbf00}
|
||||
.yellow-background{background-color:#fafa00}
|
||||
span.icon > .fa{cursor:default}
|
||||
.admonitionblock td.icon [class^="fa icon-"]{font-size:2.5em;cursor:default}
|
||||
.admonitionblock td.icon .icon-note:before{content:"\f05a";color:#8979ca}
|
||||
.admonitionblock td.icon .icon-tip:before{content:"\f0eb";color:#0077b9}
|
||||
.admonitionblock td.icon .icon-warning:before{content:"\f071";color:#d88400}
|
||||
.admonitionblock td.icon .icon-caution:before{content:"\f06d";color:#bf3400}
|
||||
.admonitionblock td.icon .icon-important:before{content:"\f06a";color:#bf0000}
|
||||
.conum[data-value]{display:inline-block;color:#000!important;background-color:#ffe157;-webkit-border-radius:100px;border-radius:100px;text-align:center;font-size:.75em;width:1.67em;height:1.67em;line-height:1.67em;font-style:normal;font-weight:700}
|
||||
.conum[data-value] *{color:#fff!important}
|
||||
.conum[data-value] + b{display:none}
|
||||
.conum[data-value]:after{content:attr(data-value)}
|
||||
pre .conum[data-value]{position:relative;top:0;color:#000!important;background-color:#ffe157;font-size:12px}
|
||||
b.conum *{color:inherit!important}
|
||||
.conum:not([data-value]):empty{display:none}
|
||||
.admonitionblock{background-color:#ebe7f8;padding:.8em 0;margin:30px 0;width:auto;border-radius:4px;overflow-x:auto}
|
||||
.admonitionblock.important{border-left:0 solid #e20000;background-color:#f9ebeb}
|
||||
.admonitionblock.warning{border-left:0 solid #d88400;background-color:#fff9e4}
|
||||
.admonitionblock.tip{border-left:0 solid #0077b9;background-color:#e9f1f6}
|
||||
.admonitionblock.caution{border-left:0 solid #e20000;background-color:#f9ebeb}
|
||||
.admonitionblock .exampleblock > .content{border:0;background-color:#fff}
|
||||
#toc a:hover{text-decoration:underline}
|
||||
.admonitionblock > table{margin-bottom:0}
|
||||
.admonitionblock > table td.content{border-left:none}
|
||||
@media print {
|
||||
#tocbot a.toc-link.node-name--H4{display:none}
|
||||
}
|
||||
.is-collapsible{max-height:1000px;overflow:hidden;transition:all 200ms ease-in-out}
|
||||
.is-collapsed{max-height:0}
|
||||
#index-link{display:none}
|
||||
ul li > p > a > code{color:#097dff}
|
||||
ul li > p > a:hover > code{color:#097dff}
|
||||
div.back-action,#toc.toc2 div.back-action{padding:.8rem 0 0 5px}
|
||||
div.back-action a,#toc.toc2 div.back-action a{position:relative;display:inline-block;padding:.6rem 1.2rem;padding-left:35px}
|
||||
div.back-action a span,#toc.toc2 div.back-action a span{position:absolute;left:5px;top:5px;display:block;color:#333;height:26px;width:26px;border-radius:13px}
|
||||
div.back-action a i,#toc.toc2 div.back-action a i{position:absolute;top:10px;left:5px}
|
||||
div.back-action a:hover span,#toc.toc2 div.back-action a:hover span{color:#000}
|
||||
#tocbot.desktop-toc{padding-top:.8rem}
|
||||
#header-spring{position:absolute;text-rendering:optimizeLegibility;top:0;left:0;right:0;height:90px;margin:0 1rem;padding:0 1rem;border-bottom:1px solid #ddddd8;border-top:3px solid #9742ec}
|
||||
#header-spring h1{margin:0;padding:0;font-size:22px;text-align:left;line-height:86px;padding-left:.6rem}
|
||||
#header-spring h1 svg{width:200px}
|
||||
#header-spring h1 svg .st0{fill:#6bb344}
|
||||
#header-spring h1 svg .st2{fill:#444}
|
||||
body.book #header-spring{position:relative;top:auto;left:auto;right:auto;margin:0}
|
||||
body.book #header > h1:only-child{border:0;padding-bottom:1.2rem;font-size:1.8rem}
|
||||
body.book #header,body.book #content,body.book #footnotes,body.book #footer{margin:0 auto}
|
||||
body.toc2 #header-spring{position:absolute;left:0;right:0;top:0}
|
||||
body.toc2 #header > h1:only-child{font-size:2.2rem}
|
||||
body.toc2 #header,body.toc2 #content,body.toc2 #footnotes,body.toc2 #footer{margin:0 auto}
|
||||
body.toc2 #content{padding-top:2rem}
|
||||
#header,#content,#footnotes,#footer{width:100%;margin-right:auto;margin-top:0;margin-bottom:0;max-width:62.5em;*zoom:1;position:relative;padding-left:.9375em;padding-right:.9375em}
|
||||
#header:before,#header:after,#content:before,#content:after,#footnotes:before,#footnotes:after,#footer:before,#footer:after{content:" ";display:table}
|
||||
#header:after,#content:after,#footnotes:after,#footer:after{clear:both}
|
||||
#content{margin-top:1.25em}
|
||||
#content:before{content:none}
|
||||
#header > h1:first-child{margin-top:2.55rem;margin-bottom:.5em}
|
||||
#header > h1:first-child + #toc{margin-top:8px;border-top:0}
|
||||
#header > h1:only-child,body.toc2 #header > h1:nth-last-child(2){border-bottom:1px solid #ddddd8;padding-bottom:8px;display:none}
|
||||
#header .details{border-bottom:1px solid #ddddd8;line-height:1.45;padding-top:0;padding-bottom:2.25em;padding-left:.25em;color:rgba(0,0,0,.6);display:-ms-flexbox;display:-webkit-flex;display:flex;-ms-flex-flow:row wrap;-webkit-flex-flow:row wrap;flex-flow:row wrap}
|
||||
#header .details span:first-child{margin-left:-.125em}
|
||||
#header .details span.email a{color:rgba(0,0,0,.85)}
|
||||
#header .details br{display:none}
|
||||
#header .details br + span:before{content:"\00a0\2013\00a0"}
|
||||
#header .details br + span.author:before{content:"\00a0\22c5\00a0";color:rgba(0,0,0,.85)}
|
||||
#header .details br + span#revremark:before{content:"\00a0|\00a0"}
|
||||
#header #revnumber{text-transform:capitalize}
|
||||
#header #revnumber:after{content:"\00a0"}
|
||||
#content > h1:first-child:not([class]){color:rgba(0,0,0,.85);border-bottom:1px solid #ddddd8;padding-bottom:8px;margin-top:0;padding-top:1.5rem;margin-bottom:1.25rem}
|
||||
h1{font-size:2.2rem;letter-spacing:-1px}
|
||||
h1,h2,h3,h4,h5,h6{font-weight:700}
|
||||
h1:focus,h2:focus,h3:focus,h4:focus,h5:focus,h6:focus{box-shadow:none;outline:none}
|
||||
h2,h3,h4,h5,h6{padding:.8rem 0 .4rem}
|
||||
h1{font-size:1.75em}
|
||||
h2{font-size:1.6rem;letter-spacing:-1px}
|
||||
h3{font-size:1.5rem}
|
||||
h4{font-size:1.4rem}
|
||||
h5{font-size:1.3rem}
|
||||
h6{font-size:1.2rem}
|
||||
pre.highlight{background:#f2f2f2;color:#666;border-radius:4px}
|
||||
pre.highlight code{color:#222}
|
||||
pre.highlight a,#toc.toc2 a{color:#000;font-size:1rem}
|
||||
pre.highlight ul.sectlevel1,#toc.toc2 ul.sectlevel1{padding-left:.2rem}
|
||||
pre.highlight ul.sectlevel1 li,#toc.toc2 ul.sectlevel1 li{line-height:1.4rem}
|
||||
::selection{background-color:#faefff}
|
||||
.literalblock pre::selection,.listingblock pre[class=highlight]::selection,.highlight::selection,pre::selection,.highlight code::selection,.highlight code span::selection{background:rgba(0,0,0,.2)!important}
|
||||
body.book #header{margin-bottom:2rem}
|
||||
body.toc2 #header{margin-bottom:0}
|
||||
.desktop-toc{display:none}
|
||||
.admonitionblock td.icon{display:none}
|
||||
.admonitionblock > table td.content{padding-left:1.25em}
|
||||
.brand-symbol{width:24px;height:24px;vertical-align:sub;margin-right:4px;}
|
||||
@media only screen and (min-width:768px) {
|
||||
#toctitle{font-size:1.375em}
|
||||
.sect1{padding-bottom:1.25em}
|
||||
.mobile-toc{display:none}
|
||||
.desktop-toc{display:block}
|
||||
.literalblock pre,.literalblock pre[class],.listingblock pre,.listingblock pre[class]{font-size:.90625em}
|
||||
.admonitionblock td.icon{display:table-cell}
|
||||
.admonitionblock > table td.content{padding-left:0}
|
||||
body.toc2{padding-right:0}
|
||||
body.toc2 #toc.toc2{position:absolute;margin-top:0!important;width:15em;top:0;border-top-width:0!important;border-bottom-width:0!important;margin-left:-15.9375em;z-index:1000;padding:1.25em 1em 1.25em 1.25em;overflow:auto}
|
||||
body.toc2 #toc.toc2 #toctitle{margin-top:0;margin-bottom:.8rem;font-size:1.2em}
|
||||
body.toc2 #toc.toc2 > ul{font-size:.9em;margin-bottom:0}
|
||||
body.toc2 #toc.toc2 ul ul{margin-left:0;padding-left:1em}
|
||||
body.toc2 #toc.toc2 ul.sectlevel0 ul.sectlevel1{padding-left:0;margin-top:.5em;margin-bottom:.5em}
|
||||
body.toc2 #header,body.toc2 #content,body.toc2 #footnotes,body.toc2 #footer{padding-left:15.9375em;padding-right:1.5rem;max-width:none}
|
||||
body.book #header-spring h1{max-width:1400px;margin:0 auto}
|
||||
body.book #header,body.book #content,body.book #footnotes,body.book #footer{max-width:1400px}
|
||||
body.is-position-fixed #toc.toc2{position:fixed;height:100%}
|
||||
h1,h2,h3,#toctitle,.sidebarblock > .content > .title,h4,h5,h6{line-height:1.2}
|
||||
h1{font-size:1.75em}
|
||||
h2{font-size:1.6em}
|
||||
h3,#toctitle,.sidebarblock > .content > .title{font-size:1.5em}
|
||||
h4{font-size:1.4em}
|
||||
h5{font-size:1.2em}
|
||||
h6{font-size:1.2em}
|
||||
#tocbot a.toc-link.node-name--H1{font-style:italic}
|
||||
#tocbot ol{margin:0;padding:0;padding-left:.6rem}
|
||||
#tocbot ol li{list-style:none;padding:0;margin:0;display:block}
|
||||
#tocbot{z-index:999}
|
||||
#tocbot .toc-link{position:relative;display:block;z-index:999;padding-right:5px;padding-top:4px;padding-bottom:4px}
|
||||
#tocbot .is-active-link{padding-right:3px;border-right:3px solid #9742ec}
|
||||
}
|
||||
@media only screen and (min-width:768px) {
|
||||
#tocbot > ul.toc-list{margin-bottom:.5em;margin-left:.125em}
|
||||
#tocbot ul.sectlevel0,#tocbot a.toc-link.node-name--H1 + ul{padding-left:0}
|
||||
#tocbot a.toc-link{height:100%}
|
||||
.is-collapsible{max-height:3000px;overflow:hidden}
|
||||
.is-collapsed{max-height:0}
|
||||
.is-active-link{font-weight:700}
|
||||
}
|
||||
@media only screen and (min-width:768px) {
|
||||
body.toc2 #header,body.toc2 #content,body.toc2 #footer{background-repeat:repeat-y;background-position:14em 0;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAMAAAAoyzS7AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTQwIDc5LjE2MDQ1MSwgMjAxNy8wNS8wNi0wMTowODoyMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTggKE1hY2ludG9zaCkiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6RDE0NUNENzNGMTVGMTFFODk5RjI5ODk3QURGRjcxMkEiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6RDE0NUNENzRGMTVGMTFFODk5RjI5ODk3QURGRjcxMkEiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpEMTQ1Q0Q3MUYxNUYxMUU4OTlGMjk4OTdBREZGNzEyQSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpEMTQ1Q0Q3MkYxNUYxMUU4OTlGMjk4OTdBREZGNzEyQSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PjmGxxYAAAAGUExURd3d2AAAAJlCnKAAAAAMSURBVHjaYmAACDAAAAIAAU9tWeEAAAAASUVORK5CYII=)}
|
||||
}
|
||||
@media only screen and (min-width:1280px) {
|
||||
body.toc2{padding-right:0}body.toc2 #toc.toc2{width:25em;left:auto;margin-left:-26.9375em}body.toc2 #toc.toc2 #toctitle{font-size:1.375em}body.toc2 #toc.toc2 > ul{font-size:.95em}body.toc2 #toc.toc2 ul ul{padding-left:1.25em}body.toc2 body.toc2.toc-right{padding-left:0;padding-right:20em}body.toc2 #header,body.toc2 #content,body.toc2 #footnotes,body.toc2 #footer{padding-left:26.9375em;padding-right:1.5rem;max-width:2000px}body.toc2 #header-spring h1{margin:0 auto;max-width:2000px}.literalblock pre,.literalblock pre[class],.listingblock pre,.listingblock pre[class]{font-size:.90625em}body.toc2 #header,body.toc2 #content,body.toc2 #footer{background-repeat:repeat-y;background-position:24em 0;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAMAAAAoyzS7AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTQwIDc5LjE2MDQ1MSwgMjAxNy8wNS8wNi0wMTowODoyMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTggKE1hY2ludG9zaCkiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6RDE0NUNENzNGMTVGMTFFODk5RjI5ODk3QURGRjcxMkEiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6RDE0NUNENzRGMTVGMTFFODk5RjI5ODk3QURGRjcxMkEiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpEMTQ1Q0Q3MUYxNUYxMUU4OTlGMjk4OTdBREZGNzEyQSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpEMTQ1Q0Q3MkYxNUYxMUU4OTlGMjk4OTdBREZGNzEyQSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PjmGxxYAAAAGUExURd3d2AAAAJlCnKAAAAAMSURBVHjaYmAACDAAAAIAAU9tWeEAAAAASUVORK5CYII=)}
|
||||
}
|
||||
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 78 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 101 KiB |
|
After Width: | Height: | Size: 8.5 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 89 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 68 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 173 KiB |
|
After Width: | Height: | Size: 161 KiB |
|
After Width: | Height: | Size: 58 KiB |
|
After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 104 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 126 KiB |
|
After Width: | Height: | Size: 78 KiB |
|
After Width: | Height: | Size: 164 KiB |
|
After Width: | Height: | Size: 145 KiB |
|
After Width: | Height: | Size: 130 KiB |
|
After Width: | Height: | Size: 265 KiB |
|
After Width: | Height: | Size: 79 KiB |
|
After Width: | Height: | Size: 6.7 KiB |
|
After Width: | Height: | Size: 138 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 118 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 440 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 45 KiB |
|
After Width: | Height: | Size: 65 KiB |
|
After Width: | Height: | Size: 67 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 15 KiB |