about page

This commit is contained in:
caishi 2021-01-11 17:18:12 +08:00
parent 4d9567bb94
commit 4ef05163d7
17 changed files with 542 additions and 168 deletions

View File

@ -94,7 +94,7 @@ export const Blueback = styled.a`{
line-height:30px;
border-radius:2px;
background-color:rgba(80,145,255,1);
color:#fff;
color:#fff!important;
padding:0px 12px;
display:inline-block;
min-width:80px;

View File

@ -1,130 +1,74 @@
import React , { useState , useEffect } from 'react';
import { Spin } from 'antd';
import { Blueback } from '../Component/layout';
import Editor from "react-monaco-editor";
import Modals from './DisposeModal';
import FileLanguage from '../Component/OpsFileLanguage';
import { Spin , Pagination } from 'antd';
import { Blueback , Banner } from '../Component/layout';
import List from './Dispose/List';
import Head from './Dispose/head';
import axios from 'axios';
function Dispose(props){
const [ spining , setSpining ] = useState(true);
const [ info , setInfo ] = useState('.trustie-pipeline.yml');
const [ visible , setVisible ] = useState(false);
const [ ymlValue , setYmlValue ] = useState("");
const [ sha , setSha ] = useState(undefined);
const [ fileLanguage , setFileLanguage ] = useState(undefined);
const [ first , setFirst ] = useState(false);
import styled from 'styled-components';
const Div = styled.div`{
padding:24px 30px;
}`;
const LIMIT = 15;
function Dispose(props){
const [ spining , setSpining ] = useState(false);
const [ list , setList ] = useState(undefined);
const [ page , setPage ] = useState(1);
const [ total , setTotal ] = useState(0);
const [ permission , setPermission ] = useState("");
const projectDetail = props.projectDetail;
let projectsId = props.match.params.projectsId;
let owner = props.match.params.owner;
//
useEffect(()=>{
if(projectDetail){
setPermission(props.projectDetail.permission);
}
},[projectDetail])
useEffect(()=>{
let l = [
{name:"流水线名字",fileName:"filename.jpg",time:"2021-01-06",status:"运行中"}
{name:"流水线名字",fileName:"filename.jpg",time:"2021-01-06",status:"运行中"},
{name:"流水线名字11",fileName:"filename111.jpg",time:"2021-01-06",status:"运行中"}
]
setList(l);
},[])
let projectsId = props.match.params.projectsId;
let owner = props.match.params.owner;
useEffect(()=>{
if(projectsId){
const url = `/${owner}/${projectsId}/get_trustie_pipeline.json`;
axios.get(url,{
params:{
project_id:projectsId
}
}).then(result=>{
if(result && result.data.content){
setInfo(result.data.name);
setYmlValue(result.data.content);
setFirst(true);
setSha(result.data.sha);
}else{
setFirst(false);
}
setSpining(false);
}).catch(error=>{
console.log(error);
})
}
},[projectsId])
//
function changeEditor(value){
setYmlValue(value);
// -
function changePage(page){
setPage(page);
}
//
function select_language(value,array){
setFileLanguage(value);
setYmlValue( array && array.content);
// 线
function addNew(){
props.history.push(`/projects/${owner}/${projectsId}/devops/dispose/new`);
}
//
function submit(){
let url = '';
const { defaultBranch } = props;
let params = {
branch: defaultBranch,
content:ymlValue,
filepath:info,
message:''
}
if(first){
// true
url = `/${owner}/${projectsId}/update_trustie_pipeline.json`;
axios.put(url,{
...params,
sha
}).then(result=>{
if(result){
setVisible(true);
}
}).catch(error=>{
console.log(error);
})
}else{
url = `/${owner}/${projectsId}/create_file.json`;
axios.post(url,params).then(result=>{
if(result){
setVisible(true);
}
}).catch(error=>{
console.log(error);
})
}
}
function suresubmit(){
setVisible(false);
props.history.push(`/projects/${owner}/${projectsId}/devops/list`);
}
return(
<Spin spinning={spining}>
<Modals visible={visible} closeFunc={(flag)=>setVisible(flag)} sureFunc={suresubmit}></Modals>
<p>编程语言</p>
<div className="mt20 mb20">
<FileLanguage language={fileLanguage} select_language={select_language}/>
<div className="disposePanel">
<Head />
<Div>
{ permission !=="Reporter" && <Blueback onClick={addNew}>新增流水线</Blueback> }
<div className="mt20 disposeList">
<List list={list} permission={permission} projectsId={projectsId} owner={owner}/>
{
total > LIMIT ?
<div className="txtright mt20 mb20">
<Pagination simple current={page} pageSize={LIMIT} onChange={changePage} total={total}/>
</div>
:""
}
</div>
</Div>
</div>
<p>配置脚本</p>
<div className="editorBody">
<p className="editorHead">{info}</p>
<Editor
height="300px"
language={"yml"}
theme={"vs-grey"}
defaultValue="请输入内容"
value={ymlValue}
options={"editor_options"}
onChange={changeEditor}
></Editor>
</div>
<Blueback onClick={submit}>确定提交</Blueback>
<List list={list}/>
</Spin>
)
}

View File

@ -0,0 +1,27 @@
import React , { useEffect , useState } from 'react';
function Choosen({name,options,chooseFunc}){
const [ index, setIndex ] = useState(undefined);
//
function chooseOption(id,k){
chooseFunc && chooseFunc(id);
setIndex(k);
}
return(
<div className="choosenList">
<span>{name}</span>
<ul>
{
options && options.map((item,key)=>{
return(
<li className={index && index === key+1 ? "active":""} onClick={()=>chooseOption(item.id,key+1)}>{item.name}</li>
)
})
}
</ul>
</div>
)
}
export default Choosen;

View File

@ -0,0 +1,17 @@
import React from 'react';
import Editor from 'react-monaco-editor';
function Editors({value,onChange,theme,height}){
return(
<Editor
height={height}
language={"yml"}
theme={theme}
placeholder="请输入内容"
value={value}
options={"editor_options"}
onChange={(value)=>onChange(value)}
></Editor>
)
}
export default Editors;

View File

@ -0,0 +1,39 @@
import React , { useEffect , useState } from 'react';
import { Input , Button } from 'antd';
import Choosen from './Choosen';
import Editors from './Editors';
function Init(){
const [ name , setName ] = useState(undefined);
const [ modelsId , setModelsId ] = useState(undefined);
const [ models , setModels ] = useState(undefined);
const [ ymlValue , setYmlValue ] = useState(undefined);
useEffect(()=>{
let list = [
{id:1,name:"docker/arm64/linux"},
{id:2,name:"docker/x86/linux"}
]
setModels(list);
},[])
function chooseFunc(id){
setModelsId(id);
}
return(
<div>
<div className="choosenList">
<span>流水线名称:</span>
<Input value={name} onChange={(e)=>setName(e.target.value)} placeholder="请输入名称" style={{width:"340px",margin:"6px 0px"}}/>
</div>
<Choosen name={"模板选择:"} options ={models} chooseFunc={chooseFunc}/>
<div className="mt15">
<Editors value={ymlValue} onChange={setYmlValue} theme={"vs-dark"} height={"400px"}/>
</div>
<div className="mt20">
<Button type={"primary"}>下一步</Button>
</div>
</div>
)
}
export default Init;

View File

@ -1,7 +1,8 @@
import React from 'react';
import { Table } from 'antd';
import { Link } from 'react-router-dom';
function List({list}){
function List({ list, permission , projectsId , owner }){
const columns = [
{
@ -16,6 +17,7 @@ function List({list}){
dataIndex:"fileName",
key:1,
width:"17%",
className:"color-blue",
ellipsis:true
},
{
@ -40,17 +42,17 @@ function List({list}){
render:(txt,item)=>{
return(
<span>
<a className="color-grey-6"><i className="iconfont iconzaibianji font-13 mr3"></i>编辑</a>
<a className="ml10 color-grey-6"><i className="iconfont iconlajitong font-13 mr3"></i>删除</a>
<a className="ml10 color-grey-6"><i className="iconfont iconyunhang font-13 mr3"></i>运行</a>
<a className="ml10 color-grey-6">查看运行记录</a>
{ permission !=="Reporter" && <Link to={`/projects/${owner}/${projectsId}/devops/dispose/1`} className="color-grey-6"><i className="iconfont iconzaibianji font-13 mr3"></i>编辑</Link> }
{ permission !=="Reporter" && <a className="ml10 color-grey-6"><i className="iconfont iconlajitong font-13 mr3"></i>删除</a> }
{ permission !=="Reporter" && <a className="ml10 color-grey-6"><i className="iconfont iconyunhang font-13 mr3"></i>运行</a> }
<Link to={`/projects/${owner}/${projectsId}/devops/list`} className="ml10 color-grey-6">查看运行记录</Link>
</span>
)
}
}
]
return(
<Table size="small" columns={columns} dataSource={list} rowKey={(row)=>row.id}></Table>
<Table size="small" columns={columns} dataSource={list} rowKey={(row)=>row.id} pagination={false}></Table>
)
}
export default List;

View File

@ -0,0 +1,25 @@
import React , { useEffect , useState } from 'react';
import { Button } from 'antd';
import Editors from './Editors';
import { Cancel } from '../../Component/layout';
function Sure(){
const [ ymlValue , setYmlValue ] = useState(undefined);
return(
<div>
<div style={{padding:"0px 15px 15px 15px"}}>
工作流名称流水线名称
</div>
<div className="editorBody" style={{marginTop:"0px"}}>
<Editors value={ymlValue} onChange={setYmlValue} theme={"vs-grey"} height={"600px"}/>
</div>
<div className="mt20">
<Button type={"primary"}>下一步</Button>
<Button type={"primary"} className="ml20">确定提交</Button>
<Cancel className="ml20">取消</Cancel>
</div>
</div>
)
}
export default Sure;

View File

@ -0,0 +1,15 @@
import React , { useEffect , useState } from 'react';
import { AlignCenterBetween } from '../../Component/layout';
import { Link } from 'react-router-dom';
function head(){
return(
<AlignCenterBetween>
<span className="font-20">工作流配置</span>
<Link to="" className="color-grey-6"><i className="iconfont icontishi1 font-14 mr3"></i>模板使用说明</Link>
</AlignCenterBetween>
)
}
export default head;

View File

@ -0,0 +1,36 @@
import React , { useEffect , useState } from 'react';
import MenusRename from './menusRename';
import MenusAdd from './menusAdd';
function Menus({step,changeStep,menuList}){
function InitActive(key){
changeStep(key)
}
function getName(name){
console.log(name);
}
return(
<ul className="menus">
{
menuList && menuList.length > 0 && menuList.map((item,key)=>{
return(
<React.Fragment key={item.id} >
<li onClick={()=>InitActive(key+1)} className={key+1 === step ?"active":""}>
<i className={`iconfont ${item.icon}`}></i>
<MenusRename name={item.stage_name} edit={item.stage_type !== "init" && item.stage_type !== "confirm"}/>
</li>
{ key !== (menuList.length-1) && menuList.length <= 10 ?
<MenusAdd getName={getName}/>:""
}
</React.Fragment>
)
})
}
</ul>
)
}
export default Menus;

View File

@ -0,0 +1,22 @@
import React , { useState , useEffect } from 'react';
import { Input } from 'antd';
function menusAdd ({ getName }){
const [ show, setShow ] = useState(false);
const [ value , setValue ] = useState(undefined);
function blurInput(){
if(value){
getName(value);
}
setShow(false);
}
return(
<li className="menuAdd">
{ !show && <i className="iconfont icontianjia" onClick={()=>setShow(true)}></i> }
{ show && <Input size={"small"} maxLength={8} style={{width:"75px"}} placeholder="新阶段名称" value={value} onChange={(e)=>setValue(e.target.value)} onBlur={blurInput} /> }
</li>
)
}
export default menusAdd;

View File

@ -0,0 +1,35 @@
import React , { useEffect , useState } from 'react';
import { Input } from 'antd';
function menusRename({ name , edit }){
const [ n , setN ] = useState(undefined);
const [ show , setShow ] = useState(false);
useEffect(()=>{
if(name){
setN(name)
}
},[name])
function changeShow(e){
e.stopPropagation();
setShow(true);
}
function blurInput(){
setShow(false);
}
return(
<div className="aboutEdit">
<span className="operateName">
{ !show && n }
{ show &&
<Input value={n} size="small" maxLength={8} onClick={(e)=>e.stopPropagation()} onBlur={blurInput} style={{width:"75px"}} onChange={(e)=>setN(e.target.value)}/>
}
</span>
{ !show && edit && <i className="iconfont iconeditUnder font-16 color-grey-9" onClick={changeShow}></i> }
</div>
)
}
export default menusRename

View File

@ -10,8 +10,16 @@ const About = Loadable({
loader: () => import('./About'),
loading: Loading,
})
const Infos = Loadable({
loader: () => import('./Infos'),
const New = Loadable({
loader: () => import('./disposePipeline'),
loading: Loading,
})
const Dispose = Loadable({
loader: () => import('./Dispose'),
loading: Loading,
})
const Stucture = Loadable({
loader: () => import('./Structure'),
loading: Loading,
})
export default ((props)=>{
@ -19,27 +27,37 @@ export default ((props)=>{
const open_devops = props.projectDetail && props.projectDetail.open_devops;
//
useEffect(()=>{
if(open_devops !== undefined){
if(open_devops){
props.history.replace(`/projects/${owner}/${projectsId}/devops/list`);
}else{
props.history.replace(`/projects/${owner}/${projectsId}/devops`);
}
}
},[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">
<Switch {...props}>
<Route path="/projects/:owner/:projectsId/devops/dispose/:disposeId"
render={
() => (<New {...props} />)
}
></Route>
<Route path="/projects/:owner/:projectsId/devops/dispose/new"
render={
() => (<New {...props} />)
}
></Route>
<Route path="/projects/:owner/:projectsId/devops/dispose"
render={
() => (<Infos {...props} />)
() => (<Dispose {...props} />)
}
></Route>
<Route path="/projects/:owner/:projectsId/devops/list"
render={
() => (<Infos {...props} />)
() => (<Stucture {...props} />)
}
></Route>
<Route path="/projects/:owner/:projectsId/devops"

View File

@ -44,13 +44,13 @@ export default ((props)=>{
return(
<div className="disposePanel">
<Banner>
{ permission !=="Reporter" && <Link to={`/projects/${owner}/${props.match.params.projectsId}/devops/dispose`} className={menu===false && "color-blue"} style={{ marginRight:"66px"}}>工作流配置</Link>}
<Link to={`/projects/${owner}/${props.match.params.projectsId}/devops/list`}className={menu===true && "color-blue"}>构建列表</Link>
{ menu===true && <a onClick={updateChildState} style={{float:"right",fontSize:"14px",color:"#FF6E21",marginTop:"5px"}}>刷新</a>}
{ permission !=="Reporter" && <Link to={`/projects/${owner}/${props.match.params.projectsId}/devops/dispose`}>工作流配置</Link>}
{/* <Link to={`/projects/${owner}/${props.match.params.projectsId}/devops/list`}className={menu===true && "color-blue"}>构建列表</Link> */}
{/* { menu===true && <a onClick={updateChildState} style={{float:"right",fontSize:"14px",color:"#FF6E21",marginTop:"5px"}}>刷新</a>} */}
</Banner>
<Div>
{ menu === true && <Structure {...props} wrappedComponentRef={(form) => childRef.current = form} ref={childRef}/> }
{ menu === false && permission !=="Reporter" && <Dispost {...props}/> }
{/* { menu === true && <Structure {...props} wrappedComponentRef={(form) => childRef.current = form} ref={childRef}/> } */}
<Dispost {...props}/>
</Div>
</div>
)

View File

@ -1,10 +1,15 @@
import React, { useState, useEffect , useImperativeHandle ,forwardRef } from "react";
import { FlexAJ, AlignCenter } from "../Component/layout";
import { FlexAJ, AlignCenter , Banner } from "../Component/layout";
import { Table, Pagination, Popconfirm } from "antd";
import { truncateCommitId } from "../common/util";
import {getUrl} from 'educoder';
import axios from "axios";
import styled from 'styled-components';
const Div = styled.div`{
padding:24px 30px;
}`;
const STATUS = [
{ name: "所有"},
{ name: "运行中", value: "running" },
@ -278,42 +283,47 @@ function Structure(props,ref){
},
];
return (
<div className="listPart">
<FlexAJ>
{renderStatus()}
{/* <Blueback>手动创建</Blueback> */}
{/* <span className="mr30">
<i className="iconfont icon-fenzhi1 font-16 mr5 color-blue"></i>分支
</span>
<span>
<i className="iconfont icon-biaoqian3 font-16 mr5 color-blue"></i>
标签
</span> */}
</FlexAJ>
<Table
onRow={(record,index)=>{
return{
onClick:(event)=>clickRows(event,record)
}
}}
columns={column}
className="normalTable"
dataSource={data}
pagination={false}
loading={tableLoading}
></Table>
{total > LIMIT ?
<div style={{ textAlign: "center", margin: "30px 50px" }}>
<Pagination
showQuickJumper
defaultCurrent={page}
total={total}
pageSize={LIMIT}
onChange={ChangePage}
></Pagination>
</div>
:
"" }
<div className="disposePanel">
<Banner>构建列表</Banner>
<Div>
<div className="listPart">
<FlexAJ>
{renderStatus()}
{/* <Blueback>手动创建</Blueback> */}
{/* <span className="mr30">
<i className="iconfont icon-fenzhi1 font-16 mr5 color-blue"></i>分支
</span>
<span>
<i className="iconfont icon-biaoqian3 font-16 mr5 color-blue"></i>
标签
</span> */}
</FlexAJ>
<Table
onRow={(record,index)=>{
return{
onClick:(event)=>clickRows(event,record)
}
}}
columns={column}
className="normalTable"
dataSource={data}
pagination={false}
loading={tableLoading}
></Table>
{total > LIMIT ?
<div style={{ textAlign: "center", margin: "30px 50px" }}>
<Pagination
showQuickJumper
defaultCurrent={page}
total={total}
pageSize={LIMIT}
onChange={ChangePage}
></Pagination>
</div>
:
"" }
</div>
</Div>
</div>
);
};

View File

@ -0,0 +1,44 @@
import React , { useEffect , useState } from 'react';
import { WhiteBack , Banner } from '../Component/layout';
import Head from './Dispose/head';
import Menus from './Dispose/menus';
import Init from './Dispose/Init';
import Sure from './Dispose/Sure';
function disposePipeline(){
const [ step , setStep ] = useState(1);
const [ menuList , setMenuList ] = useState(undefined);
useEffect(()=>{
let list = [
{stage_name:"初始化",stage_type:"init",id:1,icon:"iconinitialize"},
{stage_name:"编辑构建",stage_type:"",id:2,icon:"iconstructure"},
{stage_name:"部署",stage_type:"",id:3,icon:"iconarrange"},
{stage_name:"确认",stage_type:"confirm",id:4,icon:"iconsure"}
];
setMenuList(list);
},[])
function changeStep(s){
setStep(s);
}
return(
<div className="disposePanel">
<Head />
<WhiteBack style={{padding:"24px 30px"}}>
<div style={{minHeight:"450px"}}>
<Menus step={step} changeStep={changeStep} menuList={menuList}/>
{
step === 1 && <Init />
}
{
menuList && (step === menuList.length) && <Sure />
}
</div>
</WhiteBack>
</div>
)
}
export default disposePipeline;

View File

@ -313,4 +313,144 @@
text-align:center;
line-height:22px;
color:#666;
}
.disposeList{
min-height: 450px;
.ant-table-body{
margin:0px!important;
.ant-table-thead{
background-color: #fafafa;
}
}
}
.txtright{
text-align: right;
}
.menus{
display: flex;
box-shadow: 0px 0px 6px rgba(0,3,8,0.1);
padding:20px 0px 10px 0px;
justify-content: space-between;
margin-bottom: 20px;
& > li{
display: flex;
flex-flow: column;
align-items:center;
justify-content: center;
text-align: center;
color: #787878;
font-size: 16px;
min-width: 110px;
position: relative;
cursor: pointer;
width: 0;
&>i{
font-size: 30px!important;
height: 30px;
width: 30px;
line-height: 30px;
}
&:hover,&.active{
& > i{
color: #1890FF;
}
}
&.active::after{
position: absolute;
left: 50%;
margin-left: -25px;
bottom:-10px;
width: 50px;
height: 2px;
background-color: #1890FF;
content: "";
}
& > div{
width: 100%;
}
.aboutEdit{
position: relative;
.operateName{
position: relative;
line-height: 20px;
}
& > i{
position: absolute;
right:-22px;
top:50%;
margin-top: -16px;
display: none;
}
&:hover > i{
display: block;
}
}
}
& > li.menuAdd{
position: relative;
flex: 1;
&:hover{
& > i{
color: #787878;
}
}
& > i{
font-size: 18px!important;
position: absolute;
z-index: 1;
top:0px;
}
& > input{
position: absolute;
z-index: 1;
top:3px;
}
&::before{
position: absolute;
left: 0;
width: 100%;
border-bottom: 1px dashed #eee;
content: "";
top:15px;
margin-top: -1px;
z-index: 0;
}
}
}
.choosenList{
display: flex;
flex-wrap: wrap;
line-height: 35px;
align-items: center;
& > span{
display: block;
min-width: 75px;
text-align: right;
font-size: 14px;
color: #333;
height: 35px;
margin-right: 12px;
}
& > ul{
display: flex;
flex-wrap: wrap;
li{
padding:0px 12px;
height: 35px;
line-height: 35px;
border:1px solid #e1e4e8;
background-color: #fff;
margin:6px 0px;
margin-right: 12px;
border-radius: 5px;
cursor: pointer;
color: #333;
}
li.active{
color: #fff;
background-color: #1890FF;
}
}
}

View File

@ -516,7 +516,7 @@ class Detail extends Component {
{
platform &&
<li className={pathname==="devops" ? "active" : ""}>
<Link to={{ pathname: `/projects/${owner}/${projectsId}/devops${open_devops ? `/list`:""}`, state }}>
<Link to={{ pathname: `/projects/${owner}/${projectsId}/devops${open_devops ? `/dispose`:""}`, state }}>
<i className="iconfont icon-gongzuoliu font-13 mr8"></i>(beta)
{projectDetail && projectDetail.ops_count ? <span>{projectDetail.ops_count}</span> : ""}
</Link>