├── 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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
32 |
33 |
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 |
--------------------------------------------------------------------------------