上线actions流水线
This commit is contained in:
parent
c12369d373
commit
171b9973b4
|
|
@ -135,4 +135,25 @@ export function timeAgo(backDate) {
|
|||
// return seconds + "秒前";
|
||||
// }
|
||||
return "刚刚";
|
||||
}
|
||||
|
||||
// seconds: 秒数,返回格式为 xxhxxmxxs 的字符串
|
||||
export function secondsToTimeFormat(seconds) {
|
||||
const hours = Math.floor(seconds / 3600); // 计算小时数
|
||||
const minutes = Math.floor((seconds % 3600) / 60); // 计算分钟数
|
||||
const remainingSeconds = seconds % 60; // 计算剩余秒数
|
||||
|
||||
let result = "";
|
||||
|
||||
if (hours > 0) {
|
||||
result += `${hours}h`;
|
||||
}
|
||||
if (minutes > 0) {
|
||||
result += `${minutes}m`;
|
||||
}
|
||||
if (remainingSeconds > 0 || result === "") {
|
||||
result += `${remainingSeconds}s`;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
import fetch from './fetch';
|
||||
export function addPipelines(owner, repo, data) {
|
||||
return fetch({
|
||||
url:`/v1/${ owner }/${ repo }/pipelines`,
|
||||
method: 'POST',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export function getCardList(owner, repo, params) {
|
||||
return fetch({
|
||||
url:`/v1/${ owner }/${ repo }/pipelines`,
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
export function getTemplateList() {
|
||||
return fetch({
|
||||
url:`/action/templates.json`,
|
||||
method: 'get',
|
||||
})
|
||||
}
|
||||
|
||||
export function getProjectBranch(owner, repo) {
|
||||
return fetch({
|
||||
url:`/${ owner }/${ repo }/branches.json`,
|
||||
method: 'GET',
|
||||
})
|
||||
}
|
||||
|
||||
export function runPipeline(owner, repo, params) {
|
||||
return fetch({
|
||||
url:`/v1/${ owner }/${ repo }/actions/runs.json`,
|
||||
method: 'post',
|
||||
data: params
|
||||
})
|
||||
}
|
||||
|
||||
export function delPipelines(owner, repo, id) {
|
||||
return fetch({
|
||||
url:`/v1/${ owner }/${ repo }/pipelines/${ id }.json`,
|
||||
method: 'DELETE',
|
||||
})
|
||||
}
|
||||
|
||||
export function stopPipelines(owner, repo, params) {
|
||||
return fetch({
|
||||
url:`/v1/${ owner }/${ repo }/actions/disable.json`,
|
||||
method: 'POST',
|
||||
data: params
|
||||
})
|
||||
}
|
||||
|
||||
export function startPipelines(owner, repo, params) {
|
||||
return fetch({
|
||||
url:`/v1/${ owner }/${ repo }/actions/enable.json`,
|
||||
method: 'POST',
|
||||
data: params
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
import javaFetch from '../../javaFetch';
|
||||
|
||||
// let settings = JSON.parse(localStorage.chromesetting);
|
||||
|
||||
const service = javaFetch('/api');
|
||||
export default service;
|
||||
export const TokenKey = 'autologin_trustie';
|
||||
|
|
@ -0,0 +1,178 @@
|
|||
import { message, Popover, Button, Divider, Switch, Modal, Pagination } from "antd";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import './index.scss';
|
||||
import { secondsToTimeFormat, timeAgo } from "../../../common/DateUtil";
|
||||
import { runPipeline, delPipelines, stopPipelines, startPipelines, getCardList } from './api'
|
||||
import Nodata from "../../Nodata";
|
||||
import New from "./newModal";
|
||||
|
||||
const { confirm } = Modal;
|
||||
|
||||
function CardDevops(props) {
|
||||
const { project, match } = props || {};
|
||||
const {owner, projectsId} = match.params;
|
||||
const [list, setList] = useState([])
|
||||
const [ visible , setVisible ] = useState(false);
|
||||
const [ pageNum , setPageNum ] = useState(1);
|
||||
const [ total , setTotal ] = useState(0);
|
||||
const pageSize = 20;
|
||||
|
||||
let settings = localStorage.chromesetting && JSON.parse(localStorage.chromesetting);
|
||||
let pipelineUrl = settings && settings.common.pipeline;
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
document.title = `流水线-${owner}/${projectsId}`;
|
||||
}, [])
|
||||
|
||||
useEffect(()=>{
|
||||
getList();
|
||||
const intervalId = setInterval(() => {
|
||||
getList();
|
||||
}, 10000);
|
||||
return () => clearInterval(intervalId);
|
||||
}, [pageNum])
|
||||
|
||||
function getList(){
|
||||
getCardList(owner, projectsId, { limit: pageSize, page: pageNum }).then(res => {
|
||||
if (res.status === 0) {
|
||||
setList(res.pipelines.map(e => {
|
||||
e.run_data = e.run_data || {}
|
||||
return e
|
||||
}))
|
||||
setTotal(res.count)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 删除流水线
|
||||
async function delPipelineFunc(id) {
|
||||
confirm({
|
||||
title: '提示',
|
||||
content: '确认删除该流水线?',
|
||||
onOk() {
|
||||
delPipelines( owner, projectsId, id).then(res => {
|
||||
if(res && res.status === 0){
|
||||
message.success(`流水线删除成功!`);
|
||||
getList();
|
||||
}
|
||||
})
|
||||
},
|
||||
onCancel() {
|
||||
},
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
// 手动运行流水线
|
||||
async function run (item) {
|
||||
if (!item.file_name) {
|
||||
message.warning('请先编辑流水线内容!')
|
||||
return
|
||||
}
|
||||
runPipeline( owner, projectsId, { ref: item.branch, workflow: item.file_name.split('/').pop() }).then(res => {
|
||||
if(res && res.status === 0) {
|
||||
message.success('运行成功!')
|
||||
getList();
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 禁用/启用流水线
|
||||
async function stopPipelineFunc(id,status, fileName = undefined) {
|
||||
if (!fileName) {
|
||||
message.warning('请先编辑流水线内容!')
|
||||
return
|
||||
}
|
||||
let res = !status ? await stopPipelines(owner, projectsId, { id, workflow: fileName.split('/').pop() }) : await startPipelines(owner, projectsId, { id, workflow: fileName.split('/').pop() })
|
||||
|
||||
if(res && res.status === 0){
|
||||
message.info(`流水线${ !status ? "禁用":"启用"}成功!`);
|
||||
getList();
|
||||
}
|
||||
}
|
||||
|
||||
const listType = {
|
||||
1: '声明式',
|
||||
2: '图形化',
|
||||
}
|
||||
let toBeDesigned = -1
|
||||
|
||||
const statusEnum = {
|
||||
1: '成功',
|
||||
2: '失败',
|
||||
5: '等待',
|
||||
6: '运行中',
|
||||
0: '待启动',
|
||||
[toBeDesigned]: '待设计'
|
||||
}
|
||||
|
||||
return <div className="cardList">
|
||||
<New
|
||||
{...props}
|
||||
open={visible}
|
||||
onCancel={()=>setVisible(false)}
|
||||
onOk={()=>{setVisible(false);getList()}}
|
||||
/>
|
||||
<Button onClick={()=>{setVisible(true)}} type='primary' className="mb20" >新建流水线</Button>
|
||||
<div className="listData">
|
||||
{list && list.map(e=>{
|
||||
if (!e.run_data.status && e.run_data.status !== 0) {
|
||||
e.run_data.status = (!e.json && !e.yaml) ? -1 : 0
|
||||
}
|
||||
return <div className={`card developStatus${e.run_data.status}`} >
|
||||
<div className="statusBar"></div>
|
||||
<div className="cardContent">
|
||||
<div onClick={()=>{
|
||||
// window.location.href = `${mygetHelmetapi && mygetHelmetapi.common.zone}/${owner}/pipeline/${e.id}`
|
||||
window.open(`${ pipelineUrl }/devops/${owner}/${projectsId}/${e.id}`)
|
||||
}}>
|
||||
<div className="cartHead">
|
||||
<div className={e.pipeline_type === 1 ? "f36" : ''}>{listType[e.pipeline_type]}</div>
|
||||
<Popover content={e.pipeline_name} ><span className="task-hide weight">{e.pipeline_name}</span></Popover>
|
||||
</div>
|
||||
<div className="cardBody">
|
||||
<p>
|
||||
<div className={`statusIcon developStatus${e.run_data.status}`}></div>
|
||||
{statusEnum[+e.run_data.status] || ((!e.json && !e.yaml) ? "待设计" : "待启动")}
|
||||
{!!e.run_data.total && <span>#{e.run_data.total} {timeAgo(e.run_data.stopped)} 执行时长{secondsToTimeFormat(e.run_data.length)}</span>}
|
||||
</p>
|
||||
<p className="mt5"><i className="iconfont icon-a-fenzhi1 font-12 mr4 mb3"></i> 分支:{e.branch}</p>
|
||||
<div className="mt5">{e.run_data.schedule && <span className="scheduleBox mr10">定时</span>}历史共执行{e.run_data.total || 0}次(成功{e.run_data.success || 0}次,失败{e.run_data.failure || 0}次)</div>
|
||||
</div>
|
||||
</div>
|
||||
<Divider dashed/>
|
||||
<div className="cartBottom" >
|
||||
<div className="leftButton">
|
||||
<img src={ require('../../../images/devOps/start.png') } alt="" onClick={ () => run(e) }/>
|
||||
<img src={ require('../../../images/devOps/edit.png') } alt="" onClick={ () => window.open(`${ pipelineUrl }/devops/${owner}/${projectsId}/${e.id}/edit`) }/>
|
||||
<img src={ require('../../../images/devOps/delete.png') } alt="" onClick={ () => delPipelineFunc(e.id) }/>
|
||||
</div>
|
||||
<div className="rightButton">
|
||||
<div className="status-switch">
|
||||
<Switch size="small" className='mr10' checked={ !e.disable } onChange={()=>stopPipelineFunc(e.id, e.disable, e.file_name)} />
|
||||
{ e.disable? '禁用' : '启用' }
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
})}
|
||||
{list && !list.length && <div style={{margin: '100px auto'}}><Nodata _html="暂无数据"/></div>}
|
||||
</div>
|
||||
{
|
||||
total > pageSize &&
|
||||
<div style={{textAlign:'right',paddingTop:20}}>
|
||||
<Pagination
|
||||
simple
|
||||
defaultCurrent={pageNum}
|
||||
total={total}
|
||||
pageSize={pageSize}
|
||||
onChange={setPageNum}
|
||||
></Pagination>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
export default CardDevops
|
||||
|
|
@ -0,0 +1,245 @@
|
|||
.cardList {
|
||||
margin: 30px auto;
|
||||
width: 1200px;
|
||||
padding-bottom: 50px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.ant-btn-primary {
|
||||
margin-left: auto;
|
||||
}
|
||||
.listData {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.card {
|
||||
width: 333px;
|
||||
border-radius: 6px;
|
||||
margin-right: 31px;
|
||||
margin-bottom: 25px;
|
||||
padding: 0 1px 1px 1px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
cursor: pointer;
|
||||
&:hover{
|
||||
box-shadow: 0px 0px 15px 1px rgba(109,130,170,0.44);
|
||||
}
|
||||
.statusBar{
|
||||
height: 6px;
|
||||
margin: 0 4px;
|
||||
}
|
||||
.cardContent {
|
||||
flex-grow: 2;
|
||||
background: #FFFFFF;
|
||||
border-radius: 6px 6px 6px 6px;
|
||||
padding: 15px 17px 20px;
|
||||
box-sizing: border-box;
|
||||
.cartHead {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
div {
|
||||
width: 57px;
|
||||
height: 21px;
|
||||
border-radius: 4px 4px 4px 4px;
|
||||
border: 1px solid $primary-color;
|
||||
font-size: 13px;
|
||||
color: $primary-color;
|
||||
line-height: 21px;
|
||||
text-align: center;
|
||||
margin-right: 11px;
|
||||
}
|
||||
.f36{
|
||||
color: #F3651C;
|
||||
border-color: #F3651C;
|
||||
}
|
||||
span {
|
||||
font-family: 'alibaba-medium';
|
||||
font-size: 16px;
|
||||
color: #1E1E1E;
|
||||
}
|
||||
}
|
||||
.cardBody {
|
||||
margin-top: 12px;
|
||||
height: 133px;
|
||||
background: #d4dff13d;
|
||||
border-radius: 6px;
|
||||
padding: 20px 15px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
font-size: 14px;
|
||||
color: #082340;
|
||||
box-sizing: border-box;
|
||||
p {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
line-height: 1;
|
||||
}
|
||||
div {
|
||||
line-height: 22px;
|
||||
}
|
||||
.statusIcon {
|
||||
width: 4px;
|
||||
height: 12px;
|
||||
border-radius: 6px 6px 6px 6px;
|
||||
margin-right: 12px;
|
||||
}
|
||||
}
|
||||
.cartBottom {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.leftButton {
|
||||
img {
|
||||
width: 26px;
|
||||
margin-right: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
.rightButton {
|
||||
margin-left: auto;
|
||||
font-size: 14px;
|
||||
color: $primary-color;
|
||||
.status-switch {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
button {
|
||||
margin-right: 10px;
|
||||
}
|
||||
.ant-switch-checked {
|
||||
background: #07A35A;
|
||||
&:hover {
|
||||
background: #07A35A !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.scheduleBox{
|
||||
border-radius: 4px;
|
||||
border: 1px solid #F3651C;
|
||||
color: #F3651C;
|
||||
padding: 3px 4px;
|
||||
}
|
||||
}
|
||||
.statusIcon, .card, .statusBar{
|
||||
background: #979797;
|
||||
}
|
||||
.developStatus2.statusIcon, .card.developStatus2, .developStatus2 .statusBar{
|
||||
background: #EC6E66;
|
||||
}
|
||||
.developStatus1.statusIcon, .card.developStatus1, .developStatus1 .statusBar{
|
||||
background: #07A35A;
|
||||
}
|
||||
.developStatus5.statusIcon, .card.developStatus5, .developStatus5 .statusBar{
|
||||
background: #979797;
|
||||
}
|
||||
.developStatus6.statusIcon, .card.developStatus6, .developStatus6 .statusBar{
|
||||
background: #2D74FB;
|
||||
}
|
||||
.card.developStatus6 .statusBar {
|
||||
background-image: repeating-linear-gradient(115deg, #ffffff63 0, #2D74FB 1px, #2D74FB 10px, #ffffff63 11px, #ffffff63 20px);
|
||||
animation: 3s linear 0s infinite normal none running workflow-running;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes workflow-running{
|
||||
0% {
|
||||
background-position-x: -53.5px;
|
||||
}
|
||||
100% {
|
||||
background-position-x: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.templateContainer {
|
||||
height: 380px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.template{
|
||||
padding: 15px 18px;
|
||||
background-size: 100% 100%;
|
||||
background-image: url('../../../images/devOps/template.png');
|
||||
border-radius: 4px;
|
||||
border: 1px solid rgba(158,169,185,0.23);
|
||||
margin-bottom: 15px;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
&:hover{
|
||||
border-color: #0d5ef8;
|
||||
}
|
||||
.nodes {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
font-size: 14px;
|
||||
}
|
||||
.node{
|
||||
padding: 1px 12px;
|
||||
background: rgba(70,106,255,0.05);
|
||||
border-radius: 4px;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
.templateSelected {
|
||||
border-color: #0d5ef8;
|
||||
}
|
||||
.themeColorSpan {
|
||||
color: #0d5ef8;
|
||||
}
|
||||
|
||||
.format-style{
|
||||
div[class~="ant-modal-title"]{
|
||||
font-size: 18px;
|
||||
color: #0d0f12;
|
||||
}
|
||||
div[class~='ant-modal-content']{
|
||||
position: relative;
|
||||
&::before{
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
width: 100%;
|
||||
height: 120px;
|
||||
left: 0px;
|
||||
content: "";
|
||||
z-index: 1;
|
||||
border-radius: 9px;
|
||||
background: linear-gradient(179.62deg,#c0ceff 0%,rgba(214, 233, 255, 0.78) 49.37%,#fbfcff 92%,#fff 100%);
|
||||
}
|
||||
&::after{
|
||||
position: absolute;
|
||||
top: 30px;
|
||||
width: 180px;
|
||||
height: 140px;
|
||||
content: "";
|
||||
left: 55px;
|
||||
z-index: 2;
|
||||
background-image: url('../../../images/devOps/modal-back.png');
|
||||
}
|
||||
div[class~="ant-modal-body"]{
|
||||
min-height: 100px;
|
||||
position: relative;
|
||||
z-index: 3;
|
||||
padding: 0 24px;
|
||||
}
|
||||
div[class~="ant-modal-header"]{
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
background-color: transparent;
|
||||
position: relative;
|
||||
z-index: 3;
|
||||
border-bottom: unset;
|
||||
}
|
||||
div[class~="ant-modal-footer"]{
|
||||
border-top: unset;
|
||||
}
|
||||
}
|
||||
div[class~='format-button']{
|
||||
text-align: center;
|
||||
padding:20px 0;
|
||||
button{
|
||||
width: 100px;
|
||||
margin:0px 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,183 @@
|
|||
import React , { useState , useEffect } from 'react';
|
||||
import { getProjectBranch, addPipelines, getTemplateList } from './api';
|
||||
import './index.scss';
|
||||
import { Modal , Form , Input, Select , Button, Divider } from 'antd';
|
||||
|
||||
const modalList = [
|
||||
{
|
||||
name: '空白模板',
|
||||
id: -1,
|
||||
nodes: [{ name: '请自定义您的流水线模板' }]
|
||||
},
|
||||
]
|
||||
|
||||
const pipType = {
|
||||
all: 0,
|
||||
code: 1,
|
||||
image: 2
|
||||
}
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
|
||||
const New =({
|
||||
open,onCancel,form,match, onOk
|
||||
}) => {
|
||||
|
||||
const { getFieldDecorator } = form;
|
||||
const {owner, projectsId} = match.params;
|
||||
// const [ loading, setLoading ] = useState(false);
|
||||
const [ pipelineType, setPipelineType ] = useState(pipType.code);
|
||||
const [ branchList, setBranchList] = useState([]);
|
||||
const [ templateList, setTemplateList ] = useState([]);
|
||||
const [ templateSelected, setTemplateSelected ] = useState(-1);
|
||||
|
||||
let settings = JSON.parse(localStorage.chromesetting);
|
||||
let pipelineUrl = settings && settings.common.pipeline;
|
||||
|
||||
useEffect(() => {
|
||||
if(open){
|
||||
getTemplates();
|
||||
getBranchList()
|
||||
}
|
||||
}, [open]);
|
||||
|
||||
function getTemplates(){
|
||||
// 获取流水线模板列表
|
||||
getTemplateList().then(res=>{
|
||||
|
||||
if(res.templates){
|
||||
res.templates.map((item)=>{
|
||||
return item.nodes = JSON.parse(item.json).nodes
|
||||
})
|
||||
setTemplateList(modalList.concat(res.templates));
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function getBranchList () {
|
||||
getProjectBranch(owner, projectsId).then(res => {
|
||||
setBranchList(res)
|
||||
})
|
||||
}
|
||||
|
||||
async function onSubmit() {
|
||||
await form.validateFields();
|
||||
const { branch , pipeline_name, pipeline_type } = form.getFieldsValue();
|
||||
const params = {
|
||||
branch,pipeline_name, pipeline_type
|
||||
}
|
||||
if (pipelineType === pipType.image) params.pipelineTemplateJson = templateSelected !== -1 ? getTemplateJson(templateSelected) : {nodes: [], edges: [], combos: []}
|
||||
addPipelines(owner, projectsId, params).then(res=>{
|
||||
if(res.id){
|
||||
form.resetFields();
|
||||
onOk()
|
||||
window.open(`${ pipelineUrl }/devops/${owner}/${projectsId}/${res.id}/edit`)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const getTemplateJson = (id) => {
|
||||
let json = {}
|
||||
for (let i = 0; i < templateList.length; i++) {
|
||||
const element = templateList[i];
|
||||
if (element.id === id) json = JSON.parse(element.json)
|
||||
}
|
||||
return json
|
||||
}
|
||||
|
||||
function close() {
|
||||
form.resetFields();
|
||||
onCancel();
|
||||
}
|
||||
|
||||
return(
|
||||
<Modal
|
||||
visible={open}
|
||||
width={"686px"}
|
||||
className="format-style"
|
||||
title={"新建流水线"}
|
||||
onCancel={close}
|
||||
footer={
|
||||
<div style={{textAlign:"center"}}>
|
||||
<Button style={{width:"90px"}} onClick={close}>取消</Button>
|
||||
<Button type={"primary"} className="ml25" onClick={onSubmit} style={{width:"90px"}}>保存</Button>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Form layout={"vertical"}>
|
||||
|
||||
<Form.Item label="流水线名称">
|
||||
{getFieldDecorator("pipeline_name",{
|
||||
rules:[{ required: true, message: '请输入流水线名称' }]
|
||||
})(
|
||||
<Input placeholder={"请输入流水线名称"} maxLength={50}/>
|
||||
)}
|
||||
</Form.Item>
|
||||
<Form.Item label="代码库分支">
|
||||
{getFieldDecorator("branch",{
|
||||
rules: [{ required: true, message: '请选择代码库分支' }]
|
||||
})(
|
||||
<Select
|
||||
placeholder={"请选择代码库分支"}
|
||||
filterOption={(input, option) =>
|
||||
option.title.indexOf(input.toLowerCase()) >= 0
|
||||
}
|
||||
showSearch
|
||||
getPopupContainer={trigger => trigger.parentNode}
|
||||
>
|
||||
{
|
||||
branchList.map((item)=>{
|
||||
return <Select.Option key={item.name} value={item.name} title={item.name}>
|
||||
<span><i className="iconfont icon-a-fenzhi1 font14 mr5"/>{item.name}</span>
|
||||
</Select.Option>
|
||||
})
|
||||
}
|
||||
</Select>
|
||||
)}
|
||||
</Form.Item>
|
||||
<Form.Item label="流水线类型">
|
||||
{getFieldDecorator("pipeline_type",{
|
||||
rules: [{ required: true, message: '请选择流水线类型' }]
|
||||
})(
|
||||
<Select
|
||||
placeholder={"请选择流水线类型"}
|
||||
onChange={e => { setPipelineType(e) }}
|
||||
>
|
||||
<Option value={ pipType.code }>声明式流水线</Option>
|
||||
<Option value={ pipType.image }>图形化流水线</Option>
|
||||
</Select>
|
||||
)}
|
||||
</Form.Item>
|
||||
{
|
||||
pipelineType === pipType.image &&
|
||||
<Form.Item label="请选择流水线模板" >
|
||||
<div className='templateContainer'>
|
||||
{
|
||||
templateList.map(e => {
|
||||
console.log('e.nodes', e.nodes);
|
||||
|
||||
return <div className={`template ${ e.id === templateSelected ? 'templateSelected' : '' }`} onClick={()=>{
|
||||
setTemplateSelected(e.id)
|
||||
}}>
|
||||
<span className='col0d0Text font16'><i className="iconfont icon-moban mr10 themeColorSpan font14"/>{e.name}</span>
|
||||
<div className='mt15 nodes'>
|
||||
{e.nodes && e.nodes.map((item, index)=>{
|
||||
const nextItem = e.nodes[index+1]
|
||||
return <React.Fragment>
|
||||
<span className={`node mb10`}>{item.name}</span>
|
||||
{nextItem && <Divider className='mb10' style={{marginTop: '0', minWidth: '45px', width: '45px', borderTop: '1px solid #B8C1CF', display: 'inline-flex'}}/>}
|
||||
</React.Fragment>
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
})
|
||||
}
|
||||
</div>
|
||||
</Form.Item>
|
||||
}
|
||||
</Form>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
export default Form.create()(New)
|
||||
|
|
@ -151,6 +151,10 @@ const Review = Loadable({
|
|||
loader: () => import('../Newfile/codeReview'),
|
||||
loading: Loading,
|
||||
});
|
||||
const DevopsCard = Loadable({
|
||||
loader: () => import('../DevOps/cardList'),
|
||||
loading: Loading,
|
||||
});
|
||||
/**
|
||||
* permission:Manager:管理员,Reporter:报告人员(只有读取权限),Developer:开发人员(除不能设置仓库信息外)
|
||||
*/
|
||||
|
|
@ -176,6 +180,8 @@ function checkPathname(projectsId, owner, pathname) {
|
|||
name = "dataset"
|
||||
} else if (url.indexOf(`/devops`) > -1) {
|
||||
name = "devops"
|
||||
} else if (url.indexOf(`/actions`) > -1) {
|
||||
name = "devops"
|
||||
} else if (url.indexOf(`/source`) > -1) {
|
||||
name = "source"
|
||||
} else if (url.indexOf(`/wiki`) > -1) {
|
||||
|
|
@ -839,6 +845,12 @@ class Detail extends Component {
|
|||
() => (<DevIndex {...this.props} {...this.state} {...common} />)
|
||||
}
|
||||
></Route>
|
||||
{/* 流水线 */}
|
||||
<Route path="/:owner/:projectsId/actions"
|
||||
render={
|
||||
() => (<DevopsCard {...this.props} {...this.state} {...common} />)
|
||||
}
|
||||
></Route>
|
||||
{/* 标签列表 */}
|
||||
{/* <Route path="/:owner/:projectsId/issues/tags"
|
||||
render={
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { Skeleton , Tooltip} from 'antd';
|
||||
import { Dropdown, Menu, Skeleton , Tooltip} from 'antd';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { numFormat } from 'educoder';
|
||||
import QuitBox from './quit';
|
||||
|
|
@ -96,13 +96,28 @@ function DetailBanner({ history,list , owner , projectsId ,showNotification , ur
|
|||
{/* 引擎仅对当前仓库管理员可见 */}
|
||||
{
|
||||
item.menu_name === "devops" ?
|
||||
<li className={pathname==="devops" ? "active" : ""}>
|
||||
{/* <Link to={{ pathname: `/${owner}/${projectsId}/devops${open_devops ? `/dispose`:""}`, state }}> */}
|
||||
<Link className='newTab' to={{ pathname: `/${owner}/${projectsId}/devops`, state:{...state,open_devops} }}>
|
||||
<i className="iconfont icon-gongzuoliuicon font-13 mr5 color-grey-3"></i>引擎(Engine)
|
||||
{projectDetail && projectDetail.ops_count ? <span>{projectDetail.ops_count}</span> : ""}
|
||||
</Link>
|
||||
</li>
|
||||
<Dropdown overlay={
|
||||
<Menu>
|
||||
<Menu.Item>
|
||||
<Link className='color-dark' to={{ pathname: `/${owner}/${projectsId}/devops`, state:{...state,open_devops} }} style={{padding: '9px 12px'}}>
|
||||
<i className='iconfont icon-lianluzhuizong1 color-grey-3 font-14 mr3'></i>引擎(Engine)
|
||||
{projectDetail && projectDetail.ops_count ? <span>{projectDetail.ops_count}</span> : ""}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
<Link className='color-dark' to={{ pathname: `/${owner}/${projectsId}/actions`, state:{...state,open_devops} }} style={{padding: '9px 12px'}}>
|
||||
<i className='iconfont icon-lianluzhuizong1 color-grey-3 font-14 mr3'></i>引擎(Actions)
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
}>
|
||||
<li className={pathname==="devops" ? "active" : ""} onClick={e => e.preventDefault()}>
|
||||
<a>
|
||||
<i className="iconfont icon-gongzuoliuicon font-13 mr5 color-grey-3"></i>
|
||||
流水线(devops)
|
||||
</a>
|
||||
</li>
|
||||
</Dropdown>
|
||||
:""
|
||||
}
|
||||
{
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ const menu = [
|
|||
{name:"代码库",index:"code"},
|
||||
{name:"疑修 (Issue)",index:"issues"},
|
||||
{name:"合并请求 (PR)",index:"pulls"},
|
||||
{name:"引擎 (Engine)",index:"devops"},
|
||||
{name:"流水线 (Devops)",index:"devops"},
|
||||
{name:"数据集",index:"dataset"},
|
||||
// {name:"资源库",index:"resources"},
|
||||
{name:"里程碑",index:"versions"},
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 494 B |
Binary file not shown.
|
After Width: | Height: | Size: 483 B |
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 698 B |
Binary file not shown.
|
After Width: | Height: | Size: 54 KiB |
Loading…
Reference in New Issue