forgeplus-react/src/forge/Server/reposyncer/component/editStore.jsx

388 lines
18 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, {Fragment, forwardRef, useEffect, useState} from "react";
import { Button, Divider, Form, Icon, Input, Radio, Select, Steps, Tooltip, message, Modal } from "antd";
import github from '../../img/github.png';
import gitee from '../../img/gitee.png';
import arrow from '../../img/arrow.png';
import '../index.scss';
import axios from "axios";
import CopyTool from "../../../Component/CopyTool";
import { platformLogo, platformLogoUnText } from "..";
import { reposyncerHelpIcon } from "./storeList";
const { Step } = Steps;
export const confirmInfo1 = (tips="若存在同名从分支,首次同步主分支源码向从分支强制推送,请谨慎操作以免代码被覆盖") =>{
return <div className="flexBetween mb30" style={{alignItems: "flex-start"}}>
<Icon type="exclamation-circle" theme="filled" style={{ fontSize: '22px', color: '#CA0002' }}/>
<span className="ml10 font-15">{tips}</span>
</div>
}
export const confirmInfo2 = (info) =>{
const {mainBranch:{address, branchName, type}, branch} = info
return <div className="flexBetween">
<div className="infoBoxBlueBg" style={{width: "260px"}}>
<span>
{platformLogo(type)}
<span className="ml10">主分支</span>
</span>
<Divider dashed style={{margin: '15px'}}/>
{/* <a href={address} className="primaryColor">{address}</a> */}
<span className="primaryColor">{address}</span>
<div className="mt10">
<i className="iconfont icon-fenzhi2 font-18"></i>
{branchName}
</div>
</div>
<img src={arrow} style={{width: "40px"}}/>
<div className="infoBoxBlueBg" style={{width: "260px"}}>
<span>
{platformLogo(branch.type)}
<span className="ml10">从分支</span>
</span>
<Divider dashed style={{margin: '15px'}}/>
{/* <a href={branch.address} className="primaryColor">{branch.address}</a> */}
<span className="primaryColor">{branch.address}</span>
<div className="mt10">
<i className="iconfont icon-fenzhi2 font-18"></i>
{branch.branchName}
</div>
</div>
</div>
}
export const synchronizeTips = (platform="Github")=>{
return <Tooltip title={<div>
首次同步方向指两个仓库首次进行同步时执行git push指令的方向<br/>
若首次同步方向为GitLink同步至{platform}则本仓库代码将被强推至绑定的{platform}仓库一次后续根据webhook来监听仓库的双向同步
{/* ②若同步分支为全部分支,首次同步方向为<span style={{color: 'red'}}>GitLink同步至 {platform}</span>:系统将本仓库导入至已绑定的{platform}仓库,覆善目标仓库源码后,保持两个仓库的双向同步<br/>
③若同步分支为单个分支,首次同步方向为<span style={{color: 'red'}}>GitLink同步至 {platform}</span>:系统将本仓库目标分支导入至已绑定的{platform}仓库,覆盖目标分支名称的源码,保持两个分支的双向同步 */}
</div>}>
<Icon type="question-circle" theme="filled" style={{color: '#466aff'}} className="mb20 synchronizeTip"/>
</Tooltip>
}
// 根据同步方向显示分支form item
export const branchForm = (getFieldDecorator, setFieldsValue, getFieldsValue, initialValues, first_sync_direction = "1", branchList, platform, formItemLayout)=>{
const branchBySelectLabel = first_sync_direction === "1" ? "Gitlink" : platform;
const branchBySelectKey = first_sync_direction === "1" ? "gitlink_branch_name" : "external_branch_name";
const branchByInputLabel = first_sync_direction === "1" ? platform : "Gitlink";
const branchByInputKey = first_sync_direction === "1" ? "external_branch_name" : "gitlink_branch_name";
const branchName = getFieldsValue([branchByInputKey])[branchByInputKey]
return <Fragment>
<Form.Item label={`${branchBySelectLabel}分支`} {...formItemLayout}>
{getFieldDecorator(branchBySelectKey,{
rules:[{required:true,message:`请选择${branchBySelectLabel}仓库分支`}],
initialValue: initialValues[branchBySelectKey]
})(
<Select placeholder={`请选择${branchBySelectLabel}仓库分支`} onChange={(value)=>{
const formValue = {}
formValue[branchByInputKey] = value
setFieldsValue(formValue);
}}>
{branchList && branchList.map((item, index)=>{
const {name} = item;
return <Select.Option value={name} key={index}>{name}</Select.Option>
})}
</Select>
)}
</Form.Item>
<Form.Item hidden={!branchName} label={`${branchByInputLabel}分支`} {...formItemLayout} extra="注:若仓库存在该分支,同步后将被源分支覆盖。若不存在该分支,则会新建一条同名分支并同步代码">
{getFieldDecorator(branchByInputKey,{
rules:[{required:true,message:`请输入${branchByInputLabel}分支名称`}],
initialValue: initialValues[branchByInputKey]
})(
<Input placeholder={`请输入${branchByInputLabel}分支名称`} className="storeInput" maxLength={50} disabled/>
)}
</Form.Item>
</Fragment>
}
function EditStore(props){
const { form, history, storeDetail} = props;
const {sync_repositories_unique=[]} = storeDetail || {};
const { owner , projectsId } = props.match.params;
const { getFieldDecorator, validateFields , resetFields, setFieldsValue, getFieldsValue } = form;
const [step, setStep] = useState(0);
// 仓库分支列表
// Gitlink仓库分支列表
const [branchList, setBranchList] = useState();
const [externalRepoBranchList, setExternalRepoBranchList] = useState();
// form表单值
const [values, setValues] = useState({});
// 选择仓库类型
const [platform, setPlatform] = useState();
// 全部分支/单个分支
const [branch, setBranch] = useState("2");
const [visible, setVisible] = useState();
const stepList = [{
step: 0,
title: "配置同步仓库"
}, {
step: 1,
title: "配置同步仓库webhook"
}, {
step: 2,
title: "配置同步分支"
}]
// 用于显示确认弹框的字段
const {type, external_repo_address, external_branch_name, gitlink_branch_name} = values
const externalRepoInfo = {
address: external_repo_address,
branchName: branch === "1" ? "全部分支" : external_branch_name,
type
}
const gitlinkRepoInfo = {
address: `${window.location.origin}/${owner}/${projectsId}.git`,
branchName: branch === "1" ? "全部分支" : gitlink_branch_name,
type: "gitlink"
}
// 获取同步方向
const first_sync_direction = values.first_sync_direction || getFieldsValue(["first_sync_direction"]).first_sync_direction;
// 根据已配置仓库同步的平台显示还剩哪些平台
const synchronizedPlatformList = sync_repositories_unique.map(item=>{return item.type.split("::").pop()})
const platformList = (synchronizedPlatformList.length && ["Github", "Gitee"].filter(item=>synchronizedPlatformList.indexOf(item) === -1)) || ["Github", "Gitee"]
// 正则校验是否是gitee或者github的仓库地址
const pattern = platform && (platform === "Github" ? /^https:\/\/github\.com\/.+\/.+$/ : /^https:\/\/gitee\.com\/.+\/.+$/)
useEffect(()=>{
// 获取分支列表
axios.get(`/${owner}/${projectsId}/branches.json`, {params: {page: 1, limit: 1000}}).then(result=>{
setBranchList(result.data);
}).catch((error)=>{})
}, [])
function submit() {
const values = getFieldsValue();
values["type"] = `SyncRepositories::${values.type}`
axios.post(`/v1/${owner}/${projectsId}/sync_repositories.json`, values).then(res=>{
if(res && res.status === 200){
setVisible(false);
message.success('绑定成功');
window.location.href = `/${owner}/${projectsId}/service/reposyncer`;
}else{
message.error('绑定失败');
}
})
}
function inviteClick() {
const copyEle = document.querySelector('#webhook'); // 获取要复制的节点
if (!copyEle) {
console.error("您的CopyTool未设置正确的inputId");
return;
}
copyEle.select(); // 执行选中元素
if (document.execCommand('copy')) {
document.execCommand('copy');
document.getSelection().removeAllRanges();
message.success("复制成功");
}
}
function saveStep3Info(){
const {external_branch_name, gitlink_branch_name, first_sync_direction} = getFieldsValue();
setValues({
...values,
external_branch_name, gitlink_branch_name, first_sync_direction
})
}
const formItemLayout = {
labelCol: { span: 5 },
wrapperCol: { span: 18 },
style: {width: '60%'}
}
return <div className="storeListBox mt20">
<div className="headBox font-16 pl15 mb25">
跨平台仓库代码同步
{reposyncerHelpIcon}
</div>
<div className="registerBox">
<Steps current={step}>
{stepList.map(item=>{
const {title, step} = item;
return <Step title={title} key={step}/>
})}
</Steps>
<Form className="codeSynchronizationForm" colon={false} layout={step === 2 ? "horizontal" : "vertical"} form={form}>
{/* 第一步 */}
<Form.Item label="请选择同步仓库平台" className="storeFormItem" hidden={step}>
{getFieldDecorator("type",{
rules:[{required:true,message:"请选择同步仓库平台"}],
initialValue: values.type
})(
<Select placeholder="请选择" onChange={(value)=>{
resetFields();
setPlatform(value);
}}>
{platformList.map(item=>{
return <Select.Option key={item} value={item}>{platformLogoUnText(item)}{item}</Select.Option>
})}
</Select>
)}
</Form.Item>
<Form.Item label={`${platform}同步仓库地址`} className="storeFormItem" hidden={step || !platform}>
{getFieldDecorator("external_repo_address",{
rules:[
{required:true,message: `请输入${platform || ""}同步仓库地址`},
{pattern: pattern, message: `请输入${platform}可访问的仓库地址`}
],
initialValue: values.external_repo_address
})(
<Input placeholder={`请输入${platform}同步仓库地址`} className="storeInput" maxLength={200}/>
)}
</Form.Item>
<Form.Item label={`${platform}同步仓库授权验证`} className="storeFormItem" hidden={step || !platform} extra={<div className="pt5 pl10 font-13">
如何配置token<br/>
1登录{platform}确保拥有读写同步仓库的权限<br/>
{platform === "Github" && "2、找到已存在的token或新建一个token新建方式为点击个人头像→Settings→Developer Settings→Personal access tokens (classic)→Generate new token"}
{platform === "Gitee" && "2、找到已存在的token或新建一个token新建方式为点击个人头像→设置→私人令牌→生成新令牌"}
<br/>
3确保token拥有仓库读写权限且未过期将token复制到此处token填写框
</div>}>
{getFieldDecorator("external_token",{
rules:[{required:true,message: `请输入${platform || ""}授权token`}],
initialValue: values.external_token
})(
<Input.Password addonBefore="token" placeholder={`请输入${platform}的授权token`} className="storeInput" maxLength={300}/>
)}
</Form.Item>
{!step && <div>
<div className="mt30">
<Button style={{width: '100px'}} onClick={()=>{history.go(-1)}} className="mr30">返回</Button>
<Button type="primary" style={{width: '100px'}} onClick={()=>{
// 保存用户已经输入的值
validateFields(['type', 'external_repo_address', 'external_token'], (error, formValues )=>{
if(!error){
const address = formValues.external_repo_address.replace(".git", "").replace(`https://${platform.toLowerCase()}.com/`, "")
// 获取github/gitee仓库的分支列表
platform && axios.get(`/v1/${address}/branches/${platform.toLowerCase()}`, {params: {page: 1, limit: 100, token: formValues.external_token}}).then(res=>{
if(res && res.data && Array.isArray(res.data)){
setExternalRepoBranchList(res.data)
setStep(1);
setValues({
...values,
...formValues
})
}else{
message.error(`获取${platform}平台仓库分支列表失败,请检查参数输入是否正确`)
}
}).catch((error)=>{})
}
})
}}>下一步</Button>
</div>
</div>}
{/* 第二步 */}
{step === 1 && <div>
<Icon type="info-circle" theme="filled" style={{color: '#466aff'}}/>
<span className="color-grey-6 ml5 mb15">为了保证双向同步实时性请您完成以下操作以保证每个参与同步的仓库均已配置Webhook</span>
<p className="mt15">1复制Webhook接受请求地址</p>
<Input
id="webhook"
className="mt10 mb10 copyWebhookInput"
value={`${window.location.origin}/api/v1/${owner}/${projectsId}/sync_repositories/sync?sync_direction=2&repo_type=SyncRepositories::${platform}`}
readOnly
addonAfter={<a onClick={inviteClick}>复制链接</a>}
/>
2前往{platform}登录并进入同步仓库WebHook页面进行配置
<Button type="primary" ghost className="mt10 mb10 ml10" onClick={()=>{
const {type, external_repo_address} = values;
if(external_repo_address){
window.open(`${external_repo_address.replace(/.git$/, "")}${type === "Github" ? "/settings/hooks" : "/hooks"}`)
}else{
message.error("您暂未配置同步仓库地址")
}
}}>点击前往</Button><br/>
<div className="pl15 pb10">
添加webHook<br/>
将步骤1地址填入页面中的URL处<br/>
确保已勾选Push事件<br/>
激活
</div>
3完成配置
<div className="mt30">
<Button style={{width: '100px'}} onClick={()=>{setStep(0)}} className="mr30">上一步</Button>
<Button type="primary" style={{width: '100px'}} onClick={()=>{setStep(2)}}>下一步</Button>
</div>
</div>}
{/* 第三步 */}
{step === 2 && <div>
<div className="color-grey-6 mb15">请绑定同步分支建立单个分支的实时双向同步</div>
<Form.Item hidden>
{getFieldDecorator("sync_granularity",{
rules:[],
// initialValue: values.sync_granularity
initialValue: "2"
})(
<Radio.Group onChange={(e)=>{
const {external_repo_address} = values;
if(!external_repo_address){
message.error("您暂未配置同步仓库地址")
return
}
setBranch(e.target.value);
}} value={branch} style={{width: '80%'}}>
{/* <Radio value="1" style={{marginRight: '40%'}} disabled>全部分支<br/><span className="color-grey-6 ml25 font-12">建立仓库的实时双向同步</span></Radio> */}
<Radio value="2">单个分支<br/><span className="color-grey-6 ml25 font-12">建立单个分支的实时双向同步</span></Radio>
</Radio.Group>
)}
</Form.Item>
{branch && <div style={{display: 'flex', alignItems: "center"}}>
<Form.Item label="首次同步方向:" {...formItemLayout}>
{getFieldDecorator("first_sync_direction",{
rules:[{required:true,message:"请选择首次同步方向"}],
initialValue: values.first_sync_direction
})(
<Select placeholder="请选择首次同步方向" onChange={()=>{
resetFields(["external_branch_name", "gitlink_branch_name"]);
}}>
<Select.Option value="2">{platform}同步至GitLink</Select.Option>
<Select.Option value="1">GitLink同步至{platform}</Select.Option>
</Select>
)}
</Form.Item>
{synchronizeTips(platform)}
</div>}
{branch === "2" && first_sync_direction && branchForm(getFieldDecorator, setFieldsValue, getFieldsValue, values, first_sync_direction, first_sync_direction === "2" ? externalRepoBranchList : branchList, platform, formItemLayout)}
<div className="mt30">
<Button style={{width: '100px'}} className="mr30" onClick={()=>{
saveStep3Info();
setStep(1)
}}>上一步</Button>
{branch && <Button type="primary" style={{width: '100px'}} onClick={()=>{
saveStep3Info();
validateFields((error,values)=>{
if(!error){
setVisible(true);
}
})
}}>确认绑定</Button>}
</div>
</div>}
</Form>
<Modal
title="绑定同步分支"
visible={visible}
onCancel={()=>{setVisible(false)}}
footer={<div>
<Button style={{ width: '95px', height: '32px' }} onClick={() => { setVisible(false) }} className="mr40">再想想</Button>
<Button style={{ width: '95px', height: '32px' }} onClick={submit} type="primary" ghost>开始同步</Button>
</div>}
width={650}
className="reposyncerModal"
>
{confirmInfo1()}
{confirmInfo2({
mainBranch: values.first_sync_direction === "1" ? gitlinkRepoInfo : externalRepoInfo,
branch: values.first_sync_direction === "1" ? externalRepoInfo : gitlinkRepoInfo
})}
</Modal>
</div>
</div>
}
export default Form.create()(forwardRef(EditStore));