Showing
19 changed files
with
486 additions
and
61 deletions
@@ -5,6 +5,7 @@ import ServerModel from './server'; | @@ -5,6 +5,7 @@ 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 UserModel from './user'; | ||
8 | 9 | ||
9 | shelljs.mkdir('-p', config.dbDir); | 10 | shelljs.mkdir('-p', config.dbDir); |
10 | 11 | ||
@@ -12,10 +13,12 @@ const Server = new ServerModel(); | @@ -12,10 +13,12 @@ const Server = new ServerModel(); | ||
12 | const Building = new BuildingModel(); | 13 | const Building = new BuildingModel(); |
13 | const Project = new ProjectModel(); | 14 | const Project = new ProjectModel(); |
14 | const DeployInfo = new DeployModel(); | 15 | const DeployInfo = new DeployModel(); |
16 | +const User = new UserModel(); | ||
15 | 17 | ||
16 | export { | 18 | export { |
17 | Server, | 19 | Server, |
18 | Building, | 20 | Building, |
19 | Project, | 21 | Project, |
20 | - DeployInfo | 22 | + DeployInfo, |
23 | + User | ||
21 | }; | 24 | }; |
apps/models/user.js
0 → 100644
1 | +/** | ||
2 | + * | ||
3 | + * @author: jiangfeng<jeff.jiang@yoho.cn> | ||
4 | + * @date: 16/8/16 | ||
5 | + */ | ||
6 | + | ||
7 | +'use strict'; | ||
8 | + | ||
9 | +import Model from './model'; | ||
10 | + | ||
11 | +class User extends Model { | ||
12 | + constructor() { | ||
13 | + super('users'); | ||
14 | + } | ||
15 | + | ||
16 | + findByUsername(username) { | ||
17 | + return this.findOne({ | ||
18 | + username: username | ||
19 | + }); | ||
20 | + } | ||
21 | +} | ||
22 | + | ||
23 | +export default User; |
1 | 'use strict'; | 1 | 'use strict'; |
2 | 2 | ||
3 | import Router from 'koa-router'; | 3 | import Router from 'koa-router'; |
4 | +import md5 from 'md5'; | ||
5 | +import {User} from '../../models'; | ||
4 | 6 | ||
5 | let r = new Router(); | 7 | let r = new Router(); |
6 | 8 | ||
@@ -12,9 +14,11 @@ const login = { | @@ -12,9 +14,11 @@ const login = { | ||
12 | let username = ctx.request.body.username; | 14 | let username = ctx.request.body.username; |
13 | let password = ctx.request.body.password; | 15 | let password = ctx.request.body.password; |
14 | 16 | ||
15 | - if (username === 'yoho' && password === 'yoho9646') { | 17 | + let user = await User.findByUsername(username); |
18 | + | ||
19 | + if (user && password && user.password === md5(password)) { | ||
16 | ctx.session = { | 20 | ctx.session = { |
17 | - user: 'yoho' | 21 | + user: user |
18 | }; | 22 | }; |
19 | ctx.redirect('/projects'); | 23 | ctx.redirect('/projects'); |
20 | ctx.status = 301; | 24 | ctx.status = 301; |
@@ -18,6 +18,7 @@ let r = new Router(); | @@ -18,6 +18,7 @@ let r = new Router(); | ||
18 | const colors = ['primary', 'success', 'info', 'warning', 'danger', 'success-alt', 'info-alt', 'warning-alt', 'danger-alt', 'primary-head', 'success-head', 'danger-head']; | 18 | const colors = ['primary', 'success', 'info', 'warning', 'danger', 'success-alt', 'info-alt', 'warning-alt', 'danger-alt', 'primary-head', 'success-head', 'danger-head']; |
19 | const envs = { | 19 | const envs = { |
20 | production: '线上环境', | 20 | production: '线上环境', |
21 | + preview: '灰度环境', | ||
21 | test: '测试环境' | 22 | test: '测试环境' |
22 | }; | 23 | }; |
23 | 24 | ||
@@ -88,6 +89,7 @@ const p = { | @@ -88,6 +89,7 @@ const p = { | ||
88 | } | 89 | } |
89 | } | 90 | } |
90 | }); | 91 | }); |
92 | + | ||
91 | }, | 93 | }, |
92 | edit_page: async(ctx, next) => { | 94 | edit_page: async(ctx, next) => { |
93 | let id = ctx.query.id; | 95 | let id = ctx.query.id; |
apps/web/actions/users.js
0 → 100644
1 | +/** | ||
2 | + * 用户管理 | ||
3 | + * @author: jiangfeng<jeff.jiang@yoho.cn> | ||
4 | + * @date: 16/8/16 | ||
5 | + */ | ||
6 | + | ||
7 | +'use strict'; | ||
8 | +import Router from 'koa-router'; | ||
9 | +import md5 from 'md5'; | ||
10 | +import {User} from '../../models'; | ||
11 | + | ||
12 | +const r = new Router; | ||
13 | + | ||
14 | +const user = { | ||
15 | + async setting_page(ctx) { | ||
16 | + let users = await User.findAll(); | ||
17 | + | ||
18 | + await ctx.render('action/users', { | ||
19 | + users: users | ||
20 | + }); | ||
21 | + }, | ||
22 | + async new_page(ctx) { | ||
23 | + await ctx.render('action/users_form'); | ||
24 | + }, | ||
25 | + async edit_page(ctx) { | ||
26 | + let id = ctx.query.id; | ||
27 | + let user = await User.findById(id); | ||
28 | + | ||
29 | + user.password = "******"; | ||
30 | + await ctx.render('action/users_form', user); | ||
31 | + }, | ||
32 | + async save(ctx) { | ||
33 | + let {_id, username, password, role, state} = ctx.request.body; | ||
34 | + let user = { | ||
35 | + username: username, | ||
36 | + role: role, | ||
37 | + state: state | ||
38 | + }; | ||
39 | + | ||
40 | + if (password !== '******') { | ||
41 | + user.password = md5(password); | ||
42 | + } | ||
43 | + | ||
44 | + if (_id) { | ||
45 | + await User.update({ | ||
46 | + _id: _id | ||
47 | + }, { | ||
48 | + $set: user | ||
49 | + }); | ||
50 | + } else { | ||
51 | + await User.insert(user); | ||
52 | + } | ||
53 | + ctx.redirect('/users/setting'); | ||
54 | + ctx.status = 301; | ||
55 | + }, | ||
56 | + async del(ctx) { | ||
57 | + let id = ctx.request.body.id; | ||
58 | + await User.removeById(id); | ||
59 | + ctx.body = { | ||
60 | + code: 200 | ||
61 | + }; | ||
62 | + } | ||
63 | +}; | ||
64 | + | ||
65 | +r.get('/setting', user.setting_page); | ||
66 | +r.get('/new', user.new_page); | ||
67 | +r.get('/edit', user.edit_page); | ||
68 | +r.post('/save', user.save); | ||
69 | +r.post('/del', user.del); | ||
70 | + | ||
71 | +export default r; |
@@ -19,6 +19,16 @@ app.use(hbs({ | @@ -19,6 +19,16 @@ app.use(hbs({ | ||
19 | helpers: helpers | 19 | helpers: helpers |
20 | })); | 20 | })); |
21 | 21 | ||
22 | +const mastersUrl = [ | ||
23 | + '/projects/new', | ||
24 | + '/projects/edit', | ||
25 | + '/projects/save', | ||
26 | + '/projects/build', | ||
27 | + '/projects/deploy', | ||
28 | + '/servers', | ||
29 | + '/users' | ||
30 | +]; | ||
31 | + | ||
22 | app.use(async(ctx, next) => { | 32 | app.use(async(ctx, next) => { |
23 | ctx.locals = { | 33 | ctx.locals = { |
24 | title: 'Yoho Node.js 持续集成平台' | 34 | title: 'Yoho Node.js 持续集成平台' |
@@ -27,8 +37,23 @@ app.use(async(ctx, next) => { | @@ -27,8 +37,23 @@ app.use(async(ctx, next) => { | ||
27 | if (pjax) { | 37 | if (pjax) { |
28 | ctx.locals.layout = null; | 38 | ctx.locals.layout = null; |
29 | } | 39 | } |
30 | - await next(); | ||
31 | 40 | ||
41 | + if (ctx.session && ctx.session.user ) { | ||
42 | + ctx.locals.is_master = ctx.session.user.role === '1000'; | ||
43 | + ctx.locals.current_user = ctx.session.user; | ||
44 | + } | ||
45 | + | ||
46 | + let needMaster = mastersUrl.some(u => { | ||
47 | + return ctx.request.path.indexOf(u) === 0; | ||
48 | + }); | ||
49 | + | ||
50 | + if (needMaster) { | ||
51 | + if (ctx.locals.is_master) { | ||
52 | + await next(); | ||
53 | + } | ||
54 | + } else { | ||
55 | + await next(); | ||
56 | + } | ||
32 | // if (pjax && ctx.status == 301) { | 57 | // if (pjax && ctx.status == 301) { |
33 | // let location = ctx.response.get('Location'); | 58 | // let location = ctx.response.get('Location'); |
34 | // ctx.response.set('X-PJAX-URL', ctx.origin + location); | 59 | // ctx.response.set('X-PJAX-URL', ctx.origin + location); |
@@ -6,6 +6,7 @@ import projects from './actions/projects'; | @@ -6,6 +6,7 @@ import projects from './actions/projects'; | ||
6 | import servers from './actions/servers'; | 6 | import servers from './actions/servers'; |
7 | import login from './actions/login'; | 7 | import login from './actions/login'; |
8 | import monitor from './actions/monitor'; | 8 | import monitor from './actions/monitor'; |
9 | +import users from './actions/users'; | ||
9 | 10 | ||
10 | const noAuth = new Router(); | 11 | const noAuth = new Router(); |
11 | const base = new Router(); | 12 | const base = new Router(); |
@@ -28,6 +29,7 @@ export default function (app) { | @@ -28,6 +29,7 @@ export default function (app) { | ||
28 | base.use('/projects', projects.routes(), projects.allowedMethods()); | 29 | base.use('/projects', projects.routes(), projects.allowedMethods()); |
29 | base.use('/servers', servers.routes(), servers.allowedMethods()); | 30 | base.use('/servers', servers.routes(), servers.allowedMethods()); |
30 | base.use('/monitor', monitor.routes(), monitor.allowedMethods()); | 31 | base.use('/monitor', monitor.routes(), monitor.allowedMethods()); |
32 | + base.use('/users', users.routes(), users.allowedMethods()); | ||
31 | 33 | ||
32 | base.use('', index.routes(), index.allowedMethods()); | 34 | base.use('', index.routes(), index.allowedMethods()); |
33 | 35 |
@@ -56,8 +56,13 @@ | @@ -56,8 +56,13 @@ | ||
56 | <div class="col-sm-8 col-md-9"> | 56 | <div class="col-sm-8 col-md-9"> |
57 | <div class="panel"> | 57 | <div class="panel"> |
58 | <div class="panel-heading"> | 58 | <div class="panel-heading"> |
59 | + <div class="pull-right"> | ||
60 | + <a class="btn btn-success btn-rounded mr20 realtime-btn">开启实时加载</a> | ||
61 | + </div> | ||
59 | <h4 class="panel-title">日志</h4> | 62 | <h4 class="panel-title">日志</h4> |
60 | <p>可通过滚动鼠标加载</p> | 63 | <p>可通过滚动鼠标加载</p> |
64 | + | ||
65 | + | ||
61 | </div><!-- panel-heading --> | 66 | </div><!-- panel-heading --> |
62 | <div class="panel-body yoho-log-dark"> | 67 | <div class="panel-body yoho-log-dark"> |
63 | <div class="results-list "> | 68 | <div class="results-list "> |
@@ -214,6 +219,7 @@ | @@ -214,6 +219,7 @@ | ||
214 | return; | 219 | return; |
215 | } else { | 220 | } else { |
216 | loading = true; | 221 | loading = true; |
222 | + var scH = $logWrap[0].scrollHeight; | ||
217 | $.get('/monitor/log/query', { | 223 | $.get('/monitor/log/query', { |
218 | influxName: influxName, | 224 | influxName: influxName, |
219 | ip: ip, | 225 | ip: ip, |
@@ -227,20 +233,35 @@ | @@ -227,20 +233,35 @@ | ||
227 | $(data).each(function() { | 233 | $(data).each(function() { |
228 | appendLog(this); | 234 | appendLog(this); |
229 | }); | 235 | }); |
236 | + $logWrap.scrollTop(scH); | ||
230 | }); | 237 | }); |
231 | } | 238 | } |
232 | } | 239 | } |
233 | 240 | ||
234 | var scrollH, scrollTop = 0; | 241 | var scrollH, scrollTop = 0; |
235 | - $logWrap.scroll(function(e) { | 242 | + $logWrap.scroll(function() { |
236 | scrollH = $(this)[0].scrollHeight; | 243 | scrollH = $(this)[0].scrollHeight; |
237 | scrollTop = $(this)[0].scrollTop; | 244 | scrollTop = $(this)[0].scrollTop; |
238 | 245 | ||
239 | if (scrollTop < 10) { | 246 | if (scrollTop < 10) { |
240 | loadOld(); | 247 | loadOld(); |
241 | - } else if (scrollTop + $logWrap.height() + 50 >= scrollH) { | 248 | + } else if (scrollTop + $logWrap.height() + 40 >= scrollH) { |
242 | loadNew(); | 249 | loadNew(); |
243 | } | 250 | } |
244 | }); | 251 | }); |
252 | + | ||
253 | + var sc; | ||
254 | + | ||
255 | + $('.realtime-btn').click(function() { | ||
256 | + if ($(this).hasClass('active')) { | ||
257 | + clearInterval(sc); | ||
258 | + $(this).removeClass('active'); | ||
259 | + $(this).text('开启实时加载'); | ||
260 | + } else { | ||
261 | + $(this).addClass('active'); | ||
262 | + $(this).text('关闭实时加载'); | ||
263 | + sc = setInterval(loadNew, 2000); | ||
264 | + } | ||
265 | + }); | ||
245 | }); | 266 | }); |
246 | </script> | 267 | </script> |
@@ -83,12 +83,13 @@ | @@ -83,12 +83,13 @@ | ||
83 | <div class="col-sm-12"> | 83 | <div class="col-sm-12"> |
84 | <h5 class="lg-title mb10">环境配置</h5> | 84 | <h5 class="lg-title mb10">环境配置</h5> |
85 | <ul class="nav nav-tabs nav-primary"> | 85 | <ul class="nav nav-tabs nav-primary"> |
86 | - <li class="active"><a href="#home4" data-toggle="tab"><strong>线上环境</strong></a></li> | ||
87 | - <li><a href="#profile4" data-toggle="tab"><strong>测试环境</strong></a></li> | 86 | + <li class="active"><a href="#production4" data-toggle="tab"><strong>线上环境</strong></a></li> |
87 | + <li><a href="#preview4" data-toggle="tab"><strong>灰度环境</strong></a></li> | ||
88 | + <li><a href="#test4" data-toggle="tab"><strong>测试环境</strong></a></li> | ||
88 | </ul> | 89 | </ul> |
89 | <!-- Tab panes --> | 90 | <!-- Tab panes --> |
90 | <div class="tab-content tab-content-primary mb30"> | 91 | <div class="tab-content tab-content-primary mb30"> |
91 | - <div class="tab-pane active" id="home4"> | 92 | + <div class="tab-pane active" id="production4"> |
92 | <div class="row"> | 93 | <div class="row"> |
93 | <div class="col-sm-12"> | 94 | <div class="col-sm-12"> |
94 | <div class="form-group"> | 95 | <div class="form-group"> |
@@ -126,8 +127,48 @@ | @@ -126,8 +127,48 @@ | ||
126 | </div> | 127 | </div> |
127 | </div> | 128 | </div> |
128 | </div><!-- tab-pane --> | 129 | </div><!-- tab-pane --> |
129 | - | ||
130 | - <div class="tab-pane" id="profile4"> | 130 | + <div class="tab-pane" id="preview4"> |
131 | + <div class="row"> | ||
132 | + <div class="col-sm-12"> | ||
133 | + <div class="form-group"> | ||
134 | + <label class="control-label">目标服务器</label> | ||
135 | + <div class="col-sm-12"> | ||
136 | + {{#each servers.preview}} | ||
137 | + <div class="checkbox inline-block mr10"> | ||
138 | + <label> | ||
139 | + <input type="checkbox" | ||
140 | + name="deploy[preview][target][{{@index}}]" | ||
141 | + value="{{host}}" | ||
142 | + {{#if checked}}checked=""{{/if}}> {{host}} | ||
143 | + </label> | ||
144 | + </div> | ||
145 | + {{/each}} | ||
146 | + </div> | ||
147 | + </div> | ||
148 | + </div> | ||
149 | + </div> | ||
150 | + <div class="row"> | ||
151 | + <div class="col-sm-6"> | ||
152 | + <div class="form-group"> | ||
153 | + <label class="control-label">对应Git分支</label> | ||
154 | + <input type="text" value="{{project.deploy.preview.branchName}}" | ||
155 | + name="deploy[preview][branchName]" value="" placeholder="master" | ||
156 | + class="form-control"> | ||
157 | + </div> | ||
158 | + </div> | ||
159 | + </div> | ||
160 | + <div class="row"> | ||
161 | + <div class="col-sm-12"> | ||
162 | + <div class="form-group"> | ||
163 | + <label class="control-label">测试URL</label> | ||
164 | + <input type="text" value="{{project.deploy.preview.testUrl}}" | ||
165 | + name="deploy[preview][testUrl]" placeholder="ex: http://{host}:8080/test" | ||
166 | + class="form-control"> | ||
167 | + </div> | ||
168 | + </div> | ||
169 | + </div> | ||
170 | + </div><!-- tab-pane --> | ||
171 | + <div class="tab-pane" id="test4"> | ||
131 | <div class="row"> | 172 | <div class="row"> |
132 | <div class="col-sm-12"> | 173 | <div class="col-sm-12"> |
133 | <div class="form-group"> | 174 | <div class="form-group"> |
@@ -19,8 +19,10 @@ | @@ -19,8 +19,10 @@ | ||
19 | <div class="panel panel-default" data-env='{{deploy.env}}'> | 19 | <div class="panel panel-default" data-env='{{deploy.env}}'> |
20 | <div class="panel-heading"> | 20 | <div class="panel-heading"> |
21 | <div class="pull-right"> | 21 | <div class="pull-right"> |
22 | + {{#if is_master}} | ||
22 | <a class="btn btn-info btn-rounded mr5 log-btn"><i class="fa fa-eye"></i> 查看构建日志</a> | 23 | <a class="btn btn-info btn-rounded mr5 log-btn"><i class="fa fa-eye"></i> 查看构建日志</a> |
23 | <a class="btn btn-success btn-rounded mr20 build-btn"><i class="glyphicon glyphicon-plus"></i> 新增构建</a> | 24 | <a class="btn btn-success btn-rounded mr20 build-btn"><i class="glyphicon glyphicon-plus"></i> 新增构建</a> |
25 | + {{/if}} | ||
24 | <a href="" class="tooltips panel-minimize"><i class="fa fa-minus"></i></a> | 26 | <a href="" class="tooltips panel-minimize"><i class="fa fa-minus"></i></a> |
25 | </div> | 27 | </div> |
26 | <h4 class="panel-title">{{deploy.name}}</h4> | 28 | <h4 class="panel-title">{{deploy.name}}</h4> |
@@ -57,13 +59,16 @@ | @@ -57,13 +59,16 @@ | ||
57 | <div class="panel-icon"><i class="fa fa-cloud" style="padding-left:12px;"></i></div> | 59 | <div class="panel-icon"><i class="fa fa-cloud" style="padding-left:12px;"></i></div> |
58 | <div class="media-body"> | 60 | <div class="media-body"> |
59 | <h2 class="nomargin">{{host}}</h2> | 61 | <h2 class="nomargin">{{host}}</h2> |
60 | - <h5 class="md-title mt5">当前运行版本: <code>{{#if info}}{{info.building}}{{^}}未知部署{{/if}}</code></h5> | 62 | + <h5 class="md-title mt5">当前运行版本: <code>{{#if info}}{{info.building}}{{^}} |
63 | + 未知部署{{/if}}</code></h5> | ||
61 | </div><!-- media-body --> | 64 | </div><!-- media-body --> |
62 | <hr class="mt10 mb10"> | 65 | <hr class="mt10 mb10"> |
63 | <div class="clearfix mt5"> | 66 | <div class="clearfix mt5"> |
64 | <div class="col-xs-6 project-env" data-env="test"> | 67 | <div class="col-xs-6 project-env" data-env="test"> |
65 | <h5 class="md-title mt10">当前状态</h5> | 68 | <h5 class="md-title mt10">当前状态</h5> |
66 | - <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> | 69 | + <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}}{{^}} | ||
71 | + 未知部署{{/if}}</b></span> | ||
67 | </div> | 72 | </div> |
68 | <div class="col-xs-6"> | 73 | <div class="col-xs-6"> |
69 | 74 | ||
@@ -86,32 +91,32 @@ | @@ -86,32 +91,32 @@ | ||
86 | 91 | ||
87 | <script> | 92 | <script> |
88 | 93 | ||
89 | - $(function(){ | 94 | + $(function() { |
90 | 95 | ||
91 | var tables = {}; | 96 | var tables = {}; |
92 | - $('.building-table').each(function(){ | 97 | + $('.building-table').each(function() { |
93 | var env = $(this).parents('.panel').data('env'); | 98 | var env = $(this).parents('.panel').data('env'); |
94 | tables[env] = $(this).DataTable({ | 99 | tables[env] = $(this).DataTable({ |
95 | responsive: true, | 100 | responsive: true, |
96 | ajax: '/projects/{{project._id}}/buildings?env=' + env, | 101 | ajax: '/projects/{{project._id}}/buildings?env=' + env, |
97 | columns: [ | 102 | columns: [ |
98 | - { data: "buildTime"}, | ||
99 | - { data: "branch"}, | ||
100 | - { data: "state"}, | ||
101 | - { data: "updatedAt"}, | ||
102 | - { data: "_id" } | 103 | + {data: "buildTime"}, |
104 | + {data: "branch"}, | ||
105 | + {data: "state"}, | ||
106 | + {data: "updatedAt"}, | ||
107 | + {data: "_id"} | ||
103 | ], | 108 | ], |
104 | order: [[0, 'desc']], | 109 | order: [[0, 'desc']], |
105 | columnDefs: [{ | 110 | columnDefs: [{ |
106 | - render: function(data, type, row){ | 111 | + render: function(data, type, row) { |
107 | var color = 'warning'; | 112 | var color = 'warning'; |
108 | - if(data == 'success'){ | 113 | + if (data == 'success') { |
109 | color = 'success'; | 114 | color = 'success'; |
110 | - }else if(data == 'fail'){ | 115 | + } else if (data == 'fail') { |
111 | color = 'danger'; | 116 | color = 'danger'; |
112 | } | 117 | } |
113 | - var html = '<span id="b-'+row._id+'" class="label label-' + color + '">'; | ||
114 | - if(data != 'success' && data != 'fail'){ | 118 | + var html = '<span id="b-' + row._id + '" class="label label-' + color + '">'; |
119 | + if (data != 'success' && data != 'fail') { | ||
115 | html += '<i class="fa fa-spinner fa-spin fa-fw margin-bottom"></i>'; | 120 | html += '<i class="fa fa-spinner fa-spin fa-fw margin-bottom"></i>'; |
116 | } | 121 | } |
117 | html += data + '</span>'; | 122 | html += data + '</span>'; |
@@ -119,45 +124,49 @@ | @@ -119,45 +124,49 @@ | ||
119 | }, | 124 | }, |
120 | targets: 2 | 125 | targets: 2 |
121 | }, { | 126 | }, { |
122 | - render: function(data, type, row){ | 127 | + render: function(data, type, row) { |
123 | var disabled = row.state !== 'success'; | 128 | var disabled = row.state !== 'success'; |
124 | - return '<button '+(disabled? 'disabled' : '')+' class="btn btn-success btn-xs deploy-btn" data-id="'+data+'" data-build='+row.buildTime+'>分发</button>'; | 129 | + {{#if is_master}} |
130 | + return '<button ' + (disabled ? 'disabled' : '') + ' class="btn btn-success btn-xs deploy-btn" data-id="' + data + '" data-build=' + row.buildTime + '>分发</button>'; | ||
131 | + {{^}} | ||
132 | + return ''; | ||
133 | + {{/if}} | ||
125 | }, | 134 | }, |
126 | targets: 4 | 135 | targets: 4 |
127 | }] | 136 | }] |
128 | }); | 137 | }); |
129 | 138 | ||
130 | - $(this).on( 'draw.dt', function () { | ||
131 | - $('.deploy-btn').click(function(){ | 139 | + $(this).on('draw.dt', function() { |
140 | + $('.deploy-btn').click(function() { | ||
132 | var id = $(this).data('id'); | 141 | var id = $(this).data('id'); |
133 | var build = $(this).data('build'); | 142 | var build = $(this).data('build'); |
134 | layer.confirm('确定发布版本<code>' + build + '</code>吗?', { | 143 | layer.confirm('确定发布版本<code>' + build + '</code>吗?', { |
135 | btn: ['确定', '取消'] | 144 | btn: ['确定', '取消'] |
136 | - }, function(){ | 145 | + }, function() { |
137 | doDeploy(id); | 146 | doDeploy(id); |
138 | }); | 147 | }); |
139 | }); | 148 | }); |
140 | - } ); | 149 | + }); |
141 | }); | 150 | }); |
142 | 151 | ||
143 | - function doDeploy(build){ | 152 | + function doDeploy(build) { |
144 | $.post('/projects/deploy/' + build, function(ret) { | 153 | $.post('/projects/deploy/' + build, function(ret) { |
145 | - if(ret.code == 200){ | 154 | + if (ret.code == 200) { |
146 | layer.msg('正在分发中'); | 155 | layer.msg('正在分发中'); |
147 | - }else{ | 156 | + } else { |
148 | layer.msg(ret.msg, {icon: 5}); | 157 | layer.msg(ret.msg, {icon: 5}); |
149 | } | 158 | } |
150 | }); | 159 | }); |
151 | } | 160 | } |
152 | 161 | ||
153 | - $('.build-btn').click(function(){ | 162 | + $('.build-btn').click(function() { |
154 | var env = $(this).parents('.panel').data('env'); | 163 | var env = $(this).parents('.panel').data('env'); |
155 | var i = layer.prompt({ | 164 | var i = layer.prompt({ |
156 | title: '请输入需要构建的分支,默认为 {{deploy.branchName}}' | 165 | title: '请输入需要构建的分支,默认为 {{deploy.branchName}}' |
157 | - }, function(branch){ | 166 | + }, function(branch) { |
158 | branch = branch || '{{deploy.branchName}}'; | 167 | branch = branch || '{{deploy.branchName}}'; |
159 | - $.post('/projects/build/{{project._id}}', {env: env, branch: branch}, function(ret){ | ||
160 | - if(ret.code == 200) { | 168 | + $.post('/projects/build/{{project._id}}', {env: env, branch: branch}, function(ret) { |
169 | + if (ret.code == 200) { | ||
161 | tables[env].ajax.reload(); | 170 | tables[env].ajax.reload(); |
162 | layer.close(i); | 171 | layer.close(i); |
163 | } | 172 | } |
@@ -167,10 +176,10 @@ | @@ -167,10 +176,10 @@ | ||
167 | }); | 176 | }); |
168 | 177 | ||
169 | 178 | ||
170 | - $('.rollback-btn').click(function(){ | 179 | + $('.rollback-btn').click(function() { |
171 | layer.prompt({ | 180 | layer.prompt({ |
172 | title: '请输入回滚到哪个版本' | 181 | title: '请输入回滚到哪个版本' |
173 | - }, function(build){ | 182 | + }, function(build) { |
174 | doDeploy(build); | 183 | doDeploy(build); |
175 | }); | 184 | }); |
176 | }); | 185 | }); |
@@ -182,16 +191,16 @@ | @@ -182,16 +191,16 @@ | ||
182 | 191 | ||
183 | var tag; | 192 | var tag; |
184 | 193 | ||
185 | - $('.log-btn').click(function(){ | 194 | + $('.log-btn').click(function() { |
186 | viewLog(''); | 195 | viewLog(''); |
187 | }); | 196 | }); |
188 | 197 | ||
189 | - $('.deploy-log-btn').click(function(){ | 198 | + $('.deploy-log-btn').click(function() { |
190 | var host = $(this).data('host'); | 199 | var host = $(this).data('host'); |
191 | viewLog(host); | 200 | viewLog(host); |
192 | }); | 201 | }); |
193 | 202 | ||
194 | - function viewLog(tag){ | 203 | + function viewLog(tag) { |
195 | tag = tag; | 204 | tag = tag; |
196 | $('#log-area').show(); | 205 | $('#log-area').show(); |
197 | cm.setValue(""); | 206 | cm.setValue(""); |
@@ -201,23 +210,23 @@ | @@ -201,23 +210,23 @@ | ||
201 | title: '实时日志', | 210 | title: '实时日志', |
202 | content: $('#log-area'), | 211 | content: $('#log-area'), |
203 | area: ['1000px', '600px'], | 212 | area: ['1000px', '600px'], |
204 | - maxmin:true, | ||
205 | - cancel: function(){ | 213 | + maxmin: true, |
214 | + cancel: function() { | ||
206 | $('#log-area').hide(); | 215 | $('#log-area').hide(); |
207 | } | 216 | } |
208 | }); | 217 | }); |
209 | } | 218 | } |
210 | 219 | ||
211 | var ws = io(); | 220 | var ws = io(); |
212 | - ws.on('connect', function(){ | ||
213 | - ws.on('/building/{{project._id}}', function(data){ | 221 | + ws.on('connect', function() { |
222 | + ws.on('/building/{{project._id}}', function(data) { | ||
214 | console.log(data); | 223 | console.log(data); |
215 | $('#b-' + data.bid).text(data.state); | 224 | $('#b-' + data.bid).text(data.state); |
216 | - if(data.state == 'success'){ | 225 | + if (data.state == 'success') { |
217 | $('#b-' + data.bid).removeClass('label-warning').addClass('label-success') | 226 | $('#b-' + data.bid).removeClass('label-warning').addClass('label-success') |
218 | $('#b-' + data.bid).parent().parent().find('.deploy-btn').removeAttr('disabled'); | 227 | $('#b-' + data.bid).parent().parent().find('.deploy-btn').removeAttr('disabled'); |
219 | $('#b-' + data.bid).find('i').remove(); | 228 | $('#b-' + data.bid).find('i').remove(); |
220 | - }else if(data.state == 'fail'){ | 229 | + } else if (data.state == 'fail') { |
221 | $('#b-' + data.bid).removeClass('label-warning').addClass('label-danger'); | 230 | $('#b-' + data.bid).removeClass('label-warning').addClass('label-danger'); |
222 | $('#b-' + data.bid).find('i').remove(); | 231 | $('#b-' + data.bid).find('i').remove(); |
223 | } | 232 | } |
@@ -229,7 +238,7 @@ | @@ -229,7 +238,7 @@ | ||
229 | // } | 238 | // } |
230 | // }); | 239 | // }); |
231 | 240 | ||
232 | - ws.on('/deploy/{{project._id}}', function(data){ | 241 | + ws.on('/deploy/{{project._id}}', function(data) { |
233 | console.log(data); | 242 | console.log(data); |
234 | $('#d-' + data.host.replace(/\./g, '-')).find('b').text(data.state); | 243 | $('#d-' + data.host.replace(/\./g, '-')).find('b').text(data.state); |
235 | }); | 244 | }); |
@@ -240,7 +249,7 @@ | @@ -240,7 +249,7 @@ | ||
240 | // } | 249 | // } |
241 | // }); | 250 | // }); |
242 | }); | 251 | }); |
243 | - ws.on('error', function(){ | 252 | + ws.on('error', function() { |
244 | console.log('connect fail'); | 253 | console.log('connect fail'); |
245 | }); | 254 | }); |
246 | }); | 255 | }); |
@@ -22,7 +22,10 @@ | @@ -22,7 +22,10 @@ | ||
22 | <div class="panel panel-{{color}} noborder"> | 22 | <div class="panel panel-{{color}} noborder"> |
23 | <div class="panel-heading noborder"> | 23 | <div class="panel-heading noborder"> |
24 | <div class="panel-btns"> | 24 | <div class="panel-btns"> |
25 | - <a href="/projects/edit?id={{_id}}" class="tooltips" title="设置"><i class="fa fa-gear"></i></a> | 25 | + {{#if @root.is_master}} |
26 | + <a href="/projects/edit?id={{_id}}" class="tooltips" title="设置"><i | ||
27 | + class="fa fa-gear"></i></a> | ||
28 | + {{/if}} | ||
26 | </div><!-- panel-btns --> | 29 | </div><!-- panel-btns --> |
27 | <div class="panel-icon"><i class="fa fa-git" style="padding-left:12px;"></i></div> | 30 | <div class="panel-icon"><i class="fa fa-git" style="padding-left:12px;"></i></div> |
28 | <div class="media-body"> | 31 | <div class="media-body"> |
@@ -31,11 +34,15 @@ | @@ -31,11 +34,15 @@ | ||
31 | </div><!-- media-body --> | 34 | </div><!-- media-body --> |
32 | <hr> | 35 | <hr> |
33 | <div class="clearfix mt20"> | 36 | <div class="clearfix mt20"> |
34 | - <div class="col-xs-6 project-env" data-id="{{_id}}" data-env="production"> | 37 | + <div class="col-xs-4 project-env" data-id="{{_id}}" data-env="production"> |
35 | <h5 class="md-title nomargin">线上环境</h5> | 38 | <h5 class="md-title nomargin">线上环境</h5> |
36 | <h4 class="nomargin">{{deploy.production.target.length}}</h4> | 39 | <h4 class="nomargin">{{deploy.production.target.length}}</h4> |
37 | </div> | 40 | </div> |
38 | - <div class="col-xs-6 project-env" data-id="{{_id}}" data-env="test"> | 41 | + <div class="col-xs-4 project-env" data-id="{{_id}}" data-env="preview"> |
42 | + <h5 class="md-title nomargin">灰度环境</h5> | ||
43 | + <h4 class="nomargin">{{deploy.preview.target.length}}</h4> | ||
44 | + </div> | ||
45 | + <div class="col-xs-4 project-env" data-id="{{_id}}" data-env="test"> | ||
39 | <h5 class="md-title nomargin">测试环境</h5> | 46 | <h5 class="md-title nomargin">测试环境</h5> |
40 | <h4 class="nomargin">{{deploy.test.target.length}}</h4> | 47 | <h4 class="nomargin">{{deploy.test.target.length}}</h4> |
41 | </div> | 48 | </div> |
@@ -44,6 +51,7 @@ | @@ -44,6 +51,7 @@ | ||
44 | </div><!-- panel --> | 51 | </div><!-- panel --> |
45 | </div><!-- col-md-4 --> | 52 | </div><!-- col-md-4 --> |
46 | {{/each}} | 53 | {{/each}} |
54 | + {{#if is_master}} | ||
47 | <div class="col-md-4"> | 55 | <div class="col-md-4"> |
48 | <div class="panel panel-default noborder"> | 56 | <div class="panel panel-default noborder"> |
49 | <div class="panel-heading noborder"> | 57 | <div class="panel-heading noborder"> |
@@ -55,6 +63,7 @@ | @@ -55,6 +63,7 @@ | ||
55 | </div> | 63 | </div> |
56 | </div> | 64 | </div> |
57 | </div> | 65 | </div> |
66 | + {{/if}} | ||
58 | </div><!-- row --> | 67 | </div><!-- row --> |
59 | </div> | 68 | </div> |
60 | 69 | ||
@@ -62,7 +71,7 @@ | @@ -62,7 +71,7 @@ | ||
62 | <script> | 71 | <script> |
63 | $(function() { | 72 | $(function() { |
64 | $('.servers-page').pjax('a', '#pjax-container'); | 73 | $('.servers-page').pjax('a', '#pjax-container'); |
65 | - $('.project-env').click(function(){ | 74 | + $('.project-env').click(function() { |
66 | var id = $(this).data('id'); | 75 | var id = $(this).data('id'); |
67 | var env = $(this).data('env'); | 76 | var env = $(this).data('env'); |
68 | $.pjax({ | 77 | $.pjax({ |
@@ -73,7 +73,14 @@ | @@ -73,7 +73,14 @@ | ||
73 | </div> | 73 | </div> |
74 | </div> | 74 | </div> |
75 | </div> | 75 | </div> |
76 | - <div class="col-sm-4"> | 76 | + <div class="col-sm-2"> |
77 | + <label class="control-label"> </label> | ||
78 | + <div class="rdio rdio-primary"> | ||
79 | + <input type="radio" name="env" id="previewEnv" value="preview" {{#equals env 'preview'}}checked="checked"{{/equals}}> | ||
80 | + <label for="previewEnv">灰度环境</label> | ||
81 | + </div> | ||
82 | + </div> | ||
83 | + <div class="col-sm-2"> | ||
77 | <label class="control-label"> </label> | 84 | <label class="control-label"> </label> |
78 | <div class="rdio rdio-primary"> | 85 | <div class="rdio rdio-primary"> |
79 | <input type="radio" name="env" id="testEnv" value="test" {{#equals env 'test'}}checked="checked"{{/equals}}> | 86 | <input type="radio" name="env" id="testEnv" value="test" {{#equals env 'test'}}checked="checked"{{/equals}}> |
@@ -46,7 +46,7 @@ | @@ -46,7 +46,7 @@ | ||
46 | <td>{{host}}</td> | 46 | <td>{{host}}</td> |
47 | <td>{{username}}</td> | 47 | <td>{{username}}</td> |
48 | <td>{{port}}</td> | 48 | <td>{{port}}</td> |
49 | - <td><span class="label label-primary">{{envName}}</span></td> | 49 | + <td><span class="label label-primary {{env}}">{{envName}}</span></td> |
50 | <td>{{deployDir}}</td> | 50 | <td>{{deployDir}}</td> |
51 | <td data-id='{{_id}}'> | 51 | <td data-id='{{_id}}'> |
52 | <button class="btn btn-success btn-xs server-edit">修改</button> | 52 | <button class="btn btn-success btn-xs server-edit">修改</button> |
apps/web/views/action/users.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><a href="">系统设置</a></li> | ||
10 | + <li>用户管理</li> | ||
11 | + </ul> | ||
12 | + <h4>用户管理</h4> | ||
13 | + </div> | ||
14 | + </div> | ||
15 | + <!-- media --> | ||
16 | +</div> | ||
17 | +<!-- pageheader --> | ||
18 | + | ||
19 | +<div class="contentpanel"> | ||
20 | + <div class="panel panel-primary-head"> | ||
21 | + <div class="panel-heading"> | ||
22 | + <div class="pull-right"> | ||
23 | + <a id="new-page" href="/users/new" class="btn btn-success btn-rounded"><i class="glyphicon glyphicon-plus"></i> 新增用户</a> | ||
24 | + </div> | ||
25 | + <h4 class="panel-title">用户管理</h4> | ||
26 | + <p> </p> | ||
27 | + </div> | ||
28 | + <!-- panel-heading --> | ||
29 | + | ||
30 | + <table id="basicTable" class="table table-striped table-bordered responsive"> | ||
31 | + <thead class=""> | ||
32 | + <tr> | ||
33 | + <th>用户名</th> | ||
34 | + <th>角色</th> | ||
35 | + <th>状态</th> | ||
36 | + <th></th> | ||
37 | + </tr> | ||
38 | + </thead> | ||
39 | + | ||
40 | + <tbody> | ||
41 | + {{#each users}} | ||
42 | + <tr> | ||
43 | + <td>{{username}}</td> | ||
44 | + <td>{{#equals role '1000'}}<span class="label label-primary">运维</span>{{^}}<span class="label label-success">开发</span>{{/equals}}</td> | ||
45 | + <td>{{#equals state '1'}}<span class="label label-success">启用</span>{{^}}<span class="label label-danger">禁用</span>{{/equals}}</td> | ||
46 | + <td data-id='{{_id}}'> | ||
47 | + <button class="btn btn-success btn-xs server-edit">修改</button> | ||
48 | + <button class="btn btn-danger btn-xs server-del">删除</button> | ||
49 | + </td> | ||
50 | + </tr> | ||
51 | + {{/each}} | ||
52 | + </tbody> | ||
53 | + </table> | ||
54 | + </div> | ||
55 | + <!-- panel --> | ||
56 | +</div> | ||
57 | + | ||
58 | + | ||
59 | +<script> | ||
60 | + $(function() { | ||
61 | + $("#basicTable").DataTable({ | ||
62 | + responsive: true | ||
63 | + }); | ||
64 | + $(document).pjax('#new-page', '#pjax-container') | ||
65 | + | ||
66 | + $('.server-del').click(function(){ | ||
67 | + var id = $(this).parent().data('id'); | ||
68 | + $.post('/users/del', {id: id}, function(ret){ | ||
69 | + if(ret.code == 200){ | ||
70 | + var i = layer.alert('操作成功', function(){ | ||
71 | + layer.close(i); | ||
72 | + $.pjax.reload('#pjax-container'); | ||
73 | + }); | ||
74 | + } | ||
75 | + }); | ||
76 | + }); | ||
77 | + | ||
78 | + $('.server-edit').click(function(){ | ||
79 | + var id = $(this).parent().data('id'); | ||
80 | + $.pjax({ | ||
81 | + url: '/users/edit?id=' + id, | ||
82 | + container: '#pjax-container' | ||
83 | + }); | ||
84 | + }); | ||
85 | + }); | ||
86 | + | ||
87 | +</script> |
apps/web/views/action/users_form.hbs
0 → 100644
1 | + | ||
2 | +<div class="pageheader"> | ||
3 | + <div class="media"> | ||
4 | + <div class="pageicon pull-left"> | ||
5 | + <i class="fa fa-th-list"></i> | ||
6 | + </div> | ||
7 | + <div class="media-body"> | ||
8 | + <ul class="breadcrumb"> | ||
9 | + <li><a href=""><i class="glyphicon glyphicon-home"></i></a></li> | ||
10 | + <li><a href="">服务器管理</a></li> | ||
11 | + <li>用户信息</li> | ||
12 | + </ul> | ||
13 | + <h4>用户信息</h4> | ||
14 | + </div> | ||
15 | + </div> | ||
16 | + <!-- media --> | ||
17 | +</div> | ||
18 | +<!-- pageheader --> | ||
19 | +<div class="panel panel-default"> | ||
20 | + <div class="panel-heading"> | ||
21 | + <p> </p> | ||
22 | + </div> | ||
23 | + <form action="/users/save" method="POST" data-pjax> | ||
24 | + <input type="hidden" name="_id" value="{{_id}}"> | ||
25 | + <div class="panel-body"> | ||
26 | + <div class="row"> | ||
27 | + <div class="col-sm-6"> | ||
28 | + <div class="form-group"> | ||
29 | + <label class="control-label">Username</label> | ||
30 | + <input type="text" name="username" value="{{username}}" class="form-control" placeholder="用户名"> | ||
31 | + </div> | ||
32 | + <!-- form-group --> | ||
33 | + </div> | ||
34 | + <!-- col-sm-6 --> | ||
35 | + <div class="col-sm-6"> | ||
36 | + <div class="form-group"> | ||
37 | + <label class="control-label">Password</label> | ||
38 | + <input type="text" name="password" value="{{password}}" class="form-control" placeholder="密码"> | ||
39 | + </div> | ||
40 | + <!-- form-group --> | ||
41 | + </div> | ||
42 | + <!-- col-sm-6 --> | ||
43 | + </div> | ||
44 | + <!-- row --> | ||
45 | + <div class="row"> | ||
46 | + <div class="col-sm-2"> | ||
47 | + <div class="form-group"> | ||
48 | + <label class="control-label">Role</label> | ||
49 | + <div class="rdio rdio-primary"> | ||
50 | + <input type="radio" name="role" id="role1" value="1000" {{#equals role '1000'}}checked="checked"{{/equals}}> | ||
51 | + <label for="role1">运维</label> | ||
52 | + </div> | ||
53 | + </div> | ||
54 | + </div> | ||
55 | + <div class="col-sm-4"> | ||
56 | + <label class="control-label"> </label> | ||
57 | + <div class="rdio rdio-primary"> | ||
58 | + <input type="radio" name="role" id="role2" value="2000" {{#equals role '2000'}}checked="checked"{{/equals}}> | ||
59 | + <label for="role2">开发</label> | ||
60 | + </div> | ||
61 | + </div> | ||
62 | + <div class="col-sm-2"> | ||
63 | + <div class="form-group"> | ||
64 | + <label class="control-label">状态</label> | ||
65 | + <div class="rdio rdio-primary"> | ||
66 | + <input type="radio" name="state" id="state1" value="1" {{#equals state '1'}}checked="checked"{{/equals}}> | ||
67 | + <label for="state1">启用</label> | ||
68 | + </div> | ||
69 | + </div> | ||
70 | + </div> | ||
71 | + <div class="col-sm-4"> | ||
72 | + <label class="control-label"> </label> | ||
73 | + <div class="rdio rdio-primary"> | ||
74 | + <input type="radio" name="state" id="state2" value="0" {{#equals state '0'}}checked="checked"{{/equals}}> | ||
75 | + <label for="state2">禁用</label> | ||
76 | + </div> | ||
77 | + </div> | ||
78 | + </div> | ||
79 | + <!-- row --> | ||
80 | + </div> | ||
81 | + <!-- panel-body --> | ||
82 | + <div class="panel-footer"> | ||
83 | + <button type="submit" class="btn btn-primary">保存</button> | ||
84 | + <button type="button" class="btn btn-default ml20 go-back">取消</button> | ||
85 | + </div> | ||
86 | + </form> | ||
87 | + <!-- panel-footer --> | ||
88 | +</div> | ||
89 | + | ||
90 | +<script> | ||
91 | + $(function(){ | ||
92 | + $(document.body).off().on('submit', 'form[data-pjax]', function(event) { | ||
93 | + event.preventDefault(); // stop default submit behavior | ||
94 | + $.pjax.submit(event, '#pjax-container', { | ||
95 | + type: 'POST' | ||
96 | + }); | ||
97 | + }); | ||
98 | + | ||
99 | + $('.go-back').click(function() { | ||
100 | + window.history.go(-1); | ||
101 | + }); | ||
102 | + }); | ||
103 | +</script> | ||
104 | + |
@@ -4,7 +4,7 @@ | @@ -4,7 +4,7 @@ | ||
4 | <img class="img-circle" src="/images/photos/profile.png" alt=""> | 4 | <img class="img-circle" src="/images/photos/profile.png" alt=""> |
5 | </a> | 5 | </a> |
6 | <div class="media-body"> | 6 | <div class="media-body"> |
7 | - <h4 class="media-heading">Admin</h4> | 7 | + <h4 class="media-heading">{{current_user.username}}</h4> |
8 | <small class="text-muted">yoho-ci</small> | 8 | <small class="text-muted">yoho-ci</small> |
9 | </div> | 9 | </div> |
10 | </div> | 10 | </div> |
@@ -13,17 +13,20 @@ | @@ -13,17 +13,20 @@ | ||
13 | <h5 class="leftpanel-title">Navigation</h5> | 13 | <h5 class="leftpanel-title">Navigation</h5> |
14 | <ul class="nav nav-pills nav-stacked nav-menu"> | 14 | <ul class="nav nav-pills nav-stacked nav-menu"> |
15 | <li class="active"><a href="/index"><i class="fa fa-home"></i> <span>Dashboard</span></a></li> | 15 | <li class="active"><a href="/index"><i class="fa fa-home"></i> <span>Dashboard</span></a></li> |
16 | - <li><a href="/projects"><i class="glyphicon glyphicon-th"></i> <span>Projects</span></a></li> | 16 | + <li><a href="/projects"><i class="glyphicon glyphicon-th"></i> <span>项目</span></a></li> |
17 | <li class="parent"><a href=""><i class="fa fa-gears"></i> <span>监控中心</span></a> | 17 | <li class="parent"><a href=""><i class="fa fa-gears"></i> <span>监控中心</span></a> |
18 | <ul class="children"> | 18 | <ul class="children"> |
19 | <li><a href="/monitor/log">实时日志</a></li> | 19 | <li><a href="/monitor/log">实时日志</a></li> |
20 | </ul> | 20 | </ul> |
21 | </li> | 21 | </li> |
22 | - <li class="parent"><a href=""><i class="fa fa-gears"></i> <span>Settings</span></a> | 22 | + {{#if is_master}} |
23 | + <li class="parent"><a href=""><i class="fa fa-gears"></i> <span>系统配置</span></a> | ||
23 | <ul class="children"> | 24 | <ul class="children"> |
24 | - <li><a href="/servers/setting">Servers</a></li> | 25 | + <li><a href="/servers/setting">服务器配置</a></li> |
26 | + <li><a href="/users/setting">用户管理</a></li> | ||
25 | </ul> | 27 | </ul> |
26 | </li> | 28 | </li> |
29 | + {{/if}} | ||
27 | </ul> | 30 | </ul> |
28 | 31 | ||
29 | </div> | 32 | </div> |
@@ -47,6 +47,7 @@ | @@ -47,6 +47,7 @@ | ||
47 | "koa-session": "^3.3.1", | 47 | "koa-session": "^3.3.1", |
48 | "koa-static": "^3.0.0", | 48 | "koa-static": "^3.0.0", |
49 | "lodash": "^4.13.1", | 49 | "lodash": "^4.13.1", |
50 | + "md5": "^2.1.0", | ||
50 | "moment": "^2.13.0", | 51 | "moment": "^2.13.0", |
51 | "nedb": "^1.8.0", | 52 | "nedb": "^1.8.0", |
52 | "nedb-promise": "^2.0.0", | 53 | "nedb-promise": "^2.0.0", |
@@ -80,3 +80,15 @@ body .mainwrapper .leftpanel { | @@ -80,3 +80,15 @@ body .mainwrapper .leftpanel { | ||
80 | width: 140px; | 80 | width: 140px; |
81 | display: inline-block; | 81 | display: inline-block; |
82 | } | 82 | } |
83 | + | ||
84 | +.label.production { | ||
85 | + background-color: #428bca; | ||
86 | +} | ||
87 | + | ||
88 | +.label.preview { | ||
89 | + background-color: #F5A503; | ||
90 | +} | ||
91 | + | ||
92 | +.label.test { | ||
93 | + background-color: #5cb85c; | ||
94 | +} |
-
Please register or login to post a comment