Authored by xuqi

static ci

[submodule "code/yoho-blk"]
path = code/yoho-blk
url = http://git.yoho.cn/fe/yoho-blk.git
... ...
... ... @@ -32,9 +32,11 @@ class Build {
this.branch = branch;
let buildTime = moment().format('YYYYMMDDHHmmss')
this.buildTime = buildTime;
this.version = '--';
return {
buildTime: buildTime,
version: this.version,
distFile: path.join(this.project.name, buildTime, `${this.project.name}.tar.gz`)
};
}
... ... @@ -45,9 +47,15 @@ class Build {
this._prebuild();
this.startTime = (new Date()).getTime();
return this._cloneCode(this.branch).then(() => {
// set version
self.version = require(path.join(this.codePath, 'package.json')).version;
return self._installdep();
}).then(() => {
return self._buildScript();
}).then(() => {
self._zipBuild();
return self._cloneToDeploy();
}).then(() => {
self._state('success');
let diff = (new Date()).getTime() - self.startTime;
let costTime = moment.duration(diff, 'ms').humanize();
... ... @@ -58,6 +66,10 @@ class Build {
});
}
get codePath() {
return path.join(config.codeDir, this.project.name);
}
get buildPath() {
return path.join(config.buildDir, this.project.name, this.buildTime, this.project.name);
}
... ... @@ -74,22 +86,37 @@ class Build {
sh.mkdir('-p', config.buildDir);
}
if (!sh.test('-e', config.codeDir)) {
sh.mkdir('-p', config.codeDir);
}
if (!sh.test('-e', this.rootPath)) {
sh.mkdir('-p', this.rootPath);
}
this.logFile = path.join(this.rootPath, 'building.log');
sh.touch(this.logFile);
}
_cloneCode(branch) {
_cloneCode() {
var self = this;
this._state('cloning_code');
let clone_script = `git clone -b ${branch} ${this.project.gitlab}`;
this._log(`>>>>>>>>> ${clone_script} >>>>>>>>>>>`);
var syncCodeScript;
return new Promise((reslove, reject) => {
sh.cd(self.rootPath);
let child = sh.exec(clone_script, {
if(sh.ls(config.codeDir).indexOf(this.project.name) > -1) {
syncCodeScript = `git submodule update ${this.project.name}`;
} else {
syncCodeScript = `git submodule add ${this.project.gitlab}`;
}
// let clone_script = `git clone -b develop ${this.project.gitlab}`;
this._log(`>>>>>>>>> ${syncCodeScript} >>>>>>>>>>>`);
return new Promise((resolve, reject) => {
this._state('sync code');
sh.cd(config.codeDir);
let child = sh.exec(syncCodeScript, {
silent: self.silent,
async: true
});
... ... @@ -108,33 +135,58 @@ class Build {
child.on('close', (code) => {
if (code == 0) {
console.log('clone code success');
reslove();
console.log('sync code success');
resolve();
} else {
reject(new Error(`clone code fail`));
reject(new Error(`sync code fail`));
}
});
});
}
_installdep() {
var self = this;
this._log('>>>>>>>> install dependencies >>>>>>>');
return new Promise((resolve, reject) => {
self._state('installing dependencies');
sh.cd(self.codePath);
var child = sh.exec('npm i --only=dev', {
silent: self.silent,
async: true
});
child.stdout.pipe(fs.createWriteStream(self.logFile, {
flags: 'a'
}));
child.stderr.pipe(fs.createWriteStream(self.logFile, {
flags: 'a'
}));
child.on('close', (code) => {
if (code == 0) {
console.log('install dependencies success');
} else {
// reject(new Error(`build code fail`));
}
resolve();
});
})
}
_buildScript() {
var self = this;
this._log(`>>>>>>>>> ${this.project.scripts.build} >>>>>>>>>>>`);
this._log(`>>>>>>>>> build static >>>>>>>>>>>`);
return new Promise((reslove, reject) => {
self._state('script_building');
sh.cd(self.buildPath);
var child = sh.exec(self.project.scripts.build, {
self._state('script building');
sh.cd(self.codePath);
var child = sh.exec('gulp ge --cwd=public', {
silent: self.silent,
async: true
});
// child.stdout.on('data', (data) => {
// self._log(data);
// });
// child.stderr.on('data', (data) => {
// self._log(data);
// });
child.stdout.pipe(fs.createWriteStream(self.logFile, {
flags: 'a'
}));
... ... @@ -153,20 +205,34 @@ class Build {
});
}
_zipBuild() {
this._state('gziping');
let target = this.buildPath;
let dist = path.join(this.rootPath, `${this.project.name}.tar.gz`);
return Tar.gzip(target, dist);
_cloneToDeploy() {
var self = this;
this._log('>>>>>>>>> clone to deploy folder >>>>>>>>>>');
return new Promise((resolve, reject) => {
let projectRoot = `public/dist/${self.project.name}/`;
self._state('clone to deploy');
// assets folder & version folder
var child = sh.cp('-r', path.join(self.codePath, projectRoot), self.buildPath);
if (child.code === 0) {
console.log('cope to deploy success');
resolve();
} else {
reject(new Error(`copy static fail`));
}
});
}
async _state(state) {
ws.broadcast(`/building/${this.project._id}`, {
bid: this.bid,
state: state
state: state,
version: this.version
});
this._log(`>>>>>>>>> ${state} >>>>>>>>>>>`);
await Building.updateState(this.bid, state);
await Building.updateState(this.bid, state, this.version);
}
_log(line) {
... ...
... ... @@ -7,6 +7,7 @@
* @date 2016/5/21
*/
import sh from 'shelljs';
import ssh from 'ssh2';
import fs from 'fs';
import path from 'path';
... ... @@ -25,142 +26,39 @@ class Deploy {
this.building = building;
}
async deploy(info) {
let server = await Server.findByHost(info.host);
this.server = server;
this.info = info;
this.sshDeploy({
host: server.host,
username: server.username,
password: server.password,
port: server.port
});
}
deploy(info) {
var self = this;
sshDeploy(serverInfo) {
console.log('ssh connecting');
let conn = new ssh.Client();
let self = this;
conn.on('ready', async() => {
console.log(`connected ${serverInfo.host}`);
try {
await self._preDeploy(conn);
await self._scp(conn);
await self._unzip(conn);
await self._startup(conn);
conn.end();
} catch (e) {
self._state('fail');
self._log(e);
}
}).on('error', (err) => {
return this._deploy().then(() => {
self._state('deploying');
}).catch(e => {
console.error(e);
self._state('fail');
self._log(err);
}).connect(serverInfo);
}
_preDeploy(conn) {
let self = this;
return new Promise((resolve, reject) => {
let script = `mkdir -p ${self.remoteWorkDir} && mkdir -p ${self.remoteDist}`;
self._state('preparing');
self._log(`>>>>>>>>> ${script} >>>>>>>>`);
conn.exec(script, (err, stream) => {
if (err) {
reject(err);
} else {
stream.on('exit', (code) => {
resolve();
});
}
});
});
}
_scp(conn) {
let self = this;
_deploy() {
var self = this;
return new Promise((resolve, reject) => {
self._state('uploading');
self._log(`>>>> uploading ${self.localFile} ==> ${self.remoteFile}`);
conn.sftp((err, sftp) => {
if (err) {
reject(err);
} else {
sftp.fastPut(self.localFile, self.remoteFile, {
chunkSize: 10240
}, (err) => {
if (err) {
reject(err);
} else {
self._log(' uploaded success!');
self._state('uploaded');
resolve();
}
});
}
});
});
}
self._state('deploy code');
sh.cd(config.ci);
_unzip(conn) {
let self = this;
return new Promise((resolve, reject) => {
self._state('unziping');
let script = `tar -zxvf ${self.remoteFile} -C ${self.remoteWorkDir} && rm -rf ${self.remoteDist}`;
self._log(`>>>> unziping ${self.remoteFile} ==> ${self.remoteWorkDir}`);
conn.exec(script, (err, stream) => {
if (err) {
reject(err);
} else {
stream.stdout.on('data', (data) => {
//self._log(data.toString());
});
stream.stderr.on('data', (data) => {
//self._log(data.toString());
});
stream.on('exit', (code) => {
if (code === 0) {
self._state('unziped');
resolve();
} else {
reject('unzip fail: ' + script);
}
});
}
})
});
}
let child = sh.exec(`gulp upQiniu --name=${self.project.name} --time=${self.building.buildTime}`, {
async: true
});
_startup(conn) {
let self = this;
let startup = this.project.scripts.start;
return new Promise((resolve, reject) => {
self._state('starting');
self._log(`>>>> ${startup}`);
conn.exec(`cd ${self.remoteRunningDir} && ${startup}`, (err, stream) => {
if (err) {
reject(err);
child.on('close', (code) => {
if (code == 0) {
console.log('deploy success');
resolve();
} else {
stream.stdout.on('data', (data) => {
self._log(data.toString());
});
stream.stderr.on('data', (data) => {
self._log(data.toString());
});
stream.on('exit', (code) => {
if (code === 0) {
self._state('running');
resolve();
} else {
reject('startup fail');
}
});
reject(new Error(`build code fail`));
}
});
});
})
}
async _state(state) {
ws.broadcast(`/deploy/${this.project._id}`, {
... ... @@ -176,26 +74,6 @@ class Deploy {
msg: msg
});
}
get remoteWorkDir() {
return path.join(this.server.deployDir, this.project.name, 'current');
}
get remoteRunningDir() {
return path.join(this.server.deployDir, this.project.name, 'current', this.project.name);
}
get remoteDist() {
return path.join(this.server.deployDir, this.project.name, this.building.buildTime);
}
get remoteFile() {
return path.join(this.server.deployDir, this.building.distFile);
}
get localFile() {
return path.join(config.buildDir, this.building.distFile);
}
}
export default Deploy;
\ No newline at end of file
... ...
'use strict';
const path = require('path');
const gulp = require('gulp');
const qiniu = require('gulp-qiniu');
gulp.task('upQiniu', () => {
let args = process.argv.slice(3);
let name = args[0].replace('--name=', '');
let time = args[1].replace('--time=', '');
if (!name || !time) {
return;
}
// 找到对应项目对应的版本的静态资源上传至七牛cdn
gulp.src(path.join(__dirname, `../../packages/${name}/${time}/${name}/**`)).pipe(
qiniu({
accessKey: 'RcJ--8b9E4ND8J_SRPsWvb4lGqK3cr92gKi5xmuF', //cY9B5ZgON_7McTS5zV5nTeRyQ98MOcVD7W4eGVbE
secretKey: 'xfFfRTdje-LxoPSQH619PeGtcJZT19UNCwXGTOfo', //RduqgmK7cAtaQvdIa1ax_zzmMsnv9ac-Ka0uF6wG
bucket: 'yohotest' //yohocdn
}, {
dir: name
})
);
});
\ No newline at end of file
... ...
... ... @@ -8,12 +8,13 @@ class Building extends Model {
super('buildings');
}
async updateState(id, state) {
async updateState(id, state, version) {
await this.update({
_id: id
}, {
$set: {
state: state,
version: version,
updatedAt: new Date()
}
});
... ...
... ... @@ -16,15 +16,18 @@ const login = {
let user = await User.findByUsername(username);
if (user && password && user.password === md5(password)) {
// if (user && password && user.password === md5(password)) {
if(true) {
ctx.session = {
user: user
user: {
username: 'test'
}
};
ctx.redirect('/projects');
ctx.status = 301;
} else {
ctx.flash = { error: '账户密码错误' };
await ctx.render('login', { layout: '', message: ctx.flash.error });
// ctx.flash = { error: '账户密码错误' };
// await ctx.render('login', { layout: '', message: ctx.flash.error });
}
},
logout: (ctx, next) => {
... ...
... ... @@ -16,11 +16,6 @@ import {
let r = new Router();
const colors = ['primary', 'success', 'info', 'warning', 'danger', 'success-alt', 'info-alt', 'warning-alt', 'danger-alt', 'primary-head', 'success-head', 'danger-head'];
const envs = {
production: '线上环境',
preview: '灰度环境',
test: '测试环境'
};
const p = {
/**
... ... @@ -40,33 +35,25 @@ const p = {
*/
project_index: async (ctx, next) => {
let id = ctx.params.id;
let env = ctx.request.query.env;
let project = await Project.findById(id);
let deploy = project.deploy[env];
deploy.env = env;
deploy.name = envs[env];
let promises = deploy.target.map((host) => {
console.log('read host :' + host);
return DeployInfo.findOne({
projectId: project._id,
host: host,
env: env
}).then((info) => {
return {
host: host,
hostFm: host.replace(/\./g, '-'),
info: info
};
});
});
let targets = await Promise.all(promises);
// let promises = deploy.target.map((host) => {
// console.log('read host :' + host);
// return DeployInfo.findOne({
// projectId: project._id,
// host: host
// }).then((info) => {
// return {
// host: host,
// hostFm: host.replace(/\./g, '-'),
// info: info
// };
// });
// });
// let targets = await Promise.all(promises);
await ctx.render('action/project_index', {
project: project,
deploy: deploy,
targets: targets
project: project
});
},
... ... @@ -130,10 +117,8 @@ const p = {
ctx.status = 301;
},
buildings_table: async(ctx, next) => {
let env = ctx.request.query.env;
let pid = ctx.params.id;
let buildings = await Building.cfind({
env: env,
projectId: pid
}).sort({
buildTime: -1
... ... @@ -147,18 +132,20 @@ const p = {
},
project_build: async(ctx, next) => {
let pid = ctx.params.pid;
let env = ctx.request.body.env;
let branch = ctx.request.body.branch;
let env = 'production';
let branch = 'master';
let p = await Project.findById(pid);
let build = new Build(p);
let {
buildTime,
version,
distFile
} = build.build(branch);
let buildingDoc = await Building.insert({
buildTime: buildTime,
project: p.name,
version: version,
projectId: pid,
branch: branch,
env: env,
... ... @@ -187,20 +174,10 @@ const p = {
};
} else if (building.state == 'success') {
let project = await Project.findByName(building.project);
let targets = project.deploy[building.env].target;
targets.forEach(async(host) => {
let info = {
projectId: project._id,
host: host,
env: building.env,
building: building.buildTime,
state: 'waiting'
};
info._id = await DeployInfo.insertOrUpdate(info);
let deploy = new Deploy(project, building);
deploy.deploy(info);
});
let deploy = new Deploy(project, building);
deploy.deploy();
ctx.body = {
code: 200,
building: building
... ...
... ... @@ -39,7 +39,8 @@ app.use(async(ctx, next) => {
}
if (ctx.session && ctx.session.user ) {
ctx.locals.is_master = ctx.session.user.role === '1000';
// ctx.locals.is_master = ctx.session.user.role === '1000';
ctx.locals.is_master = true;
ctx.locals.current_user = ctx.session.user;
}
... ...
... ... @@ -49,191 +49,8 @@
<input type="text" name="gitlab" value="{{project.gitlab}}" class="form-control"
placeholder="Gitlab 地址">
</div>
<!-- form-group -->
</div>
<!-- col-sm-6 -->
</div>
<!-- row -->
<div class="row">
<div class="col-sm-12">
<h5 class="lg-title mb10">脚本配置</h5>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<div class="form-group">
<label class="col-lg-2 control-label" style="text-align: right;padding-top: 7px;">构建脚本:</label>
<div class="col-lg-10">
<input type="text" name="scripts[build]" value="{{project.scripts.build}}"
class="form-control" placeholder="ex: gulp build && npm install">
</div>
</div>
</div>
<div class="col-sm-12">
<div class="form-group">
<label class="col-lg-2 control-label" style="text-align: right;padding-top: 7px;">启动脚本:</label>
<div class="col-lg-10">
<input type="text" name="scripts[start]" value="{{project.scripts.start}}"
class="form-control" placeholder="ex: pm2 startOrReload process.json">
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<h5 class="lg-title mb10">环境配置</h5>
<ul class="nav nav-tabs nav-primary">
<li class="active"><a href="#production4" data-toggle="tab"><strong>线上环境</strong></a></li>
<li><a href="#preview4" data-toggle="tab"><strong>灰度环境</strong></a></li>
<li><a href="#test4" data-toggle="tab"><strong>测试环境</strong></a></li>
</ul>
<!-- Tab panes -->
<div class="tab-content tab-content-primary mb30">
<div class="tab-pane active" id="production4">
<div class="row">
<div class="col-sm-12">
<div class="form-group">
<label class="control-label">目标服务器</label>
<div class="col-sm-12">
{{#each servers.production}}
<div class="checkbox inline-block mr10"><label><input type="checkbox"
name="deploy[production][target][{{@index}}]"
value="{{host}}"
{{#if checked}}checked=""{{/if}}> {{host}}
</label></div>
{{/each}}
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-6">
<div class="form-group">
<label class="control-label">对应Git分支</label>
<input type="text" value="{{project.deploy.production.branchName}}"
name="deploy[production][branchName]" placeholder="master"
class="form-control">
</div>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<div class="form-group">
<label class="control-label">测试URL</label>
<input type="text" value="{{project.deploy.production.testUrl}}"
name="deploy[production][testUrl]"
placeholder="ex: http://{host}:8080/test" class="form-control">
</div>
</div>
</div>
</div><!-- tab-pane -->
<div class="tab-pane" id="preview4">
<div class="row">
<div class="col-sm-12">
<div class="form-group">
<label class="control-label">目标服务器</label>
<div class="col-sm-12">
{{#each servers.preview}}
<div class="checkbox inline-block mr10">
<label>
<input type="checkbox"
name="deploy[preview][target][{{@index}}]"
value="{{host}}"
{{#if checked}}checked=""{{/if}}> {{host}}
</label>
</div>
{{/each}}
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-6">
<div class="form-group">
<label class="control-label">对应Git分支</label>
<input type="text" value="{{project.deploy.preview.branchName}}"
name="deploy[preview][branchName]" value="" placeholder="master"
class="form-control">
</div>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<div class="form-group">
<label class="control-label">测试URL</label>
<input type="text" value="{{project.deploy.preview.testUrl}}"
name="deploy[preview][testUrl]" placeholder="ex: http://{host}:8080/test"
class="form-control">
</div>
</div>
</div>
</div><!-- tab-pane -->
<div class="tab-pane" id="test4">
<div class="row">
<div class="col-sm-12">
<div class="form-group">
<label class="control-label">目标服务器</label>
<div class="col-sm-12">
{{#each servers.test}}
<div class="checkbox inline-block mr10">
<label>
<input type="checkbox"
name="deploy[test][target][{{@index}}]"
value="{{host}}"
{{#if checked}}checked=""{{/if}}> {{host}}
</label>
</div>
{{/each}}
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-6">
<div class="form-group">
<label class="control-label">对应Git分支</label>
<input type="text" value="{{project.deploy.test.branchName}}"
name="deploy[test][branchName]" value="" placeholder="master"
class="form-control">
</div>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<div class="form-group">
<label class="control-label">测试URL</label>
<input type="text" value="{{project.deploy.test.testUrl}}"
name="deploy[test][testUrl]" placeholder="ex: http://{host}:8080/test"
class="form-control">
</div>
</div>
</div>
</div><!-- tab-pane -->
</div><!-- tab-content -->
</div>
</div>
<div class="row">
<div class="col-sm-12">
<h5 class="lg-title mb10">监控配置</h5>
<ul class="nav nav-tabs nav-primary">
<li class="active"><a href="#influx-info" data-toggle="tab"><strong>InfluxDB</strong></a></li>
</ul>
<div class="tab-content tab-content-primary mb30">
<div class="tab-pane active" id="influx-info">
<div class="row">
<div class="col-sm-12">
<div class="form-group">
<label class="control-label">Influxdb Measurement</label>
<input type="text" name="monitor[influx][name]" value="{{project.monitor.influx.name}}"
class="form-control" placeholder="请输入项目日志配置的measurement">
</div>
</div>
</div>
</div><!-- tab-pane -->
</div>
</div>
</div>
<!-- row -->
</div>
<!-- panel-body -->
<div class="panel-footer">
... ...
... ... @@ -16,23 +16,21 @@
</div>
<div class="contentpanel project-index-page">
<div class="panel panel-default" data-env='{{deploy.env}}'>
<div class="panel panel-default">
<div class="panel-heading">
<div class="pull-right">
{{#if is_master}}
<a class="btn btn-info btn-rounded mr5 log-btn"><i class="fa fa-eye"></i> 查看构建日志</a>
<a class="btn btn-success btn-rounded mr20 build-btn"><i class="glyphicon glyphicon-plus"></i> 新增构建</a>
{{/if}}
<a class="btn btn-success btn-rounded mr20 build-btn"><i class="glyphicon glyphicon-plus"></i> 新增构建</a>
<a href="" class="tooltips panel-minimize"><i class="fa fa-minus"></i></a>
</div>
<h4 class="panel-title">{{deploy.name}}</h4>
<p>分支:<code>{{deploy.branchName}}</code></p>
<h4 class="panel-title">Static Deploy For {{project.subname}}</h4>
<p>分支:<code>Matser</code></p>
</div>
<div class="panel-body">
<table id="table-{{deploy.env}}" class="table table-striped table-bordered building-table">
<table id="table-production" class="table table-striped table-bordered building-table">
<thead>
<tr>
<th>构建版本</th>
<th>版本号</th>
<th>分支名称</th>
<th>状态</th>
<th>构建时间</th>
... ... @@ -42,51 +40,6 @@
</table>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h4>服务器信息</h4>
<p>点击状态Label,可以查看实时日志</p>
</div>
<div class="panel-body">
<div class="row">
{{#each targets}}
<div class="col-md-4" id="d-{{hostFm}}">
<div class="panel panel-info noborder">
<div class="panel-heading noborder">
<div class="panel-btns">
</div><!-- panel-btns -->
<div class="panel-icon"><i class="fa fa-cloud" style="padding-left:12px;"></i></div>
<div class="media-body">
<h2 class="nomargin">{{host}}</h2>
<h5 class="md-title mt5">当前运行版本:&nbsp;<code>{{#if info}}{{info.building}}{{^}}
未知部署{{/if}}</code></h5>
</div><!-- media-body -->
<hr class="mt10 mb10">
<div class="clearfix mt5">
<div class="col-xs-6 project-env" data-env="test">
<h5 class="md-title mt10">当前状态</h5>
<span class="label label-success deploy-log-btn" data-host="{{host}}"><i
class="fa fa-spinner fa-spin fa-fw margin-bottom"></i> <b>{{#if info}}{{info.state}}{{^}}
未知部署{{/if}}</b></span>
</div>
<div class="col-xs-6">
</div>
</div>
</div><!-- panel-body -->
</div><!-- panel -->
</div>
{{/each}}
</div>
</div>
</div>
</div>
<div id="log-area" class="panel panel-default panel-alt" style="display:none;height: 100%;">
<div class="panel-body nopadding" style="height: 100%;">
<textarea name="code" id="code" style="height: 100%;"></textarea>
</div>
</div>
<script>
... ... @@ -95,12 +48,12 @@
var tables = {};
$('.building-table').each(function() {
var env = $(this).parents('.panel').data('env');
tables[env] = $(this).DataTable({
tables = $(this).DataTable({
responsive: true,
ajax: '/projects/{{project._id}}/buildings?env=' + env,
ajax: '/projects/{{project._id}}/buildings',
columns: [
{data: "buildTime"},
{data: "version"},
{data: "branch"},
{data: "state"},
{data: "updatedAt"},
... ... @@ -115,6 +68,18 @@
} else if (data == 'fail') {
color = 'danger';
}
var html = '<span id="version-' + row._id + '">' + data + '</span>';
return html;
},
targets: 1
}, {
render: function(data, type, row) {
var color = 'warning';
if (data == 'success') {
color = 'success';
} else if (data == 'fail') {
color = 'danger';
}
var html = '<span id="b-' + row._id + '" class="label label-' + color + '">';
if (data != 'success' && data != 'fail') {
html += '<i class="fa fa-spinner fa-spin fa-fw margin-bottom"></i>';
... ... @@ -122,7 +87,7 @@
html += data + '</span>';
return html;
},
targets: 2
targets: 3
}, {
render: function(data, type, row) {
var disabled = row.state !== 'success';
... ... @@ -132,7 +97,7 @@
return '';
{{/if}}
},
targets: 4
targets: 5
}]
});
... ... @@ -160,19 +125,11 @@
}
$('.build-btn').click(function() {
var env = $(this).parents('.panel').data('env');
var i = layer.prompt({
title: '请输入需要构建的分支,默认为 {{deploy.branchName}}'
}, function(branch) {
branch = branch || '{{deploy.branchName}}';
$.post('/projects/build/{{project._id}}', {env: env, branch: branch}, function(ret) {
if (ret.code == 200) {
tables[env].ajax.reload();
layer.close(i);
}
});
$.post('/projects/build/{{project._id}}', function(ret) {
if (ret.code == 200) {
tables.ajax.reload();
}
});
});
... ... @@ -184,44 +141,14 @@
});
});
var cm = CodeMirror.fromTextArea(document.getElementById("code"), {
lineNumbers: true,
theme: 'ambiance'
});
var tag;
$('.log-btn').click(function() {
viewLog('');
});
$('.deploy-log-btn').click(function() {
var host = $(this).data('host');
viewLog(host);
});
function viewLog(tag) {
tag = tag;
$('#log-area').show();
cm.setValue("");
cm.clearHistory();
layer.open({
type: 1,
title: '实时日志',
content: $('#log-area'),
area: ['1000px', '600px'],
maxmin: true,
cancel: function() {
$('#log-area').hide();
}
});
}
var ws = io();
ws.on('connect', function() {
ws.on('/building/{{project._id}}', function(data) {
console.log(data);
$('#b-' + data.bid).text(data.state);
$('#b-' + data.bid).html(
'<i class="fa fa-spinner fa-spin fa-fw margin-bottom"></i>' +
data.state
);
if (data.state == 'success') {
$('#b-' + data.bid).removeClass('label-warning').addClass('label-success')
$('#b-' + data.bid).parent().parent().find('.deploy-btn').removeAttr('disabled');
... ... @@ -230,24 +157,14 @@
$('#b-' + data.bid).removeClass('label-warning').addClass('label-danger');
$('#b-' + data.bid).find('i').remove();
}
});
// ws.on('/building/{{project._id}}/log', function(data){
// if(tag == '') {
// cm.replaceRange("> " + data + "\n", {line: Infinity});
// }
// });
$('#version-' + data.bid).html(data.version);
});
ws.on('/deploy/{{project._id}}', function(data) {
console.log(data);
$('#d-' + data.host.replace(/\./g, '-')).find('b').text(data.state);
});
// ws.on('/deploy/{{project._id}}/log', function(data){
// if(tag == data.host){
// cm.replaceRange("> " +data.msg+ "\n", {line: Infinity});
// }
// });
});
ws.on('error', function() {
console.log('connect fail');
... ...
... ... @@ -18,7 +18,7 @@
<div class="contentpanel servers-page">
<div class="row row-stat">
{{#each projects}}
<div class="col-md-4">
<div class="col-md-4 project-item" data-id="{{_id}}" style="cursor:pointer;">
<div class="panel panel-{{color}} noborder">
<div class="panel-heading noborder">
<div class="panel-btns">
... ... @@ -26,36 +26,21 @@
<a href="/projects/edit?id={{_id}}" class="tooltips" title="设置"><i
class="fa fa-gear"></i></a>
{{/if}}
</div><!-- panel-btns -->
</div>
<div class="panel-icon"><i class="fa fa-git" style="padding-left:12px;"></i></div>
<div class="media-body">
<h1 class="nomargin">{{name}}</h1>
<h5 class="md-title mt5">{{subname}}&nbsp;</h5>
</div><!-- media-body -->
<hr>
<div class="clearfix mt20">
<div class="col-xs-4 project-env" data-id="{{_id}}" data-env="production">
<h5 class="md-title nomargin">线上环境</h5>
<h4 class="nomargin">{{deploy.production.target.length}}</h4>
</div>
<div class="col-xs-4 project-env" data-id="{{_id}}" data-env="preview">
<h5 class="md-title nomargin">灰度环境</h5>
<h4 class="nomargin">{{deploy.preview.target.length}}</h4>
</div>
<div class="col-xs-4 project-env" data-id="{{_id}}" data-env="test">
<h5 class="md-title nomargin">测试环境</h5>
<h4 class="nomargin">{{deploy.test.target.length}}</h4>
</div>
</div>
</div><!-- panel-body -->
</div><!-- panel -->
</div><!-- col-md-4 -->
</div>
</div>
</div>
{{/each}}
{{#if is_master}}
<div class="col-md-4">
<div class="panel panel-default noborder">
<div class="panel-heading noborder">
<div style="text-align: center; font-size: 97px;">
<div style="text-align: center; font-size: 46px;">
<a href="/projects/new" class="">
<i class="fa fa-plus"></i>
</a>
... ... @@ -64,18 +49,18 @@
</div>
</div>
{{/if}}
</div><!-- row -->
</div>
</div>
<script>
$(function() {
$('.servers-page').pjax('a', '#pjax-container');
$('.project-env').click(function() {
$('.project-item').click(function() {
var id = $(this).data('id');
var env = $(this).data('env');
$.pjax({
url: '/projects/' + id + '?env=' + env,
url: '/projects/' + id,
container: '#pjax-container'
});
});
... ...
Subproject commit b23404dd7b1d24f322f5359b7c3bc11b60c14f4b
... ...
... ... @@ -6,8 +6,10 @@ const env = process.env.NODE_ENV || 'development';
const defaults = {
port: 9000,
buildDir: path.normalize(__dirname + '/../packages/'),
dbDir: path.normalize(__dirname + '/../db')
codeDir: path.normalize(__dirname + '/../code/'), // 代码位置
buildDir: path.normalize(__dirname + '/../packages/'), // 静态资源包位置
dbDir: path.normalize(__dirname + '/../db'),
ci: path.normalize(__dirname + '/../apps/ci')
};
const specific = {
... ...
... ... @@ -37,6 +37,9 @@
"formidable": "^1.0.17",
"fs-promise": "^0.5.0",
"fstream": "^1.0.9",
"glob": "^7.0.5",
"gulp": "^3.9.1",
"gulp-qiniu": "^0.2.4",
"handlebars": "^4.0.5",
"influx": "^4.2.1",
"koa": "^2.0.0",
... ...