Commit a2132183 authored by Gyeongho Park's avatar Gyeongho Park

[ADD] 독스 프로젝트 이관

parent 99ea364d
version: '3'
services:
proxy:
image: nginx:latest
container_name: proxy
ports:
- "80:80"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf
restart: "unless-stopped"
docs-back:
build:
context: ./docs-back
image: vridge-api-ai
container_name: vridge-api-ai
expose:
- "5000"
links:
- vridge-eureka
- mongodb
restart: "unless-stopped"
docs-front:
build:
context: ./docs-front
image: docs-front
container_name: docs-front
ports:
- "3000:3000"
volumes:
- ./docs-front/app:/var/www/app
restart: "unless-stopped"
mongodb:
image: mongo:4.4.6
container_name: mongodb
restart: always
ports:
- 27017:27017
volumes:
- /var/lib/mongodb:/data/db
-./mongodb/mongo-init.js:/docker-entrypoint-initdb.d/mongo-init.js:ro
command: [ "mongod", "--bind_ip", "0.0.0.0" ]
environment:
- TZ=Asia/Seoul
- MONGO_INITDB_ROOT_USERNAME=vridge-admin
- MONGO_INITDB_ROOT_PASSWORD=vazil523320!
- MONGO_INITDB_DATABASE=vridge-docs
- MONGO_INITDB_USERNAME=vridge-admin
- MONGO_INITDB_PASSWORD=vazil523320!
\ No newline at end of file
HELP.md
.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
out/
!**/src/main/**/out/
!**/src/test/**/out/
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
### VS Code ###
.vscode/
FROM openjdk:11
WORKDIR /app
ADD build/libs/vridge-docs-1.0.0.jar vridge-docs.jar
EXPOSE 5005
ENTRYPOINT ["java","-Dspring.profiles.active=deploy","-Djava.net.preferIPv4Stack=true", "-jar","./vridge-docs.jar"]
plugins {
id 'java'
id 'org.springframework.boot' version '2.7.8'
id 'io.spring.dependency-management' version '1.0.15.RELEASE'
}
group = 'com.vazil.vridge'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'javax.servlet:javax.servlet-api'
implementation('org.springframework.boot:spring-boot-starter-data-mongodb')
implementation "io.springfox:springfox-boot-starter:3.0.0"
implementation 'org.apache.commons:commons-lang3:3.9'
compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
tasks.named('test') {
useJUnitPlatform()
}
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
#!/bin/sh
#
# Copyright © 2015-2021 the original 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.
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$(ls -ld "$app_path")
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
APP_HOME=$(cd "${APP_HOME:-./}" && pwd -P) || exit
APP_NAME="Gradle"
APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn() {
echo "$*"
} >&2
die() {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$(uname)" in #(
CYGWIN*) cygwin=true ;; #(
Darwin*) darwin=true ;; #(
MSYS* | MINGW*) msys=true ;; #(
NONSTOP*) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ]; then
if [ -x "$JAVA_HOME/jre/sh/java" ]; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ]; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop"; then
case $MAX_FD in #(
max*)
MAX_FD=$(ulimit -H -n) ||
warn "Could not query maximum file descriptor limit"
;;
esac
case $MAX_FD in #(
'' | soft) : ;; #(
*)
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
;;
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys"; then
APP_HOME=$(cygpath --path --mixed "$APP_HOME")
CLASSPATH=$(cygpath --path --mixed "$CLASSPATH")
JAVACMD=$(cygpath --unix "$JAVACMD")
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg; do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*)
t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ]
;; #(
*) false ;;
esac
then
arg=$(cygpath --path --ignore --mixed "$arg")
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1; then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
rootProject.name = 'docs'
package com.vazil.vridge.docs;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class VridgeDocsApplication {
public static void main(String[] args) {
SpringApplication.run(VridgeDocsApplication.class, args);
}
}
package com.vazil.vridge.docs.controller;
import com.vazil.vridge.docs.model.Guide;
import com.vazil.vridge.docs.model.GuideContent;
import com.vazil.vridge.docs.service.GuideService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@AllArgsConstructor
@Log4j2
@Api("가이드 API V1")
public class GuideController {
GuideService guideService;
@ApiOperation(value="모든 가이드 목차 조회")
@GetMapping("/index")
public ResponseEntity<?> findIndex()throws Exception{
return new ResponseEntity<>(guideService.findGuideIndex(), HttpStatus.OK);
}
@ApiOperation(value="모든 가이드 목차 검색")
@GetMapping("/search")
public ResponseEntity<?> search(@RequestParam(value = "keyword", required = false) String keyword) throws Exception{
return new ResponseEntity<>(guideService.search(keyword), HttpStatus.OK);
}
@ApiOperation(value="가이드 타이틀 추가")
@PostMapping("/index")
public ResponseEntity<?> createGuideIndex(@RequestBody Guide guide) throws Exception{
return new ResponseEntity<>(guideService.createGuide(guide), HttpStatus.CREATED);
}
@PutMapping("/index")
public ResponseEntity<?> updateGuideIndex(@RequestBody Guide guide) throws Exception{
return new ResponseEntity<>(guideService.updateGuide(guide), HttpStatus.OK);
}
@PutMapping("/keyword")
public ResponseEntity<?> updateGuideKeyword(@RequestBody Guide guide) throws Exception{
return new ResponseEntity<>(guideService.updateGuideKeyword(guide), HttpStatus.OK);
}
@GetMapping
public ResponseEntity<?> getGuide(@RequestParam(value = "guideId") String guideId) throws Exception{
return new ResponseEntity<>(guideService.findGuideById(guideId), HttpStatus.OK);
}
@DeleteMapping
public ResponseEntity<?> deleteGuideIndex(@RequestParam(value = "guideId") String guideId, @RequestParam(value = "cascade", required = false) boolean cascade) throws Exception{
return new ResponseEntity<>(guideService.deleteGuide(guideId, cascade), HttpStatus.OK);
}
@PutMapping
public ResponseEntity<?> updateGuideContent(@RequestBody GuideContent guideContent) throws Exception{
return new ResponseEntity<>(guideService.updateGuideContent(guideContent), HttpStatus.OK);
}
}
package com.vazil.vridge.docs.exceptions;
import lombok.extern.log4j.Log4j2;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
@Log4j2
@ControllerAdvice
public class ControllerExceptionHandler {
//모든 예외를 핸들링하여 ErrorResponse 형식으로 반환한다.
@ExceptionHandler(Exception.class)
protected ResponseEntity<?> handleException(Exception e, ServerHttpRequest request) {
log.error("handleException :: ", e);
return new ResponseEntity<>(e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
package com.vazil.vridge.docs.model;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import nonapi.io.github.classgraph.json.Id;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.annotation.Transient;
import org.springframework.data.mongodb.core.mapping.Document;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Set;
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Document(value= "guide")
@Data
public class Guide implements Serializable {
private static final long serialVersionUID = 1928367242L;
@Id
private String id;
private String parentId;
private String title;
private Integer order;
private Integer like;
private Integer view;
private String contentKey;
private Set<String> keywordSet;
@CreatedDate
private LocalDateTime createDate;
@LastModifiedDate
private LocalDateTime updateDate;
private LocalDateTime deleteDate;
@Transient
List<Guide> children;
@Transient
private Guide prevContent;
@Transient
private Guide nextContent;
}
package com.vazil.vridge.docs.model;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.annotation.Transient;
import org.springframework.data.mongodb.core.mapping.Document;
import java.time.LocalDateTime;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Document(value= "guideContent")
public class GuideContent {
@Id
private String id;
private String guideId;
private String title;
private String content;
private Integer like;
private Integer view;
@Transient
private Guide prevContent;
@Transient
private Guide nextContent;
@CreatedDate
private LocalDateTime createDate;
@LastModifiedDate
private LocalDateTime updateDate;
private LocalDateTime deleteDate;
}
package com.vazil.vridge.docs.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.LastModifiedDate;
import java.time.LocalDateTime;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class GuideSub {
@Id
private String id;
private String title;
private String contents;
private String router;
@CreatedDate
private LocalDateTime createDate;
@LastModifiedDate
private LocalDateTime updateDate;
private LocalDateTime deleteDate;
}
package com.vazil.vridge.docs.repository;
import com.vazil.vridge.docs.model.GuideContent;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
public interface GuideContentRepository extends CrudRepository<GuideContent, String> {
Optional<GuideContent> findByGuideId(String guideId);
void deleteByGuideId(String guideId);
}
package com.vazil.vridge.docs.repository;
import com.vazil.vridge.docs.model.Guide;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface GuideRepository extends CrudRepository<Guide, String> {
List<Guide> findAllByParentIdOrderByOrderAsc(String parentId);
List<Guide> findAllByKeywordSetContaining(String keyword);
int countAllBy();
int countAllByParentId(String parentId);
}
package com.vazil.vridge.docs.service;
import com.vazil.vridge.docs.model.Guide;
import com.vazil.vridge.docs.model.GuideContent;
import com.vazil.vridge.docs.repository.GuideContentRepository;
import com.vazil.vridge.docs.repository.GuideRepository;
import com.vazil.vridge.docs.utils.TimeManager;
import lombok.AllArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.List;
import java.util.NoSuchElementException;
@Service
@AllArgsConstructor
@Log4j2
public class GuideService {
GuideRepository guideRepository;
GuideContentRepository guideContentRepository;
public List<Guide> findGuideIndex() throws Exception{
List<Guide> guides = new ArrayList<>();
List<Guide> sortedGuideList = guideRepository.findAllByParentIdOrderByOrderAsc("");
for(Guide guide : sortedGuideList) {
guide.setChildren(getGuideTreeByParentId(guide.getId()));
guides.add(guide);
}
return guides;
}
@Transactional
public Guide findGuideById(String guideId) throws Exception{
Guide targetGuide = guideRepository.findById(guideId).orElseThrow(NoSuchElementException::new);
List<Guide> sortedGuideList = new ArrayList<>();
for(Guide guide : guideRepository.findAllByParentIdOrderByOrderAsc("")) {
sortedGuideList.add(guide);
sortedGuideList.addAll(getGuideListByParentId(guide.getId()));
}
for(int i = 0; i < sortedGuideList.size(); i++) {
if(sortedGuideList.get(i).getId().equals(guideId)) {
if(i > 0) {
targetGuide.setPrevContent(sortedGuideList.get(i-1));
}
if(i < sortedGuideList.size() - 1) {
targetGuide.setNextContent(sortedGuideList.get(i+1));
}
break;
}
}
return targetGuide;
}
public List<Guide> search(String keyword) throws Exception{
return guideRepository.findAllByKeywordSetContaining(keyword);
}
@Transactional
public Guide createGuide(Guide guide) throws Exception{
guide.setCreateDate(TimeManager.now());
guide.setUpdateDate(TimeManager.now());
guide.setOrder(guide.getOrder() == null ? getLastOrder(guide.getParentId()) : 0);
guide.setParentId(StringUtils.isEmpty(guide.getParentId()) ? "" : guide.getParentId());
guide.setLike(0);
guide.setView(0);
return guideRepository.save(guide);
}
@Transactional
public Guide updateGuide(Guide guide) throws Exception{
Guide savedGuide = guideRepository.findById(guide.getId()).orElseThrow(NoSuchElementException::new);
savedGuide.setParentId(guide.getParentId());
savedGuide.setOrder(guide.getOrder());
savedGuide.setUpdateDate(TimeManager.now());
savedGuide.setContentKey(guide.getContentKey());
return guideRepository.save(savedGuide);
}
@Transactional
public Guide updateGuideKeyword(Guide guide) throws Exception{
Guide savedGuide = guideRepository.findById(guide.getId()).orElseThrow(NoSuchElementException::new);
savedGuide.setKeywordSet(guide.getKeywordSet());
return guideRepository.save(savedGuide);
}
@Transactional
public Guide deleteGuide(String guideId, boolean cascade) throws Exception{
Guide guide = guideRepository.findById(guideId).orElseThrow(NoSuchElementException::new);
String targetId = guide.getId();
String parentId = guide.getParentId();
List<Guide> children = getGuideListByParentId(targetId);
if(cascade) {
for(Guide g : children) {
guideRepository.delete(g);
//guideContentRepository.deleteByGuideId(g.getId());
}
} else {
for(Guide g : guideRepository.findAllByParentIdOrderByOrderAsc(targetId)) {
g.setParentId(parentId);
g.setOrder(getLastOrder(parentId));
guideRepository.save(g);
}
}
guideRepository.delete(guide);
//guideContentRepository.deleteByGuideId(guide.getId());
return guide;
}
///////////////////////////////////////////////////////
// Guide Content CRUD
@Transactional
public GuideContent findContentById(String guideId) throws Exception{
GuideContent targetContent = guideContentRepository.findByGuideId(guideId).orElseThrow(NoSuchElementException::new);
List<Guide> sortedGuideList = new ArrayList<>();
for(Guide guide : guideRepository.findAllByParentIdOrderByOrderAsc("")) {
sortedGuideList.add(guide);
sortedGuideList.addAll(getGuideListByParentId(guide.getId()));
}
for(int i = 0; i < sortedGuideList.size(); i++) {
if(sortedGuideList.get(i).getId().equals(guideId)) {
if(i > 0) {
targetContent.setPrevContent(sortedGuideList.get(i-1));
}
if(i < sortedGuideList.size() - 1) {
targetContent.setNextContent(sortedGuideList.get(i+1));
}
}
}
return targetContent;
}
@Transactional
public GuideContent updateGuideContent(GuideContent guideContent) throws Exception{
GuideContent savedGuideContent = guideContentRepository.findById(guideContent.getId()).orElseThrow(NoSuchElementException::new);
savedGuideContent.setContent(guideContent.getContent());
savedGuideContent.setUpdateDate(TimeManager.now());
return guideContentRepository.save(savedGuideContent);
}
/////////////////////////////////////////////////
// Common
// 트리 구조로 자식 guide를 가져옴
private List<Guide> getGuideTreeByParentId(String parentId) {
List<Guide> children = new ArrayList<>();
for(Guide childGuide : guideRepository.findAllByParentIdOrderByOrderAsc(parentId)) {
childGuide.setChildren(guideRepository.findAllByParentIdOrderByOrderAsc(childGuide.getId()));
children.add(childGuide);
}
return children;
}
//트리 구조의 guide를 1차원 리스트로 가져옴
private List<Guide> getGuideListByParentId(String parentId) {
List<Guide> children = new ArrayList<>();
for(Guide childGuide : guideRepository.findAllByParentIdOrderByOrderAsc(parentId)) {
children.add(childGuide);
children.addAll(guideRepository.findAllByParentIdOrderByOrderAsc(childGuide.getId()));
}
return children;
}
//현재 뎁스의 마지막 순번을 가져옴
private Integer getLastOrder(String parentId) {
return guideRepository.countAllByParentId(parentId);
}
}
package com.vazil.vridge.docs.swagger;
import io.swagger.annotations.ApiOperation;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
@Configuration
public class SwaggerConfig {
@Bean
public Docket api() {
return new Docket(DocumentationType.OAS_30)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("Vridge 문서(Docs)")
.description("Vridge Docs 서비스 관련 API 문서입니다.")
.contact(new springfox.documentation.service.Contact("Vazilcompany", "http://www.vazilcompany.com", "programming@vazilcompany.com"))
.license("Vridge License Version 1.0")
.licenseUrl("https://www.vridgeai.com/docs")
.version("1.0.0")
.build();
}
}
package com.vazil.vridge.docs.utils;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Component;
import java.nio.ByteBuffer;
import java.util.UUID;
/**
* @author 경호
* 시간 관리 클래스
*/
@Component
@Log4j2
public class IdUtils {
public static String generate() {
return UUID.randomUUID().toString().replace("-","");
}
public static String generateShort() {
return Long.toString(ByteBuffer.wrap(UUID.randomUUID().toString().getBytes()).getLong(), Character.MAX_RADIX).replace("-","");
}
public static String generateWithPrefix(String prefix) {
return prefix + "-" + generate();
}
public static String generateShortWithPrefix(String prefix) {
return prefix + "-" + generateShort();
}
}
package com.vazil.vridge.docs.utils;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.time.ZoneId;
/**
* @author 경호
* 시간 관리 클래스
*/
@Component
@Log4j2
public class TimeManager {
public static LocalDateTime now() {
return LocalDateTime.now(ZoneId.of("Asia/Seoul"));
}
public static boolean isExpired(LocalDateTime targetDateTime) {
LocalDateTime now = now();
return now.isAfter(targetDateTime);
}
public static LocalDateTime nowBeforeDay(int day) {
LocalDateTime now = now();
now = now.minusDays(day);
return now;
}
}
server:
port: 5000
spring:
application:
name: "vridge-docs"
data:
mongodb:
host: mongodb
port: 27017
database: vridge
username: vridge-admin
password: vazil523320!
mvc:
pathmatch:
matching-strategy: ant_path_matcher
\ No newline at end of file
,--. ,--. ,--. ,--. ,----. ,--. ,--.
\ `.' / ,--.--. `--' ,-| | ,---. ,---. ' .-./ ,--.,--. `--' ,-| | ,---.
\ / | .--' ,--. ' .-. | | .-. | | .-. : | | .---. | || | ,--. ' .-. | | .-. :
\ / | | | | \ `-' | ' '-' ' \ --. ' '--' | ' '' ' | | \ `-' | \ --.
`-' `--' `--' `---' .`- / `----' `------' `----' `--' `---' `----'
`---'
${application.title} ${application.version}
Powered by Spring Boot ${spring-boot.version}
package com.vazil.vridge.docs;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class VridgeDocsApplicationTests {
@Test
void contextLoads() {
}
}
.node_modules/
.dist/
# editorconfig.org
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = false
insert_final_newline = false
[*.md]
trim_trailing_whitespace = false
# Created by .ignore support plugin (hsz.mobi)
### Node template
# Logs
/logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
# parcel-bundler cache (https://parceljs.org/)
.cache
# next.js build output
.next
# nuxt.js build output
.nuxt
# Nuxt generate
dist
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless
# IDE / Editor
.idea
# Service worker
sw.*
# macOS
.DS_Store
# Vim swap files
*.swp
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
\ No newline at end of file
FROM node:16 as builder
WORKDIR /app
COPY . .
RUN yarn install \
--prefer-offline \
--frozen-lockfile \
--non-interactive \
--production=false
RUN yarn build
RUN rm -rf node_modules && \
NODE_ENV=production yarn install \
--prefer-offline \
--pure-lockfile \
--non-interactive \
--production=true
FROM node:16
WORKDIR /app
COPY --from=builder /app .
ENV HOST 0.0.0.0
EXPOSE 3000
CMD [ "yarn", "start" ]
# FistApp
클라우드 환경에서 인공지능 모델을 학습하고 배포 및 버전 관리 서비스
```bash
# install dependencies
$ npm install
# serve with hot reload at localhost:3000
$ npm run dev
# build for production and launch server
$ npm run build
$ npm run start
# generate static project
$ npm run generate
```
For detailed explanation on how things work, check out [Nuxt.js docs](https://nuxtjs.org).
import express from 'express'
// Create express router
const router = express.Router()
const app = express()
router.use((req, res, next) => {
Object.setPrototypeOf(req, app.request);
Object.setPrototypeOf(res, app.response);
req.res = res;
res.req = req;
next();
});
// Export the server middleware
export default {
path: '/apis',
handler: router
};
\ No newline at end of file
html {
font-size: 14px !important;
overflow: auto !important;
* {
scroll-behavior: smooth !important;
}
&.theme--dark {
background-color: #1f2023;
}
&.theme--light {
background-color: #f5f9fb;
}
&::-webkit-scrollbar {
height: 0.358rem;
width: 0.572rem; //8px;
background-color: transparent;
}
&::-webkit-scrollbar-button {
width: 0.572rem; //8px;
height: 0.358rem; //5px;
}
&::-webkit-scrollbar-track {
background: transparent;
border: thin solid transparent;
box-shadow: 0px 0px 0.215rem transparent inset; //3px
border-radius: 0.72rem; //10px;
}
&::-webkit-scrollbar-thumb {
background: transparent;
border: thin solid transparent;
border-radius: 0.72rem; //10px;
}
&::-webkit-scrollbar-thumb:hover {
background: #7d7d7d;
}
&::-webkit-scrollbar-corner {
background-color: transparent;
}
-webkit-font-smoothing: antialiased !important;
//-moz-osx-font-smoothing: grayscale;
// -webkit-text-stroke: 0.2px;
// -webkit-text-stroke: 0.2px rgba(0, 0, 0, 0.1);
}
html:hover {
&::-webkit-scrollbar {
width: 0.572rem; //8px;
background-color: transparent;
}
&::-webkit-scrollbar-track {
background: transparent;
border: thin solid transparent;
box-shadow: 0px 0px 0.215rem transparent inset; //3px
border-radius: 0.72rem; //10px;
}
&::-webkit-scrollbar-thumb {
background: #ccc;
border: thin solid #ccc;
border-radius: 0.72rem; //10px;
}
}
input:-webkit-autofill,
input:-webkit-autofill:hover,
input:-webkit-autofill:focus,
input:-webkit-autofill:active {
transition: background-color 5000s ease-in-out 0s;
-webkit-transition: background-color 9999s ease-out;
box-shadow: 0 0 0px 1000px transparent inset !important;
-webkit-box-shadow: 0 0 0px 1000px transparent inset !important;
}
@font-face {
font-family: 'Pretendard-Regular';
src: url('https://cdn.jsdelivr.net/gh/Project-Noonnu/noonfonts_2107@1.1/Pretendard-Regular.woff') format('woff');
font-weight: 400;
font-style: normal;
}
.v-application {
font-family: "Pretendard-Regular", sans-serif !important;
.v-window {
transition: none !important;
}
.container--fluid {
&.index-container{
max-width:100vw !important;
}
max-width: 1920px !important;
}
}
.v-navigation-drawer__content {
height: calc(100% - 50px) !important;
overflow-y: auto !important;
overflow-x: hidden !important;
}
.v-input {
.v-input__control {
.v-input__slot {
box-shadow: none !important;
}
}
}
.v-navigation-drawer__border {
display: none;
}
.v-btn--plain {
.v-btn__content {
opacity: 1.0 !important;
}
}
.v-btn-toggle {
.v-btn {
text-transform: none !important;
&.v-btn--is-elevated {
box-shadow: rgba(0, 0, 0, 0.1) 0px 1px 2px 0px;
}
}
}
hr {
&.v-divider {
opacity: 0.65;
}
}
// 글씨 폰트
.font-red {
color: #ff2323;
}
// defulat.vue - meta
.nuxt-route-title {
min-width: 320px;
width: 100%;
z-index: 1;
.title {
transition: all .2s ease-in-out !important;
transform: scale(1);
transform-origin: 0%;
margin: 0 8px;
}
}
// defulat.vue - subtitle
// .nuxt-route-subtitle {
// margin: 0px 2rem 20px;
// height: 20px;
// line-height: 20px;
// }
// defulat.vue - content
.nuxt-route-content {
padding: 0rem 2rem;
max-height: calc(100% - 46px) !important;
min-height: calc(100% - 46px) !important;
transform: translateY(12px);
transition: all 0.15s cubic-bezier(0.685, 0.0473, 0.346, 1);
opacity: 0.0;
&.active {
transform: translateY(0);
transition: all 0.15s cubic-bezier(0.81, 0.34, 0, 0.76);
opacity: 1;
}
}
.task-route-content {
max-height: calc(100%) !important;
min-height: calc(100%) !important;
transform: scale(0.95);
transition: all 0.1s cubic-bezier(0.685, 0.0473, 0.346, 1);
opacity: 0.0;
&.active {
transform: scale(1);
transition: all 0.1s cubic-bezier(0.81, 0.34, 0, 0.76);
opacity: 1;
}
}
//index
.index-wrap {
height: 100%;
position: relative;
.index-dashboard {
align-items: flex-start;
align-items: stretch;
display: flex;
flex-wrap: wrap;
justify-content: space-evenly;
.index-dashboard-header {
width: 100%;
}
.index-dashboard-content {
width: 100%;
height: 100%;
.card-wrap {
max-height: 100%;
min-height: 120px;
.content-card {
box-shadow: rgba(0, 3, 10, 0.05) 0px 20px 30px !important;
height: 100%;
padding: 8px;
border-radius: 10px !important;
.v-card__title {
font-size: 16px !important;
}
}
.v-card__text {
margin: 8px 0;
width: calc(100%) !important;
max-height: calc(100% - 76px) !important;
height: calc(100% - 76px) !important;
.chart-data {
height: calc(100% - 0px);
max-height: 200px !important;
width: 100%;
}
}
.v-card__title {
animation-name: fade_up;
animation-duration: 800ms;
animation-timing-function: ease-in-out;
}
}
}
}
}
.v-main {
min-height: calc(100vh - 345px) !important;
min-width: 320px;
.v-main__wrap {
min-height: 100% !important;
}
}
.default-layout {
.default-container {
max-width: 1400px;
}
}
// 기본 틀
.default-wrap {
overflow-x: hidden;
overflow-y: hidden;
// navigation-drawer
.navigation-wrap {
// &.theme--light{
// background-color: #ffffff;
// background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100%25'%3E%3Cdefs%3E%3ClinearGradient id='a' gradientUnits='userSpaceOnUse' x1='0' x2='0' y1='0' y2='100%25' gradientTransform='rotate(360,1280,660)'%3E%3Cstop offset='0' stop-color='%23ffffff'/%3E%3Cstop offset='1' stop-color='%23FFFFFF'/%3E%3C/linearGradient%3E%3Cpattern patternUnits='userSpaceOnUse' id='b' width='785' height='654.2' x='0' y='0' viewBox='0 0 1080 900'%3E%3Cg fill-opacity='0.01'%3E%3Cpolygon fill='%23444' points='90 150 0 300 180 300'/%3E%3Cpolygon points='90 150 180 0 0 0'/%3E%3Cpolygon fill='%23AAA' points='270 150 360 0 180 0'/%3E%3Cpolygon fill='%23DDD' points='450 150 360 300 540 300'/%3E%3Cpolygon fill='%23999' points='450 150 540 0 360 0'/%3E%3Cpolygon points='630 150 540 300 720 300'/%3E%3Cpolygon fill='%23DDD' points='630 150 720 0 540 0'/%3E%3Cpolygon fill='%23444' points='810 150 720 300 900 300'/%3E%3Cpolygon fill='%23FFF' points='810 150 900 0 720 0'/%3E%3Cpolygon fill='%23DDD' points='990 150 900 300 1080 300'/%3E%3Cpolygon fill='%23444' points='990 150 1080 0 900 0'/%3E%3Cpolygon fill='%23DDD' points='90 450 0 600 180 600'/%3E%3Cpolygon points='90 450 180 300 0 300'/%3E%3Cpolygon fill='%23666' points='270 450 180 600 360 600'/%3E%3Cpolygon fill='%23AAA' points='270 450 360 300 180 300'/%3E%3Cpolygon fill='%23DDD' points='450 450 360 600 540 600'/%3E%3Cpolygon fill='%23999' points='450 450 540 300 360 300'/%3E%3Cpolygon fill='%23999' points='630 450 540 600 720 600'/%3E%3Cpolygon fill='%23FFF' points='630 450 720 300 540 300'/%3E%3Cpolygon points='810 450 720 600 900 600'/%3E%3Cpolygon fill='%23DDD' points='810 450 900 300 720 300'/%3E%3Cpolygon fill='%23AAA' points='990 450 900 600 1080 600'/%3E%3Cpolygon fill='%23444' points='990 450 1080 300 900 300'/%3E%3Cpolygon fill='%23222' points='90 750 0 900 180 900'/%3E%3Cpolygon points='270 750 180 900 360 900'/%3E%3Cpolygon fill='%23DDD' points='270 750 360 600 180 600'/%3E%3Cpolygon points='450 750 540 600 360 600'/%3E%3Cpolygon points='630 750 540 900 720 900'/%3E%3Cpolygon fill='%23444' points='630 750 720 600 540 600'/%3E%3Cpolygon fill='%23AAA' points='810 750 720 900 900 900'/%3E%3Cpolygon fill='%23666' points='810 750 900 600 720 600'/%3E%3Cpolygon fill='%23999' points='990 750 900 900 1080 900'/%3E%3Cpolygon fill='%23999' points='180 0 90 150 270 150'/%3E%3Cpolygon fill='%23444' points='360 0 270 150 450 150'/%3E%3Cpolygon fill='%23FFF' points='540 0 450 150 630 150'/%3E%3Cpolygon points='900 0 810 150 990 150'/%3E%3Cpolygon fill='%23222' points='0 300 -90 450 90 450'/%3E%3Cpolygon fill='%23FFF' points='0 300 90 150 -90 150'/%3E%3Cpolygon fill='%23FFF' points='180 300 90 450 270 450'/%3E%3Cpolygon fill='%23666' points='180 300 270 150 90 150'/%3E%3Cpolygon fill='%23222' points='360 300 270 450 450 450'/%3E%3Cpolygon fill='%23FFF' points='360 300 450 150 270 150'/%3E%3Cpolygon fill='%23444' points='540 300 450 450 630 450'/%3E%3Cpolygon fill='%23222' points='540 300 630 150 450 150'/%3E%3Cpolygon fill='%23AAA' points='720 300 630 450 810 450'/%3E%3Cpolygon fill='%23666' points='720 300 810 150 630 150'/%3E%3Cpolygon fill='%23FFF' points='900 300 810 450 990 450'/%3E%3Cpolygon fill='%23999' points='900 300 990 150 810 150'/%3E%3Cpolygon points='0 600 -90 750 90 750'/%3E%3Cpolygon fill='%23666' points='0 600 90 450 -90 450'/%3E%3Cpolygon fill='%23AAA' points='180 600 90 750 270 750'/%3E%3Cpolygon fill='%23444' points='180 600 270 450 90 450'/%3E%3Cpolygon fill='%23444' points='360 600 270 750 450 750'/%3E%3Cpolygon fill='%23999' points='360 600 450 450 270 450'/%3E%3Cpolygon fill='%23666' points='540 600 630 450 450 450'/%3E%3Cpolygon fill='%23222' points='720 600 630 750 810 750'/%3E%3Cpolygon fill='%23FFF' points='900 600 810 750 990 750'/%3E%3Cpolygon fill='%23222' points='900 600 990 450 810 450'/%3E%3Cpolygon fill='%23DDD' points='0 900 90 750 -90 750'/%3E%3Cpolygon fill='%23444' points='180 900 270 750 90 750'/%3E%3Cpolygon fill='%23FFF' points='360 900 450 750 270 750'/%3E%3Cpolygon fill='%23AAA' points='540 900 630 750 450 750'/%3E%3Cpolygon fill='%23FFF' points='720 900 810 750 630 750'/%3E%3Cpolygon fill='%23222' points='900 900 990 750 810 750'/%3E%3Cpolygon fill='%23222' points='1080 300 990 450 1170 450'/%3E%3Cpolygon fill='%23FFF' points='1080 300 1170 150 990 150'/%3E%3Cpolygon points='1080 600 990 750 1170 750'/%3E%3Cpolygon fill='%23666' points='1080 600 1170 450 990 450'/%3E%3Cpolygon fill='%23DDD' points='1080 900 1170 750 990 750'/%3E%3C/g%3E%3C/pattern%3E%3C/defs%3E%3Crect x='0' y='0' fill='url(%23a)' width='100%25' height='100%25'/%3E%3Crect x='0' y='0' fill='url(%23b)' width='100%25' height='100%25'/%3E%3C/svg%3E");
// background-attachment: fixed;
// background-size: cover;
// }
// &.theme--dark{
// background-color: #22262a;
// background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100%25'%3E%3Cdefs%3E%3ClinearGradient id='a' gradientUnits='userSpaceOnUse' x1='0' x2='0' y1='0' y2='100%25' gradientTransform='rotate(360,1280,660)'%3E%3Cstop offset='0' stop-color='%2322262a'/%3E%3Cstop offset='1' stop-color='%2322262a'/%3E%3C/linearGradient%3E%3Cpattern patternUnits='userSpaceOnUse' id='b' width='785' height='654.2' x='0' y='0' viewBox='0 0 1080 900'%3E%3Cg fill-opacity='0.01'%3E%3Cpolygon fill='%23444' points='90 150 0 300 180 300'/%3E%3Cpolygon points='90 150 180 0 0 0'/%3E%3Cpolygon fill='%23AAA' points='270 150 360 0 180 0'/%3E%3Cpolygon fill='%23DDD' points='450 150 360 300 540 300'/%3E%3Cpolygon fill='%23999' points='450 150 540 0 360 0'/%3E%3Cpolygon points='630 150 540 300 720 300'/%3E%3Cpolygon fill='%23DDD' points='630 150 720 0 540 0'/%3E%3Cpolygon fill='%23444' points='810 150 720 300 900 300'/%3E%3Cpolygon fill='%23FFF' points='810 150 900 0 720 0'/%3E%3Cpolygon fill='%23DDD' points='990 150 900 300 1080 300'/%3E%3Cpolygon fill='%23444' points='990 150 1080 0 900 0'/%3E%3Cpolygon fill='%23DDD' points='90 450 0 600 180 600'/%3E%3Cpolygon points='90 450 180 300 0 300'/%3E%3Cpolygon fill='%23666' points='270 450 180 600 360 600'/%3E%3Cpolygon fill='%23AAA' points='270 450 360 300 180 300'/%3E%3Cpolygon fill='%23DDD' points='450 450 360 600 540 600'/%3E%3Cpolygon fill='%23999' points='450 450 540 300 360 300'/%3E%3Cpolygon fill='%23999' points='630 450 540 600 720 600'/%3E%3Cpolygon fill='%23FFF' points='630 450 720 300 540 300'/%3E%3Cpolygon points='810 450 720 600 900 600'/%3E%3Cpolygon fill='%23DDD' points='810 450 900 300 720 300'/%3E%3Cpolygon fill='%23AAA' points='990 450 900 600 1080 600'/%3E%3Cpolygon fill='%23444' points='990 450 1080 300 900 300'/%3E%3Cpolygon fill='%23222' points='90 750 0 900 180 900'/%3E%3Cpolygon points='270 750 180 900 360 900'/%3E%3Cpolygon fill='%23DDD' points='270 750 360 600 180 600'/%3E%3Cpolygon points='450 750 540 600 360 600'/%3E%3Cpolygon points='630 750 540 900 720 900'/%3E%3Cpolygon fill='%23444' points='630 750 720 600 540 600'/%3E%3Cpolygon fill='%23AAA' points='810 750 720 900 900 900'/%3E%3Cpolygon fill='%23666' points='810 750 900 600 720 600'/%3E%3Cpolygon fill='%23999' points='990 750 900 900 1080 900'/%3E%3Cpolygon fill='%23999' points='180 0 90 150 270 150'/%3E%3Cpolygon fill='%23444' points='360 0 270 150 450 150'/%3E%3Cpolygon fill='%23FFF' points='540 0 450 150 630 150'/%3E%3Cpolygon points='900 0 810 150 990 150'/%3E%3Cpolygon fill='%23222' points='0 300 -90 450 90 450'/%3E%3Cpolygon fill='%23FFF' points='0 300 90 150 -90 150'/%3E%3Cpolygon fill='%23FFF' points='180 300 90 450 270 450'/%3E%3Cpolygon fill='%23666' points='180 300 270 150 90 150'/%3E%3Cpolygon fill='%23222' points='360 300 270 450 450 450'/%3E%3Cpolygon fill='%23FFF' points='360 300 450 150 270 150'/%3E%3Cpolygon fill='%23444' points='540 300 450 450 630 450'/%3E%3Cpolygon fill='%23222' points='540 300 630 150 450 150'/%3E%3Cpolygon fill='%23AAA' points='720 300 630 450 810 450'/%3E%3Cpolygon fill='%23666' points='720 300 810 150 630 150'/%3E%3Cpolygon fill='%23FFF' points='900 300 810 450 990 450'/%3E%3Cpolygon fill='%23999' points='900 300 990 150 810 150'/%3E%3Cpolygon points='0 600 -90 750 90 750'/%3E%3Cpolygon fill='%23666' points='0 600 90 450 -90 450'/%3E%3Cpolygon fill='%23AAA' points='180 600 90 750 270 750'/%3E%3Cpolygon fill='%23444' points='180 600 270 450 90 450'/%3E%3Cpolygon fill='%23444' points='360 600 270 750 450 750'/%3E%3Cpolygon fill='%23999' points='360 600 450 450 270 450'/%3E%3Cpolygon fill='%23666' points='540 600 630 450 450 450'/%3E%3Cpolygon fill='%23222' points='720 600 630 750 810 750'/%3E%3Cpolygon fill='%23FFF' points='900 600 810 750 990 750'/%3E%3Cpolygon fill='%23222' points='900 600 990 450 810 450'/%3E%3Cpolygon fill='%23DDD' points='0 900 90 750 -90 750'/%3E%3Cpolygon fill='%23444' points='180 900 270 750 90 750'/%3E%3Cpolygon fill='%23FFF' points='360 900 450 750 270 750'/%3E%3Cpolygon fill='%23AAA' points='540 900 630 750 450 750'/%3E%3Cpolygon fill='%23FFF' points='720 900 810 750 630 750'/%3E%3Cpolygon fill='%23222' points='900 900 990 750 810 750'/%3E%3Cpolygon fill='%23222' points='1080 300 990 450 1170 450'/%3E%3Cpolygon fill='%23FFF' points='1080 300 1170 150 990 150'/%3E%3Cpolygon points='1080 600 990 750 1170 750'/%3E%3Cpolygon fill='%23666' points='1080 600 1170 450 990 450'/%3E%3Cpolygon fill='%23DDD' points='1080 900 1170 750 990 750'/%3E%3C/g%3E%3C/pattern%3E%3C/defs%3E%3Crect x='0' y='0' fill='url(%23a)' width='100%25' height='100%25'/%3E%3Crect x='0' y='0' fill='url(%23b)' width='100%25' height='100%25'/%3E%3C/svg%3E");
// background-attachment: fixed;
// background-size: cover;
// }
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
overflow-x: hidden;
height: calc(100vh - 0) !important;
box-shadow: rgba(0, 0, 0, 0.02) 0px 12px 36px !important;
&.theme--dark {
background-color: #22262a !important;
background: linear-gradient(135deg, #22262a 80%, #1f2023) !important;
}
&.theme--light {
background-color: #fff !important;
background: linear-gradient(135deg, #fff 80%, #f5f9fb) !important;
}
font-weight: 400 !important;
.v-list-item {
border-radius: 8px !important;
padding: 2px 14px;
justify-content: start !important;
&.active {
*{
transition: none !important;
}
border-radius: 8px !important;
&.theme--dark {
//background-color: #2f3135 !important;
*{
color: #1E88E5 !important;
}
.v-icon {
color: #1E88E5 !important;
}
}
&.theme--light {
//background-color: #f5f6f9 !important;
*{
color: #1E88E5 !important;
}
.v-icon {
color: #1E88E5 !important;
}
}
}
&::before {
border-radius: 8px !important;
}
}
.v-list-group__header {
::before {
border-radius: 10px !important;
}
border-radius: 10px !important;
&.theme--light {
color: #000 !important;
opacity: 0.7;
}
&.theme--dark {
color: #fff !important;
opacity: 0.7;
}
&.v-list-item--active {
opacity: 1.0 !important;
}
}
.privacy-wrap {
display: flex;
height: 30px;
opacity: 0.5;
margin: 10px;
font-size: 0.8rem;
}
.v-navigation-drawer__border {
display: none;
}
//메뉴
.menu-main-wrap {
position: absolute;
left: 0;
width: 100%;
top: 48px;
bottom: 0px;
overflow-y: auto !important;
height: calc(100% - 90px);
animation-name: fade;
animation-duration: 0.3s;
animation-iteration-count: 1;
animation-direction: alternate;
animation-fill-mode: forwards;
&.hide {
left: -100%;
animation-name: fade;
animation-duration: 0.3s;
animation-iteration-count: 1;
animation-direction: alternate;
animation-fill-mode: forwards;
}
.object-v-list-item {
opacity: 0.9;
&.active {
opacity: 1.0;
}
}
}
.menu-sub-wrap {
//background-color: #1e1e1e !important;
overflow-y: auto !important;
height: calc(100% - 90px);
top: 48px;
bottom: 0px;
position: absolute;
right: 0%;
width: 100%;
animation-name: fade;
animation-duration: 0.3s;
animation-iteration-count: 1;
animation-direction: alternate;
animation-fill-mode: forwards;
&.hide {
right: -100%;
animation-name: fade;
animation-duration: 0.3s;
animation-iteration-count: 1;
animation-direction: alternate;
animation-fill-mode: forwards;
}
.object-v-list-item {
opacity: 0.9;
&.active {
opacity: 1;
}
}
}
}
.font-strong {
background: linear-gradient(70deg, #d16ac2, 30%, #6347e2);
background: -webkit-linear-gradient(70deg, #d16ac2, 30%, #6347e2);
-webkit-text-fill-color: transparent;
font-weight: bold;
-webkit-background-clip: text;
font-weight: bold;
}
//app-bar
.app-bar-wrap {
transition-property: transform, left, right, box-shadow, max-width, width;
min-width: 320px;
&.active-transition {
transition-property: transform, left, right, box-shadow, max-width, width !important;
transition-duration: 0ms !important;
transition-delay: 0ms !important;
}
.modile-logo-wrap {
display: none;
}
}
.orgId-add-btn {
display: none;
&.on {
display: block;
}
}
}
// dashboard title
.dashboard-content-title {
cursor: pointer;
i {
opacity: 0;
}
&:hover {
i {
opacity: 1;
transform: translateX(10px);
}
}
}
//task 페이지
.task-wrap {
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
height: 100%;
width: 100% !important;
max-height: 100%;
max-width: 100% !important;
overflow: hidden;
margin: 0 !important;
.task-content-wrap {
height: 100%;
position: relative;
//multipane
.custom-resizer {
width: 100%;
height: 100%;
.multipane-resizer {
position: relative;
display: flex;
background-color: transparent;
width: 4px;
left: 3px;
z-index: 99;
&:before {
content: "";
width: 3px;
height: 30px;
position: absolute;
left: 1px;
top: 45%;
border-right: 1px solid #1E88E5;
border-left: 1px solid #1E88E5;
}
&:hover {
&:before {
height: 100%;
top: 0;
background-color: #1E88E5;
}
}
}
}
.task-main-wrap {
display: flex;
margin: 0;
padding: 0;
min-width: 60%;
width: 100%;
height: 100%;
.task-result-floating-button {
position: absolute;
top: 1rem;
left: 24px;
z-index: 99;
}
.task-tool-wrap {
position: absolute;
left: 10px;
top: 60px;
z-index: 99;
width: 60px !important;
text-align: center;
border-radius: 10px;
.theme--light & {
background-color: #f5f5f5;
}
.theme--dark & {
background-color: #1f2023;
}
}
.task-main-result {
position: absolute;
background-color: transparent;
-webkit-backdrop-filter: blur(6px);
backdrop-filter: blur(6px);
box-shadow: none !important;
top: 1rem;
left: 65px;
width: 320px;
overflow-y: auto;
border-radius: 10px;
z-index: 99;
.v-card {
width: 300px;
margin-left: 15px;
.theme--dark & {
background-color: rgba(50, 50, 50, 0.3) !important;
.v-card__text {
color: #f0f0f0 !important;
}
}
.theme--light & {
background-color: rgba(250, 250, 250, 0.3) !important;
.v-card__text {
color: #333 !important;
}
}
}
&:before {
content: "";
top: 15px;
left: 0;
display: inline-block;
position: fixed;
z-index: 10;
border-top: 0px solid transparent;
border-left: 0px solid transparent;
.theme--dark & {
border-right: 15px solid rgba(50, 50, 50, 0.3) !important;
}
.theme--light & {
border-right: 15px solid rgba(250, 250, 250, 0.3) !important;
}
border-bottom: 15px solid transparent;
width:15px !important;
height: 15px !important;
}
.v-data-table {
width: 100%;
}
}
.cropper-container {
height: 100% !important;
width: 100% !important;
}
.task-main-table {
height: 40px;
max-height: 40px !important;
.task-main-interface {
position: relative;
height: 40px;
.save-button {
position: absolute;
right: -10px;
top: -50px;
width: 90px;
height: 90px;
.theme--light & {
background-color: white !important;
}
.theme--dark & {
background-color: #1f2023 !important;
}
border-radius: 50%;
}
}
}
/////
}
.task-table-wrap {
height: 100%;
min-width: 21.429rem !important; //300px
.task-table-content {
display: flex;
width: 100%;
height: 100%;
}
.v-data-table>.v-data-table__wrapper>table>tbody>tr>td,
.v-data-table>.v-data-table__wrapper>table>thead>tr>td,
.v-data-table>.v-data-table__wrapper>table>tfoot>tr>td {
cursor: pointer;
}
//overflow-y:scroll ;
.task-table {
display: flex;
min-width: 14.29rem; //200px;
width: 100%;
height: 50%;
.v-card {
width: 100%;
}
&.list-table {
position: absolute;
bottom: 0;
width: 100%;
}
.v-data-table {
height: 100%;
}
}
}
}
.labelring-mobile {
display: none;
}
}
.image-cropper {
overflow: hidden !important;
height: calc(100% - 40px) !important;
width: 100%;
.cut {
width: 100%;
height: 100%;
div {
.cropper-container {
max-height: calc(100vh - 40px) !important;
overflow: hidden;
.cropper-view-box {
position: relative;
border: 1px solid #2e67af;
box-shadow: rgba(60, 80, 180, 0.1) 0px 0px 20px 5px !important;
outline: none;
}
.cropper-crop-box {
z-index: 10;
&.hide {
opacity: 0 !important;
}
div {
width: max-content !important;
}
}
.cropper-face {
background-color: #fff !important;
opacity: 0.05;
}
}
height: 100%;
width: 100%;
}
}
.cropper-bg {
background-repeat: repeat;
position: relative;
width: 100%;
height: 100%;
box-sizing: border-box;
user-select: none;
direction: ltr;
touch-action: none;
}
}
//프로젝트 설정
.setting-wrap {
//padding:1.143rem;
max-height: unset !important;
min-height: unset !important;
.v-expansion-panel-header {
font-size: 1.09rem;
}
.pjName-overlap {
pointer-events: none;
&.active {
background-color: #2e67af !important;
pointer-events: unset;
}
}
.numberUpDown {
.v-input {
input {
text-align: center;
}
}
}
.project-setting-thumbnail {
.v-input {
margin-top: 4px;
}
.v-input__prepend-outer {
display: none;
}
.v-file-input .v-file-input__text {
cursor: pointer;
}
}
}
.remove-text {
font-size: 30px;
//color: #fff;
padding: 10px 0;
font-weight: bold;
//user-select: none;
}
.model-wrap {
//padding:2.144rem;
//height: 100%;
//position: relative;
tbody {
.v-data-table__expanded.v-data-table__expanded__content {
&:hover {
cursor: pointer;
}
}
tr {
&:hover {
cursor: pointer;
}
}
}
}
.v-icon {
transition: none !important;
}
.project-thumbnail-wrap,
.org-thumbnail-wrap {
position: relative;
display: inline-block;
&:hover {
.thumbnail-upload {
opacity: 1 !important;
}
}
.thumbnail {
.v-avatar {
background-color: rgba(0, 0, 0, 0.1);
}
}
.thumbnail-upload {
transition: all 0.2s ease-in-out;
-webkit-transition: all 0.2s ease-in-out;
opacity: 0;
position: absolute !important;
background-color: rgba($color: #333, $alpha: 0.5);
top: 0 !important;
right: 0;
height: 30px;
border-radius: 5px;
color: #fff !important;
cursor: pointer;
display: block;
text-align: center;
}
.static {
opacity: 1 !important;
}
.v-text-field {
display: none;
}
}
.v-tab {
background-color: transparent !important;
&::before {
background-color: transparent !important;
}
}
.model-version {
font-family: "Lobster Two", cursive;
font-weight: bold;
font-size: 1.2rem;
}
.model-detail-info {
padding: 1.5rem 1.5rem;
.model-detail-title {
width: 100%;
h3 {
margin: 1rem 0; //14px
}
.model-detail-critical-wrap {
justify-content: flex-end;
display: flex;
.model-detail-critical {
width: 60%;
}
}
}
.cols-detail-info-table-wrap {
margin: 50px 0px !important;
}
.confusion-matrix {
height: 100%;
.title {
font-size: 1rem;
}
.table-header-rotated {
margin-top: 10px;
border-collapse: collapse;
td {
width: 60px;
}
th {
padding: 20px 40px;
}
td {
text-align: center;
padding: 20px 10px;
border: 1px solid #ccc;
}
th.rotate-title {
border-bottom: 1px solid #ccc;
border-right: 1px solid #ccc;
position: relative;
.title-text {
font-size: 0.85rem;
&.title-real {
position: absolute;
bottom: 4%;
left: 4%;
}
&.title-prediction {
position: absolute;
top: 4%;
right: 4%;
}
}
}
th.rotate {
border-right: 1px solid #ccc;
// height: 140px;
// white-space: nowrap;
>div {
//transform:translate(25px, 51px) rotate(315deg);
width: 30px;
}
// > div > span{
// border-bottom: 1px solid #ccc;
// padding:0px 20px 5px 10px;
// }
}
th.row-header {
padding: 0 10px;
border-bottom: 1px solid #ccc;
}
}
}
.model-table-wrap {
margin: 2rem 0rem 3rem;
}
.chart-common {
.precision-chart {
text-align: center;
>div>div>div {
margin: 0px auto;
}
}
.threshold-chart {
height: 100%;
text-align: center;
>div>div>div {
margin: 0px auto;
}
}
}
}
.cols-table-wrap {
.cols-detail-info-table-wrap {
.theme--dark.v-data-table {
background-color: transparent;
}
table {
display: flex;
align-items: center;
tr {
display: flex;
flex-direction: column;
th {
display: flex;
align-items: center;
}
td {
align-items: center;
display: flex;
border-bottom: thin solid rgba(255, 255, 255, 0.12);
}
}
}
}
}
//스크롤바 공통
div {
&::-webkit-scrollbar {
width: 0.572rem; //8px;
height: 0.358rem;
background-color: transparent;
}
&::-webkit-scrollbar-button {
width: 0.572rem; //8px;
height: 0.358rem; //5px;
}
&::-webkit-scrollbar-track {
background: transparent;
border: thin solid transparent;
box-shadow: 0px 0px 0.215rem transparent inset; //3px
border-radius: 0.72rem; //10px;
}
&::-webkit-scrollbar-thumb {
background: transparent;
border: thin solid transparent;
border-radius: 0.72rem; //10px;
}
&::-webkit-scrollbar-thumb:hover {
background: #7d7d7d;
}
&::-webkit-scrollbar-corner {
background-color: transparent;
}
}
div:hover {
&::-webkit-scrollbar {
width: 0.572rem; //8px;
background-color: transparent;
}
&::-webkit-scrollbar-track {
background: transparent;
border: thin solid transparent;
box-shadow: 0px 0px 0.215rem transparent inset; //3px
border-radius: 0.72rem; //10px;
}
&::-webkit-scrollbar-thumb {
background: #ccc;
border: thin solid #ccc;
border-radius: 0.72rem; //10px;
}
}
// 모델 상세페이지 공통
.object-tab-wrap {
//background-color:#1e1e1e;
}
.model-test {
padding: 1rem 10px;
.image-update-content {
.title-text {
font-size: 1.05rem;
}
.preview-text {
background-color: rgb(20, 8, 8);
font-size: 9px;
padding: 2px 3px;
display: inline-block;
margin-left: 5px;
border-radius: 5px;
}
}
.image-viewer {
margin: 0.5rem 0px;
height: 500px;
.tool-wrap {
padding: 0px 10px;
}
.image-cropper {
height: calc(500px - 55px);
.cut {
height: 100%;
>div {
height: 100%;
img {
height: 100%;
}
}
}
}
.filter-label {
.filter-title-wrap {
padding: 14px 12px;
border-bottom: 1px solid red;
.filter-title {
font-weight: bold;
margin-left: 2px;
margin-right: 5px;
display: inline-block;
}
.filter-sub-title {
font-size: 12px;
color: #737373;
}
}
.filter-content-wrap {
padding: 5px 12px;
.filter-value {
span {
font-size: 9px;
}
}
}
}
}
}
// 테이블 공통 css
.v-data-table {
padding: 1rem 0;
background-color: transparent !important;
&.bgc {
background-color: #fff !important;
}
* {
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
table {
thead {
tr {
th {
font-size: 1rem !important;
}
}
}
tbody {
tr {
cursor: pointer;
td {
font-size: 1rem !important;
}
}
}
}
&.hide-footer-select {
.v-data-footer {
padding-top: 10px;
}
.v-data-footer__select {
.v-input {
display: none !important;
}
}
}
i {
font-size: 20px !important;
}
&.history-table {
background-color: transparent;
width: 100%;
tbody {
tr {
&:hover {
cursor: default;
background-color: transparent !important;
}
a {
z-index: 10;
}
}
}
}
&.flex-table{
table{
display: flex;
overflow: hidden;
}
}
&.table-scrollable {
.v-data-table__wrapper {
height: 85%;
overflow-y: auto;
.v-data-table-header{
position: sticky;
top: 0;
z-index: 9;
}
}
&.theme--dark .v-data-table-header{
background-color: #1f2023;
}
&.theme--light .v-data-table-header{
background-color: #f5f9fb;
}
}
}
.v-data-table__expanded {
box-shadow: none !important;
}
.object-ready {
text-align: center;
padding-top: 7rem;
i {
font-size: 5rem !important;
margin-bottom: 1rem;
}
p {
font-size: 2rem;
}
}
//모델 > 개요
.training-info {
.training-title {
flex-grow: 0.2;
}
}
@keyframes leftHide {
0% {
left: 0;
}
100% {
left: -100%;
}
}
@keyframes leftShow {
0% {
left: -100%;
}
100% {
left: 0%;
}
}
@keyframes rightShow {
0% {
right: -100%;
}
100% {
right: 0%;
}
}
@keyframes rightHide {
0% {
right: 0%;
}
100% {
right: -100%;
}
}
// 관리자설정 -> 프로젝트관리
.project-admin-setting-wrap {
.project-list {
width: 100%;
.text-start {
button {
display: none;
}
}
.member-list-wrap {
.list-title {
font-size: 1.02rem;
}
}
}
}
.select-none-border {
&.v-text-field {
.v-input__control {
.v-input__slot {
&:before {
border-width: 0px !important;
}
}
}
}
}
.snotify {
width: unset !important;
z-index: 999999999 !important;
}
// snotify 큰 틀
.snotifyToast {
border-radius: 8px;
box-shadow: 1px 8px 12px -2px rgb(30 30 30 / 5%),
1px 4px 18px 3px rgb(50 50 50 / 3%) !important;
}
// snotify 아이콘
.snotify-icon {
max-height: 18px !important;
max-width: 18px !important;
right: 0px !important;
left: 16px !important;
}
.snotifyToast__inner {
font-family: "Noto Sans KR", sans-serif !important;
min-height: 50px !important;
padding: 5px 17px 5px 48px !important;
}
//글씨
.snotifyToast__body {
font-size: 1rem !important;
}
//게시판 글쓰기
.notice-read-wrap {
.theme--light & {
.title-row {
//border-top: 2px solid #333;
}
.content-row {
border-top: 1px solid #ccc;
border-bottom: 1px solid #ccc;
}
}
.theme--dark & {
.title-row {
//border-top: 2px solid #222;
}
.content-row {
border-top: 1px solid #333;
border-bottom: 1px solid #333;
}
}
.title-row {
font-size: 2rem;
padding: 1rem 1rem;
margin: 0 1rem;
}
.subtitle-row {
padding: 2rem 1rem;
margin: 0 1rem;
display: flex;
flex-direction: row;
}
.content-row {
padding: 4rem 1rem;
margin: 1rem;
min-height: 500px;
}
}
//project-card
.project-card {
position: relative;
overflow: hidden;
.project-card-thumbnail {
cursor: pointer;
}
&.theme--dark {
.project-card-thumbnail {
background-color: #333 !important;
}
}
&.theme--light {
.project-card-thumbnail {
background-color: #eee !important;
}
}
transition: all .2s ease-in-out !important;
}
//v-card 전역
.v-card {
&.task-card{
cursor: pointer;
border-radius: 16px !important;
transition: all .1s ease-in-out;
transform: scale(0.99);
&.hover{
transform: scale(1);
}
}
}
.v-picker__body {
background-color: transparent !important;
}
//v-alert
.v-alert {
box-shadow: rgba(0, 0, 0, 0.05) 0px 20px 30px !important;
}
//v-input 전역
.v-input__slot {
box-shadow: 1px 2px 3px -2px rgb(0 0 0 / 10%),
1px 1px 1px 0 rgb(0 0 0 / 5%) !important;
}
// v-swatch 전역
.vue-swatches {
top: 0;
left: 0;
position: absolute;
.vue-swatches__trigger {
width: 32px !important;
height: 32px !important;
margin: 0 12px !important;
}
.vue-swatches__container {
position: absolute;
top: 0;
left: 0;
width: 0 !important;
height: 0 !important;
box-shadow: none;
background-color: transparent !important;
.vue-swatches__wrapper {
position: fixed;
width: 160px !important;
padding: 4px !important;
border-radius: 10px;
.vue-swatches__row {
display: flex;
justify-content: center;
align-items: center;
}
.vue-swatches__swatch {
margin: 4px !important;
width: 32px !important;
height: 32px !important;
}
}
}
}
.v-dialog {
min-width: 320px !important;
}
.notice-wrap {
position: relative;
.notice-content {
min-width: 380px;
width: 380px !important;
position: absolute;
right: 3px;
border-radius: 10px !important;
margin-top: 10px;
box-shadow: rgba(0, 0, 0, 0.05) 0px 20px 30px !important;
.v-card__title{
}
.v-card__text {
height: 320px;
overflow-y: scroll;
}
.v-list {
height: 100%;
}
}
}
.admin-wrap {
position: relative;
text-align: left;
.profile-wrap {
position: relative;
}
.admin-content {
position: absolute;
margin-top: 10px;
right: 3px;
border-radius: 10px !important;
box-shadow: rgba(0, 0, 0, 0.05) 0px 20px 30px !important;
width: 300px;
min-width: 300px !important;
.profile-picture {
text-align: center;
background-color: #2e67af;
border-radius: 100% !important;
//margin:0px auto;
//line-height: 70px;
margin-right: 1.2rem;
font-size: 1.2rem;
overflow: hidden;
position: relative;
i {
font-size: 3.5rem;
position: absolute;
left: 50%;
transform: translateX(-50%);
}
}
.profile-name {
text-align: left;
width: 150px;
.name {
font-size: 1.1rem;
font-weight: 600;
}
.email {
font-size: 0.9rem;
opacity: 0.6 !important;
display: block;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
.admin-menu {
.v-list-group__items {
max-height: 240px;
overflow-y: auto;
border: 1px solid red !important;
.org-name {
display: inline-block;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
.header-button {
position: absolute;
right: 0;
}
}
.v-list-item {
padding: 1px 24px !important;
}
}
}
// 텍스트 편집기 vue2-editor
.editor-content.hide-toolbar {
.ql-toolbar {
display: none;
}
.ql-container {
border: none;
}
}
.description-line-2 {
width: 100%;
text-align: left;
text-overflow: ellipsis;
overflow: hidden;
word-break: break-word;
display: -webkit-box;
-webkit-line-clamp: 2; // 원하는 라인수
-webkit-box-orient: vertical
}
.description-line-3 {
width: 100%;
text-align: left;
text-overflow: ellipsis;
overflow: hidden;
word-break: break-word;
display: -webkit-box;
-webkit-line-clamp: 2; // 원하는 라인수
-webkit-box-orient: vertical
}
// .calendar-container {
// border: 1px solid;
// height: 28px;
// line-height: 28px;
// display: inline-block;
// padding: 0 0.5rem;
// justify-content: center;
// border-radius: 0.3rem;
// span {
// display: inline-block;
// justify-content: center;
// height: 28px;
// line-height: 28px;
// padding: 0 0.5rem;
// }
// }
@media screen and (max-width: 1080px) {
.guide-navigator {
display: none !important;
}
}
/* animation */
.fade_up_animation {
animation-name: fade_up;
animation-duration: 300ms;
animation-timing-function: ease-in-out;
}
.fade_right_animation {
animation-name: fade_right;
animation-duration: 300ms;
animation-timing-function: ease-in-out;
}
.fade_animation {
animation-name: fade;
animation-duration: 300ms;
animation-timing-function: ease-in-out;
}
.appear_animation {
animation-name: appear;
animation-delay: 300ms;
animation-duration: 1000ms;
animation-timing-function: ease-in-out;
}
.delay_100 {
animation-duration: 400ms !important;
}
.delay_200 {
animation-duration: 500ms !important;
}
.delay_300 {
animation-duration: 600ms !important;
}
.delay_400 {
animation-duration: 700ms !important;
}
.delay_500 {
animation-duration: 800ms !important;
}
.delay_600 {
animation-duration: 900ms !important;
}
.delay_700 {
animation-duration: 1000ms !important;
}
.delay_800 {
animation-duration: 1100ms !important;
}
.delay_900 {
animation-duration: 1200ms !important;
}
.delay_1000 {
animation-duration: 1300ms !important;
}
@keyframes appear {
0% {
transform: scale(0.9) !important;
}
50%{
transform: scale(1.1) !important;
}
100% {
transform: scale(1) !important;
}
}
@keyframes fade {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
@keyframes fade_up {
0% {
opacity: 0;
transform: translateY(16px);
}
30% {
opacity: 0;
transform: translateY(16px);
}
100% {
opacity: 1;
transform: translateY(0px);
}
}
@keyframes fade_right {
0% {
opacity: 0;
transform: translateX(-16px);
}
30% {
opacity: 0;
transform: translateX(-16px);
}
100% {
opacity: 1;
transform: translateX(0px);
}
}
@-webkit-keyframes rotating {
from {
-webkit-transform: rotate(0deg);
}
to {
-webkit-transform: rotate(360deg);
}
}
@keyframes rotating {
from {
-webkit-transform: rotate(0deg);
}
to {
-webkit-transform: rotate(360deg);
}
}
@-webkit-keyframes jumping {
0% {
-webkit-transform: translateY(0px);
}
50% {
-webkit-transform: translateY(-6px);
}
100% {
-webkit-transform: translateY(0px);
}
}
@keyframes jumping {
0% {
-webkit-transform: translateY(0px);
}
50% {
-webkit-transform: translateY(-6px);
}
100% {
-webkit-transform: translateY(0px);
}
}
.rotating {
animation: rotating 1s linear infinite !important;
-webkit-animation: rotating 1s linear infinite !important;
}
.jumping {
animation: jumping 1s linear infinite !important;
-webkit-animation: jumping 1s linear infinite !important;
}
.checkmark__circle {
stroke-dasharray: 166;
stroke-dashoffset: 166;
stroke-width: 2;
stroke-miterlimit: 10;
stroke: #1E88E5;
fill: none;
animation: stroke 0.6s cubic-bezier(0.65, 0, 0.45, 1) forwards
}
.checkmark {
width: 28px;
height: 28px;
border-radius: 50%;
display: block;
stroke-width: 4;
stroke: #fff;
stroke-miterlimit: 10;
margin: 10px;
box-shadow: inset 0px 0px 0px #1E88E5;
animation: fill .4s ease-in-out .4s forwards, scale .3s ease-in-out .9s both
}
.checkmark__check {
transform-origin: 50% 50%;
stroke-dasharray: 48;
stroke-dashoffset: 48;
animation: stroke 0.3s cubic-bezier(0.65, 0, 0.45, 1) 0.8s forwards
}
@keyframes stroke {
100% {
stroke-dashoffset: 0
}
}
@keyframes scale {
0%,
100% {
transform: none
}
50% {
transform: scale3d(1.1, 1.1, 1)
}
}
@keyframes fill {
100% {
box-shadow: inset 0px 0px 0px 30px #1E88E5
}
}
/* Common */
.max-width-300 {
display: inline-block;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
max-width: 300px;
}
.max-width-200 {
display: inline-block;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
max-width: 200px;
}
.max-width-100 {
display: inline-block;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
max-width: 100px;
}
.opacity-9 {
opacity: 0.8;
}
.opacity-8 {
opacity: 0.8;
}
\ No newline at end of file
// Ref: https://github.com/nuxt-community/vuetify-module#customvariables
//
// The variables you want to modify
// $font-size-root: 20px;
$menu-content-elevation: 1;
$list-item-dense-title-font-size: 1rem;
$list-dense-min-height: 42px;
\ No newline at end of file
# COMPONENTS
**This directory is not required, you can delete it if you don't want to use it.**
The components directory contains your Vue.js Components.
_Nuxt.js doesn't supercharge these components._
<template>
<v-dialog
v-model="dialog"
:persistent="persistent"
scrollable
:max-width="maxWidth"
:max-height="maxHeight"
:width="width"
:fullscreen="fullDialog"
@keydown.enter="enterKeyEvent"
overlay-opacity="0.2"
transition="slide-y-reverse-transition"
content-class="vridge-dialog-container"
>
<template v-slot:activator="{on, attrs}">
<slot name="button" v-on="on" v-bind="{on, attrs}"></slot>
</template>
<v-card class="vridge-dialog" :tile="tile" elevation="0">
<v-card-title class="vridge-dialog-title">
<slot name="title" v-if="$slots.title">
</slot>
<v-row v-else no-gutters align="center">
<span class="font-weight-bold">
{{title}}
</span>
<v-spacer/>
<v-avatar size="28" class="vridge-dialog-close" v-if="closeButton">
<v-icon class="font-weight-bold" @click="clickNo" >mdi-close</v-icon>
</v-avatar>
</v-row>
</v-card-title>
<v-card-text class="vridge-content-container" color="white" flat :style="'height:' + height +'px !important; overflow-y:' + overflow + ';background:' + background" style="min-height:140px;">
<v-container fluid style="width:100%; height:100%;">
<slot name="content">
</slot>
</v-container>
</v-card-text>
<v-card-actions class="vridge_actions" v-if="!$slots.actionButton" >
<v-spacer></v-spacer>
<v-btn
:ripple="false"
elevation="0"
@click="clickNo"
:disabled="disabledNo"
:loading="loading"
>
<span>{{$t('close')}}</span>
</v-btn>
<v-btn
color="primary"
@click="clickYes"
elevation="0"
:ripple="false"
v-if="clickYesBtn"
:loading="loading"
:disabled="disabledYes"
>
<span>{{clickYesBtn}}</span>
</v-btn>
</v-card-actions>
<v-card-actions v-else class="vridge_actions">
<v-spacer/>
<slot name="actionButton"> </slot>
</v-card-actions>
</v-card>
</v-dialog>
</template>
<script>
export default {
name: 'vridgeDialog',
props: {
persistent:{
type:Boolean,
default:false
},
borderBottom:{
type:Number
},
borderRadius:{
type:Number
},
color:{
type:String
},
colorHeader:{
type:String
},
fullscreen:{
type:Boolean,
default:false
},
disabled:{
type: Boolean,
default: false
},
icon:{
type: String,
default: ''
},
title:{
type: String,
default: '새창'
},
width:{
type: Number,
default: 500
},
height:{
type: Number,
default: 120
},
maxWidth:{
type: Number,
default: 1080
},
maxHeight:{
type: Number,
default: 1080
},
padding:{
type:String
},
overflow:{
type:String,
default:'auto'
},
background:{
type:String
},
backgroundHeader:{
type:String
},
clickYesBtn:{
type:String,
default:""
},
mobileFull:{
type:Boolean,
default:false
},
enterKeyActive:{
type:Boolean,
default:false
},
tile:{
type:Boolean,
default:false
},
closeButton:{
type:Boolean,
default:false
},
loading: {
type:Boolean,
default:false
},
disabledYes: {
type:Boolean,
default:false
},
disabledNo: {
type:Boolean,
default:false
},
autoClose: {
type:Boolean,
default:true
}
},
data: () => ({
dialog: false,
format:'YYYY-MM-DD',
isFullDialog:false
}),
watch:{
dialog() {
this.$emit('change')
if(this.dialog) {
this.init()
if(this.mobileFull){
this.isFullDialog = window.innerWidth <= 768
}
} else {
this.$emit('click-no')
this.$emit('close')
}
},
},
computed: {
fullDialog(){
return this.isFullDialog || this.fullscreen
}
},
methods:{
init(){
},
clickYes(){
if(this.autoClose) this.dialog = false
this.$emit('click-yes')
},
enterKeyEvent(){
if(this.enterKeyActive){
this.$emit('click-yes')
}
},
select (index) {
this.selectedIndex = index
},
clickNo(){
this.dialog = false
this.$emit('click-no')
},
resizeEventHandler(e) {
if(this.dialog && this.mobileFull) {
if(e.currentTarget.outerWidth <= 768) {
this.isFullDialog = true
} else {
this.isFullDialog = false
}
} else {
this.isFullDialog = false
}
},
},
mounted(){
window.addEventListener("resize", this.resizeEventHandler);
},
destroyed() {
window.removeEventListener("resize", this.resizeEventHandler);
},
}
</script>
<style lang="scss">
.vridge-dialog-container{
.theme--light &{
box-shadow: rgba(0, 0, 0, 0.1) 0px 20px 30px !important;
}
}
.theme--dark{
.vridge-dialog{
border:1px solid #2a2a2a !important;
}
}
.theme--light{
.vridge-dialog{
border:1px solid #eee !important;
}
}
.vridge-dialog{
overflow-x:hidden;
padding:24px;
> .v-card__title{
font-size:20px !important;
padding:12px !important;
//margin-bottom:12px !important;
}
.vridge-content-container{
padding:0px !important;
font-size: 14px;
transition: height 0.5s ease-in-out;
}
.vridge_actions{
padding: 12px !important;
}
}
.create-slot-cover{
padding:0;
margin:0;
.search-menu-cover{
display:flex;
background-color:transparent;
border:1px solid #e0e0e0;
border-radius: 5px;
}
.search-menu-check-cover{
.v-input--selection-controls {
padding: 0;
margin: 0;
}
.v-messages {
display:none !important;
}
}
}
.v-dialog--persistent{
.vdp-datepicker, .vdpWithInput{
height:38px;
width:100%;
input{
height: 38px;
padding-left:12px;
width:100%;
font-size:15px;
}
.vdp-datepicker__calendar{
bottom: 40px;
}
}
}
.vdpClearInput{
display:none !important;
}
.dateInput >>> input{
padding:8px 0px;
font-size: 13px;
font-weight: 500;
opacity: .7;
}
.dateInput >>> button::before{
display:none !important;
}
.calendarCustom{
font-size:20px;
margin-left:-25px;
position:relative;
color:#4159b2;
cursor:pointer;
display:flex;
align-items:center;
}
/* .v-dialog{
overflow-y: initial;
}*/
.search-menu-cover{
.vdpHeader{
background:rgba(0,0,0,0.05) !important;
}
}
.vridge-dialog-close{
transition: all 0.2s ease-in-out;
opacity: 0.7;
&:hover{
transform:scale(1.1) !important;
opacity: 1.0;
.theme--dark{
background-color:#1b1c20 !important;
}
.theme--light{
background-color:#ccc !important;
}
font-weight: bold !important;
}
}
</style>
\ No newline at end of file
# LAYOUTS
**This directory is not required, you can delete it if you don't want to use it.**
This directory contains your Application Layouts.
More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/views#layouts).
<template>
<v-app class="default-guide-wrap">
<div class="no-shadow">
<client-only>
<v-card class="guide-navigator" color="transparent" v-if="isShowNav()">
<v-icon small>mdi-text</v-icon>
<vridge-subtitle title="목차" :fontSize="14" class="font-weight-bold">
</vridge-subtitle>
<v-card-text>
<v-row no-gutters
v-for="(item, i) in $store.state.guideNavigator"
:key="item.html"
@click="goGuideNav(item.position)"
class="guide-navigator-item"
>
<span
class="guide-navigator-item-selector"
:class="{'active': currentNavIndex===i}"
>
</span>
<span class="guide-navigator-item-text" :class="{active : currentNavIndex === i}">{{item.html}}</span>
</v-row>
</v-card-text>
</v-card>
<transition name="slide-fade">
<div class="topBtn" @click="goGuideNav(0)" v-if="!$store.state.editMode" >
<a>
<i><span class="mdi mdi-chevron-up"></span></i>
</a>
</div>
<!-- .topBtn(메인비주얼 벗어나면 나타남) end -->
</transition>
</client-only>
</div>
<v-navigation-drawer
v-model="drawer"
class="navigation-wrap"
app
flat
clipped
>
<client-only>
<v-skeleton-loader
elevation="0"
type="card-heading ,list-item, list-item-three-line, list-item, list-item, list-item ,list-item, list-item, list-item, list-item,list-item,list-item"
style="width:100%;"
v-if="loadingPageList"
></v-skeleton-loader>
<v-list
v-else
>
<v-list-item
:ripple="false"
@click="[$router.push('/docs'), guideIndex.forEach((x)=>x.active = false)]"
active-class="active"
:class="{active: $route.path === '/docs'}"
>
<v-list-item-title></v-list-item-title>
</v-list-item>
<v-list-group
v-for="(item, i) in guideIndex"
:key="'guide'+i"
v-model="item.active"
:ripple="false"
prepend-icon=""
active-class="active"
>
<!--아이콘-->
<template v-slot:appendIcon>
<span v-if="item.children.length > 0" >
<v-icon v-if="item.active">
mdi-chevron-up
</v-icon>
<v-icon v-else>
mdi-chevron-right
</v-icon>
</span>
<v-icon v-else>
</v-icon>
<v-list-item
:ripple="false"
@click="openCreateDialog(item.id, item.title)"
v-if="$store.state.editMode"
>
<v-list-item-title><v-icon small left>mdi-plus</v-icon></v-list-item-title>
</v-list-item>
</template>
<template v-slot:activator>
<v-list-item-content @click="openGuide(item, item.title)">
<v-list-item-title v-text="$t('guide_title_' + item.title)"></v-list-item-title>
</v-list-item-content>
<v-btn small icon v-if="$store.state.editMode" @click="removeGuide(item.id)"><v-icon>mdi-close</v-icon></v-btn>
</template>
<div
v-for="(subItem, si) in item.children"
:key="'sub-'+si"
>
<v-list-group
v-if="subItem.children.length > 0"
v-model="subItem.active"
:ripple="false"
sub-group
class="ml-6"
active-class="active"
:style="isActiveMenu(subItem.id) ? 'border-left:2px solid #1E88E5;' : 'border-left:1px solid rgba(0,0,0,0.15);'"
prepend-icon=""
>
<!--아이콘-->
<template v-slot:appendIcon>
<v-icon v-if="subItem.active">
mdi-chevron-up
</v-icon>
<v-icon v-else>
mdi-chevron-right
</v-icon>
</template>
<template v-slot:activator>
<v-list-item-content @click="openGuide(subItem, item.title + '_' + subItem.title)">
<v-list-item-title v-text="$t('guide_title_' + item.title+ '_' + subItem.title)"></v-list-item-title>
</v-list-item-content>
<v-btn small icon v-if="$store.state.editMode" @click="removeGuide(subItem.id)"><v-icon>mdi-close</v-icon></v-btn>
</template>
<v-list-item
:ripple="false"
v-for="(grandSubItem, gsi) in subItem.children"
:key="'grand-sub-'+gsi"
:style="isActiveMenu(grandSubItem.id) ? 'border-left:2px solid #1E88E5;' : 'border-left:1px solid rgba(0,0,0,0.15);'"
class="ml-6 pl-6"
:class="{active: isActiveMenu(grandSubItem.id) }"
@click="openGuide(grandSubItem, item.title + '_' + subItem.title + '_' + grandSubItem.title)"
active-class="active"
>
<v-list-item-title v-text="$t('guide_title_' + item.title+ '_' + subItem.title+ '_' + grandSubItem.title)"></v-list-item-title>
<v-btn small icon v-if="$store.state.editMode" @click="removeGuide(grandSubItem.id)"><v-icon>mdi-close</v-icon></v-btn>
</v-list-item>
</v-list-group>
<v-list-item
v-else
:ripple="false"
@click="openGuide(subItem, item.title + '_' + subItem.title)"
active-class="active"
class="ml-6 pl-6"
:class="{active: isActiveMenu(subItem.id)}"
:style="isActiveMenu(subItem.id) ? 'border-left:2px solid #1E88E5;' : 'border-left:1px solid rgba(0,0,0,0.15);'"
>
<v-list-item-title v-text="$t('guide_title_' + item.title+ '_' + subItem.title)"></v-list-item-title>
<v-btn small icon v-if="$store.state.editMode" @click="removeGuide(subItem.id)"><v-icon>mdi-close</v-icon></v-btn>
</v-list-item>
<v-list-item
:ripple="false"
@click="openCreateDialog(subItem.id, item.title +'/'+ subItem.title)"
v-if="$store.state.editMode"
>
<v-list-item-title><v-icon small left>mdi-plus</v-icon></v-list-item-title>
</v-list-item>
</div>
</v-list-group>
<v-list-item
:ripple="false"
@click="openCreateDialog('', '')"
v-if="$store.state.editMode"
>
<v-list-item-title><v-icon small left>mdi-plus</v-icon></v-list-item-title>
</v-list-item>
</v-list>
<template v-slot:prepend>
<v-row no-gutters align="center" justify="center" @click="logoEvent()" class=" modile-logo-wrap my-4">
<NuxtLink to="/" >
<client-only>
<v-img style="margin:0 auto;" v-if="$vuetify.theme.dark === true" src="/logo_v2/03.png" contain height="26" max-width="128"></v-img>
<v-img style="margin:0 auto;" v-else src="/logo_v2/01.png" contain height="26" max-width="128"></v-img>
</client-only>
</NuxtLink>
</v-row>
</template>
</client-only>
</v-navigation-drawer>
<v-app-bar
app
flat
class="app-bar-wrap"
clipped-left
>
<v-row no-gutters align="center">
<span style="cursor:pointer;" class="pa-2">
<img @click="$router.push('/docs')" class="mobile-hide" src="/logo_v2/01.png" cover height="28"/>
<img @click="$router.push('/docs')" class="mobile-show" src="/logo_v2/s01.png" cover height="36"/>
</span>
<v-spacer/>
<v-text-field
v-if="showSearch"
v-model="keyword"
:maxlength="50"
v-on:keyup.enter="search"
solo
dense
single-line
rounded
class="ma-2"
hide-details
label="검색"
style="border:1px solid #eee;max-width:240px;">
</v-text-field>
<v-icon @click="showSearch = !showSearch">{{showSearch ? 'mdi-close' : 'mdi-magnify'}}</v-icon>
<!-- <v-btn @click="$store.state.editMode = !$store.state.editMode" elevation="0">편집</v-btn> -->
<v-app-bar-nav-icon class="mobile-show" v-if="!drawer" @click.stop="drawer = !drawer" />
</v-row>
<v-progress-linear absolute bottom :value="scrollPositionRate" height="1" background-color="#eee" color="primary" style="transition:none !important;"></v-progress-linear>
</v-app-bar>
<v-main>
<client-only>
<Nuxt class="nuxt-route-content active" />
<vridge-dialog
ref="newPageDialog"
>
<template
v-slot:content
>
<v-text-field v-model="createGuideTitle" label="타이틀" :maxlength="50"></v-text-field>
<v-tooltip
bottom>
<template v-slot:activator="{on,attrs}">
<v-btn
v-bind="attrs"
v-on="on"
elevation="0"
icon
>
<v-icon small>mdi-comment-question</v-icon>
</v-btn>
</template>
<span>
예시)
https://api.github.com/repos/vazilcompany/vridge-docs/contents/guide/ko/overview/overview.md 일경우
>> overview/overview.md 입력
</span>
</v-tooltip>
<v-text-field v-model="createGuideContentKey" label="가이드 github 경로"></v-text-field>
</template>
<template v-slot:actionButton>
<v-btn @click="createGuide()">등록</v-btn>
</template>
</vridge-dialog>
</client-only>
<ChannelTalk/>
</v-main>
</v-app>
</template>
<script>
import "~/assets/scss/guide.scss"
import ChannelTalk from '../components/channeltalk/ChannelTalk.vue'
export default{
head () {
return {
titleTemplate(title) {
return title + (this.$route.meta.title ? ' - ' + this.$route.meta.title : ' - 서비스 가이드')
}
}
},
components:{
ChannelTalk
},
data:() => ({
//스크롤 헤더 트래커
navPositionList: [],
currentNavIndex: 0,
keyword:'',
drawer:true,
guideTitle:'',
clipped: false,
guideIndex:[],
loadingPageList: false,
targetParentId: null,
createGuideTitle : '',
createGuideContentKey :'',
listGroup:null,
call:0,
scrollPositionRate:0,
showSearch:false,
}),
methods:{
openIndex() {
let route = this.$router.resolve({path: '/'});
window.open(route.href, '_self');
},
//로고 클릭 이벤트
logoEvent(){
this.openIndex()
},
isActiveMenu(id) {
if(this.$route.path !== '/docs' && id === this.$store.state.guideId) {
return true
}
return false
},
// 가이드 페이지 이동
openGuide(guide, path) {
this.$router.push('/docs/' + path)
this.$store.commit('setGuide',{guideId:guide.id, flag:false})
this.$store.state.guideId = guide.id
},
async getGuideIndex() {
this.loadingPageList = true
await this.$axios.get('/guide/index')
.then(res => {
this.guideIndex = res.data.body
})
.catch(err => {
console.log(err)
})
this.loadingPageList = false
this.activeGuideTreeIndex()
},
// 현재 페이지의 가이드 메뉴를 찾아서 활성화함
activeGuideTreeIndex() {
if(!this.$store.state.guide) return
for(let i = 0; i < this.guideIndex.length; i++) {
this.activeGuideTreeIndexRecursive(this.guideIndex[i], this.guideIndex[i], this.guideIndex[i])
}
},
// 재귀 호출로 트리 구조의 자식들 메뉴를 활성화
activeGuideTreeIndexRecursive(guide, parent, grandParent) {
if(guide.id === this.$store.state.guideId) {
guide.active = true
parent.active = true
grandParent.active = true
return
}
if(guide.children) {
for(let i = 0; i < guide.children.length; i++) {
this.activeGuideTreeIndexRecursive(guide.children[i], guide, parent)
}
}
},
// 가이드 생성
createGuide() {
if(!this.createGuideTitle || !this.createGuideContentKey) {
alert('내용 누락')
return
}
let guide = {
parentId:this.targetParentId,
title: this.createGuideTitle,
contentKey: this.createGuideContentKey
}
this.$axios.post('/guide/index', guide)
.then(res=>{
this.$refs.newPageDialog.dialog = false
this.getGuideIndex()
})
.catch(err=>{
console.log(err)
})
},
openCreateDialog(parentId, prefix) {
this.$refs.newPageDialog.dialog = true
this.createGuideTitle = ''
this.createGuideContentKey = prefix ? prefix + '/' : ''
this.targetParentId = parentId
},
removeGuide(guideId){
this.$axios.delete("/guide", {
params:{
guideId: guideId,
cascade:true
}
})
.then(res => {
this.getGuideIndex()
})
.catch(err => {
console.log(err)
})
},
//가이드 네비게이션 이동
goGuideNav(position) {
window.scrollTo({top:position, behavior:'smooth'})
},
getCurrentNavIndex(scrollPosition){
const navLen = this.navPositionList.length;
if(navLen <= 1) {
this.currentNavIndex = 0;
return
}
for(let i=navLen-1; i>=0; i--){
if(scrollPosition >= this.navPositionList[i]){
this.currentNavIndex = i;
return;
}
}
},
search(){
if(this.keyword){
this.$router.push('/docs/search?keyword=' + this.keyword)
this.keyword = ''
}
},
scrollEvent(e) {
let scrollLocation = document.documentElement.scrollTop; // 현재 스크롤바 위치
let windowHeight = window.innerHeight; // 스크린 창
let fullHeight = document.body.scrollHeight; // margin 값은 포함 x
this.scrollPositionRate = (scrollLocation) * 100 / (fullHeight - windowHeight)
this.getCurrentNavIndex(Math.ceil(window.scrollY));
},
isShowNav() {
return this.$store.state.guideNavigator
}
},
created() {
this.getGuideIndex()
},
mounted(){
window.addEventListener("scroll", this.scrollEvent);
},
destroyed() {
window.removeEventListener('scroll', this.scrollEvent)
},
watch:{
'$store.state.guideNavigator'() {
this.navPositionList = this.$store.state.guideNavigator.map((nav) => nav.position)
},
'$store.state.guideTreeFlag'() {
this.activeGuideTreeIndex()
}
},
}
</script>
\ No newline at end of file
<template>
<div>
<v-container class="error-container">
<v-row no-gutters justify="center" align="center" class="py-16">
<v-col justify="center" align="center">
<v-row no-gutters class="error_img_box" >
<img src="/error_img.png" />
<v-col cols="12" v-if="error.statusCode === 404" v-html="pageNotFound" justify="center" align="center" class="mb-16 error_text"></v-col>
<v-col cols="12" v-else justify="center" v-html="otherError" style="text-align:center;" align="center" class="mb-16 error_text">
</v-col>
<v-col cols="12" justify="center" align="start">
<span class="error_number">{{error.statusCode}}</span>
</v-col>
</v-row>
<v-row no-gutters justify="center" align="center">
<NuxtLink to="/" class="home-btn" style="margin-right: 16px; margin-top: 50px:">
홈으로
</NuxtLink>
</v-row>
</v-col>
</v-row>
</v-container>
</div>
</template>
<script>
export default {
props: {
error: {
type: Object,
default: null
}
},
data () {
return {
pageNotFound: "<h3>존재하지 않거나, 사용할 수 없는 페이지 입니다.</h3>"
}
},
head () {
const title = this.error.statusCode === 404 ? 'Not Found': 'ERROR'
return {
title
}
}
}
</script>
<style lang="scss">
.error-container{
margin-top: 64px;
}
@font-face {
font-family: 'EF_MACHO';
src: url('https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_2206-01@1.0/EF_MACHO.woff2') format('woff2');
font-weight: normal;
font-style: normal;
}
.home-btn {
height: 42px;
line-height: 42px;
width: 120px;
text-align: center;
border-radius: 5px;
color:#fff !important;
// background-color: #4f4f4f;
background: linear-gradient(70deg, #d16ac2, 30%, #6347e2);
background: -webkit-linear-gradient(70deg, #d16ac2, 30%, #6347e2);
}
.cont_center_error {
position: relative;
}
.cont_box_error {
min-width: 100%;
}
.cont_error {
position: absolute;
top: 38%;
left: 50%;
transform: translate(-50%, -50%);
padding-bottom: 0 !important;
}
.error_img_box {
position: relative;
img {
margin:auto;
height: 400px;
}
}
.error_text {
text-align: center;
margin-bottom: 65px !important;
h3 {
margin-top: 20px;
font-size: 1.4em;
}
}
.error_number {
color: #fff;
letter-spacing: 10px;
font-size: 4.6em;
line-height: 1.1;
text-shadow: 0 2px 5px #fff;
position: absolute;
top: calc(60% - 25px);
left: calc(50% - 193px);
font-family: 'EF_MACHO' !important;
animation: error_number 2.2s linear infinite;
}
@keyframes error_number {
0% {
opacity: 1;
}
10% {
opacity: .5;
}
20% {
opacity: 1;
}
100% {
opacity: 1;
}
}
@media screen and (max-width: 1024px) {
.cont_center_error {
.cont_box_error {
min-height: 680px;
}
.cont_error {
margin-top: 0px;
}
img {
height: 250px;
object-fit: cover;
margin-top: 0;
}
.error_number {
font-size: 2.3em !important;
top: 50%;
left: calc(50% - 119px);
}
.error_text {
h3 {
font-size: 1.2em;
}
}
}//.cont_center_error end
}//@media screen and (max-width: 1024px) end
@media screen and (max-width: 820px) {
.cont_center_error {
.cont_box_error {
min-height: 680px;
}
.cont_error {
margin-top: 0px;
}
img {
height: 350px;
}
.error_number {
font-size: 3.6em !important;
top: calc(55% - 6px);
left: calc(50% - 170px);
}
.error_text {
h3 {
font-size: 1.2em;
}
}
}//.cont_center_error end
}
@media screen and (max-width: 466px) {
.cont_center_error {
.cont_error {
top: 45%;
}
img {
height: 220px;
}
.error_number {
font-size: 2em !important;
top: 161px;
left: calc(50% - 105px);
}
.error_text {
h3 {
font-size: 1em;
}
}
}//.cont_center_error end
}//@media screen and (max-width: 466px) end
@media screen and (max-width: 360px) {
.cont_center_error {
.cont_error {
top: 41%;
}
img {
height: 170px;
}
.error_number {
font-size: 1.4em !important;
top: 124px;
left: calc(50% - 82px);
}
.error_text {
text-align: center;
padding: 0 15px;
h3 {
font-size: 1em;
}
}
}//.cont_center_error end
}//@media screen and (max-width: 360px) end
</style>
import webpack from 'webpack'
export default {
// Global page headers: https://go.nuxtjs.dev/config-head
head: {
script: [
{ src: 'https://static.nid.naver.com/js/naveridlogin_js_sdk_2.0.0.js' },
{ src: 'https://docs.opencv.org/3.4/opencv.js' },
],
titleTemplate: 'Vridge AI - %s',
title: 'Vridge AI',
htmlAttrs: {
lang: 'ko'
},
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{ hid: 'og:title', property: 'og:title', content: 'Vridge AI' },
{ hid: 'og:site_name', property: 'og:site_name', content: 'Vridge AI' },
{ hid: 'description', name: 'description', content: '클라우드 환경에서 인공지능 모델을 학습하고 배포 및 버전 관리 서비스' },
{ hid: 'og:image', property: 'og:image', content: 'https://d2q9yzkd471o7j.cloudfront.net/logo/head_logo.png?w=300&h=300' }
],
link: [
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' },
{ rel: 'preconnect', href: 'https://fonts.gstatic.com' },
{ rel: 'stylesheet', href: 'https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@100;300;400;500;700;900&display=swap' },
{ rel: 'stylesheet', href: 'https://fonts.googleapis.com/earlyaccess/nanumgothic.css'},
{ rel: 'stylesheet', href: 'https://cdn.jsdelivr.net/npm/@mdi/font@6.5.95/css/materialdesignicons.min.css'},
]
},
// loading: '~/components/LoadingBar.vue',
loading:false,
// Global CSS: https://go.nuxtjs.dev/config-css
css: [
"~/assets/scss/common.scss",
"~/assets/scss/transition.scss",
],
// Plugins to run before rendering page: https://go.nuxtjs.dev/config-plugins
plugins: [
'~/plugins/global.js'
],
// Auto import components: https://go.nuxtjs.dev/config-components
components: true,
// Modules for dev and build (recommended): https://go.nuxtjs.dev/config-modules
buildModules: [
// https://go.nuxtjs.dev/vuetify
'@nuxtjs/vuetify',
'@nuxtjs/svg'
],
// Modules: https://go.nuxtjs.dev/config-modules
modules: [
'@nuxtjs/auth',
'@nuxtjs/axios',
'@nuxtjs/pwa',
'nuxt-route-meta',
['cookie-universal-nuxt', { alias: 'cookiz' }],
"vue2-editor/nuxt",
'nuxt-healthcheck',
'nuxt-i18n',
],
// Vuetify module configuration: https://go.nuxtjs.dev/config-vuetify
vuetify: {
customVariables: ['~/assets/variables.scss'],
treeShake: true,
defaultAssets: {
font: {
family: 'Noto Sans KR'
}
}
},
animejs: true,
// Build Configuration: https://go.nuxtjs.dev/config-build
build: {
vendor: ['axios'],
hotMiddleware:{
client:{
quiet:true
}
},
html: {
minify: {
collapseWhitespace: true, // as @dario30186 mentioned
removeComments: true, // 👈 add this line
},
},
plugins: [
new webpack.ProvidePlugin({
// global modules
$: 'jquery',
jQuery: 'jquery',
IScroll: 'iscroll',
// _: 'lodash'
})
],
extend (config, ctx) {
config.module.rules.push({
enforce: 'pre',
test: /\.txt$/,
loader: 'raw-loader',
exclude: /(node_modules)/
});
}
},
axios:{
baseURL: 'http://localhost:5000'
},
healthCheck: {
contentType: 'application/json',
healthy: () => {
return JSON.stringify({result: 'ok'})
}
},
generate: {
minify: {
collapseWhitespace: false
}
}
}
\ No newline at end of file
{
"name": "vridge-docs",
"version": "1.0.0",
"private": true,
"scripts": {
"dev": "nuxt",
"build": "nuxt build",
"start": "nuxt start",
"generate": "nuxt generate"
},
"dependencies": {
"@nuxtjs/axios": "^5.13.6",
"@nuxtjs/pwa": "^3.3.5",
"@nuxtjs/vuetify": "^1.11.3",
"atob": "^2.1.2",
"axios": "^0.21.1",
"body-parser": "^1.19.0",
"cookie-universal-nuxt": "^2.1.5",
"core-js": "^3.9.1",
"express": "^4.17.1",
"express-session": "^1.17.2",
"js-cookie": "^2.2.1",
"nuxt": "^2.15.3",
"nuxt-healthcheck": "^1.0.1",
"nuxt-i18n": "^6.28.1",
"nuxt-route-meta": "^2.2.1",
"nuxt-vuex-localstorage": "^1.3.0",
"sass-loader": "^10.2.0",
"vue-no-ssr": "^1.1.1",
"vue-swatches": "^2.1.1",
"vue-upload-component": "^2.8.22",
"vue2-editor": "^2.10.3",
"vuex-persistedstate": "^4.0.0"
},
"devDependencies": {
"@nuxtjs/svg": "^0.2.0",
"raw-loader": "^4.0.2"
}
}
# PAGES
This directory contains your Application Views and Routes.
The framework reads all the `*.vue` files inside this directory and creates the router of your application.
More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/routing).
<template>
<div class="guide-wrap-container">
<v-container class="guide-wrap">
<v-row no-gutters align="center" class="guide-wrap-header">
<v-skeleton-loader
elevation="0"
type="text"
v-if="loading"
style="width:200px; height:36px; padding-top:12px; padding-bottom:12px; margin-bottom:24px;"
></v-skeleton-loader>
<v-skeleton-loader
elevation="0"
type="heading"
v-if="loading"
style="width:100%;"
></v-skeleton-loader>
<span v-if="!loading && guide !== null">
<v-breadcrumbs
class="ma-0 px-0 py-2 "
:items="getBreadcrumbs(guide)"
>
<template v-slot:divider>
<v-icon dark>mdi-chevron-right</v-icon>
</template>
<template v-slot:item= "{item}">
<!-- <span class="text--grey text--caption" style="cursor:pointer;" @click="openGuide(item.content)" >{{item.text}}</span> -->
<span class="text--caption">{{item.text}}</span>
</template>
</v-breadcrumbs>
<h1 v-text="$t('guide_title_' + $route.params.guide)" class="font-weight-bold"></h1>
</span>
<v-spacer/>
<span v-if="!loading && guide !== null">
<!-- <span class="">{{$time.dateToFormatKorean(guide.updateDate)}}</span> -->
<v-tooltip bottom transition="slide-y-transition">
<template v-slot:activator="{on, attrs}">
<v-btn dark v-on="on" v-bind="attrs" icon @click="openGithub(guide.contentKey)"><v-icon>mdi-github</v-icon></v-btn>
</template>
<span>
Github 리소스 보기
</span>
</v-tooltip>
</span>
</v-row>
<v-row class="guide-content pb-16" no-gutters>
<v-skeleton-loader
elevation="0"
class="my-4"
v-if="loading"
type="sentences, divider, image, paragraph , sentences, paragraph, sentences"
style="width:100%;"
></v-skeleton-loader>
<div v-if="$store.state.editMode && guide">
<v-chip v-if="guide.keywordSet !== null" small label v-for="item in guide.keywordSet" :key="item" class="mr-1" close @click:close="removeKeyword(item)">
{{item}}
</v-chip>
<vridge-dialog
ref="keywordDialog"
title="키워드 추가"
@click-yes="addKeyword()"
:clickYesBtn="$t('create')"
>
<template v-slot:button>
<v-btn small elevation="0" @click="$refs.keywordDialog.dialog = true">추가</v-btn>
</template>
<template v-slot:content>
<v-text-field
v-model="newKeyword"
hide-details
solo-inverted
>
</v-text-field>
</template>
</vridge-dialog>
</div>
<div v-if="!loading && guide && guide.content" v-html="$md.render(guide.content)" style="padding-bottom:200px;"></div>
</v-row>
<v-divider class="my-8"/>
<v-row no-gutters align="center">
<v-btn class="primary--text" :ripple="false" height="64" text outlined @click="openGuide(guide.prevContent)" v-if="guide !== null && guide.prevContent">
<v-row no-gutters align="center">
<v-col class="mr-8">
<v-icon left>mdi-chevron-left</v-icon>
</v-col>
<v-col align="end" justify="end">
<span class="grey--text">이전 페이지</span> <br/>
<span style="font-size:1.1rem;font-weight:bold;">
{{$t('guide_title_' + parsingContentTitle(guide.prevContent))}}
</span>
</v-col>
</v-row>
</v-btn>
<v-skeleton-loader
v-else-if="guide === null"
elevation="0"
type="button"
></v-skeleton-loader>
<v-spacer/>
<v-btn class="primary--text" :ripple="false" height="64" text outlined @click="openGuide(guide.nextContent)" v-if="guide !== null && guide.nextContent">
<v-row no-gutters align="center">
<v-col align="start" justify="start">
<span class="grey--text">다음 페이지</span> <br/>
<span style="font-size:1.1rem;font-weight:bold;">
{{$t('guide_title_' + parsingContentTitle(guide.nextContent))}}
</span>
</v-col>
<v-col class="ml-8">
<v-icon right>mdi-chevron-right</v-icon>
</v-col>
</v-row>
</v-btn>
<v-skeleton-loader
v-else-if="guide === null"
elevation="0"
type="button"
></v-skeleton-loader>
</v-row>
</v-container>
<!-- <v-container v-if="$store.state.editMode" fluid>
<v-row no-gutters v-if="guide !== null" align="center">
<v-text-field filled :maxlength="50" hide-details solo label="title" v-model="guide.title" class="no-shadow font-weight-bold px-4">
</v-text-field>
<v-spacer/>
<span class="grey--text" style="font-size:1rem; " v-if="!loading && guide !== null">
updated: <span class="mx-2">{{$time.toLocalDateTimeString(guide.updateDate)}}</span>
</span>
<v-btn text @click="$store.state.editMode = false">취소</v-btn>
<v-btn class="mr-4" color="primary" @click="save">저장</v-btn>
</v-row>
<v-row no-gutters >
<v-card v-if="guide" elevation="0" style="width:100%; height:100%;">
<v-card-text>
<VueEditor v-if="guide.content" v-model="guide.content" class="editor-content" />
</v-card-text>
</v-card>
</v-row>
</v-container> -->
</div>
</template>
<script>
export default {
layout:'guide',
data:() => ({
guide:null,
loading: true,
newKeyword:'',
}),
methods:{
async getGuide(){
this.loading = true
await this.$axios.get('/guide',{
params:{
guideId: this.$store.state.guideId
}
})
.then(res=>{
this.guide = res.data.body
})
.catch(err=>{
console.log(err)
})
await this.getGuideContents(this.guide.contentKey)
this.loading = false
this.generateNavitator()
},
async getGuideContents(contentKey) {
await this.$axios.get("https://api.github.com/repos/vazilcompany/vridge-docs/contents/guide/ko/" + contentKey,{
headers:{
Authorization: 'token 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'
}
})
.then(res=>{
let content = this.b64DecodeUnicode(res.data.content)
this.guide.content = content
this.parsingContentTitle(this.guide)
})
.catch(err=>{
this.guide.content = ''
console.log(err)
})
},
parsingContentTitle(content) {
let keyArray = content.contentKey.replace('.md','').split('/')
let titleKey = ''
if(keyArray[0] !== keyArray[1]) {
for(let i = 0; i < keyArray.length - 1; i++) {
titleKey += keyArray[i] + '_'
}
titleKey += content.title
} else {
titleKey = content.title
}
return titleKey
},
getBreadcrumbs(content){
let items = []
let keyArray = content.contentKey.replace('.md','').split('/')
let titleKey = 'guide_title'
if(keyArray[0] !== keyArray[1]) {
for(let i = 0; i < keyArray.length; i++) {
titleKey += '_' + keyArray[i]
items.push({
text: this.$t(titleKey),
href: titleKey,
content: content
})
}
} else {
titleKey += '_' + keyArray[0]
items.push({
text: this.$t(titleKey),
href: titleKey,
content: content
})
}
return items
},
b64DecodeUnicode(str) {
// Going backwards: from bytestream, to percent-encoding, to original string.
return decodeURIComponent(atob(str).split('').map(function(c) {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
}).join(''));
},
async save(){
await this.$axios.put("/guide", this.guide)
.then(res =>{
})
.catch(err=>{
console.log(err)
})
},
remove(){
this.$axios.delete("/guide", {
guideId: this.$store.state.guideId,
cascade:false
})
.then(res => {
this.$router.push('/docs')
})
.catch(err => {
console.log(err)
})
},
async addKeyword() {
if(this.newKeyword) {
if(!this.guide.keywordSet)
this.guide.keywordSet = []
this.guide.keywordSet.push(this.newKeyword)
await this.$axios.put('/guide/keyword', this.guide)
.then(res=>{
this.$snotify.success('키워드 추가 성공')
})
.catch(err=>{
console.log(err)
})
this.newKeyword = ''
}
},
removeKeyword(keyword) {
this.guide.keywordSet.splice(this.guide.keywordSet.indexOf(keyword), 1)
this.$axios.put('/guide/keyword', this.guide)
.then(res=>{
this.$snotify.success('키워드 저장 성공')
})
.catch(err=>{
console.log(err)
})
},
openGuide(guide) {
this.$router.push('/docs/' + this.parsingContentTitle(guide))
this.$store.commit('setGuide', {guideId:guide.id, flag:true})
this.$store.state.guideId = guide.id
},
openGithub(contentKey) {
window.open('https://github.com/vazilcompany/vridge-docs/blob/main/guide/ko/' + contentKey, "_blank")
},
isActiveIndex(index) {
// return index >= 0 && index < this.$store.state.guidePaths.length
return false
},
async generateNavitator() {
this.loading = true
let newNavigator = []
setTimeout(()=>{
for(let elem of document.getElementsByTagName('h2')) {
let nav = {
tag: elem.nodeName,
html: elem.innerHTML,
position: Math.ceil(elem.offsetTop),
element: elem
}
newNavigator.push(nav)
}
this.$store.commit('setGuideNavigator', newNavigator)
}, 300)
this.loading = false
},
},
mounted(){
if(this.$store.state.guideId)
this.getGuide();
},
watch:{
}
}
</script>
<style lang="scss">
.quillWrapper{
border:none !important;
}
.ql-toolbar{
border:none !important;
}
.ql-container{
border:none !important;
height: calc(100vh - 280px) !important;
}
.ql-editor{
border:none !important;
}
</style>
<template>
<guide-board-editor
:board="board"
>
</guide-board-editor>
</template>
<script>
export default {
layout:'guide',
meta:{
title:'가이드 편집',
},
components: {
},
data:()=>({
board:null
}),
mounted(){
this.getBoard()
},
methods:{
getBoard(){
this.board.title = this.$route.params.guideSub
this.board.content = this.$store.state.guidSubList.contents
},
}
};
</script>
\ No newline at end of file
<template>
<div class="guide-wrap-container">
<v-container class="guide-wrap">
<v-row no-gutters align="center" justify="center" class="guide-wrap-header">
<h1 class="font-weight-bold">서비스 가이드</h1>
</v-row>
<v-row no-gutters class="guide-content">
<v-col cols="12" align="center" justify="center" class="mt-8">
브릿지 AI 플랫폼에서 제공하는 서비스를 사용하는 방법을 설명합니다.
</v-col>
<v-card style="width:100%;" flat color="transparent" class="px-4">
<v-divider class="my-12"/>
<v-col cols="12" class="my-4"><h3 class="font-weight-bold">목차</h3></v-col>
<v-treeview :items="guideIndex" item-text="title">
<template v-slot:label="{ item }">
<a @click="openGuide(item)">{{$t('guide_title_' + parsingContentTitle(item))}}</a>
</template>
</v-treeview>
</v-card>
</v-row>
</v-container>
</div>
</template>
<script>
export default {
layout:'guide',
data:() => ({
guideIndex: []
}),
methods:{
getGuideIndex() {
this.$store.state.guideId = null
this.$store.commit('setGuide', {guideId:null, flag:false})
this.$axios.get('/docs/index')
.then(res => {
this.guideIndex = res.data.body
})
.catch(err => {
console.log(err)
})
},
openGuide(guide) {
this.$router.push('/docs/' + this.parsingContentTitle(guide))
this.$store.commit('setGuide', {guideId:guide.id, flag:true})
this.$store.state.guideId = guide.id
},
parsingContentTitle(content) {
let keyArray = content.contentKey.replace('.md','').split('/')
let titleKey = ''
if(keyArray[0] !== keyArray[1]) {
for(let i = 0; i < keyArray.length - 1; i++) {
titleKey += keyArray[i] + '_'
}
titleKey += content.title
} else {
titleKey = content.title
}
return titleKey
},
},
mounted(){
this.getGuideIndex()
},
watch:{
}
}
</script>
\ No newline at end of file
<template>
<div>
<v-container class="guide-wrap">
<v-row no-gutters align="center">
<v-skeleton-loader
elevation="0"
type="heading"
v-if="loading"
style="width:100%;"
></v-skeleton-loader>
<h1 v-else class="font-weight-bold primary--text">'{{keyword}}'</h1> 키워드에 대한 검색 결과입니다.
</v-row>
<v-row no-gutters class="mt-8">
<v-progress-circular
size="64"
v-if="loading"
indeterminate
></v-progress-circular>
<div v-else>
<v-col cols="12">
<h3><span class="primary--text">{{documents.length}}</span> 건의 문서가 검색되었습니다.</h3>
</v-col>
<v-simple-table dense class="history-table mt-4" :key="tableKey">
<template v-slot:default>
<thead>
<tr>
<th class="text-center" style="min-width:200px;">
메뉴
</th>
<th class="text-center" style="min-width:200px;">
문서
</th>
</tr>
</thead>
<tbody>
<tr
v-for="item in documents"
:key="item.id"
class="text-center"
>
<td>{{ $t('guide_title_' + parsingContentTitle(item))}}</td>
<td><a @click="openGuide(item)">{{ $t('guide_title_' + parsingContentTitle(item)) }}</a></td>
</tr>
</tbody>
</template>
</v-simple-table>
</div>
</v-row>
</v-container>
</div>
</template>
<script>
export default {
layout:'guide',
meta:{
title:'가이드',
},
components: {
},
data:()=>({
keyword: '',
loading:false,
documents:[],
tableKey:0,
}),
mounted(){
this.readKeyword(this.$route.query['keyword'])
},
methods:{
async readKeyword(keyword){
this.loading = true
if(keyword) {
this.keyword = keyword
}
this.documents = []
await this.$axios.get('/docs/search',{
params:{
keyword: this.keyword
}
})
.then(res => {
this.documents = res.data.body
})
.catch(err => {
console.log(err)
})
this.tableKey++
this.loading = false
},
openGuide(guide) {
this.$router.push('/docs/' + this.parsingContentTitle(guide))
this.$store.commit('setGuide', {guideId:guide.id, flag:true})
this.$store.state.guideId = guide.id
},
parsingContentTitle(content) {
let keyArray = content.contentKey.replace('.md','').split('/')
let titleKey = ''
if(keyArray[0] !== keyArray[1]) {
for(let i = 0; i < keyArray.length - 1; i++) {
titleKey += keyArray[i] + '_'
}
titleKey += content.title
} else {
titleKey = content.title
}
return titleKey
},
},
watch:{
'$route.query'() {
this.readKeyword(this.$route.query['keyword'])
}
}
};
</script>
\ No newline at end of file
import Vue from 'vue'
const time = {
toLocalDateTimeString(dateString) {
try{
if(typeof dateString !== 'string') {
let date = new Date(dateString[0], (dateString[1] - 1), dateString[2], dateString[3], dateString[4], dateString[5]);
date.setHours(date.getHours() + 9);
return date.toISOString().replace('T', ' ').substring(0, 19);
} else {
let date = new Date(dateString);
date.setHours(date.getHours() + 9);
return date.toISOString().replace('T', ' ').substring(0, 19);
}
}catch{
return ''
}
},
getTimeFromNow(date) {
const now = new Date();
const today = now
const timeValue = new Date(date);
const betweenTime = Math.floor((today.getTime() - timeValue.getTime()) / 1000 / 60);
if (betweenTime < 1) return '방금전';
if (betweenTime < 60) {
return `${betweenTime}분전`;
}
const betweenTimeHour = Math.floor(betweenTime / 60);
if (betweenTimeHour < 24) {
return `${betweenTimeHour}시간전`;
}
const betweenTimeDay = Math.floor(betweenTime / 60 / 24);
if (betweenTimeDay < 365) {
return `${betweenTimeDay}일전`;
}
return `${Math.floor(betweenTimeDay / 365)}년전`;
},
getTimeFromAnotherTime(targetDate, baseDate) {
const now = new Date(baseDate);
const today = now
const timeValue = new Date(targetDate);
const betweenTime = Math.floor((today.getTime() - timeValue.getTime()) / 1000 / 60);
if (betweenTime < 1) {
return `${betweenTime * 60}초`;
}
if (betweenTime < 60) {
return `${betweenTime}분`;
}
const betweenTimeHour = Math.floor(betweenTime / 60);
if (betweenTimeHour < 24) {
return `${betweenTimeHour}시간`;
}
const betweenTimeDay = Math.floor(betweenTime / 60 / 24);
if (betweenTimeDay < 365) {
return `${betweenTimeDay}일`;
}
return `${Math.floor(betweenTimeDay / 365)}년`;
},
getElapsedTimeFromSecond(second){
let min = Math.floor(second/60);
let hour = Math.floor(min/60);
let sec = second%60;
min = min%60;
let th = hour;
let tm = min;
let ts = sec;
if(th<10){
th = "0" + hour;
}
if(tm < 10){
tm = "0" + min;
}
if(ts < 10){
ts = "0" + sec;
}
return th + ":" + tm + ":" + ts
},
dateToFormatKorean (date) {
if(typeof date !== 'string') {
return date[0] + '년 ' + date[1] + '월 ' + date[2] +'일'
} else {
let dateString = date.substring(0,10)
return dateString.substring(0,4) + '년 ' + dateString.substring(5,7) + '월 ' + dateString.substring(8) +'일'
}
},
dateTimeToFormatKorean (date) {
if(typeof date !== 'string') {
date = this.convertDateArrayToString(date)
}
var week = ['일', '월', '화', '수', '목', '금', '토'];
var dayOfWeek = week[new Date(date).getDay()];
return date.substring(0,4) + '년 ' + date.substring(5,7) + '월 ' + date.substring(8,10) +'일 ' + dayOfWeek + '요일 ' + date.substring(11,19)
},
dateShotToFormatKorean (date) {
if(typeof date !== 'string') {
return date[0] + '.' + date[1] + '.' + date[2]
} else {
let dateString = date.substring(0,10)
return dateString.substring(0,4) + '.' + dateString.substring(5,7) + '.' + dateString.substring(8)
}
},
simpleFormat(date){
let data = new Date(date)
return data.toLocaleDateString();
},
convertDateArrayToString(dateArray) {
try{
let date = dateArray[0] + "-" + this.addTimeBlank(dateArray[1]) + "-" + this.addTimeBlank(dateArray[2]) + "T" + this.addTimeBlank(dateArray[3])+":" + this.addTimeBlank(dateArray[4]) + ":" + this.addTimeBlank(dateArray[5])
this.addTimeBlank(dateArray[3])
return date
}catch{
return new Date().toLocaleTimeString()
}
},
addTimeBlank(time) {
return time.toString().length === 1 ? '0' + time : time.toString()
}
}
const utils = {
// 소수점2자리까지의 바이트 단위로 변환시키는 함수
convertByteToString(bytes, decimals = 2) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const dm = decimals < 0 ? 0 : decimals;
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
},
checkViewMode(level, vuetify) {
let viewMode = ['xs', 'sm', 'md', 'lg', 'xl']
if(viewMode.slice(0,level).indexOf(vuetify.breakpoint.name) !== -1) {
return false
} else {
return true
}
},
btoa(content) {
return window.btoa(content)
},
btoaEscape(content) {
return window.btoa(encodeURIComponent(content).replace(/%([0-9A-F]{2})/g,function(match,p1){return String.fromCharCode('0x'+p1)}))
},
// 이미지파일과 어노테이션 파일을 Zip으로 압축해서 다운받는 함수
async downloadDatasetZip(files, name){
let zip = new JSZip()
return new Promise((resolve, reject) => {
for (let i = 0; i < files.length; i++) {
let filename = files[i].filename
let folder = files[i].folder
zip.file(folder+"/"+filename, files[i].image,{ base64: true, createFolders: true })
filename = filename.substring(0,filename.indexOf("."))
zip.file(folder+"/"+filename + '.xml', atob(files[i].annotation))
}
zip.generateAsync({
type: "blob"
}).then((content) => {
FileSaver.saveAs(content, name + "_dataset_" + Date.now() + ".zip")
resolve()
})
})
},
getSizeOfImageFile(file) {
return new Promise((resolve, reject)=> {
let img= new Image();
let src = URL.createObjectURL(file);
let size = {
width:0,
height:0
}
img.src = src
img.onload = function () {
size.width = this.width
size.height = this.height
resolve(size)
};
img.onerror = reject
})
},
// 클립보드 저장 함수
copy(val, callback = null) {
if(val) {
if(navigator.clipboard) {
navigator.clipboard.writeText(val).then(() => {
})
} else {
const t = document.createElement("textarea");
document.body.appendChild(t);
t.value = val;
t.select();
document.execCommand('copy');
document.body.removeChild(t);
}
if(callback !== null)
callback()
}
},
isNumeric(num, opt){
// 좌우 trim(공백제거)을 해준다.
num = String(num).replace(/^\s+|\s+$/g, "");
if(typeof opt == "undefined" || opt == "1"){
// 모든 10진수 (부호 선택, 자릿수구분기호 선택, 소수점 선택)
var regex = /^[+\-]?(([1-9][0-9]{0,2}(,[0-9]{3})*)|[0-9]+){1}(\.[0-9]+)?$/g;
}else if(opt == "2"){
// 부호 미사용, 자릿수구분기호 선택, 소수점 선택
var regex = /^(([1-9][0-9]{0,2}(,[0-9]{3})*)|[0-9]+){1}(\.[0-9]+)?$/g;
}else if(opt == "3"){
// 부호 미사용, 자릿수구분기호 미사용, 소수점 선택
var regex = /^[0-9]+(\.[0-9]+)?$/g;
}else{
// only 숫자만(부호 미사용, 자릿수구분기호 미사용, 소수점 미사용)
var regex = /^[0-9]$/g;
}
if( regex.test(num) ){
num = num.replace(/,/g, "");
return isNaN(num) ? false : true;
}else{ return false; }
}
}
const properties = {
getImageCdnUrl(key, w, h, q) {
let f = key.split('.').pop() === 'png' ? 'webp' : ''
let url = 'https://d2q9yzkd471o7j.cloudfront.net/' + key + '?w=' + w + '&h=' + h + '&f=' + f + '&q=' + q
return url
},
getAvatar(key, w, h, q) {
let defaultAvatar = []
for(let i=1; i<=8; ++i){
for(let j=1; j<=3; ++j){
defaultAvatar.push(`/profileAvatar/${i}-${j}.png`)
}
}
for(let i=1; i<=6; ++i){
defaultAvatar.push(`/profileSmaple${i}.png`)
}
if(typeof key === 'undefined' || key === null) {
return '/default-image.png'
}else if(defaultAvatar.indexOf(key) !== -1) {
return key
}else{
return this.getImageCdnUrl(key, w, h, q)
}
},
getProjectKey(orgId, projectId, projectType) {
return btoa(orgId + '/' + projectType.toLowerCase() + '/' + projectId)
},
// auth = $auth
getUserInfo(auth) {
let user = auth.user.body
return {
id: user.id,
name: user.name,
email: user.email,
avatar: user.avatar
}
},
getUserInfoBase64(auth) {
return Buffer.from(JSON.stringify(this.getUserInfo(auth))).toString("base64")
},
// 공개용(데모용) 조직인지 확인
isPublicOrg(orgId) {
const publicOrgsId = ['6c34d2b00b604a689953cb0d5bd4b1cf','o-84e97db78ba14166ae523aa2283a378f']
return orgId && publicOrgsId.indexOf(orgId) !== -1
}
}
const regular = {
basic(data){
let reg=/[\{\}\[\]\/?.,;:|\)*~`!^\-_+<>@\#$%&\\\=\(\'\"]/gi;
return reg.test(data)
},
noBlank(data){ // 공백 없을 경우 true 반환
let reg = /[\s]/g;
return !reg.test(data)
},
passwordPatten(data){
//영문,숫자,특수문자(!@$%^&* 만 허용) 9 이상
let reg= /^(?=.*[A-Za-z])(?=.*\d)(?=.*[$@$!%*#?&])[A-Za-z\d$@$!%*#?&]{9,20}$/g
return reg.test(data)
},
emailPatten(data){
let reg = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
return reg.test(data)
},
name(data){
let reg = /^[ㄱ-ㅎ가-힣a-zA-Z\s]+$/
return reg.test(data)
},
projName(data){ // 한글,영문,숫자,공백만 입력되었는지 TEST
let reg = /^[ㄱ-ㅎ가-힣a-zA-Z0-9\s]+$/
return reg.test(data)
},
projDescription(data){ // 한글,영문,숫자,공백,특수문자만 입력되었는지 TEST
let reg = /^[ㄱ-ㅎ가-힣a-zA-Z0-9\s!?@#$%^&*():;+-=~{}<>\_\[\]\|\\\"\'\,\.\/\`\₩]+$/
return reg.test(data)
},
number(data){
let reg = /^[0-9]/
return reg.test(data)
},
phone(data) {
let reg = /^[+]*[(]{0,1}[0-9]{1,3}[)]{0,1}[-\s\./0-9]*$/g
return reg.test(data)
}
}
const rules = {
title: [
v => v.length > 0 || '값을 입력해주세요.',
v => v.length <= 20 || '20자 초과되었습니다.',
v => regular.noBlank(v[0]) || '공백으로 시작할 수 없습니다.',
v => v === "" || regular.projName(v) || '한글,영문,숫자 외의 문자가 입력되었습니다.'
],
description: [
v => v.length <= 100 || '100자 초과되었습니다.',
v => v === "" || regular.projDescription(v) || '적절하지 않은 문자가 입력되었습니다.'
],
}
const env = {
getPaymentCallbackUrl(){
if(typeof process.env.PAYMENT_CALLBACK_URL === 'undefined'){
return 'http://localhost:3000/console/admin/pay'
} else {
return process.env.PAYMENT_CALLBACK_URL
}
}
}
const lang = {
getString(key){
let la = navigator.language || navigator.userLanguage
if(la.indexOf('en')!==-1) {
return dictionary['en'][key]
} else {
return dictionary['ko'][key]
}
}
}
const dictionary = {
ko:{
dashboard: '대시보드',
alert_save:'성공적으로 저장되었습니다.',
},
en:{
dashboard: 'dashboard',
alert_save:'It has been saved successfully.',
}
}
const move = {
async setOrganizationAndProject(project, axios, store) {
// 프로젝트 참여 여부 확인
const isParticipated = await axios.get('/auth/project/user-list',{
params:{
userId: store.state.auth.user.body.id
}
})
.then(res=>{
const projectList = res.data.body
if(projectList.findIndex(item => item.id === project.id) > -1){
return true
} else {
return false
}
})
.catch(err=>{
console.log('프로젝트 참여 여부 확인 중 에러발생 err : ', err)
})
if(isParticipated){
if(project.organizationId !== store.state.organizationId) {
axios.defaults.headers.common['orgId'] = project.organizationId
store.commit('setOrganizationId', project.organizationId)
store.commit('setKeepOrganizationId', project.organizationId)
}
axios.defaults.headers.common['projectId'] = project.id
store.commit('setProject', project)
} else {
alert("프로젝트 접근 권한이 없습니다.")
}
return isParticipated
},
async goOrg(org, axios, store, router) {
store.state.organization = org
axios.defaults.headers.common['orgId'] = org.orgId
store.commit('setOrganizationId', org.orgId)
store.commit('setKeepOrganizationId', org.orgId)
router.push('/console')
},
async goProject(project, axios, store, router) {
if(await this.setOrganizationAndProject(project,axios,store))
router.push({path:'/console/project/'+ project.id})
},
async goProjectById(projectId, axios, store, router) {
let project = null
await axios.get('/auth/project/' + projectId, {
params:{
m: true
}
})
.then(res=> {
project= res.data.body
})
if(await this.setOrganizationAndProject(project,axios,store))
router.push({path:'/console/project/'+ project.id})
},
async goProjectModel(project, model, axios, store, router) {
if(await this.setOrganizationAndProject(project,axios,store))
router.push({path:'/console/project/'+ project.id +'/models/' + model.versionId})
},
async goProjectWebPoint(project, point, axios, store, router) {
if(await this.setOrganizationAndProject(project,axios,store))
router.push({path:'/console/project/'+ project.id +'/webpoint/' + point.id})
},
async goProjectEdgePoint(project, point, axios, store, router) {
if(await this.setOrganizationAndProject(project,axios,store) ){
router.push({path:'/console/project/' + project.id + '/edges/' + point.id})
}
},
async goProjectCallback(projectId, axios, store, callback) {
let project = null
await axios.get('/auth/project/' + projectId, {
params:{
m: true
}
})
.then(res=> {
project= res.data.body
})
if(await this.setOrganizationAndProject(project,axios,store)){
if(callback) {
callback()
}
}
},
}
const colors = {
colToHex(c) {
let color = (c < 75) ? c + 75 : c
let hex = color.toString(16);
return hex.length == 1 ? "0" + hex : hex;
},
rgbToHex(r,g,b) {
return "#" + this.colToHex(r) + this.colToHex(g) + this.colToHex(b);
},
getRandomColor() {
return this.rgbToHex(
Math.floor(Math.random() * 255),
Math.floor(Math.random() * 255),
Math.floor(Math.random() * 255));
},
getRandomColorByEmail(email) {
return this.rgbToHex(
Math.floor((email.length * email.length * email.charCodeAt(0)) % 255),
Math.floor((email.length * email.length * email.charCodeAt(1)) % 255),
Math.floor((email.length * email.length * email.charCodeAt(2)) % 255));
},
getSplitColor(i) {
if(i === 1 || i === 'VALID') {
return '#2E7D32'
} else if (i === 2 || i === 'TEST') {
return '#9E9D24'
} else {
return '#01579B'
}
},
}
Vue.prototype.$time = time;
Vue.prototype.$utils = utils;
Vue.prototype.$regular = regular;
Vue.prototype.$rules = rules;
Vue.prototype.$lang = lang;
Vue.prototype.$properties = properties;
Vue.prototype.$colors = colors;
Vue.prototype.$move = move;
export default ({app}, inject) => {
inject('time', time);
inject('utils', utils);
inject('regular', regular);
inject('rules', rules);
inject('env', env);
inject('lang', lang);
inject('properties', properties);
inject('colors', colors);
inject('move', move);
}
import createPersistedState from 'vuex-persistedstate'
export default ({store}) => {
createPersistedState({
})(store)
}
\ No newline at end of file
# STATIC
**This directory is not required, you can delete it if you don't want to use it.**
This directory contains your static files.
Each file inside this directory is mapped to `/`.
Thus you'd want to delete this README.md before deploying to production.
Example: `/static/robots.txt` is mapped as `/robots.txt`.
More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/assets#static).
# STORE
**This directory is not required, you can delete it if you don't want to use it.**
This directory contains your Vuex Store files.
Vuex Store option is implemented in the Nuxt.js framework.
Creating a file in this directory automatically activates the option in the framework.
More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/vuex-store).
import Vue from 'vue'
import Vuex from 'vuex';
Vue.use(Vuex)
//엄격모드 비활성화
export const strict = false
const store = () => new Vuex.Store({
state:{
editMode:false,
guideId: null,
guideNavigator: [],
theme:false
},
mutations:{
setTheme: (state, theme) => {
state.theme = theme
},
setGuide: (state, {guideId, flag}) => {
state.guideId = guideId
if(flag) {
state.guideTreeFlag = !state.guideTreeFlag
}
},
setGuideNavigator: (state, guideNavigator) => {
state.guideNavigator = guideNavigator
}
},
actions:{
},
getters: {
}
})
export default store
\ No newline at end of file
db.createUser(
{
user: "vridge-admin",
pwd: "vazil523320!",
roles: [
{
role: ["readWrite", "userAdmin"],
db: "vridge-docs"
}
]
}
);
\ No newline at end of file
user nginx;
worker_processes 1;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
upstream docs-back {
server docs-back:5000;
}
upstream docs-front {
server docs-front:3000;
}
server {
listen 80;
client_max_body_size 500M;
server_name vridge-api;
location / {
proxy_pass http://docs-front;
proxy_redirect off;
}
location /api {
proxy_pass http://docs-back;
proxy_redirect off;
}
}
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
keepalive_timeout 65;
#include /etc/nginx/conf.d/*.conf;
}
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<annotationProcessing>
<profile name="Gradle Imported" enabled="true">
<outputRelativeToContentRoot value="true" />
<processorPath useClasspath="false">
<entry name="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.projectlombok/lombok/1.18.24/13a394eed5c4f9efb2a6d956e2086f1d81e857d9/lombok-1.18.24.jar" />
</processorPath>
<module name="docs.main" />
</profile>
</annotationProcessing>
<bytecodeTargetLevel target="11" />
</component>
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="FrameworkDetectionExcludesConfiguration">
<file type="web" url="file://$PROJECT_DIR$/vridge-docs" />
</component>
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$/vridge-docs" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$/vridge-docs" />
</set>
</option>
</GradleProjectSettings>
</option>
</component>
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
</profile>
<version value="1.0" />
</component>
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/vridge-docs/docs.iml" filepath="$PROJECT_DIR$/vridge-docs/docs.iml" />
<module fileurl="file://$PROJECT_DIR$/vridge-docs/docs.main.iml" filepath="$PROJECT_DIR$/vridge-docs/docs.main.iml" />
<module fileurl="file://$PROJECT_DIR$/vridge-docs/docs.test.iml" filepath="$PROJECT_DIR$/vridge-docs/docs.test.iml" />
</modules>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="11" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
<component name="RemoteRepositoriesConfiguration">
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Maven Central repository" />
<option name="url" value="https://repo1.maven.org/maven2" />
</remote-repository>
<remote-repository>
<option name="id" value="jboss.community" />
<option name="name" value="JBoss Community repository" />
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
</remote-repository>
<remote-repository>
<option name="id" value="MavenRepo" />
<option name="name" value="MavenRepo" />
<option name="url" value="https://repo.maven.apache.org/maven2/" />
</remote-repository>
</component>
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
</component>
<component name="libraryTable">
<library name="Gradle: ch.qos.logback:logback-classic:1.2.11">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/ch.qos.logback/logback-classic/1.2.11/4741689214e9d1e8408b206506cbe76d1c6a7d60/logback-classic-1.2.11.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/ch.qos.logback/logback-classic/1.2.11/c6d3537d4af04b787f2fc15c52215df03ce66098/logback-classic-1.2.11-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: ch.qos.logback:logback-core:1.2.11">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/ch.qos.logback/logback-core/1.2.11/a01230df5ca5c34540cdaa3ad5efb012f1f1f792/logback-core-1.2.11.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/ch.qos.logback/logback-core/1.2.11/35e61a57db9f8567acd01d1b6a671f21f867d1e9/logback-core-1.2.11-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: com.fasterxml.jackson.core:jackson-annotations:2.13.4">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.core/jackson-annotations/2.13.4/858c6cc78e1f08a885b1613e1d817c829df70a6e/jackson-annotations-2.13.4.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.core/jackson-annotations/2.13.4/f6b8a8daa9bcf7ece61c673e53f5d00b9e92e70e/jackson-annotations-2.13.4-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: com.fasterxml.jackson.core:jackson-core:2.13.4">
<ANNOTATIONS>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/externalAnnotations/com/fasterxml/jackson/core/jackson-core/2.9.6-an1/jackson-core-2.9.6-an1-annotations.zip!/" />
</ANNOTATIONS>
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.core/jackson-core/2.13.4/cf934c681294b97ef6d80082faeefbe1edadf56/jackson-core-2.13.4.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.core/jackson-core/2.13.4/dd8da2766363ea64383d1344349acce91d5a5469/jackson-core-2.13.4-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: com.fasterxml.jackson.core:jackson-databind:2.13.4.2">
<ANNOTATIONS>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/externalAnnotations/com/fasterxml/jackson/core/jackson-databind/2.9.6-an1/jackson-databind-2.9.6-an1-annotations.zip!/" />
</ANNOTATIONS>
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.core/jackson-databind/2.13.4.2/325c06bdfeb628cfb80ebaaf1a26cc1eb558a585/jackson-databind-2.13.4.2.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.core/jackson-databind/2.13.4.2/99771ed18f5059ca6b4fb51142d7aa4a2eba9002/jackson-databind-2.13.4.2-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.13.4">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.datatype/jackson-datatype-jdk8/2.13.4/557dbba5d8dfc7b7f944c58fe084109afcb5670b/jackson-datatype-jdk8-2.13.4.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.datatype/jackson-datatype-jdk8/2.13.4/4cf0dea5221013dcffa6e4c659488fef60e38141/jackson-datatype-jdk8-2.13.4-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.13.4">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.datatype/jackson-datatype-jsr310/2.13.4/e6d820112871f33cd94a1dcc54eef58874753b5/jackson-datatype-jsr310-2.13.4.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.datatype/jackson-datatype-jsr310/2.13.4/5624a1fd372ce2b69b9ce25fd4aab4c7aa041811/jackson-datatype-jsr310-2.13.4-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: com.fasterxml.jackson.module:jackson-module-parameter-names:2.13.4">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.module/jackson-module-parameter-names/2.13.4/858ccf6624b5fac6044813e845063edb6a62cf37/jackson-module-parameter-names-2.13.4.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.module/jackson-module-parameter-names/2.13.4/436f808013c552acb57385765d6dc4cb40b1f178/jackson-module-parameter-names-2.13.4-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: com.jayway.jsonpath:json-path:2.7.0">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.jayway.jsonpath/json-path/2.7.0/f9d7d9659f2694e61142046ff8a216c047f263e8/json-path-2.7.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.jayway.jsonpath/json-path/2.7.0/f98abbc2176cd35716cbad60d7f1d4c97ac89dd1/json-path-2.7.0-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: com.vaadin.external.google:android-json:0.0.20131108.vaadin1">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.vaadin.external.google/android-json/0.0.20131108.vaadin1/fa26d351fe62a6a17f5cda1287c1c6110dec413f/android-json-0.0.20131108.vaadin1.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.vaadin.external.google/android-json/0.0.20131108.vaadin1/bf42d7e47a3228513b626dd7d37ac6f072aeca4f/android-json-0.0.20131108.vaadin1-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: jakarta.activation:jakarta.activation-api:1.2.2">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/jakarta.activation/jakarta.activation-api/1.2.2/99f53adba383cb1bf7c3862844488574b559621f/jakarta.activation-api-1.2.2.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/jakarta.activation/jakarta.activation-api/1.2.2/c45b5962230be8a5f93759203870c98917bb8b31/jakarta.activation-api-1.2.2-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: jakarta.annotation:jakarta.annotation-api:1.3.5">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/jakarta.annotation/jakarta.annotation-api/1.3.5/59eb84ee0d616332ff44aba065f3888cf002cd2d/jakarta.annotation-api-1.3.5.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/jakarta.annotation/jakarta.annotation-api/1.3.5/1ad35f11d17abb52426bfe15ea7b4c583795012/jakarta.annotation-api-1.3.5-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: jakarta.xml.bind:jakarta.xml.bind-api:2.3.3">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/jakarta.xml.bind/jakarta.xml.bind-api/2.3.3/48e3b9cfc10752fba3521d6511f4165bea951801/jakarta.xml.bind-api-2.3.3.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/jakarta.xml.bind/jakarta.xml.bind-api/2.3.3/d83744bae211a4072c39f007000a13f501a88395/jakarta.xml.bind-api-2.3.3-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: net.bytebuddy:byte-buddy-agent:1.12.22">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/net.bytebuddy/byte-buddy-agent/1.12.22/9c4127080df12304336ca90c2ef3f8b7d72915c1/byte-buddy-agent-1.12.22.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/net.bytebuddy/byte-buddy-agent/1.12.22/acce797bc3d23c01050bbecfa90f92cb4390d886/byte-buddy-agent-1.12.22-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: net.bytebuddy:byte-buddy:1.12.22">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/net.bytebuddy/byte-buddy/1.12.22/984e536b4f3fb668b21f15b90c1e8704292d4bdd/byte-buddy-1.12.22.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/net.bytebuddy/byte-buddy/1.12.22/55bd1be786c3a8a0c66b4f2e4a82716abe881121/byte-buddy-1.12.22-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: net.minidev:accessors-smart:2.4.8">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/net.minidev/accessors-smart/2.4.8/6e1bee5a530caba91893604d6ab41d0edcecca9a/accessors-smart-2.4.8.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/net.minidev/accessors-smart/2.4.8/f8801f70a860e4e3a9485f9923dde4204cb70c96/accessors-smart-2.4.8-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: net.minidev:json-smart:2.4.8">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/net.minidev/json-smart/2.4.8/7c62f5f72ab05eb54d40e2abf0360a2fe9ea477f/json-smart-2.4.8.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/net.minidev/json-smart/2.4.8/399ee58414b3686be3d4f3b59f6bc11d30d2309e/json-smart-2.4.8-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.apache.logging.log4j:log4j-api:2.17.2">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.apache.logging.log4j/log4j-api/2.17.2/f42d6afa111b4dec5d2aea0fe2197240749a4ea6/log4j-api-2.17.2.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.apache.logging.log4j/log4j-api/2.17.2/91a89f75de4f567c38dc7ec9f83f2a1afd2c2915/log4j-api-2.17.2-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.apache.logging.log4j:log4j-to-slf4j:2.17.2">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.apache.logging.log4j/log4j-to-slf4j/2.17.2/17dd0fae2747d9a28c67bc9534108823d2376b46/log4j-to-slf4j-2.17.2.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.apache.logging.log4j/log4j-to-slf4j/2.17.2/c5e9730fac0589670eb001946c9986bbd22bc3b9/log4j-to-slf4j-2.17.2-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.apache.tomcat.embed:tomcat-embed-core:9.0.71">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.apache.tomcat.embed/tomcat-embed-core/9.0.71/adaed61b4eaa5b52448336c0881fcd828fd51a2f/tomcat-embed-core-9.0.71.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.apache.tomcat.embed/tomcat-embed-core/9.0.71/2ec6df0f12a3b51d0d7b21dc9a0be5ced9945f5b/tomcat-embed-core-9.0.71-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.apache.tomcat.embed:tomcat-embed-el:9.0.71">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.apache.tomcat.embed/tomcat-embed-el/9.0.71/8fe43848c27ec921c8c5d6dcbd8b959076d7bf99/tomcat-embed-el-9.0.71.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.apache.tomcat.embed/tomcat-embed-el/9.0.71/4f5242587726872581f004ddd8a49dc86911df20/tomcat-embed-el-9.0.71-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.apache.tomcat.embed:tomcat-embed-websocket:9.0.71">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.apache.tomcat.embed/tomcat-embed-websocket/9.0.71/987b6460af04b08bc9914788d2762080afb09541/tomcat-embed-websocket-9.0.71.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.apache.tomcat.embed/tomcat-embed-websocket/9.0.71/41800a8478ee5716c70da6cd576959b798f72498/tomcat-embed-websocket-9.0.71-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.apiguardian:apiguardian-api:1.1.2">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.apiguardian/apiguardian-api/1.1.2/a231e0d844d2721b0fa1b238006d15c6ded6842a/apiguardian-api-1.1.2.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.apiguardian/apiguardian-api/1.1.2/e0787a997746ac32639e0bf3cb27af8dce8a3428/apiguardian-api-1.1.2-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.assertj:assertj-core:3.22.0">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.assertj/assertj-core/3.22.0/c300c0c6a24559f35fa0bd3a5472dc1edcd0111e/assertj-core-3.22.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.assertj/assertj-core/3.22.0/2b9454bcf528122cfb8aaffa2f02e1c1dd4d3ec5/assertj-core-3.22.0-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.hamcrest:hamcrest:2.2">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.hamcrest/hamcrest/2.2/1820c0968dba3a11a1b30669bb1f01978a91dedc/hamcrest-2.2.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.hamcrest/hamcrest/2.2/a0a13cfc629420efb587d954f982c4c6a100da25/hamcrest-2.2-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.junit.jupiter:junit-jupiter-api:5.8.2">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.junit.jupiter/junit-jupiter-api/5.8.2/4c21029217adf07e4c0d0c5e192b6bf610c94bdc/junit-jupiter-api-5.8.2.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.junit.jupiter/junit-jupiter-api/5.8.2/429d353563c5fb35a3d47076bedbf75161e976c2/junit-jupiter-api-5.8.2-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.junit.jupiter:junit-jupiter-engine:5.8.2">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.junit.jupiter/junit-jupiter-engine/5.8.2/c598b4328d2f397194d11df3b1648d68d7d990e3/junit-jupiter-engine-5.8.2.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.junit.jupiter/junit-jupiter-engine/5.8.2/ea87fe3d752e24397f71f347872e79a89c61a704/junit-jupiter-engine-5.8.2-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.junit.jupiter:junit-jupiter-params:5.8.2">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.junit.jupiter/junit-jupiter-params/5.8.2/ddeafe92fc263f895bfb73ffeca7fd56e23c2cce/junit-jupiter-params-5.8.2.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.junit.jupiter/junit-jupiter-params/5.8.2/26d07692b618b8da29479febec7d55e18107ee48/junit-jupiter-params-5.8.2-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.junit.jupiter:junit-jupiter:5.8.2">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.junit.jupiter/junit-jupiter/5.8.2/5a817b1e63f1217e5c586090c45e681281f097ad/junit-jupiter-5.8.2.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.junit.jupiter/junit-jupiter/5.8.2/5fd01c33132462aab3905cb74bd1097a713ab91a/junit-jupiter-5.8.2-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.junit.platform:junit-platform-commons:1.8.2">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.junit.platform/junit-platform-commons/1.8.2/32c8b8617c1342376fd5af2053da6410d8866861/junit-platform-commons-1.8.2.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.junit.platform/junit-platform-commons/1.8.2/386b61876dd33d0ef0381189d3c1db9455d99659/junit-platform-commons-1.8.2-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.junit.platform:junit-platform-engine:1.8.2">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.junit.platform/junit-platform-engine/1.8.2/b737de09f19864bd136805c84df7999a142fec29/junit-platform-engine-1.8.2.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.junit.platform/junit-platform-engine/1.8.2/ef25ff956479198a71a7d1b23e11f27fb09393b6/junit-platform-engine-1.8.2-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.mockito:mockito-core:4.5.1">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.mockito/mockito-core/4.5.1/ed456e623e5afc6f4cee3ae58144e5c45f3b3bf/mockito-core-4.5.1.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.mockito/mockito-core/4.5.1/c08b438bd19ba98419cf7919ba63e909f75b71c/mockito-core-4.5.1-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.mockito:mockito-junit-jupiter:4.5.1">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.mockito/mockito-junit-jupiter/4.5.1/f81fb60bd69b3a6e5537ae23b883326f01632a61/mockito-junit-jupiter-4.5.1.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.mockito/mockito-junit-jupiter/4.5.1/6aa189452f42167be3dc9ce5ac029df0f15ba0e5/mockito-junit-jupiter-4.5.1-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.objenesis:objenesis:3.2">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.objenesis/objenesis/3.2/7fadf57620c8b8abdf7519533e5527367cb51f09/objenesis-3.2.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.objenesis/objenesis/3.2/9f5da90020a255f25b6d8da8db273e2d38ce9805/objenesis-3.2-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.opentest4j:opentest4j:1.2.0">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.opentest4j/opentest4j/1.2.0/28c11eb91f9b6d8e200631d46e20a7f407f2a046/opentest4j-1.2.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.opentest4j/opentest4j/1.2.0/41d55b3c2254de9837b4ec8923cbd371b8a7eab5/opentest4j-1.2.0-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.ow2.asm:asm:9.1">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.ow2.asm/asm/9.1/a99500cf6eea30535eeac6be73899d048f8d12a8/asm-9.1.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.ow2.asm/asm/9.1/3fb15dd478bf8dcb039aa0d035f9fff9e4229c61/asm-9.1-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.projectlombok:lombok:1.18.24">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.projectlombok/lombok/1.18.24/13a394eed5c4f9efb2a6d956e2086f1d81e857d9/lombok-1.18.24.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.projectlombok/lombok/1.18.24/a95c9f4bc8e3000f7b7adfdf5732b13cca5451e/lombok-1.18.24-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.skyscreamer:jsonassert:1.5.1">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.skyscreamer/jsonassert/1.5.1/6d842d0faf4cf6725c509a5e5347d319ee0431c3/jsonassert-1.5.1.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.skyscreamer/jsonassert/1.5.1/56cfa73a7ab13fbb8d433570add90f087d40e243/jsonassert-1.5.1-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.slf4j:jul-to-slf4j:1.7.36">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.slf4j/jul-to-slf4j/1.7.36/ed46d81cef9c412a88caef405b58f93a678ff2ca/jul-to-slf4j-1.7.36.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.slf4j/jul-to-slf4j/1.7.36/86e5d1e467b4b30ece98254248e7e726e747ec58/jul-to-slf4j-1.7.36-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.slf4j:slf4j-api:1.7.36">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.slf4j/slf4j-api/1.7.36/6c62681a2f655b49963a5983b8b0950a6120ae14/slf4j-api-1.7.36.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.slf4j/slf4j-api/1.7.36/ae9c1aae0033af915cfa75d850eb9d880f21a701/slf4j-api-1.7.36-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.springframework.boot:spring-boot-autoconfigure:2.7.8">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot-autoconfigure/2.7.8/cb835d82d00116e907d341d11096c3476ab49721/spring-boot-autoconfigure-2.7.8.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot-autoconfigure/2.7.8/e085b7d55cd1cafaef530d48c94bb83aadf9865a/spring-boot-autoconfigure-2.7.8-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.springframework.boot:spring-boot-devtools:2.7.8">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot-devtools/2.7.8/963bc838a51a9271bcb71af51f58d761d4b13cda/spring-boot-devtools-2.7.8.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot-devtools/2.7.8/4692a63d4af6756fc9686f9ebed3970e6fd1d10e/spring-boot-devtools-2.7.8-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.springframework.boot:spring-boot-starter-json:2.7.8">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot-starter-json/2.7.8/fb09a04154ce2aea974755bc011a588eee2332aa/spring-boot-starter-json-2.7.8.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot-starter-json/2.7.8/7e145839272f15164ed9bfc494dc8c22a56af94/spring-boot-starter-json-2.7.8-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.springframework.boot:spring-boot-starter-logging:2.7.8">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot-starter-logging/2.7.8/439c2c6fb705c4e7338b2c7a975a84b4f0cb3724/spring-boot-starter-logging-2.7.8.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot-starter-logging/2.7.8/73117840ea57939e081b182734b11c57a33e8485/spring-boot-starter-logging-2.7.8-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.springframework.boot:spring-boot-starter-test:2.7.8">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot-starter-test/2.7.8/5751fec1eae46b9dce1ff399e25deade13b5265d/spring-boot-starter-test-2.7.8.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot-starter-test/2.7.8/7a83e576dfa1bcc0a49cbdd7fd4ab8c858f112a1/spring-boot-starter-test-2.7.8-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.springframework.boot:spring-boot-starter-tomcat:2.7.8">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot-starter-tomcat/2.7.8/8e7a62215cc56473c891480f722dda43dd6059d9/spring-boot-starter-tomcat-2.7.8.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot-starter-tomcat/2.7.8/709662dc7e249a233dd65117422c35594a50197d/spring-boot-starter-tomcat-2.7.8-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.springframework.boot:spring-boot-starter-web:2.7.8">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot-starter-web/2.7.8/7596f0263544e8230a5bf5357a02fb391f036f77/spring-boot-starter-web-2.7.8.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot-starter-web/2.7.8/9a7a145138871cc68c54c8efcd7853d2ec8b8ff4/spring-boot-starter-web-2.7.8-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.springframework.boot:spring-boot-starter:2.7.8">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot-starter/2.7.8/df091ed288bd1dcf89fa0554a29fe96f27802efc/spring-boot-starter-2.7.8.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot-starter/2.7.8/acc5aee24b310460f7388c3b982bf6f8b488a33c/spring-boot-starter-2.7.8-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.springframework.boot:spring-boot-test-autoconfigure:2.7.8">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot-test-autoconfigure/2.7.8/48f875d696d55257ce380d2c399d60030508d081/spring-boot-test-autoconfigure-2.7.8.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot-test-autoconfigure/2.7.8/a734bcc5dcc88ecc1d189df4d498197ff168dd3e/spring-boot-test-autoconfigure-2.7.8-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.springframework.boot:spring-boot-test:2.7.8">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot-test/2.7.8/e90d0e2f4502d38244fbee6f5f03056abd24888b/spring-boot-test-2.7.8.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot-test/2.7.8/89b141c1a8ec903cd8b67fb8e72a590af89783e9/spring-boot-test-2.7.8-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.springframework.boot:spring-boot:2.7.8">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot/2.7.8/8db5af0f1171bb402c27a85fe97d741bddaa6fee/spring-boot-2.7.8.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot/2.7.8/5ffb580e518563f466fb33efe8da3fbf301c5c00/spring-boot-2.7.8-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.springframework:spring-aop:5.3.25">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.springframework/spring-aop/5.3.25/722e30759b29331726f9deed76f80b22345ee627/spring-aop-5.3.25.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.springframework/spring-aop/5.3.25/f06034fea72f454582a071134c608a906aa84842/spring-aop-5.3.25-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.springframework:spring-beans:5.3.25">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.springframework/spring-beans/5.3.25/b3aeae036b4ea1abfa1f9604d452e19664efe5f6/spring-beans-5.3.25.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.springframework/spring-beans/5.3.25/cb20df8e727b921a584b8474fdb0cce147378569/spring-beans-5.3.25-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.springframework:spring-context:5.3.25">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.springframework/spring-context/5.3.25/268a70ce4f44333ce0f13304c5f8c53b3df5f5f4/spring-context-5.3.25.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.springframework/spring-context/5.3.25/537aa7309840dbbba15fae30fac8bdd07368bd75/spring-context-5.3.25-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.springframework:spring-core:5.3.25">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.springframework/spring-core/5.3.25/85382e86321227506bf7f97ed80e2ab88bce25f0/spring-core-5.3.25.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.springframework/spring-core/5.3.25/a1dbd0ee414155d8330405526538e0956c0f9435/spring-core-5.3.25-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.springframework:spring-expression:5.3.25">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.springframework/spring-expression/5.3.25/d681cdb86611f03d8ef29654edde219fe5afef1d/spring-expression-5.3.25.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.springframework/spring-expression/5.3.25/658a263ecd0962be8508d20a6b3e1df3eb552247/spring-expression-5.3.25-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.springframework:spring-jcl:5.3.25">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.springframework/spring-jcl/5.3.25/2e65a986dc7f98b40faed8df1d50db77c0b96c61/spring-jcl-5.3.25.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.springframework/spring-jcl/5.3.25/fad06a90f8b4eb6838d90426b8e8cabbf90f833b/spring-jcl-5.3.25-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.springframework:spring-test:5.3.25">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.springframework/spring-test/5.3.25/42a55c25a4da3bc330d8ab3ea7648cd76d0830d4/spring-test-5.3.25.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.springframework/spring-test/5.3.25/9292b679f6314704bec3ce40092d27f4f82f4bfa/spring-test-5.3.25-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.springframework:spring-web:5.3.25">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.springframework/spring-web/5.3.25/c69815e7931cd3ce7f19cc8028fd1c36626120d6/spring-web-5.3.25.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.springframework/spring-web/5.3.25/1c1a05f9e56b8cee98d71c3d610f177f17b3f73d/spring-web-5.3.25-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.springframework:spring-webmvc:5.3.25">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.springframework/spring-webmvc/5.3.25/62a8258bcc4f7a58dd69af5140481b64653c90/spring-webmvc-5.3.25.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.springframework/spring-webmvc/5.3.25/315c7bb3f46a09d2b2dc1f6ff8b671b36a122ce8/spring-webmvc-5.3.25-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.xmlunit:xmlunit-core:2.9.1">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.xmlunit/xmlunit-core/2.9.1/e5833662d9a1279a37da3ef6f62a1da29fcd68c4/xmlunit-core-2.9.1.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.xmlunit/xmlunit-core/2.9.1/8ef88e77c158cdc17de55ad94e1e7e7972a91bd6/xmlunit-core-2.9.1-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.yaml:snakeyaml:1.30">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.yaml/snakeyaml/1.30/8fde7fe2586328ac3c68db92045e1c8759125000/snakeyaml-1.30.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.yaml/snakeyaml/1.30/2fa17b2e1e6edc10ce8dec15df18a844efd8318b/snakeyaml-1.30-sources.jar!/" />
</SOURCES>
</library>
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AutoImportSettings">
<option name="autoReloadType" value="SELECTIVE" />
</component>
<component name="ChangeListManager">
<list default="true" id="f681a272-ae15-4a3b-9bce-1ecf749cb9e2" name="Default Changelist" comment="" />
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="ComposerSettings">
<execution />
</component>
<component name="ExternalProjectsData">
<projectState path="$PROJECT_DIR$/vridge-docs">
<ProjectState />
</projectState>
</component>
<component name="ExternalProjectsManager">
<system id="GRADLE">
<state>
<projects_view>
<tree_state>
<expand />
<select />
</tree_state>
</projects_view>
</state>
</system>
</component>
<component name="Git.Settings">
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$/.." />
</component>
<component name="ProjectId" id="2LUQElMgpUPf3MSMtXaVLhOVftf" />
<component name="ProjectLevelVcsManager" settingsEditedManually="true" />
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent">
<property name="RequestMappingsPanelOrder0" value="0" />
<property name="RequestMappingsPanelOrder1" value="1" />
<property name="RequestMappingsPanelWidth0" value="75" />
<property name="RequestMappingsPanelWidth1" value="75" />
<property name="RunOnceActivity.OpenProjectViewOnStart" value="true" />
<property name="RunOnceActivity.ShowReadmeOnStart" value="true" />
<property name="WebServerToolWindowFactoryState" value="false" />
<property name="aspect.path.notification.shown" value="true" />
<property name="last_opened_file_path" value="$PROJECT_DIR$" />
</component>
<component name="RecentsManager">
<key name="CopyFile.RECENT_KEYS">
<recent name="C:\dev\vridge-docs\docs-back\vridge-docs" />
<recent name="C:\dev\vridge-docs\docs-back\vridge-docs\src\main\resources" />
</key>
</component>
<component name="RunManager">
<configuration name="VridgeDocsApplication" type="SpringBootApplicationConfigurationType" factoryName="Spring Boot">
<module name="docs.main" />
<option name="SPRING_BOOT_MAIN_CLASS" value="com.vazil.vridge.docs.VridgeDocsApplication" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
<changelist id="f681a272-ae15-4a3b-9bce-1ecf749cb9e2" name="Default Changelist" comment="" />
<created>1675923074883</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1675923074883</updated>
<workItem from="1675923075956" duration="466000" />
<workItem from="1675923572846" duration="97000" />
</task>
<servers />
</component>
<component name="Vcs.Log.Tabs.Properties">
<option name="TAB_STATES">
<map>
<entry key="MAIN">
<value>
<State />
</value>
</entry>
</map>
</option>
<option name="oldMeFiltersMigrated" value="true" />
</component>
</project>
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment