Showing
24 changed files
with
1490 additions
and
66 deletions
apps/ci/api_cache.js
0 → 100644
1 | + | ||
2 | +import Memcached from 'memcached'; | ||
3 | +import ws from '../../lib/ws'; | ||
4 | +import _ from 'lodash'; | ||
5 | + | ||
6 | +class ApiCache { | ||
7 | + | ||
8 | + constructor(host) { | ||
9 | + this.host = host; | ||
10 | + this.memcached = new Memcached(host); | ||
11 | + } | ||
12 | + | ||
13 | + delKey(key) { | ||
14 | + this._log(`deleting ${key}`) | ||
15 | + | ||
16 | + this.memcached.del(key, (err) => { | ||
17 | + if (err) { | ||
18 | + this._log(`deleted ${key} fail`) | ||
19 | + } else { | ||
20 | + this._log(`deleted ${key} success`) | ||
21 | + } | ||
22 | + }); | ||
23 | + } | ||
24 | + | ||
25 | + flushAll() { | ||
26 | + this.memcached.flush(err => { | ||
27 | + if (err) { | ||
28 | + this._log(`flush all fail.` + err.toString()) | ||
29 | + } else { | ||
30 | + this._log(`flush all success`) | ||
31 | + } | ||
32 | + this.memcached.end(); | ||
33 | + }); | ||
34 | + } | ||
35 | + | ||
36 | + clean(key) { | ||
37 | + let begin = new Date(); | ||
38 | + | ||
39 | + let key1 = `apiCache:${key}`; | ||
40 | + let key2 = `apiCache2:${key}`; | ||
41 | + | ||
42 | + let count = 0; | ||
43 | + | ||
44 | + this.memcached.items( ( err, result ) => { | ||
45 | + if( err ) console.error( err ); | ||
46 | + | ||
47 | + if (result.length === 0) { | ||
48 | + this._log('empty items') | ||
49 | + } | ||
50 | + // for each server... | ||
51 | + result.forEach(itemSet => { | ||
52 | + | ||
53 | + var keys = Object.keys( itemSet ); | ||
54 | + keys.pop(); // we don't need the "server" key, but the other indicate the slab id's | ||
55 | + | ||
56 | + var len = keys.length; | ||
57 | + | ||
58 | + if (keys.length === 0) { | ||
59 | + this._log('empty item set'); | ||
60 | + } | ||
61 | + | ||
62 | + keys.forEach(stats => { | ||
63 | + | ||
64 | + // get a cachedump for each slabid and slab.number | ||
65 | + this.memcached.cachedump( itemSet.server, parseInt(stats, 10), itemSet[stats].number, ( err, response ) => { | ||
66 | + // dump the shizzle | ||
67 | + | ||
68 | + if (response) { | ||
69 | + if (_.isArray(response)) { | ||
70 | + _.each(response, keyObj => { | ||
71 | + count ++ ; | ||
72 | + if (keyObj.key && (keyObj.key.indexOf(key1) >= 0 || keyObj.key.indexOf(key2) >= 0 || keyObj.key.indexOf(key) >= 0)) { | ||
73 | + this.delKey(keyObj.key); | ||
74 | + } else { | ||
75 | + // this._log(`skip ${keyObj.key}`) | ||
76 | + } | ||
77 | + }); | ||
78 | + } else { | ||
79 | + count ++; | ||
80 | + if (response.key && (response.key.indexOf(key1) >= 0 || response.key.indexOf(key2) >= 0 || response.key.indexOf(key) >= 0)) { | ||
81 | + this.delKey(response.key); | ||
82 | + } else { | ||
83 | + // this._log(`skip ${response.key}`) | ||
84 | + } | ||
85 | + } | ||
86 | + } | ||
87 | + | ||
88 | + len --; | ||
89 | + | ||
90 | + if (len === 0) { | ||
91 | + this.memcached.end(); | ||
92 | + let end = new Date(); | ||
93 | + this._log(`>>>>>> clean success in ${end.getTime() - begin.getTime()} , all keys: ${count}`) | ||
94 | + } | ||
95 | + }) | ||
96 | + }) | ||
97 | + }) | ||
98 | + }); | ||
99 | + } | ||
100 | + | ||
101 | + _log(message) { | ||
102 | + ws.broadcast(`/api_cache/log`, { | ||
103 | + host: this.host, | ||
104 | + msg: message | ||
105 | + }); | ||
106 | + } | ||
107 | +} | ||
108 | + | ||
109 | +export default ApiCache; | ||
110 | + |
apps/ci/delete_restart.js
0 → 100644
1 | +/** | ||
2 | + * 分发部署 | ||
3 | + * | ||
4 | + * @class Restart | ||
5 | + * @author shenzm<zhimin.shen@yoho.cn> | ||
6 | + * @date 2016/10/12 | ||
7 | + */ | ||
8 | + | ||
9 | +import ssh from 'ssh2'; | ||
10 | +import path from 'path'; | ||
11 | + | ||
12 | +import ws from '../../lib/ws'; | ||
13 | +import { | ||
14 | + RestartInfo, | ||
15 | + Server | ||
16 | +} from '../models'; | ||
17 | + | ||
18 | +class DeleteRestart { | ||
19 | + | ||
20 | + constructor(project) { | ||
21 | + this.project = project; | ||
22 | + } | ||
23 | + | ||
24 | + async deleteRestart(info) { | ||
25 | + let server = await Server.findByHost(info.host); | ||
26 | + this.server = server; | ||
27 | + this.info = info; | ||
28 | + this.sshDeleteRestart({ | ||
29 | + host: server.host, | ||
30 | + username: server.username, | ||
31 | + password: server.password, | ||
32 | + port: server.port | ||
33 | + }); | ||
34 | + } | ||
35 | + | ||
36 | + sshDeleteRestart(serverInfo) { | ||
37 | + console.log('ssh connecting', serverInfo); | ||
38 | + let conn = new ssh.Client(); | ||
39 | + let self = this; | ||
40 | + conn.on('ready', async() => { | ||
41 | + console.log(`connected ${serverInfo.host}`); | ||
42 | + try { | ||
43 | + await self._delete(conn); | ||
44 | + await self._restart(conn); | ||
45 | + conn.end(); | ||
46 | + } catch (e) { | ||
47 | + self._state('fail'); | ||
48 | + self._log(e); | ||
49 | + } | ||
50 | + }).on('error', (err) => { | ||
51 | + self._state('fail'); | ||
52 | + self._log(err); | ||
53 | + }).connect(serverInfo); | ||
54 | + } | ||
55 | + | ||
56 | + _delete(conn) { | ||
57 | + let self = this; | ||
58 | + let script = `pm2 delete ${self.project.name}`; | ||
59 | + return new Promise((resolve, reject) => { | ||
60 | + self._state('deleteing'); | ||
61 | + self._log(`>>>> ${script}`); | ||
62 | + conn.exec(`cd ${self.remoteRunningDir} && ${script}`, (err, stream) => { | ||
63 | + if (err) { | ||
64 | + reject(err); | ||
65 | + } else { | ||
66 | + stream.stdout.on('data', (data) => { | ||
67 | + self._log(data.toString()); | ||
68 | + }); | ||
69 | + stream.stderr.on('data', (data) => { | ||
70 | + self._log(data.toString()); | ||
71 | + }); | ||
72 | + stream.on('exit', (code) => { | ||
73 | + if (code === 0) { | ||
74 | + self._state('deleted'); | ||
75 | + resolve(); | ||
76 | + } else { | ||
77 | + reject('delete fail: ' + script); | ||
78 | + } | ||
79 | + }); | ||
80 | + } | ||
81 | + }); | ||
82 | + }); | ||
83 | + } | ||
84 | + | ||
85 | + _restart(conn) { | ||
86 | + let self = this; | ||
87 | + let startup = this.project.scripts.start; | ||
88 | + return new Promise((resolve, reject) => { | ||
89 | + self._state('restarting'); | ||
90 | + self._log(`>>>> ${startup}`); | ||
91 | + conn.exec(`cd ${self.remoteRunningDir} && ${startup}`, (err, stream) => { | ||
92 | + if (err) { | ||
93 | + reject(err); | ||
94 | + } else { | ||
95 | + stream.stdout.on('data', (data) => { | ||
96 | + self._log(data.toString()); | ||
97 | + }); | ||
98 | + stream.stderr.on('data', (data) => { | ||
99 | + self._log(data.toString()); | ||
100 | + }); | ||
101 | + stream.on('exit', (code) => { | ||
102 | + if (code === 0) { | ||
103 | + self._state('running'); | ||
104 | + resolve(); | ||
105 | + } else { | ||
106 | + reject('restart fail'); | ||
107 | + } | ||
108 | + }); | ||
109 | + } | ||
110 | + }); | ||
111 | + }); | ||
112 | + } | ||
113 | + | ||
114 | + async _state(state) { | ||
115 | + ws.broadcast(`/restart/${this.project._id}`, { | ||
116 | + host: this.info.host, | ||
117 | + state: state | ||
118 | + }); | ||
119 | + await RestartInfo.updateState(this.info._id, state); | ||
120 | + } | ||
121 | + | ||
122 | + _log(msg) { | ||
123 | + console.log("log: ", msg); | ||
124 | + ws.broadcast(`/restart/${this.project._id}/log`, { | ||
125 | + host: this.info.host, | ||
126 | + msg: msg | ||
127 | + }); | ||
128 | + } | ||
129 | + | ||
130 | + get remoteRunningDir() { | ||
131 | + return path.join(this.server.deployDir, this.project.name, 'current', this.project.name); | ||
132 | + } | ||
133 | +} | ||
134 | + | ||
135 | +export default DeleteRestart; |
@@ -160,7 +160,7 @@ class Deploy { | @@ -160,7 +160,7 @@ class Deploy { | ||
160 | }); | 160 | }); |
161 | }); | 161 | }); |
162 | } | 162 | } |
163 | - | 163 | + |
164 | async _state(state) { | 164 | async _state(state) { |
165 | ws.broadcast(`/deploy/${this.project._id}`, { | 165 | ws.broadcast(`/deploy/${this.project._id}`, { |
166 | host: this.info.host, | 166 | host: this.info.host, |
@@ -170,6 +170,7 @@ class Deploy { | @@ -170,6 +170,7 @@ class Deploy { | ||
170 | } | 170 | } |
171 | 171 | ||
172 | _log(msg) { | 172 | _log(msg) { |
173 | + console.log(msg) | ||
173 | ws.broadcast(`/deploy/${this.project._id}/log`, { | 174 | ws.broadcast(`/deploy/${this.project._id}/log`, { |
174 | host: this.info.host, | 175 | host: this.info.host, |
175 | msg: msg | 176 | msg: msg |
apps/ci/restart.js
0 → 100644
1 | +/** | ||
2 | + * 分发部署 | ||
3 | + * | ||
4 | + * @class Restart | ||
5 | + * @author shenzm<zhimin.shen@yoho.cn> | ||
6 | + * @date 2016/10/12 | ||
7 | + */ | ||
8 | + | ||
9 | +import ssh from 'ssh2'; | ||
10 | +import path from 'path'; | ||
11 | + | ||
12 | +import ws from '../../lib/ws'; | ||
13 | +import { | ||
14 | + RestartInfo, | ||
15 | + Server | ||
16 | +} from '../models'; | ||
17 | + | ||
18 | +class Restart { | ||
19 | + | ||
20 | + constructor(project) { | ||
21 | + this.project = project; | ||
22 | + } | ||
23 | + | ||
24 | + async restart(info) { | ||
25 | + let server = await Server.findByHost(info.host); | ||
26 | + this.server = server; | ||
27 | + this.info = info; | ||
28 | + this.sshRestart({ | ||
29 | + host: server.host, | ||
30 | + username: server.username, | ||
31 | + password: server.password, | ||
32 | + port: server.port | ||
33 | + }); | ||
34 | + } | ||
35 | + | ||
36 | + sshRestart(serverInfo) { | ||
37 | + console.log('ssh connecting', serverInfo); | ||
38 | + let conn = new ssh.Client(); | ||
39 | + let self = this; | ||
40 | + conn.on('ready', async() => { | ||
41 | + console.log(`connected ${serverInfo.host}`); | ||
42 | + try { | ||
43 | + await self._restart(conn); | ||
44 | + conn.end(); | ||
45 | + } catch (e) { | ||
46 | + self._state('fail'); | ||
47 | + self._log(e); | ||
48 | + } | ||
49 | + }).on('error', (err) => { | ||
50 | + self._state('fail'); | ||
51 | + self._log(err); | ||
52 | + }).connect(serverInfo); | ||
53 | + } | ||
54 | + | ||
55 | + _restart(conn) { | ||
56 | + let self = this; | ||
57 | + let startup = this.project.scripts.start; | ||
58 | + return new Promise((resolve, reject) => { | ||
59 | + self._state('restarting'); | ||
60 | + self._log(`>>>> ${startup}`); | ||
61 | + conn.exec(`cd ${self.remoteRunningDir} && ${startup}`, (err, stream) => { | ||
62 | + if (err) { | ||
63 | + reject(err); | ||
64 | + } else { | ||
65 | + stream.stdout.on('data', (data) => { | ||
66 | + self._log(data.toString()); | ||
67 | + }); | ||
68 | + stream.stderr.on('data', (data) => { | ||
69 | + self._log(data.toString()); | ||
70 | + }); | ||
71 | + stream.on('exit', (code) => { | ||
72 | + if (code === 0) { | ||
73 | + self._state('running'); | ||
74 | + resolve(); | ||
75 | + } else { | ||
76 | + reject('restart fail'); | ||
77 | + } | ||
78 | + }); | ||
79 | + } | ||
80 | + }); | ||
81 | + }); | ||
82 | + } | ||
83 | + | ||
84 | + async _state(state) { | ||
85 | + ws.broadcast(`/restart/${this.project._id}`, { | ||
86 | + host: this.info.host, | ||
87 | + state: state | ||
88 | + }); | ||
89 | + await RestartInfo.updateState(this.info._id, state); | ||
90 | + } | ||
91 | + | ||
92 | + _log(msg) { | ||
93 | + console.log(msg); | ||
94 | + ws.broadcast(`/restart/${this.project._id}/log`, { | ||
95 | + host: this.info.host, | ||
96 | + msg: msg | ||
97 | + }); | ||
98 | + } | ||
99 | + | ||
100 | + get remoteRunningDir() { | ||
101 | + return path.join(this.server.deployDir, this.project.name, 'current', this.project.name); | ||
102 | + } | ||
103 | +} | ||
104 | + | ||
105 | +export default Restart; |
apps/models/degrade.js
0 → 100644
1 | +'use strict'; | ||
2 | + | ||
3 | +import Model from './model'; | ||
4 | +import _ from 'lodash'; | ||
5 | + | ||
6 | +const defaultDegrades = [ | ||
7 | + { | ||
8 | + path: '/pc/common/disMyYohoHover', | ||
9 | + name: '【禁止】公共头部>>> MY有货鼠标移入显示账户信息' | ||
10 | + }, | ||
11 | + { | ||
12 | + path: '/pc/common/removeCartCount', | ||
13 | + name: '【移除】公共头部>>> 购物车图标显示的购物车商品数目' | ||
14 | + }, | ||
15 | + { | ||
16 | + path: '/pc/common/disSearchAssociation', | ||
17 | + name: '【禁止】公共头部>>> 搜索框执行搜索联想' | ||
18 | + }, | ||
19 | + { | ||
20 | + path: '/pc/common/disCartHover', | ||
21 | + name: '【禁止】公共头部>>> 购物车图标鼠标移入请求且不显示购物车商品列表' | ||
22 | + }, | ||
23 | + { | ||
24 | + path: '/pc/brands/disBrandNameHover', | ||
25 | + name: '【禁止】品牌一览>>> 品牌名移入显示品牌简介Tip' | ||
26 | + }, | ||
27 | + { | ||
28 | + path: '/pc/product/removeRecentView', | ||
29 | + name: '【移除】商品列表/商品详情>>> 最近浏览' | ||
30 | + }, | ||
31 | + { | ||
32 | + path: '/pc/guang/removeHotTag', | ||
33 | + name: '【移除】逛>>> 热门标签' | ||
34 | + }, | ||
35 | + { | ||
36 | + path: '/pc/guang/removeAd', | ||
37 | + name: '【移除】逛>>> 广告banner' | ||
38 | + }, | ||
39 | + { | ||
40 | + path: '/pc/guang/removeItemComment', | ||
41 | + name: '【移除】逛>>> 详情页评论' | ||
42 | + }, | ||
43 | + //wap | ||
44 | + { | ||
45 | + path: '/wap/plustar/removeCollect', | ||
46 | + name: '【移除】Plustar>>> 品牌收藏' | ||
47 | + }, | ||
48 | + { | ||
49 | + path: '/wap/plustar/removeRelatedPost', | ||
50 | + name: '【移除】Plustar>>> 相关资讯' | ||
51 | + }, | ||
52 | + { | ||
53 | + path: '/wap/search/removeHotSearch', | ||
54 | + name: '【移除】搜索>>> 热门搜索' | ||
55 | + }, | ||
56 | + { | ||
57 | + path: '/wap/ucenter/removePrefer', | ||
58 | + name: '【移除】个人中心>>> 为您优选' | ||
59 | + }, | ||
60 | + { | ||
61 | + path: '/wap/common/removeCartCount', | ||
62 | + name: '【移除】公共>>> 购物车icon商品数目' | ||
63 | + }, | ||
64 | + { | ||
65 | + path: '/wap/cart/removePrefer', | ||
66 | + name: '【移除】购物车>>> 为您优选新品' | ||
67 | + } | ||
68 | +]; | ||
69 | + | ||
70 | +class Degrade extends Model { | ||
71 | + | ||
72 | + constructor() { | ||
73 | + super('degrade'); | ||
74 | + } | ||
75 | + | ||
76 | + async init() { | ||
77 | + for (let i of defaultDegrades) { | ||
78 | + let count = await this.count({ | ||
79 | + path: i.path | ||
80 | + }); | ||
81 | + | ||
82 | + if (count === 0) { | ||
83 | + await this.insert(i); | ||
84 | + } | ||
85 | + } | ||
86 | + } | ||
87 | +} | ||
88 | + | ||
89 | +export default Degrade; |
apps/models/degrade_server.js
0 → 100644
apps/models/delete_restart.js
0 → 100644
1 | +'use strict'; | ||
2 | + | ||
3 | +import Model from './model'; | ||
4 | + | ||
5 | +class DeleteRestart extends Model { | ||
6 | + | ||
7 | + constructor() { | ||
8 | + super('delete_restart'); | ||
9 | + } | ||
10 | + | ||
11 | + async updateState(id, state) { | ||
12 | + await this.update({ | ||
13 | + _id: id | ||
14 | + }, { | ||
15 | + $set: { | ||
16 | + state: state | ||
17 | + } | ||
18 | + }); | ||
19 | + } | ||
20 | +} | ||
21 | + | ||
22 | +export default DeleteRestart; |
@@ -5,12 +5,17 @@ import ServerModel from './server'; | @@ -5,12 +5,17 @@ import ServerModel from './server'; | ||
5 | import BuildingModel from './building'; | 5 | import BuildingModel from './building'; |
6 | import ProjectModel from './project'; | 6 | import ProjectModel from './project'; |
7 | import DeployModel from './deploy'; | 7 | import DeployModel from './deploy'; |
8 | +import RestartModel from './restart'; | ||
9 | +import DeleteRestartModel from './delete_restart'; | ||
8 | import UserModel from './user'; | 10 | import UserModel from './user'; |
9 | import HotfixModel from './hotfix'; | 11 | import HotfixModel from './hotfix'; |
10 | import OperationLoggerModel from './operation_logger'; | 12 | import OperationLoggerModel from './operation_logger'; |
11 | import PageCacheModel from './page_cache'; | 13 | import PageCacheModel from './page_cache'; |
12 | import CdnCacheModel from './cdn_cache'; | 14 | import CdnCacheModel from './cdn_cache'; |
13 | import ProductCacheModel from './product_cache'; | 15 | import ProductCacheModel from './product_cache'; |
16 | +import MemcachedHostModel from './memcached_host'; | ||
17 | +import DegradeModel from './degrade'; | ||
18 | +import DegradeServerModel from './degrade_server'; | ||
14 | 19 | ||
15 | shelljs.mkdir('-p', config.dbDir); | 20 | shelljs.mkdir('-p', config.dbDir); |
16 | 21 | ||
@@ -18,18 +23,25 @@ const Server = new ServerModel(); | @@ -18,18 +23,25 @@ const Server = new ServerModel(); | ||
18 | const Building = new BuildingModel(); | 23 | const Building = new BuildingModel(); |
19 | const Project = new ProjectModel(); | 24 | const Project = new ProjectModel(); |
20 | const DeployInfo = new DeployModel(); | 25 | const DeployInfo = new DeployModel(); |
26 | +const RestartInfo = new RestartModel(); | ||
27 | +const DeleteRestartInfo = new DeleteRestartModel(); | ||
21 | const User = new UserModel(); | 28 | const User = new UserModel(); |
22 | const Hotfix = new HotfixModel(); | 29 | const Hotfix = new HotfixModel(); |
23 | const OperationLogger = new OperationLoggerModel(); | 30 | const OperationLogger = new OperationLoggerModel(); |
24 | const PageCache = new PageCacheModel(); | 31 | const PageCache = new PageCacheModel(); |
25 | const CdnCache = new CdnCacheModel(); | 32 | const CdnCache = new CdnCacheModel(); |
26 | const ProductCache = new ProductCacheModel(); | 33 | const ProductCache = new ProductCacheModel(); |
34 | +const MemcachedHost = new MemcachedHostModel(); | ||
35 | +const Degrade = new DegradeModel(); | ||
36 | +const DegradeServer = new DegradeServerModel(); | ||
27 | 37 | ||
28 | User.init(); | 38 | User.init(); |
29 | PageCache.init(); | 39 | PageCache.init(); |
30 | CdnCache.init(); | 40 | CdnCache.init(); |
31 | ProductCache.init(); | 41 | ProductCache.init(); |
32 | 42 | ||
43 | +Degrade.init(); | ||
44 | + | ||
33 | export { | 45 | export { |
34 | Server, | 46 | Server, |
35 | Building, | 47 | Building, |
@@ -40,5 +52,10 @@ export { | @@ -40,5 +52,10 @@ export { | ||
40 | OperationLogger, | 52 | OperationLogger, |
41 | PageCache, | 53 | PageCache, |
42 | CdnCache, | 54 | CdnCache, |
43 | - ProductCache | 55 | + ProductCache, |
56 | + MemcachedHost, | ||
57 | + RestartInfo, | ||
58 | + DeleteRestartInfo, | ||
59 | + Degrade, | ||
60 | + DegradeServer | ||
44 | }; | 61 | }; |
apps/models/memcached_host.js
0 → 100644
1 | +/** | ||
2 | + * | ||
3 | + * @author: jiangfeng<jeff.jiang@yoho.cn> | ||
4 | + * @date: 16/8/22 | ||
5 | + */ | ||
6 | + | ||
7 | +'use strict'; | ||
8 | + | ||
9 | +import Model from './model'; | ||
10 | + | ||
11 | +class MemcachedHost extends Model { | ||
12 | + | ||
13 | + constructor() { | ||
14 | + super('memecached_host'); | ||
15 | + } | ||
16 | + | ||
17 | +} | ||
18 | + | ||
19 | +export default MemcachedHost; |
apps/models/restart.js
0 → 100644
1 | +'use strict'; | ||
2 | + | ||
3 | +import Model from './model'; | ||
4 | + | ||
5 | +class Restart extends Model { | ||
6 | + | ||
7 | + constructor() { | ||
8 | + super('restart'); | ||
9 | + } | ||
10 | + | ||
11 | + async updateState(id, state) { | ||
12 | + await this.update({ | ||
13 | + _id: id | ||
14 | + }, { | ||
15 | + $set: { | ||
16 | + state: state | ||
17 | + } | ||
18 | + }); | ||
19 | + } | ||
20 | +} | ||
21 | + | ||
22 | +export default Restart; |
apps/web/actions/api_cache.js
0 → 100644
1 | + | ||
2 | + | ||
3 | +'use strict'; | ||
4 | + | ||
5 | +import Router from 'koa-router'; | ||
6 | +import _ from 'lodash'; | ||
7 | + | ||
8 | +import { | ||
9 | + MemcachedHost | ||
10 | +} from '../../models'; | ||
11 | + | ||
12 | +import ApiCache from '../../ci/api_cache'; | ||
13 | + | ||
14 | +let r = new Router(); | ||
15 | + | ||
16 | +const api_cache = { | ||
17 | + async index(ctx) { | ||
18 | + let hosts = await MemcachedHost.findAll(); | ||
19 | + | ||
20 | + await ctx.render('action/api_cache', {hosts: hosts}); | ||
21 | + }, | ||
22 | + | ||
23 | + async addHost(ctx) { | ||
24 | + let q = ctx.request.body; | ||
25 | + | ||
26 | + await MemcachedHost.insert({host: q.host}); | ||
27 | + return ctx.body = { | ||
28 | + code: 200 | ||
29 | + }; | ||
30 | + }, | ||
31 | + | ||
32 | + async delHost(ctx) { | ||
33 | + let q = ctx.request.body; | ||
34 | + | ||
35 | + await MemcachedHost.removeById(q.id); | ||
36 | + return ctx.body = { | ||
37 | + code: 200 | ||
38 | + }; | ||
39 | + }, | ||
40 | + | ||
41 | + async cleanKey(ctx) { | ||
42 | + let q = ctx.request.body; | ||
43 | + let key = q.key; | ||
44 | + | ||
45 | + let hosts = await MemcachedHost.findAll(); | ||
46 | + | ||
47 | + _.each(hosts, (h) => { | ||
48 | + (new ApiCache(h.host)).clean(key); | ||
49 | + }); | ||
50 | + | ||
51 | + return ctx.body = { | ||
52 | + code: 200 | ||
53 | + }; | ||
54 | + }, | ||
55 | + | ||
56 | + async flushAll(ctx) { | ||
57 | + let hosts = await MemcachedHost.findAll(); | ||
58 | + | ||
59 | + _.each(hosts, (h) => { | ||
60 | + (new ApiCache(h.host)).flushAll(); | ||
61 | + }); | ||
62 | + | ||
63 | + return ctx.body = { | ||
64 | + code: 200 | ||
65 | + }; | ||
66 | + } | ||
67 | +}; | ||
68 | + | ||
69 | +r.get('/', api_cache.index); | ||
70 | +r.post('/host/add', api_cache.addHost); | ||
71 | +r.post('/host/del', api_cache.delHost); | ||
72 | +r.post('/clean', api_cache.cleanKey); | ||
73 | +r.post('/flush', api_cache.flushAll); | ||
74 | + | ||
75 | +export default r; |
apps/web/actions/degrade.js
0 → 100644
1 | +'use strict'; | ||
2 | + | ||
3 | +import Router from 'koa-router'; | ||
4 | +import moment from 'moment'; | ||
5 | +import _ from 'lodash'; | ||
6 | + | ||
7 | +import {Degrade, DegradeServer} from '../../models'; | ||
8 | + | ||
9 | +import getter from '../../zookeeper/getter'; | ||
10 | +import setter from '../../zookeeper/setter'; | ||
11 | + | ||
12 | +const router = new Router(); | ||
13 | + | ||
14 | +const ctl = { | ||
15 | + async getServer() { | ||
16 | + let server = await DegradeServer.findAll({}); | ||
17 | + | ||
18 | + server = _.last(server); | ||
19 | + | ||
20 | + if (server) { | ||
21 | + return `${server.ip}:${server.port}`; | ||
22 | + } else { | ||
23 | + return 'localhost:2181'; | ||
24 | + } | ||
25 | + }, | ||
26 | + async index (ctx) { | ||
27 | + let count = await DegradeServer.count({}); | ||
28 | + let render; | ||
29 | + | ||
30 | + if (count) { | ||
31 | + let serverPath = await ctl.getServer(); | ||
32 | + let degrades = await Degrade.findAll(); | ||
33 | + | ||
34 | + for (let i of degrades) { | ||
35 | + i.checked = await getter(serverPath, i.path); | ||
36 | + } | ||
37 | + | ||
38 | + let pc = _.filter(degrades, o => _.startsWith(o.path, '/pc')); | ||
39 | + let wap = _.filter(degrades, o => _.startsWith(o.path, '/wap')); | ||
40 | + | ||
41 | + let serverSplit = serverPath.split(':'); | ||
42 | + render = { | ||
43 | + ip: serverSplit[0], | ||
44 | + port: serverSplit[1], | ||
45 | + render: { | ||
46 | + pc: pc, | ||
47 | + wap: wap | ||
48 | + } | ||
49 | + } | ||
50 | + } | ||
51 | + | ||
52 | + await ctx.render('action/degrade', render); | ||
53 | + }, | ||
54 | + async server(ctx) { | ||
55 | + let ip = ctx.request.body.ip; | ||
56 | + let port = ctx.request.body.port; | ||
57 | + | ||
58 | + let serverCount = await DegradeServer.count({}); | ||
59 | + | ||
60 | + // keep one server | ||
61 | + if (serverCount) { | ||
62 | + let serverConfig = await DegradeServer.findAll({}); | ||
63 | + let id = _.last(serverConfig)._id; // get the latest item | ||
64 | + | ||
65 | + await DegradeServer.update({ | ||
66 | + _id: id | ||
67 | + }, { | ||
68 | + $set: { | ||
69 | + ip: ip, | ||
70 | + port: port | ||
71 | + } | ||
72 | + }); | ||
73 | + } else { | ||
74 | + await DegradeServer.insert({ | ||
75 | + ip: ip, | ||
76 | + port: port | ||
77 | + }); | ||
78 | + } | ||
79 | + | ||
80 | + ctx.body = { | ||
81 | + code: 200, | ||
82 | + message: `${serverCount ? 'update' : 'new'} server success` | ||
83 | + }; | ||
84 | + }, | ||
85 | + async setter(ctx) { | ||
86 | + let {checked, id} = ctx.query; | ||
87 | + | ||
88 | + let theDegrade = await Degrade.findById(id); | ||
89 | + | ||
90 | + let path = theDegrade.path; | ||
91 | + | ||
92 | + let serverPath = await ctl.getServer(); | ||
93 | + | ||
94 | + await setter(serverPath, path, checked.toString()); | ||
95 | + | ||
96 | + ctx.body = { | ||
97 | + code: 200, | ||
98 | + message: 'update success' | ||
99 | + }; | ||
100 | + } | ||
101 | +}; | ||
102 | + | ||
103 | +router.get('/', ctl.index); | ||
104 | +router.post('/server', ctl.server); | ||
105 | +router.get('/setter', ctl.setter); | ||
106 | + | ||
107 | +export default router; |
@@ -2,15 +2,21 @@ | @@ -2,15 +2,21 @@ | ||
2 | 2 | ||
3 | import Router from 'koa-router'; | 3 | import Router from 'koa-router'; |
4 | import moment from 'moment'; | 4 | import moment from 'moment'; |
5 | +import Rp from 'request-promise'; | ||
5 | import Build from '../../ci/build'; | 6 | import Build from '../../ci/build'; |
6 | import Deploy from '../../ci/deploy'; | 7 | import Deploy from '../../ci/deploy'; |
8 | +import Restart from '../../ci/restart'; | ||
9 | +import DeleteRestart from '../../ci/delete_restart'; | ||
7 | import Operation from '../../logger/operation'; | 10 | import Operation from '../../logger/operation'; |
11 | +import ws from '../../../lib/ws'; | ||
8 | 12 | ||
9 | import { | 13 | import { |
10 | Building, | 14 | Building, |
11 | Project, | 15 | Project, |
12 | Server, | 16 | Server, |
13 | - DeployInfo | 17 | + DeployInfo, |
18 | + RestartInfo, | ||
19 | + DeleteRestartInfo | ||
14 | } from '../../models'; | 20 | } from '../../models'; |
15 | 21 | ||
16 | let r = new Router(); | 22 | let r = new Router(); |
@@ -132,10 +138,16 @@ const p = { | @@ -132,10 +138,16 @@ const p = { | ||
132 | }, { | 138 | }, { |
133 | $set: project | 139 | $set: project |
134 | }); | 140 | }); |
135 | - await Operation.action(ctx.session.user, 'EDIT_PROJECT_INFO', '修改项目信息', {_id: id, name: project.name}); | 141 | + await Operation.action(ctx.session.user, 'EDIT_PROJECT_INFO', '修改项目信息', { |
142 | + _id: id, | ||
143 | + name: project.name | ||
144 | + }); | ||
136 | } else { | 145 | } else { |
137 | await Project.insert(project); | 146 | await Project.insert(project); |
138 | - await Operation.action(ctx.session.user, 'NEW_PROJECT_INFO', '新增项目信息', {_id: id, name: project.name}); | 147 | + await Operation.action(ctx.session.user, 'NEW_PROJECT_INFO', '新增项目信息', { |
148 | + _id: id, | ||
149 | + name: project.name | ||
150 | + }); | ||
139 | } | 151 | } |
140 | ctx.redirect('/projects'); | 152 | ctx.redirect('/projects'); |
141 | ctx.status = 301; | 153 | ctx.status = 301; |
@@ -148,7 +160,7 @@ const p = { | @@ -148,7 +160,7 @@ const p = { | ||
148 | projectId: pid | 160 | projectId: pid |
149 | }).sort({ | 161 | }).sort({ |
150 | buildTime: -1 | 162 | buildTime: -1 |
151 | - }).exec(); | 163 | + }).limit(30).exec(); |
152 | buildings.forEach(b => { | 164 | buildings.forEach(b => { |
153 | b.updatedAt = moment(b.updatedAt).format('YYYY-MM-DD HH:mm:ss'); | 165 | b.updatedAt = moment(b.updatedAt).format('YYYY-MM-DD HH:mm:ss'); |
154 | }); | 166 | }); |
@@ -183,7 +195,12 @@ const p = { | @@ -183,7 +195,12 @@ const p = { | ||
183 | 195 | ||
184 | build.run(id); | 196 | build.run(id); |
185 | 197 | ||
186 | - await Operation.action(ctx.session.user, 'NEW_PROJECT_BUILDING', '新增项目构建', {_id: id, project: p.name, branch: branch, env: env}); | 198 | + await Operation.action(ctx.session.user, 'NEW_PROJECT_BUILDING', '新增项目构建', { |
199 | + _id: id, | ||
200 | + project: p.name, | ||
201 | + branch: branch, | ||
202 | + env: env | ||
203 | + }); | ||
187 | 204 | ||
188 | ctx.body = { | 205 | ctx.body = { |
189 | code: 200, | 206 | code: 200, |
@@ -225,7 +242,12 @@ const p = { | @@ -225,7 +242,12 @@ const p = { | ||
225 | } | 242 | } |
226 | }); | 243 | }); |
227 | 244 | ||
228 | - await Operation.action(ctx.session.user, 'PROJECT_DEPLOY', '项目分发部署', {_id: buildingId, project: project.name, branch: building.branch, env: building.env}); | 245 | + await Operation.action(ctx.session.user, 'PROJECT_DEPLOY', '项目分发部署', { |
246 | + _id: buildingId, | ||
247 | + project: project.name, | ||
248 | + branch: building.branch, | ||
249 | + env: building.env | ||
250 | + }); | ||
229 | 251 | ||
230 | ctx.body = { | 252 | ctx.body = { |
231 | code: 200, | 253 | code: 200, |
@@ -237,6 +259,182 @@ const p = { | @@ -237,6 +259,182 @@ const p = { | ||
237 | msg: '该版本未构建成功,暂不能分发' | 259 | msg: '该版本未构建成功,暂不能分发' |
238 | }; | 260 | }; |
239 | } | 261 | } |
262 | + }, | ||
263 | + project_restart: async(ctx) => { | ||
264 | + const projectId = ctx.request.body.id; | ||
265 | + const host = ctx.request.body.host; | ||
266 | + const env = ctx.request.body.env; | ||
267 | + const project = await Project.findById(projectId); | ||
268 | + | ||
269 | + if (!project) { | ||
270 | + ctx.body = { | ||
271 | + code: 201, | ||
272 | + msg: '该项目不存在' | ||
273 | + }; | ||
274 | + return; | ||
275 | + } | ||
276 | + | ||
277 | + let hosts = []; | ||
278 | + if (host === 'all') { | ||
279 | + // 全部重启 | ||
280 | + hosts = project.deploy[env].target; | ||
281 | + } else { | ||
282 | + // 单台重启 | ||
283 | + hosts.push(host); | ||
284 | + } | ||
285 | + | ||
286 | + hosts.forEach(async(host) => { | ||
287 | + let doc = await DeployInfo.findOne({ | ||
288 | + projectId: projectId, | ||
289 | + host: host, | ||
290 | + env: env | ||
291 | + }); | ||
292 | + | ||
293 | + if (!doc) { | ||
294 | + return; | ||
295 | + } | ||
296 | + | ||
297 | + let info = { | ||
298 | + projectId: projectId, | ||
299 | + host: host, | ||
300 | + env: env, | ||
301 | + createdAt: new Date(), | ||
302 | + updatedAt: new Date(), | ||
303 | + state: 'waiting' | ||
304 | + }; | ||
305 | + | ||
306 | + let restartDoc = await RestartInfo.insert(info); | ||
307 | + let restart = new Restart(project); | ||
308 | + | ||
309 | + info._id = restartDoc[0]._id; | ||
310 | + restart.restart(info); | ||
311 | + | ||
312 | + await Operation.action(ctx.session.user, 'PROJECT_RESTART', '项目重启', { | ||
313 | + _id: info._id, | ||
314 | + project: project.name, | ||
315 | + branch: project.deploy[env].branchName, | ||
316 | + env: env | ||
317 | + }); | ||
318 | + }); | ||
319 | + | ||
320 | + ctx.body = { | ||
321 | + code: 200 | ||
322 | + }; | ||
323 | + }, | ||
324 | + project_deleterestart: async(ctx) => { | ||
325 | + const projectId = ctx.request.body.id; | ||
326 | + const host = ctx.request.body.host; | ||
327 | + const env = ctx.request.body.env; | ||
328 | + const project = await Project.findById(projectId); | ||
329 | + | ||
330 | + if (!project) { | ||
331 | + ctx.body = { | ||
332 | + code: 201, | ||
333 | + msg: '该项目不存在' | ||
334 | + }; | ||
335 | + return; | ||
336 | + } | ||
337 | + | ||
338 | + let hosts = []; | ||
339 | + if (host === 'all') { | ||
340 | + // 全部重启 | ||
341 | + hosts = project.deploy[env].target; | ||
342 | + } else { | ||
343 | + // 单台重启 | ||
344 | + hosts.push(host); | ||
345 | + } | ||
346 | + | ||
347 | + hosts.forEach(async(host) => { | ||
348 | + let doc = await DeployInfo.findOne({ | ||
349 | + projectId: projectId, | ||
350 | + host: host, | ||
351 | + env: env | ||
352 | + }); | ||
353 | + | ||
354 | + if (!doc) { | ||
355 | + return; | ||
356 | + } | ||
357 | + | ||
358 | + let info = { | ||
359 | + projectId: projectId, | ||
360 | + host: host, | ||
361 | + env: env, | ||
362 | + createdAt: new Date(), | ||
363 | + updatedAt: new Date(), | ||
364 | + state: 'waiting' | ||
365 | + }; | ||
366 | + | ||
367 | + let delrestartDoc = await DeleteRestartInfo.insert(info); | ||
368 | + let deleteRestart = new DeleteRestart(project); | ||
369 | + | ||
370 | + info._id = delrestartDoc[0]._id; | ||
371 | + deleteRestart.deleteRestart(info); | ||
372 | + | ||
373 | + await Operation.action(ctx.session.user, 'PROJECT_DELETERESTART', '项目删除重启', { | ||
374 | + _id: info._id, | ||
375 | + project: project.name, | ||
376 | + branch: project.deploy[env].branchName, | ||
377 | + env: env | ||
378 | + }); | ||
379 | + }); | ||
380 | + | ||
381 | + ctx.body = { | ||
382 | + code: 200 | ||
383 | + }; | ||
384 | + }, | ||
385 | + project_monit: async(ctx) => { | ||
386 | + const projectId = ctx.request.body.id; | ||
387 | + const env = ctx.request.body.env; | ||
388 | + const project = await Project.findById(projectId); | ||
389 | + if (!project) { | ||
390 | + ctx.body = { | ||
391 | + code: 201, | ||
392 | + msg: '该项目不存在' | ||
393 | + }; | ||
394 | + return; | ||
395 | + } | ||
396 | + | ||
397 | + const hosts = project.deploy[env].target; | ||
398 | + hosts.forEach((host) => { | ||
399 | + var obj = { | ||
400 | + 'total': 0, | ||
401 | + 'status': {}, | ||
402 | + }; | ||
403 | + | ||
404 | + Rp({ | ||
405 | + uri: `http://${host}:9615`, | ||
406 | + json: true | ||
407 | + }).then(function(data) { | ||
408 | + var processes = data.processes || []; | ||
409 | + processes.forEach(function(p) { | ||
410 | + if (p.name === 'pm2-server-monit') { | ||
411 | + obj.cpuUsg = p.pm2_env.axm_monitor['CPU usage'].value; | ||
412 | + obj.freeMen = p.pm2_env.axm_monitor['Free memory'].value; | ||
413 | + } | ||
414 | + | ||
415 | + if (p.name === project.name) { | ||
416 | + obj.total++; | ||
417 | + | ||
418 | + if (!obj.status[p.pm2_env.status]) { | ||
419 | + obj.status[p.pm2_env.status] = 1; | ||
420 | + } else { | ||
421 | + obj.status[p.pm2_env.status]++; | ||
422 | + } | ||
423 | + } | ||
424 | + }); | ||
425 | + }).catch(function(err) { | ||
426 | + obj.errmsg = '获取监控状态失败' | ||
427 | + }).finally(function() { | ||
428 | + ws.broadcast(`/monit/${projectId}`, { | ||
429 | + host: host, | ||
430 | + monitdata: obj | ||
431 | + }); | ||
432 | + }); | ||
433 | + }); | ||
434 | + | ||
435 | + ctx.body = { | ||
436 | + code: 200 | ||
437 | + }; | ||
240 | } | 438 | } |
241 | }; | 439 | }; |
242 | 440 | ||
@@ -248,4 +446,7 @@ r.get('/:id/buildings', p.buildings_table); | @@ -248,4 +446,7 @@ r.get('/:id/buildings', p.buildings_table); | ||
248 | r.post('/save', p.save); | 446 | r.post('/save', p.save); |
249 | r.post('/build/:pid', p.project_build); | 447 | r.post('/build/:pid', p.project_build); |
250 | r.post('/deploy/:building', p.project_deploy); | 448 | r.post('/deploy/:building', p.project_deploy); |
449 | +r.post('/restart', p.project_restart); | ||
450 | +r.post('/deleterestart', p.project_deleterestart); | ||
451 | +r.post('/monit', p.project_monit); | ||
251 | export default r; | 452 | export default r; |
@@ -12,6 +12,8 @@ import operationLog from './actions/operation_log'; | @@ -12,6 +12,8 @@ import operationLog from './actions/operation_log'; | ||
12 | import pageCache from './actions/page_cache'; | 12 | import pageCache from './actions/page_cache'; |
13 | import cdnCache from './actions/cdn_cache'; | 13 | import cdnCache from './actions/cdn_cache'; |
14 | import productCache from './actions/product_cache'; | 14 | import productCache from './actions/product_cache'; |
15 | +import apiCache from './actions/api_cache'; | ||
16 | +import degrade from './actions/degrade'; | ||
15 | 17 | ||
16 | const noAuth = new Router(); | 18 | const noAuth = new Router(); |
17 | const base = new Router(); | 19 | const base = new Router(); |
@@ -40,6 +42,8 @@ export default function (app) { | @@ -40,6 +42,8 @@ export default function (app) { | ||
40 | base.use('/page_cache', pageCache.routes(), pageCache.allowedMethods()); | 42 | base.use('/page_cache', pageCache.routes(), pageCache.allowedMethods()); |
41 | base.use('/cdn_cache', cdnCache.routes(), cdnCache.allowedMethods()); | 43 | base.use('/cdn_cache', cdnCache.routes(), cdnCache.allowedMethods()); |
42 | base.use('/product_cache', productCache.routes(), productCache.allowedMethods()); | 44 | base.use('/product_cache', productCache.routes(), productCache.allowedMethods()); |
45 | + base.use('/api_cache', apiCache.routes(), apiCache.allowedMethods()); | ||
46 | + base.use('/degrade', degrade.routes(), degrade.allowedMethods()); | ||
43 | 47 | ||
44 | base.use('', index.routes(), index.allowedMethods()); | 48 | base.use('', index.routes(), index.allowedMethods()); |
45 | 49 |
apps/web/views/action/api_cache.hbs
0 → 100644
1 | +<div class="pageheader"> | ||
2 | + <div class="media"> | ||
3 | + <div class="pageicon pull-left"> | ||
4 | + <i class="fa fa-th-list"></i> | ||
5 | + </div> | ||
6 | + <div class="media-body"> | ||
7 | + <ul class="breadcrumb"> | ||
8 | + <li><a href=""><i class="glyphicon glyphicon-home"></i></a></li> | ||
9 | + <li>缓存管理</li> | ||
10 | + </ul> | ||
11 | + <h4>Api缓存清理</h4> | ||
12 | + </div> | ||
13 | + </div> | ||
14 | + <!-- media --> | ||
15 | +</div> | ||
16 | +<!-- pageheader --> | ||
17 | + | ||
18 | +<div class="contentpanel"> | ||
19 | + | ||
20 | + <div class="row"> | ||
21 | + <div class="col-sm-4 col-md-3"> | ||
22 | + <div> | ||
23 | + <button class="btn btn-danger" id="flush-btn">Flush All</button> | ||
24 | + </div> | ||
25 | + | ||
26 | + <h4 class="md-title mb5">memcached列表</h4> | ||
27 | + <div class="host-list"> | ||
28 | + {{#each hosts}} | ||
29 | + <p><span>{{host}}</span><span class="fa fa-times pull-right host-remove" style="color: red; cursor: pointer;" data-id="{{_id}}" data-host="{{host}}"></span></p> | ||
30 | + {{/each}} | ||
31 | + </div> | ||
32 | + | ||
33 | + <div class="mb20"></div> | ||
34 | + | ||
35 | + <div> | ||
36 | + <button class="btn btn-success" id="add-btn">添加</button> | ||
37 | + </div> | ||
38 | + | ||
39 | + <br> | ||
40 | + | ||
41 | + </div><!-- col-sm-4 --> | ||
42 | + <div class="col-sm-8 col-md-9"> | ||
43 | + <div class="panel"> | ||
44 | + <div class="panel-heading"> | ||
45 | + <div class="pull-right"> | ||
46 | + <a class="btn btn-success btn-rounded mr20 api-clean-btn">删除缓存</a> | ||
47 | + </div> | ||
48 | + <div class="panel-title"> | ||
49 | + 缓存key前缀: | ||
50 | + <input type="text" style="width: 33%; height: 40px;" id="api-key"> | ||
51 | + </div> | ||
52 | + | ||
53 | + | ||
54 | + </div><!-- panel-heading --> | ||
55 | + <div class="panel-body yoho-log-dark"> | ||
56 | + <div class="results-list" id="api-cache-log"> | ||
57 | + </div><!-- results-list --> | ||
58 | + </div><!-- panel-body --> | ||
59 | + </div><!-- panel --> | ||
60 | + </div><!-- col-sm-8 --> | ||
61 | + </div><!-- row --> | ||
62 | + | ||
63 | +</div> | ||
64 | + | ||
65 | +<script> | ||
66 | + $(document).on('ready pjax:success', function() { | ||
67 | + $('#add-btn').click(function(){ | ||
68 | + var i = layer.prompt({ | ||
69 | + title: '请输入host,例如 127.0.0.1:11211' | ||
70 | + }, function(host) { | ||
71 | + $.post('/api_cache/host/add', {host: host}, function(ret) { | ||
72 | + if (ret.code == 200) { | ||
73 | + $('.host-list').append('<p>' + host + '</p>'); | ||
74 | + layer.close(i); | ||
75 | + } | ||
76 | + }); | ||
77 | + }); | ||
78 | + }); | ||
79 | + | ||
80 | + $('.api-clean-btn').click(function() { | ||
81 | + var key = $('#api-key').val(); | ||
82 | + | ||
83 | + if (key) { | ||
84 | + $.post('/api_cache/clean', {key: key}, function(ret) { | ||
85 | + if (ret.code == 200) { | ||
86 | + layer.msg('正在清理中'); | ||
87 | + } | ||
88 | + }); | ||
89 | + } else { | ||
90 | + layer.msg('请输入key'); | ||
91 | + } | ||
92 | + }); | ||
93 | + | ||
94 | + $('.host-remove').click(function(){ | ||
95 | + var id = $(this).data('id'); | ||
96 | + var host = $(this).data('host'); | ||
97 | + var parent = $(this).parent(); | ||
98 | + var i = layer.confirm('确定删除host:<code>' + host + '</code>吗?', { | ||
99 | + btn: ['确定', '取消'] | ||
100 | + }, function() { | ||
101 | + $.post('/api_cache/host/del', {id: id}, function(ret) { | ||
102 | + if (ret.code === 200) { | ||
103 | + $(parent).remove(); | ||
104 | + layer.close(i); | ||
105 | + } | ||
106 | + }); | ||
107 | + }); | ||
108 | + }); | ||
109 | + | ||
110 | + $('#flush-btn').click(function(){ | ||
111 | + var i = layer.confirm('确定清楚所有缓存吗?', { | ||
112 | + btn: ['确定', '取消'] | ||
113 | + }, function() { | ||
114 | + $.post('/api_cache/flush', function(ret) { | ||
115 | + if (ret.code === 200) { | ||
116 | + layer.close(i); | ||
117 | + } | ||
118 | + }); | ||
119 | + }); | ||
120 | + }); | ||
121 | + | ||
122 | + function layoutResize() { | ||
123 | + $('.yoho-log-dark').height($('body').height() - 340); | ||
124 | + } | ||
125 | + | ||
126 | + $(window).resize(function() { | ||
127 | + layoutResize(); | ||
128 | + }); | ||
129 | + | ||
130 | + layoutResize(); | ||
131 | + | ||
132 | + var $logWrap = $('.yoho-log-dark'); | ||
133 | + var timer = 0; | ||
134 | + | ||
135 | + var ws = io(); | ||
136 | + ws.on('connect', function() { | ||
137 | + ws.on('/api_cache/log', function(data) { | ||
138 | + $('#api-cache-log').append('<p><span class="host">'+data.host+'</span><span class="message">'+data.msg+'</span></p>'); | ||
139 | + | ||
140 | + if (timer == 0) { | ||
141 | + timer = setTimeout(function(){ | ||
142 | + $logWrap.scrollTop($logWrap[0].scrollHeight); | ||
143 | + timer = 0; | ||
144 | + }, 1000); | ||
145 | + } | ||
146 | + }); | ||
147 | + }); | ||
148 | + ws.on('error', function() { | ||
149 | + console.log('connect fail'); | ||
150 | + }); | ||
151 | + }); | ||
152 | +</script> |
apps/web/views/action/degrade.hbs
0 → 100644
1 | +<style> | ||
2 | + .degrade-page { | ||
3 | + padding: 20px; | ||
4 | + } | ||
5 | + | ||
6 | + .degrade-tab li { | ||
7 | + cursor: pointer; | ||
8 | + } | ||
9 | + | ||
10 | + .pc-degrade, | ||
11 | + .wap-degrade { | ||
12 | + list-style: none; | ||
13 | + padding: 20px; | ||
14 | + } | ||
15 | + | ||
16 | + .err-tip { | ||
17 | + color: #a94442; | ||
18 | + } | ||
19 | +</style> | ||
20 | +<div class="degrade-page"> | ||
21 | + <div class="panel panel-info"> | ||
22 | + <div class="panel-heading">zookeeper server</div> | ||
23 | + <div class="panel-body"> | ||
24 | + <div class="form-inline"> | ||
25 | + <div class="form-group"> | ||
26 | + <label for="server-ip">Server IP</label> | ||
27 | + <input id="server-ip" class="form-control" type="text" placeholder="input server ip" value="{{ip}}"> | ||
28 | + </div> | ||
29 | + <div class="form-group"> | ||
30 | + <label for="server-port">Server Port</label> | ||
31 | + <input id="server-port" class="form-control" type="text" placeholder="input server port" value="{{port}}"> | ||
32 | + </div> | ||
33 | + <span id="server-sure" class="btn btn-default">确定</span> | ||
34 | + <span id="err-tip" class="err-tip hide"> | ||
35 | + <i class="glyphicon glyphicon-remove-sign"></i> | ||
36 | + 小哥,填错了吧 | ||
37 | + </span> | ||
38 | + </div> | ||
39 | + </div> | ||
40 | + </div> | ||
41 | + <div class="panel panel-danger"> | ||
42 | + <div class="panel-heading">degrade point</div> | ||
43 | + <div id="pjax-container" class="panel-body"> | ||
44 | + {{#render}} | ||
45 | + <ul id="degrade-tab" class="nav nav-tabs degrade-tab" role="tablist"> | ||
46 | + <li role="presentation" class="active"> | ||
47 | + <a>PC</a> | ||
48 | + </li> | ||
49 | + <li role="presentation"> | ||
50 | + <a>WAP</a> | ||
51 | + </li> | ||
52 | + </ul> | ||
53 | + | ||
54 | + <ul class="pc-degrade degrade-content"> | ||
55 | + {{#each pc}} | ||
56 | + <li data-id="{{_id}}"> | ||
57 | + <div class="checkbox"> | ||
58 | + <label> | ||
59 | + <input type="checkbox"{{#if checked}} checked{{/if}}> | ||
60 | + {{name}} | ||
61 | + </label> | ||
62 | + </div> | ||
63 | + </li> | ||
64 | + {{/each}} | ||
65 | + </ul> | ||
66 | + | ||
67 | + <ul class="wap-degrade degrade-content hide"> | ||
68 | + {{#each wap}} | ||
69 | + <li data-id="{{_id}}"> | ||
70 | + <div class="checkbox"> | ||
71 | + <label> | ||
72 | + <input type="checkbox"{{#if checked}} checked{{/if}}> | ||
73 | + {{name}} | ||
74 | + </label> | ||
75 | + </div> | ||
76 | + </li> | ||
77 | + {{/each}} | ||
78 | + </ul> | ||
79 | + {{^}} | ||
80 | + Waitting for connecting to server... | ||
81 | + {{/render}} | ||
82 | + </div> | ||
83 | + </div> | ||
84 | +</div> | ||
85 | +<script> | ||
86 | + $(function() { | ||
87 | + var $ip = $('#server-ip'); | ||
88 | + var $port = $('#server-port'); | ||
89 | + var $tip = $('#err-tip'); | ||
90 | + | ||
91 | + function validateIP(ip) { | ||
92 | + if (ip === 'localhost' || | ||
93 | + /^(\d)+\.(\d)+\.(\d)+\.(\d)+$/.test(ip)) | ||
94 | + { | ||
95 | + $ip.closest('.form-group').removeClass('has-error'); | ||
96 | + return true; | ||
97 | + } | ||
98 | + | ||
99 | + $ip.closest('.form-group').addClass('has-error'); | ||
100 | + return false; | ||
101 | + } | ||
102 | + | ||
103 | + function validatePort(port) { | ||
104 | + if (/^[1-9]\d*$/.test(port) && +port >= 1 && +port <= 65535) { | ||
105 | + $port.closest('.form-group').removeClass('has-error'); | ||
106 | + return true; | ||
107 | + } | ||
108 | + | ||
109 | + $port.closest('.form-group').addClass('has-error'); | ||
110 | + return false; | ||
111 | + } | ||
112 | + | ||
113 | + // server | ||
114 | + $('#server-sure').click(function() { | ||
115 | + var ip = $.trim($ip.val()); | ||
116 | + var port = $.trim($port.val()); | ||
117 | + | ||
118 | + if (!validateIP(ip) || !validatePort(port)) { | ||
119 | + $tip.removeClass('hide'); | ||
120 | + return; | ||
121 | + } | ||
122 | + | ||
123 | + $tip.addClass('hide'); | ||
124 | + | ||
125 | + var serverPrompt = confirm('小伙砸,你确认修改zookeeper的server到:' + ip + ':' + port + '吗?') | ||
126 | + | ||
127 | + if (!serverPrompt) { | ||
128 | + return; | ||
129 | + } | ||
130 | + | ||
131 | + $.ajax({ | ||
132 | + type: 'POST', | ||
133 | + url: '/degrade/server', | ||
134 | + data: { | ||
135 | + ip: ip, | ||
136 | + port: port | ||
137 | + } | ||
138 | + }).then(function(data) { | ||
139 | + if (data.code === 200) { | ||
140 | + $.pjax.reload('#pjax-container'); | ||
141 | + } | ||
142 | + }); | ||
143 | + }); | ||
144 | + | ||
145 | + $('#degrade-tab').on('click', 'li', function() { | ||
146 | + var $this = $(this); | ||
147 | + | ||
148 | + if ($this.hasClass('active')) { | ||
149 | + return; | ||
150 | + } | ||
151 | + | ||
152 | + $('li', $('#degrade-tab')).toggleClass('active'); | ||
153 | + | ||
154 | + var index = $this.index(); | ||
155 | + | ||
156 | + if (index === 0) { | ||
157 | + | ||
158 | + //PC active | ||
159 | + $('.pc-degrade').removeClass('hide'); | ||
160 | + $('.wap-degrade').addClass('hide'); | ||
161 | + } else { | ||
162 | + | ||
163 | + // wap active | ||
164 | + $('.wap-degrade').removeClass('hide'); | ||
165 | + $('.pc-degrade').addClass('hide'); | ||
166 | + } | ||
167 | + }); | ||
168 | + | ||
169 | + // change | ||
170 | + $('.degrade-content input[type="checkbox"]').change(function() { | ||
171 | + var $checkbox = $(this), | ||
172 | + $li = $checkbox.closest('li'); | ||
173 | + | ||
174 | + var checked = $checkbox.prop('checked'); | ||
175 | + | ||
176 | + var id = $li.data('id'); | ||
177 | + | ||
178 | + $.ajax({ | ||
179 | + url: '/degrade/setter', | ||
180 | + data: { | ||
181 | + checked: checked, | ||
182 | + id: id | ||
183 | + } | ||
184 | + }); | ||
185 | + }); | ||
186 | + }) | ||
187 | +</script> |
@@ -10,6 +10,7 @@ | @@ -10,6 +10,7 @@ | ||
10 | <li>{{project.name}}</li> | 10 | <li>{{project.name}}</li> |
11 | </ul> | 11 | </ul> |
12 | <h4>{{project.name}} ({{project.subname}})</h4> | 12 | <h4>{{project.name}} ({{project.subname}})</h4> |
13 | + <input id="info" type="hidden" data-id='{{project._id}}' data-env='{{deploy.env}}'> | ||
13 | </div> | 14 | </div> |
14 | </div> | 15 | </div> |
15 | <!-- media --> | 16 | <!-- media --> |
@@ -47,6 +48,8 @@ | @@ -47,6 +48,8 @@ | ||
47 | <div class="panel-heading"> | 48 | <div class="panel-heading"> |
48 | <h4>服务器信息</h4> | 49 | <h4>服务器信息</h4> |
49 | <p>点击状态Label,可以查看实时日志</p> | 50 | <p>点击状态Label,可以查看实时日志</p> |
51 | + <button class="btn btn-info btn-xs restart-btn" data-host='all'>全部重启</button> | ||
52 | + <button class="btn btn-warning btn-xs deleterestart-btn" data-host='all'>全部删除重启</button> | ||
50 | </div> | 53 | </div> |
51 | <div class="panel-body"> | 54 | <div class="panel-body"> |
52 | <div class="row"> | 55 | <div class="row"> |
@@ -58,20 +61,35 @@ | @@ -58,20 +61,35 @@ | ||
58 | </div><!-- panel-btns --> | 61 | </div><!-- panel-btns --> |
59 | <div class="panel-icon"><i class="fa fa-cloud" style="padding-left:12px;"></i></div> | 62 | <div class="panel-icon"><i class="fa fa-cloud" style="padding-left:12px;"></i></div> |
60 | <div class="media-body"> | 63 | <div class="media-body"> |
61 | - <h2 class="nomargin">{{host}}</h2> | ||
62 | - <h5 class="md-title mt5">当前运行版本: <code>{{#if info}}{{info.building}}{{^}} | 64 | + <h2 class="nomargin">{{host}}</h2> |
65 | + <h5 class="md-title mt5 version">当前运行版本: <code>{{#if info}}{{info.building}}{{^}} | ||
63 | 未知部署{{/if}}</code></h5> | 66 | 未知部署{{/if}}</code></h5> |
64 | </div><!-- media-body --> | 67 | </div><!-- media-body --> |
65 | <hr class="mt10 mb10"> | 68 | <hr class="mt10 mb10"> |
69 | + <div class="clearfix mt5" style="padding-left:10px;"> | ||
70 | + <button class="btn btn-info btn-xs restart-btn" data-host='{{host}}'>重启</button> | ||
71 | + <button class="btn btn-warning btn-xs deleterestart-btn" data-host='{{host}}'>删除重启</button> | ||
72 | + </div> | ||
73 | + <div class="clearfix mt5" style="padding-left:10px;"> | ||
74 | + <h5 class="md-title mt10">服务器状态</h5> | ||
75 | + <span class="label label-serstatus"></span> | ||
76 | + </div> | ||
66 | <div class="clearfix mt5"> | 77 | <div class="clearfix mt5"> |
67 | - <div class="col-xs-6 project-env" data-env="test"> | 78 | + <div class="col-xs-4 project-env" data-env="test"> |
68 | <h5 class="md-title mt10">当前状态</h5> | 79 | <h5 class="md-title mt10">当前状态</h5> |
80 | + </div> | ||
81 | + <div class="col-xs-4"> | ||
82 | + <h5 class="md-title mt10">进程状态</h5> | ||
83 | + </div> | ||
84 | + </div> | ||
85 | + <div class="clearfix mt5"> | ||
86 | + <div class="col-xs-4 project-env" data-env="test"> | ||
69 | <span class="label label-success deploy-log-btn" data-host="{{host}}"><i | 87 | <span class="label label-success deploy-log-btn" data-host="{{host}}"><i |
70 | class="fa fa-spinner fa-spin fa-fw margin-bottom"></i> <b>{{#if info}}{{info.state}}{{^}} | 88 | class="fa fa-spinner fa-spin fa-fw margin-bottom"></i> <b>{{#if info}}{{info.state}}{{^}} |
71 | 未知部署{{/if}}</b></span> | 89 | 未知部署{{/if}}</b></span> |
72 | </div> | 90 | </div> |
73 | - <div class="col-xs-6"> | ||
74 | - | 91 | + <div class="col-xs-4"> |
92 | + <span class="label label-status"></span> | ||
75 | </div> | 93 | </div> |
76 | </div> | 94 | </div> |
77 | </div><!-- panel-body --> | 95 | </div><!-- panel-body --> |
@@ -177,6 +195,49 @@ | @@ -177,6 +195,49 @@ | ||
177 | 195 | ||
178 | }); | 196 | }); |
179 | 197 | ||
198 | + $('.restart-btn').click(function(){ | ||
199 | + var id = $('#info').data('id'); | ||
200 | + var env = $('#info').data('env'); | ||
201 | + var host = $(this).data('host'); | ||
202 | + | ||
203 | + layer.confirm('确定重启吗?', { | ||
204 | + btn: ['确定', '取消'] | ||
205 | + }, function() { | ||
206 | + $.post('/projects/restart', { | ||
207 | + id: id, | ||
208 | + host: host, | ||
209 | + env: env | ||
210 | + },function(ret) { | ||
211 | + if (ret.code == 200) { | ||
212 | + layer.msg('正在重启中'); | ||
213 | + } else { | ||
214 | + layer.msg(ret.msg, {icon: 5}); | ||
215 | + } | ||
216 | + }); | ||
217 | + }); | ||
218 | + }); | ||
219 | + | ||
220 | + $('.deleterestart-btn').click(function(){ | ||
221 | + var id = $('#info').data('id'); | ||
222 | + var env = $('#info').data('env'); | ||
223 | + var host = $(this).data('host'); | ||
224 | + | ||
225 | + layer.confirm('确定删除后重启吗?', { | ||
226 | + btn: ['确定', '取消'] | ||
227 | + }, function() { | ||
228 | + $.post('/projects/deleterestart', { | ||
229 | + id: id, | ||
230 | + host: host, | ||
231 | + env: env | ||
232 | + },function(ret) { | ||
233 | + if (ret.code == 200) { | ||
234 | + layer.msg('正在删除重启中'); | ||
235 | + } else { | ||
236 | + layer.msg(ret.msg, {icon: 5}); | ||
237 | + } | ||
238 | + }); | ||
239 | + }); | ||
240 | + }) | ||
180 | 241 | ||
181 | $('.rollback-btn').click(function() { | 242 | $('.rollback-btn').click(function() { |
182 | layer.prompt({ | 243 | layer.prompt({ |
@@ -226,6 +287,45 @@ | @@ -226,6 +287,45 @@ | ||
226 | $('#d-' + data.host.replace(/\./g, '-')).find('b').text(data.state); | 287 | $('#d-' + data.host.replace(/\./g, '-')).find('b').text(data.state); |
227 | }); | 288 | }); |
228 | 289 | ||
290 | + ws.on('/restart/{{project._id}}', function(data) { | ||
291 | + console.log(data); | ||
292 | + $('#d-' + data.host.replace(/\./g, '-')).find('b').text(data.state); | ||
293 | + }); | ||
294 | + | ||
295 | + ws.on('/deleterestart/{{project._id}}', function(data) { | ||
296 | + console.log(data); | ||
297 | + $('#d-' + data.host.replace(/\./g, '-')).find('b').text(data.state); | ||
298 | + }); | ||
299 | + | ||
300 | + ws.on('/monit/{{project._id}}', function(data) { | ||
301 | + console.log(data); | ||
302 | + var label = $('#d-' + data.host.replace(/\./g, '-')).find('.label-status'); | ||
303 | + var servers = $('#d-' + data.host.replace(/\./g, '-')).find('.label-serstatus'); | ||
304 | + | ||
305 | + data = data.monitdata; | ||
306 | + if (data.errmsg) { | ||
307 | + label.text(data.errmsg).removeClass('label-success').addClass('label-danger'); | ||
308 | + servers.text("获取失败").removeClass('label-success').addClass('label-danger'); | ||
309 | + } else { | ||
310 | + var msg = "线程:" + data.total | ||
311 | + for(var s in data.status) { | ||
312 | + msg += '; [' + s + ']:' + data.status[s]; | ||
313 | + } | ||
314 | + label.text(msg); | ||
315 | + | ||
316 | + if (data.total != data.status.online) { | ||
317 | + label.removeClass('label-success').addClass('label-danger'); | ||
318 | + } else { | ||
319 | + label.removeClass('label-danger').addClass('label-success'); | ||
320 | + } | ||
321 | + | ||
322 | + msg = ""; | ||
323 | + if (data.cpuUsg) msg += "CPU:" + data.cpuUsg + ";"; | ||
324 | + if (data.freeMen) msg += "可用内存:" + data.freeMen; | ||
325 | + servers.text(msg).removeClass('label-danger').addClass('label-success'); | ||
326 | + } | ||
327 | + }); | ||
328 | + | ||
229 | // ws.on('/deploy/{{project._id}}/log', function(data){ | 329 | // ws.on('/deploy/{{project._id}}/log', function(data){ |
230 | // if(tag == data.host){ | 330 | // if(tag == data.host){ |
231 | // cm.replaceRange("> " +data.msg+ "\n", {line: Infinity}); | 331 | // cm.replaceRange("> " +data.msg+ "\n", {line: Infinity}); |
@@ -235,5 +335,25 @@ | @@ -235,5 +335,25 @@ | ||
235 | ws.on('error', function() { | 335 | ws.on('error', function() { |
236 | console.log('connect fail'); | 336 | console.log('connect fail'); |
237 | }); | 337 | }); |
338 | + | ||
339 | + var projectid = $('#info').data('id'); | ||
340 | + var env = $('#info').data('env'); | ||
341 | + var monit = function() { | ||
342 | + $.post('/projects/monit', { | ||
343 | + id: projectid, | ||
344 | + env: env | ||
345 | + },function(ret) { | ||
346 | + if (ret.code != 200) { | ||
347 | + layer.msg(ret.msg, {icon: 5}); | ||
348 | + } | ||
349 | + }); | ||
350 | + } | ||
351 | + setInterval(function(){ | ||
352 | + // 监控服务状态(轮训) | ||
353 | + monit(); | ||
354 | + }, 5000); | ||
355 | + | ||
356 | + monit(); | ||
357 | + | ||
238 | }); | 358 | }); |
239 | </script> | 359 | </script> |
@@ -28,8 +28,9 @@ | @@ -28,8 +28,9 @@ | ||
28 | <li class="parent"><a href=""><i class="fa fa-history"></i> <span>缓存管理</span></a> | 28 | <li class="parent"><a href=""><i class="fa fa-history"></i> <span>缓存管理</span></a> |
29 | <ul class="children"> | 29 | <ul class="children"> |
30 | <li><a href="/page_cache/query">PageCahe清理</a></li> | 30 | <li><a href="/page_cache/query">PageCahe清理</a></li> |
31 | - <li><a href="/cdn_cache/query">CDN清理</a></li> | ||
32 | - <li><a href="/product_cache/query">商品清理</a></li> | 31 | + {{!-- <li><a href="/cdn_cache/query">CDN清理</a></li> |
32 | + <li><a href="/product_cache/query">商品清理</a></li> --}} | ||
33 | + <li><a href="/api_cache">ApiCahe清理</a></li> | ||
33 | </ul> | 34 | </ul> |
34 | </li> | 35 | </li> |
35 | {{#if is_master}} | 36 | {{#if is_master}} |
@@ -41,6 +42,7 @@ | @@ -41,6 +42,7 @@ | ||
41 | </ul> | 42 | </ul> |
42 | </li> | 43 | </li> |
43 | {{/if}} | 44 | {{/if}} |
45 | + <li><a href="/degrade"><i class="fa fa-hand-o-down"></i> <span>降级配置</span></a></li> | ||
44 | </ul> | 46 | </ul> |
45 | 47 | ||
46 | </div> | 48 | </div> |
apps/zookeeper/creator.js
0 → 100644
1 | +'use strict'; | ||
2 | + | ||
3 | +import zookeeper from 'node-zookeeper-client'; | ||
4 | + | ||
5 | +module.exports = (server, path) => { | ||
6 | + const client = zookeeper.createClient(server); | ||
7 | + | ||
8 | + client.once('connected', () => { | ||
9 | + client.mkdirp(path, new Buffer('false'), (err, path) => { | ||
10 | + if (err) { | ||
11 | + console.log('Node %s create err', path, err.stack); | ||
12 | + } else { | ||
13 | + console.log('Node %s is created', path); | ||
14 | + } | ||
15 | + | ||
16 | + client.close(); | ||
17 | + }); | ||
18 | + }); | ||
19 | + | ||
20 | + client.connect(); | ||
21 | +}; |
apps/zookeeper/getter.js
0 → 100644
1 | +'usu strict'; | ||
2 | + | ||
3 | +import _ from 'lodash'; | ||
4 | +import zookeeper from 'node-zookeeper-client'; | ||
5 | + | ||
6 | +import creator from './creator'; | ||
7 | + | ||
8 | +const getter = (server, client, path, resolve, reject) => { | ||
9 | + client.exists(path, (err, stat) => { | ||
10 | + if (err) { | ||
11 | + console.log('path %s exits error', path, err.stack); | ||
12 | + resolve(false); | ||
13 | + return; | ||
14 | + } | ||
15 | + | ||
16 | + if (stat) { | ||
17 | + client.getData( | ||
18 | + path, | ||
19 | + (err, data, stat) => { | ||
20 | + if (err) { | ||
21 | + console.log('Got path %s data error', path, err.stack); | ||
22 | + } | ||
23 | + client.close(); | ||
24 | + | ||
25 | + resolve((data && data.toString('utf8') === 'true') ? true : false); | ||
26 | + } | ||
27 | + ) | ||
28 | + } else { | ||
29 | + // 不存在的路径 | ||
30 | + console.log('no path %s, we will create it with value "false" automatic', path); | ||
31 | + client.close(); | ||
32 | + | ||
33 | + // create path | ||
34 | + creator(server, path); | ||
35 | + | ||
36 | + resolve(false); | ||
37 | + } | ||
38 | + }); | ||
39 | + | ||
40 | +}; | ||
41 | + | ||
42 | +module.exports = (server, path) => new Promise((resolve, reject) => { | ||
43 | + const client = zookeeper.createClient(server); | ||
44 | + | ||
45 | + client.once('connected', () => { | ||
46 | + getter(server, client, path, resolve, reject); | ||
47 | + }); | ||
48 | + | ||
49 | + client.connect(); | ||
50 | +}); |
apps/zookeeper/setter.js
0 → 100644
1 | +'usu strict'; | ||
2 | + | ||
3 | +import _ from 'lodash'; | ||
4 | +import zookeeper from 'node-zookeeper-client'; | ||
5 | + | ||
6 | +module.exports = (server, path, val) => new Promise((resolve, reject) => { | ||
7 | + const client = zookeeper.createClient(server); | ||
8 | + | ||
9 | + client.once('connected', function () { | ||
10 | + client.setData(path, new Buffer(val.toString()), function(err, data, stat) { | ||
11 | + console.log('path %s data change to', path, val); | ||
12 | + resolve(); | ||
13 | + client.close(); | ||
14 | + }); | ||
15 | + }); | ||
16 | + | ||
17 | + client.connect(); | ||
18 | +}); |
npm-debug.log
deleted
100644 → 0
1 | -0 info it worked if it ends with ok | ||
2 | -1 verbose cli [ '/usr/local/bin/node', | ||
3 | -1 verbose cli '/usr/local/bin/npm', | ||
4 | -1 verbose cli 'run', | ||
5 | -1 verbose cli 'babel', | ||
6 | -1 verbose cli 'app.js' ] | ||
7 | -2 info using npm@3.8.9 | ||
8 | -3 info using node@v6.2.0 | ||
9 | -4 verbose run-script [ 'prebabel', 'babel', 'postbabel' ] | ||
10 | -5 info lifecycle yoho-node-ci@0.0.1~prebabel: yoho-node-ci@0.0.1 | ||
11 | -6 silly lifecycle yoho-node-ci@0.0.1~prebabel: no script for prebabel, continuing | ||
12 | -7 info lifecycle yoho-node-ci@0.0.1~babel: yoho-node-ci@0.0.1 | ||
13 | -8 verbose lifecycle yoho-node-ci@0.0.1~babel: unsafe-perm in lifecycle true | ||
14 | -9 verbose lifecycle yoho-node-ci@0.0.1~babel: PATH: /usr/local/lib/node_modules/npm/bin/node-gyp-bin:/Users/chenfeng/Documents/source/yoho/yoho-node-ci/node_modules/.bin:/usr/local/bin:/usr/local/lib/node_modules/npm/bin/node-gyp-bin:/Users/chenfeng/Documents/source/yoho/yoho-node-ci/node_modules/.bin:/usr/local/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/share/dotnet:/Users/chenfeng/Documents/tools/android-sdk-macosx/platform-tools:/Users/chenfeng/Documents/tools/android-sdk-macosx/tools:/usr/local/nginx/sbin | ||
15 | -10 verbose lifecycle yoho-node-ci@0.0.1~babel: CWD: /Users/chenfeng/Documents/source/yoho/yoho-node-ci | ||
16 | -11 silly lifecycle yoho-node-ci@0.0.1~babel: Args: [ '-c', 'babel-node "app.js"' ] | ||
17 | -12 silly lifecycle yoho-node-ci@0.0.1~babel: Returned: code: 1 signal: null | ||
18 | -13 info lifecycle yoho-node-ci@0.0.1~babel: Failed to exec babel script | ||
19 | -14 verbose stack Error: yoho-node-ci@0.0.1 babel: `babel-node "app.js"` | ||
20 | -14 verbose stack Exit status 1 | ||
21 | -14 verbose stack at EventEmitter.<anonymous> (/usr/local/lib/node_modules/npm/lib/utils/lifecycle.js:245:16) | ||
22 | -14 verbose stack at emitTwo (events.js:106:13) | ||
23 | -14 verbose stack at EventEmitter.emit (events.js:191:7) | ||
24 | -14 verbose stack at ChildProcess.<anonymous> (/usr/local/lib/node_modules/npm/lib/utils/spawn.js:24:14) | ||
25 | -14 verbose stack at emitTwo (events.js:106:13) | ||
26 | -14 verbose stack at ChildProcess.emit (events.js:191:7) | ||
27 | -14 verbose stack at maybeClose (internal/child_process.js:850:16) | ||
28 | -14 verbose stack at Process.ChildProcess._handle.onexit (internal/child_process.js:215:5) | ||
29 | -15 verbose pkgid yoho-node-ci@0.0.1 | ||
30 | -16 verbose cwd /Users/chenfeng/Documents/source/yoho/yoho-node-ci | ||
31 | -17 error Darwin 16.0.0 | ||
32 | -18 error argv "/usr/local/bin/node" "/usr/local/bin/npm" "run" "babel" "app.js" | ||
33 | -19 error node v6.2.0 | ||
34 | -20 error npm v3.8.9 | ||
35 | -21 error code ELIFECYCLE | ||
36 | -22 error yoho-node-ci@0.0.1 babel: `babel-node "app.js"` | ||
37 | -22 error Exit status 1 | ||
38 | -23 error Failed at the yoho-node-ci@0.0.1 babel script 'babel-node "app.js"'. | ||
39 | -23 error Make sure you have the latest version of node.js and npm installed. | ||
40 | -23 error If you do, this is most likely a problem with the yoho-node-ci package, | ||
41 | -23 error not with npm itself. | ||
42 | -23 error Tell the author that this fails on your system: | ||
43 | -23 error babel-node "app.js" | ||
44 | -23 error You can get information on how to open an issue for this project with: | ||
45 | -23 error npm bugs yoho-node-ci | ||
46 | -23 error Or if that isn't available, you can get their info via: | ||
47 | -23 error npm owner ls yoho-node-ci | ||
48 | -23 error There is likely additional logging output above. | ||
49 | -24 verbose exit [ 1, true ] |
@@ -6,7 +6,7 @@ | @@ -6,7 +6,7 @@ | ||
6 | "scripts": { | 6 | "scripts": { |
7 | "test": "./node_modules/.bin/ava", | 7 | "test": "./node_modules/.bin/ava", |
8 | "babel": "./node_modules/.bin/babel-node", | 8 | "babel": "./node_modules/.bin/babel-node", |
9 | - "dev": "./node_modules/.bin/nodemon -e js,hbs -i public/,packages/ --exec npm run babel -- app.js", | 9 | + "dev": "./node_modules/.bin/nodemon -e js,hbs -i public/ -i packages/ --exec npm run babel -- app.js", |
10 | "start": "./node_modules/.bin/babel-node app.js" | 10 | "start": "./node_modules/.bin/babel-node app.js" |
11 | }, | 11 | }, |
12 | "repository": { | 12 | "repository": { |
@@ -51,9 +51,11 @@ | @@ -51,9 +51,11 @@ | ||
51 | "lodash": "^4.13.1", | 51 | "lodash": "^4.13.1", |
52 | "md5": "^2.1.0", | 52 | "md5": "^2.1.0", |
53 | "md5-file": "^3.1.1", | 53 | "md5-file": "^3.1.1", |
54 | + "memcached": "^2.2.2", | ||
54 | "moment": "^2.13.0", | 55 | "moment": "^2.13.0", |
55 | "nedb": "^1.8.0", | 56 | "nedb": "^1.8.0", |
56 | "nedb-promise": "^2.0.0", | 57 | "nedb-promise": "^2.0.0", |
58 | + "node-zookeeper-client": "^0.2.2", | ||
57 | "qn": "^1.3.0", | 59 | "qn": "^1.3.0", |
58 | "qs": "^6.2.0", | 60 | "qs": "^6.2.0", |
59 | "request-promise": "^4.1.1", | 61 | "request-promise": "^4.1.1", |
-
Please register or login to post a comment