- {form_type === "new" ? "新建" :( form_type === "copy" ? "复制" : "编辑")}任务
+ {form_type === "new" ? "新建" :( form_type === "copy" ? "复制" : "编辑")}易修
{getFieldDecorator("subject", {
rules: [
{
required: true,
- message: "请填写任务标题",
+ message: "请填写易修标题",
},
]
- })()}
+ })()}
- this.props.history.push(form_type === "new" ? `/projects/${owner}/${projectsId || orderId}/issues` : `/projects/${owner}/${projectsId}/issues/${orderId}/detail`)}
+ this.props.history.push(form_type === "new" ? `/${owner}/${projectsId || orderId}/issues` : `/${owner}/${projectsId}/issues/${orderId}`)}
>
取消
diff --git a/src/forge/SecuritySetting/Index.jsx b/src/forge/SecuritySetting/Index.jsx
new file mode 100644
index 000000000..d9d4e49c2
--- /dev/null
+++ b/src/forge/SecuritySetting/Index.jsx
@@ -0,0 +1,82 @@
+import React from "react";
+
+import { Route, Switch } from "react-router-dom";
+import { withRouter } from "react-router";
+
+import { SnackbarHOC } from "educoder";
+import { CNotificationHOC } from "../../modules/courses/common/CNotificationHOC";
+import { TPMIndexHOC } from "../../modules/tpm/TPMIndexHOC";
+import Loadable from "react-loadable";
+import Loading from "../../Loading";
+import { Box , Gap , LongWidth } from '../Component/layout';
+import { getImageUrl } from 'educoder';
+import { Link } from 'react-router-dom';
+
+import './Index.scss';
+
+const SSHNew = Loadable({
+ loader: () => import("./sub/New"),
+ loading: Loading,
+});
+const Profile = Loadable({
+ loader: () => import("../users/Material/Index"),
+ loading: Loading,
+});
+const SSHIndex = Loadable({
+ loader: () => import("./sub/SSH"),
+ loading: Loading,
+});
+function Index(props){
+ const { current_user } = props;
+ const { pathname } = props.location;
+
+ return(
+
+
+
+
+
+
![]({getImageUrl(`/${current_user)
+
{current_user && current_user.username}
+
+
+ - 个人信息
+ - -1 ?"active":""}>基本资料
+
+
+ - 安全设置
+ - -1 ?"active":""}>SSH密钥
+
+
+
+
+
+ (
+
+ )}
+ >
+ (
+
+ )}
+ >
+ (
+
+ )}
+ >
+
+
+
+
+
+
+
+ )
+}
+export default withRouter((CNotificationHOC()(SnackbarHOC()(TPMIndexHOC(Index))))
+);
\ No newline at end of file
diff --git a/src/forge/SecuritySetting/Index.scss b/src/forge/SecuritySetting/Index.scss
new file mode 100644
index 000000000..f533f0530
--- /dev/null
+++ b/src/forge/SecuritySetting/Index.scss
@@ -0,0 +1,150 @@
+.whiteBack{
+ background-color: #fff;
+ .boies{
+ width: 1200px;
+ margin:0px auto;
+ padding:30px 0px 10px;
+ .shortW{
+ width: 198px;
+ border: 1px solid rgba(153, 153, 153, 0.22);
+ border-radius: 4px;
+ min-height: 400px;
+ margin-bottom: 30px;
+ .userDetail{
+ background: rgba(153, 153, 153, 0.05);
+ border-radius: 4px 4px 0px 0px;
+ padding:20px 25px;
+ display: flex;
+ align-items: center;
+ justify-content: flex-start;
+ img{
+ height: 48px;
+ width: 48px;
+ border-radius: 50%;
+ margin-right: 12px;
+ }
+ span{
+ font-size: 16px;
+ color: #333;
+ max-width: 90px;
+ display: block;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ }
+ }
+ .securityUl{
+ padding:20px 16px;
+ color: #333;
+ margin-bottom: 0px;
+ padding-bottom: 0px;
+ li{
+ margin-bottom: 10px;
+ height: 27px;
+ line-height: 27px;
+ position: relative;
+ cursor: pointer;
+ a{
+ color: #666;
+ &:hover{
+ color: #333;
+ }
+ }
+ &.active a{
+ color: #333;
+ }
+ &:first-child{
+ font-size: 16px;
+ }
+ &.active::before{
+ position: absolute;
+ left: -16px;
+ top:0px;
+ height: 100%;
+ width: 2px;
+ content: "";
+ background-color: #2A61FF;
+ }
+ }
+ }
+ }
+ .sshHead{
+ display: flex;
+ align-items: center;
+ padding:15px 20px;
+ justify-content: space-between;
+ border-bottom: 1px solid #EEEEEE;
+ &>span{
+ font-size: 18px;
+ }
+ }
+ .ant-list-item{
+ padding:20px;
+ border-bottom: 1px solid #eee!important;
+ &>img{
+ margin-right: 24px;
+ }
+ &>div{
+ flex:1;
+ width: 0;
+ margin-right: 20px;
+ p{
+ margin-bottom: 8px!important;
+ }
+ span{
+ font-size: 12px;
+ }
+ }
+ .ant-btn.ant-btn-danger{
+ background-color: #fff;
+ border-color: #D0D0D0;
+ color: #DF0002;
+ &:hover{
+ background-color: #DF0002;
+ color: #fff;
+ border-color: #DF0002;
+ }
+ }
+ }
+ .questionLink{
+ padding:15px 20px;
+ a{
+ color: #4B7AFF;
+ &:hover{
+ text-decoration: underline;
+ }
+ }
+ }
+ .sshForm{
+ padding:15px 20px;
+ .ant-col.ant-form-item-label{
+ font-size: 16px;
+ color:#333;
+ }
+ }
+ }
+}
+.descModal{
+ .ant-modal-title{
+ text-align: left;
+ font-size: 20px;
+ }
+ .keyContent{
+ border:1px solid #eee;
+ border-radius: 4px;
+ padding:10px 15px;
+ margin-top: 10px;
+ max-height: 200px;
+ overflow-y: auto;
+ }
+ .keysTitle{
+ display: flex;
+ align-items: flex-start;
+ justify-content: flex-start;
+ flex-wrap: wrap;
+ span:last-child{
+ word-break: break-all;
+ flex:1;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/forge/SecuritySetting/img/miyao_middle_icon.png b/src/forge/SecuritySetting/img/miyao_middle_icon.png
new file mode 100644
index 000000000..03584cfe3
Binary files /dev/null and b/src/forge/SecuritySetting/img/miyao_middle_icon.png differ
diff --git a/src/forge/SecuritySetting/sub/DeleteBox.jsx b/src/forge/SecuritySetting/sub/DeleteBox.jsx
new file mode 100644
index 000000000..02d816b32
--- /dev/null
+++ b/src/forge/SecuritySetting/sub/DeleteBox.jsx
@@ -0,0 +1,28 @@
+import React from 'react';
+import { AlignCenter } from '../../Component/layout';
+import Modals from '../../Component/PublicModal/Index';
+import { Button } from 'antd';
+
+function DeleteBox({ visible , onCancel ,onSuccess }) {
+
+ return(
+
+
+
+
+ }
+ >
+
+
+ 您确定要删除此 SSH 密钥吗?
+
此操作将永久删除该SSH密钥,且不可恢复。如果您想再次使用该密钥,则需要您重新上传。
+
+
+ )
+}
+export default DeleteBox;
\ No newline at end of file
diff --git a/src/forge/SecuritySetting/sub/New.jsx b/src/forge/SecuritySetting/sub/New.jsx
new file mode 100644
index 000000000..68a65db37
--- /dev/null
+++ b/src/forge/SecuritySetting/sub/New.jsx
@@ -0,0 +1,59 @@
+import React , { forwardRef, useState } from 'react';
+import { Link } from 'react-router-dom';
+import { Button, Form , Input } from 'antd';
+import Axios from 'axios';
+
+const { TextArea } = Input;
+function New({ form , showNotification , history }) {
+ const { getFieldDecorator, validateFields , setFieldsValue } = form;
+ const [ msg , setMsg ] = useState(undefined);
+
+ function submit() {
+ validateFields((error,values)=>{
+ if(!error){
+ const url = `/public_keys.json`;
+ Axios.post(url,{
+ ...values
+ }).then(result=>{
+ if(result){
+ if(result.data.status === 0){
+ history.push(`/settings/SSH`);
+ showNotification("密钥创建成功!");
+ }
+ let s = {
+ status:result.data.status,
+ message:result.data.message
+ }
+ setMsg(s);
+ }
+ }).catch(error=>{})
+ }
+ })
+ }
+
+ return(
+
+
+ SSH密钥添加SSH密钥
+
+
+ {getFieldDecorator("title",{
+ rules:[{required:true,message:"请输入密钥标题"}]
+ })(
+
+ )}
+
+
+ {getFieldDecorator("key",{
+ rules:[{required:true,message:"请输入密钥"}]
+ })(
+
+ )}
+
+
+
+
+ )
+}
+export default Form.create()(forwardRef(New));
\ No newline at end of file
diff --git a/src/forge/SecuritySetting/sub/SSH.jsx b/src/forge/SecuritySetting/sub/SSH.jsx
new file mode 100644
index 000000000..bc004e119
--- /dev/null
+++ b/src/forge/SecuritySetting/sub/SSH.jsx
@@ -0,0 +1,96 @@
+import React, { useState , useEffect } from 'react';
+import { Button , List , Pagination , Skeleton } from 'antd';
+import miyao from '../img/miyao_middle_icon.png';
+import axios from 'axios';
+import DeleteBox from './DeleteBox';
+import SSHDetail from './SSHDetail';
+
+const limit = 10;
+function SSH(props) {
+ const [ list ,setList ] = useState(undefined);
+ const [ page , setPage ] = useState(1);
+ const [ total , setTotal ] = useState(0);
+ const [ id , setId ] = useState(undefined);
+ const [ visible , setVisible ] = useState(false);
+ const [ visibleDesc , setVisibleDesc ] = useState(false);
+ const [ content , setContent ] = useState(undefined);
+
+ useEffect(()=>{
+ Init();
+ },[page])
+
+ function Init() {
+ const url = `/public_keys.json`;
+ axios.get(url,{
+ params:{
+ page,limit
+ }
+ }).then(result=>{
+ if(result && result.data){
+ setList(result.data.public_keys);
+ setTotal(result.data.total_count);
+ }
+ }).catch(error=>{})
+ }
+
+ function onSuccess() {
+ if(id){
+ const url = `/public_keys/${id}.json`;
+ axios.delete(url).then(result=>{
+ if(result && result.data){
+ props.showNotification("密钥删除成功!");
+ setVisible(false);
+ if(page>1 && (list && list.length===1)){
+ setPage(page-1);
+ }else{
+ Init();
+ }
+ }
+ }).catch(error=>{})
+ }
+ }
+
+ return(
+
+
setVisible(false)} onSuccess={onSuccess}/>
+ setVisibleDesc(false)} desc={content}/>
+
+ SSH密钥
+
+
+ {
+ list && list.length > 0 &&
+
+ {
+ list.map((i,k)=>{
+ return(
+
+
+
+
+
+ )
+ })
+ }
+
+
+ }
+ {
+ !list &&
+ }
+ {
+ total > limit &&
+
+
{setPage(p)}} pageSize={limit} total={total}/>
+
+ }
+ { (!list || (list && list.length === 0)) && 您还没有添加任何SSH密钥
}
+ 如何生成SSH密钥?
+
+ )
+}
+export default SSH;
\ No newline at end of file
diff --git a/src/forge/SecuritySetting/sub/SSHDetail.jsx b/src/forge/SecuritySetting/sub/SSHDetail.jsx
new file mode 100644
index 000000000..c71c5d4fa
--- /dev/null
+++ b/src/forge/SecuritySetting/sub/SSHDetail.jsx
@@ -0,0 +1,31 @@
+import React from 'react';
+
+import { Button, Modal } from 'antd';
+
+function SSHDetail({visible , onCancel , desc }) {
+ return(
+
+
+
+ SSH Key:
+ {desc && desc.name}
+
+
添加时间:{desc && desc.created_time}
+
+ 公钥指纹:
+ {desc && desc.fingerprint}
+
+
{desc && desc.content}
+
+
+
+ )
+}
+export default SSHDetail;
\ No newline at end of file
diff --git a/src/forge/Settings/Branch.js b/src/forge/Settings/Branch.js
index d0a235d87..3e19f37e7 100644
--- a/src/forge/Settings/Branch.js
+++ b/src/forge/Settings/Branch.js
@@ -23,6 +23,7 @@ export default ((props)=>{
const [ page , setPage ] = useState(1);
let defaultBranch = props.defaultBranch;
+
useEffect(()=>{
if(defaultBranch){
setBranch(defaultBranch);
@@ -76,6 +77,8 @@ export default ((props)=>{
.then((result) => {
if (result) {
props.showNotification(`分支设置成功!`);
+ const { getDetail } = props;
+ getDetail && getDetail();
}
})
.catch((error) => {
@@ -85,7 +88,7 @@ export default ((props)=>{
// 跳转
function settingRule(protectBranch){
- props.history.push(`/projects/${owner}/${projectsId}/setting/branch/${protectBranch}`);
+ props.history.push(`/${owner}/${projectsId}/settings/branches/${protectBranch}`);
}
// 翻页
@@ -95,7 +98,7 @@ export default ((props)=>{
return(
- 分支设置
+ 分支设置
默认分支
diff --git a/src/forge/Settings/BranchRule.jsx b/src/forge/Settings/BranchRule.jsx
index d15ecd00d..85b9bf20c 100644
--- a/src/forge/Settings/BranchRule.jsx
+++ b/src/forge/Settings/BranchRule.jsx
@@ -290,7 +290,7 @@ export default Form.create()(
{
- history.push(`/projects/${owner}/${projectsId}/setting/branch`);
+ history.push(`/${owner}/${projectsId}/settings/branches`);
}}
>
取消
diff --git a/src/forge/Settings/Collaborator.js b/src/forge/Settings/Collaborator.js
index 4ae292633..0a50cae10 100644
--- a/src/forge/Settings/Collaborator.js
+++ b/src/forge/Settings/Collaborator.js
@@ -1,4 +1,4 @@
-import React, { useState } from "react";
+import React, { useState , useRef } from "react";
import {WhiteBack} from '../Component/layout';
import AddMember from '../Component/AddMember';
import AddGroup from '../Component/AddGroup';
@@ -8,9 +8,11 @@ import Group from './CollaboratorGroup';
function Collaborator(props){
const [ nav , setNav] = useState("1");
const [ newId , setNewId] = useState(undefined);
+ const [ addOperation , setAddOperation] = useState(true);
const [ newGroupId , setNewGroupId] = useState(undefined);
const {projectsId ,owner} = props.match.params;
+
const author = props && props.projectDetail && props.projectDetail.author;
function getID(id){
@@ -19,6 +21,7 @@ function Collaborator(props){
function getGroupID(id){
setNewGroupId(id);
}
+
return (
@@ -27,15 +30,17 @@ function Collaborator(props){
author && author.type === "Organization" ?
{setNav("1");setNewId(undefined)}}>协作者管理
- {setNav("2");setNewId(undefined)}}>团队管理
+ {setNav("2");setNewId(undefined);setNewGroupId(undefined)}}>团队管理
:
协作者管理
}
{
- nav === "1" ?
-
- :
+ nav === "1" &&
+
+ }
+ {
+ (nav !== "1" && addOperation) &&
}
@@ -44,7 +49,7 @@ function Collaborator(props){
nav === "1" ?
:
-
+
}
diff --git a/src/forge/Settings/CollaboratorGroup.jsx b/src/forge/Settings/CollaboratorGroup.jsx
index c6ab6ffbc..5c38b1aa7 100644
--- a/src/forge/Settings/CollaboratorGroup.jsx
+++ b/src/forge/Settings/CollaboratorGroup.jsx
@@ -10,7 +10,7 @@ const roles = {
read: "报告者",
};
const limit = 15;
-function CollaboratorGroup({ newGroupId, owner, projectsId }) {
+function CollaboratorGroup({ newGroupId, owner, projectsId , setAddOperation }) {
const [list, setList] = useState(undefined);
const [isSpin, setIsSpin] = useState(true);
const [page, setPage] = useState(1);
@@ -34,6 +34,7 @@ function CollaboratorGroup({ newGroupId, owner, projectsId }) {
setList(result.data.teams);
setTotal(result.data.total_count);
setIsSpin(false);
+ setAddOperation(result.data.can_add);
}
})
.catch((error) => {});
@@ -47,16 +48,15 @@ function CollaboratorGroup({ newGroupId, owner, projectsId }) {
// 添加团队
function addGroup(id) {
const url = `/${owner}/${projectsId}/teams.json`;
- axios
- .post(url, {
- team_id: id,
- })
- .then((result) => {
- if (result && result.data) {
- getData();
- }
- })
- .catch((error) => {});
+ axios.post(url, {
+ team_id: id,
+ })
+ .then((result) => {
+ if (result && result.data) {
+ getData();
+ }
+ })
+ .catch((error) => {});
}
// 删除团队
@@ -77,7 +77,11 @@ function CollaboratorGroup({ newGroupId, owner, projectsId }) {
title: "团队名",
dataIndex: "name",
render: (value, item) => {
- return
{value};
+ if(item.is_admin || item.is_member){
+ return
{value};
+ }else{
+ return
{value};
+ }
},
},
{
diff --git a/src/forge/Settings/CollaboratorMember.jsx b/src/forge/Settings/CollaboratorMember.jsx
index 86459d874..447e3b262 100644
--- a/src/forge/Settings/CollaboratorMember.jsx
+++ b/src/forge/Settings/CollaboratorMember.jsx
@@ -58,8 +58,12 @@ function CollaboratorMember({projectsId,owner,project_id,author,showNotification
})
.then((result) => {
if (result) {
- setListData(result.data.members);
- setTotal(result.data.total_count);
+ if(page > 1 && ( listData && listData.length === 1)){
+ setPage(page-1);
+ }else{
+ setListData(result.data.members);
+ setTotal(result.data.total_count);
+ }
setIsSpin(false);
}
})
@@ -182,7 +186,7 @@ function CollaboratorMember({projectsId,owner,project_id,author,showNotification
render: (text, item) => (
(
-
+
{text}
),
diff --git a/src/forge/Settings/Index.js b/src/forge/Settings/Index.js
index 17ae87614..3ebb5b49a 100644
--- a/src/forge/Settings/Index.js
+++ b/src/forge/Settings/Index.js
@@ -20,6 +20,14 @@ const Setting = Loadable({
loader: () => import("./Setting"),
loading: Loading,
});
+const WebhookNew = Loadable({
+ loader: () => import("./Webhooks/New"),
+ loading: Loading,
+});
+const Webhook = Loadable({
+ loader: () => import("./Webhooks/Index"),
+ loading: Loading,
+});
const Collaborator = Loadable({
loader: () => import("./Collaborator"),
loading: Loading,
@@ -41,25 +49,25 @@ class Index extends Component {
const { projectsId , owner } = this.props.match.params;
const { pathname } = this.props.history.location;
- const flag = pathname === `/projects/${owner}/${projectsId}/setting`;
+ const flag = pathname === `/${owner}/${projectsId}/settings`;
return (
-
-
+
基本设置
- -1 ? "active" : ""
+ pathname.indexOf("settings/collaborators") > -1 ? "active" : ""
}
>
-
+
协作者管理
@@ -67,21 +75,33 @@ class Index extends Component {
- -1 ? "active" : ""
+ pathname.indexOf("settings/webhooks") > -1 ? "active" : ""
}
>
-
+
+
+ Webhooks
+
+
+
+ - -1 ? "active" : ""
+ }
+ >
+
+
分支设置
- -1 ? "active" : ""}
+ className={pathname.indexOf("settings/labels") > -1 ? "active" : ""}
>
-
+
项目标签
@@ -90,7 +110,7 @@ class Index extends Component {
{/*
- -1 ? "active" : ""
+ pathname.indexOf("settings/manage") > -1 ? "active" : ""
}
>
@@ -103,49 +123,68 @@ class Index extends Component {
-
+
+ {/* webhooks */}
+ (
+
+ )}
+ >
+ (
+
+ )}
+ >
+ (
+
+ )}
+ >
{/* 协作者 */}
(
)}
>
{/* 修改仓库信息 */}
(
)}
>
(
)}
>
(
)}
>
(
)}
>
(
)}
>
{/* 修改仓库信息 */}
(
)}
diff --git a/src/forge/Settings/ManageWebNew.jsx b/src/forge/Settings/ManageWebNew.jsx
index f58d5cc70..64c52e174 100644
--- a/src/forge/Settings/ManageWebNew.jsx
+++ b/src/forge/Settings/ManageWebNew.jsx
@@ -80,7 +80,7 @@ export default Form.create()(
[],
)}
- 推送、创建,删除分支事件白名单,支持匹配符。如果为空或者 *,所有分支的事件均被触发。
语法参见 github.com/gobwas/glob 。示例: Master, ${'{master,release*}'}。
+ 推送、创建,删除分支事件白名单,支持匹配符。如果为空或者 *,所有分支的事件均被触发。
语法参见 github.com/gobwas/glob 。示例: Master, ${'{master,release*}'}。
{helper(
"",
"active",
diff --git a/src/forge/Settings/Setting.js b/src/forge/Settings/Setting.js
index dc6cbe2db..c3150323f 100644
--- a/src/forge/Settings/Setting.js
+++ b/src/forge/Settings/Setting.js
@@ -2,6 +2,8 @@ import React, { Component } from "react";
import { Form, Input, Checkbox, Select , Spin } from "antd";
import Title from '../Component/Title';
import {WhiteBack} from '../Component/layout';
+import DivertModal from '../Divert/DivertModal';
+import { Link } from 'react-router-dom';
import axios from "axios";
import "./setting.scss";
const { TextArea } = Input;
@@ -12,6 +14,7 @@ const menu = [
{name:"代码库",index:"code"},
{name:"易修 (Issue)",index:"issues"},
{name:"合并请求",index:"pulls"},
+ {name:"Wiki",index:"wiki"},
{name:"工作流(beta版)",index:"devops"},
// {name:"资源库",index:"resources"},
{name:"里程碑",index:"versions"},
@@ -25,14 +28,18 @@ class Setting extends Component {
LanguageList: undefined,
private_check: undefined,
loading:true,
- project_units:['home',"activity","code"]
+ project_units:['home',"activity","code"],
+ divertVisible:false,
+ is_transfering:undefined,
+ projectName:undefined
};
}
componentDidUpdate=(prevPros)=>{
+
if(prevPros && this.props && !this.props.checkIfLogin()){
this.props.history.push("/403")
- return
+ return;
}
}
componentDidMount = () => {
@@ -71,9 +78,12 @@ class Setting extends Component {
project_units:units
});
this.setState({
+ projectName:result.data.project_name,
private_check: result.data.private,
loading:false,
- project_units:units
+ project_units:units,
+ transfer:result.data.transfer,
+ is_transfering:result.data.is_transfering,
});
}
})
@@ -163,16 +173,15 @@ class Setting extends Component {
// 删除本仓库
deleteProject = () => {
+ const { projectsId , owner } = this.props.match.params;
+ const { projectName } = this.state;
this.props.confirm({
- content: "删除后无法恢复,是否确认删除本仓库?",
+ content: 该操作无法撤销!且将会一并删除相关的易修、合并请求、工作流、里程碑、动态等数据。
是否确认删除 {owner}/{projectName}({projectsId})?,
onOk: () => {
- const { projectsId , owner } = this.props.match.params;
const url = `/${owner}/${projectsId}.json`;
- axios
- .delete(url)
- .then((result) => {
+ axios.delete(url).then((result) => {
this.props.showNotification("仓库删除成功!");
- this.props.history.push("/projects");
+ this.props.history.push(`/${owner}`);
})
.catch((error) => {
console.log(error);
@@ -186,12 +195,61 @@ class Setting extends Component {
});
};
+ // 转移仓库
+ DivertProject=()=>{
+ this.setState({
+ divertVisible:true
+ })
+ }
+ // 取消仓库转移
+ CancelDivertProject=()=>{
+ this.props.confirm({
+ content: "是否确认取消将此项目转移给他人?",
+ onOk: () => {
+ const { projectsId , owner } = this.props.match.params;
+ const url = `/${owner}/${projectsId}/applied_transfer_projects/cancel.json`;
+ axios.post(url).then(result=>{
+ if(result && result.data){
+ this.setState({
+ is_transfering:false
+ })
+ }
+ }).catch(error=>{})
+ },
+ });
+
+ }
+ // 确定转移仓库
+ onSuccess=(owner)=>{
+ if(owner){
+ this.setState({
+ is_transfering:true,
+ transfer:owner
+ })
+ }
+ this.setState({
+ divertVisible:false
+ })
+ }
+
render() {
const { getFieldDecorator } = this.props.form;
+ const { projectsId , owner } = this.props.match.params;
+ const { projectDetail } = this.props;
- const { CategoryList, LanguageList, private_check ,loading } = this.state;
+ const { CategoryList, LanguageList, private_check ,loading , divertVisible , is_transfering, transfer } = this.state;
+ let mirror = projectDetail && projectDetail.mirror;
+ let type = projectDetail && projectDetail.type;
+ const forked_from_project_id = this.props && this.props.projectDetail && this.props.projectDetail.forked_from_project_id;
return (
+
{this.setState({divertVisible:false})}}
+ />
基本设置
@@ -215,18 +273,22 @@ class Setting extends Component {
- 将仓库设为私有
+ 将仓库设为私有
+
+ { forked_from_project_id ?`(Fork仓库的可见性实时同步自源仓库,不支持直接修改)`:`(修改仓库的可见性,将会影响到该仓库下所有Fork仓库的可见性)`}
+
)}
-
+
{getFieldDecorator("project_description", {
rules: [],
})(
)}
@@ -262,7 +324,7 @@ class Setting extends Component {
{item.name}
)
})
@@ -279,23 +341,48 @@ class Setting extends Component {
{/* 镜像设置部分,暂无接口,先不显示 */}
{/* */}
-
-
-
危险操作区
-
+ {
+ projectDetail && projectDetail.permission && (projectDetail.permission === "Admin" || projectDetail.permission === "Owner")?
+
-
删除本仓库
-
- 删除仓库是永久性的,
- 无法撤消,且删除后,与仓库关联的项目/任务/合并请求/版本发布等,均会被删除
-
+
危险操作区
+
+
+
转移仓库
+
+ {
+ is_transfering ?
+ 此仓库正在转移给【
+ {transfer && {transfer.name}}
+ 】,请联系对方接收此仓库。
+ :
+ `将此仓库转移给其他用户或组织`
+ }
+
+
+ {
+ is_transfering ?
+
取消转移
+ :
+
转移
+ }
+
+
+
+
删除本仓库
+
+ 删除仓库是永久性的,
+ 无法撤消,且删除后,与仓库关联的项目/任务/合并请求/版本发布等,均会被删除
+
+
+
+ 删除本仓库
+
+
-
- 删除本仓库
-
-
-
-
+
+ :""
+ }
);
diff --git a/src/forge/Settings/Webhooks/Index.jsx b/src/forge/Settings/Webhooks/Index.jsx
new file mode 100644
index 000000000..57df13b47
--- /dev/null
+++ b/src/forge/Settings/Webhooks/Index.jsx
@@ -0,0 +1,113 @@
+import React, { useEffect, useState } from 'react';
+import { Banner , FlexAJ } from '../../Component/layout';
+import DeleteBox from '../../Component/DeleteModal/Index';
+import './Index.scss';
+import { Button , List, Pagination } from 'antd';
+import { Link } from 'react-router-dom';
+import axios from 'axios';
+
+const limit = 15;
+function Index(props) {
+ const [ data , setData ] = useState(undefined);
+ const [ page , setPage ] = useState(1);
+ const [ total , setTotal ] = useState(1);
+ const [ deleteId , setDeleteId ] = useState(undefined);
+ const [ visible , setVisible ] = useState(false);
+ const [ url , setUrl ] = useState(undefined);
+
+ const { owner , projectsId } = props.match.params;
+
+ useEffect(()=>{
+ if(owner && projectsId){
+ getData();
+ }
+ },[owner,projectsId,page])
+
+ function getData() {
+ const url = `/${owner}/${projectsId}/webhooks.json`;
+ axios.get(url,{
+ params:{page,limit}
+ }).then(result=>{
+ if(result && result.data){
+ setData(result.data.webhooks);
+ setTotal(result.data.total_count);
+ }
+ }).catch(error=>{})
+ }
+
+ function deleteFunc(id,url) {
+ setDeleteId(id);
+ setUrl(url);
+ setVisible(true);
+ }
+
+ function onSuccess() {
+ if(deleteId){
+ const url = `/${owner}/${projectsId}/webhooks/${deleteId}.json`;
+ axios.delete(url).then(result=>{
+ if(result){
+ props.showNotification("webhook删除成功!");
+ if(page>1 && data.length === 1){
+ setPage(page-1);
+ }else{
+ getData();
+ }
+ setVisible(false);
+ }
+ }).catch(error=>{})
+ }
+ }
+
+ function addFunc() {
+ if(total >= 20){
+ return props.showNotification("webhooks数量已到上限!请删除暂不使用的webhooks以进行添加操作");
+ }
+ props.history.push(`/${owner}/${projectsId}/settings/webhooks/new`)
+ }
+
+ return(
+
+
setVisible(false)}
+ onSuccess={onSuccess}
+ title="删除Webhook"
+ content="您确定要删除此Webhook吗?"
+ subTitle={`删除后未来事件将不会推送至此Webhook地址:${url}`}
+ />
+
+
+ Webhooks
+
+
+
+
+
每当特定事件(如push代码,合并请求被编辑)发生时,我们将通过webhook给您提供的远程URL发送post请求。您可以在我们的webhooks指南中了解更多信息
+ {
+ data && data.length> 0 &&
+
+ {data.map((i,k)=>{
+ return(
+
+
+ {i.url}
+
+
+
+
+
+ )
+ })}
+
+ }
+ {
+ total > limit &&
+
+
{setPage(e)}} pageSize={limit}/>
+
+ }
+
+
+ )
+}
+export default Index;
\ No newline at end of file
diff --git a/src/forge/Settings/Webhooks/Index.scss b/src/forge/Settings/Webhooks/Index.scss
new file mode 100644
index 000000000..6c166a730
--- /dev/null
+++ b/src/forge/Settings/Webhooks/Index.scss
@@ -0,0 +1,171 @@
+.hookpanel{
+ &>p{
+ padding:20px 25px 0px 20px;
+ }
+ .ant-list{
+ .ant-list-item{
+ padding:15px 20px;
+ &:last-child{
+ border-bottom: 1px solid #e8e8e8;
+ }
+ .webName{
+ flex:1;
+ margin-right: 15px;
+ word-break: break-all;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ }
+ }
+ }
+}
+.newPanel{
+ .ant-form .ant-form-item-label label{
+ font-size: 16px;
+ }
+ .has-success .ant-form-explain{
+ font-size: 12px;
+ color: #999;
+ line-height: 20px;
+ margin-top: 5px;
+ word-break: break-all;
+ }
+ .ant-form{
+ .ant-row{
+ padding:0px 194px 0px 20px;
+ &.topLine{
+ border-top: 1px solid #EEEEEE;
+ padding-top:20px;
+ margin-top: 25px;
+ }
+ &.bottomLine{
+ border-bottom: 1px solid #EEEEEE;
+ padding-bottom:20px;
+ margin-bottom: 25px;
+ .ant-checkbox + span{
+ font-size: 16px;
+ }
+ }
+ }
+ }
+ .eventCb{
+ padding:0px 194px 0px 45px;
+ .ant-checkbox-group{
+ width: 100%;
+ }
+ .colSpan{
+ display: flex;
+ flex-wrap: wrap;
+ &>span{
+ width: 50%;
+ margin:0px;
+ display: block;
+ &>span{
+ display: block;
+ padding-left: 24px;
+ margin-bottom: 20px;
+ font-size: 12px;
+ }
+ }
+ }
+ }
+ .ant-form-item-children{
+ display: block;
+ }
+ .ant-checkbox + span{
+ color: #333;
+ &>span{
+ color: #333333;
+ height: 20px;
+ line-height: 20px;
+ padding-left: 24px;
+ font-size: 12px;
+ display: block;
+ }
+ }
+}
+.deschead{
+ background: #FAFCFF;
+ border-radius: 4px 4px 0px 0px;
+ border: 1px solid rgba(42, 97, 255, 0.23);
+ height: 50px;
+ padding:0px 20px;
+ margin-top: 20px;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+}
+.deschead.mg{
+ margin:20px!important;
+ padding:0px 12px;
+}
+.historyColl{
+ .ant-collapse-header{
+ background-color: #fff;
+ }
+ .panelHeader{
+ display: flex;
+ span{
+ width: 20%;
+ color: #333;
+ font-weight: 400;
+ }
+ .time{
+ text-align: right;
+ }
+ .name{
+ flex:1;
+ }
+ }
+ .ant-collapse-content-box{
+ background-color: #fff;
+ }
+}
+.contentMenu{
+ li{
+ font-weight: 500;
+ }
+ margin-left:14px;
+}
+.contentPanel{
+ .retitle{
+ font-size: 16px;
+ font-weight: 600;
+ color: #333333;
+ margin:20px 0px 10px!important;
+ }
+ .con{
+ background: #EEEEEE;
+ border-radius: 4px;
+ padding:15px;
+ max-height: 400px;
+ overflow: auto;
+ p{
+ font-size: 13px;
+ color:#333;
+ line-height: 28px;
+ &>span:first-child{
+ font-weight: 500;
+ }
+ }
+ }
+ .conEditor{
+ .overflow-guard{
+ border-radius: 4px;
+ }
+ .margin-view-overlays{
+ background-color: #eee;
+ &>div{
+ background-color: #eee;
+ }
+ }
+ .lines-content{
+ background-color: #eee;
+ }
+ .view-lines{
+ &>div{
+ background-color: #eee;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/forge/Settings/Webhooks/New.jsx b/src/forge/Settings/Webhooks/New.jsx
new file mode 100644
index 000000000..d90934f04
--- /dev/null
+++ b/src/forge/Settings/Webhooks/New.jsx
@@ -0,0 +1,337 @@
+import React , { forwardRef, useEffect, useState } from 'react';
+import { Form , Input , Button , Select , Radio , Checkbox } from 'antd';
+import { Banner } from '../../Component/layout';
+import { Link } from 'react-router-dom';
+import axios from 'axios';
+import PushHistory from './sub/PushHistory';
+import DeleteBox from '../../Component/DeleteModal/Index';
+import './Index.scss';
+
+// ,"issue_assign","issue_label","issue_milestone","issue_comment","issue_only","fork",
+//"pull_request_milestone","pull_request_sync","pull_request_comment","repository","pull_request_label"
+const eventArray = [
+ "create","delete","push","pull_request_assign","pull_request_review","pull_request_only"
+]
+function New({ form , match , showNotification , history }) {
+ const [ httpValue , setHttpValue ] = useState("POST");
+ const [ posthttpValue , setPostHttpValue ] = useState("json");
+ const [ condition , setCondition ] = useState("push");
+ const [ event , setEvent ] = useState(["push"]);
+ const [ visible , setVisible ] = useState(false);
+ const [ data , setData ] = useState(undefined);
+ const [ eventFlag , setEventFlag ] = useState(false);
+
+ const { getFieldDecorator, validateFields , setFieldsValue } = form;
+ const { id , owner , projectsId } = match.params;
+
+ function compareArray(params) {
+ if(params && params.length > 0){
+ if(params.length === 1 && params[0] === "push"){
+ setEvent(['push']);
+ return "push";
+ }else if(params.length === eventArray.length){
+ setEvent(eventArray);
+ return "all";
+ }else{
+ setEvent([]);
+ return "forevent";
+ }
+ }
+ }
+
+ useEffect(()=>{
+ if(id){
+ Init();
+ }else{
+ let e = compareArray(event);
+ setCondition(e);
+ setFieldsValue({
+ url:"",
+ secret:"",
+ http_method:httpValue,
+ content_type:posthttpValue,
+ eventCondition:condition,
+ event:event,
+ branch_filter:"*",
+ active:true
+ })
+ }
+ },[id])
+
+ function Init() {
+ const url = `/${owner}/${projectsId}/webhooks/${id}/edit.json`;
+ axios.get(url).then(result=>{
+ if(result){
+ let e = compareArray(result.data.events);
+ setFieldsValue({
+ ...result.data,
+ eventCondition:e,
+ active:result.data.is_active
+ })
+ setData(result.data);
+ setHttpValue(result.data.http_method);
+ setCondition(e);
+ setEvent(result.data.events);
+ }
+ }).catch(error=>{})
+ }
+
+ function changeHTTP(value) {
+ setHttpValue(value);
+ }
+
+ function changeCondition(e) {
+ setCondition(e.target.value);
+ if(e.target.value === "push"){
+ setEvent(["push"])
+ }else if(e.target.value === "all"){
+ setEvent(eventArray)
+ }else{
+ setEvent([])
+ }
+ }
+
+ function submit() {
+ validateFields((error,values)=>{
+ if(!error){
+ if(values.eventCondition === "forevent" && event.length === 0){
+ setEventFlag(true);
+ return;
+ }
+ let e = values.eventCondition === "push" ? ['push'] : values.eventCondition === "all" ? eventArray : event;
+
+ if(id){
+ // 编辑
+ const url = `/${owner}/${projectsId}/webhooks/${id}.json`;
+ axios.patch(url,{
+ webhook:{
+ ...values,
+ events:e
+ }
+ }).then(result=>{
+ if(result){
+ showNotification("webhook更新成功!");
+ history.push(`/${owner}/${projectsId}/settings/webhooks`);
+ }
+ }).catch(error=>{})
+ }else{
+ // 保存
+ const url = `/${owner}/${projectsId}/webhooks.json`;
+ axios.post(url,{
+ webhook:{
+ ...values,
+ events:e
+ }
+ }).then(result=>{
+ if(result && result.data && result.data.id){
+ showNotification("webhook新建成功!");
+ history.push(`/${owner}/${projectsId}/settings/webhooks`);
+ }
+ }).catch(error=>{})
+ }
+
+ }
+ })
+ }
+
+ function checkAddr(rule, value, callback) {
+ let reg = /(http|ftp|https):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&:/~\+#]*[\w\-\@?^=%&/~\+#])?/;
+ if(!value){
+ callback();
+ }
+ if (!reg.test(value)){
+ callback("请输入有效的URL");
+ }
+ callback();
+ }
+
+ function deleteFunc() {
+ if(id){
+ setVisible(true);
+ }
+ }
+
+ function onSuccess() {
+ if(id){
+ const url = `/${owner}/${projectsId}/webhooks/${id}.json`;
+ axios.delete(url).then(result=>{
+ if(result){
+ showNotification("webhook删除成功!");
+ history.push(`/${owner}/${projectsId}/settings/webhooks`);
+ }
+ }).catch(error=>{})
+ }
+ }
+
+ const radioStyle = {
+ display: 'block',
+ height: '30px',
+ lineHeight: '30px',
+ };
+
+ return(
+
+
setVisible(false)}
+ onSuccess={onSuccess}
+ title="删除Webhook"
+ content="您确定要删除此Webhook吗?"
+ subTitle={`删除后未来事件将不会推送至此Webhook地址:${data && data.url}`}
+ />
+
+ Webhooks
+
+ {id ? "更新" : "添加"}Webhook
+
+
+
当webhook被触发时,我们将向以下URL发送通知,包括已选择事件的详细信息。更多信息可查阅webhooks指南。
+
+ {getFieldDecorator("url",{
+ rules:[
+ {required:true,message:"请输入目标URL"},
+ {
+ validator:checkAddr
+ }
+ ]
+ })(
+
+ )}
+
+
+ {getFieldDecorator("secret",{
+ rules:[]
+ })(
+
+ )}
+
+
+ {getFieldDecorator("http_method",{
+ rules:[]
+ })(
+
+ )}
+
+
+ {getFieldDecorator("content_type",{
+ rules:[]
+ })(
+
+ )}
+
+
+ {getFieldDecorator("eventCondition",{
+ rules:[]
+ })(
+
+ 只是push事件
+ 所有事件
+ 自定义事件
+
+ )}
+
+
+
{setEvent(e)}}>
+ 代码库事件
+
+
+ 推送
+ git推送到存储库
+
+ {/*
+ 代码库
+ 创建或删除代码库
+ */}
+
+ 创建
+ 创建分支或标签
+
+
+ 删除
+ 删除分支或标签
+
+
+ 合并请求事件
+
+
+ 合并请求
+ 合并请求被打开、被关闭、被重新打开或被编辑
+
+
+ 合并请求分配
+ 合并请求被分配或取消分配
+
+ {/*
+ 合并请求收入里程碑
+ 合并请求被记录或取消记录于里程碑中
+ */}
+ {/*
+ 合并请求被评论
+ 合并请求评论被创建、编辑或删除
+ */}
+ {/*
+ 合并请求标签
+ 合并请求的标签被更新或清除
+ */}
+
+ 合并请求审查
+ 合并请求被批准、拒绝或提出审查意见,审查人员的修改,审查线程已解决或未解决
+
+ {/*
+ 合并请求被同步
+ 合并请求被同步
+ */}
+
+
+ { eventFlag &&
请选择自定义事件!}
+
+
推送、创建,删除分支事件的分支白名单,使用 glob 模式匹配指定。若为空或 *,则将报告所有分支的事件。语法文档见github.com/gobwas/glob。示例:master,{'{'}master,release*{'}'}。}
+ colon={false}
+ style={{marginTop:'15px'}}
+ >
+ {getFieldDecorator("branch_filter",{
+ rules:[]
+ })(
+
+ )}
+
+
+ {getFieldDecorator("active",{
+ valuePropName:"checked"
+ })(
+ 激活激活后触发事件的信息将发送到此Webhook地址
+ )}
+
+
+
+ {
+ id &&
+ }
+
+
+ { id && }
+
+ )
+}
+export default Form.create()(forwardRef(New));
\ No newline at end of file
diff --git a/src/forge/Settings/Webhooks/images/fault.png b/src/forge/Settings/Webhooks/images/fault.png
new file mode 100755
index 000000000..b2b35295f
Binary files /dev/null and b/src/forge/Settings/Webhooks/images/fault.png differ
diff --git a/src/forge/Settings/Webhooks/images/icon.png b/src/forge/Settings/Webhooks/images/icon.png
new file mode 100644
index 000000000..ae30823fa
Binary files /dev/null and b/src/forge/Settings/Webhooks/images/icon.png differ
diff --git a/src/forge/Settings/Webhooks/sub/PushHistory.jsx b/src/forge/Settings/Webhooks/sub/PushHistory.jsx
new file mode 100644
index 000000000..b9c6a462b
--- /dev/null
+++ b/src/forge/Settings/Webhooks/sub/PushHistory.jsx
@@ -0,0 +1,82 @@
+import React, { useEffect, useState } from 'react';
+import { Button, Collapse , Tooltip , Spin } from 'antd';
+import axios from 'axios';
+import Content from './historyContent';
+import Fault from '../images/fault.png';
+const { Panel } = Collapse;
+
+function PushHistory({id,owner,projectsId,showNotification}) {
+ const [ list , setList ] = useState(undefined);
+ const [ isSpin , setIsSpin ] = useState(false);
+
+ useEffect(()=>{
+ if(id && owner && projectsId){
+ Init();
+ }
+ },[id,owner,projectsId])
+
+ function Init() {
+ const url = `/${owner}/${projectsId}/webhooks/${id}/tasks.json`;
+ axios.get(url,{
+ params:{page:1,limit:10}
+ }).then(result=>{
+ if(result && result.data){
+ setList(result.data.tasks);
+ setIsSpin(false);
+ }
+ }).catch(error=>{})
+ }
+
+ function testFunc() {
+ setIsSpin(true);
+ const url = `/${owner}/${projectsId}/webhooks/${id}/test.json`;
+ axios.post(url).then(result=>{
+ if(result && result.data){
+ // Init();
+ showNotification("测试推送已经加入到队列,请耐心等待数秒再刷新推送记录!");
+ setIsSpin(false);
+ }
+ }).catch(error=>{setIsSpin(false);})
+ }
+
+ return(
+
+
+
最近推送历史
+
+ 刷新
+
+
+
+ {
+ list && list.length>0 &&
+
+ {
+ list.map((i,k)=>{
+ return(
+
+ {
+ i.is_succeed ?
+ 响应成功,类型:{i.response_content && i.response_content.status}}>
+
+
+ :
+
+ }
+ {i.uuid}
+ {/* {i.type} */}
+ {i.delivered_time}
+
+ }>
+
+
+ )
+ })
+ }
+
+ }
+
+ )
+}
+export default PushHistory;
\ No newline at end of file
diff --git a/src/forge/Settings/Webhooks/sub/historyContent.jsx b/src/forge/Settings/Webhooks/sub/historyContent.jsx
new file mode 100644
index 000000000..3559b98f5
--- /dev/null
+++ b/src/forge/Settings/Webhooks/sub/historyContent.jsx
@@ -0,0 +1,144 @@
+import React, { useEffect, useState } from 'react';
+import { Menu , Tag } from 'antd';
+import Nodata from '../../../Nodata';
+import ReactJson from 'react-json-view';
+import Editor from "../../../Component/Monaco";
+
+function HistoryContent({request_content,payload_content,response_content}) {
+ const [ key , setKey ] = useState("request");
+ const [ requestHeader , setRequestHeader ] = useState(undefined);
+ const [ responseHeader , setResponseHeader ] = useState(undefined);
+
+ useEffect(()=>{
+ if(request_content){
+ let arr = [];
+ Object.keys(request_content).map((item,key)=>{
+ arr.push({name:item,value:request_content[item],k:key});
+ })
+ setRequestHeader(arr);
+ }
+ },[request_content])
+
+ useEffect(()=>{
+ if(response_content && response_content.headers){
+ let arr = [];
+ Object.keys(response_content.headers).map((item,key)=>{
+ arr.push({name:item,value:response_content.headers[item],k:key});
+ })
+ setResponseHeader(arr);
+ }
+ },[response_content])
+
+ function isJSON(str) {
+ if (typeof str === 'string') {
+ try {
+ var obj=JSON.parse(str);
+ if(typeof obj === 'object' && obj ){
+ return true;
+ }else{
+ return false;
+ }
+ } catch(e) {
+ return false;
+ }
+ }
+ }
+
+
+
+ return(
+