├── mods └── mods.txt ├── deployLogs └── deployLogs ├── src ├── home │ ├── controller │ │ ├── static.js │ │ ├── base.js │ │ ├── index.js │ │ ├── install.js │ │ ├── user.js │ │ ├── cperson.js │ │ ├── history.js │ │ ├── logserver.js │ │ ├── auth.js │ │ └── machine.js │ ├── config │ │ └── config.js │ ├── model │ │ ├── index.js │ │ ├── install.js │ │ ├── cperson.js │ │ ├── user.js │ │ ├── machine.js │ │ ├── project.js │ │ └── history.js │ └── logic │ │ ├── auth.js │ │ ├── index.js │ │ └── user.js └── common │ ├── config │ ├── locale │ │ └── en.js │ ├── env │ │ ├── production.js │ │ ├── testing.js │ │ └── development.js │ ├── error.js │ ├── hook.js │ ├── view.js │ ├── config.js │ ├── websocket.js │ ├── session.js │ └── db.js │ ├── service │ ├── impl │ │ ├── build_java.sh │ │ ├── deploy.sh │ │ ├── build_javascript.sh │ │ ├── diffdir.js │ │ ├── svn.js │ │ └── git.js │ ├── verutil.js │ ├── email.js │ ├── restart.js │ ├── fileutil.js │ ├── deploy.js │ ├── build.js │ └── cvs.js │ ├── model │ └── user.js │ ├── bootstrap │ ├── global.js │ └── middleware.js │ ├── middleware │ └── auth.js │ └── controller │ └── error.js ├── stop.sh ├── frontend ├── src │ ├── common │ │ └── lib.js │ ├── component │ │ ├── header.jsx │ │ ├── project_detail_component │ │ │ ├── build_log.jsx │ │ │ ├── stateinfo.js │ │ │ ├── detail_ajax.js │ │ │ └── baseinfo.jsx │ │ ├── sidebar.jsx │ │ ├── install_form.jsx │ │ ├── new_project.jsx │ │ ├── project_list.jsx │ │ ├── charge_person_list.jsx │ │ ├── changePass_form.jsx │ │ ├── charge_person_form.jsx │ │ ├── user_stat.jsx │ │ ├── project_stat.jsx │ │ ├── login_form.jsx │ │ ├── env_stat.jsx │ │ ├── build_history.jsx │ │ ├── register_form.jsx │ │ ├── stat.jsx │ │ ├── project_detail.jsx │ │ └── machine_list.jsx │ ├── entry │ │ ├── install.jsx │ │ ├── auth.jsx │ │ └── index.jsx │ └── style │ │ ├── project.less │ │ ├── app.less │ │ ├── diffcode.less │ │ └── project_list.less ├── .eslintrc ├── .gitignore └── .editorconfig ├── start.sh ├── docs └── imgs │ ├── lo.png │ ├── login.png │ ├── login.tiff │ ├── buildlog.png │ ├── checkout.png │ ├── createdb.png │ ├── dobuild.png │ ├── dobuild.tiff │ ├── dodeploy.png │ ├── buildlog.tiff │ ├── buildresult.png │ ├── checkout.tiff │ ├── commitdiff.png │ ├── commitdiff.tiff │ ├── commitlog.png │ ├── commitlog.tiff │ ├── createdb.tiff │ ├── deploydone.png │ ├── deploydone.tiff │ ├── dodeploy.tiff │ ├── go_machine1.png │ ├── go_macine1.png │ ├── go_macine1.tiff │ ├── incdeploy.png │ ├── incdeploy.tiff │ ├── modifydiff.png │ ├── modifydiff.tiff │ ├── newmachine1.png │ ├── newmachine2.png │ ├── newmachine3.png │ ├── newproject.png │ ├── newproject.tiff │ ├── buildresult.tiff │ ├── buildresult2.png │ ├── buildresult2.tiff │ ├── go_machine1.tiff │ ├── java_machine2.png │ ├── java_macine1.png │ ├── java_macine1.tiff │ ├── newmachine1.tiff │ ├── newmachine2.tiff │ ├── newmachine3.tiff │ ├── project_info.png │ ├── project_info.tiff │ ├── java_machine2.tiff │ └── %B5%A3%AC%D5%E6%A2%D5%C9%EC.tiff ├── .thinkjsrc ├── webpack.config.js ├── .gitignore ├── www ├── README.md ├── testing.js ├── production.js └── development.js ├── pm2.json ├── view ├── home │ ├── index_index.html │ ├── auth_index.html │ └── install_index.html └── login │ └── login_index.html ├── LICENSE ├── package.json ├── nginx.conf └── db └── db.sql /mods/mods.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /deployLogs/deployLogs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/home/controller/static.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /stop.sh: -------------------------------------------------------------------------------- 1 | forever stop www/production.js 2 | -------------------------------------------------------------------------------- /frontend/src/common/lib.js: -------------------------------------------------------------------------------- 1 | import 'antd/dist/antd.css'; 2 | -------------------------------------------------------------------------------- /frontend/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint-config-airbnb" 3 | } 4 | -------------------------------------------------------------------------------- /start.sh: -------------------------------------------------------------------------------- 1 | forever start -a -o out.log -e err.log www/production.js 2 | -------------------------------------------------------------------------------- /docs/imgs/lo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdfe/ideploy/HEAD/docs/imgs/lo.png -------------------------------------------------------------------------------- /src/common/config/locale/en.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | export default { 4 | 5 | }; -------------------------------------------------------------------------------- /docs/imgs/login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdfe/ideploy/HEAD/docs/imgs/login.png -------------------------------------------------------------------------------- /docs/imgs/login.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdfe/ideploy/HEAD/docs/imgs/login.tiff -------------------------------------------------------------------------------- /src/common/config/env/production.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | export default { 4 | 5 | }; -------------------------------------------------------------------------------- /src/common/config/env/testing.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | export default { 4 | 5 | }; -------------------------------------------------------------------------------- /docs/imgs/buildlog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdfe/ideploy/HEAD/docs/imgs/buildlog.png -------------------------------------------------------------------------------- /docs/imgs/checkout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdfe/ideploy/HEAD/docs/imgs/checkout.png -------------------------------------------------------------------------------- /docs/imgs/createdb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdfe/ideploy/HEAD/docs/imgs/createdb.png -------------------------------------------------------------------------------- /docs/imgs/dobuild.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdfe/ideploy/HEAD/docs/imgs/dobuild.png -------------------------------------------------------------------------------- /docs/imgs/dobuild.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdfe/ideploy/HEAD/docs/imgs/dobuild.tiff -------------------------------------------------------------------------------- /docs/imgs/dodeploy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdfe/ideploy/HEAD/docs/imgs/dodeploy.png -------------------------------------------------------------------------------- /src/common/config/env/development.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | export default { 4 | 5 | }; -------------------------------------------------------------------------------- /docs/imgs/buildlog.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdfe/ideploy/HEAD/docs/imgs/buildlog.tiff -------------------------------------------------------------------------------- /docs/imgs/buildresult.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdfe/ideploy/HEAD/docs/imgs/buildresult.png -------------------------------------------------------------------------------- /docs/imgs/checkout.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdfe/ideploy/HEAD/docs/imgs/checkout.tiff -------------------------------------------------------------------------------- /docs/imgs/commitdiff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdfe/ideploy/HEAD/docs/imgs/commitdiff.png -------------------------------------------------------------------------------- /docs/imgs/commitdiff.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdfe/ideploy/HEAD/docs/imgs/commitdiff.tiff -------------------------------------------------------------------------------- /docs/imgs/commitlog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdfe/ideploy/HEAD/docs/imgs/commitlog.png -------------------------------------------------------------------------------- /docs/imgs/commitlog.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdfe/ideploy/HEAD/docs/imgs/commitlog.tiff -------------------------------------------------------------------------------- /docs/imgs/createdb.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdfe/ideploy/HEAD/docs/imgs/createdb.tiff -------------------------------------------------------------------------------- /docs/imgs/deploydone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdfe/ideploy/HEAD/docs/imgs/deploydone.png -------------------------------------------------------------------------------- /docs/imgs/deploydone.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdfe/ideploy/HEAD/docs/imgs/deploydone.tiff -------------------------------------------------------------------------------- /docs/imgs/dodeploy.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdfe/ideploy/HEAD/docs/imgs/dodeploy.tiff -------------------------------------------------------------------------------- /docs/imgs/go_machine1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdfe/ideploy/HEAD/docs/imgs/go_machine1.png -------------------------------------------------------------------------------- /docs/imgs/go_macine1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdfe/ideploy/HEAD/docs/imgs/go_macine1.png -------------------------------------------------------------------------------- /docs/imgs/go_macine1.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdfe/ideploy/HEAD/docs/imgs/go_macine1.tiff -------------------------------------------------------------------------------- /docs/imgs/incdeploy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdfe/ideploy/HEAD/docs/imgs/incdeploy.png -------------------------------------------------------------------------------- /docs/imgs/incdeploy.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdfe/ideploy/HEAD/docs/imgs/incdeploy.tiff -------------------------------------------------------------------------------- /docs/imgs/modifydiff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdfe/ideploy/HEAD/docs/imgs/modifydiff.png -------------------------------------------------------------------------------- /docs/imgs/modifydiff.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdfe/ideploy/HEAD/docs/imgs/modifydiff.tiff -------------------------------------------------------------------------------- /docs/imgs/newmachine1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdfe/ideploy/HEAD/docs/imgs/newmachine1.png -------------------------------------------------------------------------------- /docs/imgs/newmachine2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdfe/ideploy/HEAD/docs/imgs/newmachine2.png -------------------------------------------------------------------------------- /docs/imgs/newmachine3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdfe/ideploy/HEAD/docs/imgs/newmachine3.png -------------------------------------------------------------------------------- /docs/imgs/newproject.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdfe/ideploy/HEAD/docs/imgs/newproject.png -------------------------------------------------------------------------------- /docs/imgs/newproject.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdfe/ideploy/HEAD/docs/imgs/newproject.tiff -------------------------------------------------------------------------------- /.thinkjsrc: -------------------------------------------------------------------------------- 1 | { 2 | "createAt": "2016-02-23 19:19:45", 3 | "mode": "module", 4 | "es": true 5 | } -------------------------------------------------------------------------------- /docs/imgs/buildresult.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdfe/ideploy/HEAD/docs/imgs/buildresult.tiff -------------------------------------------------------------------------------- /docs/imgs/buildresult2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdfe/ideploy/HEAD/docs/imgs/buildresult2.png -------------------------------------------------------------------------------- /docs/imgs/buildresult2.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdfe/ideploy/HEAD/docs/imgs/buildresult2.tiff -------------------------------------------------------------------------------- /docs/imgs/go_machine1.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdfe/ideploy/HEAD/docs/imgs/go_machine1.tiff -------------------------------------------------------------------------------- /docs/imgs/java_machine2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdfe/ideploy/HEAD/docs/imgs/java_machine2.png -------------------------------------------------------------------------------- /docs/imgs/java_macine1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdfe/ideploy/HEAD/docs/imgs/java_macine1.png -------------------------------------------------------------------------------- /docs/imgs/java_macine1.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdfe/ideploy/HEAD/docs/imgs/java_macine1.tiff -------------------------------------------------------------------------------- /docs/imgs/newmachine1.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdfe/ideploy/HEAD/docs/imgs/newmachine1.tiff -------------------------------------------------------------------------------- /docs/imgs/newmachine2.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdfe/ideploy/HEAD/docs/imgs/newmachine2.tiff -------------------------------------------------------------------------------- /docs/imgs/newmachine3.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdfe/ideploy/HEAD/docs/imgs/newmachine3.tiff -------------------------------------------------------------------------------- /docs/imgs/project_info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdfe/ideploy/HEAD/docs/imgs/project_info.png -------------------------------------------------------------------------------- /docs/imgs/project_info.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdfe/ideploy/HEAD/docs/imgs/project_info.tiff -------------------------------------------------------------------------------- /docs/imgs/java_machine2.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdfe/ideploy/HEAD/docs/imgs/java_machine2.tiff -------------------------------------------------------------------------------- /src/home/config/config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * config 4 | */ 5 | export default { 6 | //key: value 7 | }; -------------------------------------------------------------------------------- /src/common/service/impl/build_java.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | projectPath=$1 3 | task=$2 4 | cd $projectPath 5 | echo '开始构建' 6 | $2 7 | -------------------------------------------------------------------------------- /src/home/model/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * model 4 | */ 5 | export default class extends think.model.base { 6 | 7 | } -------------------------------------------------------------------------------- /docs/imgs/%B5%A3%AC%D5%E6%A2%D5%C9%EC.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdfe/ideploy/HEAD/docs/imgs/%B5%A3%AC%D5%E6%A2%D5%C9%EC.tiff -------------------------------------------------------------------------------- /src/common/model/user.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * model 4 | */ 5 | export default class extends think.model.base { 6 | 7 | } -------------------------------------------------------------------------------- /src/home/controller/base.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | export default class extends think.controller.base { 4 | /** 5 | * some base method in here 6 | */ 7 | } -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | module.exports = function(webpackConfig) { 3 | webpackConfig.babel.plugins.push('antd'); 4 | return webpackConfig; 5 | }; 6 | -------------------------------------------------------------------------------- /src/common/config/error.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * err config 4 | */ 5 | export default { 6 | //key: value 7 | key: "errno", //error number 8 | msg: "errmsg" //error message 9 | }; 10 | -------------------------------------------------------------------------------- /src/common/config/hook.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * hook config 5 | * https://thinkjs.org/doc/middleware.html#toc-df6 6 | */ 7 | export default { 8 | controller_before: ["auth"] 9 | } 10 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .idea/ 3 | .ipr 4 | .iws 5 | *~ 6 | ~* 7 | *.diff 8 | *.patch 9 | *.bak 10 | .DS_Store 11 | Thumbs.db 12 | .project 13 | .*proj 14 | .svn/ 15 | *.swp 16 | *.swo 17 | *.pyc 18 | *.pyo 19 | node_modules 20 | dist 21 | -------------------------------------------------------------------------------- /src/home/logic/auth.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * logic 4 | * @param {} [] 5 | * @return {} [] 6 | */ 7 | export default class extends think.logic.base { 8 | /** 9 | * index action logic 10 | * @return {} [] 11 | */ 12 | indexAction(){ 13 | 14 | } 15 | } -------------------------------------------------------------------------------- /src/home/logic/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * logic 4 | * @param {} [] 5 | * @return {} [] 6 | */ 7 | export default class extends think.logic.base { 8 | /** 9 | * index action logic 10 | * @return {} [] 11 | */ 12 | indexAction(){ 13 | 14 | } 15 | } -------------------------------------------------------------------------------- /src/home/logic/user.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * logic 4 | * @param {} [] 5 | * @return {} [] 6 | */ 7 | export default class extends think.logic.base { 8 | /** 9 | * index action logic 10 | * @return {} [] 11 | */ 12 | indexAction(){ 13 | 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .idea/ 3 | .ipr 4 | .iws 5 | *~ 6 | ~* 7 | *.diff 8 | *.patch 9 | *.bak 10 | .DS_Store 11 | Thumbs.db 12 | .project 13 | .*proj 14 | .svn/ 15 | *.swp 16 | *.swo 17 | *.pyc 18 | *.pyo 19 | node_modules 20 | dist 21 | app/ 22 | temp/* 23 | npm-debug.log 24 | www/static/app/ 25 | -------------------------------------------------------------------------------- /src/common/bootstrap/global.js: -------------------------------------------------------------------------------- 1 | /** 2 | * this file will be loaded before server started 3 | * you can define global functions used in controllers, models, templates 4 | */ 5 | 6 | /** 7 | * use global.xxx to define global functions 8 | * 9 | * global.fn1 = function(){ 10 | * 11 | * } 12 | */ 13 | -------------------------------------------------------------------------------- /src/common/config/view.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * template config 4 | */ 5 | export default { 6 | type: 'nunjucks', 7 | content_type: 'text/html', 8 | file_ext: '.html', 9 | file_depr: '_', 10 | root_path: think.ROOT_PATH + '/view', 11 | adapter: { 12 | ejs: {} 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /src/common/middleware/auth.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * middleware 4 | */ 5 | export default class extends think.middleware.base { 6 | /** 7 | * run 8 | * @return {} [] 9 | */ 10 | run(){ 11 | // if(this.session.user){ 12 | // this.next(); 13 | // }else{ 14 | 15 | // } 16 | } 17 | } -------------------------------------------------------------------------------- /www/README.md: -------------------------------------------------------------------------------- 1 | # antd-demo 2 | 3 | ## Environment 4 | 5 | ``` 6 | node >= 4 7 | ``` 8 | 9 | ## Code Style 10 | 11 | https://github.com/airbnb/javascript 12 | 13 | ## Develop 14 | 15 | ``` 16 | npm run dev 17 | ``` 18 | 19 | 访问 http://127.0.0.1:8989 20 | 21 | ## Build 22 | 23 | ``` 24 | npm run build 25 | ``` 26 | -------------------------------------------------------------------------------- /src/home/controller/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import Base from './base.js'; 4 | 5 | export default class extends Base { 6 | /** 7 | * index action 8 | * @return {Promise} [] 9 | */ 10 | indexAction(){ 11 | //auto render template file index_index.html 12 | return this.display(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /frontend/.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | 15 | [Makefile] 16 | indent_style = tab 17 | -------------------------------------------------------------------------------- /src/common/service/impl/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | userid=$1 3 | pw=$2 4 | h5zip=$3 5 | ip=$4 6 | port=$5 7 | topath=$6 8 | expect -c " 9 | spawn rsync -raqpPL --delete $h5zip $userid@$ip:$topath 10 | expect { 11 | \"*assword\" {set timeout 300;send \"$pw\r\";} 12 | \"yes/no\" {send \"yes\r\"; exp_continue;} 13 | } 14 | expect eof" 15 | -------------------------------------------------------------------------------- /src/common/config/config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * config 4 | */ 5 | export default { 6 | cvsUser: 'test', 7 | cvsPass: 'test', 8 | emailHost: 'smtp.qq.com', 9 | emailport: 465, 10 | emailUser: '34343434@qq.com', 11 | emailPass: 'xxxx', 12 | cvsDir: '/temp', 13 | etcdConf:['127.0.0.1:2379','127.0.0.1:4001'], 14 | port: 2016 15 | }; 16 | -------------------------------------------------------------------------------- /www/testing.js: -------------------------------------------------------------------------------- 1 | var thinkjs = require('thinkjs'); 2 | var path = require('path'); 3 | 4 | var rootPath = path.dirname(__dirname); 5 | 6 | var instance = new thinkjs({ 7 | APP_PATH: rootPath + path.sep + 'app', 8 | RUNTIME_PATH: rootPath + path.sep + 'runtime', 9 | ROOT_PATH: rootPath, 10 | RESOURCE_PATH: __dirname, 11 | env: 'testing' 12 | }); 13 | 14 | instance.run(); -------------------------------------------------------------------------------- /pm2.json: -------------------------------------------------------------------------------- 1 | { 2 | "apps": [{ 3 | "name": "wdfe-build2", 4 | "script": "www/production.js", 5 | "cwd": "/Users/shaokaiming/Playground//Users/shaokaiming/Playground/wdfe-build2", 6 | "exec_mode": "cluster", 7 | "instances": 0, 8 | "max_memory_restart": "1G", 9 | "autorestart": true, 10 | "node_args": [], 11 | "args": [], 12 | "env": { 13 | 14 | } 15 | }] 16 | } -------------------------------------------------------------------------------- /src/common/config/websocket.js: -------------------------------------------------------------------------------- 1 | export default { 2 | on: true, //是否开启 WebSocket 3 | type: "socket.io", //使用的 WebSocket 库类型,默认为 socket.io 4 | allow_origin: "", //允许的 origin 5 | adp: undefined, // socket 存储的 adapter,socket.io 下使用 6 | path: "", //url path for websocket 7 | messages: { 8 | index: "home/logserver/index", 9 | deploy: "home/logserver/deploy", 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /www/production.js: -------------------------------------------------------------------------------- 1 | var thinkjs = require('thinkjs'); 2 | var path = require('path'); 3 | 4 | var rootPath = path.dirname(__dirname); 5 | 6 | var instance = new thinkjs({ 7 | APP_PATH: rootPath + path.sep + 'app', 8 | RUNTIME_PATH: rootPath + path.sep + 'runtime', 9 | ROOT_PATH: rootPath, 10 | RESOURCE_PATH: __dirname, 11 | env: 'production' 12 | }); 13 | 14 | //preload packages before start server. 15 | instance.run(true); -------------------------------------------------------------------------------- /src/common/config/session.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * session configs 5 | */ 6 | export default { 7 | name: 'wdfebuild', 8 | type: 'memory', 9 | secret: '8!3B$5DG', 10 | timeout: 24 * 3600, 11 | cookie: { // cookie options 12 | length: 32, 13 | httponly: true 14 | }, 15 | adapter: { 16 | file: { 17 | path: think.RUNTIME_PATH + '/session', 18 | } 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /src/home/controller/install.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import Base from './base.js'; 4 | import InsModel from '../model/install'; 5 | export default class extends Base { 6 | /** 7 | * index action 8 | * @return {Promise} [] 9 | */ 10 | async indexAction(){ 11 | return this.display(); 12 | } 13 | 14 | doAction(){ 15 | let ins = new InsModel(); 16 | let res = ins.createTable(); 17 | return this.success('成功'); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /www/development.js: -------------------------------------------------------------------------------- 1 | var thinkjs = require('thinkjs'); 2 | var path = require('path'); 3 | 4 | var rootPath = path.dirname(__dirname); 5 | 6 | var instance = new thinkjs({ 7 | APP_PATH: rootPath + path.sep + 'app', 8 | RUNTIME_PATH: rootPath + path.sep + 'runtime', 9 | ROOT_PATH: rootPath, 10 | RESOURCE_PATH: __dirname, 11 | env: 'development' 12 | }); 13 | 14 | //compile src/ to app/ 15 | instance.compile({ 16 | retainLines: true, 17 | log: true 18 | }); 19 | 20 | instance.run(); -------------------------------------------------------------------------------- /frontend/src/component/header.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {Row, Col} from 'antd'; 3 | 4 | const Header = React.createClass({ 5 | render() { 6 | return ( 7 | 8 | 前端构建平台 11 | {this.props.title || '登录'} 12 | 13 | 14 | ); 15 | } 16 | }); 17 | 18 | export default Header; 19 | -------------------------------------------------------------------------------- /src/common/config/db.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * db config 4 | * @type {Object} 5 | */ 6 | export default { 7 | type: 'mysql', 8 | log_sql: true, 9 | log_connect: true, 10 | adapter: { 11 | mysql: { 12 | host: '127.0.0.1', 13 | port: '', 14 | database: 'wdfe_publish', 15 | user: 'root', 16 | password: '', 17 | prefix: '', 18 | encoding: 'utf8' 19 | }, 20 | mongo: { 21 | 22 | } 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /frontend/src/entry/install.jsx: -------------------------------------------------------------------------------- 1 | import '../common/lib'; 2 | import Header from '../component/header'; 3 | import InstallForm from '../component/install_form'; 4 | import ReactDOM from 'react-dom'; 5 | import React, {Component} from 'react'; 6 | class Install extends Component { 7 | constructor(props) { 8 | super(props); 9 | } 10 | render() { 11 | return ( 12 |
13 | 14 |
15 | ); 16 | } 17 | } 18 | 19 | ReactDOM.render( 20 |
, document.getElementById('react-header')); 21 | ReactDOM.render( 22 | , document.getElementById('react-content')); 23 | -------------------------------------------------------------------------------- /frontend/src/style/project.less: -------------------------------------------------------------------------------- 1 | .section-title { 2 | font-size: 20px; 3 | font-weight: bold; 4 | margin-bottom: 10px; 5 | color: #000000; 6 | border-bottom: 1px solid #E9E9E9; 7 | } 8 | 9 | .code-input { 10 | width: 100%; 11 | } 12 | 13 | .section-bottom { 14 | border-bottom: 0px solid #E9E9E9; 15 | } 16 | 17 | .project-box { 18 | padding: 20px; 19 | border: 1px solid #E9E9E9; 20 | border-radius: 5px; 21 | margin-bottom: 20px; 22 | } 23 | 24 | .section-gap { 25 | padding-bottom: 16px; 26 | border-bottom: 1px solid #E9E9E9; 27 | } 28 | 29 | .row-width {} 30 | 31 | .project-action { 32 | margin-bottom: 20px; 33 | text-align: right; 34 | a { 35 | margin-left: 10px; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/home/model/install.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | import fs from 'fs'; 3 | /** 4 | * model 5 | */ 6 | export default class extends think.model.base { 7 | async createTable() { 8 | let model=think.model('', think.config('db'), 'home'); 9 | let dir = __dirname.replace('app/home/model','')+'db/db.sql'; 10 | var sql = fs.readFileSync(dir).toString('utf-8'); 11 | let sqlArray = sql.split(';'); 12 | let reArray = []; 13 | for(let i =0 ;i5){ 16 | reArray[i] = await model.execute(execSql); 17 | } 18 | else{ 19 | reArray[i] = 'nosql' 20 | } 21 | } 22 | return reArray; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/common/service/impl/build_javascript.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | export PATH=/usr/lib64/qt-3.3/bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/opt/soft/node-v4.4.4-linux-x64/bin:/root/bin:/Users/boutell/npm/bin 3 | modulesPath=./temp/mods/$3/ 4 | projectPath=$1 5 | task=$2 6 | pwd 7 | 8 | if [ "$4" -eq "2" ]; then 9 | rm -rf $modulesPath 10 | fi 11 | 12 | if [ ! -d $modulesPath ]; then 13 | mkdir $modulesPath 14 | fi 15 | 16 | cp -f $projectPath/package.json $modulesPath 17 | 18 | cd $modulesPath 19 | 20 | #更新依赖库 21 | 22 | if [ "$4" -eq "2" ]; then 23 | npm install 24 | fi 25 | echo 'npm installed' 26 | 27 | cd ../../../ 28 | pwd 29 | 30 | cd $projectPath 31 | pwd 32 | echo '创建node_modules软连接 ln -s ${modulesPath}/node_modules node_modules ---' 33 | ln -s ../../mods/$3/node_modules ./node_modules 34 | pwd 35 | 36 | echo '开始构建' 37 | 38 | npm run $2 39 | -------------------------------------------------------------------------------- /src/common/bootstrap/middleware.js: -------------------------------------------------------------------------------- 1 | /** 2 | * this file will be loaded before server started 3 | * you can register middleware 4 | * https://thinkjs.org/doc/middleware.html 5 | */ 6 | 7 | /** 8 | * 9 | * think.middleware('xxx', http => { 10 | * 11 | * }) 12 | * 13 | */ 14 | 15 | think.middleware('auth', function*(http) { 16 | const reqUrl = http.url; 17 | 18 | console.log('auth middleware run', reqUrl); 19 | let value = yield http.session("user"); 20 | //过滤掉'home/user' 21 | let path = http.url.replace(http.host, ''); 22 | path = path.split('?')[0]; 23 | 24 | if (reqUrl.indexOf('install') != -1 || reqUrl.indexOf('auth') != -1 || path == '/home/user' || value) { 25 | console.log('auth pass'); 26 | } else { 27 | console.log('auth fail'); 28 | return http.redirect("/auth"); 29 | http.prevent(); 30 | } 31 | }) 32 | -------------------------------------------------------------------------------- /frontend/src/entry/auth.jsx: -------------------------------------------------------------------------------- 1 | import '../common/lib'; 2 | import Header from '../component/header'; 3 | import LoginForm from '../component/login_form'; 4 | import RegisterForm from '../component/register_form'; 5 | import ReactDOM from 'react-dom'; 6 | import React, {Component} from 'react'; 7 | class Auth extends Component { 8 | constructor(props) { 9 | super(props); 10 | this.state = { 11 | isShowRegForm: false 12 | }; 13 | } 14 | 15 | handleRegForm(isShow) { 16 | this.setState({isShowRegForm: isShow}) 17 | } 18 | 19 | render() { 20 | return ( 21 |
22 | 23 | 24 |
25 | ); 26 | } 27 | } 28 | 29 | ReactDOM.render( 30 |
, document.getElementById('react-header')); 31 | ReactDOM.render( 32 | , document.getElementById('react-content')); 33 | -------------------------------------------------------------------------------- /frontend/src/style/app.less: -------------------------------------------------------------------------------- 1 | body { 2 | background: #ffffff; 3 | } 4 | 5 | .header { 6 | height: 50px; 7 | line-height: 50px; 8 | -webkit-box-shadow: 0 0 0 1px rgba(0, 0, 0, .1); 9 | box-shadow: 0 0 0 1px rgba(0, 0, 0, .1); 10 | margin-bottom: 20px; 11 | } 12 | 13 | .tc { 14 | text-align: center; 15 | } 16 | 17 | #machineAction { 18 | margin-bottom: 20px; 19 | } 20 | 21 | #react-content { 22 | margin: 0 30px; 23 | } 24 | 25 | #react-content form { 26 | margin: 0 auto; 27 | padding: 3em 1em 1em; 28 | } 29 | 30 | .project-list { 31 | .item { 32 | list-style: none; 33 | float: left; 34 | width: 100px; 35 | height: 100px; 36 | text-align: center; 37 | font-size: 16px; 38 | } 39 | } 40 | 41 | #siderbar { 42 | float: left; 43 | } 44 | 45 | #content { 46 | float: left; 47 | padding: 0 20px; 48 | width: 100%; 49 | } 50 | 51 | @import './project.less'; 52 | @import './project_list.less'; 53 | -------------------------------------------------------------------------------- /src/common/service/verutil.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | import fs from 'fs'; 3 | import path from 'path'; 4 | /** 5 | * 版本计算 6 | */ 7 | export default class { 8 | /** 9 | 根据老版本获取新版本 10 | */ 11 | getNewVer(v) { 12 | let per = '00'; 13 | let now = new Date(); 14 | let month = now.getMonth() + 1; 15 | let day = now.getDate(); 16 | let today = now.getFullYear() + this.fixZero(month, 2) + this.fixZero(day, 2); 17 | let vCount; 18 | if (!v) v = today + per + '001'; 19 | vCount = parseInt(v.slice(-3)); 20 | //如果到999重新从0开始 21 | if (vCount === 999) vCount = 0; 22 | vCount = this.fixZero(++vCount, 3); 23 | return today + per + vCount; 24 | } 25 | 26 | fixZero(n, l) { 27 | let i; 28 | let z = ''; 29 | l = Math.max(('' + n).length, l); 30 | for (i = 0; i < l; i++) { 31 | z += '0'; 32 | } 33 | z += n; 34 | return z.slice(-1 * l); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /frontend/src/component/project_detail_component/build_log.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {Menu, Icon,Button,Row,Col,Table} from 'antd'; 3 | import {Link} from 'react-router'; 4 | const SubMenu = Menu.SubMenu; 5 | const MenuItemGroup = Menu.ItemGroup; 6 | const BuildLog = React.createClass({ 7 | appendLog(logContent){ 8 | let buildLogDom = this.refs.buildLogContent.getDOMNode(); 9 | $(buildLogDom).append(logContent); 10 | buildLogDom.scrollTop = buildLogDom.scrollHeight; 11 | }, 12 | render() { 13 | 14 | return ( 15 |
16 | 17 |
后台日志:
18 |
25 |
26 |
27 | ); 28 | } 29 | }); 30 | 31 | export default BuildLog; 32 | -------------------------------------------------------------------------------- /view/home/index_index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 构建平台 4 | 5 | 6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 |
16 |
17 | 18 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 wdfe 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /frontend/src/component/sidebar.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {Menu, Icon} from 'antd'; 3 | import {Link} from 'react-router'; 4 | const SubMenu = Menu.SubMenu; 5 | const MenuItemGroup = Menu.ItemGroup; 6 | 7 | const Sidebar = React.createClass({ 8 | getInitialState() { 9 | return {current: '1'}; 10 | }, 11 | render() { 12 | return ( 13 | 14 | 15 | 工程列表 16 | 17 | 18 | 新建工程 19 | 20 | 21 | 修改密码 22 | 23 | 24 | 统计 25 | 26 | 27 | 批量部署 28 | 29 | 30 | ); 31 | } 32 | }); 33 | 34 | export default Sidebar; 35 | -------------------------------------------------------------------------------- /frontend/src/component/project_detail_component/stateinfo.js: -------------------------------------------------------------------------------- 1 | let InitState = { 2 | isNpmInstall: '1', 3 | incExc: 1, 4 | tipModalVisible: false, 5 | projectId: '-1', 6 | tipModalCheckVisible: 'block', 7 | code_url: '', 8 | checkoutLoading: false, 9 | buildLoading: false, 10 | branchDeploy: false, 11 | canQuickDeploy: false, 12 | tagLoading: false, 13 | deployLoading: false, 14 | quickDeployLoading: false, 15 | addData: [], 16 | changeData: [], 17 | authors: [], 18 | showChangeLog: [], 19 | isShowChangeModal: false, 20 | sameData: [], 21 | buildLog: '', 22 | diffModalVisible: false, 23 | selectedFiles: [], 24 | selectedMachines: [], 25 | buildTask: '', 26 | buildDesc: '', 27 | buildPass: '', 28 | buildType: 1, 29 | sendOp: false, 30 | selectedMacRowKeys: [], 31 | infoCol: [ 32 | { 33 | title: '基本信息:', 34 | width: '100px', 35 | dataIndex: 'keyName' 36 | }, { 37 | title: '', 38 | width: '700px', 39 | dataIndex: 'keyValue' 40 | } 41 | ], 42 | selectedRowKeys: { 43 | 'add': [], 44 | 'change': [], 45 | 'same': [] 46 | }, 47 | curTab: 'add', 48 | infoData: [], 49 | machineList: [], 50 | canBuild: false, 51 | canTag: false, 52 | TagText: '打tag', 53 | canDeploy: false, 54 | canNotify: true, 55 | debug: false, 56 | notifyCharger: false 57 | }; 58 | export default InitState; 59 | -------------------------------------------------------------------------------- /frontend/src/component/install_form.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import { 3 | Button, 4 | Form, 5 | Input, 6 | Row, 7 | Col, 8 | Modal 9 | } from 'antd'; 10 | const createForm = Form.create; 11 | const FormItem = Form.Item; 12 | const confirm = Modal.confirm; 13 | function noop() { 14 | return false; 15 | } 16 | 17 | class installForm extends React.Component { 18 | constructor(props) { 19 | super(props); 20 | } 21 | handleSubmit(event) { 22 | event.preventDefault(); 23 | let me = this; 24 | $.ajax({ 25 | type: 'POST', 26 | url: '/home/install/do', 27 | data: { 28 | }, 29 | success: res => { 30 | if (res.errno == 0) { 31 | //如果安装成功则跳登录页 32 | window.location.href = './auth'; 33 | } else { 34 | } 35 | } 36 | }) 37 | } 38 | render() { 39 | return ( 40 |
41 |
42 | 欢迎使用布道部署系统,配置好数据库后,请点击以下按钮导入数据库表格 43 |
44 |
45 | 46 |
47 |
48 | ); 49 | } 50 | } 51 | 52 | installForm = createForm()(installForm); 53 | export default installForm; 54 | //ReactDOM.render(, mountNode); 55 | -------------------------------------------------------------------------------- /frontend/src/style/diffcode.less: -------------------------------------------------------------------------------- 1 | table { 2 | width: 100%; 3 | font-family: "Menlo", "Liberation Mono", "Consolas", "DejaVu Sans Mono", "Ubuntu Mono", "Courier New", "andale mono", "lucida console", monospace; 4 | border: none; 5 | margin: 0px; 6 | padding: 0px; 7 | border-collapse: collapse; 8 | border: 1px solid #e5e5e5; 9 | font-size: 13px; 10 | color: #333; 11 | } 12 | .old_line, .new_line, .diff_line { 13 | margin: 0px; 14 | padding: 0px; 15 | border: none; 16 | background: whitesmoke; 17 | color: rgba(0,0,0,0.3)!important; 18 | padding: 0px 5px; 19 | border-right: 1px solid #e5e5e5; 20 | text-align: right; 21 | min-width: 35px; 22 | max-width: 50px; 23 | width: 35px; 24 | -webkit-user-select: none; 25 | -moz-user-select: none; 26 | -ms-user-select: none; 27 | user-select: none; 28 | } 29 | .unfold { 30 | cursor: pointer; 31 | } 32 | td { 33 | line-height: 1.5; 34 | font-size: 13px; 35 | } 36 | .matched { 37 | color: #e5e5e5; 38 | background: #fafafa; 39 | } 40 | .line_content { 41 | display: block; 42 | margin: 0px; 43 | padding: 0px 0.5em; 44 | border: none; 45 | max-width: 900px; 46 | white-space: pre-wrap; 47 | word-break: break-all; 48 | } 49 | .old { 50 | background: #ffecec; 51 | } 52 | .old .old_line, .old .new_line { 53 | background: #ffdddd; 54 | border-color: #f1c0c0; 55 | } 56 | .new .old_line, .new .new_line { 57 | background: #dbffdb; 58 | border-color: #c1e9c1; 59 | } 60 | .new { 61 | background: #eaffea; 62 | } 63 | -------------------------------------------------------------------------------- /view/home/auth_index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 36 | 39 | 40 | 41 |
42 |
43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /view/home/install_index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 36 | 39 | 40 | 41 |
42 |
43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/home/controller/user.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | import User from '../model/user' 3 | /** 4 | * rest controller 5 | * @type {Class} 6 | */ 7 | export default class extends think.controller.rest { 8 | /** 9 | * init 10 | * @param {Object} http [] 11 | * @return {} [] 12 | */ 13 | init(http) { 14 | super.init(http); 15 | } 16 | 17 | indexAction() {} 18 | /** 19 | *rest get 根据用户名获取用户 20 | */ 21 | async putAction() { 22 | console.log('updatePass action======================='); 23 | let sessionUser = await this.http.session('user'); 24 | let name = sessionUser.name; 25 | let repass = this.param('name'), 26 | pass = this.param('pass'); 27 | if (repass != pass) { 28 | return this.fail('2次输入密码不一致'); 29 | } 30 | let userModel = new User(); 31 | let affectedRows = await userModel.updatePass(name, pass); 32 | if (affectedRows) { 33 | return this.fail('修改成功!'); 34 | } else { 35 | return this.fail('修改失败') 36 | } 37 | } 38 | async getAction() { 39 | let data; 40 | let name = this.get('name'); 41 | let userModel = new User(); 42 | if (name) { 43 | data = await userModel.getUserByName(name); 44 | return this.success(data); 45 | } else { 46 | data = await userModel.getAll(); 47 | return this.success(data); 48 | } 49 | } 50 | /** 51 | * before magic method 52 | * @return {Promise} [] 53 | */ 54 | __before() { 55 | 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/common/service/email.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | import mailSender from 'nodemailer'; 3 | import request from 'request'; 4 | export default class extends think.service.base { 5 | /** 6 | * init 7 | * @return {} [] 8 | */ 9 | init(...args) { 10 | super.init(...args); 11 | } 12 | 13 | sendEmail(name, subject, content, maillist) { 14 | let emailHost = think.config('emailHost'); 15 | let emailPort = think.config('emailport') || 465; 16 | let emailUser = think.config('emailUser'); 17 | let emailPass = think.config('emailPass'); 18 | let smtpConfig = { 19 | host: emailHost, // 主机 20 | ignoreTLS: false, 21 | secureConnection: true, // 使用 SSL 22 | port: emailPort, // SMTP 端口 23 | auth: { 24 | user: emailUser, // 账号 25 | pass: emailPass // 密码 26 | } 27 | }; 28 | console.log(smtpConfig); 29 | let smtpTransport = mailSender.createTransport("SMTP",smtpConfig); 30 | var mailOptions = { 31 | from: emailUser, // 发件地址 32 | to: maillist, // 收件列表 33 | subject: subject, // 标题 34 | html: content // html 内容 35 | } 36 | return new Promise((resolve, reject) => { 37 | smtpTransport.sendMail(mailOptions, function(error, response){ 38 | console.log(error); 39 | console.log(response); 40 | smtpTransport.close(); // 如果没用,关闭连接池 41 | if (!error) { 42 | resolve(error, response.message); 43 | } else { 44 | reject(error, response.message); 45 | } 46 | }); 47 | }) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /frontend/src/component/project_detail_component/detail_ajax.js: -------------------------------------------------------------------------------- 1 | export default class { 2 | getProjectById(id,that) { 3 | $.ajax({ 4 | type: 'get', 5 | url: '/home/project/get_project_by_id', 6 | data: { 7 | id: id 8 | }, 9 | success: function(res) { 10 | let itemDatas = res.data; 11 | let baseInfoArray = []; 12 | let codeLang = itemDatas.code_lang; 13 | // if(itemDatas.code_lang == 0){ 14 | // codeLang = 'javascript'; 15 | // } 16 | // else if(itemDatas.code_lang == 1){ 17 | // codeLang = 'php'; 18 | // } 19 | // else if(itemDatas.code_lang == 2){ 20 | // codeLang = 'java'; 21 | // } 22 | baseInfoArray.push({key: '1', keyName: '项目名称:', keyValue: itemDatas.name}); 23 | baseInfoArray.push({key: '2', keyName: '创建人:', keyValue: itemDatas.creater}); 24 | baseInfoArray.push({key: '3', keyName: '仓库地址:', keyValue: itemDatas.code_url}); 25 | baseInfoArray.push({key: '4', keyName: '代码语言:', keyValue: codeLang}); 26 | let vcsTypeStr = (itemDatas.vcs_type == 1 ? "git" : "svn"); 27 | that.setState({ 28 | sessionUser: res.data.sessionUser, 29 | infoData: baseInfoArray, 30 | projectData: itemDatas, 31 | code_url: itemDatas.code_url, 32 | name: itemDatas.name, 33 | vcs_type: itemDatas.vcs_type 34 | }); 35 | } 36 | }) 37 | } 38 | 39 | getMachineList(id,self) { 40 | $.ajax({ 41 | url: '/home/machine/project/id/' + id, 42 | type: 'GET', 43 | success: function(res) { 44 | if (res.errno == 0) { 45 | self.setState({machineList: res.data}) 46 | } 47 | } 48 | }) 49 | } 50 | checkout(){ 51 | 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/home/model/cperson.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * @authors Your Name (you@example.org) 4 | * @date 2016-06-16 17:46:07 5 | * @version $Id$ 6 | */ 7 | 8 | 'use strict'; 9 | /** 10 | * model 11 | */ 12 | export default class extends think.model.base { 13 | async getCpersonById(id) { 14 | let cpersonModel = think.model('cperson', think.config('db'), 'home'); 15 | let data = await cpersonModel.where({ 16 | 'id': id 17 | }).field('cperson.id,cperson.name,cperson.add_time,cperson.project_id,cperson.status,cperson.email').select(); 18 | return data; 19 | } 20 | 21 | async getCpersonByProject(projectId) { 22 | let cpersonModel = think.model('cperson', think.config('db'), 'home'); 23 | let data = await cpersonModel.where({ 24 | 'project_id': projectId 25 | }).field('cperson.id,cperson.name,cperson.add_time,cperson.project_id,cperson.status,cperson.email').select(); 26 | console.log('data', data) 27 | return data; 28 | } 29 | async updateCperson(data) { 30 | let cpersonModel = think.model("cperson", think.config("db"), "home"); 31 | let affectedRows = await cpersonModel.where({ 32 | id: data.id 33 | }).update(data); 34 | return affectedRows; 35 | } 36 | async deleteCperson(id) { 37 | let cpersonModel = think.model("cperson", think.config("db"), "home"); 38 | let affectedRows = await cpersonModel.where({ 39 | id: id 40 | }).delete(id); 41 | return affectedRows; 42 | } 43 | async newCperson(data) { 44 | let cpersonModel = think.model("cperson", think.config("db"), "home"); 45 | console.log(data); 46 | let insertId = await cpersonModel.add(data); 47 | return insertId; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/home/model/user.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | import rp from 'request-promise'; 3 | 4 | /** 5 | * model 6 | */ 7 | export default class extends think.model.base { 8 | async getUserByName(name) { 9 | let userModel = think.model("user", think.config("db"), "home"); 10 | let data = await userModel.where({ 11 | "name": name 12 | }).find(); 13 | return data; 14 | } 15 | async loginFromLDAP(name, pass) { 16 | console.log(name,pass); 17 | let requestOption = { 18 | method: 'GET', 19 | uri: 'http://yourldap.com/api/login', 20 | qs: { 21 | username: name, 22 | password: pass 23 | }, 24 | json: true 25 | }; 26 | let data = await rp(requestOption); 27 | let result; 28 | try { 29 | result = (data.result.result.status_code == 0); 30 | }catch(e) { 31 | result = false; 32 | } 33 | return result; 34 | } 35 | async getUserById(id) { 36 | let userModel = think.model("user", think.config("db"), "home"); 37 | let data = await userModel.where({ 38 | "id": id 39 | }).find(); 40 | return data; 41 | } 42 | async updatePass(name,pass) { 43 | let user = {}; 44 | user.name = name; 45 | user.pass = pass; 46 | let userModel = think.model("user", think.config("db"), "home"); 47 | let affectedRows = await userModel.where({ 48 | "name": name, 49 | }).update(user); 50 | return affectedRows; 51 | } 52 | async getAll() { 53 | let userModel = think.model("user", think.config("db"), "home"); 54 | let data = await userModel.fieldReverse('pass').select(); 55 | return data; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/home/model/machine.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * model 4 | */ 5 | export default class extends think.model.base { 6 | async getMachineById(machineId) { 7 | let machineModel = think.model('machine', think.config('db'), 'home'); 8 | let data = await machineModel.where({ 9 | 'id': machineId 10 | }).find(); 11 | return data; 12 | } 13 | 14 | async getMachinesByProject(projectId) { 15 | let machineModel = think.model('machine', think.config('db'), 'home'); 16 | // let data = await machineModel.where({ 'project_id': projectId }).select(); 17 | let data = await machineModel.join({ 18 | table: 'project', 19 | join: 'inner', 20 | as: 'p', 21 | on: ['project_id', 'id'] 22 | }).where({ 23 | 'project_id': projectId 24 | }).field('machine.id,machine.name,machine.type,op_env_id,is_lock,lock_user,ip,sdir,dir,task,ssh_user,ssh_pass,server_dir,after_deploy_shell,before_deploy_shell,machine.deploy_hook as deploy_hook,machine.hook_params as hook_params,project_id,p.name as pro_name').select(); 25 | return data; 26 | } 27 | async updateMachine(data) { 28 | let machineModel = think.model("machine", think.config("db"), "home"); 29 | let affectedRows = await machineModel.where({ 30 | id: data.id 31 | }).update(data); 32 | return affectedRows; 33 | } 34 | async deleteMachine(id) { 35 | let machineModel = think.model("machine", think.config("db"), "home"); 36 | let affectedRows = await machineModel.where({ 37 | id: id 38 | }).delete(id); 39 | return affectedRows; 40 | } 41 | async newMachine(data) { 42 | let machineModel = think.model("machine", think.config("db"), "home"); 43 | let insertId = await machineModel.add(data); 44 | return insertId; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/common/service/restart.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | import childProcessPro from 'child-process-es6-promise'; 3 | import fileUtil from './fileutil'; 4 | import fs from 'fs' 5 | export default class extends think.service.base { 6 | /** 7 | * init 8 | * @return {} [] 9 | */ 10 | init(...args) { 11 | super.init(...args); 12 | } 13 | // async deploy(deployInfo, deployUser, deployPass,deployLogFile) { 14 | async execRemoteShell(ip,dir,shell) { 15 | //ansible 127.0.0.1 -m shell -a 'cd /Users/waynelu/vue-ssr-hmr-template && ./restart.sh || ./start.sh' 16 | let cmd = 'ansible ' +ip+' -m shell -a \'cd '+dir+' && '+shell+'\''; 17 | console.log(cmd); 18 | //改成同步执行 19 | let result = await childProcessPro.exec(cmd, { 20 | maxBuffer: 100 * 1024 * 1024 21 | }); 22 | let options = { 23 | encoding: 'utf8', 24 | mode: 438, 25 | flag: 'a' 26 | }; 27 | console.log(result); 28 | //fs.writeFileSync(deployLogFile, result.stdout, options); 29 | 30 | return result; 31 | //let pro = childProcessPro.spawn('./src/common/service/impl/deploy.sh', [shUser, shPass, sourceDirFinal, deployInfo.ip, shPort, targetDir]); 32 | 33 | // var proChild = pro.child; 34 | // proChild.stdout.on('data', (data) => { 35 | // console.log(`stdout: ${data}`); 36 | // let options = { encoding: 'utf8', mode: 438, flag: 'a' }; 37 | // fs.writeFileSync(deployLogFile, data, options); 38 | // }); 39 | // proChild.stderr.on('data', (data) => { 40 | // console.log(`stderr: ${data}`); 41 | // let options = { encoding: 'utf8', mode: 438, flag: 'a' }; 42 | // fs.writeFileSync(deployLogFile, data, options); 43 | // }); 44 | // 45 | // proChild.on('close', (code) => {}); 46 | //return pro; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/home/model/project.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * model 4 | */ 5 | export default class extends think.model.base { 6 | /** 7 | */ 8 | async getProjectById(id) { 9 | let proModel = think.model("project", think.config("db"), "home"); 10 | let data = await proModel.where({ 11 | "id": id 12 | }).find(); 13 | return data; 14 | } 15 | async getProjectList(pageId, pageSize) { 16 | let proModel = think.model("project", think.config("db"), "home"); 17 | let data = await proModel.page(pageId, pageSize).countSelect(); 18 | return data; 19 | } 20 | async newProject(pro) { 21 | let proModel = think.model("project", think.config("db"), "home"); 22 | console.log(pro) 23 | let insertId = await proModel.add(pro); 24 | 25 | return insertId; 26 | } 27 | async updateProject(pro) { 28 | console.log(pro); 29 | let proModel = think.model("project", think.config("db"), "home"); 30 | let affectedRows = await proModel.where({ 31 | id: pro.id 32 | }).update(pro); 33 | return affectedRows; 34 | } 35 | async getProjectBuildInfo(projectId, machindId) { 36 | let proModel = think.model("project", think.config("db"), "home"); 37 | let data = await proModel.join({ 38 | table: 'machine', 39 | join: 'inner', 40 | as: 'm' 41 | }).where({ 42 | 'project.id': projectId, 43 | 'm.id': machindId 44 | }).field('project.id as pro_id,project.name as pro_name,project.code_lang as code_lang,' 45 | +'project.hook_params as hook_params,project.deploy_hook as deploy_hook,build_hook,' 46 | +'m.hook_params as machine_hook_params,m.deploy_hook as machine_deploy_hook,' 47 | +'m.id as machine_id,m.name as machine_name,ip,sdir,dir,task,ssh_user,ssh_pass,type,is_lock,' 48 | +'lock_user,server_dir,after_deploy_shell,before_deploy_shell').select(); 49 | return data; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/common/controller/error.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * error controller 4 | */ 5 | export default class extends think.controller.base { 6 | /** 7 | * display error page 8 | * @param {Number} status [] 9 | * @return {Promise} [] 10 | */ 11 | displayError(status){ 12 | 13 | //hide error message on production env 14 | if(think.env === 'production'){ 15 | this.http.error = null; 16 | } 17 | 18 | let errorConfig = this.config('error'); 19 | let message = this.http.error && this.http.error.message || ''; 20 | if(this.isJsonp()){ 21 | return this.jsonp({ 22 | [errorConfig.key]: status, 23 | [errorConfig.msg]: message 24 | }) 25 | }else if(this.isAjax()){ 26 | return this.fail(status, message); 27 | } 28 | 29 | let module = 'common'; 30 | if(think.mode !== think.mode_module){ 31 | module = this.config('default_module'); 32 | } 33 | let file = `${module}/error/${status}.html`; 34 | let options = this.config('tpl'); 35 | options = think.extend({}, options, {type: 'base', file_depr: '_'}); 36 | this.fetch(file, {}, options).then(content => { 37 | content = content.replace('ERROR_MESSAGE', message); 38 | this.type(options.content_type); 39 | this.end(content); 40 | }); 41 | } 42 | /** 43 | * Bad Request 44 | * @return {Promise} [] 45 | */ 46 | _400Action(){ 47 | return this.displayError(400); 48 | } 49 | /** 50 | * Forbidden 51 | * @return {Promise} [] 52 | */ 53 | _403Action(){ 54 | return this.displayError(403); 55 | } 56 | /** 57 | * Not Found 58 | * @return {Promise} [] 59 | */ 60 | _404Action(){ 61 | return this.displayError(404); 62 | } 63 | /** 64 | * Internal Server Error 65 | * @return {Promise} [] 66 | */ 67 | _500Action(){ 68 | return this.displayError(500); 69 | } 70 | /** 71 | * Service Unavailable 72 | * @return {Promise} [] 73 | */ 74 | _503Action(){ 75 | return this.displayError(503); 76 | } 77 | } -------------------------------------------------------------------------------- /frontend/src/component/new_project.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import { 3 | Button, 4 | Form, 5 | Input, 6 | Row, 7 | Col, 8 | Radio, 9 | Modal 10 | } from 'antd'; 11 | const FormItem = Form.Item; 12 | const RadioGroup = Radio.Group; 13 | 14 | class CustomizedForm extends Component { 15 | constructor(props) { 16 | super(props); 17 | } 18 | getValidateStatus(field) { 19 | const {isFieldValidating, getFieldError, getFieldValue} = this.props.form; 20 | 21 | if (isFieldValidating(field)) { 22 | return 'validating'; 23 | } else if (!!getFieldError(field)) { 24 | return 'error'; 25 | } else if (getFieldValue(field)) { 26 | return 'success'; 27 | } 28 | } 29 | render() { 30 | const {getFieldProps} = this.props.form; 31 | const nameProps = getFieldProps('name', { 32 | rules: [ 33 | { 34 | required: true, 35 | message: '请输入项目名' 36 | } 37 | ] 38 | }); 39 | return ( 40 |
41 |
42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | svn 51 | git 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 |
63 |
64 | ); 65 | } 66 | } 67 | 68 | CustomizedForm = Form.create({})(CustomizedForm); 69 | 70 | export default CustomizedForm; 71 | -------------------------------------------------------------------------------- /src/home/controller/cperson.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * @authors Your Name (you@example.org) 4 | * @date 2016-06-16 17:47:19 5 | * @version $Id$ 6 | */ 7 | 8 | 'use strict'; 9 | import Base from './base.js'; 10 | import Cperson from '../model/cperson'; 11 | import Moment from 'moment'; 12 | /** 13 | * rest controller 14 | * @type {Class} 15 | */ 16 | export default class extends Base { 17 | 18 | async indexAction() { 19 | let cpersonModel = new Cperson(); 20 | let data; 21 | if (this.http.isPost()) { 22 | let values = this.post(); 23 | values.project_id = parseInt(values.project_id); 24 | values.add_time = new Moment().format('YYYY-MM-DD HH:mm:ss'); 25 | let insertId = await cpersonModel.newCperson(values); 26 | return this.success(insertId); 27 | } else { 28 | let id = this.get('id'); 29 | if (id) { 30 | data = await cpersonModel.getCpersonById(id); 31 | return this.success(data); 32 | } else { 33 | return this.fail('缺少projectId'); 34 | } 35 | } 36 | } 37 | async updateAction() { 38 | if (this.http.isPost()) { 39 | let cpersonModel = new Cperson(); 40 | let values = this.post(); 41 | let aRows = await cpersonModel.updateCperson(values); 42 | return this.success(aRows); 43 | } 44 | } 45 | async deleteAction() { 46 | if (this.http.isPost()) { 47 | let cpersonModel = new Cperson(); 48 | let id = this.post('id'); 49 | let aRows = await cpersonModel.deleteCperson(id); 50 | return this.success(aRows); 51 | } 52 | } 53 | 54 | async projectAction() { 55 | let cpersonModel = new Cperson(); 56 | let data; 57 | let projectId = this.get('id'); 58 | if (projectId) { 59 | console.log(data); 60 | data = await cpersonModel.getCpersonByProject(projectId); 61 | return this.success(data); 62 | } else { 63 | return this.fail('缺少projectId'); 64 | } 65 | 66 | } 67 | 68 | /** 69 | * before magic method 70 | * @return {Promise} [] 71 | */ 72 | __before() { 73 | 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /frontend/src/component/project_detail_component/baseinfo.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {Menu, Icon,Button,Row,Col,Table} from 'antd'; 3 | import {Link} from 'react-router'; 4 | const SubMenu = Menu.SubMenu; 5 | const MenuItemGroup = Menu.ItemGroup; 6 | 7 | const BaseInfo = React.createClass({ 8 | getInitialState() { 9 | return { 10 | current: '1', 11 | infoCol: [ 12 | { 13 | title: '基本信息:', 14 | width: '100px', 15 | dataIndex: 'keyName' 16 | }, { 17 | title: '', 18 | width: '700px', 19 | dataIndex: 'keyValue' 20 | } 21 | ] 22 | }; 23 | }, 24 | handleProForm(isShow, promFormTitle) { 25 | let id = this.props.projectId; 26 | window.location.href = '#/new_project?id=' + id; 27 | }, 28 | render() { 29 | let showPage = false; 30 | return ( 31 |
32 |
33 | 修改 56 | 57 | 58 | 59 |
60 | 61 | 62 | 63 | ); 64 | } 65 | }); 66 | 67 | export default BaseInfo; 68 | -------------------------------------------------------------------------------- /src/home/controller/history.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import Base from './base.js'; 4 | import HisModel from '../model/history'; 5 | import fs from 'fs'; 6 | export default class extends Base { 7 | /** 8 | * index action 9 | * @return {Promise} [] 10 | */ 11 | indexAction() { 12 | //auto render template file index_index.html 13 | return this.display(); 14 | } 15 | async getHistoryListAction() { 16 | let proId = this.param('proId'); 17 | let pageId = this.param('pageId'); 18 | let pageSize = this.param('pageSize'); 19 | let hModel = new HisModel(); 20 | let dataList = await hModel.getHistoryList(proId, pageId, pageSize); 21 | return this.success(dataList); 22 | } 23 | async getHistoryLogAction() { 24 | let buildLog = this.param('buildLog'); 25 | let deployLog = this.param('deployLog'); 26 | let dir = __dirname.replace('app/home/controller', ''); 27 | buildLog=dir+buildLog.replace(dir, ''); 28 | deployLog=dir+deployLog.replace(dir, ''); 29 | let logContent = fs.readFileSync(buildLog, "utf-8") + fs.readFileSync(deployLog, "utf-8");; 30 | return this.success(logContent); 31 | } 32 | async getTotalStatAction(){ 33 | let startTime = this.param('startTime'); 34 | let endTime = this.param('endTime'); 35 | let user = this.param('user'); 36 | let type = this.param('type'); 37 | let hModel = new HisModel(); 38 | let dataList = await hModel.getTotalStat(startTime,endTime,user,type); 39 | return this.success(dataList); 40 | } 41 | async getUserStatAction() { 42 | let startTime = this.param('startTime'); 43 | let endTime = this.param('endTime'); 44 | let projectId = this.param('id'); 45 | let type = this.param('type'); 46 | let hModel = new HisModel(); 47 | let dataList = await hModel.getUserStat(startTime,endTime,projectId,type); 48 | return this.success(dataList); 49 | } 50 | async getEnvStatAction() { 51 | let startTime = this.param('startTime'); 52 | let endTime = this.param('endTime'); 53 | let projectId = this.param('id'); 54 | let user = this.param('user'); 55 | let hModel = new HisModel(); 56 | let dataList = await hModel.getEnvStat(startTime,endTime,projectId,user); 57 | return this.success(dataList); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "thinkjs-application", 3 | "description": "application created by thinkjs", 4 | "version": "1.0.0", 5 | "entry": { 6 | "index": "./frontend/src/entry/index.jsx", 7 | "install": "./frontend/src/entry/install.jsx", 8 | "auth": "./frontend/src/entry/auth.jsx" 9 | }, 10 | "scripts": { 11 | "dev": "node www/development.js", 12 | "frontdev": "atool-build -o www/static/app/ -w --devtool cheap-module-source-map --no-compress", 13 | "build": "atool-build -o www/static/app/ && babel --presets es2015-loose,stage-1 --plugins transform-runtime src/ --out-dir app/ --retain-lines", 14 | "start": "node www/production.js" 15 | }, 16 | "dependencies": { 17 | "node-etcd": "^5.0.2", 18 | "antd": "^1.6.3", 19 | "babel-runtime": "6.x.x", 20 | "child-process-es6-promise": "^1.0.0", 21 | "child-process-promise": "^2.0.2", 22 | "echarts-for-react": "^1.1.6", 23 | "jszip": "3.1.3", 24 | "moment": "^2.13.0", 25 | "nodemailer": "0.7.1", 26 | "nunjucks": "^2.2.0", 27 | "react": "0.14.x", 28 | "react-addons-update": "15.3.2", 29 | "react-dom": "0.14.x", 30 | "react-router": "^2.3.0", 31 | "request": "^2.72.0", 32 | "request-promise": "^4.1.1", 33 | "socket.io": "^1.3.7", 34 | "thinkjs": "2.1.x" 35 | }, 36 | "devDependencies": { 37 | "dora": "0.3.x", 38 | "dora-plugin-webpack": "0.5.x", 39 | "dora-plugin-hmr": "0.4.x", 40 | "dora-plugin-livereload": "0.3.x", 41 | "dora-plugin-proxy": "0.6.x", 42 | "eslint": "2.x", 43 | "eslint-config-airbnb": "6.x", 44 | "eslint-plugin-react": "4.x", 45 | "pre-commit": "1.x", 46 | "babel-plugin-antd": "0.2.x", 47 | "atool-build": "0.6.x", 48 | "babel-cli": "6.x.x", 49 | "babel-core": "^6.3.21", 50 | "babel-loader": "^6.2.0", 51 | "babel-plugin-react-transform": "^2.0.0-beta1", 52 | "babel-plugin-transform-runtime": "^6.3.13", 53 | "babel-preset-es2015": "^6.3.13", 54 | "babel-preset-es2015-loose": "6.x.x", 55 | "babel-preset-react": "^6.3.13", 56 | "babel-preset-stage-0": "^6.3.13", 57 | "babel-preset-stage-1": "6.x.x", 58 | "css-loader": "^0.23.1", 59 | "less": "^2.6.1", 60 | "less-loader": "^2.2.3", 61 | "postcss-loader": "^0.8.2", 62 | "react-transform-hmr": "^1.0.1", 63 | "style-loader": "^0.13.1", 64 | "webpack": "^1.8.4", 65 | "webpack-dev-server": "^1.14.1" 66 | }, 67 | "repository": "", 68 | "license": "MIT" 69 | } 70 | -------------------------------------------------------------------------------- /frontend/src/component/project_list.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import { 3 | Row, 4 | Col, 5 | Table, 6 | Form, 7 | Input, 8 | Button, 9 | Checkbox, 10 | Tabs, 11 | Badge, 12 | Select, 13 | Pagination 14 | } from 'antd'; 15 | import ProForm from './project_form'; 16 | import '../style/project.less' 17 | import '../style/project_list.less'; 18 | const FormItem = Form.Item; 19 | const TabPane = Tabs.TabPane; 20 | const Option = Select.Option; 21 | class ProjectList extends React.Component { 22 | constructor(props) { 23 | super(props); 24 | this.state = { 25 | isShowProForm: false, 26 | items: { 27 | currentPage: 1, 28 | numsPerPage: 32 29 | }, 30 | proItemsData: [] 31 | }; 32 | } 33 | handleClick(itemID) { 34 | window.location.href = '#project_detail?id=' + itemID; 35 | } 36 | 37 | getItemData(pageId, pageSize) { 38 | let self = this; 39 | $.ajax({ 40 | type: 'get', 41 | url: '/home/project/get_project_list', 42 | data: { 43 | pageId: pageId, 44 | pageSize: pageSize 45 | }, 46 | success: function(res) { 47 | self.setState({items: res.data, proItemsData: res.data.data}) 48 | } 49 | }) 50 | } 51 | 52 | componentDidMount() { 53 | this.getItemData(this.state.items.currentPage, this.state.items.numsPerPage); 54 | } 55 | 56 | onChange(page) { 57 | this.getItemData(page, 32); 58 | } 59 | showTotal(total) { 60 | return `共 ${total} 条`; 61 | } 62 | render() { 63 | var self = this; 64 | return ( 65 |
66 | 67 | {this.state.proItemsData.map(function(item) { 68 | let imgUrl = 'https://github.com/identicons/' + item.id + '.png'; 69 | return
70 |
71 |
72 | 73 | 74 |
75 |
76 |
{item.name}
77 |

Create By {item.creater}.

78 |
79 |
80 | 81 | }) 82 | } 83 | 84 | 85 | 86 | 87 | 88 | ); 89 | } 90 | }; 91 | export default ProjectList; 92 | -------------------------------------------------------------------------------- /src/home/controller/logserver.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import Base from './base.js'; 4 | import { 5 | spawn, 6 | fork 7 | } from 'child_process'; 8 | export default class extends Base { 9 | /** 10 | * index action 11 | * @return {Promise} [] 12 | */ 13 | async deployAction(self) { 14 | let socket = self.http.socket; 15 | let data = self.http.data; 16 | let logFile = await self.http.session('deployLogFile'); 17 | const sh = spawn('tail', ['-f', logFile]); 18 | let me = this; 19 | //在client主动关掉连接的时候,把进程干掉 20 | socket.on('disconnect', function() { 21 | console.log('client close socket==========='); 22 | // socket.disconnect(); 23 | sh.kill(); 24 | }); 25 | sh.stdout.on('data', (data) => { 26 | me.emit('logserver', '' + data); 27 | let endText = '' + data; 28 | if (data.indexOf('end!!!') >= 0) { 29 | console.log('close socket===========:' + data); 30 | socket.disconnect(); 31 | sh.kill(); 32 | } 33 | }); 34 | sh.stderr.on('data', (data) => { 35 | me.emit("logserver", '' + data); 36 | }); 37 | sh.on('close', (code) => { 38 | console.log('close child process exited with code ' + code); 39 | }); 40 | sh.on('exit', function(code) { 41 | console.log('exit child process exited with code ' + code); 42 | }); 43 | } 44 | 45 | async indexAction(self) { 46 | console.log('indexAction into '); 47 | let socket = self.http.socket; 48 | let data = self.http.data; 49 | let logFile = await self.http.session('logFile'); 50 | const sh = spawn('tail', ['-f', logFile]); 51 | let me = this; 52 | //在client主动关掉连接的时候,把进程干掉 53 | socket.on('disconnect', function() { 54 | console.log('client close socket==========='); 55 | //socket.disconnect(); 56 | sh.kill(); 57 | }); 58 | sh.stdout.on('data', (data) => { 59 | me.emit('logserver', '' + data); 60 | let endText = '' + data; 61 | if (data.indexOf('end!!!') >= 0) { 62 | console.log('close socket===========:' + data); 63 | socket.disconnect(); 64 | sh.kill(); 65 | } 66 | }); 67 | sh.stderr.on('data', (data) => { 68 | me.emit("logserver", '' + data); 69 | }); 70 | sh.on('close', (code) => { 71 | console.log('close child process exited with code ' + code); 72 | }); 73 | sh.on('exit', function(code) { 74 | console.log('exit child process exited with code ' + code); 75 | }); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /frontend/src/style/project_list.less: -------------------------------------------------------------------------------- 1 | .image{ 2 | height: 63px; 3 | display: block; 4 | position: relative; 5 | background-color: rgba(0,0,0,.05); 6 | -webkit-box-sizing: border-box; 7 | -moz-box-sizing: border-box; 8 | -ms-box-sizing: border-box; 9 | box-sizing: border-box; 10 | border-radius: .2em; 11 | } 12 | 13 | .item>.image>img { 14 | display: block; 15 | width: 63px; 16 | height: 63px; 17 | margin: 0 auto; 18 | } 19 | .item{ 20 | height: 63px; 21 | } 22 | 23 | .ui.corner.label { 24 | background-color: transparent; 25 | position: absolute; 26 | top: 0; 27 | right: 0; 28 | z-index: 10; 29 | margin: 0; 30 | width: 3em; 31 | height: 3em; 32 | padding: 0; 33 | text-align: center; 34 | -webkit-transition: color .2s ease; 35 | -moz-transition: color .2s ease; 36 | transition: color .2s ease; 37 | } 38 | .ui.label:last-child { 39 | margin-right: 0; 40 | } 41 | a.ui.label { 42 | cursor: pointer; 43 | } 44 | .ui.label { 45 | font-size: .8125rem; 46 | } 47 | .ui.label { 48 | display: inline-block; 49 | vertical-align: middle; 50 | margin: -.25em .25em 0; 51 | background-color: #E8E8E8; 52 | border-color: #E8E8E8; 53 | padding: .5em .8em; 54 | color: rgba(0,0,0,.65); 55 | text-transform: uppercase; 56 | font-weight: 400; 57 | border-radius: .325em; 58 | -webkit-box-sizing: border-box; 59 | -moz-box-sizing: border-box; 60 | -ms-box-sizing: border-box; 61 | box-sizing: border-box; 62 | -webkit-transition: background .1s linear; 63 | -moz-transition: background .1s linear; 64 | transition: background .1s linear; 65 | } 66 | 67 | .item { 68 | min-height: 140px; 69 | padding-bottom: 0; 70 | } 71 | .item:nth-child(4n+1) { 72 | clear: left; 73 | } 74 | .item { 75 | min-width: 140px; 76 | margin-left: .5%; 77 | margin-right: .5%; 78 | } 79 | .item, .item>.content{ 80 | margin-top: 10px; 81 | -webkit-box-sizing: border-box; 82 | -moz-box-sizing: border-box; 83 | -ms-box-sizing: border-box; 84 | box-sizing: border-box; 85 | } 86 | .item { 87 | display: block; 88 | float: left; 89 | position: relative; 90 | top: 0; 91 | margin: 0 .5em 2.5em; 92 | background-color: #FFF; 93 | line-height: 1.2; 94 | font-size: 1em; 95 | -webkit-box-shadow: 0 0 0 1px rgba(0,0,0,.1); 96 | box-shadow: 0 0 0 1px rgba(0,0,0,.1); 97 | border-bottom: .2em solid rgba(0,0,0,.2); 98 | border-radius: .33em; 99 | -webkit-transition: -webkit-box-shadow .2s ease; 100 | -moz-transition: box-shadow .2s ease; 101 | transition: box-shadow .2s ease; 102 | padding: .5em; 103 | } 104 | 105 | .item>.content>.name { 106 | text-align: center; 107 | font-size: 18px; 108 | } 109 | .item>.content>.name { 110 | display: block; 111 | font-size: 1.25em; 112 | font-weight: 700; 113 | margin-bottom: .2em; 114 | color: rgba(0,0,0,.7); 115 | } 116 | 117 | .item>.content>.description { 118 | clear: both; 119 | margin: 0; 120 | color: rgba(0,0,0,.45); 121 | } 122 | -------------------------------------------------------------------------------- /frontend/src/entry/index.jsx: -------------------------------------------------------------------------------- 1 | import '../common/lib'; 2 | import '../style/app.less'; 3 | import Header from '../component/header'; 4 | import Sidebar from '../component/sidebar'; 5 | import ProjectForm from '../component/project_form'; 6 | import ProjectList from '../component/project_list'; 7 | import MachineList from '../component/machine_list'; 8 | import ChargePersonList from '../component/charge_person_list'; 9 | import MachineForm from '../component/machine_form'; 10 | import ChargePersonForm from '../component/charge_person_form'; 11 | import BuildHistory from '../component/build_history'; 12 | import ProjectDetail from '../component/project_detail'; 13 | import BatchDeploy from '../component/batch_deploy'; 14 | import Stat from '../component/stat'; 15 | import ProjectStat from '../component/project_stat'; 16 | import UserStat from '../component/user_stat'; 17 | import EnvStat from '../component/env_stat'; 18 | import ChangePassForm from '../component/changePass_form'; 19 | import ReactDOM from 'react-dom'; 20 | import {Row, Col} from 'antd'; 21 | import {Router, Route, hashHistory} from 'react-router' 22 | import React, {Component} from 'react'; 23 | 24 | class MainLayout extends Component { 25 | constructor(props) { 26 | super(props); 27 | this.state = { 28 | headerTitle: '工程' 29 | } 30 | } 31 | componentWillReceiveProps(props) { 32 | this.changeHeaderTitle(props.routes[1].headerTitle); 33 | } 34 | changeHeaderTitle(title) { 35 | this.setState({headerTitle: title}); 36 | } 37 | render() { 38 | return ( 39 |
40 |
41 | 42 |
43 | 44 | 45 | 46 |
47 | {this.props.children} 48 |
49 | 50 | 51 | 52 | ); 53 | } 54 | } 55 | 56 | class App extends Component { 57 | render() { 58 | return ( 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | ); 78 | } 79 | } 80 | 81 | ReactDOM.render( 82 | , document.getElementById('root')); 83 | -------------------------------------------------------------------------------- /src/home/controller/auth.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import Base from './base.js'; 4 | import UserModel from '../model/user' 5 | export default class extends Base { 6 | /** 7 | * 登录验证 8 | * index action 9 | * @return {Promise} [] 10 | */ 11 | async indexAction() { 12 | if (this.http.isPost()) { 13 | let name = this.post('name'), 14 | pass = this.post('pass'); 15 | let userModel = new UserModel(); 16 | /** 17 | * promise 18 | */ 19 | // let pro = userModel.getUserById(name); 20 | // let me=this; 21 | // pro.then(function(data){ 22 | // return me.success(data.pass); 23 | // }) 24 | /** 25 | * Generator 26 | */ 27 | // let gen=userModel.getUserById(name) 28 | // let reData=gen.next(); 29 | // return this.success('登陆成功'+reData.value); 30 | /** 31 | * await 32 | */ 33 | 34 | /* 35 | * 先检查LDAP是否能登录成功;否则尝试连接本地数据库 36 | */ 37 | let canloginFromLDAP = false; //await userModel.loginFromLDAP(name, pass); 38 | 39 | if(canloginFromLDAP) { 40 | let info = { 41 | name: name, 42 | pass: pass 43 | }; 44 | this.http.session('user', info); 45 | return this.success(info); 46 | }else { 47 | let userInfo = await userModel.getUserByName(name); 48 | if(userInfo && userInfo.pass === pass) { 49 | this.http.session('user', userInfo); 50 | return this.success(userInfo); 51 | } else { 52 | return this.fail('登陆失败') 53 | } 54 | } 55 | 56 | } else { 57 | return this.display(); 58 | } 59 | } 60 | async updatePassAction() { 61 | if (this.http.isPost()) { 62 | let repass = this.post('name'), 63 | pass = this.post('pass'); 64 | if(repass != pass){ 65 | return this.fail('密码不一致'); 66 | } 67 | let userModel = new UserModel(); 68 | let affectedRows = await userModel.updateUser(name,pass); 69 | if (userInfo && userInfo.pass === pass) { 70 | this.http.session('user', userInfo); 71 | return this.success(userInfo); 72 | } else { 73 | return this.fail('登陆失败') 74 | } 75 | 76 | } else { 77 | return this.display(); 78 | } 79 | } 80 | async isuserexistAction() { 81 | if (this.http.isGet()) { 82 | let name = this.get('name'); 83 | let data = await this.model('user').where({ 84 | name: name 85 | }).find(); 86 | if (data) { 87 | return this.success({ 88 | isExist: true 89 | }); 90 | } else { 91 | return this.success({ 92 | isExist: false 93 | }) 94 | } 95 | } 96 | } 97 | 98 | // testAction(){ 99 | // return this.success(); 100 | // } 101 | } 102 | -------------------------------------------------------------------------------- /frontend/src/component/charge_person_list.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import { 3 | Button, 4 | Table, 5 | Modal, 6 | Row, 7 | Col, 8 | Input 9 | } from 'antd'; 10 | 11 | class ChargePersonList extends Component { 12 | 13 | constructor(props) { 14 | super(props); 15 | this.state = { 16 | projectId: this.props.location.query.projectId, 17 | projectName: this.props.location.query.projectName, 18 | data: [], 19 | pagination: {}, 20 | loading: true, 21 | showDeletModal: false 22 | } 23 | } 24 | handleTableChange(pagination, filters, sorter) { 25 | const pager = this.state.pagination; 26 | pager.current = pagination.current; 27 | this.setState({pagination: pager}); 28 | } 29 | addPerson() { 30 | window.location.href = '#/chargePerson_form?projectId=' + this.state.projectId; 31 | } 32 | handleChargePersonAction(e) { 33 | var self = this; 34 | const action = e.currentTarget.dataset.action; 35 | const id = e.currentTarget.dataset.id; 36 | if (action == 'edit') { 37 | window.location.href = '#/chargePerson_form?action=edit&id=' + id; 38 | } else if (action == 'delete') { 39 | self.setState({showDeletModal: true, curDeleteId: id}) 40 | } 41 | } 42 | handleChargePersonDelete() { 43 | var self = this; 44 | if (self.state.curDeleteId) { 45 | $.ajax({ 46 | url: '/home/cperson/delete', 47 | type: 'POST', 48 | data: { 49 | id: self.state.curDeleteId 50 | }, 51 | success: function(res) { 52 | if (!res.errno) { 53 | self.setState({showDeletModal: false, curDeleteId: null}) 54 | window.location.reload(); 55 | } 56 | } 57 | }) 58 | } 59 | } 60 | closeModal() { 61 | this.setState({showDeletModal: false}) 62 | } 63 | fetch() { 64 | const self = this; 65 | $.ajax({ 66 | url: '/home/cperson/project/id/' + self.state.projectId, 67 | type: 'GET', 68 | success: function(res) { 69 | if (res.errno == 0) { 70 | self.setState({data: res.data, loading: false}) 71 | } 72 | } 73 | }) 74 | } 75 | componentDidMount() { 76 | this.fetch(); 77 | } 78 | render() { 79 | const self = this; 80 | const columns = [ 81 | { 82 | title: '负责人', 83 | dataIndex: 'name' 84 | }, { 85 | title: '负责人邮箱', 86 | dataIndex: 'email' 87 | }, { 88 | title: '添加时间', 89 | dataIndex: 'add_time' 90 | }, { 91 | title: '操作', 92 | dataIndex: 'id', 93 | render(text, record) { 94 | return ( 95 | 96 | 编辑 97 | 98 | 删除 99 | 100 | ); 101 | } 102 | } 103 | ]; 104 | return ( 105 |
106 |
107 | 108 | 109 | 110 |
111 |
项目:{this.state.projectName}
112 |
113 | 114 |

确认要删除该负责人?

115 |
116 |
117 | ); 118 | } 119 | } 120 | 121 | export default ChargePersonList; 122 | -------------------------------------------------------------------------------- /src/common/service/impl/diffdir.js: -------------------------------------------------------------------------------- 1 | // var mod_co = require('./checkout.js'); 2 | import fs from 'fs' ; 3 | import diffUtil from './diff'; 4 | import sysPath from 'path' ; 5 | import crypto from 'crypto' ; 6 | 7 | export default class diffdir { 8 | getMd5(s) { 9 | var md5sum = crypto.createHash('md5'); 10 | md5sum.update(s, 'utf8'); 11 | return md5sum.digest('hex'); 12 | } 13 | getAllFiles(pro, result, exp) { 14 | var files = {}; 15 | files.same = []; 16 | files.change = []; 17 | files.add = []; 18 | //var local = utils.readJsonConf(result.newDir + '/' + pro.build_conf); 19 | var k; 20 | let self = this; 21 | function handleFile(obj) { 22 | var files_obj = { 23 | path_n: obj.n, 24 | path_o: obj.o 25 | }; 26 | if (obj.o && fs.existsSync(obj.o)) { 27 | var md51 = self.getMd5(fs.readFileSync(obj.n)); 28 | var md52 = self.getMd5(fs.readFileSync(obj.o)); 29 | if (md51 === md52) { 30 | files_obj.type = 'same'; 31 | files.same.push(files_obj); 32 | } else { 33 | files_obj.type = 'change'; 34 | files_obj.diff = self.getDiffData(obj); 35 | files.change.push(files_obj); 36 | } 37 | } else if (obj.o && !fs.existsSync(obj.o)) { 38 | files_obj.type = 'del'; 39 | files.del.push(files_obj); 40 | } else { 41 | 42 | files_obj.type = 'add'; 43 | files.add.push(files_obj); 44 | } 45 | 46 | 47 | } 48 | 49 | //某个目录是否不是在例外里 50 | function isExp(dir, exp) { 51 | for (let i = 0; i < exp.length; i++) { 52 | if (dir == exp[i]) { 53 | return true; 54 | } 55 | } 56 | return false; 57 | } 58 | //比较新旧2个文件夹的文件修改情况 59 | function walk(path, exp) { 60 | let pathArr = path.split(sysPath.sep); 61 | let lastDir = pathArr[pathArr.length - 1]; 62 | // console.log('lastDir:'+lastDir+' '+exp); 63 | if (lastDir == '') { 64 | lastDir = pathArr[pathArr.length - 2]; 65 | } 66 | //如果例外 67 | if (isExp(lastDir, exp)) { 68 | // console.log('lastDir+-----------:'+lastDir); 69 | } else { 70 | var dirList = fs.readdirSync(path); 71 | dirList.forEach(function(item) { 72 | if (fs.existsSync(path + '/' + item) && fs.statSync(path + '/' + item).isFile()) { 73 | var newFile = path + '/' + item; 74 | var oldFile = newFile.replace(result.newDir, result.oldDir); 75 | var oldFileExist = fs.existsSync(oldFile); 76 | handleFile({ 77 | n: newFile, 78 | o: oldFileExist ? oldFile : false 79 | }) 80 | } 81 | }); 82 | 83 | dirList.forEach(function(item) { 84 | if (fs.existsSync(path + '/' + item) && fs.statSync(path + '/' + item).isDirectory()) { 85 | walk(path + '/' + item, exp); 86 | } 87 | }); 88 | } 89 | 90 | } 91 | 92 | 93 | if (fs.existsSync(result.newDir)) { 94 | walk(result.newDir, exp); 95 | } 96 | return files; 97 | } 98 | getDiffData(obj) { 99 | var text1 = fs.readFileSync(obj.n, 'utf8'); 100 | var text2 = fs.readFileSync(obj.o, 'utf8'); 101 | var dmp = new diffUtil.diff_match_patch(); 102 | var d = dmp.diff_main(text2, text1); 103 | return dmp.diff_prettyHtml(d); 104 | } 105 | diff(newDir, oldDir, exp) { 106 | var result = {}; 107 | result.newDir = newDir; 108 | result.oldDir = oldDir; 109 | return this.getAllFiles({}, result, exp); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/common/service/fileutil.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | import fs from 'fs'; 3 | import path from 'path'; 4 | import childProcessPro from 'child-process-es6-promise'; 5 | /** 6 | * 删除文件 7 | */ 8 | export default class { 9 | /** 10 | 获取checkout目录 11 | */ 12 | getCvsDir(username, id) { 13 | return './temp/' + username + '/' + id; 14 | } 15 | /** 16 | 获取上一个tag dir 17 | */ 18 | getLastTagDir(username, id) { 19 | return './temp/' + username + '/' + id + '_old'; 20 | } 21 | /* 22 | 获取增量更新是的构建目录 23 | */ 24 | getIncBuildDir(username, id) { 25 | return './temp/' + username + '/' + id + '_inc'; 26 | } 27 | //copy文件,单文件复制,如果已经有文件则覆盖 28 | copyOneFile(src, dest) { 29 | //如果没有这个源文件,则返回 30 | if (!fs.existsSync(src)) { 31 | return; 32 | } 33 | //如果没有文件夹,则建立文件夹 34 | let splitIndex = dest.lastIndexOf('/'); 35 | let destDir = dest.substring(0, splitIndex); 36 | if (!fs.existsSync(destDir)) { 37 | this.mkDirSync(destDir); 38 | } 39 | //如果已经有这个文件,则删除 40 | let exists = fs.existsSync(dest); 41 | if (exists) { 42 | fs.unlinkSync(dest); 43 | } 44 | fs.linkSync(src, dest); 45 | } 46 | //copy目录 47 | async copyRecursiveSync(src, dest) { 48 | let excStr = await childProcessPro.exec('cp -rf ' + src + '/* ' + dest); 49 | } 50 | //逐级生成目录 51 | mkDirSync(dirpath) { 52 | if (!fs.existsSync(dirpath)) { 53 | let pathtmp; 54 | dirpath.split(path.sep).forEach(function(dirname) { 55 | if (dirname == '') { 56 | return; 57 | } 58 | if (pathtmp) { 59 | pathtmp = path.join(pathtmp, dirname); 60 | } else { 61 | pathtmp = path.sep + dirname; 62 | } 63 | if (!fs.existsSync(pathtmp)) { 64 | if (!fs.mkdirSync(pathtmp)) { 65 | return false; 66 | } 67 | } 68 | }); 69 | } 70 | return true; 71 | } 72 | //删除文件夹promise 73 | deleteDir(dir, exp) { 74 | let self = this; 75 | return new Promise(function(resolve, reject) { 76 | var dirs = []; 77 | try { 78 | self.iterator(dir, dirs, exp); 79 | for (var i = 0, el; el = dirs[i++];) { 80 | fs.rmdirSync(el); //一次性删除所有收集到的目录 81 | } 82 | resolve({ 83 | 'status': 1 84 | }); 85 | return; 86 | } catch (e) { //如果文件或目录本来就不存在,fs.statSync会报错,不过我们还是当成没有异常发生 87 | let status = e.code === "ENOENT" ? 1 : 0; 88 | resolve({ 89 | 'status': 1, 90 | 'msg': e 91 | }); 92 | return; 93 | } 94 | }); 95 | } 96 | iterator(url, dirs, exp) { 97 | var stat = fs.statSync(url); 98 | if (stat.isDirectory()) { 99 | let pathArr = url.split(path.sep); 100 | let lastDir = pathArr[pathArr.length - 1]; 101 | if (lastDir == '') { 102 | lastDir = pathArr[pathArr.length - 2]; 103 | } 104 | if (lastDir == exp) { 105 | console.log('lastDir:' + lastDir); 106 | } else { 107 | dirs.unshift(url); //收集目录 108 | this.inner(url, dirs, exp); 109 | } 110 | 111 | } else if (stat.isFile()) { 112 | fs.unlinkSync(url); //直接删除文件 113 | } 114 | } 115 | 116 | inner(path, dirs, exp) { 117 | var arr = fs.readdirSync(path); 118 | for (var i = 0, el; el = arr[i++];) { 119 | this.iterator(path + "/" + el, dirs, exp); 120 | } 121 | } 122 | 123 | } 124 | -------------------------------------------------------------------------------- /src/common/service/deploy.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | import childProcessPro from 'child-process-es6-promise'; 3 | import fileUtil from './fileutil'; 4 | import fs from 'fs' 5 | export default class extends think.service.base { 6 | /** 7 | * init 8 | * @return {} [] 9 | */ 10 | init(...args) { 11 | super.init(...args); 12 | } 13 | async opDeploy(sourceDirList, deployInfo, deployUser, deployPass, deployLogFile, isInc, incExc, opInfo) { 14 | let fileU = new fileUtil(); 15 | let buildDir = fileU.getCvsDir(deployUser, deployInfo.pro_id); 16 | //如果是增量更新,切换目录 17 | if (isInc && incExc == 1) { 18 | buildDir = fileU.getLastTagDir(deployUser, deployInfo.pro_id); 19 | } 20 | buildDir = buildDir.substring(2); 21 | let sendOpService = think.service('sendop'); 22 | let sendOpS = new sendOpService(); 23 | let options = { 24 | encoding: 'utf8', 25 | mode: 438, 26 | flag: 'a' 27 | }; 28 | // fs.writeFileSync(deployLogFile, JSON.parse(opInfo), options); 29 | if (sourceDirList.length == 1) { 30 | opInfo.itemZip += buildDir + (sourceDirList[0][0] == '/' ? '' : '/') + sourceDirList[0]; 31 | } else if (sourceDirList.length == 2) { 32 | opInfo.itemZip += buildDir + (sourceDirList[0][0] == '/' ? '' : '/') + sourceDirList[0]; 33 | opInfo.staticZip += buildDir + (sourceDirList[1][0] == '/' ? '' : '/') + sourceDirList[1]; 34 | } 35 | 36 | // return {"data": {"info": "task add successful", "taskId": "22421"}, "statusCode": 200}; 37 | let opResult = await sendOpS.sendOp(opInfo); 38 | return opResult; 39 | } 40 | // async deploy(deployInfo, deployUser, deployPass,deployLogFile) { 41 | async deploy(sourceDir, targetDir, deployInfo, deployUser, deployPass, deployLogFile, isInc, incExc) { 42 | let fileU = new fileUtil(); 43 | let buildDir = fileU.getCvsDir(deployUser, deployInfo.pro_id); 44 | //如果是增量更新,切换目录 45 | if (isInc && incExc == 1) { 46 | buildDir = fileU.getLastTagDir(deployUser, deployInfo.pro_id); 47 | } 48 | let shUser, shPass; 49 | if (deployInfo.ssh_user == '${user}') { 50 | //部署线上环境,使用session 用户名 51 | shUser = deployUser; 52 | shPass = deployPass; 53 | } else { 54 | shUser = deployInfo.ssh_user; 55 | shPass = deployInfo.ssh_pass; 56 | } 57 | let shPort = 22; 58 | //路径自动添加反斜杠 59 | let sourceDirFinal = buildDir + (sourceDir[0] == '/' ? '' : '/') + sourceDir; 60 | console.log('./src/common/service/impl/deploy.sh', shUser, shPass, sourceDirFinal, deployInfo.ip, shPort, targetDir); 61 | let cmdParam = [shUser, shPass, sourceDirFinal, deployInfo.ip, shPort, targetDir].join(' '); 62 | let cmd = './src/common/service/impl/deploy.sh ' + cmdParam; 63 | //改成同步执行 64 | let changeLog = await childProcessPro.exec(cmd, { 65 | maxBuffer: 100 * 1024 * 1024 66 | }); 67 | let options = { 68 | encoding: 'utf8', 69 | mode: 438, 70 | flag: 'a' 71 | }; 72 | fs.writeFileSync(deployLogFile, changeLog.stdout, options); 73 | 74 | return sourceDirFinal; 75 | //let pro = childProcessPro.spawn('./src/common/service/impl/deploy.sh', [shUser, shPass, sourceDirFinal, deployInfo.ip, shPort, targetDir]); 76 | 77 | // var proChild = pro.child; 78 | // proChild.stdout.on('data', (data) => { 79 | // console.log(`stdout: ${data}`); 80 | // let options = { encoding: 'utf8', mode: 438, flag: 'a' }; 81 | // fs.writeFileSync(deployLogFile, data, options); 82 | // }); 83 | // proChild.stderr.on('data', (data) => { 84 | // console.log(`stderr: ${data}`); 85 | // let options = { encoding: 'utf8', mode: 438, flag: 'a' }; 86 | // fs.writeFileSync(deployLogFile, data, options); 87 | // }); 88 | // 89 | // proChild.on('close', (code) => {}); 90 | //return pro; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /frontend/src/component/changePass_form.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import { 3 | Button, 4 | Form, 5 | Input, 6 | Row, 7 | Col, 8 | Modal 9 | } from 'antd'; 10 | const createForm = Form.create; 11 | const FormItem = Form.Item; 12 | const confirm = Modal.confirm; 13 | function noop() { 14 | return false; 15 | } 16 | 17 | class changePassForm extends React.Component { 18 | constructor(props) { 19 | super(props); 20 | } 21 | getValidateStatus(field) { 22 | const {isFieldValidating, getFieldError, getFieldValue} = this.props.form; 23 | 24 | if (isFieldValidating(field)) { 25 | return 'validating'; 26 | } else if (!!getFieldError(field)) { 27 | return 'error'; 28 | } else if (getFieldValue(field)) { 29 | return 'success'; 30 | } 31 | } 32 | 33 | handleReset(event) { 34 | event.preventDefault(); 35 | this.props.form.resetFields(); 36 | } 37 | /** 38 | * 显示提示Modal 39 | */ 40 | showConfirm(title, content) { 41 | confirm({title: title, content: content, onOk() {}, onCancel() {}}); 42 | } 43 | handleSubmit(event) { 44 | console.log(event); 45 | event.preventDefault(); 46 | let me = this; 47 | this.props.form.validateFields((errors, values) => { 48 | if (!!errors) { 49 | return; 50 | } 51 | $.ajax({ 52 | type: 'PUT', 53 | url: '/home/user/update_pass', 54 | data: { 55 | name: values.name, 56 | pass: values.passwd 57 | }, 58 | success: res => { 59 | if (res.errno == 0) { 60 | me.showConfirm('修改密码成功', '修改密码成功!!') 61 | //window.location.href = './'; 62 | } else { 63 | me.showConfirm('修改密码失败', res.errmsg); 64 | } 65 | } 66 | }) 67 | }); 68 | } 69 | 70 | showModal(e) { 71 | this.props.regFormHandler(true); 72 | } 73 | 74 | render() { 75 | const {getFieldProps, getFieldError, isFieldValidating} = this.props.form; 76 | const nameProps = getFieldProps('name', { 77 | rules: [ 78 | { 79 | required: true, 80 | message: '请填写用户名' 81 | } 82 | ] 83 | }); 84 | const passwdProps = getFieldProps('passwd', { 85 | rules: [ 86 | { 87 | required: true, 88 | whitespace: true, 89 | message: '请填写密码' 90 | } 91 | ] 92 | }); 93 | 94 | const formItemLayout = { 95 | labelCol: { 96 | span: 7 97 | }, 98 | wrapperCol: { 99 | span: 12 100 | } 101 | }; 102 | const resetLayout = { 103 | labelCol: { 104 | span: 18 105 | }, 106 | wrapperCol: { 107 | span: 1 108 | } 109 | }; 110 | 111 | const regLayout = { 112 | labelCol: { 113 | span: 14 114 | }, 115 | wrapperCol: { 116 | span: 5 117 | } 118 | }; 119 | return ( 120 |
121 |
122 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 重置 136 | 137 | 138 | 142 | 145 |     146 | 147 | 148 |
149 |
150 | ); 151 | } 152 | } 153 | 154 | changePassForm = createForm()(changePassForm); 155 | export default changePassForm; 156 | //ReactDOM.render(, mountNode); 157 | -------------------------------------------------------------------------------- /nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | server_name example.com www.example.com; 4 | root /Users/shaokaiming/Playground//Users/shaokaiming/Playground/wdfe-build2/www; 5 | set $node_port 8360; 6 | 7 | index index.js index.html index.htm; 8 | if ( -f $request_filename/index.html ){ 9 | rewrite (.*) $1/index.html break; 10 | } 11 | if ( !-f $request_filename ){ 12 | rewrite (.*) /index.js; 13 | } 14 | location = /index.js { 15 | proxy_http_version 1.1; 16 | proxy_set_header X-Real-IP $remote_addr; 17 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 18 | proxy_set_header Host $http_host; 19 | proxy_set_header X-NginX-Proxy true; 20 | proxy_set_header Upgrade $http_upgrade; 21 | proxy_set_header Connection "upgrade"; 22 | proxy_pass http://127.0.0.1:$node_port$request_uri; 23 | proxy_redirect off; 24 | } 25 | 26 | location = /development.js { 27 | deny all; 28 | } 29 | 30 | location = /testing.js { 31 | deny all; 32 | } 33 | 34 | location = /production.js { 35 | deny all; 36 | } 37 | 38 | location ~ /static/ { 39 | etag on; 40 | expires max; 41 | } 42 | } 43 | 44 | 45 | 46 | 47 | ## http/2 nginx conf 48 | 49 | # server { 50 | # listen 80; 51 | # server_name example.com www.example.com; 52 | # rewrite ^(.*) https://example.com$1 permanent; 53 | # } 54 | # 55 | # server { 56 | # listen 443 ssl http2 fastopen=3 reuseport; 57 | # server_name www.thinkjs.org thinkjs.org; 58 | # set $node_port 8360; 59 | # 60 | # root /Users/shaokaiming/Playground//Users/shaokaiming/Playground/wdfe-build2/www; 61 | # 62 | # keepalive_timeout 70; 63 | # 64 | # ssl_certificate /path/to/certificate; 65 | # ssl_certificate_key /path/to/certificate.key; 66 | # ssl_protocols TLSv1 TLSv1.1 TLSv1.2; 67 | # ssl_ciphers "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA"; 68 | # ssl_prefer_server_ciphers on; 69 | 70 | # # openssl dhparam -out dhparams.pem 2048 71 | # ssl_dhparam /path/to/dhparams.pem; 72 | # 73 | # ssl_session_cache shared:SSL:10m; 74 | # ssl_session_timeout 10m; 75 | # 76 | # ssl_session_ticket_key /path/to/tls_session_ticket.key; 77 | # ssl_session_tickets on; 78 | # 79 | # ssl_stapling on; 80 | # ssl_stapling_verify on; 81 | # ssl_trusted_certificate /path/to/startssl_trust_chain.crt; 82 | # 83 | # 84 | # add_header x-Content-Type-Options nosniff; 85 | # add_header X-Frame-Options deny; 86 | # add_header Strict-Transport-Security "max-age=16070400"; 87 | # 88 | # index index.js index.html index.htm; 89 | # if ( -f $request_filename/index.html ){ 90 | # rewrite (.*) $1/index.html break; 91 | # } 92 | # if ( !-f $request_filename ){ 93 | # rewrite (.*) /index.js; 94 | # } 95 | # location = /index.js { 96 | # proxy_http_version 1.1; 97 | # proxy_set_header X-Real-IP $remote_addr; 98 | # proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 99 | # proxy_set_header Host $http_host; 100 | # proxy_set_header X-NginX-Proxy true; 101 | # proxy_set_header Upgrade $http_upgrade; 102 | # proxy_set_header Connection "upgrade"; 103 | # proxy_pass http://127.0.0.1:$node_port$request_uri; 104 | # proxy_redirect off; 105 | # } 106 | # 107 | # location = /production.js { 108 | # deny all; 109 | # } 110 | # 111 | # location = /testing.js { 112 | # deny all; 113 | # } 114 | # 115 | # location ~ /static/ { 116 | # etag on; 117 | # expires max; 118 | # } 119 | #} 120 | 121 | -------------------------------------------------------------------------------- /src/common/service/build.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | import childProcessPro from 'child-process-es6-promise'; 3 | import fs from 'fs'; 4 | import fileUtil from './fileutil'; 5 | export default class extends think.service.base { 6 | /** 7 | * init 8 | * @return {} [] 9 | */ 10 | init() {} 11 | //设置最终构建目录,如果是增量部署,则新建一个目录用来构建,将上一次tag文件和本次选择构建文件增加进来,形成新目录 12 | async initBuildDir(buildInfo, deployFiles, username, homeDir, incExc) { 13 | let fileU = new fileUtil(); 14 | let chunkDir = fileU.getCvsDir(username, buildInfo.pro_id); 15 | let lastTagDir = fileU.getLastTagDir(username, buildInfo.pro_id); 16 | //如果是除以下文件以外的文件都部署,那么构建目录还是在chunkDir,否则构建目录在lastTagDir 17 | if (incExc == 2) { 18 | lastTagDir = fileU.getCvsDir(username, buildInfo.pro_id); 19 | chunkDir = fileU.getLastTagDir(username, buildInfo.pro_id); 20 | } 21 | //如果有选择文件则把新文件copy到构建目录 22 | if (deployFiles && deployFiles.length > 0) { 23 | for (let i = 0; i < deployFiles.length; i++) { 24 | let fileObj = deployFiles[i]; 25 | let srcFile = homeDir + chunkDir.substring(2) + fileObj.path_n; 26 | let destFile = homeDir + lastTagDir.substring(2) + fileObj.path_n; 27 | fileU.copyOneFile(srcFile, destFile); 28 | } 29 | } 30 | 31 | } 32 | async build(buildInfo, logFile, username, isNpmInstall, isInc, incExc, sync) { 33 | let self = this; 34 | let fileU = new fileUtil(); 35 | let buildDir = fileU.getCvsDir(username, buildInfo.pro_id); 36 | //如果是增量文件,则在tag目录构建 37 | if (isInc && incExc == 1) { 38 | buildDir = fileU.getLastTagDir(username, buildInfo.pro_id); 39 | } 40 | let packageFileDir = buildDir + '/package.json'; 41 | let task = buildInfo.task; 42 | console.log('task:', buildInfo); 43 | let code_lang = buildInfo.code_lang; 44 | let build_shell = './src/common/service/impl/build_' + code_lang + '.sh'; 45 | console.log(build_shell, buildDir, task, buildInfo.pro_id, isNpmInstall); 46 | ///执行build_hook before 47 | buildInfo.shellParams={ 48 | build_shell: build_shell, 49 | buildDir: buildDir, 50 | task:task, 51 | isNpmInstall:isNpmInstall 52 | } 53 | let hook= buildInfo.build_hook; 54 | let hookArray = []; 55 | if(hook&&hook.length>0){ 56 | let hookStrArray = hook.split(';'); 57 | for(let i=0;i0;i++ ){ 58 | let hookInstance = think.service(hookStrArray[i]); 59 | //let hookInstance = new hookService(); 60 | hookArray.push(hookInstance); 61 | } 62 | } 63 | 64 | for(let i=0 ;i { 89 | fs.writeFileSync(logFile, data, options); 90 | }); 91 | childProcess.stderr.on('data', (data) => { 92 | fs.writeFileSync(logFile, data, options); 93 | }); 94 | childProcess.on('close', (code) => { 95 | fs.writeFileSync(logFile, buildInfo.pro_name + ' build end!!!\n', options); 96 | }); 97 | } 98 | 99 | buildInfo.buildDir = buildDir; 100 | for(let i=0 ;i { 46 | if (!!errors) { 47 | return; 48 | } else { 49 | const query = this.props.location.query; 50 | let url = '/home/cperson'; 51 | let successMsg = '添加负责人成功'; 52 | let data = this.props.form.getFieldsValue(); 53 | if (query.action == 'edit') { 54 | url = '/home/cperson/update'; 55 | data.id = query.id; 56 | successMsg = '更新负责人成功'; 57 | } 58 | $.ajax({ 59 | type: 'POST', 60 | url: url, 61 | data: data, 62 | success: function(res) { 63 | if (res.errno == 0) { 64 | // alert(successMsg); 65 | ReactDOM.render( 66 | , document.getElementById('result')); 67 | } else { 68 | // alert(res.errmsg); 69 | ReactDOM.render( 70 | , document.getElementById('result')); 71 | } 72 | } 73 | }); 74 | } 75 | }) 76 | } 77 | getValidateStatus(field) { 78 | const {isFieldValidating, getFieldError, getFieldValue} = this.props.form; 79 | 80 | if (isFieldValidating(field)) { 81 | return 'validating'; 82 | } else if (!!getFieldError(field)) { 83 | return 'error'; 84 | } else if (getFieldValue(field)) { 85 | return 'success'; 86 | } 87 | } 88 | render() { 89 | const {getFieldProps, getFieldError, isFieldValidating} = this.props.form; 90 | const nameProps = getFieldProps('name', { 91 | rules: [ 92 | { 93 | required: true, 94 | message: '请输入负责人' 95 | } 96 | ] 97 | }); 98 | const emailProps = getFieldProps('email', { 99 | validate: [ 100 | { 101 | rules: [ 102 | { 103 | required: true 104 | } 105 | ], 106 | trigger: 'onBlur' 107 | }, { 108 | rules: [ 109 | { 110 | type: 'email', 111 | message: '请输入正确的邮箱地址' 112 | } 113 | ], 114 | trigger: ['onBlur', 'onChange'] 115 | } 116 | ] 117 | }); 118 | return ( 119 | 120 |
121 |
122 |
123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 |
138 |
139 | 140 | ); 141 | } 142 | } 143 | 144 | MachineForm = Form.create({})(MachineForm); 145 | 146 | export default MachineForm; 147 | -------------------------------------------------------------------------------- /frontend/src/component/user_stat.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import { 3 | Button, 4 | Table, 5 | Modal, 6 | Row, 7 | Col, 8 | Form, 9 | DatePicker, 10 | TimePicker, 11 | Input 12 | } from 'antd'; 13 | import ReactEcharts from 'echarts-for-react'; 14 | const FormItem = Form.Item; 15 | const RangePicker = DatePicker.RangePicker; 16 | class UserStat extends React.Component { 17 | constructor(props) { 18 | super(props); 19 | this.state = { 20 | projectName: '', 21 | totalOption: this.getTotalOption(), 22 | envOption: this.getTotalOption() 23 | } 24 | } 25 | getTotalOption() { 26 | let option = { 27 | title: { 28 | text: '' 29 | }, 30 | color: ['#3398DB'], 31 | tooltip: { 32 | trigger: 'axis', 33 | axisPointer: { // 坐标轴指示器,坐标轴触发有效 34 | type: 'shadow' // 默认为直线,可选为:'line' | 'shadow' 35 | } 36 | }, 37 | grid: { 38 | left: '3%', 39 | right: '4%', 40 | bottom: '3%', 41 | containLabel: true 42 | }, 43 | xAxis: [ 44 | { 45 | type: 'category', 46 | data: [], 47 | axisTick: { 48 | alignWithLabel: true 49 | } 50 | } 51 | ], 52 | yAxis: [ 53 | { 54 | type: 'value' 55 | } 56 | ], 57 | series: [ 58 | { 59 | name: '部署次数', 60 | type: 'bar', 61 | barWidth: '60%', 62 | data: [] 63 | } 64 | ] 65 | }; 66 | return option; 67 | } 68 | dataAjax(url, title, stateOption) { 69 | const self = this; 70 | $.ajax({ 71 | type: 'get', 72 | url: url, 73 | success: function(res) { 74 | let option = self.getTotalOption(); 75 | option.title.text = title; 76 | if (res.errno == 0) { 77 | for (let i = 0; i < res.data.length; i++) { 78 | let data = res.data[i]; 79 | if (stateOption == 'envOption') { 80 | if (data.type == 1) { 81 | option.xAxis[0].data.push('正式'); 82 | } else if (data.type == 2) { 83 | option.xAxis[0].data.push('预发布'); 84 | } else if (data.type == 3) { 85 | option.xAxis[0].data.push('测试'); 86 | } 87 | } else { 88 | option.xAxis[0].data.push(data.name); 89 | } 90 | option.series[0].data.push(data.pub); 91 | } 92 | let stateData = {}; 93 | stateData[stateOption] = option; 94 | self.setState(stateData); 95 | } 96 | } 97 | }) 98 | } 99 | fetch(startTime,endTime) { 100 | if(!startTime){ 101 | startTime=endTime=''; 102 | } 103 | //用户部署统计 104 | let user=this.props.location.query.id; 105 | this.setState({ 106 | projectName: user 107 | }) 108 | //总统计 109 | this.dataAjax('/home/history/get_total_stat?startTime='+startTime+'&endTime='+endTime+'&user='+user, '部署系统项目部署统计', 'totalOption'); 110 | //环境统计 111 | this.dataAjax('/home/history/get_env_stat?startTime='+startTime+'&endTime='+endTime+'&user='+user, '部署系统环境部署统计', 'envOption'); 112 | } 113 | componentDidMount() { 114 | this.fetch(); 115 | 116 | } 117 | totalDateChange(dates, dateString) { 118 | this.fetch(dateString[0],dateString[1]); 119 | } 120 | 121 | render() { 122 | const formItemLayout = { 123 | labelCol: { 124 | span: 8 125 | }, 126 | wrapperCol: { 127 | span: 16 128 | } 129 | }; 130 | return ( 131 |
132 | 133 |
{this.state.projectName}的部署统计:
134 |
135 |
136 | 137 | 138 | 139 |
140 |
141 | 145 |
146 |
147 | 151 |
152 |
153 | ); 154 | } 155 | } 156 | 157 | export default Form.create({})(UserStat); 158 | -------------------------------------------------------------------------------- /frontend/src/component/project_stat.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import { 3 | Button, 4 | Table, 5 | Modal, 6 | Row, 7 | Col, 8 | Form, 9 | DatePicker, 10 | TimePicker, 11 | Input 12 | } from 'antd'; 13 | import ReactEcharts from 'echarts-for-react'; 14 | const FormItem = Form.Item; 15 | const RangePicker = DatePicker.RangePicker; 16 | class Stat extends React.Component { 17 | constructor(props) { 18 | super(props); 19 | this.state = { 20 | projectName: '', 21 | userOption: this.getTotalOption(), 22 | envOption: this.getTotalOption() 23 | } 24 | } 25 | getTotalOption() { 26 | let option = { 27 | title: { 28 | text: '' 29 | }, 30 | color: ['#3398DB'], 31 | tooltip: { 32 | trigger: 'axis', 33 | axisPointer: { // 坐标轴指示器,坐标轴触发有效 34 | type: 'shadow' // 默认为直线,可选为:'line' | 'shadow' 35 | } 36 | }, 37 | grid: { 38 | left: '3%', 39 | right: '4%', 40 | bottom: '3%', 41 | containLabel: true 42 | }, 43 | xAxis: [ 44 | { 45 | type: 'category', 46 | data: [], 47 | axisTick: { 48 | alignWithLabel: true 49 | } 50 | } 51 | ], 52 | yAxis: [ 53 | { 54 | type: 'value' 55 | } 56 | ], 57 | series: [ 58 | { 59 | name: '部署次数', 60 | type: 'bar', 61 | barWidth: '60%', 62 | data: [] 63 | } 64 | ] 65 | }; 66 | return option; 67 | } 68 | dataAjax(url, title, stateOption) { 69 | const self = this; 70 | $.ajax({ 71 | type: 'get', 72 | url: url, 73 | success: function(res) { 74 | let option = self.getTotalOption(); 75 | option.title.text = title; 76 | if (res.errno == 0) { 77 | for (let i = 0; i < res.data.length; i++) { 78 | let data = res.data[i]; 79 | if (stateOption == 'envOption') { 80 | if (data.type == 1) { 81 | option.xAxis[0].data.push('正式'); 82 | } else if (data.type == 2) { 83 | option.xAxis[0].data.push('预发布'); 84 | } else if (data.type == 3) { 85 | option.xAxis[0].data.push('测试'); 86 | } 87 | } else { 88 | option.xAxis[0].data.push(data.name); 89 | } 90 | option.series[0].data.push(data.pub); 91 | } 92 | let stateData = {}; 93 | stateData[stateOption] = option; 94 | self.setState(stateData); 95 | } 96 | } 97 | }) 98 | } 99 | fetch(startTime,endTime) { 100 | if(!startTime){ 101 | startTime=endTime=''; 102 | } 103 | //用户部署统计 104 | let project_id=this.props.location.query.id; 105 | this.setState({ 106 | projectName: this.props.location.query.name 107 | }) 108 | this.dataAjax('/home/history/get_user_stat?id='+project_id+'&startTime='+startTime+'&endTime='+endTime, '部署系统用户部署统计', 'userOption'); 109 | //环境统计 110 | this.dataAjax('/home/history/get_env_stat?id='+project_id+'&startTime='+startTime+'&endTime='+endTime, '部署系统环境部署统计', 'envOption'); 111 | } 112 | componentDidMount() { 113 | this.fetch(); 114 | 115 | } 116 | totalDateChange(dates, dateString) { 117 | this.fetch(dateString[0],dateString[1]); 118 | } 119 | 120 | render() { 121 | const formItemLayout = { 122 | labelCol: { 123 | span: 8 124 | }, 125 | wrapperCol: { 126 | span: 16 127 | } 128 | }; 129 | return ( 130 |
131 | 132 |
{this.state.projectName}项目的部署统计:
133 |
134 |
135 | 136 | 137 | 138 |
139 |
140 | 144 |
145 |
146 | 150 |
151 |
152 | ); 153 | } 154 | } 155 | 156 | export default Form.create({})(Stat); 157 | -------------------------------------------------------------------------------- /frontend/src/component/login_form.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import { 3 | Button, 4 | Form, 5 | Input, 6 | Row, 7 | Col, 8 | Modal 9 | } from 'antd'; 10 | const createForm = Form.create; 11 | const FormItem = Form.Item; 12 | const confirm = Modal.confirm; 13 | function noop() { 14 | return false; 15 | } 16 | 17 | class loginForm extends React.Component { 18 | constructor(props) { 19 | super(props); 20 | } 21 | 22 | getValidateStatus(field) { 23 | const {isFieldValidating, getFieldError, getFieldValue} = this.props.form; 24 | 25 | if (isFieldValidating(field)) { 26 | return 'validating'; 27 | } else if (!!getFieldError(field)) { 28 | return 'error'; 29 | } else if (getFieldValue(field)) { 30 | return 'success'; 31 | } 32 | } 33 | 34 | handleReset(event) { 35 | event.preventDefault(); 36 | this.props.form.resetFields(); 37 | } 38 | /** 39 | * 显示提示Modal 40 | */ 41 | showConfirm(title, content) { 42 | confirm({title: title, content: content, onOk() {}, onCancel() {}}); 43 | } 44 | handleSubmit(event) { 45 | console.log(event); 46 | event.preventDefault(); 47 | let me = this; 48 | this.props.form.validateFields((errors, values) => { 49 | if (!!errors) { 50 | console.log('login form validate error'); 51 | return; 52 | } 53 | console.log('Submit!!!'); 54 | console.log(values); 55 | $.ajax({ 56 | type: 'POST', 57 | url: '/home/auth', 58 | data: { 59 | name: values.name, 60 | pass: values.passwd 61 | }, 62 | success: res => { 63 | if (res.errno == 0) { 64 | window.location.href = './'; 65 | } else { 66 | me.showConfirm('登录失败', '用户或者密码名字错误') 67 | } 68 | } 69 | }) 70 | }); 71 | } 72 | 73 | showModal(e) { 74 | this.props.regFormHandler(true); 75 | } 76 | 77 | render() { 78 | const {getFieldProps, getFieldError, isFieldValidating} = this.props.form; 79 | const nameProps = getFieldProps('name', { 80 | rules: [ 81 | { 82 | required: true, 83 | message: '请填写用户名' 84 | } 85 | ] 86 | }); 87 | const passwdProps = getFieldProps('passwd', { 88 | rules: [ 89 | { 90 | required: true, 91 | whitespace: true, 92 | message: '请填写密码' 93 | } 94 | ] 95 | }); 96 | 97 | const formItemLayout = { 98 | labelCol: { 99 | span: 7 100 | }, 101 | wrapperCol: { 102 | span: 12 103 | } 104 | }; 105 | const resetLayout = { 106 | labelCol: { 107 | span: 18 108 | }, 109 | wrapperCol: { 110 | span: 1 111 | } 112 | }; 113 | 114 | const regLayout = { 115 | labelCol: { 116 | span: 14 117 | }, 118 | wrapperCol: { 119 | span: 5 120 | } 121 | }; 122 | return ( 123 |
124 |
125 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 重置 139 | 140 | 141 | 145 | 148 |     149 | 150 | 151 | 152 | 153 | 注册 156 | 157 |
158 |
159 | ); 160 | } 161 | } 162 | 163 | loginForm = createForm()(loginForm); 164 | export default loginForm; 165 | //ReactDOM.render(, mountNode); 166 | -------------------------------------------------------------------------------- /src/home/model/history.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * model 4 | */ 5 | export default class extends think.model.base { 6 | 7 | async getHistoryList(proId, pageId, pageSize) { 8 | let hisModel = think.model("history", think.config("db"), "home"); 9 | let data = await hisModel.where({ 10 | "project_id": proId 11 | }).order("id DESC").page(pageId, pageSize).countSelect(); 12 | return data; 13 | } 14 | async newHistory(his) { 15 | let hisModel = think.model("history", think.config("db"), "home"); 16 | let insertId = await hisModel.add(his); 17 | return insertId; 18 | } 19 | async getTotalStat(startTime,endTime,user,te) { 20 | let hisModel = think.model("history", think.config("db"), "home"); 21 | let sql = "select project_id ,count(*) as pub,name from history,project where "+ 22 | "history.project_id=project.id "; 23 | let endSql = " group by project_id order by pub desc"; 24 | if(startTime&&startTime.length>0){ 25 | endSql = "and date(history.pub_time)<='"+endTime+"' and date(history.pub_time)>'"+startTime+"'"+endSql; 26 | } 27 | let type = parseInt(te); 28 | if(user&&user.length>0){ 29 | sql = "select project_id ,count(*) as pub,name from history,project where "+ 30 | "history.project_id=project.id and history.user='"+user+"' "; 31 | }else if(type > 0){ 32 | sql = "select project_id ,count(*) as pub,name from history,project where "+ 33 | "history.project_id=project.id and history.type="+type+" "; 34 | } 35 | sql = sql+ endSql; 36 | let dataList = await hisModel.query(sql); 37 | return dataList; 38 | } 39 | async getUserStat(startTime,endTime,projectId,te) { 40 | let hisModel = think.model("history", think.config("db"), "home"); 41 | let pId = parseInt(projectId); 42 | let type = parseInt(te); 43 | let sql = "select user as name ,count(*) as pub from history"; 44 | let endSql = " group by user order by pub desc"; 45 | if(startTime&&startTime.length>0){ 46 | endSql = " date(history.pub_time)<='"+endTime+"' and date(history.pub_time)>'"+startTime+"'"+endSql; 47 | } 48 | if(pId>0){ 49 | sql = "select user as name ,count(*) as pub from history where project_id="+pId+" "; 50 | if(startTime&&startTime.length>0){ 51 | sql = sql+" and "+ endSql; 52 | } 53 | else{ 54 | sql = sql+ endSql; 55 | } 56 | }else if(type > 0){ 57 | sql = "select user as name ,count(*) as pub from history where type="+type+" "; 58 | if(startTime&&startTime.length>0){ 59 | sql = sql+" and "+ endSql; 60 | } 61 | else{ 62 | sql = sql+ endSql; 63 | } 64 | } 65 | else{ 66 | if(startTime&&startTime.length>0){ 67 | sql = sql+" where "+ endSql; 68 | } 69 | else{ 70 | sql = sql+ endSql; 71 | } 72 | } 73 | 74 | let dataList = await hisModel.query(sql); 75 | return dataList; 76 | } 77 | 78 | async getEnvStat(startTime,endTime,projectId,user) { 79 | let hisModel = think.model("history", think.config("db"), "home"); 80 | let pId = parseInt(projectId); 81 | let sql = "select type,count(*) as pub from history "; 82 | let endSql = " group by type order by pub desc"; 83 | if(startTime&&startTime.length>0){ 84 | endSql = " date(history.pub_time)<='"+endTime+"' and date(history.pub_time)>'"+startTime+"'"+endSql; 85 | } 86 | if(pId>0){ 87 | sql = "select type,count(*) as pub from history where project_id="+pId+" "; 88 | if(startTime&&startTime.length>0){ 89 | sql = sql+" and "+ endSql; 90 | } 91 | else{ 92 | sql = sql+ endSql; 93 | } 94 | } 95 | else if(user&&user.length>0){ 96 | sql = "select type,count(*) as pub from history where user='"+user+"' "; 97 | if(startTime&&startTime.length>0){ 98 | sql = sql+" and "+ endSql; 99 | } 100 | else{ 101 | sql = sql+ endSql; 102 | } 103 | } 104 | else{ 105 | if(startTime&&startTime.length>0){ 106 | sql = sql+" where "+ endSql; 107 | } 108 | else{ 109 | sql = sql+ endSql; 110 | } 111 | } 112 | let dataList = await hisModel.query(sql); 113 | return dataList; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /frontend/src/component/env_stat.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import { 3 | Button, 4 | Table, 5 | Modal, 6 | Row, 7 | Col, 8 | Form, 9 | DatePicker, 10 | TimePicker, 11 | Input 12 | } from 'antd'; 13 | import ReactEcharts from 'echarts-for-react'; 14 | const FormItem = Form.Item; 15 | const RangePicker = DatePicker.RangePicker; 16 | class Stat extends React.Component { 17 | constructor(props) { 18 | super(props); 19 | this.state = { 20 | totalOption: this.getTotalOption(), 21 | userOption: this.getTotalOption(), 22 | } 23 | } 24 | getTotalOption() { 25 | let option = { 26 | title: { 27 | text: '' 28 | }, 29 | color: ['#3398DB'], 30 | tooltip: { 31 | trigger: 'axis', 32 | axisPointer: { // 坐标轴指示器,坐标轴触发有效 33 | type: 'shadow' // 默认为直线,可选为:'line' | 'shadow' 34 | } 35 | }, 36 | grid: { 37 | left: '3%', 38 | right: '4%', 39 | bottom: '3%', 40 | containLabel: true 41 | }, 42 | xAxis: [ 43 | { 44 | type: 'category', 45 | data: [], 46 | axisTick: { 47 | alignWithLabel: true 48 | } 49 | } 50 | ], 51 | yAxis: [ 52 | { 53 | type: 'value' 54 | } 55 | ], 56 | series: [ 57 | { 58 | name: '部署次数', 59 | type: 'bar', 60 | barWidth: '60%', 61 | data: [] 62 | } 63 | ] 64 | }; 65 | return option; 66 | } 67 | dataAjax(url, title, stateOption) { 68 | const self = this; 69 | $.ajax({ 70 | type: 'get', 71 | url: url, 72 | success: function(res) { 73 | let option = self.getTotalOption(); 74 | option.title.text = title; 75 | if (res.errno == 0) { 76 | for (let i = 0; i < res.data.length; i++) { 77 | let data = res.data[i]; 78 | if (stateOption == 'envOption') { 79 | if (data.type == 1) { 80 | option.xAxis[0].data.push('正式'); 81 | } else if (data.type == 2) { 82 | option.xAxis[0].data.push('预发布'); 83 | } else if (data.type == 3) { 84 | option.xAxis[0].data.push('测试'); 85 | } 86 | } else { 87 | option.xAxis[0].data.push(data.name); 88 | } 89 | option.series[0].data.push(data.pub); 90 | } 91 | let stateData = {}; 92 | stateData[stateOption] = option; 93 | self.setState(stateData); 94 | } 95 | } 96 | }) 97 | } 98 | fetch(startTime,endTime) { 99 | if(!startTime){ 100 | startTime=endTime=''; 101 | } 102 | let type=this.props.location.query.type; 103 | let envName =''; 104 | if(type == 1){ 105 | envName = '正式'; 106 | } 107 | else if(type == 2){ 108 | envName = '预发布'; 109 | } 110 | else if(type == 3){ 111 | envName = '测试'; 112 | } 113 | this.setState({ 114 | projectName: envName 115 | }) 116 | //总统计 117 | this.dataAjax('/home/history/get_total_stat?type='+type+'&startTime='+startTime+'&endTime='+endTime, '部署系统项目部署统计', 'totalOption'); 118 | //用户部署统计 119 | this.dataAjax('/home/history/get_user_stat?type='+type+'&startTime='+startTime+'&endTime='+endTime, '部署系统用户部署统计', 'userOption'); 120 | } 121 | componentDidMount() { 122 | this.fetch(); 123 | } 124 | totalDateChange(dates, dateString) { 125 | this.fetch(dateString[0],dateString[1]); 126 | } 127 | 128 | render() { 129 | const formItemLayout = { 130 | labelCol: { 131 | span: 8 132 | }, 133 | wrapperCol: { 134 | span: 16 135 | } 136 | }; 137 | return ( 138 |
139 | 140 |
{this.state.projectName}环境的部署统计:
141 |
142 |
143 | 144 | 145 | 146 |
147 |
148 | 149 | 154 | 155 |
156 | 157 |
158 | 162 |
163 |
164 | ); 165 | } 166 | } 167 | 168 | export default Form.create({})(Stat); 169 | -------------------------------------------------------------------------------- /frontend/src/component/build_history.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | // import Echarts from 'echarts'; 3 | import { 4 | Row, 5 | Col, 6 | Table, 7 | Form, 8 | Input, 9 | Button, 10 | Checkbox, 11 | Tabs, 12 | Badge, 13 | Modal, 14 | Select, 15 | Pagination 16 | } from 'antd'; 17 | const FormItem = Form.Item; 18 | const TabPane = Tabs.TabPane; 19 | const Option = Select.Option; 20 | class BuildHistory extends Component { 21 | constructor(props) { 22 | super(props); 23 | self = this; 24 | this.state = { 25 | infoCol: [ 26 | { 27 | title: '版本号:', 28 | width: '150px', 29 | dataIndex: 'tag' 30 | }, { 31 | title: '版本说明', 32 | width: '150px', 33 | dataIndex: 'pub_desc' 34 | }, { 35 | title: '发布时间:', 36 | width: '180px', 37 | dataIndex: 'pub_time' 38 | }, { 39 | title: '发布人', 40 | width: '120px', 41 | dataIndex: 'user' 42 | }, { 43 | title: '发布机器', 44 | width: '120px', 45 | dataIndex: 'ip' 46 | }, { 47 | title: '日志', 48 | width: '100px', 49 | dataIndex: 'log', 50 | render(text, record) { 51 | return ( 52 | 53 | 查看 54 | 55 | ); 56 | } 57 | } 58 | ], 59 | infoData: [ 60 | { 61 | key: '1', 62 | tag: '201588821313213', 63 | pub_desc: '装修项目', 64 | pub_time: '2015-04-06 21:21:50', 65 | user: 'waynelu', 66 | ip: '10.2.22.23', 67 | log: '查看' 68 | } 69 | ], 70 | logContent: '', 71 | pager: {}, 72 | showLogModal: false 73 | }; 74 | } 75 | closeModal() { 76 | this.setState({showLogModal: false}) 77 | } 78 | fetch(pageId, pageSize) { 79 | const self = this; 80 | $.ajax({ 81 | type: 'get', 82 | url: '/home/history/get_history_list', 83 | data: { 84 | proId: this.props.location.query.projectId, 85 | pageId: pageId, 86 | pageSize: pageSize 87 | }, 88 | success: function(res) { 89 | let pager = {}; 90 | pager.total = res.data.count; 91 | pager.current = res.data.currentPage; 92 | pager.pageSize = res.data.numsPerPage; 93 | self.setState({pager: pager, infoData: res.data.data}) 94 | } 95 | }) 96 | } 97 | componentDidMount() { 98 | this.fetch(1, 10); 99 | } 100 | handleViewLog(e) { 101 | this.setState({showLogModal: true, logContent: '正在读取日志...........................'}); 102 | let self = this; 103 | let buildLog = e.currentTarget.dataset.blog; 104 | let deployLog = e.currentTarget.dataset.dlog; 105 | $.ajax({ 106 | type: 'post', 107 | url: '/home/history/get_history_log', 108 | data: { 109 | buildLog: buildLog, 110 | deployLog: deployLog 111 | }, 112 | success: function(res) { 113 | self.setState({ 114 | showLogModal: true, 115 | logContent: res.data.replace(/\n/g, "
") 116 | }); 117 | } 118 | }) 119 | } 120 | onChange(page) { 121 | this.fetch(page, 10); 122 | } 123 | showTotal(total) { 124 | return `共 ${total} 条`; 125 | } 126 | render() { 127 | 128 | return 129 |
构建历史:
130 |
131 | 134 | 135 | 136 | 139 |
143 |
144 | 145 | 146 |

147 |

156 |

157 |
158 |
; 159 | } 160 | } 161 | 162 | export default BuildHistory; 163 | -------------------------------------------------------------------------------- /src/home/controller/machine.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | import Base from './base.js'; 3 | import Machine from '../model/machine'; 4 | import ProModel from '../model/project'; 5 | import request from 'request'; 6 | /** 7 | * rest controller 8 | * @type {Class} 9 | */ 10 | export default class extends Base { 11 | 12 | 13 | async indexAction() { 14 | let machineModel = new Machine(); 15 | let data; 16 | if (this.http.isPost()) { 17 | let values = this.post(); 18 | let batch = values.batch; 19 | //如果是批量添加 20 | let start = 1; 21 | let end = 1; 22 | if(batch.indexOf('-')>0){ 23 | let batchArr = batch.split('-'); 24 | start = parseInt(batchArr[0]); 25 | end = parseInt(batchArr[1]); 26 | } 27 | for(let i = start ; i <= end;i ++){ 28 | let machine = {}; 29 | machine.name = values.name.replace(/\${NO}/g,''+i); 30 | machine.task = values.task.replace(/\${NO}/g,''+i); 31 | machine.type = values.type; 32 | machine.ip = values.ip; 33 | machine.sdir = values.sdir.replace(/\${NO}/g,''+i); 34 | machine.dir = values.dir.replace(/\${NO}/g,''+i); 35 | machine.ssh_user = values.ssh_user; 36 | machine.ssh_pass = values.ssh_pass; 37 | machine.project_id = values.project_id; 38 | machine.op_env_id = values.op_env_id; 39 | await machineModel.newMachine(machine); 40 | } 41 | 42 | // let insertId = await machineModel.newMachine(values); 43 | return this.success(0); 44 | } else { 45 | let id = this.get('id'); 46 | if (id) { 47 | console.log(data); 48 | data = await machineModel.getMachineById(id); 49 | return this.success(data); 50 | } else { 51 | return this.fail('缺少machineId'); 52 | } 53 | } 54 | } 55 | async updateAction(){ 56 | if (this.http.isPost()) { 57 | let machineModel = new Machine(); 58 | let values = this.post(); 59 | let aRows = await machineModel.updateMachine(values); 60 | return this.success(aRows); 61 | } 62 | } 63 | async lockAction(){ 64 | if (this.http.isPost()) { 65 | let machineModel = new Machine(); 66 | let ac = this.post('ac'); 67 | let machineArr = JSON.parse(this.param('machineArr')); 68 | let sessionUser = await this.http.session('user'); 69 | let username = sessionUser.name; 70 | let isAdmin = this.post('isAdmin'); 71 | let errorMsg = ''; 72 | let hasError =false; 73 | //批量锁定 74 | for(let i = 0;i < machineArr.length;i ++){ 75 | let mInfo = machineArr[i]; 76 | let data = await machineModel.getMachineById(mInfo.id); 77 | 78 | //如果环境已经被锁定,并且当前用户不是你,那么无法做锁定解锁操作 79 | if(data.is_lock==1&&data.lock_user != username){ 80 | if(!isAdmin){ 81 | errorMsg += data.name+'('+data.ip+'),'; 82 | hasError = true; 83 | //return this.success({'status':403,'msg':'当前环境并'+data.lock_user+'锁定,请联系他解锁!'}); 84 | } 85 | } 86 | if(ac == 'lock'){ 87 | mInfo.is_lock=1; 88 | mInfo.lock_user=username; 89 | } 90 | else{ 91 | mInfo.is_lock=0; 92 | mInfo.lock_user=''; 93 | } 94 | let aRows = await machineModel.updateMachine(mInfo); 95 | } 96 | 97 | 98 | if(hasError){ 99 | return this.success({'status':403,'msg':'环境'+errorMsg+'锁定,请联系他解锁!其他环境操作成功!'}); 100 | } 101 | return this.success({'status':200,'msg':'操作成功'}); 102 | } 103 | } 104 | async deleteAction(){ 105 | if (this.http.isPost()) { 106 | let machineModel = new Machine(); 107 | let id = this.post('id'); 108 | let aRows = await machineModel.deleteMachine(id); 109 | return this.success(aRows); 110 | } 111 | } 112 | 113 | async projectAction() { 114 | let machineModel = new Machine(); 115 | let data; 116 | let projectId = this.get('id'); 117 | if (projectId) { 118 | 119 | data = await machineModel.getMachinesByProject(projectId); 120 | console.log(data); 121 | return this.success(data); 122 | } else { 123 | return this.fail('缺少projectId'); 124 | } 125 | 126 | } 127 | 128 | /** 129 | * before magic method 130 | * @return {Promise} [] 131 | */ 132 | __before() { 133 | 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /frontend/src/component/register_form.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import { 3 | Button, 4 | Form, 5 | Input, 6 | Row, 7 | Col, 8 | Modal 9 | } from 'antd'; 10 | const createForm = Form.create; 11 | const FormItem = Form.Item; 12 | 13 | function noop() { 14 | return false; 15 | } 16 | 17 | class regForm extends React.Component { 18 | 19 | getValidateStatus(field) { 20 | const {isFieldValidating, getFieldError, getFieldValue} = this.props.form; 21 | 22 | if (isFieldValidating(field)) { 23 | return 'validating'; 24 | } else if (!!getFieldError(field)) { 25 | return 'error'; 26 | } else if (getFieldValue(field)) { 27 | return 'success'; 28 | } 29 | } 30 | 31 | handleSubmit(e) { 32 | e.preventDefault(); 33 | console.log('收到表单值:', this.props.form.getFieldsValue()); 34 | } 35 | 36 | userExists(rule, value, callback) { 37 | if (!value) { 38 | callback(); 39 | } else { 40 | $.ajax({ 41 | url: '/home/user', 42 | type: 'GET', 43 | data: { 44 | name: value 45 | }, 46 | success: res => { 47 | if (res.errno == 0) { 48 | if (res.data && res.data.name) { 49 | callback([new Error('抱歉,该用户名已被占用。')]); 50 | } else { 51 | callback(); 52 | } 53 | } 54 | } 55 | }) 56 | } 57 | } 58 | 59 | hideModal() { 60 | this.props.regFormHandler(false); 61 | } 62 | 63 | checkRegRePass(rule, value, callback) { 64 | const {getFieldValue} = this.props.form; 65 | let repass = getFieldValue('regPass'); 66 | if (value && value !== getFieldValue('regPass')) { 67 | callback(new Error('两次输入密码不一致!')); 68 | } else { 69 | callback(); 70 | } 71 | } 72 | 73 | handleRegister() { 74 | var _self = this; 75 | const {getFieldValue} = _self.props.form; 76 | _self.props.form.validateFields((errors, values) => { 77 | if (!!errors) { 78 | console.log('Errors in form!!!'); 79 | return; 80 | } 81 | if (getFieldValue('regName') && getFieldValue('regPass')) { 82 | $.ajax({ 83 | type: 'POST', 84 | url: '/home/user', 85 | data: { 86 | name: getFieldValue('regName'), 87 | pass: getFieldValue('regPass') 88 | }, 89 | success: function(res) { 90 | if (res.errno == 0) { 91 | _self.hideModal(); 92 | } 93 | } 94 | }) 95 | } 96 | 97 | }); 98 | 99 | } 100 | render() { 101 | const {getFieldProps, getFieldError, isFieldValidating} = this.props.form; 102 | const formItemLayout = { 103 | labelCol: { 104 | span: 6 105 | }, 106 | wrapperCol: { 107 | span: 14 108 | } 109 | }; 110 | const regUserProps = getFieldProps('regName', { 111 | rules: [ 112 | { 113 | required: true, 114 | message: '请填写用户名' 115 | }, { 116 | validator: this.userExists 117 | } 118 | ] 119 | }); 120 | 121 | const regPassProps = getFieldProps('regPass', { 122 | rules: [ 123 | { 124 | required: true, 125 | whitespace: true, 126 | message: '请填写密码' 127 | } 128 | ] 129 | }); 130 | const regRePassProps = getFieldProps('regRePass', { 131 | rules: [ 132 | { 133 | required: true, 134 | whitespace: true, 135 | message: '请再次输入密码' 136 | }, { 137 | validator: this.checkRegRePass.bind(this) 138 | } 139 | ] 140 | }); 141 | 142 | const regFormItemLayout = { 143 | labelCol: { 144 | span: 8 145 | }, 146 | wrapperCol: { 147 | span: 16 148 | } 149 | }; 150 | return ( 151 | 152 |
153 | 154 | 155 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 |
178 |
179 | ); 180 | } 181 | }; 182 | 183 | export default Form.create()(regForm); 184 | -------------------------------------------------------------------------------- /frontend/src/component/stat.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import { 3 | Button, 4 | Table, 5 | Modal, 6 | Row, 7 | Col, 8 | Form, 9 | DatePicker, 10 | TimePicker, 11 | Input 12 | } from 'antd'; 13 | import ReactEcharts from 'echarts-for-react'; 14 | const FormItem = Form.Item; 15 | const RangePicker = DatePicker.RangePicker; 16 | class Stat extends React.Component { 17 | constructor(props) { 18 | super(props); 19 | this.state = { 20 | totalOption: this.getTotalOption(), 21 | totalOptionData:{}, 22 | userOption: this.getTotalOption(), 23 | userOptionData:{}, 24 | envOption: this.getTotalOption(), 25 | startTime: '', 26 | entTime: '', 27 | envOptionData:{} 28 | } 29 | } 30 | getTotalOption() { 31 | let option = { 32 | title: { 33 | text: '' 34 | }, 35 | color: ['#3398DB'], 36 | tooltip: { 37 | trigger: 'axis', 38 | axisPointer: { // 坐标轴指示器,坐标轴触发有效 39 | type: 'shadow' // 默认为直线,可选为:'line' | 'shadow' 40 | } 41 | }, 42 | grid: { 43 | left: '3%', 44 | right: '4%', 45 | bottom: '3%', 46 | containLabel: true 47 | }, 48 | xAxis: [ 49 | { 50 | type: 'category', 51 | data: [], 52 | ext:[], 53 | axisTick: { 54 | alignWithLabel: true 55 | } 56 | } 57 | ], 58 | yAxis: [ 59 | { 60 | type: 'value' 61 | } 62 | ], 63 | series: [ 64 | { 65 | name: '部署次数', 66 | type: 'bar', 67 | barWidth: '60%', 68 | data: [] 69 | } 70 | ] 71 | }; 72 | return option; 73 | } 74 | onChartClick(param, echart) { 75 | if(echart._dom.className == 'total'){ 76 | let dataObject = this.state.totalOptionData.data[param.dataIndex]; 77 | window.location.href = '#project_stat?id=' + dataObject.project_id+'&name='+dataObject.name; 78 | } 79 | else if(echart._dom.className == 'user'){ 80 | let dataObject = this.state.userOptionData.data[param.dataIndex]; 81 | window.location.href = '#user_stat?id=' + dataObject.name; 82 | } 83 | else if(echart._dom.className == 'env'){ 84 | let dataObject = this.state.envOptionData.data[param.dataIndex]; 85 | window.location.href = '#env_stat?type=' + dataObject.type; 86 | } 87 | 88 | } 89 | onChartLegendselectchanged(param, echart) { 90 | } 91 | 92 | dataAjax(url, title, stateOption) { 93 | const self = this; 94 | $.ajax({ 95 | type: 'get', 96 | url: url, 97 | success: function(res) { 98 | let option = self.getTotalOption(); 99 | option.title.text = title; 100 | if (res.errno == 0) { 101 | for (let i = 0; i < res.data.length; i++) { 102 | let data = res.data[i]; 103 | if (stateOption == 'envOption') { 104 | if (data.type == 1) { 105 | option.xAxis[0].data.push('正式'); 106 | } else if (data.type == 2) { 107 | option.xAxis[0].data.push('预发布'); 108 | } else if (data.type == 3) { 109 | option.xAxis[0].data.push('测试'); 110 | } 111 | } else { 112 | option.xAxis[0].data.push(data.name); 113 | } 114 | option.series[0].data.push(data.pub); 115 | } 116 | let stateData = {}; 117 | stateData[stateOption] = option; 118 | self.setState(stateData); 119 | let extData = {}; 120 | extData[stateOption+'Data'] = res; 121 | self.setState(extData); 122 | } 123 | } 124 | }) 125 | } 126 | fetch(startTime,endTime) { 127 | if(!startTime){ 128 | startTime=endTime=''; 129 | } 130 | //总统计 131 | 132 | this.dataAjax('/home/history/get_total_stat?startTime='+startTime+'&endTime='+endTime, '部署系统项目部署统计', 'totalOption'); 133 | //用户部署统计 134 | this.dataAjax('/home/history/get_user_stat?startTime='+startTime+'&endTime='+endTime, '部署系统用户部署统计', 'userOption'); 135 | //环境统计 136 | this.dataAjax('/home/history/get_env_stat?startTime='+startTime+'&endTime='+endTime, '部署系统环境部署统计', 'envOption'); 137 | } 138 | componentDidMount() { 139 | this.fetch(); 140 | } 141 | totalDateChange(dates, dateString) { 142 | this.fetch(dateString[0],dateString[1]); 143 | } 144 | 145 | render() { 146 | const formItemLayout = { 147 | labelCol: { 148 | span: 8 149 | }, 150 | wrapperCol: { 151 | span: 16 152 | } 153 | }; 154 | 155 | let onEvents = { 156 | 'click': this.onChartClick.bind(this), 157 | 'legendselectchanged': this.onChartLegendselectchanged 158 | }; 159 | return ( 160 |
161 | 162 |
部署系统统计
163 |
164 |
165 | 166 | 167 | 168 |
169 |
170 | 171 | 177 | 178 |
179 | 180 |
181 | 187 |
188 |
189 | 195 |
196 |
197 | ); 198 | } 199 | } 200 | 201 | export default Form.create({})(Stat); 202 | -------------------------------------------------------------------------------- /src/common/service/cvs.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | import svn from './impl/svn'; 3 | import git from './impl/git'; 4 | import diffDir from './impl/diffdir'; 5 | import fileUtil from './fileutil'; 6 | import fs from 'fs'; 7 | import childProcessPro from 'child-process-es6-promise'; 8 | export default class extends think.service.base { 9 | /** 10 | * init 11 | * @return {} [] 12 | */ 13 | init(type) { 14 | this.type = type; 15 | let config = { 16 | username: think.config('cvsUser'), 17 | password: think.config('cvsPass') 18 | } 19 | if (type == 2) { 20 | this.cvsClient = new svn(config); 21 | } else { 22 | this.cvsClient = new git(config); 23 | } 24 | } 25 | 26 | export (codeUrl, dirPath = "/temp", isBranch, callback) { 27 | this.cvsClient.export([codeUrl, dirPath], isBranch, callback); 28 | } 29 | exportTag(codeUrl, dirPath = "/temp", newV, callback) { 30 | this.cvsClient.exportTag(codeUrl, dirPath, newV, callback); 31 | } 32 | 33 | async getChangeLog(pro, username) { 34 | let fileU = new fileUtil(); 35 | let cvsDir = fileU.getCvsDir(username, pro.id); 36 | let isBranch = pro.branch_deploy; 37 | let lastTagUrl = this.cvsClient.getLastTagUrl(pro.codeUrl, pro.id, pro.lastTag, isBranch); 38 | let reArray = await this.cvsClient.getChangeLog(pro.codeUrl, lastTagUrl, pro.lastTag, cvsDir); 39 | return reArray; 40 | } 41 | async getFileChangeInfo(pro,username,commit,lastCommit,filename){ 42 | let fileU = new fileUtil(); 43 | let cvsDir = fileU.getCvsDir(username, pro.id); 44 | let isBranch = pro.branch_deploy; 45 | let lastTagUrl = this.cvsClient.getLastTagUrl(pro.codeUrl, pro.id, pro.lastTag, isBranch); 46 | let content = await this.cvsClient.getFileDiff(pro.codeUrl, lastTagUrl,commit,lastCommit,filename,cvsDir); 47 | let conArr = content.split('\n'); 48 | return conArr; 49 | } 50 | async copyTag(codeUrl, proId, newV, username, isInc, homeDir, incExc) { 51 | let fileU = new fileUtil(); 52 | let cvsDir = fileU.getCvsDir(username, proId); 53 | if (isInc && incExc == 1) { 54 | cvsDir = fileU.getLastTagDir(username, proId); 55 | } 56 | //删除dist build bad code!!!; 57 | // await childProcessPro.exec('rm -rf dist build', { 58 | // cwd: cvsDir 59 | // }); 60 | // await childProcessPro.exec('rm -rf dist weidian', { 61 | // cwd: cvsDir 62 | // }); 63 | return new Promise((resolve, reject) => { 64 | this.cvsClient.copyTag(codeUrl, proId, newV, cvsDir, isInc, homeDir, (err, result) => { 65 | if (err) { 66 | reject(err); 67 | } else { 68 | resolve(result); 69 | } 70 | 71 | }) 72 | }) 73 | } 74 | async getCheckoutInfo(pro, username, nowLogFile) { 75 | console.log(pro); 76 | //分支发布 77 | let isBranch = pro.branch_deploy; 78 | 79 | let fileU = new fileUtil(); 80 | let cvsDir = fileU.getCvsDir(username, pro.id); 81 | //删除文件 82 | let self = this; 83 | //删除除了node_modules文件夹以外的所有文件 84 | //删除文件夹 85 | if (fs.existsSync(cvsDir)) { 86 | await childProcessPro.exec('rm -rf ' + cvsDir); 87 | } 88 | let lastTagDir = fileU.getLastTagDir(username, pro.id); 89 | if (fs.existsSync(lastTagDir)) { 90 | await childProcessPro.exec('rm -rf ' + lastTagDir); 91 | } 92 | //没有上一个tag或者非快捷发布 93 | if (pro.lastTag&&!pro.isQuickDeploy) { 94 | 95 | let lastTagUrl = this.cvsClient.getLastTagUrl(pro.codeUrl, pro.id, pro.lastTag, isBranch); 96 | // let changeLog = await self.getChangeLog(pro.codeUrl,lastTagUrl,pro.lastTag,cvsDir); 97 | return new Promise((resolve, reject) => { 98 | // todo: 增加项目配置项:diff路径,从配置中取 99 | let options = { 100 | encoding: 'utf8', 101 | mode: 438, 102 | flag: 'a' 103 | }; 104 | fs.writeFileSync(nowLogFile, '=================start to checkout code:\n', options); 105 | //先拉取上一个tag版本 106 | self.exportTag(lastTagUrl, lastTagDir, pro.lastTag, (err, stdout) => { 107 | 108 | if (err) { 109 | let errmsg = err.toString() + err.stack; 110 | fs.writeFileSync(nowLogFile, errmsg, options); 111 | reject(err); 112 | return; 113 | } 114 | fs.writeFileSync(nowLogFile, stdout, options); 115 | //或者正式仓库目录 116 | self.export(pro.codeUrl, cvsDir, isBranch, (err, stdout) => { 117 | if (err) { 118 | let errmsg = err.toString() + err.stack; 119 | fs.writeFileSync(nowLogFile, errmsg, options); 120 | reject(err); 121 | return; 122 | } 123 | fs.writeFileSync(nowLogFile, stdout, options); 124 | //过滤node_modules文件夹 125 | let diff = new diffDir(); 126 | let result = diff.diff(cvsDir, lastTagDir, ['node_modules', '.git']); 127 | //result.changeInfo = changeLog; 128 | 129 | resolve(result); 130 | }) 131 | }) 132 | }) 133 | } else { 134 | 135 | return new Promise((resolve, reject) => { 136 | let options = { 137 | encoding: 'utf8', 138 | mode: 438, 139 | flag: 'a' 140 | }; 141 | fs.writeFileSync(nowLogFile, '=================start to checkout code:\n', options); 142 | self.export(pro.codeUrl, cvsDir, isBranch, function(err, stdout) { 143 | // console.log('exprot success'); 144 | if (err) { 145 | let errmsg = err.toString() + err.stack; 146 | fs.writeFileSync(nowLogFile, errmsg, options); 147 | reject(err); 148 | return; 149 | } 150 | //过滤node_modules文件夹 151 | fs.writeFileSync(nowLogFile, stdout, options); 152 | let diff = new diffDir(); 153 | let result = diff.diff(cvsDir, undefined, ['node_modules', '.git']); 154 | resolve(result); 155 | }) 156 | }) 157 | } 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /frontend/src/component/project_detail.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import InitState from './project_detail_component/stateinfo'; 4 | import Ajax from './project_detail_component/detail_ajax'; 5 | import BaseInfo from './project_detail_component/baseinfo'; 6 | import BuildLog from './project_detail_component/build_log'; 7 | import CodeView from './project_detail_component/code_view'; 8 | import DoBuild from './project_detail_component/do_build'; 9 | import DoDeploy from './project_detail_component/do_deploy' 10 | import { 11 | Timeline, 12 | Row, 13 | Card, 14 | CheckBox, 15 | Col, 16 | Table, 17 | Form, 18 | Input, 19 | Button, 20 | Checkbox, 21 | Tabs, 22 | Badge, 23 | Select, 24 | Icon, 25 | Modal, 26 | Radio, 27 | Tag 28 | } from 'antd'; 29 | import {Link} from 'react-router'; 30 | import '../style/project.less'; 31 | const FormItem = Form.Item; 32 | const TabPane = Tabs.TabPane; 33 | const Option = Select.Option; 34 | const RadioGroup = Radio.Group; 35 | const ajax = new Ajax(); 36 | class ProjectDetail extends Component { 37 | constructor(props) { 38 | super(props); 39 | InitState.projectId = this.props.location.query.id; 40 | this.state = InitState; 41 | } 42 | 43 | componentDidMount() { 44 | const query = this.props.location.query; 45 | let id = query.id; 46 | ajax.getProjectById(id,this); 47 | ajax.getMachineList(id,this); 48 | if (query.debug) { 49 | //增加调试模式 50 | this.setState({canDeploy: true, canDeploy: true, canBuild: true, canTag: true, debug: true}) 51 | } 52 | } 53 | setProState(state){ 54 | this.setState(state); 55 | } 56 | setCanBuild(canBuild){ 57 | this.setState({canBuild: canBuild}); 58 | } 59 | doBuildLog(tempLog){ 60 | let bLog = this.refs.buildLog; 61 | bLog.appendLog(tempLog); 62 | } 63 | websocketOn(msgType, event, callback) { 64 | let socketHost = window.location.protocol + '//' + window.location.host; //+'/'+msgType; 65 | let socket = io(socketHost); 66 | socket.emit(msgType); 67 | socket.on(event, function(data) { 68 | callback(data); 69 | }); 70 | } 71 | handleCheckout(isQuickDeploy){ 72 | this.refs.codeView.handleCheckout(isQuickDeploy); 73 | } 74 | handleBuild(isQuickDeploy) { 75 | this.refs.doBuild.handleBuild(isQuickDeploy); 76 | } 77 | handleDeploy(isQuickDeploy) { 78 | this.refs.doDeploy.handleDeploy(isQuickDeploy); 79 | } 80 | 81 | closeTipModal() { 82 | this.setState({tipModalVisible: false}) 83 | if (this.state.isLock) { 84 | $.ajax({ 85 | url: '/home/machine/lock', 86 | type: 'POST', 87 | data: { 88 | machineArr: JSON.stringify(this.state.selectedMachines), 89 | ac: 'lock' 90 | }, 91 | success: function(res) { 92 | if (!res.errno) { 93 | Modal.success({title: '锁定机器成功'}); 94 | } 95 | } 96 | }) 97 | } 98 | } 99 | 100 | 101 | handleIsLockCheck(e) { 102 | this.setState({isLock: e.target.checked}) 103 | } 104 | 105 | handleDebug() { 106 | this.setState({tipModalVisible: true}) 107 | } 108 | 109 | render() { 110 | let that = this; 111 | return ( 112 |
113 |
114 | {this.state.debug 115 | ? 116 | : null} 117 |
118 | 119 | 123 | 124 | 125 | 126 | 143 | 144 | 158 | 184 | 185 |
186 |
187 | 是否锁定该环境 188 |
189 |
190 | 191 |
192 | ) 193 | } 194 | } 195 | 196 | export default ProjectDetail; 197 | -------------------------------------------------------------------------------- /src/common/service/impl/svn.js: -------------------------------------------------------------------------------- 1 | //从远程fetch 代码到本地, 这里使用 svn 的export进行获取代码 2 | import childProcessPro from 'child-process-es6-promise'; 3 | var exec = require('child_process').exec; 4 | 5 | function SvnClient(config = {}) { 6 | this._userName = config.username; 7 | this._password = config.password; 8 | } 9 | SvnClient.prototype = { 10 | //对svn来说分支和非分支是2个地址,没有不一样的地方 11 | export: function(params, isBranch, cb) { 12 | console.log('-----'+params+'------------------') 13 | let cmdStr = [ 14 | 'svn export --username', 15 | this._userName, 16 | '--password', 17 | this._password, 18 | '--force' 19 | ]; 20 | // exec('svn help',function(err,data){ 21 | // console.log('---dasda--'+err+'---dasda--'); 22 | // cb(null, data); 23 | // }) 24 | cmdStr = cmdStr.concat(params).join(' '); 25 | exec(cmdStr, function(err, data) { 26 | console.log('---dasda--'+err+'---dasda--', data); 27 | if (err) { 28 | cb(err); 29 | } else { 30 | cb(null, data); 31 | } 32 | }); 33 | }, 34 | /** 35 | **/ 36 | async getSvnInfo(codeUrl) { 37 | let cmd = 'svn info ' + codeUrl; 38 | console.log('getSvnInfo:' + cmd); 39 | let changeLog = await childProcessPro.exec(cmd, { 40 | maxBuffer: 100 * 1024 * 1024 41 | }); 42 | 43 | let info = {}; 44 | 45 | let infoArray = changeLog.stdout.split('\n'); 46 | for (let i = 0; i < infoArray.length; i++) { 47 | let lineArray = infoArray[i].split(': '); 48 | info[lineArray[0]] = lineArray[1]; 49 | } 50 | return info; 51 | }, 52 | async getFileDiff(codeUrl, lastTagUrl,commit,lastCommit,filename,cvsDir) { 53 | //svn diff -c 39049 src/js/user/index.js 54 | let cmd = 'svn diff -c '+commit+' '+codeUrl+'/'+filename; 55 | let changeLog = await childProcessPro.exec(cmd, { 56 | cwd: cvsDir, 57 | maxBuffer: 100 * 1024 * 1024 58 | }); 59 | return changeLog.stdout; 60 | }, 61 | async getChangeLog(codeUrl, lastTagUrl, lastTag, cvsDir) { 62 | let info = undefined, 63 | lastInfo = undefined, 64 | rev = undefined, 65 | lastRev = undefined, 66 | cmd = undefined; 67 | try { 68 | info = await this.getSvnInfo(codeUrl); 69 | lastInfo = await this.getSvnInfo(lastTagUrl); 70 | } catch (e) { 71 | console.log(e); 72 | } 73 | if (info && lastInfo) { 74 | rev = info['Revision'] || info['版本']; 75 | lastRev = lastInfo['Last Changed Rev'] || lastInfo['最后修改的版本']; 76 | cmd = 'svn log ' + codeUrl + ' -v -r r' + lastRev + ':r' + rev; 77 | } else { 78 | cmd = 'svn log ' + codeUrl + ' -v '; 79 | } 80 | console.log(cmd); 81 | 82 | 83 | let changeLog = await childProcessPro.exec(cmd, { 84 | cwd: cvsDir, 85 | maxBuffer: 100 * 1024 * 1024 86 | }); 87 | let reInfo = []; 88 | let temp = {}; 89 | 90 | //[{'author':'waynelu',chaninfo:[{'rev':'r2323','time':'2323131',chang}] 91 | let logArr = changeLog.stdout.split('------------------------------------------------------------------------'); 92 | think.log('changeLog.stdout', 'INFO'); 93 | for (let i = 1; i < logArr.length - 1; i++) { 94 | let lineArray = logArr[i]; 95 | let logArrMsg = lineArray.split('\n\n'); 96 | let msgSplit = logArrMsg[0]; 97 | let msg = logArrMsg[1]; 98 | let infoArray = msgSplit.split('\n'); 99 | let infoData = {}; 100 | let revInfoArray = infoArray[1].split(' | '); 101 | infoData.rev = revInfoArray[0]; 102 | infoData.author = revInfoArray[1]; 103 | infoData.time = revInfoArray[2]; 104 | infoData.changeFilesLog = infoArray.slice(3, infoArray.length); 105 | infoData.msg = msg; 106 | if (!temp[infoData.author]) { 107 | let recInfo = { 108 | 'author': infoData.author, 109 | 'changeLog': [infoData] 110 | }; 111 | temp[infoData.author] = recInfo; 112 | reInfo.push(recInfo); 113 | } else { 114 | temp[infoData.author].changeLog.push(infoData); 115 | } 116 | } 117 | return reInfo; 118 | 119 | }, 120 | getLastTagUrl: function(url, pid, lastTag) { 121 | let urlArray; 122 | if (url.indexOf('trunk') > -1) { 123 | urlArray = url.split('trunk'); 124 | } else if (url.indexOf('branches') > -1) { 125 | urlArray = url.split('branches'); 126 | } 127 | else if (url.indexOf('tags') > -1) { 128 | urlArray = url.split('tags'); 129 | } 130 | return urlArray[0] + 'tags/project_' + pid + '/' + lastTag; 131 | }, 132 | exportTag: function(codeUrl, dirPath, newV, callback) { 133 | this.export([codeUrl, dirPath], false, callback); 134 | }, 135 | copyTag: function(codeUrl, proId, newV, cvsDir, isInc, homeDir, cb) { 136 | console.log('copyTag:' + isInc + ' cvsDir: ' + cvsDir); 137 | let urlArray; 138 | if (codeUrl.indexOf('trunk') > -1) { 139 | urlArray = codeUrl.split('trunk'); 140 | } else if (codeUrl.indexOf('branches') > -1) { 141 | urlArray = codeUrl.split('branches'); 142 | } 143 | let tagUrl = urlArray[0] + 'tags/project_' + proId + '/' + newV; 144 | let tagMsg = '"App build: proId-' + proId + ', proVersion-' + newV + '"'; 145 | 146 | //增量更新打tag 147 | if (isInc) { 148 | let importDir = homeDir + cvsDir.substring(2); 149 | let paramsInc = [importDir, tagUrl, '-m', tagMsg]; 150 | let cmdStrInc = [ 151 | 'svn import --username', 152 | this._userName, 153 | '--password', 154 | this._password 155 | ]; 156 | cmdStrInc = cmdStrInc.concat(paramsInc).join(' '); 157 | console.log(cmdStrInc); 158 | exec(cmdStrInc, function(err, data) { 159 | if (err) { 160 | cb(err); 161 | } else { 162 | cb(null, data); 163 | } 164 | }); 165 | return; 166 | } 167 | console.log('copyTag no importTag') 168 | 169 | let params = [codeUrl, tagUrl, '--parents', '-m', tagMsg]; 170 | let cmdStr = [ 171 | 'svn copy --username', 172 | this._userName, 173 | '--password', 174 | this._password 175 | ]; 176 | cmdStr = cmdStr.concat(params).join(' '); 177 | exec(cmdStr, function(err, data) { 178 | if (err) { 179 | cb(err); 180 | } else { 181 | cb(null, data); 182 | } 183 | }); 184 | } 185 | 186 | }; 187 | 188 | module.exports = SvnClient; 189 | -------------------------------------------------------------------------------- /frontend/src/component/machine_list.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import { 3 | Button, 4 | Table, 5 | Modal, 6 | Row, 7 | Col, 8 | Input 9 | } from 'antd'; 10 | 11 | class MachineList extends Component { 12 | constructor(props) { 13 | super(props); 14 | this.state = { 15 | projectId: this.props.location.query.projectId, 16 | data: [], 17 | pagination: {}, 18 | loading: true, 19 | lockTitle: '提示', 20 | lockAction: '', 21 | showLockModal: false, 22 | lockInfo: '确认操作吗?', 23 | showDeletModal: false 24 | } 25 | } 26 | 27 | handleTableChange(pagination, filters, sorter) { 28 | const pager = this.state.pagination; 29 | pager.current = pagination.current; 30 | this.setState({pagination: pager}); 31 | } 32 | addMachine() { 33 | window.location.href = '#/new_machine?projectId=' + this.state.projectId; 34 | } 35 | handleMachineAction(e) { 36 | console.log(e); 37 | var self = this; 38 | const action = e.currentTarget.dataset.action; 39 | const id = e.currentTarget.dataset.id; 40 | if (action == 'edit') { 41 | window.location.href = '#/new_machine?action=edit&id=' + id+'&projectId=' + this.state.projectId; 42 | } else if (action == 'delete') { 43 | self.setState({showDeletModal: true, curDeleteId: id}) 44 | } else if (action == 'unlock') { 45 | self.setState({showLockModal: true, lockAction: 'unlock', lockInfo: '确认解锁该环境吗?', curLockId: id}) 46 | } else if (action == 'lock') { 47 | self.setState({showLockModal: true, lockAction: 'lock', lockInfo: '确认锁定该环境吗?', curLockId: id}) 48 | } 49 | 50 | } 51 | handleMachineLock() { 52 | const query = this.props.location.query; 53 | var isAdmin = query.admin; 54 | var self = this; 55 | self.setState({showLockModal: false}); 56 | if (self.state.curLockId) { 57 | let macInfo = {}; 58 | macInfo.id = self.state.curLockId; 59 | $.ajax({ 60 | url: '/home/machine/lock', 61 | type: 'POST', 62 | data: { 63 | //id:self.state.curLockId, 64 | machineArr: JSON.stringify([macInfo]), 65 | ac: self.state.lockAction, 66 | isAdmin: isAdmin 67 | }, 68 | success: function(res) { 69 | if (!res.errno) { 70 | let resDoat = res.data; 71 | self.setState({showLockModal: true, lockAction: null, lockInfo: res.data.msg, curLockId: null}) 72 | //window.location.reload(); 73 | } 74 | } 75 | }) 76 | } else { 77 | window.location.reload(); 78 | } 79 | } 80 | 81 | handleMachineDelete() { 82 | var self = this; 83 | if (self.state.curDeleteId) { 84 | $.ajax({ 85 | url: '/home/machine/delete', 86 | type: 'POST', 87 | data: { 88 | id: self.state.curDeleteId 89 | }, 90 | success: function(res) { 91 | if (!res.errno) { 92 | 93 | self.setState({showDeletModal: false, curDeleteId: null}) 94 | window.location.reload(); 95 | } 96 | } 97 | }) 98 | } 99 | } 100 | closeModal() { 101 | this.setState({showDeletModal: false}) 102 | } 103 | closeLockModal() { 104 | this.setState({showLockModal: false}); 105 | window.location.reload(); 106 | } 107 | fetch() { 108 | const self = this; 109 | $.ajax({ 110 | url: '/home/machine/project/id/' + self.state.projectId, 111 | type: 'GET', 112 | success: function(res) { 113 | if (res.errno == 0) { 114 | self.setState({data: res.data, loading: false}) 115 | } 116 | } 117 | }) 118 | } 119 | componentDidMount() { 120 | this.fetch(); 121 | } 122 | render() { 123 | const self = this; 124 | const columns = [ 125 | { 126 | title: '机器名', 127 | width: '100', 128 | dataIndex: 'name' 129 | }, { 130 | title: '项目名', 131 | width: '100', 132 | dataIndex: 'pro_name' 133 | }, { 134 | title: '执行任务', 135 | width: '100', 136 | dataIndex: 'task' 137 | }, { 138 | title: '机器ip', 139 | width: '100', 140 | dataIndex: 'ip' 141 | }, { 142 | title: '发布源目录', 143 | width: '300', 144 | dataIndex: 'sdir' 145 | }, { 146 | title: '发布目标目录', 147 | width: '300', 148 | dataIndex: 'dir' 149 | }, { 150 | title: 'ssh用户名', 151 | width: '100', 152 | dataIndex: 'ssh_user' 153 | }, { 154 | title: 'ssh密码', 155 | width: '100', 156 | dataIndex: 'ssh_pass' 157 | }, { 158 | title: '操作', 159 | width: '140', 160 | dataIndex: 'id', 161 | render(text, record) { 162 | return ( 163 | 164 | 编辑 165 | 166 | 删除 167 | 168 | ); 169 | } 170 | }, { 171 | title: '锁定状态', 172 | width: '200', 173 | dataIndex: 'is_lock', 174 | render(text, record) { 175 | if (text == 1) { 176 | return ( 177 | 178 | 被{record.lock_user}锁定 179 | 180 | 解锁 181 | 182 | ); 183 | } else { 184 | return ( 185 | 186 | 未锁定 187 | 188 | 锁定 189 | 190 | ); 191 | } 192 | 193 | } 194 | } 195 | ]; 196 | return ( 197 |
198 |
199 | 200 | 201 | 202 | 203 |
204 |
208 | 209 |

确认要删除该机器?

210 |
211 | 212 |

{this.state.lockInfo}

213 |
214 |
215 | ); 216 | } 217 | } 218 | 219 | export default MachineList; 220 | -------------------------------------------------------------------------------- /view/login/login_index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | H5发布系统 8 | 9 | 10 | 11 | 12 | 13 | 24 | 25 |
26 |
27 |
28 | 29 |
30 |
31 |
32 | 33 |
34 | 35 |
36 |
37 | 38 |
39 | 40 | 41 |
42 | 43 |
44 |
45 |
46 |
47 | 48 |
49 | 50 | 51 | 52 |
53 | 54 |
55 |
56 |
57 |
58 |
<%=data.msg%>
59 |
60 |
登录
61 |
注册
62 |
63 |
64 | 65 | 109 | 110 | 111 | 112 | 113 | 114 | 192 | 193 | -------------------------------------------------------------------------------- /db/db.sql: -------------------------------------------------------------------------------- 1 | -- MySQL dump 10.13 Distrib 5.6.22, for osx10.8 (x86_64) 2 | -- 3 | -- Host: localhost Database: wdfe_publish 4 | -- ------------------------------------------------------ 5 | -- Server version 5.6.22 6 | 7 | /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; 8 | /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; 9 | /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; 10 | /*!40101 SET NAMES utf8 */; 11 | /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; 12 | /*!40103 SET TIME_ZONE='+00:00' */; 13 | /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; 14 | /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; 15 | /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; 16 | /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; 17 | 18 | -- 19 | -- Table structure for table `cperson` 20 | -- 21 | 22 | DROP TABLE IF EXISTS `cperson`; 23 | /*!40101 SET @saved_cs_client = @@character_set_client */; 24 | /*!40101 SET character_set_client = utf8 */; 25 | CREATE TABLE `cperson` ( 26 | `id` int(11) NOT NULL AUTO_INCREMENT, 27 | `project_id` int(11) DEFAULT NULL, 28 | `name` varchar(100) DEFAULT NULL COMMENT 'charge person name unique', 29 | `add_time` varchar(20) DEFAULT NULL COMMENT 'add_time timestamp', 30 | `status` varchar(10) DEFAULT '0' COMMENT 'status', 31 | `email` varchar(100) DEFAULT NULL, 32 | PRIMARY KEY (`id`) 33 | ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8; 34 | /*!40101 SET character_set_client = @saved_cs_client */; 35 | 36 | -- 37 | -- Table structure for table `history` 38 | -- 39 | 40 | DROP TABLE IF EXISTS `history`; 41 | /*!40101 SET @saved_cs_client = @@character_set_client */; 42 | /*!40101 SET character_set_client = utf8 */; 43 | CREATE TABLE `history` ( 44 | `id` int(11) NOT NULL AUTO_INCREMENT, 45 | `project_id` int(11) unsigned NOT NULL, 46 | `user` varchar(50) DEFAULT NULL COMMENT 'creater', 47 | `ip` varchar(40) DEFAULT NULL COMMENT 'ip suit for ipv6', 48 | `pub_time` varchar(20) DEFAULT NULL COMMENT 'publish time eg:1452854398154', 49 | `pub_desc` varchar(2000) DEFAULT NULL COMMENT 'publish desc', 50 | `tag` varchar(15) DEFAULT NULL COMMENT 'eg:2016011500279', 51 | `build_log` varchar(2083) DEFAULT NULL COMMENT 'build_log', 52 | `deploy_log` varchar(2083) DEFAULT NULL COMMENT 'deploy_log', 53 | `status` varchar(10) DEFAULT '0', 54 | `machine_id` int(11) unsigned NOT NULL, 55 | `type` int(11) unsigned NOT NULL, 56 | PRIMARY KEY (`id`) 57 | ) ENGINE=InnoDB AUTO_INCREMENT=133 DEFAULT CHARSET=utf8; 58 | /*!40101 SET character_set_client = @saved_cs_client */; 59 | 60 | -- 61 | -- Table structure for table `machine` 62 | -- 63 | 64 | DROP TABLE IF EXISTS `machine`; 65 | /*!40101 SET @saved_cs_client = @@character_set_client */; 66 | /*!40101 SET character_set_client = utf8 */; 67 | CREATE TABLE `machine` ( 68 | `id` int(11) NOT NULL AUTO_INCREMENT, 69 | `name` varchar(100) DEFAULT 'machin name', 70 | `ip` varchar(40) DEFAULT NULL COMMENT 'ip suit for ipv6', 71 | `ssh_user` varchar(30) DEFAULT NULL COMMENT 'ssh_user', 72 | `ssh_pass` varchar(30) DEFAULT NULL COMMENT 'ssh_pass', 73 | `dir` varchar(1000) DEFAULT NULL, 74 | `sdir` varchar(1000) DEFAULT NULL, 75 | `project_id` int(11) DEFAULT NULL, 76 | `task` varchar(45) DEFAULT NULL, 77 | `type` int(11) unsigned NOT NULL, 78 | `lock_user` varchar(30) DEFAULT NULL COMMENT 'lock_user', 79 | `is_lock` int(11) DEFAULT '0', 80 | `op_env_id` int(11) DEFAULT '0' COMMENT 'op_env_id', 81 | `server_dir` varchar(1000) DEFAULT NULL, 82 | `after_deploy_shell` varchar(1000) DEFAULT NULL, 83 | `before_deploy_shell` varchar(1000) DEFAULT NULL, 84 | `deploy_hook` varchar(1000) DEFAULT NULL, 85 | `hook_params` varchar(1000) DEFAULT NULL, 86 | PRIMARY KEY (`id`) 87 | ) ENGINE=InnoDB AUTO_INCREMENT=34 DEFAULT CHARSET=utf8; 88 | /*!40101 SET character_set_client = @saved_cs_client */; 89 | 90 | -- 91 | -- Table structure for table `project` 92 | -- 93 | 94 | DROP TABLE IF EXISTS `project`; 95 | /*!40101 SET @saved_cs_client = @@character_set_client */; 96 | /*!40101 SET character_set_client = utf8 */; 97 | CREATE TABLE `project` ( 98 | `id` int(11) unsigned NOT NULL AUTO_INCREMENT, 99 | `name` varchar(50) DEFAULT NULL COMMENT 'project name unique', 100 | `creater` varchar(50) DEFAULT NULL COMMENT 'creater', 101 | `vcs_type` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT 'vcs type 0:svn 1.git , default svn', 102 | `code_url` varchar(2083) DEFAULT NULL COMMENT 'code url', 103 | `build_type` tinyint(3) unsigned DEFAULT '0' COMMENT 'build type :grunt gulp ... defalut grunt', 104 | `online_tag` varchar(20) DEFAULT NULL COMMENT 'online tag. eg:2016010800203', 105 | `last_tag` varchar(20) DEFAULT NULL COMMENT 'last tag. eg:2015122800202', 106 | `pub_time` varchar(20) DEFAULT NULL COMMENT 'pub_time timestamp', 107 | `status` varchar(10) DEFAULT '0' COMMENT 'status', 108 | `op_item_id` int(11) DEFAULT '0' COMMENT 'op_item_id', 109 | `op_item_name` varchar(10) DEFAULT '0' COMMENT 'op_item_name', 110 | `code_lang` varchar(50) DEFAULT 'javascript', 111 | `hook_params` varchar(1000) DEFAULT NULL, 112 | `deploy_hook` varchar(1000) DEFAULT NULL, 113 | `build_hook` varchar(1000) DEFAULT NULL, 114 | PRIMARY KEY (`id`), 115 | UNIQUE KEY `name` (`name`) 116 | ) ENGINE=InnoDB AUTO_INCREMENT=23 DEFAULT CHARSET=utf8; 117 | /*!40101 SET character_set_client = @saved_cs_client */; 118 | 119 | -- 120 | -- Table structure for table `pub_machines` 121 | -- 122 | 123 | DROP TABLE IF EXISTS `pub_machines`; 124 | /*!40101 SET @saved_cs_client = @@character_set_client */; 125 | /*!40101 SET character_set_client = utf8 */; 126 | CREATE TABLE `pub_machines` ( 127 | `id` int(11) NOT NULL AUTO_INCREMENT, 128 | `mid` int(11) NOT NULL, 129 | `pid` int(11) NOT NULL, 130 | `ip` varchar(40) DEFAULT NULL COMMENT 'ip suit for ipv6', 131 | `dir` varchar(512) DEFAULT NULL COMMENT 'dist dir', 132 | `sdir` varchar(512) DEFAULT NULL COMMENT 'source dir', 133 | `ssh_user` varchar(30) DEFAULT NULL COMMENT 'ssh_user', 134 | `ssh_pass` varchar(30) DEFAULT NULL COMMENT 'ssh_pass', 135 | `type` tinyint(3) unsigned NOT NULL DEFAULT '2' COMMENT 'machine type 0:pro 1:pre 2:test', 136 | `task_name` varchar(30) DEFAULT NULL COMMENT 'build task name. eg: test9', 137 | PRIMARY KEY (`id`), 138 | KEY `mid` (`mid`), 139 | CONSTRAINT `pub_machines_ibfk_1` FOREIGN KEY (`mid`) REFERENCES `machine` (`id`) 140 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 141 | /*!40101 SET character_set_client = @saved_cs_client */; 142 | 143 | -- 144 | -- Table structure for table `user` 145 | -- 146 | 147 | DROP TABLE IF EXISTS `user`; 148 | /*!40101 SET @saved_cs_client = @@character_set_client */; 149 | /*!40101 SET character_set_client = utf8 */; 150 | CREATE TABLE `user` ( 151 | `id` int(11) NOT NULL AUTO_INCREMENT, 152 | `name` varchar(50) DEFAULT NULL COMMENT 'creater max length 30 character & unique', 153 | `pass` varchar(30) DEFAULT NULL COMMENT 'pass max length 30 character', 154 | `avatar` varchar(512) DEFAULT NULL COMMENT 'avatar', 155 | PRIMARY KEY (`id`), 156 | UNIQUE KEY `name` (`name`) 157 | ) ENGINE=InnoDB AUTO_INCREMENT=20 DEFAULT CHARSET=utf8; 158 | /*!40101 SET character_set_client = @saved_cs_client */; 159 | /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; 160 | 161 | /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; 162 | /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; 163 | /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; 164 | /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; 165 | /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; 166 | /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; 167 | /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; 168 | 169 | -- Dump completed on 2017-02-23 23:58:18 170 | -------------------------------------------------------------------------------- /src/common/service/impl/git.js: -------------------------------------------------------------------------------- 1 | //从远程fetch 代码到本地, 这里使用 svn 的export进行获取代码 2 | import childProcessPro from 'child-process-es6-promise'; 3 | var exec = require('child_process').exec; 4 | var spawn = require('child_process').spawn; 5 | 6 | function GitClient(config) { 7 | this._userName = config.username; 8 | this._password = config.password; 9 | } 10 | 11 | GitClient.prototype = { 12 | getFileDiff: async function(codeUrl, lastTagUrl,commit,lastCommit,filename,cvsDir) { 13 | //git show 94323900d9f613772800b1387c808cc8e7c1432a src/components/h5index/question/operate.vue 14 | let cmd = 'git show '+commit+' '+filename; 15 | let changeLog = await childProcessPro.exec(cmd, { 16 | cwd: cvsDir, 17 | maxBuffer: 100 * 1024 * 1024 18 | }); 19 | return changeLog.stdout; 20 | }, 21 | getChangeLog: async function(codeUrl, lastTagUrl, lastTag, cvsDir) { 22 | //如果是tag或者分支部署 23 | let nowCode = 'HEAD'; 24 | if(codeUrl.indexOf('#') > 0){ 25 | nowCode = codeUrl.split('#')[1]; 26 | } 27 | let cmd = 'git log ' + lastTag + '..'+nowCode+' --date=format:"%Y-%m-%d\ %H:%M:%S" --name-status'; 28 | if (!lastTag || lastTag.length <= 1) { 29 | cmd = 'git log --date=format:"%Y-%m-%d\ %H:%M:%S" --name-status'; 30 | } 31 | 32 | let changeLog = await childProcessPro.exec(cmd, { 33 | cwd: cvsDir, 34 | maxBuffer: 100 * 1024 * 1024 35 | }); 36 | let reInfo = []; 37 | let temp = {}; 38 | //[{'author':'waynelu',chaninfo:[{'rev':'r2323','time':'2323131',chang}] 39 | let logArr = []; 40 | if (changeLog.stdout && changeLog.stdout.length > 3) { 41 | logArr = changeLog.stdout.split('\n\ncommit'); 42 | } 43 | 44 | for (let i = 0; i < logArr.length; i++) { 45 | let lineArray = logArr[i]; 46 | let infoArray = lineArray.split('\n\n'); 47 | 48 | let infoData = {}; 49 | let revInfoArray = infoArray[0].split('\n'); 50 | infoData.msg = infoArray[1]; 51 | infoData.rev = 'commit' + revInfoArray[0].replace('commit', ''); 52 | for (let x = 1; x < revInfoArray.length; x++) { 53 | let temStr = revInfoArray[x]; 54 | if (temStr.indexOf('Author:') >= 0) { 55 | infoData.author = temStr.replace('Author:', '').split(' ')[1]; 56 | } 57 | if (temStr.indexOf('Date:') >= 0) { 58 | infoData.time = temStr.replace('Date:', ''); 59 | } 60 | 61 | } 62 | 63 | 64 | if (infoArray[2]) { 65 | infoData.changeFilesLog = infoArray[2].split('\n'); 66 | } else { 67 | infoData.changeFilesLog = []; 68 | } 69 | 70 | if (!temp[infoData.author]) { 71 | let recInfo = { 72 | 'author': infoData.author, 73 | 'changeLog': [infoData] 74 | }; 75 | temp[infoData.author] = recInfo; 76 | reInfo.push(recInfo); 77 | } else { 78 | temp[infoData.author].changeLog.push(infoData); 79 | } 80 | } 81 | return reInfo; 82 | 83 | 84 | }, 85 | export: function(params, isBranch, cb) { 86 | //git clone --branch master git://git.somewhere destination_path 87 | var cmdStr = [ 88 | 'git clone' 89 | ]; 90 | let codeUrl = params[0]; 91 | let cvsDir = params[1]; 92 | let branch = ''; 93 | //如果是分支 94 | if (isBranch) { 95 | let urlArray = codeUrl.split('#'); 96 | branch = urlArray[1]; 97 | params[0] = urlArray[0]; 98 | } 99 | cmdStr = cmdStr.concat(params).join(' '); 100 | console.log(cmdStr); 101 | let cmdDir = params[1]; 102 | let self = this; 103 | exec(cmdStr, { 104 | cwd: cvsDir, 105 | maxBuffer: 100 * 1024 * 1024 106 | }, function(err, data, stderr) { 107 | // self.delGitDir(cmdDir,function(){ 108 | console.log('git checkout stderr: ', stderr); 109 | if (err) { 110 | cb(err); 111 | } else { 112 | if (isBranch) { 113 | exec('git checkout ' + branch, { 114 | cwd: cvsDir, 115 | maxBuffer: 100 * 1024 * 1024 116 | }, function(err2, data2, stderr2) { 117 | if (err2) { 118 | cb(err2); 119 | } else { 120 | cb(null, stderr + '\n' + stderr2); 121 | } 122 | }) 123 | } else { 124 | cb(null, stderr); 125 | } 126 | 127 | } 128 | //}); 129 | 130 | }); 131 | }, 132 | getLastTagUrl: function(trunkUrl, pid, lastTag, isBranch) { 133 | let reUrl = trunkUrl; 134 | if (isBranch) { 135 | let urlArray = trunkUrl.split('#'); 136 | if (urlArray.length >= 2) { 137 | reUrl = urlArray[0]; 138 | } 139 | } 140 | return reUrl; 141 | }, 142 | /** 143 | * 兼容ssh方式和http方式的tag checkout 144 | * @param params 145 | * @param tag 146 | * @param dir 147 | * @param cb 148 | */ 149 | exportTag: function(codeUrl, cvsDir, newV, callback) { 150 | //git clone --branch master git://git.somewhere destination_path 151 | let cmdStr = 'git clone ' + codeUrl + ' ' + cvsDir; 152 | console.log('export Tag:' + cmdStr); 153 | exec(cmdStr, function(err, data) { 154 | if (err) { 155 | callback(err); 156 | } else { 157 | exec('git checkout ' + newV, { 158 | cwd: cvsDir 159 | }, function(err2, data2) { 160 | if (err2) { 161 | callback(err2); 162 | } else { 163 | callback(null, data); 164 | } 165 | }) 166 | } 167 | }); 168 | }, 169 | /** 170 | * git配置 171 | * @param cmdDir git 仓库所在路径 172 | * @param email 用户邮箱 备用 173 | * @param name 用户名 备用 174 | * @param callback 回调函数 175 | */ 176 | config: function(cmdDir, callback, email, name) { 177 | var userEmail = email ? email : ''; 178 | var userName = name ? name : ''; 179 | var cmdStr = [ 180 | 'git config', 181 | 'user.email', 182 | userEmail, 183 | '&&', 184 | 'git config', 185 | 'user.name', 186 | userName 187 | ]; 188 | console.log('cmdDir:' + cmdDir); 189 | cmdStr = cmdStr.join(' '); 190 | exec(cmdStr, { 191 | cwd: cmdDir 192 | }, function(err, data) { 193 | 194 | if (err) { 195 | callback(err); 196 | } else { 197 | callback(null, data); 198 | } 199 | }); 200 | }, 201 | /** 202 | * 将现有代码打上tag 203 | * git tag -a 20213123123131 -m 'tag test' 204 | * @param params 205 | * @param cmdDir 命令路径 206 | * @param callback 207 | */ 208 | copyTag: function(codeUrl, proId, newV, cvsDir, isInc, homeDir, callback) { 209 | 210 | // var cmdStr = [ 211 | // 'git tag' 212 | // ]; 213 | // cmdStr.push(newV);//版本号 214 | // cmdStr = cmdStr.concat(params); 215 | // cmdStr = cmdStr.join(' '); 216 | let tagMsg = '"App build: proId-' + proId + ', proVersion-' + newV + '"'; 217 | let cmdStr = 'git add .;git commit -a -m "inc commit tag";git tag -a ' + newV + ' -m ' + tagMsg + ';git push --tags'; 218 | //console.log('===========================' + cmdDir + cmdStr + '====================/n/n'); 219 | 220 | exec(cmdStr, { 221 | cwd: cvsDir 222 | }, function(err, data) { 223 | if (err) { 224 | callback(err); 225 | } else { 226 | var pushCmdStr = 'git push origin ' + newV; 227 | exec(pushCmdStr, { 228 | cwd: cvsDir 229 | }, function(err, data) { 230 | callback(null, data); 231 | }); 232 | } 233 | }); 234 | }, 235 | /** 236 | * 删除.git 文件夹 237 | * @param cmdDir 238 | */ 239 | delGitDir: function(cmdDir, callback) { 240 | //' ./server/temp/temp_dir_' + pro.id + '/.git' 241 | exec('rm -rf ' + cmdDir + '/.git', function(err, data) { 242 | if (err) { 243 | callback(err); 244 | } else { 245 | callback(null, data); 246 | } 247 | }); 248 | } 249 | 250 | }; 251 | 252 | module.exports = GitClient; 253 | --------------------------------------------------------------------------------