issue:11627

This commit is contained in:
helinfeng 2025-06-23 17:18:25 +08:00
parent 0ce695ba08
commit c255a36c9d
25 changed files with 40692 additions and 12425 deletions

50066
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -182,7 +182,7 @@
"eslintConfig": {
"extends": "react-app"
},
"proxy": "http://localhost:3000",
"proxy": "http://172.16.100.182:81",
"port": "3007",
"devDependencies": {
"@babel/runtime": "7.0.0-beta.51",
@ -199,6 +199,7 @@
"mockjs": "^1.1.0",
"optimize-css-assets-webpack-plugin": "^5.0.3",
"purgecss": "^2.1.2",
"react-json-view": "^1.12.3",
"reqwest": "^2.0.5",
"resize-observer-polyfill": "^1.5.1",
"terser-webpack-plugin": "^2.3.5",

View File

@ -12,7 +12,7 @@ broadcastChannelOnmessage('refreshPage', () => {
function locationurl(list) {
if (window.location.port !== "3007") {
window.location.href = list
window.location.href = list
}
}
// TODO 开发期多个身份切换
@ -24,20 +24,21 @@ if (isDev) {
parsed = queryString.parse(_search);
}
debugType = window.location.search.indexOf('debug=t') !== -1 ? 'teacher' :
window.location.search.indexOf('debug=s') !== -1 ? 'student' :
window.location.search.indexOf('debug=a') !== -1 ? 'admin' : parsed.debug || 'admin'
}
window.location.search.indexOf('debug=s') !== -1 ? 'student' :
window.location.search.indexOf('debug=a') !== -1 ? 'admin' : parsed.debug || 'admin'
}
window._debugType = debugType;
export function initAxiosInterceptors(props) {
// 判断网络是否连接
initOnlineOfflineListener();
// var proxy = "http://172.16.100.220:81";
var proxy = 'http://172.16.100.130:81'
// var proxy = 'http://172.16.100.130:81'
var proxy = 'http://172.16.100.182:81'
//响应前的设置
axios.interceptors.request.use(
config => {
if(config.url.indexOf("http") !== -1) {
if (config.url.indexOf("http") !== -1) {
return config
}
requestProxy(config);
@ -59,7 +60,7 @@ export function initAxiosInterceptors(props) {
},
err => {
return Promise.reject(err);
});
});
axios.interceptors.response.use(function (response) {
if (response === undefined) {

View File

@ -1,7 +1,7 @@
import React , { Component } from 'react';
import { Dropdown , Menu , Icon , Pagination , Spin } from 'antd';
import React, { Component } from 'react';
import { Dropdown, Menu, Icon, Pagination, Spin } from 'antd';
import '../css/index.scss';
import '../Branch/branch.css';
import '../Branch/branch.scss';
import './activity.css';
import NoneData from '../Nodata';
@ -11,126 +11,126 @@ import axios from 'axios';
const LIMIT = 15;
const ARRAY = [
{
id:1,
name:'1天'
id: 1,
name: '1天'
},
{
id:3,
name:'3天'
id: 3,
name: '3天'
},
{
id:7,
name:'1周'
id: 7,
name: '1周'
},
{
id:30,
name:'1个月'
id: 30,
name: '1个月'
}
]
class Activity extends Component{
constructor(props){
class Activity extends Component {
constructor(props) {
super(props);
this.state={
time:'30',
type:undefined,
state:undefined,
page:1,
this.state = {
time: '30',
type: undefined,
state: undefined,
page: 1,
data:undefined,
project_trends:undefined,
data: undefined,
project_trends: undefined,
isSpin:false
isSpin: false
}
}
componentDidMount=()=>{
const { time,type,status,page } = this.state;
componentDidMount = () => {
const { time, type, status, page } = this.state;
this.setState({
isSpin:true
isSpin: true
})
this.getInfo(time,type,status,page);
this.getInfo(time, type, status, page);
}
getInfo =(time,type,status,page)=>{
const { projectsId , owner } = this.props.match.params;
const url = `/${owner}/${projectsId}/activity.json`;
axios.get(url,{
params:{
time,type,status,page
getInfo = (time, type, status, page) => {
const { projectsId, owner } = this.props.match.params;
const url = `/${owner}/${projectsId}/activity.json`;
axios.get(url, {
params: {
time, type, status, page
}
}).then(result=>{
if(result){
}).then(result => {
if (result) {
this.setState({
data:result.data,
project_trends:result.data.project_trends,
isSpin:false
data: result.data,
project_trends: result.data.project_trends,
isSpin: false
})
}
}).catch(error=>{
}).catch(error => {
console.log(error);
})
}
// 切换周期
changeTime=(e)=>{
changeTime = (e) => {
this.setState({
time:e.key,
isSpin:true
time: e.key,
isSpin: true
})
const { type,status,page } = this.state;
this.getInfo(e.key,type,status,page);
const { type, status, page } = this.state;
this.getInfo(e.key, type, status, page);
}
//筛选
changeTrends=(type,status)=>{
//筛选
changeTrends = (type, status) => {
this.setState({
type,status
type, status
})
const {time,page}=this.state;
this.getInfo(time,type,status,page);
}
const { time, page } = this.state;
this.getInfo(time, type, status, page);
}
// 分页
ChangePage=(page)=>{
ChangePage = (page) => {
this.setState({
page
})
const { time,type,status } = this.state;
this.getInfo(time,type,status,page);
const { time, type, status } = this.state;
this.getInfo(time, type, status, page);
}
menu =()=> (
menu = () => (
<Menu>
{
ARRAY && ARRAY.map((item,key)=>{
return(
ARRAY && ARRAY.map((item, key) => {
return (
<Menu.Item key={item.id} onClick={this.changeTime}>{item.name}</Menu.Item>
)
})
}
</Menu>
)
render(){
const { time , data , page , project_trends , isSpin } = this.state;
render() {
const { time, data, page, project_trends, isSpin } = this.state;
let name = time && ARRAY.filter(item=>item.id === parseInt(time)) ;
const second_per = (parseInt(data && data.close_issues_count)/parseInt(data && data.issues_count)*100)+'%';
const third_per = (parseInt(data && data.close_issues_count)/parseInt(data && data.issues_count)*100)+'%';
const fourth_per = (parseInt(data && data.open_issues_count)/parseInt(data && data.issues_count)*100)+'%';
return(
let name = time && ARRAY.filter(item => item.id === parseInt(time));
const second_per = (parseInt(data && data.close_issues_count) / parseInt(data && data.issues_count) * 100) + '%';
const third_per = (parseInt(data && data.close_issues_count) / parseInt(data && data.issues_count) * 100) + '%';
const fourth_per = (parseInt(data && data.open_issues_count) / parseInt(data && data.issues_count) * 100) + '%';
return (
<div className="main">
<div className="normalBox">
<div class="normalBox-title">概览</div>
<div className="orderInfo">
<div>
<div className="percentLine prPercent">
<p className="percent_purple" style={{width:'100%'}}></p>
<p className="percent_green resetStyle" style={{width:`${second_per}`}}></p>
<p className="percent_purple" style={{ width: '100%' }}></p>
<p className="percent_green resetStyle" style={{ width: `${second_per}` }}></p>
</div>
<span>{data && data.pr_all_count}合并请求</span>
</div>
<div>
<div className="percentLine">
<p className="percent_red" style={{width:`${third_per}`}}></p>
<p className="percent_green" style={{width:`${fourth_per}`}}></p>
<p className="percent_red" style={{ width: `${third_per}` }}></p>
<p className="percent_green" style={{ width: `${fourth_per}` }}></p>
</div>
<span>{data && data.issues_count}任务</span>
</div>
@ -138,19 +138,19 @@ class Activity extends Component{
<ul className="percentBox">
<li>
<span className="purple">{data && data.pr_count}</span>
<span className="change" onClick={()=>this.changeTrends("PullRequest","close")}>已处理的合并请求</span>
<span className="change" onClick={() => this.changeTrends("PullRequest", "close")}>已处理的合并请求</span>
</li>
<li>
<span className="green">{data && data.new_pr_count}</span>
<span className="change" onClick={()=>this.changeTrends("PullRequest","create")}>未处理的合并请求</span>
<span className="change" onClick={() => this.changeTrends("PullRequest", "create")}>未处理的合并请求</span>
</li>
<li>
<span className="red">{data && data.close_issues_count}</span>
<span className="change" onClick={()=>this.changeTrends("Issue","close")}>已关闭的任务</span>
<span className="change" onClick={() => this.changeTrends("Issue", "close")}>已关闭的任务</span>
</li>
<li>
<span className="green">{data && data.open_issues_count}</span>
<span className="change" onClick={()=>this.changeTrends("Issue","create")}>未处理的任务</span>
<span className="change" onClick={() => this.changeTrends("Issue", "create")}>未处理的任务</span>
</li>
</ul>
</div>
@ -159,7 +159,7 @@ class Activity extends Component{
<Dropdown overlay={this.menu()} trigger={['click']} placement="bottomLeft">
<a className="ant-dropdown-link">
<span className="color-grey-9 mr3">周期:</span>
{name && name.length>0 && name[0].name} <Icon type="down" />
{name && name.length > 0 && name[0].name} <Icon type="down" />
</a>
</Dropdown>
</div>
@ -167,17 +167,17 @@ class Activity extends Component{
<Spin spinning={isSpin}>
{
project_trends && project_trends.length > 0 ?
<div className="activity_list">
{
project_trends && project_trends.map((item,key)=>{
return(
<ActivityItem item={item} {...this.props} ></ActivityItem>
)
})
}
</div>
:
<NoneData _html="暂时还没有相关数据!" />
<div className="activity_list">
{
project_trends && project_trends.map((item, key) => {
return (
<ActivityItem item={item} {...this.props} ></ActivityItem>
)
})
}
</div>
:
<NoneData _html="暂时还没有相关数据!" />
}
</Spin>
{

View File

@ -1,39 +1,27 @@
import React, { Component } from 'react';
import { Dropdown, Icon, Tooltip } from 'antd';
import "./branch.css";
import React, { useState } from 'react';
import { Menu } from 'antd';
import "./branch.scss";
import CopyTool from '../Component/CopyTool';
class CloneAddress extends Component {
// 点击按钮复制功能
jsCopy = () => {
var e = document.getElementById("copy_rep_content");
e.select();
document.execCommand("Copy");
}
render() {
const { http_url, downloadUrl } = this.props;
return (
<div className="gitAddressClone">
{/* <p className="addressTips"><span>版本库地址已变更,请基于新地址提交代码</span></p> */}
{
http_url && <span>HTTP</span>
}
<input type="text" id="copy_rep_content" value={http_url} />
<Tooltip title="复制链接">
<span className="color-blue" onClick={() => this.jsCopy()}><i className="iconfont icon-fuzhi"></i></span>
</Tooltip>
{
downloadUrl &&
<span>
<Dropdown overlay={downloadUrl} trigger={['click']} placement="bottomRight">
<a className="ant-dropdown-link">
<Icon type="cloud-download" className="font-18 fl color-blue" />
</a>
</Dropdown>
</span>
}
function CloneAddress({ http_url, ssh_url, zip_url, tar_url }) {
const [key, setKey] = useState("HTTP");
return (
<div className="downMenu">
<div>
<Menu className="urlMenu" selectedKeys={[key]} mode={"horizontal"}>
<Menu.Item key="HTTP" onClick={(e) => { setKey(e.key) }}>HTTP</Menu.Item>
{ssh_url && <Menu.Item key="SSH" onClick={(e) => { setKey(e.key) }}>SSH</Menu.Item>}
</Menu>
<div className="gitAddressClone">
<input type="text" id="copy_rep_content" value={key === "HTTP" ? http_url : ssh_url} />
<CopyTool inputId="copy_rep_content" className="copytool" />
</div>
</div>
)
}
<Menu className="edu-txt-center">
{zip_url && <Menu.Item style={{ borderTop: "1px solid #eee" }}><a href={zip_url}>下载 ZIP</a></Menu.Item>}
{tar_url && <Menu.Item><a href={tar_url}>下载 TAR.GZ</a></Menu.Item>}
</Menu>
</div>
)
}
export default CloneAddress;

View File

@ -1,73 +1,73 @@
import React , { useState , useEffect } from 'react';
import { Popover , Input , Spin } from 'antd';
import './branch.css';
import { getBranch , getTag } from '../GetData/getData';
import React, { useState, useEffect } from 'react';
import { Popover, Input, Spin } from 'antd';
import './branch.scss';
import { getBranch, getTag } from '../GetData/getData';
export default (({ projectsId , branch , owner , changeBranch , branchList , tagflag = true })=>{
const [ showValue , setShowValue ] = useState(branch);
const [ inputValue , setInputValue] = useState(undefined);
const [ nav , setNav ] = useState(0);
const [ isSpin , setIsSpin ] = useState(true);
const [ flag , setFlag ] = useState(false);
export default (({ projectsId, branch, owner, changeBranch, branchList, tagflag = true }) => {
const [showValue, setShowValue] = useState(branch);
const [inputValue, setInputValue] = useState(undefined);
const [nav, setNav] = useState(0);
const [isSpin, setIsSpin] = useState(true);
const [flag, setFlag] = useState(false);
const [ data , setData ] = useState(undefined);
const [ datas , setDatas ] = useState(undefined);
const [data, setData] = useState(undefined);
const [datas, setDatas] = useState(undefined);
useEffect(()=>{
useEffect(() => {
setShowValue(branch);
},[branch])
}, [branch])
useEffect(()=>{
useEffect(() => {
document.body.addEventListener('click', e => {
let name = e.target.className;
let turn = name === "ant-input OptionsInput" || name === "navli active"|| name === "navli" || name === "padding10 bor-bottom-greyE";
if(turn){
let turn = name === "ant-input OptionsInput" || name === "navli active" || name === "navli" || name === "padding10 bor-bottom-greyE";
if (turn) {
return;
}else{
} else {
setFlag(false);
}
})
})
useEffect(()=>{
if(branchList){
useEffect(() => {
if (branchList) {
setData(branchList);
setDatas(branchList);
setIsSpin(false);
}
},[branchList])
}, [branchList])
async function getBranchs(id,owner){
let result = await getBranch(id,owner);
async function getBranchs(id, owner) {
let result = await getBranch(id, owner);
setData(result);
setDatas(result);
setIsSpin(false);
}
async function getTags(id,owner){
let result = await getTag(id,owner);
async function getTags(id, owner) {
let result = await getTag(id, owner);
setData(result);
setDatas(result);
setIsSpin(false);
}
function changeInputValue(e){
function changeInputValue(e) {
setInputValue(e.target.value);
let filter = e.target.value ? data && data.length>0 && data.filter(item=>item.name.indexOf(e.target.value)>-1) : data;
let filter = e.target.value ? data && data.length > 0 && data.filter(item => item.name.indexOf(e.target.value) > -1) : data;
setDatas(filter);
}
function changeNav(nav){
function changeNav(nav) {
setNav(nav);
setIsSpin(true);
if(nav === 0){
getBranchs(projectsId,owner);
}else{
getTags(projectsId,owner);
if (nav === 0) {
getBranchs(projectsId, owner);
} else {
getTags(projectsId, owner);
}
}
function chooseitem(value){
function chooseitem(value) {
// setShowValue(value);
changeBranch(value);
}
@ -76,33 +76,33 @@ export default (({ projectsId , branch , owner , changeBranch , branchList , tag
const menu = (
<div>
<div className="padding10 bor-bottom-greyE">
<Input
placeholder="请输入分支或标签名称搜索"
autocomplete="off" className="OptionsInput" value={inputValue}
onChange={changeInputValue} style={{width:"220px"}}
<Input
placeholder="请输入分支或标签名称搜索"
autocomplete="off" className="OptionsInput" value={inputValue}
onChange={changeInputValue} style={{ width: "220px" }}
/>
<ul className="navUl">
<li className={nav === 0?"navli active":"navli"} onClick={()=>changeNav(0)}><i className="iconfont icon-fenzhi1 font-14 mr3"></i>分支列表</li>
{ tagflag && <li className={nav === 1?"navli active":"navli"} onClick={()=>changeNav(1)}><i className="iconfont icon-biaoqian3 font-14 mr3"></i>标签列表</li> }
<li className={nav === 0 ? "navli active" : "navli"} onClick={() => changeNav(0)}><i className="iconfont icon-fenzhi1 font-14 mr3"></i>分支列表</li>
{tagflag && <li className={nav === 1 ? "navli active" : "navli"} onClick={() => changeNav(1)}><i className="iconfont icon-biaoqian3 font-14 mr3"></i>标签列表</li>}
</ul>
</div>
<Spin spinning={isSpin}>
<ul className="OptionsUl" id="ul-btn">
{
datas && datas.length>0 ?
datas.map((item,key)=>{
return(
<li key={key} onClick={()=>chooseitem(item.name)}><a className="task-hide ulALink">{item.name}</a></li>
)
}):
<p className="listTips">暂无{inputValue}{nav === 0 ?"分支":"标签"}~</p>
datas && datas.length > 0 ?
datas.map((item, key) => {
return (
<li key={key} onClick={() => chooseitem(item.name)}><a className="task-hide ulALink">{item.name}</a></li>
)
}) :
<p className="listTips">暂无{inputValue}{nav === 0 ? "分支" : "标签"}~</p>
}
</ul>
</Spin>
</div>
);
return(
<Popover placement="bottom" visible={flag} content={menu} onClick={()=>setFlag(!flag)} overlayClassName="branch-tagBox-list">
return (
<Popover placement="bottom" visible={flag} content={menu} onClick={() => setFlag(!flag)} overlayClassName="branch-tagBox-list">
<div className="branch-tagBox">
{/* {nav === 0 ?"分支":"标签"} */}
<span className="color-grey-9 mr3 ml8"><i className="iconfont icon-fenzhi2 font-18"></i></span>

View File

@ -1,84 +0,0 @@
.branchDropdown{
border:1px solid #eee;
border-radius: 4px;
display: flex;
justify-content: center;
height: 40px;
line-height: 40px;
min-width: 220px;
}
.branchDropdown .ant-dropdown-trigger{
width: 100%;
padding:0px 15px;
display: flex;
justify-content: space-between;
align-items: center;
cursor: pointer;
}
.branchOptions{
width: 220px;
box-shadow: 0px 0px 3px 1px rgba(134, 134, 134, 0.4);
border-radius: 3px;
background: #fff;
max-height: 300px;
}
.OptionsUl{
max-height: 220px;
overflow-y: auto;
}
.OptionsUl li{
height: 35px;
line-height: 35px;
cursor: pointer;
padding:0px 10px;
}
.OptionsUl li:hover{
background-color: #F0F0F0;
}
.OptionsUl li a{
display: block;
}
.OptionsInput{
height: 32px;
padding-left: 4px;
line-height: 32px;
width: 100%;
}
.branch-tagBox{
border:1px solid #eee;
border-radius: 3px;
height: 40px;
display: flex;
align-items: center;
cursor: pointer;
min-width: 140px;
}
.branch-tagBox-list .ant-popover-arrow{
display: none;
}
.branch-tagBox-list.ant-popover.ant-popover-placement-bottom{
padding-top:0px;
}
.branch-tagBox .ant-dropdown-link{
display: block;
flex:1;
}
.branch-tagBox-list .ant-popover-inner-content{
padding:0px;
}
.navUl{
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 5px;
}
.navUl li{
cursor: pointer;
}
.navUl li.active{
color:#5091FF;
}
.listTips{
padding:20px 0px;
text-align: center;
}

View File

@ -0,0 +1,131 @@
.branchDropdown {
border: 1px solid #eee;
border-radius: 4px;
display: flex;
justify-content: center;
height: 40px;
line-height: 40px;
min-width: 220px;
}
.branchDropdown .ant-dropdown-trigger {
width: 100%;
padding: 0px 15px;
display: flex;
justify-content: space-between;
align-items: center;
cursor: pointer;
}
.branchOptions {
width: 220px;
box-shadow: 0px 0px 3px 1px rgba(134, 134, 134, 0.4);
border-radius: 3px;
background: #fff;
max-height: 300px;
}
.OptionsUl {
max-height: 220px;
overflow-y: auto;
}
.OptionsUl li {
height: 35px;
line-height: 35px;
cursor: pointer;
padding: 0px 10px;
}
.OptionsUl li:hover {
background-color: #F0F0F0;
}
.OptionsUl li a {
display: block;
}
.OptionsInput {
height: 32px;
padding-left: 4px;
line-height: 32px;
width: 100%;
}
.branch-tagBox {
border: 1px solid #eee;
border-radius: 3px;
height: 40px;
display: flex;
align-items: center;
cursor: pointer;
min-width: 140px;
}
.branch-tagBox-list .ant-popover-arrow {
display: none;
}
.branch-tagBox-list.ant-popover.ant-popover-placement-bottom {
padding-top: 0px;
}
.branch-tagBox .ant-dropdown-link {
display: block;
flex: 1;
}
.branch-tagBox-list .ant-popover-inner-content {
padding: 0px;
}
.navUl {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 5px;
}
.navUl li {
cursor: pointer;
}
.navUl li.active {
color: #5091FF;
}
.listTips {
padding: 20px 0px;
text-align: center;
}
.urlMenu {
line-height: 30px;
margin-bottom: 10px;
padding: 15px 20px 0px 20px;
border-bottom: none;
li.ant-menu-item {
height: 30px;
line-height: 30px;
padding: 0px 5px;
margin-right: 20px !important;
&.ant-menu-item-selected,
&.ant-menu-item-active {
color: #333;
}
&.ant-menu-item-selected {
border-color: #466aff !important;
}
&.ant-menu-item-active {
border-color: transparent;
}
}
}
.copytool {
margin: 0px 10px;
}

View File

@ -0,0 +1,51 @@
import React, { useState, useCallback, memo } from 'react';
import { Tooltip } from 'antd';
CopyTool.defaultProps = {
beforeText: '复制链接', //
afterText: '复制成功', //
className: '', //svgclass
inputId: 'copyText', //ID
timeOut: true, //beforeText
};
function CopyTool({ beforeText, afterText, className, inputId, timeOut }) {
const [title, setTitle] = useState(() => {
return beforeText;
});
//
const copyUrl = useCallback(() => {
const copyEle = document.querySelector(`#${inputId}`); //
if (!copyEle) {
console.error("您的CopyTool未设置正确的inputId");
return;
}
copyEle.select(); //
if (document.execCommand('copy')) {
document.execCommand('copy');
}
document.getSelection().removeAllRanges();
setTitle(afterText);
if (timeOut) {
setTimeout(function () {
setTitle(beforeText);
}, 1500)
}
}, []);
return (
<Tooltip
placement="top"
title={title}
onVisibleChange={() => { setTitle(beforeText) }}
>
<i className={`iconfont icon-fuzhiicon ${className}`} style={{ color: '#466aff' }} onClick={copyUrl}></i>
</Tooltip>
);
}
export default memo(CopyTool);

View File

@ -0,0 +1,33 @@
import React from 'react';
import { AlignCenter } from '../layout';
import { Button } from 'antd';
import Modals from '../PublicModal/Index';
function DeleteBox({
visible ,
onCancel ,
onSuccess ,
title ,
subTitle,
content
}) {
return(
<Modals
title={title}
btn={
<div>
<Button size={'large'} onClick={onCancel}>取消</Button>
<Button type={"danger"} size={"large"} onClick={onSuccess}>确认删除</Button>
</div>
}
onCancel={onCancel}
visible={visible}
>
<div className="desc">
<AlignCenter className="descMain"><i className="iconfont icon-shanchu_tc_icon mr10"></i>{content}</AlignCenter>
<p className="task-hide-2" style={{WebkitLineClamp:5}}>删除后未来事件将不会推送至此Webhook地址<span title={subTitle}>{subTitle}</span></p>
</div>
</Modals>
)
}
export default DeleteBox;

View File

@ -0,0 +1,62 @@
import React, {useEffect, useRef, useState} from 'react';
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api.js';
import './Component.scss';
function Monaco(props) {
const {
style = { // dom
height: '400px',
},
value = '', //
onChange = () => { //
},
fontSize = 14, //
monacoOptions = {
scrollBeyondLastLine: false,
lineNumbers: "off",
wordWrap: true,
overviewRulerBorder: true,
lineHeight: 24,
readOnly:true
}, // monaco
language = 'html', // js ts sql css json html
} = props;
const editOrRef = useRef();
const ThisEditor = useRef();
useEffect(() => {
ThisEditor.current = monaco.editor.create(editOrRef.current, {
value: value || '',
language,
theme: "vs-grey",
fontSize: fontSize + 'px',
minimap: { //
enabled: false,
},
...monacoOptions,
});
ThisEditor.current.onDidChangeModelContent((e) => {
let newValue = ThisEditor.current.getValue();
onChange(newValue);
});
return () => {
ThisEditor.current.dispose();
ThisEditor.current = undefined; //
}
}, []);
useEffect(() => {
if (ThisEditor.current) {
ThisEditor.current.updateOptions({
fontSize: fontSize + 'px',
})
}
}, [fontSize]);
return (
<div style={style} ref={editOrRef}>
</div>
);
}
export default Monaco;

View File

@ -0,0 +1,20 @@
import React from 'react';
import { Modal } from 'antd';
import './Index.scss';
function Modals({title,children,btn,onCancel,visible}) {
return(
<Modal
visible={visible}
onCancel={onCancel}
title={title}
width={"520px"}
footer={btn}
centered={true}
wrapClassName={"deleteBox"}
>
{children}
</Modal>
)
}
export default Modals;

View File

@ -0,0 +1,75 @@
.deleteBox{
z-index: 1033;
.ant-modal-close-x{
font-size: 17px!important;
}
.ant-modal-header{
background-color: #f8f8f8;
padding:10px 30px;
.ant-modal-title{
text-align: left;
font-size: 16px;
font-weight: bold;
}
}
.ant-modal-close{
top:0px !important;
font-size: 24px !important;
}
.ant-modal-body{
padding:30px 50px;
p{
font-size: 14px;
line-height: 26px;
color:#666;
word-break: break-all;
}
.desc{
.descMain{
align-items: center;
justify-content: center;
font-size: 20px;
margin-bottom: 10px;
i.red{
color:#DF0002;
}
}
}
}
.ant-modal-footer{
border-top: none;
text-align: center;
padding-bottom: 40px;
button,a{
width: 96px;
height: 32px;
margin:0px 20px;
font-weight: 400;
font-size: 14px;
&.ant-btn{
border-color: #D0D0D0;
color: #666;
&:hover,&:active,&:focus{
background: #f3f4f6;
}
}
&.ant-btn-danger{
background-color: #fff;
color: #DF0002;
border-color: #D0D0D0;
&:hover,&:active,&:focus{
border-color: #DF0002;
background-color: #fff;
}
}
&.ant-btn.ant-btn-primary{
background-color: #466AFF;
color: #fff;
border-color: #466AFF;
&:hover,&:focus,&:active{
background-color: rgba(70,106,255,0.85);
}
}
}
}
}

View File

@ -7,6 +7,8 @@ export const Banner = styled.div`{
border-bottom:1px solid #eee;
background-color:#fff;
border-radius:5px 5px 0px 0px;
justify-content: space-between;
display: flex;
}`
export const AlignCenterBetween = styled.div`{
display:flex;

View File

@ -1,6 +1,6 @@
import React , { useEffect , useState } from 'react';
import { WhiteBack , Box , LongWidth , ShortWidth , Gap , AlignCenter , FlexAJ } from '../Component/layout';
import { Dropdown , Menu , Divider , Spin, Button } from 'antd';
import React, { useEffect, useState } from 'react';
import { WhiteBack, Box, LongWidth, ShortWidth, Gap, AlignCenter, FlexAJ } from '../Component/layout';
import { Dropdown, Menu, Divider, Spin, Button } from 'antd';
import { getImageUrl } from "educoder";
import { Link } from 'react-router-dom';
import CloneAddress from '../Branch/CloneAddress';
@ -24,45 +24,46 @@ import Invite from './sub/Invite';
* projectDetail.type:0是托管项目1是镜像项目2是同步镜像项目(为2时不支持在线创建在线上传在线修改在线删除创建合并请求等功能)
*/
function turnbar(str){
if(str && str.length>0 && str.indexOf("/")>-1){
return str.replaceAll('/','%2F');
function turnbar(str) {
if (str && str.length > 0 && str.indexOf("/") > -1) {
return str.replaceAll('/', '%2F');
}
return str;
}
function returnbar(str){
if(str && str.length>0 && str.indexOf("%2F")>-1){
return str.replaceAll('%2F','/');
function returnbar(str) {
if (str && str.length > 0 && str.indexOf("%2F") > -1) {
return str.replaceAll('%2F', '/');
}
return str;
}
function CoderDepot(props){
const [ projectDetail , setProjectDetail ]= useState(undefined);
const [ inviteCode , setInviteCode ] = useState(undefined);
const [ treeValue , setTreeValue ] = useState(undefined);
const [ treeValuePath , setTreeValuePath ] = useState(undefined);
const [ lastCommit,setLastCommit ] = useState(undefined);
const [ lastCommitAuthor,setLastCommitAuthor ] = useState(undefined);
const [ type ,setType ] = useState('dir');
const [ hide , setHide ] = useState(true);
const [ hideBtn , setHideBtn ] = useState(false);
const [ commitCount ,setCommitCount ] = useState(0);
const [ dirInfo ,setDirInfo ] = useState(undefined);//
const [ fileInfo ,setFileInfo ] = useState(undefined);//
const [ zip_url , setZip_url ] = useState(undefined);
const [ tar_url , setTar_url ] = useState(undefined);
const [ readOnly , setReadOnly] = useState(true);
const [ isSpin , setIsSpin] = useState(true);
const [ visible ,setVisible ] = useState(false);
const [ mainFlag ,setMainFlag ] = useState(false);
const [ openModal , setOpenModal ] = useState(false);
const [ desc , setDesc ] = useState(undefined);
const [ website , setWebsite ] = useState(undefined);
const [ lesson_url , setLessonUrl ] = useState(undefined);
const [ readme , setReadme ] = useState(undefined);
const [ defaultBranch , setDefaultBranch ] = useState(undefined);
const [ editReadme , setEditReadme ] = useState(false);
function CoderDepot(props) {
const [projectDetail, setProjectDetail] = useState(undefined);
const [inviteCode, setInviteCode] = useState(undefined);
const [treeValue, setTreeValue] = useState(undefined);
const [treeValuePath, setTreeValuePath] = useState(undefined);
const [lastCommit, setLastCommit] = useState(undefined);
const [lastCommitAuthor, setLastCommitAuthor] = useState(undefined);
const [type, setType] = useState('dir');
const [hide, setHide] = useState(true);
const [hideBtn, setHideBtn] = useState(false);
const [commitCount, setCommitCount] = useState(0);
const [dirInfo, setDirInfo] = useState(undefined);//
const [fileInfo, setFileInfo] = useState(undefined);//
const [zip_url, setZip_url] = useState(undefined);
const [tar_url, setTar_url] = useState(undefined);
const [readOnly, setReadOnly] = useState(true);
const [isSpin, setIsSpin] = useState(true);
const [visible, setVisible] = useState(false);
const [mainFlag, setMainFlag] = useState(false);
const [openModal, setOpenModal] = useState(false);
const [desc, setDesc] = useState(undefined);
const [website, setWebsite] = useState(undefined);
const [lesson_url, setLessonUrl] = useState(undefined);
const [readme, setReadme] = useState(undefined);
const [defaultBranch, setDefaultBranch] = useState(undefined);
const [editReadme, setEditReadme] = useState(false);
const [downLoadType, setDownLoadType] = useState("http");
const owner = props.match.params.owner;
const projectsId = props.match.params.projectsId;
@ -71,8 +72,8 @@ function CoderDepot(props){
const details = props.projectDetail;
let pathname = props.history.location.pathname;
useEffect(()=>{
if(details){
useEffect(() => {
if (details) {
setProjectDetail(details);
setDesc(details.description);
setWebsite(details.website);
@ -80,40 +81,40 @@ function CoderDepot(props){
setDefaultBranch(details.default_branch);
setInviteCode(details.invite_code);
}
},[details])
}, [details])
useEffect(()=>{
if(treeValue){
useEffect(() => {
if (treeValue) {
setTreeValuePath(treeValue.split('/'));
}else{
} else {
setTreeValuePath(undefined);
}
},[treeValue])
}, [treeValue])
useEffect(()=>{
if (projectsId && owner && defaultBranch){
let b = turnbar(branchName) ;
if(pathname.indexOf(`/projects/${owner}/${projectsId}`) > -1 && pathname.indexOf(`/tree/${b}/`) > -1) {
useEffect(() => {
if (projectsId && owner && defaultBranch) {
let b = turnbar(branchName);
if (pathname.indexOf(`/projects/${owner}/${projectsId}`) > -1 && pathname.indexOf(`/tree/${b}/`) > -1) {
let url = pathname.split(`/tree/${b}/`)[1];
setTreeValue(url);
getFileInfo(url,branchName);
getFileInfo(url, branchName);
setType("file");
}else{
} else {
setTreeValue(undefined);
getDirInfo(branchName || defaultBranch);
setType("dir");
}
}
},[projectsId,owner,pathname,defaultBranch])
}
}, [projectsId, owner, pathname, defaultBranch])
//
function getDirInfo(branch){
function getDirInfo(branch) {
setIsSpin(true);
const url = `/${owner}/${projectsId}/entries.json`;
axios.get(url, {
params: { ref: branch }
axios.get(url, {
params: { ref: branch }
}).then((result) => {
if (result) {
setCommitCount(result.data.commits_count);
@ -130,38 +131,37 @@ function CoderDepot(props){
setEditReadme(false);
setHide(true);
}
setTimeout(function(){setIsSpin(false);},500);
}).catch(error=>{setIsSpin(false);})
setTimeout(function () { setIsSpin(false); }, 500);
}).catch(error => { setIsSpin(false); })
}
useEffect(()=>{
if(projectDetail && lastCommit)
{
useEffect(() => {
if (projectDetail && lastCommit) {
let ele = document.getElementById("ptxt");
if(ele){
if (ele) {
let h = ele.offsetHeight;
if( h > 18 ) setHideBtn(true);
if (h > 18) setHideBtn(true);
}
}
},[projectDetail,lastCommit])
}, [projectDetail, lastCommit])
//
function getFileInfo(path, ref){
function getFileInfo(path, ref) {
setIsSpin(true);
const url = `/${owner}/${projectsId}/sub_entries.json`;
axios.get(url, {
params:{
filepath:path,
ref:ref || branchName,
axios.get(url, {
params: {
filepath: path,
ref: ref || branchName,
type
}
}).then((result) => {
if (result) {
let en = result.data.entries;
if(en.type){
if (en.type) {
setDirInfo(undefined);
setFileInfo(en);
setType(en.type);
}else{
} else {
setFileInfo(undefined);
setDirInfo(en);
setType("dir");
@ -173,65 +173,65 @@ function CoderDepot(props){
setReadOnly(!editReadme);
setHide(true);
}
setTimeout(function(){setIsSpin(false);},500)
}).catch(error=>{setIsSpin(false);})
setTimeout(function () { setIsSpin(false); }, 500)
}).catch(error => { setIsSpin(false); })
}
//
function changeBranch(value){
function changeBranch(value) {
let checkvalue = turnbar(value);
let url = `/projects/${owner}/${projectsId}${value && `/tree/${checkvalue}`}${treeValue ? `/${treeValue}`:""}`;
let url = `/projects/${owner}/${projectsId}${value && `/tree/${checkvalue}`}${treeValue ? `/${treeValue}` : ""}`;
props.history.push(url);
}
//
function fileMenu(){
function fileMenu() {
let b = branchName || defaultBranch;
let checkvalue = turnbar(b);
return (
<Menu>
<Menu.Item><a onClick={()=>urlLink(`/projects/${owner}/${projectsId}/${checkvalue}/uploadfile${treeValue === undefined ? "" : `/${treeValue}`}`)}>上传文件</a></Menu.Item>
<Menu.Item><a onClick={()=>urlLink(`/projects/${owner}/${projectsId}/${checkvalue}/newfile${treeValue === undefined ? "" : `/${treeValue}`}`)}>新建文件</a></Menu.Item>
</Menu>
<Menu>
<Menu.Item><a onClick={() => urlLink(`/projects/${owner}/${projectsId}/${checkvalue}/uploadfile${treeValue === undefined ? "" : `/${treeValue}`}`)}>上传文件</a></Menu.Item>
<Menu.Item><a onClick={() => urlLink(`/projects/${owner}/${projectsId}/${checkvalue}/newfile${treeValue === undefined ? "" : `/${treeValue}`}`)}>新建文件</a></Menu.Item>
</Menu>
)
}
function getPathUrl(array,index){
if(array && array.length>0 && index){
function getPathUrl(array, index) {
if (array && array.length > 0 && index) {
let str = "";
for(let i=0;i<index;i++){
for (let i = 0; i < index; i++) {
str += `/${array[i]}`;
}
return str.substr(1);
}
}
//
function returnMain(){
function returnMain() {
setTreeValue(undefined);
let branch = branchName || defaultBranch;
let checkvalue = turnbar(branch);
props.history.push(`/projects/${owner}/${projectsId}/tree/${checkvalue}`);
};
//
function returnUlr(url){
function returnUlr(url) {
let enBranch = turnbar(branchName);
props.history.push(`/projects/${owner}/${projectsId}/tree${enBranch?`/${enBranch}`:""}/${url}`);
props.history.push(`/projects/${owner}/${projectsId}/tree${enBranch ? `/${enBranch}` : ""}/${url}`);
}
//
function goToSubRoot(path,type,filename){
if(type!=="submodule"){
function goToSubRoot(path, type, filename) {
if (type !== "submodule") {
let enBranch = branchName || defaultBranch;
let checkvalue = turnbar(enBranch);
setType(type);
props.history.push(`/projects/${owner}/${projectsId}${`/tree/${checkvalue}`}${path?`/${path}`:""}`);
props.history.push(`/projects/${owner}/${projectsId}${`/tree/${checkvalue}`}${path ? `/${path}` : ""}`);
}
}
function onEdit(readOnly){
function onEdit(readOnly) {
setReadOnly(readOnly);
setEditReadme(false);
}
function ChangeFile(path, readOnly){
function ChangeFile(path, readOnly) {
//
let enBranch = branchName || defaultBranch;
let checkvalue = turnbar(enBranch);
@ -240,12 +240,12 @@ function CoderDepot(props){
setEditReadme(true);
};
function changeHide(hide){
function changeHide(hide) {
setHide(!hide);
}
function urlLink(link){
if(props.checkIfLogin()===false){
function urlLink(link) {
if (props.checkIfLogin() === false) {
props.showLoginDialog()
return false;
}
@ -253,25 +253,20 @@ function CoderDepot(props){
}
const downloadMenu = (
<div className="downMenu">
<div style={{padding:"20px",borderBottom:"1px solid #eee"}}>
<CloneAddress
http_url={projectDetail && projectDetail.clone_url}
showNotification={props.showNotification}/>
</div>
<Menu className="edu-txt-center">
<Menu.Item><a href={zip_url}>下载 ZIP</a></Menu.Item>
<Menu.Item><a href={tar_url}>下载 TAR.GZ</a></Menu.Item>
</Menu>
</div>
<CloneAddress
http_url={projectDetail && projectDetail.clone_url}
ssh_url={(projectDetail && props && props.platform) && projectDetail.ssh_url}
zip_url={(props && props.platform) && zip_url}
tar_url={(props && props.platform) && tar_url}
showNotification={props.showNotification} />
)
// website
function okUpdate(d,w,l){
function okUpdate(d, w, l) {
const url = `/${owner}/${projectsId}.json`;
axios.put(url,{
description:d,website:w,lesson_url:l
}).then(result=>{
if(result && result.data && result.data.id){
axios.put(url, {
description: d, website: w, lesson_url: l
}).then(result => {
if (result && result.data && result.data.id) {
setDesc(result.data.description);
setWebsite(result.data.website);
setLessonUrl(result.data.lesson_url);
@ -279,35 +274,35 @@ function CoderDepot(props){
})
}
let n = fileInfo && fileInfo.name;
const mdFlag = n && n.substring(n.length-3,n.length) === ".md";
const mdFlag = n && n.substring(n.length - 3, n.length) === ".md";
const { current_user } = props;
const fileOperate = type === "dir" && projectDetail && projectDetail.type !== 2 && ((projectDetail.permission && projectDetail.permission !=="Reporter") || (current_user && current_user.admin));
return(
const fileOperate = type === "dir" && projectDetail && projectDetail.type !== 2 && ((projectDetail.permission && projectDetail.permission !== "Reporter") || (current_user && current_user.admin));
return (
<WhiteBack>
<UpdateDescModal desc={desc} website={website} lesson_url={lesson_url} visible={openModal} onCancel={()=>setOpenModal(false)} onOk={okUpdate}/>
<UpdateDescModal desc={desc} website={website} lesson_url={lesson_url} visible={openModal} onCancel={() => setOpenModal(false)} onOk={okUpdate} />
<Spin spinning={isSpin}>
{
(dirInfo || fileInfo) &&
(dirInfo || fileInfo) &&
<React.Fragment>
<DrawerPanel
history={props.history}
owner={owner}
projectsId={projectsId}
<DrawerPanel
history={props.history}
owner={owner}
projectsId={projectsId}
name={projectDetail && projectDetail.name}
branch={branchName || defaultBranch}
visible={visible}
onClose={()=>setVisible(false)}
list = {mainFlag ? dirInfo : undefined}
branch={branchName || defaultBranch}
visible={visible}
onClose={() => setVisible(false)}
list={mainFlag ? dirInfo : undefined}
/>
<div className="drawerBtn" onClick={()=>setVisible(true)}>
<div className="drawerBtn" onClick={() => setVisible(true)}>
<i className="iconfont icon-youjiantou font-16"></i>
<span>目录</span>
</div>
</React.Fragment>
}
<div style={{minHeight:"500px"}}>
{
<div style={{ minHeight: "500px" }}>
{
projectDetail &&
<Box className="Panels">
<LongWidth>
@ -317,17 +312,17 @@ function CoderDepot(props){
<div className="mr20">
{
props && props.platform ?
<SelectBranch
repo_id={projectDetail && projectDetail.repo_id}
projectsId={projectsId}
branch={branchName || defaultBranch}
changeBranch={changeBranch}
owner={owner}
history={props.history}
branchList={projectDetail && projectDetail.branches && projectDetail.branches.list}
></SelectBranch>
:
<span>分支<span className="color-grey-6">{branchName || defaultBranch}</span></span>
<SelectBranch
repo_id={projectDetail && projectDetail.repo_id}
projectsId={projectsId}
branch={branchName || defaultBranch}
changeBranch={changeBranch}
owner={owner}
history={props.history}
branchList={projectDetail && projectDetail.branches && projectDetail.branches.list}
></SelectBranch>
:
<span>分支<span className="color-grey-6">{branchName || defaultBranch}</span></span>
}
</div>
<AlignCenter className="mr20">
@ -346,17 +341,17 @@ function CoderDepot(props){
<AlignCenter>
<div className="mr20 addOptionBtn">
{
projectDetail.type !== 2 &&
<a onClick={()=>urlLink(`/projects/${owner}/${projectsId}/pulls/new`)} >+ 合并请求</a>
projectDetail.type !== 2 &&
<a onClick={() => urlLink(`/projects/${owner}/${projectsId}/pulls/new`)} >+ 合并请求</a>
}
<a onClick={()=>urlLink(`/projects/${owner}/${projectsId}/issues/new`)} >+ 任务</a>
<a onClick={() => urlLink(`/projects/${owner}/${projectsId}/issues/new`)} >+ 任务</a>
</div>
{ fileOperate &&
{fileOperate &&
<Dropdown overlay={fileMenu()} className="mr20" trigger={['click']}>
<Button type="default">文件 <i className="iconfont icon-sanjiaoxing-down ml3 font-14 color-grey-9"></i></Button>
</Dropdown>
}
<Dropdown overlay={downloadMenu} placement="bottomRight" trigger={['click']}>
<Button type={'primary'}>下载 <i className="iconfont icon-sanjiaoxing-down ml3 font-14 color-white"></i></Button>
</Dropdown>
@ -364,81 +359,81 @@ function CoderDepot(props){
</FlexAJ>
{
dirInfo || fileInfo ?
<div className="listtable">
{
lastCommit &&
<div className="listtablehead">
<User url={getImageUrl(`/${lastCommitAuthor && lastCommitAuthor.image_url}`)} name={lastCommitAuthor && lastCommitAuthor.name} id={lastCommitAuthor && lastCommitAuthor.id} login={lastCommitAuthor && lastCommitAuthor.login}/>
<div className={hideBtn && hide ? "ellipsistxt hidetxt" :"ellipsistxt"}>
<pre id="ptxt">{lastCommit && lastCommit.message}</pre>
</div>
{ hideBtn && <span className="ellipsis" onClick={()=>changeHide(hide)}><i className="iconfont icon-shenglvehao"></i></span> }
<div className="listtable">
{
lastCommit &&
<div className="listtablehead">
<User url={getImageUrl(`/${lastCommitAuthor && lastCommitAuthor.image_url}`)} name={lastCommitAuthor && lastCommitAuthor.name} id={lastCommitAuthor && lastCommitAuthor.id} login={lastCommitAuthor && lastCommitAuthor.login} />
<div className={hideBtn && hide ? "ellipsistxt hidetxt" : "ellipsistxt"}>
<pre id="ptxt">{lastCommit && lastCommit.message}</pre>
</div>
{hideBtn && <span className="ellipsis" onClick={() => changeHide(hide)}><i className="iconfont icon-shenglvehao"></i></span>}
<span className="ml12 color-grey-9 mt3">{lastCommit && lastCommit.time_from_now}</span>
{ commitCount ? <Link to={`/projects/${owner}/${projectsId}/commits`} className="ml12 color-grey-9"><i className="iconfont icon-tijiao mr3 font-17 color-grey-9"></i>{commitCount}次提交</Link>:"" }
</div>
}
<ul className="listtablebody">
{
treeValuePath && treeValuePath.length > 0 &&
<Path
identifier={projectDetail && projectDetail.identifier}
treeValuePath={treeValuePath}
returnUlr={returnUlr}
returnMain={returnMain}
getPathUrl={getPathUrl}
/>
<span className="ml12 color-grey-9 mt3">{lastCommit && lastCommit.time_from_now}</span>
{commitCount ? <Link to={`/projects/${owner}/${projectsId}/commits`} className="ml12 color-grey-9"><i className="iconfont icon-tijiao mr3 font-17 color-grey-9"></i>{commitCount}次提交</Link> : ""}
</div>
}
{
dirInfo && dirInfo.length > 0 &&
dirInfo.map((item,key)=>{
return(
<Catalogue
owner={owner}
item={item}
projectsId={projectsId}
goToSubRoot={goToSubRoot}
/>
)
})
}
{
fileInfo &&
<CoderRootFileDetail
{...props}
detail={fileInfo}
readOnly={readOnly}
md={mdFlag}
onEdit={onEdit}
currentBranch={branchName || defaultBranch}
type={projectDetail.type}
></CoderRootFileDetail>
}
</ul>
</div>
: ""
<ul className="listtablebody">
{
treeValuePath && treeValuePath.length > 0 &&
<Path
identifier={projectDetail && projectDetail.identifier}
treeValuePath={treeValuePath}
returnUlr={returnUlr}
returnMain={returnMain}
getPathUrl={getPathUrl}
/>
}
{
dirInfo && dirInfo.length > 0 &&
dirInfo.map((item, key) => {
return (
<Catalogue
owner={owner}
item={item}
projectsId={projectsId}
goToSubRoot={goToSubRoot}
/>
)
})
}
{
fileInfo &&
<CoderRootFileDetail
{...props}
detail={fileInfo}
readOnly={readOnly}
md={mdFlag}
onEdit={onEdit}
currentBranch={branchName || defaultBranch}
type={projectDetail.type}
></CoderRootFileDetail>
}
</ul>
</div>
: ""
}
{
(dirInfo && dirInfo.length === 0) && (fileInfo && fileInfo.length === 0) ? <Nodata _html="暂未发现文件"/> :""
}
(dirInfo && dirInfo.length === 0) && (fileInfo && fileInfo.length === 0) ? <Nodata _html="暂未发现文件" /> : ""
}
{/* readme文件显示(显示文件详情时不显示readme文件) */}
{ dirInfo && (readme && readme.content) ? <ReadMe ChangeFile={ChangeFile} readme={readme} operate={props && (props.isManager || props.isDeveloper) && projectDetail.type !==2 } history={props.history} /> :"" }
{dirInfo && (readme && readme.content) ? <ReadMe ChangeFile={ChangeFile} readme={readme} operate={props && (props.isManager || props.isDeveloper) && projectDetail.type !== 2} history={props.history} /> : ""}
</div>
</LongWidth>
{
!fileInfo &&
!fileInfo &&
<ShortWidth>
<Gap style={{paddingLeft:"30px"}}>
<Gap style={{ paddingLeft: "30px" }}>
<div className="panelmenu">
<FlexAJ className="font-18 color-grey-6 mb20" style={{lineHeight:"28px"}}>简介
{projectDetail.permission && (projectDetail.permission==="Admin" || projectDetail.permission==="Owner") && <i onClick={()=>setOpenModal(true)} className="iconfont icon-anquanshezhi color-grey-9 font-15"></i>}
<FlexAJ className="font-18 color-grey-6 mb20" style={{ lineHeight: "28px" }}>简介
{projectDetail.permission && (projectDetail.permission === "Admin" || projectDetail.permission === "Owner") && <i onClick={() => setOpenModal(true)} className="iconfont icon-anquanshezhi color-grey-9 font-15"></i>}
</FlexAJ>
{desc && <p className="font-14 color-grey-9 mb15 task-hide-2" style={{lineHeight:"22px",WebkitLineClamp:"4",textAlign:"justify",wordBreak:"break-all"}}>{desc}</p>}
{desc && <p className="font-14 color-grey-9 mb15 task-hide-2" style={{ lineHeight: "22px", WebkitLineClamp: "4", textAlign: "justify", wordBreak: "break-all" }}>{desc}</p>}
{
website &&
<p className="color-grey-6 df">
<i className="iconfont icon-lianjie2 font-15 mr10 color-grey-9"></i>
<a href={website} target="_blank" style={{wordBreak:"break-all",lineHeight:"20px",marginTop:"5px",textDecoration:"underline"}}>{website}</a>
<a href={website} target="_blank" style={{ wordBreak: "break-all", lineHeight: "20px", marginTop: "5px", textDecoration: "underline" }}>{website}</a>
</p>
}
<p>
@ -458,10 +453,10 @@ function CoderDepot(props){
}
</div>
{
inviteCode &&
inviteCode &&
<div>
<Divider />
<Invite code={inviteCode} className={"detailsCode"}/>
<Invite code={inviteCode} className={"detailsCode"} />
</div>
}
{
@ -469,7 +464,7 @@ function CoderDepot(props){
<div>
<Divider />
<p className="font-16 color-grey-6">实践课程</p>
<a href={lesson_url} target="_blank" className="color-grey-6" style={{textDecoration:"underline"}}>{lesson_url}</a>
<a href={lesson_url} target="_blank" className="color-grey-6" style={{ textDecoration: "underline" }}>{lesson_url}</a>
</div>
}
{/* 发布 */}
@ -477,7 +472,7 @@ function CoderDepot(props){
projectDetail && projectDetail.release_versions &&
<React.Fragment>
<Divider />
<Releases owner={owner} projectsId={projectsId} releaseVersions={projectDetail.release_versions} history={props.history}/>
<Releases owner={owner} projectsId={projectsId} releaseVersions={projectDetail.release_versions} history={props.history} />
</React.Fragment>
}
{/* 贡献者 */}
@ -486,10 +481,10 @@ function CoderDepot(props){
<Contributors contributors={projectDetail && projectDetail.contributors} owner={owner} projectsId={projectsId} />
}
{/* 语言 */}
{ projectDetail && projectDetail.languages &&
{projectDetail && projectDetail.languages &&
<React.Fragment>
<Divider />
<LanguagePower languages={projectDetail.languages}/>
<LanguagePower languages={projectDetail.languages} />
</React.Fragment>
}
</Gap>

View File

@ -1,46 +1,53 @@
/* recommandProjects */
.recommandProjects{
.recommandProjects {
display: flex;
max-width: 1200px;
margin:20px auto;
margin: 20px auto;
}
.recommandProjects >div{
.recommandProjects>div {
background-color: #fff;
border-radius: 10px;
width: 220px;
margin-right: 25px;
cursor: pointer;
border: 1px solid #eee;
&:last-child{
&:last-child {
margin-right: 0px;
}
}
.recommandProjects > div:hover{
.recommandProjects>div:hover {
box-shadow: 0px 2px 20px 0px rgba(0, 0, 0, 0.1);
}
.recommandProjects > div .mainInfo{
.recommandProjects>div .mainInfo {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 160px;
border-bottom: 1px solid #eee;
padding:20px;
padding: 20px;
box-sizing: border-box;
img{
img {
height: 50px;
width:50px;
width: 50px;
border-radius: 50%;
}
.name{
.name {
font-size: 13px;
color: #666;
height: 18px;
line-height: 18px;
margin-top:12px;
margin-top: 12px;
}
.school{
margin-top:12px;
.school {
margin-top: 12px;
color: #333;
font-size: 16px;
height: 22px;
@ -48,24 +55,28 @@
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
max-width:100%;
max-width: 100%;
}
}
.recommandProjects{
.baseInfo{
padding:18px 15px;
.recommandProjects {
.baseInfo {
padding: 18px 15px;
display: flex;
font-size: 12px;
color:#888;
.look{
i{
color: #888;
.look {
i {
margin-right: 5px;
}
margin-right: 10px;
}
.type{
flex:1;
width:0;
.type {
flex: 1;
width: 0;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
@ -73,268 +84,323 @@
}
}
}
// coderDepot
.Panels{
.Panels {
max-width: 1200px;
margin: 0 auto;
.panelmenu{
padding-top:30px;
.panelmenu {
padding-top: 30px;
}
.addOptionBtn{
.addOptionBtn {
height: 32px;
line-height: 30px;
display: flex;
border:1px solid #d9d9d9;
border: 1px solid #d9d9d9;
border-radius: 2px;
a{
padding:0px 13px;
a {
padding: 0px 13px;
color: rgba(0, 0, 0, 0.65);
cursor: pointer;
}
& > a:first-child{
&>a:first-child {
border-right: 1px solid #d9d9d9;
}
}
.infoCount{
.infoCount {
display: inline-block;
padding:0px 5px;
padding: 0px 5px;
height: 16px;
line-height: 16px;
background-color: #eee;
color:#999;
color: #999;
border-radius: 12px;
margin-left: 10px;
font-size: 12px;
}
.attrPerson{
.attrPerson {
padding-top: 15px;
display: flex;
flex-wrap: wrap;
a{
a {
margin: 10px 10px 0px 0px;
img{
img {
border-radius: 50%;
width: 35px;
height: 35px;
}
&:nth-child(6){
&:nth-child(6) {
margin-right: 0px;
}
}
}
.progress{
.progress {
display: flex;
border-radius: 10px;
height: 7px;
margin-top: 12px;
span{
&:first-child{
span {
&:first-child {
border-radius: 10px 0px 0px 10px;
}
&:last-child{
&:last-child {
border-radius: 0px 10px 10px 0px;
}
}
}
.progresstip{
.progresstip {
margin-top: 15px;
flex-wrap: wrap;
i.zero{
i.zero {
position: absolute;
display: block;
border-radius: 50%;
height: 8px;
width: 8px;
left: 0px;
top:10px
top: 10px
}
&>span{
&>span {
padding-left: 15px;
position: relative;
min-width: 33.5%;
span{
span {
color: #666;
&:last-child{
&:last-child {
color: #999;
margin-left: 5px;
}
}
}
}
.listtable{
.listtable {
margin-top: 20px;
border:1px solid #d9d9d9;
border: 1px solid #d9d9d9;
border-radius: 4px;
.listtablehead{
.listtablehead {
display: flex;
justify-content: space-between;
align-items: flex-start;
border-bottom: 1px solid #d9d9d9;
padding:7px 20px;
padding: 7px 20px;
border-radius: 4px 4px 0px 0px;
background-color: #FAFBFC;
.ellipsistxt{
.ellipsistxt {
margin-top: 6px;
#ptxt{
#ptxt {
margin-bottom: 0px;
word-break: break-all;
overflow: unset;
white-space:pre-wrap; /* css3.0 */
white-space:-moz-pre-wrap; /* Firefox */
white-space:-pre-wrap; /* Opera 4-6 */
white-space:-o-pre-wrap; /* Opera 7 */
word-wrap:break-word;
white-space: pre-wrap;
/* css3.0 */
white-space: -moz-pre-wrap;
/* Firefox */
white-space: -pre-wrap;
/* Opera 4-6 */
white-space: -o-pre-wrap;
/* Opera 7 */
word-wrap: break-word;
}
margin-left: 13px;
line-height:18px;
flex:1;
width: 0;
color: #666;
&.hidetxt{
&.hidetxt {
height: 18px;
overflow: hidden;
position: relative;
padding-right:8px;
&::after{
padding-right: 8px;
&::after {
position: absolute;
right: 0px;
bottom: 0px;
content:"...";
content: "...";
}
}
}
.ellipsis{
.ellipsis {
margin-left: 8px;
cursor: pointer;
border-radius: 2px;
background-color: #c1c1c1;
padding:0px 4px;
padding: 0px 4px;
height: 14px;
line-height: 14px;
margin-top: 9px;
i{
font-size: 15px!important;
i {
font-size: 15px !important;
color: #fff;
height: 14px;
line-height: 14px;
}
}
}
.listtablebody{
li.listtablepath{
a{color: #40a9ff;}
p{
margin-bottom: 0px!important;
.listtablebody {
li.listtablepath {
a {
color: #40a9ff;
}
p {
margin-bottom: 0px !important;
}
}
& > li{
&>li {
height: 42px;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid #d9d9d9;
padding:0px 20px 0px 24px;
& > span:first-child{
padding: 0px 20px 0px 24px;
&>span:first-child {
width: 30%;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
& > span:nth-child(2){
&>span:nth-child(2) {
width: 60%;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
& > span:nth-child(3){
&>span:nth-child(3) {
width: 10%;
text-align: right;
}
}
&> li:last-child{
&>li:last-child {
border-bottom: none;
}
}
}
}
.drawerBtn{
.drawerBtn {
position: fixed;
left: -13px;
border:1px solid rgb(207,205,223);
border: 1px solid rgb(207, 205, 223);
width: 34px;
border-radius: 0px 12px 12px 0px;
height: 70px;
top:50%;
top: 50%;
margin-top: -35px;
cursor: pointer;
display: flex;
flex-direction: column;
align-items: flex-end;
justify-content: center;
&:hover{
box-shadow: 1px 0px 7px rgba(0,0,0,0.1);
&:hover {
box-shadow: 1px 0px 7px rgba(0, 0, 0, 0.1);
}
span{
span {
writing-mode: vertical-lr;
color: #202429;
width: 25px;
}
i{
i {
color: #24292e;
height: 18px;
line-height: 18px;
width: 18px;
}
}
.downMenu{
box-shadow: 0px 0px 9px rgba(134, 134, 134,0.4);
.downMenu {
width: 329px;
box-shadow: 0px 0px 9px rgba(134, 134, 134, 0.4);
background-color: #fff;
.ant-menu-vertical .ant-menu-item:hover{
.ant-menu-vertical .ant-menu-item:hover {
background-color: #e6f7ff;
}
}
.menuslist{
.menuslist {
max-height: 200px;
overflow-y: auto;
padding:10px 15px;
padding: 10px 15px;
border-radius: 4px;
.ant-dropdown-menu-item{
.ant-dropdown-menu-item {
border-radius: 8px;
text-align: left!important;
a{
text-align: left !important;
a {
width: 350px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
}
.ant-dropdown-menu-item.active{
.ant-dropdown-menu-item.active {
background-color: #e6f7ff;
}
}
.catelogue{
border:1px solid rgb(211, 211, 211);
.catelogue {
border: 1px solid rgb(211, 211, 211);
font-size: 15px;
font-weight: normal;
border-radius: 5px;
margin-right: 10px;
padding:0px 10px;
padding: 0px 10px;
height: 30px;
line-height: 30px;
color: #666!important;
color: #666 !important;
display: flex;
align-items: center;
span{
span {
margin-top: 1px;
}
}
.submoduleStyle{
.submoduleStyle {
cursor: default;
i{
i {
cursor: default;
}
&:hover{
&:hover {
color: #05101a;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -7,7 +7,7 @@ import "./setting.scss";
import Loadable from "react-loadable";
import Loading from "../../Loading";
import { Box, Long, Short, Gap } from '../Component/layout';
import {Badge} from 'antd'
import { Badge } from 'antd'
const BranchNew = Loadable({
loader: () => import("./BranchRule"),
@ -25,6 +25,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,
@ -43,7 +51,7 @@ const ManageNew = Loadable({
});
class Index extends Component {
render() {
const { projectsId , owner } = this.props.match.params;
const { projectsId, owner } = this.props.match.params;
const { pathname } = this.props.history.location;
const flag = pathname === `/projects/${owner}/${projectsId}/setting`;
@ -70,6 +78,18 @@ class Index extends Component {
</Link>
</p>
</li>
<li
className={
pathname.indexOf("setting/webhooks") > -1 ? "active" : ""
}
>
<p>
<Link to={`/projects/${owner}/${projectsId}/setting/webhooks`} className="w-100">
<i className="iconfont icon-a-xuanzhongwebhookicon font-18 mr10 color-grey-9"></i>
网络钩子
</Link>
</p>
</li>
<li
className={
pathname.indexOf("setting/audit") > -1 ? "active" : ""
@ -77,8 +97,8 @@ class Index extends Component {
>
<p>
<Link to={`/projects/${owner}/${projectsId}/setting/audit`} className="w-100">
<i className="iconfont icon-zhongqingdianxinicon10 font-18 mr10"></i>
{(this.props.projectDetail && this.props.projectDetail.pending_verify_count) ? <Badge dot offset={[6,-2]}><span style={{color:'#05101a',fontSize:'16px'}}>审核管理</span></Badge> : ''}
<i className="iconfont icon-zhongqingdianxinicon10 font-18 mr10"></i>
{(this.props.projectDetail && this.props.projectDetail.pending_verify_count) ? <Badge dot offset={[6, -2]}><span style={{ color: '#05101a', fontSize: '16px' }}>审核管理</span></Badge> : ''}
</Link>
</p>
</li>
@ -89,7 +109,7 @@ class Index extends Component {
>
<p>
<Link to={`/projects/${owner}/${projectsId}/setting/branch`} className="w-100">
<i className="iconfont icon-fenzhi font-20 mr10"></i>
<i className="iconfont icon-fenzhi font-20 mr10"></i>
分支设置
</Link>
</p>
@ -122,6 +142,25 @@ class Index extends Component {
<Long>
<Gap className="list-r-Info">
<Switch {...this.props}>
{/* webhooks */}
<Route
path="/projects/:owner/:projectsId/setting/webhooks/new"
render={(props) => (
<WebhookNew {...this.props} {...props} {...this.state} />
)}
></Route>
<Route
path="/projects/:owner/:projectsId/setting/webhooks/:id"
render={(props) => (
<WebhookNew {...this.props} {...props} {...this.state} />
)}
></Route>
<Route
path="/projects/:owner/:projectsId/setting/webhooks"
render={(props) => (
<Webhook {...this.props} {...props} {...this.state} />
)}
></Route>
{/* 协作者 */}
<Route
path="/projects/:owner/:projectsId/setting/collaborator"
@ -151,7 +190,7 @@ class Index extends Component {
<Route
path="/projects/:owner/:projectsId/setting/branch"
render={(props) => (
<Branch {...this.props} {...props } {...this.state} />
<Branch {...this.props} {...props} {...this.state} />
)}
></Route>
<Route

View File

@ -0,0 +1,111 @@
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 >= 50) {
return props.showNotification("webhooks数量已到上限请删除暂不使用的webhooks以进行添加操作");
}
props.history.push(`/projects/${owner}/${projectsId}/setting/webhooks/new`)
}
return (
<div>
<DeleteBox
visible={visible}
onCancel={() => setVisible(false)}
onSuccess={onSuccess}
title="删除Webhook"
content="您确定要删除此Webhook吗"
subTitle={`删除后未来事件将不会推送至此Webhook地址${url}`}
/>
<Banner>
<span>Webhooks(网络钩子)</span>
<Button type="primary" size="large" onClick={addFunc}>添加Webhook</Button>
</Banner>
<div className="hookpanel">
<p className="color-grey-3">每当特定事件如push代码合并请求被编辑发生时我们将通过webhook给您提供的远程URL发送post请求您可以在我们的<a className="color-blue hoverLine" target="_blank" href="https://forum.trustie.net/forums/3408/detail">webhooks指南</a>中了解更多信息</p>
{
data && data.length > 0 &&
<List>
{data.map((i, k) => {
return (
<List.Item key={k}>
<i className="iconfont icon-a-xuanzhongwebhookicon color-grey-d mr12 font-17"></i>
<Link to={`/${owner}/${projectsId}/settings/webhooks/${i.id}`} className="webName">{i.url}</Link>
<span>
<Button ghost type={"primary"} onClick={() => { props.history.push(`/projects/${owner}/${projectsId}/settings/webhooks/${i.id}`) }}>编辑</Button>
<Button ghost className="ml20" type="danger" onClick={() => { deleteFunc(i.id, i.url) }}>删除</Button>
</span>
</List.Item>
)
})}
</List>
}
{
total > limit &&
<div className="edu-txt-center mt20 mb20">
<Pagination current={page} total={total} onChange={e => { setPage(e) }} pageSize={limit} />
</div>
}
</div>
</div>
)
}
export default Index;

View File

@ -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;
}
}
}
}

View File

@ -0,0 +1,339 @@
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(`/projects/${owner}/${projectsId}/setting/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(`/projects/${owner}/${projectsId}/setting/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(`/projects/${owner}/${projectsId}/setting/webhooks`);
}
}).catch(error => { })
}
}
const radioStyle = {
display: 'block',
height: '30px',
lineHeight: '30px',
};
return (
<div className="newPanel">
<DeleteBox
visible={visible}
onCancel={() => setVisible(false)}
onSuccess={onSuccess}
title="删除Webhook"
content="您确定要删除此Webhook吗"
subTitle={`${data && data.url}`}
/>
<Banner>
<span>
<Link to={`/${owner}/${projectsId}/settings/webhooks`} className="color-blue">Webhooks</Link>
<i className="iconfont icon-youjiantou ml5 mr5 font-12"></i>
<span>{id ? "更新" : "添加"}Webhook</span>
</span>
</Banner>
<div>
<p className="deschead mg"><span>当webhook被触发时我们将向以下URL发送通知包括已选择事件的详细信息更多信息可查阅<a className="color-blue hoverLine" target="_blank" href="https://forum.trustie.net/forums/3408/detail">webhooks指南</a></span></p>
<Form>
<input type="password" style={{ display: "none" }} />
<Form.Item label="目标URL" colon={false}>
{getFieldDecorator("url", {
rules: [
{ required: true, message: "请输入目标URL" },
{
validator: checkAddr
}
]
})(
<Input placeholder="请输入目标URL" maxLength="100" size="large" autocomplete='off' />
)}
</Form.Item>
<Form.Item label="Webhook密钥" colon={false}>
{getFieldDecorator("secret", {
rules: []
})(
<Input.Password placeholder="请输入Webhook密钥" autocomplete='new-password' size="large" maxLength={"50"} />
)}
</Form.Item>
<Form.Item label="Http请求方法" colon={false}>
{getFieldDecorator("http_method", {
rules: []
})(
<Select onSelect={changeHTTP} size="large">
<Select.Option value="GET">GET</Select.Option>
<Select.Option value="POST">POST</Select.Option>
</Select>
)}
</Form.Item>
<Form.Item
label="POST请求类型"
colon={false}
style={{ display: httpValue === "POST" ? "block" : "none" }}
>
{getFieldDecorator("content_type", {
rules: []
})(
<Select size="large">
<Select.Option value="json">application/json</Select.Option>
<Select.Option value="form">application/x-www-form-urlencoded</Select.Option>
</Select>
)}
</Form.Item>
<Form.Item label="您希望哪些事件触发此webhook" colon={false} className="topLine">
{getFieldDecorator("eventCondition", {
rules: []
})(
<Radio.Group onChange={changeCondition}>
<Radio style={radioStyle} value="push">只是push事件</Radio>
<Radio style={radioStyle} value="all">所有事件</Radio>
<Radio style={radioStyle} value="forevent">自定义事件</Radio>
</Radio.Group>
)}
</Form.Item>
<div className="eventCb"
style={{ display: condition === "forevent" ? "block" : "none" }}
>
<Checkbox.Group value={event} onChange={(e) => { setEvent(e) }}>
<p className="color-grey-3 mb10">代码库事件</p>
<div className="colSpan">
<span>
<Checkbox value="push">推送</Checkbox>
<span>git推送到存储库</span>
</span>
{/* <span>
<Checkbox value="repository">代码库</Checkbox>
<span>创建或删除代码库</span>
</span> */}
<span>
<Checkbox value="create">创建</Checkbox>
<span>创建分支或标签</span>
</span>
<span>
<Checkbox value="delete">删除</Checkbox>
<span>删除分支或标签</span>
</span>
</div>
<p className="color-grey-3 mt10 mb10">合并请求事件</p>
<div className="colSpan">
<span>
<Checkbox value="pull_request_only">合并请求</Checkbox>
<span>合并请求被打开被关闭被重新打开或被编辑</span>
</span>
<span>
<Checkbox value="pull_request_assign">合并请求分配</Checkbox>
<span>合并请求被分配或取消分配</span>
</span>
{/* <span>
<Checkbox value="pull_request_milestone">合并请求收入里程碑</Checkbox>
<span>合并请求被记录或取消记录于里程碑中</span>
</span> */}
{/* <span>
<Checkbox value="pull_request_comment">合并请求被评论</Checkbox>
<span>合并请求评论被创建编辑或删除</span>
</span> */}
{/* <span>
<Checkbox value="pull_request_label">合并请求标签</Checkbox>
<span>合并请求的标签被更新或清除</span>
</span> */}
{/* <span>
<Checkbox value="pull_request_review">合并请求审查</Checkbox>
<span>合并请求被批准拒绝或提出审查意见审查人员的修改审查线程已解决或未解决</span>
</span> */}
{/* <span>
<Checkbox value="pull_request_sync">合并请求被同步</Checkbox>
<span>合并请求被同步</span>
</span> */}
</div>
</Checkbox.Group>
{eventFlag && <span style={{ color: "#DF0002" }}>请选择自定义事件</span>}
</div>
<Form.Item
label="分支过滤"
className="topLine"
help={<span>推送创建删除分支事件的分支白名单使用 glob 模式匹配指定若为空或 *则将报告所有分支的事件语法文档见<a href="http://github.com/gobwas/glob" target="_blank" className="hoverLine color-blue">github.com/gobwas/glob</a>示例master,{'{'}master,release*{'}'}</span>}
colon={false}
style={{ marginTop: '15px' }}
>
{getFieldDecorator("branch_filter", {
rules: []
})(
<Input size="large" />
)}
</Form.Item>
<Form.Item
className="topLine bottomLine"
colon={false}
style={{ height: "110px", paddingBottom: "0px" }}
>
{getFieldDecorator("active", {
valuePropName: "checked"
})(
<Checkbox>激活<span className="subCbDesc">激活后触发事件的信息将发送到此Webhook地址</span></Checkbox>
)}
</Form.Item>
<Button type="primary" onClick={submit} className="ml20" style={{ width: id ? "" : "100px" }}>{id ? "更新Webhook" : "添加"}</Button>
{
id && <Button type="danger" className="ml20" onClick={deleteFunc}>删除</Button>
}
</Form>
</div>
{id && <PushHistory id={id} owner={owner} projectsId={projectsId} showNotification={showNotification} />}
</div>
)
}
export default Form.create()(forwardRef(New));

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 778 B

View File

@ -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(
<div className="pt30">
<div className="deschead">
<span className="font-16">最近推送历史</span>
<span>
<a className="color-blue" onClick={Init}>刷新</a>
<Button type="primary" className="ml20" onClick={testFunc} loading={isSpin}>测试推送</Button>
</span>
</div>
{
list && list.length>0 &&
<Collapse accordion bordered={false} className="historyColl">
{
list.map((i,k)=>{
return(
<Panel header={
<div className="panelHeader">
{
i.is_succeed ?
<Tooltip title={<span className="pl20 pr20">响应成功类型{i.response_content && i.response_content.status}</span>}>
<i className="iconfont icon-chenggongicon font-14 mr10" style={{color:"#2DB44D"}}></i>
</Tooltip>
:
<img src={Fault} alt="" className="mr10" height="15px" style={{marginTop:"2px"}}/>
}
<span className="name">{i.uuid}</span>
{/* <span>{i.type}</span> */}
<span className="time">{i.delivered_time}</span>
</div>
}>
<Content request_content={i.request_content && i.request_content.headers} payload_content={i.payload_content} response_content={i.response_content}/>
</Panel>
)
})
}
</Collapse>
}
</div>
)
}
export default PushHistory;

View File

@ -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(
<div className="contentPanel">
<div>
<Menu selectedKeys={[key]} onSelect={(e)=>setKey(e.key)} mode={"horizontal"} className="contentMenu">
<Menu.Item key={"request"}><i className="iconfont icon-qingqiuicon font-15 mr5"></i>请求内容</Menu.Item>
<Menu.Item key={"response"}><i className="iconfont icon-xiangyingicon font-16 mr5"></i>响应内容
{response_content && <Tag color={response_content.status === 200 ? "#2DB44D":"#CA0002"} style={{margin:"0px",marginLeft:"3px",fontSize:"12px"}}>{response_content.status}</Tag>}
</Menu.Item>
</Menu>
</div>
<div style={{paddingLeft:"16px"}}>
{
key === "request" ?
<div>
{
requestHeader && requestHeader.length > 0 &&
<div>
<p className="retitle">头信息</p>
<div className="con">
{
requestHeader.map((i,k)=>{
return(
<p><span>{i.name}: </span><span>{i.value}</span></p>
)
})
}
</div>
</div>
}
{
payload_content &&
<div>
<p className="retitle">内容</p>
<div className="con">
<ReactJson
src={payload_content}
name={false}
displayObjectSize={false}
displayDataTypes={false}
enableClipboard={false}
/>
</div>
</div>
}
</div>
:
<div>
{
(!responseHeader && ((response_content && !response_content.body) || !response_content))?
<Nodata _html="暂无数据~"/>
:""
}
{
responseHeader && responseHeader.length > 0 &&
<div>
<p className="retitle">头信息</p>
<div className="con">
{
responseHeader.map((i,k)=>{
return(
<p><span>{i.name}: </span><span>{i.value}</span></p>
)
})
}
</div>
</div>
}
{
response_content && response_content.body &&
<div>
<p className="retitle">响应体</p>
{
isJSON(response_content.body) ?
<div className="con">
<ReactJson
enableClipboard={false}
src={JSON.parse(response_content.body)}
name={false}
displayObjectSize={false}
displayDataTypes={false}
/>
</div>
:
<div className="conEditor">
<Editor value={response_content.body}/>
</div>
}
</div>
}
</div>
}
</div>
</div>
)
}
export default HistoryContent;