Merge branch 'develop' into dev_orgnize

This commit is contained in:
caishi 2021-01-29 14:45:34 +08:00
commit 7b7b206741
12 changed files with 447 additions and 70 deletions

View File

@ -27,7 +27,7 @@ if (isDev) {
}
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=a') !== -1 ? 'admin' : parsed.debug || 'student'
}
function clearAllCookie() {
cookie.remove('_educoder_session', { path: '/' });

View File

@ -1,5 +1,5 @@
import React , { useState , useEffect } from 'react';
import { Spin } from 'antd';
import { Spin , Pagination } from 'antd';
import { Blueback } from '../Component/layout';
import List from './Dispose/List';
import Head from './Dispose/head';
@ -11,13 +11,16 @@ import styled from 'styled-components';
const Div = styled.div`{
padding:24px 30px;
}`;
const limit = 15;
function Dispose(props){
const [ spining , setSpining ] = useState(true);
const [ updateInfo , setUpdateInfo ] = useState(undefined);
const [ list , setList ] = useState(undefined);
const [ permission , setPermission ] = useState(undefined);
const [ visible , setVisible ] = useState(false);
const [ page , setPage ] = useState(1);
const [ totalCount , setTotalCount ] = useState(0);
const [ branchList , setBranchList ] =useState(undefined);
const projectDetail = props.projectDetail;
@ -36,7 +39,8 @@ function Dispose(props){
const url = `/ci/pipelines/list.json`;
axios.get(url,{
params:{
identifier:projectsId
identifier:projectsId,
page,limit
}
}).then(result=>{
if(result && result.data){
@ -48,20 +52,30 @@ function Dispose(props){
useEffect(()=>{
Init();
},[])
},[page])
useEffect(()=>{
if(owner && projectsId){
const url = `/${owner}/${projectsId}/branches.json`;
axios.get(url).then(result=>{
if(result && result.data){
setBranchList(result.data);
}
}).catch(error=>{})
}
},[owner,projectsId])
// 线
function addNew(pipeline_name,id){
function addNew(pipeline_name,id,branch,event){
setVisible(true);
setUpdateInfo(undefined);
if(pipeline_name){
let l = {pipeline_name,id}
let l = {pipeline_name,id,branch,event}
setUpdateInfo(l);
}
}
function onOk(pipeline_name,updateId){
function onOk(pipeline_name,updateId,branch,event){
if(pipeline_name){
if(!updateId){
//
@ -69,7 +83,7 @@ function Dispose(props){
axios.post(url,{
pipeline_name,
file_name:".trustie-pipeline.yml",
identifier:projectsId
repo:projectsId,branch,event,owner
}).then(result=>{
setVisible(false);
if(result && result.data){
@ -83,7 +97,7 @@ function Dispose(props){
//
const url = `/ci/pipelines/${updateId}.json`;
axios.put(url,{
pipeline_name
pipeline_name,repo:projectsId,branch,event,owner
}).then(result=>{
if(result && result.data){
setVisible(false);
@ -110,17 +124,28 @@ function Dispose(props){
}).catch(error=>{})
}
//
function toModalManage(){
props.history.push(`/projects/cxt/DevOps-mo/devops/mould`);
}
const operate = current_user && (permission && permission !== "Reporter");
return(
<Spin spinning={spining}>
<PipelineName visible={visible} value={updateInfo} onCancel={()=>{setVisible(false);}} onOk={onOk}/>
<PipelineName branchList={branchList} visible={visible} value={updateInfo} onCancel={()=>setVisible(false)} onOk={onOk}/>
<div className="disposePanel">
<Head />
<Head manager={ operate ? toModalManage : undefined} />
<Div>
{ operate && (!list ||(list && list.length === 0)) && <Blueback onClick={()=>addNew(undefined,undefined)}>新增流水线</Blueback> }
{ operate && <Blueback onClick={()=>addNew(undefined,undefined)}>新增流水线</Blueback> }
<div className="mt20 disposeList">
<List list={list} operate={operate} projectsId={projectsId} owner={owner} showModal={addNew} deleteFunc={deleteFunc}/>
{
totalCount > limit &&
<div className="mt20 pb20" style={{textAlign:'center'}}>
<Pagination simple current={page} pageSize={limit} total={totalCount} onChange={(page)=>setPage(page)}/>
</div>
}
</div>
</Div>
</div>

View File

@ -1,9 +1,9 @@
import React from 'react';
import Editor from 'react-monaco-editor';
function Editors({value,onChange,theme,height,visible}){
function Editors({value,onChange,theme,height,visible,width="100%",Numbers="on"}){
const editor_options = {
lineNumbers: "on",
lineNumbers: Numbers,
wordWrap: true, //
selectOnLineNumbers: true,
lineHeight: 24,
@ -23,6 +23,7 @@ function Editors({value,onChange,theme,height,visible}){
return(
<Editor
height={height}
width={width}
language={"yaml"}
theme={theme}
placeholder="请输入内容"

View File

@ -2,6 +2,14 @@ import React from 'react';
import { Table , Popconfirm } from 'antd';
import { Link } from 'react-router-dom';
const STATUS = {
running:"运行中",
failure:"未通过",
error:"未通过",
success:"已通过",
killed:"已撤销",
pending:"准备中"
}
function List({ list, operate , projectsId , owner , showModal , deleteFunc }){
const columns = [
@ -9,11 +17,11 @@ function List({ list, operate , projectsId , owner , showModal , deleteFunc }){
title:"流水线名称",
dataIndex:"pipeline_name",
key:1,
width:"20%",
width:"19%",
ellipsis:true,
render:(txt,item)=>{
return(
<span onDoubleClick={()=>showModal(txt,item.id)} style={{display:"block",cursor:"pointer"}}>{txt}</span>
<span onDoubleClick={()=>showModal(txt,item.id,item.branch,item.event)} style={{display:"block",cursor:"pointer"}}>{txt}</span>
)
}
},
@ -21,7 +29,7 @@ function List({ list, operate , projectsId , owner , showModal , deleteFunc }){
title:"文件名称",
dataIndex:"file_name",
key:1,
width:"17%",
width:"15%",
className:"color-blue",
ellipsis:true
},
@ -29,14 +37,31 @@ function List({ list, operate , projectsId , owner , showModal , deleteFunc }){
title:"创建时间",
dataIndex:"created_at",
key:1,
width:"18%",
width:"15%",
ellipsis:true
},
{
title:"最近构建时间",
dataIndex:"last_build_time",
key:1,
width:"15%",
ellipsis:true
},
{
title:"最近构建状态",
dataIndex:"pipeline_status",
key:1,
width:"15%",
ellipsis:true,
render:(txt)=>{
return(STATUS[txt])
}
},
{
title:"操作",
dataIndex:"operation",
key:1,
width:"30%",
width:"21%",
render:(txt,item)=>{
return(
<span>
@ -49,7 +74,7 @@ function List({ list, operate , projectsId , owner , showModal , deleteFunc }){
<a className="mr10 color-grey-6"><i className="iconfont icon-lajitong font-13 mr3"></i>删除</a>
</Popconfirm>:""
}
<Link to={`/projects/${owner}/${projectsId}/devops/list`} className="color-grey-6"><i className="iconfont icon-yunhang font-13 mr3"></i>查看运行记录</Link>
<Link to={`/projects/${owner}/${projectsId}/devops/list/${item.branch}`} className="color-grey-6"><i className="iconfont icon-yunhang font-13 mr3"></i>查看运行记录</Link>
</span>
)
}

View File

@ -1,17 +1,29 @@
import React , { useEffect , useState } from 'react';
import { Modal , Input } from 'antd';
import { Modal , Input , Select } from 'antd';
const { Option }= Select;
function PipelineName({visible,onCancel,onOk,value}){
const EVENT = ["push","pull_request","tag","cron","custom","promote","rollback"]
function PipelineName({visible,onCancel,onOk,value ,branchList}){
const [ name , setName ] = useState(undefined);
const [ branchValue , setBranchValue ] = useState(undefined);
const [ eventValue , setEventValue ] = useState(EVENT[0]);
useEffect(()=>{
if(branchList && branchList.length>0){
setBranchValue(branchList[0].name);
}
},[branchList])
useEffect(()=>{
if(value){
setName(value.pipeline_name);
setBranchValue(value.branch);
setEventValue(value.event);
}
},[value])
function onSure(){
onOk(name,value && value.id);
onOk(name,value && value.id,branchValue,eventValue);
}
return(
<Modal
@ -26,6 +38,27 @@ function PipelineName({visible,onCancel,onOk,value}){
<span>流水线名称:</span>
<Input value={name} onChange={(e)=>setName(e.target.value)} placeholder="请输入名称" style={{width:"340px",margin:"6px 0px"}}/>
</div>
<div className="choosenList mt20">
<span>触发条件:</span>
<Select value={branchValue} style={{width:"160px"}} onChange={(e)=>setBranchValue(e)}>
{
branchList && branchList.length>0 && branchList.map((item,key)=>{
return(
<Option value={item.name} key={key}>{item.name}</Option>
)
})
}
</Select>
<Select value={eventValue} style={{width:"160px",marginLeft:"20px"}} onChange={(e)=>setEventValue(e)}>
{
EVENT.map((item,key)=>{
return(
<Option value={item} key={key}>{item}</Option>
)
})
}
</Select>
</div>
</Modal>
)
}

View File

@ -1,12 +1,17 @@
import React from 'react';
import { AlignCenterBetween } from '../../Component/layout';
import { AlignCenterBetween , Blueline , FlexAJ } from '../../Component/layout';
function head(){
function head({manager}){
return(
<AlignCenterBetween>
<span className="font-20">工作流配置</span>
<a href={`https://forum.trustie.net/forums/3111/detail`} target="_blank" className="color-grey-6"><i className="iconfont icon-tishi1 font-14 mr3"></i>模板使用说明</a>
<FlexAJ>
<a href={`https://forum.trustie.net/forums/3111/detail`} target="_blank" className="color-grey-6"><i className="iconfont icon-tishi1 font-14 mr3"></i>模板使用说明</a>
{
manager && <Blueline style={{marginLeft:"20px"}} onClick={manager}>模板管理</Blueline>
}
</FlexAJ>
</AlignCenterBetween>
)
}

View File

@ -22,20 +22,11 @@ const Stucture = Loadable({
loader: () => import('./Structure'),
loading: Loading,
})
const Mould = Loadable({
loader: () => import('./Mould'),
loading: Loading,
})
export default ((props)=>{
const { projectsId , owner } = props.match.params;
const open_devops = props.projectDetail && props.projectDetail.open_devops;
//
// useEffect(()=>{
// if(open_devops !== undefined){
// if(open_devops){
// props.history.replace(`/projects/${owner}/${projectsId}/devops/dispose`);
// }else{
// props.history.replace(`/projects/${owner}/${projectsId}/devops`);
// }
// }
// },[open_devops])
return(
<WhiteBack className="opsPanel">
@ -45,6 +36,11 @@ export default ((props)=>{
(p) => (<New {...props} {...p}/>)
}
></Route>
<Route path="/projects/:owner/:projectsId/devops/mould"
render={
(p) => (<Mould {...props} {...p}/>)
}
></Route>
<Route path="/projects/:owner/:projectsId/devops/dispose/new"
render={
(p) => (<New {...props} {...p}/>)
@ -55,7 +51,7 @@ export default ((props)=>{
(p) => (<Dispose {...props} {...p}/>)
}
></Route>
<Route path="/projects/:owner/:projectsId/devops/list"
<Route path="/projects/:owner/:projectsId/devops/list/:branch"
render={
(p) => (<Stucture {...props} {...p}/>)
}

155
src/forge/DevOps/Mould.jsx Normal file
View File

@ -0,0 +1,155 @@
import React , { useEffect , useState , useRef } from 'react';
import { Banner , Blueback , FlexAJ , Spin } from '../Component/layout';
import { Input , Table ,Pagination , Select , Popconfirm } from 'antd';
import styled from 'styled-components';
import axios from 'axios';
import New from './MouldNew';
const { Option } = Select;
const Div = styled.div`{
padding:24px 30px;
min-height:420px;
}`;
const STAGE = [
{stage_name:"所有",stage_type:"all"},
  {stage_name:"初始化",stage_type:"init"},
  {stage_name:"编译构建",stage_type:"build"},
  {stage_name:"部署",stage_type:"deploy"},
  {stage_name:"其他",stage_type:"customize"}
]
const limit = 15;
function Mould(props){
const [ visible ,setVisible ] = useState(false);
const [ list ,setList ] = useState(undefined);
const [ page ,setPage ] = useState(1);
const [ totalCount ,setTotalCount ] = useState(0);
const [ stageType ,setStageType ] = useState("all");
const [ search ,setSearch ] = useState(undefined);
const childRef = useRef();
useEffect(()=>{
Init(page,stageType);
},[page,stageType])
function Init(page,stageType,searchvalue){
const url = `/ci/templates/list.json`;
axios.get(url,{
params:{
page,limit,stage_type:stageType,name:searchvalue
}
}).then(result=>{
if(result && result.data){
setList(result.data.templates);
setTotalCount(result.data.total_count);
}
}).catch(error=>{})
}
const columns=[
{
title:"名称",
dataIndex:"template_name",
key:1,
ellipsis:true
},
{
title:"所属阶段",
dataIndex:"stage_type",
key:2,
ellipsis:true,
render:(txt,item)=>{
let i = STAGE.filter(item=>item.stage_type === txt);
return i && i.length>0 && i[0].stage_name
}
},
{
title:"模板类型",
dataIndex:"category",
key:3,
ellipsis:true
},
{
title:"操作",
dataIndex:"operation",
key:4,
ellipsis:true,
render:(txt,item)=>{
return(
<span>
<a className="mr10 color-grey-6" onClick={()=>editMouldFunc(item)}><i className="iconfont icon-zaibianji font-13 mr3"></i>编辑</a>
<Popconfirm title={"确定要删除此模板?"} onConfirm={()=>deleteMouldFunc(item.id)} okText="确定" cancelText={"取消"}>
<a className="mr10 color-grey-6"><i className="iconfont icon-lajitong font-13 mr3"></i>删除</a>
</Popconfirm>
</span>
)
}
}
]
//
function editMouldFunc(item){
if (childRef.current) {
childRef.current.setEditInfo(item);
}
setVisible(true);
}
//
function deleteMouldFunc(id){
const url = `/ci/templates/${id}.json`;
axios.delete(url).then(result=>{
if(result && result.data){
props.showNotification("模板删除成功!");
Init(page,stageType,search);
}
})
}
function searchValue(){
Init(page,stageType,search);
}
function newMouldFunc(){
if (childRef.current) {
childRef.current.setEditInfo(undefined);
}
setVisible(true);
}
function onOk(){
Init(page,"all");
}
return(
<div>
<New wrappedComponentRef={(f) => childRef.current = f} ref={childRef} visible={visible} onCancel={()=>setVisible(false)} onOk={onOk}></New>
<Banner>工作流 - 模板管理</Banner>
<Div className="disposeList">
<FlexAJ>
<Blueback onClick={newMouldFunc}>新建模板</Blueback>
<FlexAJ>
<span className="mr10">阶段</span>
<Select onChange={e=>setStageType(e)} value={stageType} style={{width:"180px"}}>
{
STAGE.map((item,key)=>{
return(
<Option value={item.stage_type}>{item.stage_name}</Option>
)
})
}
</Select>
<Input placeholder="请输入模板名称" value={search} onChange={(e)=>setSearch(e.target.value)} allowClear style={{width:"160px",marginLeft:"15px"}}/>
<Blueback className="ml15" onClick={searchValue}>搜索</Blueback>
</FlexAJ>
</FlexAJ>
<Table className="mt20" size="small" columns={columns} dataSource={list} rowKey={(row)=>row.id} pagination={false}></Table>
{
totalCount > limit &&
<div className="mt20 pb20" style={{textAlign:'center'}}>
<Pagination simple current={page} pageSize={limit} total={totalCount} onChange={(page)=>setPage(page)}/>
</div>
}
</Div>
</div>
)
}
export default Mould;

View File

@ -0,0 +1,139 @@
import React , { useImperativeHandle , useState , forwardRef , useCallback } from 'react';
import { Form , Modal , Input , Select , Spin } from 'antd';
import Editor from './Dispose/Editors';
import axios from 'axios';
const { Option } = Select;
const TYPE = ["Java","C","C++","Python","Go","Ruby","R","PHP",
    "Perl","Node","Docker","Rust","Swift","Erlang","Other"]
function MouldNew({ form , visible , onCancel , onOk }, ref){
const [value , setValue ] = useState(undefined);
const [isSpin , setIsSpin ] = useState(false);
const [valueFlag , setValueFlag ] = useState(false);
const [ id , setId ] = useState(false);
const { getFieldDecorator, validateFields , setFieldsValue , getFieldsValue } = form;
useImperativeHandle(ref, () => ({
setEditInfo: (info) => {
if(info){
setFieldsValue({
...info
})
setValue(info.content);
setId(info.id);
}else{
clear();
setId(undefined);
}
}
}))
const helper = useCallback(
(label, name, rules, widget, className, isRequired,flag) => (
<Form.Item label={label} >
{getFieldDecorator(name, { rules, validateFirst: true , valuePropName:flag ? "checked":"value" })(widget)}
</Form.Item>
),
[]
);
function changeContent(v){
if(v){
setValue(v);
setValueFlag(false);
}
}
function cancel(){
clear();
onCancel();
}
function sure(){
if(!value){
setValueFlag(true);
return;
}
validateFields((error,values)=>{
if(!error){
setIsSpin(true);
const url = `/ci/templates.json`;
axios.post(url,{
...values,id,content:value
}).then(result=>{
if(result && result.data){
setIsSpin(false);
cancel();
onOk();
}
}).catch(error=>{})
}
})
}
function clear(){
setFieldsValue({
stage_type:"all",
template_name:undefined,
category:"Java",
})
setValue("");
setValueFlag(false);
}
return(
<Modal
visible={visible}
width="500px"
title={"新建/编辑模板"}
onCancel={cancel}
onOk={sure}
centered={true}
>
<Spin spinning={isSpin}>
<Form layout={"inline"}>
{helper(
"所属阶段",
"stage_type",
[{required:true,message:"请选择所属阶段"}],
<Select placeholder="请选择所属阶段" style={{width:"350px"}}>
<Option value="all">所有</Option>
<Option value="init">初始化</Option>
<Option value="build">编译构建</Option>
<Option value="deploy">部署</Option>
<Option value="customize">其他</Option>
</Select>,true
)}
{helper(
"模板名称",
"template_name",
[{required:true,message:"请输入模板名称"}],
<Input placeholder="请输入模板名称" style={{width:"350px"}}/>,true
)}
{helper(
"模板分类",
"category",
[{required:true,message:"请选择模板分类"}],
<Select placeholder="请选择模板分类" style={{width:"350px"}}>
{
TYPE.map((item,key)=>{
return(
<Option value={item}>{item}</Option>
)
})
}
</Select>,true
)}
<div style={{display:'flex',justifyContent:"flex-start"}}>
<span><span className="color-red">* </span>模板内容</span>
<div>
<div className="editorPanel">
<Editor Numbers={"off"} width={"350px"} value={value} height="200px" theme="vs-grey" onChange={changeContent}/>
</div>
{ valueFlag && <span className="color-red">请输入模板内容</span>}
</div>
</div>
</Form>
</Spin>
</Modal>
)
}
export default Form.create()(forwardRef(MouldNew));

View File

@ -28,6 +28,7 @@ function Structure(props,ref){
let projectsId = props.match.params.projectsId;
let owner = props.match.params.owner;
let branch = props.match.params.branch;
const permission = props.projectDetail && props.projectDetail.permission;
useImperativeHandle(ref, () => ({
@ -49,7 +50,7 @@ function Structure(props,ref){
axios.get(url,{
params:{
search:status,
page,limit:LIMIT
page,limit:LIMIT,branch
}
}).then((result) => {
if (result && result.data) {

View File

@ -261,8 +261,6 @@ function disposePipeline(props){
//
function sureSubmit(){
setLoading(true);
let sync = datas.sync || 0;
let url = '';
const { defaultBranch } = props;
let params = {
branch: defaultBranch,
@ -273,32 +271,18 @@ function disposePipeline(props){
owner:owner,
repo:projectsId
}
if(sync === 1){
// true
url = `/${owner}/${projectsId}/update_trustie_pipeline.json`;
axios.put(url,{
...params
}).then(result=>{
if(result){
props.history.push(`/projects/${owner}/${projectsId}/devops/dispose`);
}
setLoading(false);
}).catch(error=>{
console.log(error);
setLoading(false);
})
}else{
url = `/ci/pipelines/${disposeId}/create_trustie_pipeline.json`;
axios.post(url,params).then(result=>{
if(result){
props.history.push(`/projects/${owner}/${projectsId}/devops/dispose`);
}
setLoading(false);
}).catch(error=>{
console.log(error);
setLoading(false);
})
}
let url = `/${owner}/${projectsId}/update_trustie_pipeline.json`;
axios.put(url,{
...params
}).then(result=>{
if(result){
props.history.push(`/projects/${owner}/${projectsId}/devops/dispose`);
}
setLoading(false);
}).catch(error=>{
console.log(error);
setLoading(false);
})
}
return(

View File

@ -487,4 +487,17 @@
.stepsBody.active{
display: block;
}
}
.editorPanel{
border:1px solid #eee;
.margin{
width: 0px;
}
.monaco-scrollable-element.editor-scrollable{
left: 0px!important;
width: 100%!important;
.view-lines{
width: 336px!important;
}
}
}