forked from Gitlink/forgeplus-react
388 lines
18 KiB
JavaScript
388 lines
18 KiB
JavaScript
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)); |