From 3e2a999dbb5721aa7f8b7785ac855ca879832ad3 Mon Sep 17 00:00:00 2001 From: dutexion Date: Mon, 5 Aug 2024 12:49:42 +0900 Subject: [PATCH] =?UTF-8?q?feat=20::=20=EB=8F=8C=EC=95=84=EA=B0=80?= =?UTF-8?q?=EB=8A=94=20=EC=BD=94=EB=93=9C=EB=8A=94=20=EC=99=84=EB=B2=BD?= =?UTF-8?q?=ED=95=9C=20=EC=BD=94=EB=93=9C=20(=20=EC=BB=A8=ED=85=8C?= =?UTF-8?q?=EC=9D=B4=EB=84=88=20=EC=97=86=EC=9D=84=20=EA=B2=BD=EC=9A=B0=20?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/assets/versions.ts | 2 + .../Team/deploy/Container/NoneContainer.tsx | 649 +++++++++--------- 2 files changed, 343 insertions(+), 308 deletions(-) create mode 100644 src/assets/versions.ts diff --git a/src/assets/versions.ts b/src/assets/versions.ts new file mode 100644 index 0000000..07f2c68 --- /dev/null +++ b/src/assets/versions.ts @@ -0,0 +1,2 @@ +export const nodeVersion: string[] = ['22.5.1', '22.5', '22', '20.16', '20.10', '20', '18.20.4', '18.20', '18']; +export const jdkVersion: string[] = ['21', '17', '11', '8']; diff --git a/src/pages/Team/deploy/Container/NoneContainer.tsx b/src/pages/Team/deploy/Container/NoneContainer.tsx index 12815f9..d5b2a60 100644 --- a/src/pages/Team/deploy/Container/NoneContainer.tsx +++ b/src/pages/Team/deploy/Container/NoneContainer.tsx @@ -1,334 +1,371 @@ -import { XButton } from '@/components/common/XButton'; -import { theme } from '@/style/theme'; -import styled from '@emotion/styled'; -import { LayoutBox } from '@/components/common/LayoutBox'; -import ExamImg1Src from '@/assets/example/ExamImg1.png'; -import ExamImg2Src from '@/assets/example/ExamImg2.png'; -import ExamImg3Src from '@/assets/example/ExamImg3.png'; import { getDetailDeploy } from '@/utils/apis/deploy'; -import { useParams } from 'react-router-dom'; -import { useEffect, useState } from 'react'; import { DeployDetailType } from '@/utils/types/deployType'; -import { checkFileExists } from '@/utils/apis/github'; +import { useState, useEffect } from 'react'; +import { useParams } from 'react-router-dom'; +import styled from '@emotion/styled'; +import { theme } from '@/style/theme'; +import { LayoutBox } from '@/components/common/LayoutBox'; import { Input } from '@/components/common/Input'; -import { Radio } from '@/components/common/Radio'; +import { SelectBar } from '@/components/common/SelectBar'; +import { XButton } from '@/components/common/XButton'; +import { nodeVersion, jdkVersion } from '@/assets/versions'; +import { + writeContainerConfig, + writeContainerGradle, + writeContainerNginx, + writeContainerNode, +} from '@/utils/apis/container'; + +const renderTypeList = ['csr', 'ssr']; +const frameworkList = ['spring boot', 'node']; export const TeamDeployNoneContainer = () => { - const { deployUUID } = useParams(); - const [data, setData] = useState(); - const [usePrefix, setUsePrefix] = useState(true); - const [configName, setConfigName] = useState(data?.deploy_name); - const [configPort, setConfigPort] = useState('8080'); - const [configPrefix, setConfigPrefix] = useState('/' + data?.deploy_name); - const [configDomainProd, setConfigDomainProd] = useState(data?.deploy_name + '.xquare.app'); - const [configDomainStag, setConfigDomainStag] = useState(data?.deploy_name + '-stag.xquare.app'); - const [isCreatedConfig, setIsCreatedConfig] = useState(false); - const [isCreatedDocker, setIsCreatedDocker] = useState(false); + const { deployUUID, env } = useParams(); + const [deployData, setDeployData] = useState(); - const handleOptionChange = (option: boolean) => { - setUsePrefix(option); - }; + // 공통 상태 + const [deployType, setDeployType] = useState<'frontend' | 'backend'>('frontend'); + const [renderType, setRenderType] = useState<'csr' | 'ssr'>('csr'); + const [framework, setFramework] = useState<'spring boot' | 'node'>('node'); + + // stag 환경 상태 + const [stagPort, setStagPort] = useState(''); + const [stagBranch, setStagBranch] = useState(''); + const [stagDomain, setStagDomain] = useState(''); + + // prod 환경 상태 + const [prodPort, setProdPort] = useState(''); + const [prodBranch, setProdBranch] = useState(''); + const [prodDomain, setProdDomain] = useState(''); + + // 빌드 관련 상태 + const [selectedNodeVersion, setSelectedNodeVersion] = useState(nodeVersion[0]); + const [selectedJdkVersion, setSelectedJdkVersion] = useState(jdkVersion[0]); + const [buildCommand, setBuildCommand] = useState(''); + const [outputDir, setOutputDir] = useState(''); useEffect(() => { if (!deployUUID) return; getDetailDeploy(deployUUID).then((res) => { - setData(res.data); - setConfigName(res.data?.deploy_name); - setConfigPrefix(`/${res.data?.deploy_name}`); - setConfigDomainProd(`${res.data?.deploy_name}.xquare.app`); - setConfigDomainStag(`${res.data?.deploy_name}-stag.xquare.app`); + setDeployData(res.data); }); - }, [deployUUID]); - - const handlePortChange = (e: React.ChangeEvent) => { - setConfigPort(e.target.value); - }; - - const handlePrefixChange = (e: React.ChangeEvent) => { - setConfigPrefix(e.target.value); - }; + }, []); - const handleCreateConfigFile = () => { - const branchName = prompt('Branch 이름을 입력해 주세요'); - if (!branchName) return; + const generateRequestData = (): { + stag: { branch: string; container_port: string; domain: string }; + prod: { branch: string; container_port: string; domain: string }; + language: string; + node_version?: string; + build_commands?: string[]; + command?: string; + output_dir?: string; + jdk_version?: string; + } => { + const commonData = { + stag: { + branch: stagBranch, + container_port: stagPort, + domain: `${stagDomain}.xquare.app`, + }, + prod: { + branch: prodBranch, + container_port: prodPort, + domain: `${prodDomain}.xquare.app`, + }, + language: deployType === 'frontend' || framework === 'node' ? 'javascript' : 'java', + }; - const content = usePrefix - ? `config:\n name: ${configName}\n port: ${configPort}\n prefix: ${configPrefix}` - : `config:\n name: dms\n port: ${configPort}\n domain:\n prod: ${configDomainProd}\n stag: ${configDomainStag}`; - const encodedContent = encodeURIComponent(content); - const url = `${data?.github_full_url}/new/${branchName}?filename=.xquare/config.yaml&value=${encodedContent}`; - - window.open(url, '_blank'); - }; - - const handleCheckConfigFile = async () => { - const branchName = prompt('Branch 이름을 입력해 주세요'); - if (!branchName) return; - - const filePath = '.xquare/config.yaml'; - try { - const fileExists = await checkFileExists( - data?.github_full_url.split('https://github.com/')[1] ?? '', - branchName, - filePath, - ); - if (fileExists) { - setIsCreatedConfig(true); + let specificData = {}; + if (deployType === 'frontend') { + specificData = + renderType === 'csr' + ? { + node_version: selectedNodeVersion, + build_commands: [buildCommand], + command: 'yarn dev', + } + : { + node_version: selectedNodeVersion, + build_commands: [buildCommand], + output_dir: outputDir, + }; + } else { + // backend + if (framework === 'spring boot') { + specificData = { + jdk_version: selectedJdkVersion, + output_dir: '/build/libs/*.jar', + build_commands: [buildCommand || './gradlew build -x test'], + }; } else { - setIsCreatedConfig(false); + // node + specificData = { + node_version: selectedNodeVersion, + build_commands: [buildCommand], + command: 'yarn dev', + }; } - } catch (error) { - console.error('Error checking file existence:', error); - setIsCreatedConfig(null); } + + return { ...commonData, ...specificData }; }; - const handleCheckDockerFile = async () => { - const branchName = prompt('Branch 이름을 입력해 주세요'); - if (!branchName) return; + const onSubmit = () => { + const requestData = generateRequestData(); - const filePath = 'Dockerfile'; - try { - const fileExists = await checkFileExists( - data?.github_full_url.split('https://github.com/')[1] ?? '', - branchName, - filePath, - ); - if (fileExists) { - setIsCreatedDocker(true); - } else { - setIsCreatedDocker(false); + if (!deployUUID) return; + + writeContainerConfig(deployUUID, { + prod: requestData.prod, + stag: requestData.stag, + language: requestData.language, + }); + + const timeout = setTimeout(() => { + if ( + deployType === 'backend' && + framework === 'spring boot' && + requestData.build_commands && + requestData.jdk_version && + requestData.output_dir + ) { + writeContainerGradle(deployUUID, 'prod', { + build_commands: requestData.build_commands, + jdk_version: requestData.jdk_version, + output_dir: requestData.output_dir, + }); + writeContainerGradle(deployUUID, 'stag', { + build_commands: requestData.build_commands, + jdk_version: requestData.jdk_version, + output_dir: requestData.output_dir, + }); + } else if ( + (deployType === 'backend' && + framework === 'node' && + requestData.build_commands && + requestData.command && + requestData.node_version) || + (deployType === 'frontend' && + renderType === 'ssr' && + requestData.build_commands && + requestData.command && + requestData.node_version) + ) { + writeContainerNode(deployUUID, 'prod', { + node_version: requestData.node_version, + build_commands: requestData.build_commands, + command: requestData.command, + }); + writeContainerNode(deployUUID, 'stag', { + node_version: requestData.node_version, + build_commands: requestData.build_commands, + command: requestData.command, + }); + } else if ( + deployType === 'frontend' && + renderType === 'csr' && + requestData.build_commands && + requestData.output_dir && + requestData.node_version + ) { + writeContainerNginx(deployUUID, 'prod', { + node_version: requestData.node_version, + build_commands: requestData.build_commands, + output_dir: requestData.output_dir, + }); + writeContainerNginx(deployUUID, 'stag', { + node_version: requestData.node_version, + build_commands: requestData.build_commands, + output_dir: requestData.output_dir, + }); } - } catch (error) { - console.error('Error checking file existence:', error); - setIsCreatedDocker(null); - } + }, 1500); }; return ( - - - {data?.team_name_ko} + + + {deployData && ( + + {deployData.team_name_ko} / {deployData.deploy_name} + + )} 컨테이너 정의한 배포에 대한 컨테이너를 인프라에 생성, 관리합니다. - - GitHub Actions 워크플로우를 사용해 프로젝트를 Xquare 서버에 배포하고 관리할 수 있습니다. -
- 아래 설명에 따라 워크플로우를 설정하여 서버를 배포해보세요. -
-
- + + 1 - - 프로젝트 레포지토리 정보가 정상적으로 등록되었습니다! - - - {data?.github_full_url} - - - 재확인 - + + 아래 정보를 입력해 주세요! + + setDeployType('frontend')}> + frontend + + setDeployType('backend')}> + backend + - 2 - - 레포지토리의 .xquare/config.yaml 경로에 배포를 위한 설정 파일을 등록해주세요. - - 아래 정보를 입력하여 설정 파일을 생성해보세요! - - -
-                    {usePrefix ? (
-                      <>
-                        
config:
-
  name: {configName}
-
  port: {configPort}
-
  prefix:{' ' + configPrefix}
- - ) : ( - <> -
config:
-
  name: {configName}
-
  port: {configPort}
-
  domain:
-
    prod:{' ' + configDomainProd}
-
    stag:{' ' + configDomainStag}
- - )} -
-
- + + 아래 정보를 입력해 주세요! + + + 개발 환경(stag) + setStagPort(e.target.value)} + /> + setStagBranch(e.target.value)} + /> + setStagDomain(e.target.value)} /> - - 배포 URL 설정 - - handleOptionChange(true)} isBold> - prefix 사용 - - handleOptionChange(false)} isBold> - xquare.app 하위 도메인 사용 - - - - {usePrefix ? ( - <> - - - ) : ( - <> - setConfigDomainProd(e.target.value)} - label="prod domain" - /> - setConfigDomainStag(e.target.value)} - label="stag domain" - /> - - )} + .xquare.app + + + + 운영 환경(prod) + setProdPort(e.target.value)} + /> + setProdBranch(e.target.value)} + /> + + setProdDomain(e.target.value)} + /> + .xquare.app -
- - - {(() => { - switch (isCreatedConfig) { - case null: - return '파일 확인 중 오류가 발생했습니다.'; - case true: - return 'Config 파일이 생성되었습니다!'; - case false: - return 'Config 파일이 생성되지 않았습니다.'; - default: - return ''; - } - })()} - - - 생성 - - - 확인 -
- 3 - - GitHub repository에 Dockerfile을 생성합니다. - - - {(() => { - switch (isCreatedDocker) { - case null: - return '파일 확인 중 오류가 발생했습니다.'; - case true: - return 'docker 파일이 생성되었습니다!'; - case false: - return 'docker 파일이 생성되지 않았습니다.'; - default: - return ''; + + 본인의 프로젝트에 맞게 선택해 주세요. + +
+ - - 확인 - - - - - - - 4 - - Github Personal Access Token을 발급받습니다. repo 권한을 반드시 포함해야 합니다. - - -
- 발급받은 Personal Access Token을 Github Actions Secret으로 등록합니다. - - (Repository Setting {'>'} Secrets and variables {'>'} Actions) - + onSelect={(index) => { + if (index !== undefined) { + if (deployType === 'frontend') { + setRenderType(renderTypeList[index] as 'csr' | 'ssr'); + } else { + setFramework(frameworkList[index] as 'spring boot' | 'node'); + } + } + }} + values={deployType === 'frontend' ? renderTypeList : frameworkList} + label={deployType === 'frontend' ? '렌더링 방식을 설정해 주세요.' : '프레임워크를 선택해주세요'} + />
- -
-
-
- - - 5 - -
- 아래 버튼을 눌러 배포 키를 발급받습니다. 발급받은 배포 키는 한 번만 확인할 수 있습니다. - - 발급받은 Access key를 Repository의 Secret으로 등록합니다. (Repository Setting {'>'} Secrets and - variables {'>'} Actions) - -
- - - - - 발급받기 - - - +
+ { + if (index !== undefined) { + if (deployType === 'frontend' || framework === 'node') { + setSelectedNodeVersion(nodeVersion[index]); + } else { + setSelectedJdkVersion(jdkVersion[index]); + } + } + }} + values={deployType === 'frontend' || framework === 'node' ? nodeVersion : jdkVersion} + label={ + deployType === 'frontend' || framework === 'node' + ? 'node 버전을 선택해주세요' + : 'jdk 버전을 선택해주세요' + } + /> +
+ setBuildCommand(e.target.value)} + /> + {(deployType === 'backend' || (deployType === 'frontend' && renderType === 'ssr')) && ( + setOutputDir(e.target.value)} + /> + )}
- - - 6 - -
- .github/workflows 경로 아래에 배포에 대한 Git Action을 작성합니다. - xquare action을 넣을 job 아래에 OIDC 권한을 허용해줍니다. -
- - 자신의 프로젝트에 대한 이미지를 빌드하기 전 필요한 필요한 동작이 있다면 추가합니다. - -
-
- - - 7 - 배포 Action을 실행하여 애플리케이션을 배포합니다. - - - + + + + 생성하기 + + + ); }; +const Wrapper = styled.div` + width: 100%; + display: flex; + flex-direction: column; + align-items: center; + gap: 30px; +`; + +const TitleContainer = styled.div` + width: 100%; + max-width: 1120px; + display: flex; + flex-direction: column; + gap: 4px; + margin-top: 80px; +`; + const TeamName = styled.div` font-size: 20px; font-weight: 500; @@ -345,7 +382,6 @@ const Describtion = styled.div` font-size: 24px; font-weight: 100; color: ${theme.color.gray8}; - margin-bottom: 26px; `; const Text = styled.div` @@ -369,37 +405,34 @@ const NumberBox = styled.div` font-weight: 500; `; -const WrapperBox = styled.div<{ width?: number; height?: number; radius?: number }>` - width: ${({ width }) => width + 'px'}; - height: ${({ height }) => height + 'px'}; - padding: 0 30px 0 30px; - height: ${({ height }) => `${height}px`}; - border-radius: ${({ radius }) => `${radius}px`}; - background-color: ${theme.color.gray2}; +const StepContainerWrapper = styled.div` + max-width: 1120px; + padding-bottom: 40px; + width: 100%; display: flex; - justify-content: center; - align-items: center; - font-size: 20px; - color: ${theme.color.gray7}; + flex-direction: column; + gap: 48px; `; -const SecondStepContainer = styled.div` - width: 956px; - border-radius: 6px; - border: 1px solid ${theme.color.gray5}; - padding: 20px 46px; +const DeployTypeBox = styled.div<{ isSelected: boolean }>` + width: 160px; + height: 46px; + border-radius: 4px; + cursor: pointer; + transition: 0.1s linear; + color: ${({ isSelected }) => (isSelected ? theme.color.mainDark1 : theme.color.gray5)}; + background-color: ${({ isSelected }) => (isSelected ? theme.color.mainLight2 : theme.color.gray1)}; + border: 1px solid ${({ isSelected }) => (isSelected ? theme.color.mainDark1 : theme.color.gray5)}; display: flex; - flex-direction: column; - gap: 20px; + justify-content: center; + align-items: center; `; -const SixthStepContainer = styled.div` - width: 980px; - height: 1388px; - border-radius: 6px; - border: 1px solid ${theme.color.gray5}; - padding: 20px 46px; +const ButtonWrapper = styled.div` + max-width: 1120px; + width: 100%; + height: 50px; display: flex; - flex-direction: column; - gap: 40px; + justify-content: end; + padding-bottom: 200px; `;