This commit is contained in:
ryanmao 2020-10-03 22:28:16 +08:00
parent 62b15a84cf
commit a0a3bea743
21 changed files with 465 additions and 52 deletions

View File

@ -40,7 +40,7 @@ const sqls = `
Target Server Version : 3030001
File Encoding : 65001
Date: 02/10/2020 16:02:56
Date: 02/10/2020 22:40:17
*/
PRAGMA foreign_keys = false;
@ -99,7 +99,8 @@ CREATE TABLE "t_model" (
"times" datetime,
"del" integer DEFAULT 0,
"envs" text,
"wrkdir" text
"wrkdir" text,
"clrdir" integer DEFAULT 0
);
-- ----------------------------

Binary file not shown.

View File

@ -10,7 +10,7 @@
Target Server Version : 3030001
File Encoding : 65001
Date: 02/10/2020 16:02:56
Date: 02/10/2020 22:40:17
*/
PRAGMA foreign_keys = false;
@ -69,7 +69,8 @@ CREATE TABLE "t_model" (
"times" datetime,
"del" integer DEFAULT 0,
"envs" text,
"wrkdir" text
"wrkdir" text,
"clrdir" integer DEFAULT 0
);
-- ----------------------------

View File

@ -85,3 +85,14 @@ func (c *execManager) StopTask(id int) {
//_, err := comm.Db.Cols("state").Where("id=?", v.Id).Update(v)
//return err
}
func (c *execManager) TaskRun(id int) {
c.lk.Lock()
defer c.lk.Unlock()
e, ok := c.tasks[id]
if ok {
if e.cncl == nil {
}
}
}

View File

@ -4,10 +4,13 @@ import (
"bytes"
"context"
"errors"
"fmt"
"gokins/comm"
"gokins/model"
"gokins/service/dbService"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"time"
@ -35,34 +38,61 @@ func (c *RunTask) start() {
return
}
c.ctx, c.cncl = context.WithCancel(context.Background())
go func() {
defer ruisUtil.Recovers("RunTask start", nil)
c.plugs = nil
err := comm.Db.Where("del!='1' and tid=?", c.Mr.Tid).OrderBy("sort ASC,id ASC").Find(&c.plugs)
if err != nil {
c.end(2, "db err:"+err.Error())
return
}
for _, v := range c.plugs {
select {
case <-c.ctx.Done():
c.end(-1, "手动停止")
return
default:
err := c.run(v)
if err != nil {
println("cmd run err:", err.Error())
c.end(2, err.Error())
return
}
time.Sleep(time.Second)
}
}
c.end(4, "")
}()
go c.run()
}
func (c *RunTask) run(pgn *model.TPlugin) (rterr error) {
func (c *RunTask) run() {
defer ruisUtil.Recovers("RunTask start", nil)
c.plugs = nil
if c.Md.Wrkdir != "" {
if ruisIo.PathExists(c.Md.Wrkdir) {
if c.Md.Clrdir == 1 {
err := rmDirFiles(c.Md.Wrkdir)
if err != nil {
c.end(2, "运行目录创建失败:"+err.Error())
return
}
}
} else {
if c.Md.Clrdir != 1 {
c.end(2, "运行目录不存在")
return
}
err := os.MkdirAll(c.Md.Wrkdir, 0755)
if err != nil {
c.end(2, "运行目录创建失败:"+err.Error())
return
}
}
}
err := comm.Db.Where("del!='1' and tid=?", c.Mr.Tid).OrderBy("sort ASC,id ASC").Find(&c.plugs)
if err != nil {
c.end(2, "db err:"+err.Error())
return
}
if len(c.plugs) <= 0 {
c.end(2, "无插件")
return
}
for _, v := range c.plugs {
select {
case <-c.ctx.Done():
c.end(-1, "手动停止")
return
default:
err := c.runs(v)
if err != nil {
println("cmd run err:", err.Error())
c.end(2, err.Error())
return
}
time.Sleep(time.Second)
}
}
c.end(4, "")
}
func (c *RunTask) runs(pgn *model.TPlugin) (rterr error) {
defer ruisUtil.Recovers("RunTask.run", func(errs string) {
rterr = errors.New(errs)
})
@ -95,14 +125,14 @@ func (c *RunTask) run(pgn *model.TPlugin) (rterr error) {
cmd := exec.CommandContext(c.ctx, name, par0, pgn.Cont)
cmd.Stdout = c.stdout
cmd.Stderr = c.stdout
if c.Md.Wrkdir != "" && ruisIo.PathExists(c.Md.Wrkdir) {
cmd.Dir = c.Md.Wrkdir
}
if c.Md.Envs != "" {
str := strings.ReplaceAll(c.Md.Envs, "\t", "")
envs := strings.Split(str, "\n")
cmd.Env = envs
}
if c.Md.Wrkdir != "" {
cmd.Dir = c.Md.Wrkdir
}
err := cmd.Run()
rn.State = 4
if err != nil {
@ -120,7 +150,7 @@ func (c *RunTask) run(pgn *model.TPlugin) (rterr error) {
return err
}
if pgn.Exend == 1 && rn.Excode != 0 {
return errors.New("cmd exit err")
return fmt.Errorf("程序执行错误:%d", rn.Excode)
}
return nil
}
@ -142,3 +172,22 @@ func (c *RunTask) stop() {
c.cncl = nil
}
}
func rmDirFiles(dir string) error {
d, err := os.Open(dir)
if err != nil {
return err
}
defer d.Close()
names, err := d.Readdirnames(0)
if err != nil {
return err
}
for _, name := range names {
err = os.RemoveAll(filepath.Join(dir, name))
if err != nil {
return err
}
}
return nil
}

View File

@ -13,6 +13,7 @@ type TModel struct {
Del int
Envs string
Wrkdir string
Clrdir int
}
type TModelRun struct {

View File

@ -14,6 +14,7 @@ type Model struct {
Times time.Time
Envs string
Wrkdir string
Clrdir int
}
func (Model) TableName() string {
@ -23,7 +24,7 @@ func (Model) TableName() string {
func (c *Model) Save() error {
var err error
if c.Id > 0 {
_, err = comm.Db.Cols("title", "desc", "envs", "wrkdir").Where("id=?", c.Id).Update(c)
_, err = comm.Db.Cols("title", "desc", "envs", "wrkdir", "clrdir").Where("id=?", c.Id).Update(c)
} else {
c.Times = time.Now()
_, err = comm.Db.Insert(c)

View File

@ -13,6 +13,7 @@ type ModelRun struct {
Times time.Time
Timesd time.Time
State int
Errs string
Nick string `xorm:"-"`
Times1 string `xorm:"-"`

37
models/plug_run.go Normal file
View File

@ -0,0 +1,37 @@
package models
import (
"fmt"
"gokins/service/dbService"
"time"
)
type PluginUI struct {
Id int `xorm:"pk autoincr"`
Tid int //model id
Type int
Title string
Times time.Time
Sort int
Exend int
RunStat int `xorm:"-"`
Hstm string `xorm:"-"`
}
func (PluginUI) TableName() string {
return "t_plugin"
}
func (c *PluginUI) ToUI(mrid int) {
run := dbService.FindPluginRun(c.Tid, mrid, c.Id)
if run != nil {
c.RunStat = run.State
c.Hstm = "0"
if run.State >= 2 {
c.Hstm = fmt.Sprintf("%.3f", run.Timesd.Sub(run.Times).Seconds())
} else if run.State == 1 {
c.Hstm = fmt.Sprintf("%.3f", time.Since(run.Times).Seconds())
}
}
}

View File

@ -18,6 +18,7 @@ func Init() {
gpModel := comm.Gin.Group("/model")
gpModel.Use(utilService.MidNeedLogin)
gpModel.Any("/get", core.GinHandler(server.ModelGet))
gpModel.Any("/list", core.GinHandler(server.ModelList))
gpModel.Any("/edit", core.GinHandler(server.ModelEdit))
gpModel.Any("/del", core.GinHandler(server.ModelDel))
@ -30,4 +31,6 @@ func Init() {
gpPlug.Any("/list", core.GinHandler(server.PlugList))
gpPlug.Any("/edit", core.GinHandler(server.PlugEdit))
gpPlug.Any("/del", core.GinHandler(server.PlugDel))
gpPlug.Any("/runs", core.GinHandler(server.PlugRuns))
gpPlug.Any("/log", core.GinHandler(server.PlugLog))
}

View File

@ -7,12 +7,26 @@ import (
"gokins/mgr"
"gokins/model"
"gokins/models"
"gokins/service/dbService"
"gokins/service/utilService"
"github.com/gin-gonic/gin"
ruisUtil "github.com/mgr9525/go-ruisutil"
)
func ModelGet(c *gin.Context, req *ruisUtil.Map) {
id, err := req.GetInt("id")
if err != nil || id <= 0 {
c.String(500, "param err")
return
}
m := dbService.GetModel(int(id))
if m == nil {
c.String(404, "not found")
return
}
c.JSON(200, m)
}
func ModelList(c *gin.Context, req *ruisUtil.Map) {
pg, _ := req.GetInt("page")
q := req.GetString("q")

View File

@ -5,6 +5,7 @@ import (
"gokins/comm"
"gokins/model"
"gokins/models"
"gokins/service/dbService"
"github.com/gin-gonic/gin"
ruisUtil "github.com/mgr9525/go-ruisutil"
@ -49,3 +50,56 @@ func PlugDel(c *gin.Context, req *ruisUtil.Map) {
}
c.String(200, fmt.Sprintf("%d", m.Id))
}
func PlugRuns(c *gin.Context, req *ruisUtil.Map) {
id, err := req.GetInt("id")
if err != nil || id <= 0 {
c.String(500, "param err")
return
}
mr := dbService.GetModelRun(int(id))
if mr == nil {
c.String(404, "not found")
return
}
ls := make([]*models.PluginUI, 0)
ses := comm.Db.Where("del!='1' and tid=?", mr.Tid).OrderBy("sort ASC,id ASC")
err = ses.Find(&ls)
if err != nil {
c.String(500, "find err:"+err.Error())
return
}
for _, v := range ls {
v.ToUI(mr.Id)
}
c.JSON(200, ls)
}
func PlugLog(c *gin.Context, req *ruisUtil.Map) {
tid, err := req.GetInt("tid")
if err != nil || tid <= 0 {
c.String(500, "param err")
return
}
pid, err := req.GetInt("pid")
if err != nil || pid <= 0 {
c.String(500, "param err")
return
}
mr := dbService.GetModelRun(int(tid))
if mr == nil {
c.String(404, "not found")
return
}
e := dbService.FindPluginRun(mr.Tid, mr.Id, int(pid))
res := ruisUtil.NewMap()
res.Set("up", false)
res.Set("text", "")
if e.State == 1 {
res.Set("up", true)
res.Set("text", e.Output)
} else if e.State >= 2 {
res.Set("text", e.Output)
}
c.JSON(200, res)
}

View File

@ -19,3 +19,17 @@ func GetModel(id int) *model.TModel {
}
return nil
}
func GetModelRun(id int) *model.TModelRun {
if id <= 0 {
return nil
}
e := new(model.TModelRun)
ok, err := comm.Db.Where("id=?", id).Get(e)
if err != nil {
return nil
}
if ok {
return e
}
return nil
}

View File

@ -19,6 +19,20 @@ func GetPlugin(id int) *model.TPlugin {
}
return nil
}
func GetPluginRun(id int) *model.TPluginRun {
if id <= 0 {
return nil
}
e := new(model.TPluginRun)
ok, err := comm.Db.Where("id=?", id).Get(e)
if err != nil {
return nil
}
if ok {
return e
}
return nil
}
func FindPluginRun(mid, tid, pid int) *model.TPluginRun {
if mid <= 0 || tid <= 0 || pid <= 0 {
return nil

View File

@ -23,8 +23,9 @@ let routes = [
iconCls: 'el-icon-message',//图标样式class
children: [
{ path: '/models', component: require('@/views/ruis/ModelList'), name: '流水线' },
{ path: '/models/info', component: require('@/views/ruis/PlugList'), name: '流水线详情', hidden: true },
{ path: '/models/info', component: require('@/views/ruis/PlugList'), name: '流水线插件', hidden: true },
{ path: '/models/runs', component: require('@/views/ruis/RunList'), name: '流水线运行', hidden: true },
{ path: '/models/plug/runs', component: require('@/views/ruis/PlugRunList'), name: '流水日志', hidden: true },
]
},
/*{

View File

@ -89,6 +89,7 @@
this.filters.page=res.data.Page;
//NProgress.done();
}).catch(err=>{
this.loading = false;
this.$message({
message: err.response.data||'服务器错误',
type: 'error'

View File

@ -8,9 +8,12 @@
<el-form-item label="描述">
<el-input type="textarea" v-model="formData.Desc" auto-complete="off"></el-input>
</el-form-item>
<el-form-item label="工作目录">
<el-form-item label="运行目录">
<el-input v-model="formData.Wrkdir" auto-complete="off"></el-input>
</el-form-item>
<el-form-item label="目录问题">
<el-switch v-model="formData.clrdir" active-text="创建或清空运行目录"></el-switch>
</el-form-item>
<el-form-item label="环境变量">
<el-input type="textarea" v-model="formData.Envs" auto-complete="off" :rows="8" placeholder="每行一个变量,格式name=xxxx"></el-input>
</el-form-item>
@ -47,16 +50,18 @@
Id:'',
Title: '',
Desc: '',
Envs:'',
Wrkdir:'',
Envs:''
clrdir:false
}
if(e)
this.formData={
Id:e.Id,
Title: e.Title,
Desc: e.Desc,
Envs:e.Envs,
Wrkdir:e.Wrkdir,
Envs:e.Envs
clrdir:e.Clrdir==1
}
},/*handleSelect:function(id){
this.tmpltCont='';
@ -81,6 +86,7 @@
this.$refs.formd.validate((valid) => {
if (valid) {
this.formLoading = true;
this.formData.Clrdir=this.formData.clrdir?1:2;
this.$post('/model/edit',this.formData).then(res=>{
console.log(res);
this.$emit('submitOK');

View File

@ -21,16 +21,16 @@
</el-table-column> -->
<el-table-column type="index" width="60">
</el-table-column>
<el-table-column label="名称" sortable>
<el-table-column label="名称" width="250" sortable>
<template slot-scope="{row}">
<div class="wxmpTit">
<el-link type="primary" @click="$router.push({path:'/models/runs?id='+row.Id})">
<img :src="row.Avat"/>
<!-- <img :src="row.Avat"/> -->
{{ row.Title }}</el-link>
</div>
</template>
</el-table-column>
<el-table-column prop="Desc" label="描述" width="180" sortable>
<el-table-column prop="Desc" label="描述" sortable>
</el-table-column>
<el-table-column label="状态" width="80">
<template slot-scope="{row}">
@ -41,7 +41,7 @@
<el-table-column label="操作" width="150">
<template slot-scope="{row}">
<el-button size="small" @click="$refs.editor.show(row)">编辑</el-button>
<el-button size="small" @click="$router.push({path:'/models/info?id='+row.Id})">插件</el-button>
<!-- <el-button size="small" @click="$router.push({path:'/models/info?id='+row.Id})">插件</el-button> -->
<el-popconfirm title="确定要删除吗?" @onConfirm="handleDel(row)">
<el-button type="danger" size="small" slot="reference">删除</el-button>
</el-popconfirm>
@ -96,6 +96,7 @@ import ModelForm from './ModelForm'
this.filters.page=res.data.Page;
//NProgress.done();
}).catch(err=>{
this.loading = false;
this.$message({
message: err.response.data||'服务器错误',
type: 'error'

View File

@ -21,13 +21,14 @@
<span>{{ row.Title }}</span>
</template>
</el-table-column>
<el-table-column label="类型" width="80" sortable>
<template slot-scope="{row}">
<span>Shell</span>
</template>
</el-table-column>
<el-table-column prop="Sort" label="排序" width="100" sortable>
</el-table-column>
<el-table-column label="状态" width="80">
<template slot-scope="{row}">
<span v-if="row.Cancel" style="color:red">取消授权</span>
<span v-if="!row.Cancel" style="color:green">正常</span>
</template>
<el-table-column prop="Times" label="创建时间" width="200" sortable>
</el-table-column>
<el-table-column label="操作" width="150">
<template slot-scope="{row}">
@ -82,6 +83,7 @@ import PlugForm from './PlugForm'
this.listdata = res.data;
//NProgress.done();
}).catch(err=>{
this.loading = false;
this.$message({
message: err.response.data||'服务器错误',
type: 'error'

View File

@ -0,0 +1,158 @@
<template>
<section>
<!--工具条-->
<el-col :span="24" class="toolbar" style="padding-bottom: 0px;">
<el-button type="warning" @click="$router.back(-1)">返回</el-button>
<el-button type="primary" @click="getList">刷新</el-button>
</el-col>
<div class="mains">
<div style="width:400px;margin-right:10px">
<el-card class="box-card runs" :shadow="mpdata[it.Id]&&mpdata[it.Id].selected?'always':'hover'"
:class="mpdata[it.Id]&&mpdata[it.Id].selected?'runselect':''"
v-for="(it,idx) in listdata" :key="'run'+it.Id">
<div class="runrow" @click="showLog(idx)">
<div style="flex:1">{{idx+1}}. {{it.Title}}
<br/><span style="color:#909399">{{it.Hstm}}s</span>
</div>
<div>
<el-tag v-if="it.RunStat==0" type="info">等待</el-tag>
<el-tag v-if="it.RunStat==1" type="warning">运行</el-tag>
<el-tag v-if="it.RunStat==2" type="danger">失败</el-tag>
<el-tag v-if="it.RunStat==4" type="success">成功</el-tag>
</div>
</div>
</el-card>
</div>
<div style="flex:1;white-space: break-spaces;word-break: break-all;">
<el-card class="box-card">
<div style="" v-text="logs[selid]&&logs[selid].text"></div>
</el-card>
</div>
</div>
<!--工具条-->
<el-col :span="24" class="toolbar">
</el-col>
</section>
</template>
<script>
//import NProgress from 'nprogress'
export default {
data() {
return {
tid:'',
loading: false,
listdata: [],
selid:0,
mpdata:{},
logs:{}
}
},
mounted() {
this.tid=this.$route.query.id;
if(this.tid==null||this.tid==''){
this.$router.push({ path: '/' });
return
}
this.start();
this.getList();
},destroyed(){
clearInterval(window.plugTimer);
},
methods: {
//
getList() {
this.loading = true;
//NProgress.start();
this.$post('/plug/runs',{id:this.tid}).then((res) => {
console.log(res);
this.loading = false;
this.listdata = res.data;
}).catch(err=>{
this.loading = false;
this.$message({
message: err.response.data||'服务器错误',
type: 'error'
});
});
},selsChange(sels) {
this.sels = sels;
},start(){
let that=this;
let tmr=window.plugTimer;
if(tmr)clearInterval(tmr);
tmr=setInterval(() => {
that.getList();
that.getLog();
/*for(let k in that.mpdata){
let v=that.mpdata[k];
if(v&&v.selected)getLog(k);
}*/
}, 1000);
window.plugTimer=tmr;
},showLog(idx){
for(let i in this.listdata){
let e=this.listdata[i];
if(this.mpdata[e.Id])
this.mpdata[e.Id].selected=false;
}
let e=this.listdata[idx];
if(this.mpdata[e.Id]){
this.mpdata[e.Id].selected=true;
}else{
this.mpdata[e.Id]={selected:true}
}
this.selid=e.Id;
this.getLog();
this.$forceUpdate();
console.log('showLog:',this.mpdata[idx]);
},getLog(){
if(this.selid==''||this.selid<=0)return;
let v=this.logs[this.selid]
if(v&&v.up==false)return
this.$post('/plug/log',{tid:this.tid,pid:this.selid}).then(res=>{
this.logs[this.selid]=res.data;
})
},batchRemove(){
}
}
}
</script>
<style scoped>
.wxmpTit{
line-height: 60px;
margin-top: 5px;
margin-bottom: 5px;
}
.wxmpTit img{
width: 60px;
height: 60px;
float: left;
margin-right: 10px;
}
.mains{
display:flex;
clear:both;
}
.mains .runs{
margin-bottom: 10px;
cursor: pointer;
}
.mains .runselect{
border: 1px solid red;
}
.mains .runrow{
display: flex;
width: 100%;
}
</style>

View File

@ -3,24 +3,50 @@
<!--工具条-->
<el-col :span="24" class="toolbar" style="padding-bottom: 0px;">
<el-form :inline="true">
<el-form-item>
<el-button type="primary" @click="handleRun">运行</el-button>
</el-form-item>
<el-form-item>
<el-button type="primary" v-on:click="getList">刷新</el-button>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleRun">运行</el-button>
<el-button type="primary" v-on:click="$refs.editor.show(md)">编辑</el-button>
</el-form-item>
<el-form-item>
<el-button type="primary" v-on:click="$router.push('/models/info?id='+md.Id)">插件列表</el-button>
</el-form-item>
</el-form>
</el-col>
<el-card class="box-card">
<el-row class="text item infoItem">
<el-col :span="10">任务名称{{md.Title}}</el-col>
<el-col :span="6">创建时间{{md.Times}}</el-col>
</el-row>
<el-row class="text item infoItem">
<el-col :span="12">任务描述{{md.Desc}}</el-col>
</el-row>
<el-row class="text item infoItem">
<el-col :span="10">运行目录{{md.Wrkdir}}</el-col>
<el-col :span="6">创建或清空运行目录{{md.Clrdir==1?'是':'否'}}</el-col>
</el-row>
<el-row class="text item infoItem">
<el-col :span="12">环境变量<p v-text="md.Envs"></p></el-col>
</el-row>
</el-card>
<!--列表-->
<el-table :data="listdata" highlight-current-row v-loading="loading" @selection-change="selsChange" style="width: 100%;">
<el-table-column type="index" width="60">
<el-table-column label="编号" width="80">
<template slot-scope="{row}">
<el-link type="primary" @click="$router.push('/models/plug/runs?id='+row.Id)">
#{{row.Id}}</el-link>
</template>
</el-table-column>
<el-table-column prop="Times1" label="运行时间" width="200" sortable>
</el-table-column>
<el-table-column prop="Times2" label="结束时间" width="200" sortable>
</el-table-column>
<el-table-column prop="Nick" label="执行人" sortable>
<el-table-column prop="Nick" label="执行人" width="200" sortable>
</el-table-column>
<el-table-column label="状态" width="80">
<template slot-scope="{row}">
@ -31,6 +57,8 @@
<span v-if="row.State==4" style="color:green">运行成功</span>
</template>
</el-table-column>
<el-table-column prop="Errs" label="错误" sortable>
</el-table-column>
<el-table-column label="操作" width="100">
<template scope="{row}">
<el-button type="danger" size="small" @click="handleDel(row)" v-if="row.State==0||row.State==1">停止</el-button>
@ -44,13 +72,16 @@
<el-pagination layout="prev, pager, next" :current-page.sync="page" :total="total" :page-size="limit" @current-change="getList" style="float:right;">
</el-pagination>
</el-col>
<ModelForm ref="editor" @submitOK="getInfo()"/>
</section>
</template>
<script>
import ModelForm from './ModelForm'
//import NProgress from 'nprogress'
export default {
components:{ModelForm},
data() {
return {
tid:'',
@ -60,17 +91,25 @@
limit:0,
listdata: [],
sels: [],//
md:{}
}
},
mounted() {
this.tid=this.$route.query.id;
if(this.tid==null||this.tid==''){
this.$router.push({ path: '/' });
this.$router.push({ path: '/' });
return
}
this.getInfo();
this.getList();
},
methods: {
getInfo(){
this.$post('/model/get',{id:this.tid}).then(res=>{
this.md=res.data;
})
},
//
getList() {
this.loading = true;
@ -84,6 +123,7 @@
this.page=res.data.Page;
//NProgress.done();
}).catch(err=>{
this.loading = false;
this.$message({
message: err.response.data||'服务器错误',
type: 'error'
@ -125,4 +165,7 @@
float: left;
margin-right: 10px;
}
.infoItem{
margin-bottom: 10px;
}
</style>