Showing
21 changed files
with
572 additions
and
10 deletions
apps/logger/operation.js
0 → 100644
1 | +/** | ||
2 | + * 用户操作日志 | ||
3 | + * @author: jiangfeng<jeff.jiang@yoho.cn> | ||
4 | + * @date: 2016/8/22 | ||
5 | + */ | ||
6 | + | ||
7 | +import {OperationLogger} from '../models'; | ||
8 | + | ||
9 | +const OperationLog = { | ||
10 | + | ||
11 | + async action(user, action, description, meta) { | ||
12 | + await OperationLogger.insert({ | ||
13 | + user: { | ||
14 | + id: user._id, | ||
15 | + username: user.username | ||
16 | + }, | ||
17 | + action: action, | ||
18 | + description: description, | ||
19 | + meta: meta, | ||
20 | + time: new Date() | ||
21 | + }); | ||
22 | + } | ||
23 | +}; | ||
24 | + | ||
25 | +export default OperationLog; |
apps/models/hotfix.js
0 → 100644
@@ -6,6 +6,8 @@ import BuildingModel from './building'; | @@ -6,6 +6,8 @@ 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 | import UserModel from './user'; |
9 | +import HotfixModel from './hotfix'; | ||
10 | +import OperationLoggerModel from './operation_logger'; | ||
9 | 11 | ||
10 | shelljs.mkdir('-p', config.dbDir); | 12 | shelljs.mkdir('-p', config.dbDir); |
11 | 13 | ||
@@ -14,11 +16,17 @@ const Building = new BuildingModel(); | @@ -14,11 +16,17 @@ const Building = new BuildingModel(); | ||
14 | const Project = new ProjectModel(); | 16 | const Project = new ProjectModel(); |
15 | const DeployInfo = new DeployModel(); | 17 | const DeployInfo = new DeployModel(); |
16 | const User = new UserModel(); | 18 | const User = new UserModel(); |
19 | +const Hotfix = new HotfixModel(); | ||
20 | +const OperationLogger = new OperationLoggerModel(); | ||
21 | + | ||
22 | +User.init(); | ||
17 | 23 | ||
18 | export { | 24 | export { |
19 | Server, | 25 | Server, |
20 | Building, | 26 | Building, |
21 | Project, | 27 | Project, |
22 | DeployInfo, | 28 | DeployInfo, |
23 | - User | 29 | + User, |
30 | + Hotfix, | ||
31 | + OperationLogger | ||
24 | }; | 32 | }; |
apps/models/operation_logger.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 OperationLogger extends Model { | ||
12 | + | ||
13 | + constructor() { | ||
14 | + super('operation_logger'); | ||
15 | + } | ||
16 | + | ||
17 | +} | ||
18 | + | ||
19 | +export default OperationLogger; |
@@ -7,12 +7,26 @@ | @@ -7,12 +7,26 @@ | ||
7 | 'use strict'; | 7 | 'use strict'; |
8 | 8 | ||
9 | import Model from './model'; | 9 | import Model from './model'; |
10 | +import md5 from 'md5'; | ||
10 | 11 | ||
11 | class User extends Model { | 12 | class User extends Model { |
12 | constructor() { | 13 | constructor() { |
13 | super('users'); | 14 | super('users'); |
14 | } | 15 | } |
15 | 16 | ||
17 | + async init() { | ||
18 | + let count = await this.count({}); | ||
19 | + | ||
20 | + if (count === 0) { | ||
21 | + await this.insert({ | ||
22 | + "username": "yoho", | ||
23 | + "password": md5("yoho9646"), | ||
24 | + "role": "1000", | ||
25 | + "state": "1" | ||
26 | + }); | ||
27 | + } | ||
28 | + } | ||
29 | + | ||
16 | findByUsername(username) { | 30 | findByUsername(username) { |
17 | return this.findOne({ | 31 | return this.findOne({ |
18 | username: username | 32 | username: username |
apps/web/actions/hotfix.js
0 → 100644
1 | +/** | ||
2 | + * app hotfix 配置 | ||
3 | + * @author: jiangfeng<jeff.jiang@yoho.cn> | ||
4 | + * @date: 2016/8/19 | ||
5 | + */ | ||
6 | +'use strict'; | ||
7 | + | ||
8 | +import Router from 'koa-router'; | ||
9 | +import md5File from 'md5-file'; | ||
10 | +import md5 from 'md5'; | ||
11 | +import {Hotfix} from '../../models'; | ||
12 | +import qn from '../../../lib/qiniu' | ||
13 | +import path from 'path'; | ||
14 | +import Operation from '../../logger/operation'; | ||
15 | + | ||
16 | +let r = new Router(); | ||
17 | + | ||
18 | +const PRIVATE_KEY = 'fd4ad5fcsa0de589af23234ks1923ks'; | ||
19 | + | ||
20 | +const hf = { | ||
21 | + async list_page(ctx) { | ||
22 | + let type = ctx.params.type; | ||
23 | + let hotfixs = await Hotfix.cfind({ | ||
24 | + type: type | ||
25 | + }).sort({ | ||
26 | + time: -1 | ||
27 | + }).exec(); | ||
28 | + | ||
29 | + await ctx.render('action/hotfix_list', {hotfixs: hotfixs, type: type}); | ||
30 | + }, | ||
31 | + async new_page(ctx) { | ||
32 | + let type = ctx.params.type; | ||
33 | + | ||
34 | + await ctx.render('action/hotfix_form', {type: type}); | ||
35 | + }, | ||
36 | + async save(ctx) { | ||
37 | + let type = ctx.params.type; | ||
38 | + let body = ctx.request.body; | ||
39 | + | ||
40 | + let {appName, appVersion, patchVersion, _files} = body; | ||
41 | + let batchFile; | ||
42 | + | ||
43 | + if (_files && _files.batch) { | ||
44 | + batchFile = _files.batch; | ||
45 | + } else { | ||
46 | + return ctx.body = { | ||
47 | + code: 400, | ||
48 | + message: '未上传补丁文件' | ||
49 | + }; | ||
50 | + } | ||
51 | + | ||
52 | + let filecode = md5File.sync(batchFile.path); | ||
53 | + | ||
54 | + let hotfixRaw = { | ||
55 | + type: type, | ||
56 | + appName: appName, | ||
57 | + appVersion: appVersion, | ||
58 | + patchVersion: patchVersion, | ||
59 | + filecode: filecode, | ||
60 | + time: new Date() | ||
61 | + }; | ||
62 | + | ||
63 | + let batchKey = `app-hotfix2/${appName}/${type.toLowerCase()}/${appVersion}`; | ||
64 | + | ||
65 | + if (patchVersion) { | ||
66 | + batchKey = path.join(batchKey, patchVersion); | ||
67 | + } | ||
68 | + batchKey = path.join(batchKey, batchFile.name); | ||
69 | + | ||
70 | + let apiFileKey = `app-hotfix2/${appName}/${type.toLowerCase()}/${appVersion}/api.json`; | ||
71 | + let batchUpload = await qn.uploadFileAsync(batchFile.path, {key: batchKey}); | ||
72 | + | ||
73 | + if (batchUpload && batchUpload.url) { | ||
74 | + let apiData = { | ||
75 | + data: { | ||
76 | + url: batchUpload.url, | ||
77 | + patchv: patchVersion, | ||
78 | + filecode: md5(filecode + 'yohopatch2016') | ||
79 | + } | ||
80 | + }; | ||
81 | + hotfixRaw.patchFile = batchUpload.url; | ||
82 | + apiData.md5 = md5(PRIVATE_KEY + ':' + JSON.stringify(apiData.data)); | ||
83 | + | ||
84 | + console.log(apiFileKey); | ||
85 | + let apiUpload = await qn.key(apiFileKey).uploadAsync(JSON.stringify(apiData), {key: apiFileKey}); | ||
86 | + | ||
87 | + if (apiUpload && apiUpload.url) { | ||
88 | + hotfixRaw.apiFile = apiUpload.url; | ||
89 | + } else { | ||
90 | + return ctx.body = { | ||
91 | + code: 400, | ||
92 | + message: '上传api文件失败, 请重试' | ||
93 | + }; | ||
94 | + } | ||
95 | + } else { | ||
96 | + return ctx.body = { | ||
97 | + code: 400, | ||
98 | + message: '上传补丁文件失败, 请重试' | ||
99 | + }; | ||
100 | + } | ||
101 | + | ||
102 | + await Hotfix.insert(hotfixRaw); | ||
103 | + | ||
104 | + Operation.action(ctx.session.user, 'NEW_HOTFIX', '新增hotfix', { | ||
105 | + type: type, | ||
106 | + appName: appName, | ||
107 | + appVersion: appVersion, | ||
108 | + batchFile: hotfixRaw.patchFile | ||
109 | + }); | ||
110 | + | ||
111 | + return ctx.body = { | ||
112 | + code: 200 | ||
113 | + }; | ||
114 | + } | ||
115 | +}; | ||
116 | + | ||
117 | +r.get('/:type', hf.list_page); | ||
118 | +r.get('/new/:type', hf.new_page); | ||
119 | +r.post('/save/:type', hf.save); | ||
120 | + | ||
121 | +export default r; |
@@ -3,6 +3,7 @@ | @@ -3,6 +3,7 @@ | ||
3 | import Router from 'koa-router'; | 3 | import Router from 'koa-router'; |
4 | import md5 from 'md5'; | 4 | import md5 from 'md5'; |
5 | import {User} from '../../models'; | 5 | import {User} from '../../models'; |
6 | +import Operation from '../../logger/operation'; | ||
6 | 7 | ||
7 | let r = new Router(); | 8 | let r = new Router(); |
8 | 9 | ||
@@ -20,6 +21,8 @@ const login = { | @@ -20,6 +21,8 @@ const login = { | ||
20 | ctx.session = { | 21 | ctx.session = { |
21 | user: user | 22 | user: user |
22 | }; | 23 | }; |
24 | + | ||
25 | + Operation.action(user, 'LOGIN', '用户登陆'); | ||
23 | ctx.redirect('/projects'); | 26 | ctx.redirect('/projects'); |
24 | ctx.status = 301; | 27 | ctx.status = 301; |
25 | } else { | 28 | } else { |
@@ -29,8 +32,9 @@ const login = { | @@ -29,8 +32,9 @@ const login = { | ||
29 | }, | 32 | }, |
30 | logout: (ctx, next) => { | 33 | logout: (ctx, next) => { |
31 | ctx.session = null; | 34 | ctx.session = null; |
32 | - ctx.redirect('/'); | ||
33 | - ctx.status = 301; | 35 | + console.log('logout!'); |
36 | + ctx.set('Cache-Control', 'no-cache'); | ||
37 | + ctx.redirect('/projects'); | ||
34 | } | 38 | } |
35 | }; | 39 | }; |
36 | 40 |
apps/web/actions/operation_log.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 Router from 'koa-router'; | ||
10 | + | ||
11 | +import { | ||
12 | + OperationLogger | ||
13 | +} from '../../models'; | ||
14 | + | ||
15 | +const r = new Router(); | ||
16 | + | ||
17 | +r.get('/log', async (ctx) => { | ||
18 | + await ctx.render('action/operation_log'); | ||
19 | +}); | ||
20 | + | ||
21 | +r.get('/log/query', async (ctx) => { | ||
22 | + console.log(ctx.query); | ||
23 | +}); | ||
24 | + | ||
25 | +export default r; |
@@ -4,7 +4,7 @@ import Router from 'koa-router'; | @@ -4,7 +4,7 @@ import Router from 'koa-router'; | ||
4 | import moment from 'moment'; | 4 | import moment from 'moment'; |
5 | import Build from '../../ci/build'; | 5 | import Build from '../../ci/build'; |
6 | import Deploy from '../../ci/deploy'; | 6 | import Deploy from '../../ci/deploy'; |
7 | - | 7 | +import Operation from '../../logger/operation'; |
8 | 8 | ||
9 | import { | 9 | import { |
10 | Building, | 10 | Building, |
@@ -38,7 +38,7 @@ const p = { | @@ -38,7 +38,7 @@ const p = { | ||
38 | /** | 38 | /** |
39 | * 单个项目首页 | 39 | * 单个项目首页 |
40 | */ | 40 | */ |
41 | - project_index: async (ctx, next) => { | 41 | + project_index: async(ctx, next) => { |
42 | let id = ctx.params.id; | 42 | let id = ctx.params.id; |
43 | let env = ctx.request.query.env; | 43 | let env = ctx.request.query.env; |
44 | let project = await Project.findById(id); | 44 | let project = await Project.findById(id); |
@@ -123,8 +123,10 @@ const p = { | @@ -123,8 +123,10 @@ const p = { | ||
123 | }, { | 123 | }, { |
124 | $set: project | 124 | $set: project |
125 | }); | 125 | }); |
126 | + await Operation.action(ctx.session.user, 'EDIT_PROJECT_INFO', '修改项目信息', {_id: id, name: project.name}); | ||
126 | } else { | 127 | } else { |
127 | await Project.insert(project); | 128 | await Project.insert(project); |
129 | + await Operation.action(ctx.session.user, 'NEW_PROJECT_INFO', '新增项目信息', {_id: id, name: project.name}); | ||
128 | } | 130 | } |
129 | ctx.redirect('/projects'); | 131 | ctx.redirect('/projects'); |
130 | ctx.status = 301; | 132 | ctx.status = 301; |
@@ -171,6 +173,9 @@ const p = { | @@ -171,6 +173,9 @@ const p = { | ||
171 | let id = buildingDoc[0]._id; | 173 | let id = buildingDoc[0]._id; |
172 | 174 | ||
173 | build.run(id); | 175 | build.run(id); |
176 | + | ||
177 | + await Operation.action(ctx.session.user, 'NEW_PROJECT_BUILDING', '新增项目构建', {_id: id, project: p.name, branch: branch, env: env}); | ||
178 | + | ||
174 | ctx.body = { | 179 | ctx.body = { |
175 | code: 200, | 180 | code: 200, |
176 | id: id | 181 | id: id |
@@ -201,6 +206,9 @@ const p = { | @@ -201,6 +206,9 @@ const p = { | ||
201 | let deploy = new Deploy(project, building); | 206 | let deploy = new Deploy(project, building); |
202 | deploy.deploy(info); | 207 | deploy.deploy(info); |
203 | }); | 208 | }); |
209 | + | ||
210 | + await Operation.action(ctx.session.user, 'PROJECT_DEPLOY', '项目分发部署', {_id: buildingId, project: project.name, branch: building.branch, env: building.env}); | ||
211 | + | ||
204 | ctx.body = { | 212 | ctx.body = { |
205 | code: 200, | 213 | code: 200, |
206 | building: building | 214 | building: building |
@@ -2,6 +2,7 @@ | @@ -2,6 +2,7 @@ | ||
2 | 2 | ||
3 | import Router from 'koa-router'; | 3 | import Router from 'koa-router'; |
4 | 4 | ||
5 | +import Operation from '../../logger/operation'; | ||
5 | import { | 6 | import { |
6 | Server | 7 | Server |
7 | } from '../../models'; | 8 | } from '../../models'; |
@@ -48,15 +49,21 @@ const servers = { | @@ -48,15 +49,21 @@ const servers = { | ||
48 | }, { | 49 | }, { |
49 | $set: server | 50 | $set: server |
50 | }); | 51 | }); |
52 | + await Operation.action(ctx.session.user, 'EDIT_SERVER', '修改服务器配置', {_id: _id, host: host}); | ||
51 | } else { | 53 | } else { |
52 | await Server.insert(server); | 54 | await Server.insert(server); |
55 | + await Operation.action(ctx.session.user, 'NEW_SERVER', '新增服务器配置', {_id: _id, host: host}); | ||
53 | } | 56 | } |
57 | + | ||
54 | ctx.redirect('/servers/setting'); | 58 | ctx.redirect('/servers/setting'); |
55 | ctx.status = 301; | 59 | ctx.status = 301; |
56 | }, | 60 | }, |
57 | del: async(ctx, next) => { | 61 | del: async(ctx, next) => { |
58 | let id = ctx.request.body.id; | 62 | let id = ctx.request.body.id; |
63 | + let server = await Server.findById(id); | ||
64 | + | ||
59 | await Server.removeById(id); | 65 | await Server.removeById(id); |
66 | + await Operation.action(ctx.session.user, 'DELETE_SERVER', '删除服务器配置', {_id: id, host: server.host}); | ||
60 | ctx.body = { | 67 | ctx.body = { |
61 | code: 200 | 68 | code: 200 |
62 | }; | 69 | }; |
@@ -8,6 +8,7 @@ | @@ -8,6 +8,7 @@ | ||
8 | import Router from 'koa-router'; | 8 | import Router from 'koa-router'; |
9 | import md5 from 'md5'; | 9 | import md5 from 'md5'; |
10 | import {User} from '../../models'; | 10 | import {User} from '../../models'; |
11 | +import Operation from '../../logger/operation'; | ||
11 | 12 | ||
12 | const r = new Router; | 13 | const r = new Router; |
13 | 14 | ||
@@ -47,15 +48,20 @@ const user = { | @@ -47,15 +48,20 @@ const user = { | ||
47 | }, { | 48 | }, { |
48 | $set: user | 49 | $set: user |
49 | }); | 50 | }); |
51 | + await Operation.action(ctx.session.user, 'EDIT_USER', '修改用户', {_id: _id, username: user.username}); | ||
50 | } else { | 52 | } else { |
51 | await User.insert(user); | 53 | await User.insert(user); |
54 | + await Operation.action(ctx.session.user, 'NEW_USER', '新增用户', {_id: _id, username: user.username}); | ||
52 | } | 55 | } |
53 | ctx.redirect('/users/setting'); | 56 | ctx.redirect('/users/setting'); |
54 | ctx.status = 301; | 57 | ctx.status = 301; |
55 | }, | 58 | }, |
56 | async del(ctx) { | 59 | async del(ctx) { |
57 | let id = ctx.request.body.id; | 60 | let id = ctx.request.body.id; |
61 | + let u = await User.findById(id); | ||
58 | await User.removeById(id); | 62 | await User.removeById(id); |
63 | + | ||
64 | + await Operation.action(ctx.session.user, 'DELETE_USER', '删除用户', {_id: id, username: u.username}); | ||
59 | ctx.body = { | 65 | ctx.body = { |
60 | code: 200 | 66 | code: 200 |
61 | }; | 67 | }; |
@@ -7,6 +7,8 @@ import servers from './actions/servers'; | @@ -7,6 +7,8 @@ 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 | import users from './actions/users'; |
10 | +import hotfix from './actions/hotfix'; | ||
11 | +import operationLog from './actions/operation_log'; | ||
10 | 12 | ||
11 | const noAuth = new Router(); | 13 | const noAuth = new Router(); |
12 | const base = new Router(); | 14 | const base = new Router(); |
@@ -30,6 +32,8 @@ export default function (app) { | @@ -30,6 +32,8 @@ export default function (app) { | ||
30 | base.use('/servers', servers.routes(), servers.allowedMethods()); | 32 | base.use('/servers', servers.routes(), servers.allowedMethods()); |
31 | base.use('/monitor', monitor.routes(), monitor.allowedMethods()); | 33 | base.use('/monitor', monitor.routes(), monitor.allowedMethods()); |
32 | base.use('/users', users.routes(), users.allowedMethods()); | 34 | base.use('/users', users.routes(), users.allowedMethods()); |
35 | + base.use('/hotfix', hotfix.routes(), hotfix.allowedMethods()); | ||
36 | + base.use('/operation', operationLog.routes(), operationLog.allowedMethods()); | ||
33 | 37 | ||
34 | base.use('', index.routes(), index.allowedMethods()); | 38 | base.use('', index.routes(), index.allowedMethods()); |
35 | 39 |
apps/web/views/action/hotfix_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="">Hotfix</a></li> | ||
11 | + <li>{{type}}</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 id="hotfix-form" action="/hotfix/save/{{type}}" method="POST" enctype="multipart/form-data" 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">App名称</label> | ||
30 | + <select id="appName" name="appName" data-placeholder="Choose a Host..." class="form-control" style="height: 40px;"> | ||
31 | + <option value="yohobuy">yohobuy</option> | ||
32 | + </select> | ||
33 | + </div> | ||
34 | + <!-- form-group --> | ||
35 | + </div> | ||
36 | + <!-- col-sm-6 --> | ||
37 | + <div class="col-sm-6"> | ||
38 | + <div class="form-group"> | ||
39 | + <label class="control-label">App版本</label> | ||
40 | + <input type="text" name="appVersion" value="{{appVersion}}" class="form-control" placeholder="App version. (必填)"> | ||
41 | + </div> | ||
42 | + <!-- form-group --> | ||
43 | + </div> | ||
44 | + <!-- col-sm-6 --> | ||
45 | + </div> | ||
46 | + <!-- row --> | ||
47 | + <div class="row"> | ||
48 | + <div class="col-sm-6"> | ||
49 | + <div class="form-group"> | ||
50 | + <label class="control-label">补丁版本</label> | ||
51 | + <input type="text" name="patchVersion" value="{{patchVersion}}" class="form-control" placeholder="patch version (可为空)"> | ||
52 | + </div> | ||
53 | + <!-- form-group --> | ||
54 | + </div> | ||
55 | + <!-- col-sm-6 --> | ||
56 | + <div class="col-sm-6"> | ||
57 | + <div class="form-group"> | ||
58 | + <label class="control-label">补丁文件</label> | ||
59 | + <input type="file" name="batch" class="form-control" placeholder="upload the patch file (必填)"> | ||
60 | + </div> | ||
61 | + <!-- form-group --> | ||
62 | + </div> | ||
63 | + </div> | ||
64 | + <!-- row --> | ||
65 | + </div> | ||
66 | + <!-- panel-body --> | ||
67 | + <div class="panel-footer"> | ||
68 | + <button type="submit" class="btn btn-primary">保存</button> | ||
69 | + <button type="button" class="btn btn-default ml20 go-back">取消</button> | ||
70 | + </div> | ||
71 | + </form> | ||
72 | + <!-- panel-footer --> | ||
73 | +</div> | ||
74 | + | ||
75 | +<div class="panel panel-default"> | ||
76 | + <div class="panel-heading"> | ||
77 | + <h4 class="panel-title">参数说明</h4> | ||
78 | + </div><!-- panel-heading --> | ||
79 | + <div class="panel-body"> | ||
80 | + <div class="alert alert-danger">App版本必须和调用接口时app_version一致</div> | ||
81 | + </div> | ||
82 | +</div> | ||
83 | + | ||
84 | +<script> | ||
85 | + $(function(){ | ||
86 | +// $('#hotfix-form').off().on('submit', function(event) { | ||
87 | +// event.preventDefault(); // stop default submit behavior | ||
88 | +// $.pjax.submit(event, '#pjax-container', { | ||
89 | +// type: 'POST', | ||
90 | +// dataType: 'json', | ||
91 | +// push: false | ||
92 | +// }); | ||
93 | +// }).on('pjax:success', function(e, data) { | ||
94 | +// console.log(111) | ||
95 | +// return false; | ||
96 | +// }); | ||
97 | + | ||
98 | +// $('#appName').select2(); | ||
99 | + | ||
100 | + $('#hotfix-form').ajaxForm(function(d) { | ||
101 | + if (d && d.code === 200) { | ||
102 | + $.pjax({ | ||
103 | + url: '/hotfix/{{type}}', | ||
104 | + container: '#pjax-container' | ||
105 | + }); | ||
106 | + } else { | ||
107 | + alert(d.message); | ||
108 | + } | ||
109 | + }); | ||
110 | + | ||
111 | + | ||
112 | + $('.go-back').click(function() { | ||
113 | + window.history.go(-1); | ||
114 | + }); | ||
115 | + }); | ||
116 | +</script> | ||
117 | + |
apps/web/views/action/hotfix_list.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="">Hotfix</a></li> | ||
10 | + <li>{{type}}</li> | ||
11 | + </ul> | ||
12 | + <h4>Hotfix</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="/hotfix/new/{{type}}" class="btn btn-success btn-rounded"><i class="glyphicon glyphicon-plus"></i> 新增Hotfix</a> | ||
24 | + </div> | ||
25 | + <h4 class="panel-title">{{type}} hotfix</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>App名称</th> | ||
34 | + <th>App版本</th> | ||
35 | + <th>补丁版本</th> | ||
36 | + <th>文件MD5</th> | ||
37 | + <th>接口文件</th> | ||
38 | + <th>补丁文件</th> | ||
39 | + </tr> | ||
40 | + </thead> | ||
41 | + | ||
42 | + <tbody> | ||
43 | + {{#each hotfixs}} | ||
44 | + <tr> | ||
45 | + <td>{{appName}}</td> | ||
46 | + <td>{{appVersion}}</td> | ||
47 | + <td>{{patchVersion}}</td> | ||
48 | + <td>{{filecode}}</td> | ||
49 | + <td>{{apiFile}}</td> | ||
50 | + <td>{{patchFile}}</td> | ||
51 | + </tr> | ||
52 | + {{/each}} | ||
53 | + </tbody> | ||
54 | + </table> | ||
55 | + </div> | ||
56 | + <!-- panel --> | ||
57 | +</div> | ||
58 | + | ||
59 | + | ||
60 | +<script> | ||
61 | + $(function() { | ||
62 | + $("#basicTable").DataTable({ | ||
63 | + responsive: true, | ||
64 | + pageLength: 25 | ||
65 | + }); | ||
66 | + $(document).pjax('#new-page', '#pjax-container') | ||
67 | + | ||
68 | + | ||
69 | + }); | ||
70 | + | ||
71 | +</script> |
apps/web/views/action/operation_log.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="/servers">系统管理</a></li> | ||
10 | + <li>操作日志</li> | ||
11 | + </ul> | ||
12 | + <h4>所有用户数据操作日志</h4> | ||
13 | + </div> | ||
14 | + </div> | ||
15 | + <!-- media --> | ||
16 | +</div> | ||
17 | + | ||
18 | +<div class="contentpanel project-index-page"> | ||
19 | + <div class="panel panel-default"> | ||
20 | + <div class="panel-heading"> | ||
21 | + <div class="pull-right"> | ||
22 | + <a href="" class="tooltips panel-minimize"><i class="fa fa-minus"></i></a> | ||
23 | + </div> | ||
24 | + <h4 class="panel-title">操作日志</h4> | ||
25 | + </div> | ||
26 | + <div class="panel-body"> | ||
27 | + <table id="table-log" class="table table-striped table-bordered building-table"> | ||
28 | + <thead> | ||
29 | + <tr> | ||
30 | + <th>操作用户</th> | ||
31 | + <th>时间</th> | ||
32 | + <th>动作</th> | ||
33 | + <th>描述</th> | ||
34 | + <th>数据</th> | ||
35 | + </tr> | ||
36 | + </thead> | ||
37 | + </table> | ||
38 | + </div> | ||
39 | + </div> | ||
40 | +</div> | ||
41 | + | ||
42 | +<script> | ||
43 | + | ||
44 | + $(function() { | ||
45 | + | ||
46 | + var tables = {}; | ||
47 | + $('.building-table').each(function() { | ||
48 | + var env = $(this).parents('.panel').data('env'); | ||
49 | + tables[env] = $(this).DataTable({ | ||
50 | + responsive: true, | ||
51 | + ajax: '/operation/log/query', | ||
52 | + columns: [ | ||
53 | + {data: "username"}, | ||
54 | + {data: "time"}, | ||
55 | + {data: "action"}, | ||
56 | + {data: "description"}, | ||
57 | + {data: "meta"} | ||
58 | + ], | ||
59 | + order: [[1, 'desc']] | ||
60 | + }); | ||
61 | + }); | ||
62 | + }); | ||
63 | +</script> |
@@ -51,7 +51,7 @@ | @@ -51,7 +51,7 @@ | ||
51 | <script src="/js/pace.min.js"></script> | 51 | <script src="/js/pace.min.js"></script> |
52 | <script src="/js/retina.min.js"></script> | 52 | <script src="/js/retina.min.js"></script> |
53 | <script src="/js/jquery.cookies.js"></script> | 53 | <script src="/js/jquery.cookies.js"></script> |
54 | - | 54 | + <script src="/js/jquery.form.min.js"></script> |
55 | <script src="/js/flot/jquery.flot.min.js"></script> | 55 | <script src="/js/flot/jquery.flot.min.js"></script> |
56 | <script src="/js/flot/jquery.flot.resize.min.js"></script> | 56 | <script src="/js/flot/jquery.flot.resize.min.js"></script> |
57 | <script src="/js/flot/jquery.flot.spline.min.js"></script> | 57 | <script src="/js/flot/jquery.flot.spline.min.js"></script> |
@@ -14,16 +14,23 @@ | @@ -14,16 +14,23 @@ | ||
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>项目</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="glyphicon glyphicon-wrench"></i> <span>APP Hotfix</span></a> |
18 | + <ul class="children"> | ||
19 | + <li><a href="/hotfix/Android">Android</a></li> | ||
20 | + <li><a href="/hotfix/iOS">iOS</a></li> | ||
21 | + </ul> | ||
22 | + </li> | ||
23 | + <li class="parent"><a href=""><i class="fa fa-eye"></i> <span>监控中心</span></a> | ||
18 | <ul class="children"> | 24 | <ul class="children"> |
19 | <li><a href="/monitor/log">实时日志</a></li> | 25 | <li><a href="/monitor/log">实时日志</a></li> |
20 | </ul> | 26 | </ul> |
21 | </li> | 27 | </li> |
22 | {{#if is_master}} | 28 | {{#if is_master}} |
23 | - <li class="parent"><a href=""><i class="fa fa-gears"></i> <span>系统配置</span></a> | 29 | + <li class="parent"><a href=""><i class="fa fa-gears"></i> <span>系统管理</span></a> |
24 | <ul class="children"> | 30 | <ul class="children"> |
25 | <li><a href="/servers/setting">服务器配置</a></li> | 31 | <li><a href="/servers/setting">服务器配置</a></li> |
26 | <li><a href="/users/setting">用户管理</a></li> | 32 | <li><a href="/users/setting">用户管理</a></li> |
33 | + <li><a href="/operation/log">操作记录</a></li> | ||
27 | </ul> | 34 | </ul> |
28 | </li> | 35 | </li> |
29 | {{/if}} | 36 | {{/if}} |
lib/qiniu.js
0 → 100644
1 | +/** | ||
2 | + * | ||
3 | + * @author: jiangfeng<jeff.jiang@yoho.cn> | ||
4 | + * @date: 16/8/19 | ||
5 | + */ | ||
6 | + | ||
7 | +'use strict'; | ||
8 | + | ||
9 | +import qn from 'qn'; | ||
10 | +import Promise from 'bluebird'; | ||
11 | +import _ from 'lodash'; | ||
12 | + | ||
13 | +const config = { | ||
14 | + accessKey: 'cY9B5ZgON_7McTS5zV5nTeRyQ98MOcVD7W4eGVbE', | ||
15 | + secretKey: 'RduqgmK7cAtaQvdIa1ax_zzmMsnv9ac-Ka0uF6wG', | ||
16 | + origin: 'http://cdn.yoho.cn', | ||
17 | + bucket: 'yohocdn' | ||
18 | +}; | ||
19 | + | ||
20 | + | ||
21 | +const _default = Promise.promisifyAll(qn.create(config)); | ||
22 | + | ||
23 | +_default.key = function(key) { | ||
24 | + let _conifg = _.clone(config); | ||
25 | + _conifg.bucket = _conifg.bucket + ":" + key; | ||
26 | + return Promise.promisifyAll(qn.create(_conifg)); | ||
27 | +}; | ||
28 | + | ||
29 | +export default _default; | ||
30 | + |
@@ -84,7 +84,7 @@ function formy(ctx, opts) { | @@ -84,7 +84,7 @@ function formy(ctx, opts) { | ||
84 | form | 84 | form |
85 | .on('end', function() { | 85 | .on('end', function() { |
86 | fields = qs.parse(fieldsArray.join('&')); | 86 | fields = qs.parse(fieldsArray.join('&')); |
87 | - if (!Object.keys(files).length === 0) { | 87 | + if (Object.keys(files).length !== 0) { |
88 | fields._files = files; | 88 | fields._files = files; |
89 | } | 89 | } |
90 | done(null, fields); | 90 | done(null, fields); |
@@ -100,7 +100,7 @@ function formy(ctx, opts) { | @@ -100,7 +100,7 @@ function formy(ctx, opts) { | ||
100 | // fields[field] = [fields[field], value]; | 100 | // fields[field] = [fields[field], value]; |
101 | // } | 101 | // } |
102 | // } else { | 102 | // } else { |
103 | - | 103 | + // |
104 | // fields[field] = value; | 104 | // fields[field] = value; |
105 | // } | 105 | // } |
106 | fieldsArray.push(field + '=' + value); | 106 | fieldsArray.push(field + '=' + value); |
@@ -32,6 +32,7 @@ | @@ -32,6 +32,7 @@ | ||
32 | "author": "jiangfeng <jeff.jiang@yoho.cn>", | 32 | "author": "jiangfeng <jeff.jiang@yoho.cn>", |
33 | "license": "ISC", | 33 | "license": "ISC", |
34 | "dependencies": { | 34 | "dependencies": { |
35 | + "bluebird": "^3.4.1", | ||
35 | "co": "^4.6.0", | 36 | "co": "^4.6.0", |
36 | "co-body": "^4.2.0", | 37 | "co-body": "^4.2.0", |
37 | "formidable": "^1.0.17", | 38 | "formidable": "^1.0.17", |
@@ -48,9 +49,11 @@ | @@ -48,9 +49,11 @@ | ||
48 | "koa-static": "^3.0.0", | 49 | "koa-static": "^3.0.0", |
49 | "lodash": "^4.13.1", | 50 | "lodash": "^4.13.1", |
50 | "md5": "^2.1.0", | 51 | "md5": "^2.1.0", |
52 | + "md5-file": "^3.1.1", | ||
51 | "moment": "^2.13.0", | 53 | "moment": "^2.13.0", |
52 | "nedb": "^1.8.0", | 54 | "nedb": "^1.8.0", |
53 | "nedb-promise": "^2.0.0", | 55 | "nedb-promise": "^2.0.0", |
56 | + "qn": "^1.3.0", | ||
54 | "qs": "^6.2.0", | 57 | "qs": "^6.2.0", |
55 | "shelljs": "^0.7.0", | 58 | "shelljs": "^0.7.0", |
56 | "socket.io": "^1.4.6", | 59 | "socket.io": "^1.4.6", |
public/js/jquery.form.min.js
0 → 100644
1 | +/*! | ||
2 | + * jQuery Form Plugin | ||
3 | + * version: 3.51.0-2014.06.20 | ||
4 | + * Requires jQuery v1.5 or later | ||
5 | + * Copyright (c) 2014 M. Alsup | ||
6 | + * Examples and documentation at: http://malsup.com/jquery/form/ | ||
7 | + * Project repository: https://github.com/malsup/form | ||
8 | + * Dual licensed under the MIT and GPL licenses. | ||
9 | + * https://github.com/malsup/form#copyright-and-license | ||
10 | + */ | ||
11 | +!function(e){"use strict";"function"==typeof define&&define.amd?define(["jquery"],e):e("undefined"!=typeof jQuery?jQuery:window.Zepto)}(function(e){"use strict";function t(t){var r=t.data;t.isDefaultPrevented()||(t.preventDefault(),e(t.target).ajaxSubmit(r))}function r(t){var r=t.target,a=e(r);if(!a.is("[type=submit],[type=image]")){var n=a.closest("[type=submit]");if(0===n.length)return;r=n[0]}var i=this;if(i.clk=r,"image"==r.type)if(void 0!==t.offsetX)i.clk_x=t.offsetX,i.clk_y=t.offsetY;else if("function"==typeof e.fn.offset){var o=a.offset();i.clk_x=t.pageX-o.left,i.clk_y=t.pageY-o.top}else i.clk_x=t.pageX-r.offsetLeft,i.clk_y=t.pageY-r.offsetTop;setTimeout(function(){i.clk=i.clk_x=i.clk_y=null},100)}function a(){if(e.fn.ajaxSubmit.debug){var t="[jquery.form] "+Array.prototype.join.call(arguments,"");window.console&&window.console.log?window.console.log(t):window.opera&&window.opera.postError&&window.opera.postError(t)}}var n={};n.fileapi=void 0!==e("<input type='file'/>").get(0).files,n.formdata=void 0!==window.FormData;var i=!!e.fn.prop;e.fn.attr2=function(){if(!i)return this.attr.apply(this,arguments);var e=this.prop.apply(this,arguments);return e&&e.jquery||"string"==typeof e?e:this.attr.apply(this,arguments)},e.fn.ajaxSubmit=function(t){function r(r){var a,n,i=e.param(r,t.traditional).split("&"),o=i.length,s=[];for(a=0;o>a;a++)i[a]=i[a].replace(/\+/g," "),n=i[a].split("="),s.push([decodeURIComponent(n[0]),decodeURIComponent(n[1])]);return s}function o(a){for(var n=new FormData,i=0;i<a.length;i++)n.append(a[i].name,a[i].value);if(t.extraData){var o=r(t.extraData);for(i=0;i<o.length;i++)o[i]&&n.append(o[i][0],o[i][1])}t.data=null;var s=e.extend(!0,{},e.ajaxSettings,t,{contentType:!1,processData:!1,cache:!1,type:u||"POST"});t.uploadProgress&&(s.xhr=function(){var r=e.ajaxSettings.xhr();return r.upload&&r.upload.addEventListener("progress",function(e){var r=0,a=e.loaded||e.position,n=e.total;e.lengthComputable&&(r=Math.ceil(a/n*100)),t.uploadProgress(e,a,n,r)},!1),r}),s.data=null;var c=s.beforeSend;return s.beforeSend=function(e,r){r.data=t.formData?t.formData:n,c&&c.call(this,e,r)},e.ajax(s)}function s(r){function n(e){var t=null;try{e.contentWindow&&(t=e.contentWindow.document)}catch(r){a("cannot get iframe.contentWindow document: "+r)}if(t)return t;try{t=e.contentDocument?e.contentDocument:e.document}catch(r){a("cannot get iframe.contentDocument: "+r),t=e.document}return t}function o(){function t(){try{var e=n(g).readyState;a("state = "+e),e&&"uninitialized"==e.toLowerCase()&&setTimeout(t,50)}catch(r){a("Server abort: ",r," (",r.name,")"),s(k),j&&clearTimeout(j),j=void 0}}var r=f.attr2("target"),i=f.attr2("action"),o="multipart/form-data",c=f.attr("enctype")||f.attr("encoding")||o;w.setAttribute("target",p),(!u||/post/i.test(u))&&w.setAttribute("method","POST"),i!=m.url&&w.setAttribute("action",m.url),m.skipEncodingOverride||u&&!/post/i.test(u)||f.attr({encoding:"multipart/form-data",enctype:"multipart/form-data"}),m.timeout&&(j=setTimeout(function(){T=!0,s(D)},m.timeout));var l=[];try{if(m.extraData)for(var d in m.extraData)m.extraData.hasOwnProperty(d)&&l.push(e.isPlainObject(m.extraData[d])&&m.extraData[d].hasOwnProperty("name")&&m.extraData[d].hasOwnProperty("value")?e('<input type="hidden" name="'+m.extraData[d].name+'">').val(m.extraData[d].value).appendTo(w)[0]:e('<input type="hidden" name="'+d+'">').val(m.extraData[d]).appendTo(w)[0]);m.iframeTarget||v.appendTo("body"),g.attachEvent?g.attachEvent("onload",s):g.addEventListener("load",s,!1),setTimeout(t,15);try{w.submit()}catch(h){var x=document.createElement("form").submit;x.apply(w)}}finally{w.setAttribute("action",i),w.setAttribute("enctype",c),r?w.setAttribute("target",r):f.removeAttr("target"),e(l).remove()}}function s(t){if(!x.aborted&&!F){if(M=n(g),M||(a("cannot access response document"),t=k),t===D&&x)return x.abort("timeout"),void S.reject(x,"timeout");if(t==k&&x)return x.abort("server abort"),void S.reject(x,"error","server abort");if(M&&M.location.href!=m.iframeSrc||T){g.detachEvent?g.detachEvent("onload",s):g.removeEventListener("load",s,!1);var r,i="success";try{if(T)throw"timeout";var o="xml"==m.dataType||M.XMLDocument||e.isXMLDoc(M);if(a("isXml="+o),!o&&window.opera&&(null===M.body||!M.body.innerHTML)&&--O)return a("requeing onLoad callback, DOM not available"),void setTimeout(s,250);var u=M.body?M.body:M.documentElement;x.responseText=u?u.innerHTML:null,x.responseXML=M.XMLDocument?M.XMLDocument:M,o&&(m.dataType="xml"),x.getResponseHeader=function(e){var t={"content-type":m.dataType};return t[e.toLowerCase()]},u&&(x.status=Number(u.getAttribute("status"))||x.status,x.statusText=u.getAttribute("statusText")||x.statusText);var c=(m.dataType||"").toLowerCase(),l=/(json|script|text)/.test(c);if(l||m.textarea){var f=M.getElementsByTagName("textarea")[0];if(f)x.responseText=f.value,x.status=Number(f.getAttribute("status"))||x.status,x.statusText=f.getAttribute("statusText")||x.statusText;else if(l){var p=M.getElementsByTagName("pre")[0],h=M.getElementsByTagName("body")[0];p?x.responseText=p.textContent?p.textContent:p.innerText:h&&(x.responseText=h.textContent?h.textContent:h.innerText)}}else"xml"==c&&!x.responseXML&&x.responseText&&(x.responseXML=X(x.responseText));try{E=_(x,c,m)}catch(y){i="parsererror",x.error=r=y||i}}catch(y){a("error caught: ",y),i="error",x.error=r=y||i}x.aborted&&(a("upload aborted"),i=null),x.status&&(i=x.status>=200&&x.status<300||304===x.status?"success":"error"),"success"===i?(m.success&&m.success.call(m.context,E,"success",x),S.resolve(x.responseText,"success",x),d&&e.event.trigger("ajaxSuccess",[x,m])):i&&(void 0===r&&(r=x.statusText),m.error&&m.error.call(m.context,x,i,r),S.reject(x,"error",r),d&&e.event.trigger("ajaxError",[x,m,r])),d&&e.event.trigger("ajaxComplete",[x,m]),d&&!--e.active&&e.event.trigger("ajaxStop"),m.complete&&m.complete.call(m.context,x,i),F=!0,m.timeout&&clearTimeout(j),setTimeout(function(){m.iframeTarget?v.attr("src",m.iframeSrc):v.remove(),x.responseXML=null},100)}}}var c,l,m,d,p,v,g,x,y,b,T,j,w=f[0],S=e.Deferred();if(S.abort=function(e){x.abort(e)},r)for(l=0;l<h.length;l++)c=e(h[l]),i?c.prop("disabled",!1):c.removeAttr("disabled");if(m=e.extend(!0,{},e.ajaxSettings,t),m.context=m.context||m,p="jqFormIO"+(new Date).getTime(),m.iframeTarget?(v=e(m.iframeTarget),b=v.attr2("name"),b?p=b:v.attr2("name",p)):(v=e('<iframe name="'+p+'" src="'+m.iframeSrc+'" />'),v.css({position:"absolute",top:"-1000px",left:"-1000px"})),g=v[0],x={aborted:0,responseText:null,responseXML:null,status:0,statusText:"n/a",getAllResponseHeaders:function(){},getResponseHeader:function(){},setRequestHeader:function(){},abort:function(t){var r="timeout"===t?"timeout":"aborted";a("aborting upload... "+r),this.aborted=1;try{g.contentWindow.document.execCommand&&g.contentWindow.document.execCommand("Stop")}catch(n){}v.attr("src",m.iframeSrc),x.error=r,m.error&&m.error.call(m.context,x,r,t),d&&e.event.trigger("ajaxError",[x,m,r]),m.complete&&m.complete.call(m.context,x,r)}},d=m.global,d&&0===e.active++&&e.event.trigger("ajaxStart"),d&&e.event.trigger("ajaxSend",[x,m]),m.beforeSend&&m.beforeSend.call(m.context,x,m)===!1)return m.global&&e.active--,S.reject(),S;if(x.aborted)return S.reject(),S;y=w.clk,y&&(b=y.name,b&&!y.disabled&&(m.extraData=m.extraData||{},m.extraData[b]=y.value,"image"==y.type&&(m.extraData[b+".x"]=w.clk_x,m.extraData[b+".y"]=w.clk_y)));var D=1,k=2,A=e("meta[name=csrf-token]").attr("content"),L=e("meta[name=csrf-param]").attr("content");L&&A&&(m.extraData=m.extraData||{},m.extraData[L]=A),m.forceSync?o():setTimeout(o,10);var E,M,F,O=50,X=e.parseXML||function(e,t){return window.ActiveXObject?(t=new ActiveXObject("Microsoft.XMLDOM"),t.async="false",t.loadXML(e)):t=(new DOMParser).parseFromString(e,"text/xml"),t&&t.documentElement&&"parsererror"!=t.documentElement.nodeName?t:null},C=e.parseJSON||function(e){return window.eval("("+e+")")},_=function(t,r,a){var n=t.getResponseHeader("content-type")||"",i="xml"===r||!r&&n.indexOf("xml")>=0,o=i?t.responseXML:t.responseText;return i&&"parsererror"===o.documentElement.nodeName&&e.error&&e.error("parsererror"),a&&a.dataFilter&&(o=a.dataFilter(o,r)),"string"==typeof o&&("json"===r||!r&&n.indexOf("json")>=0?o=C(o):("script"===r||!r&&n.indexOf("javascript")>=0)&&e.globalEval(o)),o};return S}if(!this.length)return a("ajaxSubmit: skipping submit process - no element selected"),this;var u,c,l,f=this;"function"==typeof t?t={success:t}:void 0===t&&(t={}),u=t.type||this.attr2("method"),c=t.url||this.attr2("action"),l="string"==typeof c?e.trim(c):"",l=l||window.location.href||"",l&&(l=(l.match(/^([^#]+)/)||[])[1]),t=e.extend(!0,{url:l,success:e.ajaxSettings.success,type:u||e.ajaxSettings.type,iframeSrc:/^https/i.test(window.location.href||"")?"javascript:false":"about:blank"},t);var m={};if(this.trigger("form-pre-serialize",[this,t,m]),m.veto)return a("ajaxSubmit: submit vetoed via form-pre-serialize trigger"),this;if(t.beforeSerialize&&t.beforeSerialize(this,t)===!1)return a("ajaxSubmit: submit aborted via beforeSerialize callback"),this;var d=t.traditional;void 0===d&&(d=e.ajaxSettings.traditional);var p,h=[],v=this.formToArray(t.semantic,h);if(t.data&&(t.extraData=t.data,p=e.param(t.data,d)),t.beforeSubmit&&t.beforeSubmit(v,this,t)===!1)return a("ajaxSubmit: submit aborted via beforeSubmit callback"),this;if(this.trigger("form-submit-validate",[v,this,t,m]),m.veto)return a("ajaxSubmit: submit vetoed via form-submit-validate trigger"),this;var g=e.param(v,d);p&&(g=g?g+"&"+p:p),"GET"==t.type.toUpperCase()?(t.url+=(t.url.indexOf("?")>=0?"&":"?")+g,t.data=null):t.data=g;var x=[];if(t.resetForm&&x.push(function(){f.resetForm()}),t.clearForm&&x.push(function(){f.clearForm(t.includeHidden)}),!t.dataType&&t.target){var y=t.success||function(){};x.push(function(r){var a=t.replaceTarget?"replaceWith":"html";e(t.target)[a](r).each(y,arguments)})}else t.success&&x.push(t.success);if(t.success=function(e,r,a){for(var n=t.context||this,i=0,o=x.length;o>i;i++)x[i].apply(n,[e,r,a||f,f])},t.error){var b=t.error;t.error=function(e,r,a){var n=t.context||this;b.apply(n,[e,r,a,f])}}if(t.complete){var T=t.complete;t.complete=function(e,r){var a=t.context||this;T.apply(a,[e,r,f])}}var j=e("input[type=file]:enabled",this).filter(function(){return""!==e(this).val()}),w=j.length>0,S="multipart/form-data",D=f.attr("enctype")==S||f.attr("encoding")==S,k=n.fileapi&&n.formdata;a("fileAPI :"+k);var A,L=(w||D)&&!k;t.iframe!==!1&&(t.iframe||L)?t.closeKeepAlive?e.get(t.closeKeepAlive,function(){A=s(v)}):A=s(v):A=(w||D)&&k?o(v):e.ajax(t),f.removeData("jqxhr").data("jqxhr",A);for(var E=0;E<h.length;E++)h[E]=null;return this.trigger("form-submit-notify",[this,t]),this},e.fn.ajaxForm=function(n){if(n=n||{},n.delegation=n.delegation&&e.isFunction(e.fn.on),!n.delegation&&0===this.length){var i={s:this.selector,c:this.context};return!e.isReady&&i.s?(a("DOM not ready, queuing ajaxForm"),e(function(){e(i.s,i.c).ajaxForm(n)}),this):(a("terminating; zero elements found by selector"+(e.isReady?"":" (DOM not ready)")),this)}return n.delegation?(e(document).off("submit.form-plugin",this.selector,t).off("click.form-plugin",this.selector,r).on("submit.form-plugin",this.selector,n,t).on("click.form-plugin",this.selector,n,r),this):this.ajaxFormUnbind().bind("submit.form-plugin",n,t).bind("click.form-plugin",n,r)},e.fn.ajaxFormUnbind=function(){return this.unbind("submit.form-plugin click.form-plugin")},e.fn.formToArray=function(t,r){var a=[];if(0===this.length)return a;var i,o=this[0],s=this.attr("id"),u=t?o.getElementsByTagName("*"):o.elements;if(u&&!/MSIE [678]/.test(navigator.userAgent)&&(u=e(u).get()),s&&(i=e(':input[form="'+s+'"]').get(),i.length&&(u=(u||[]).concat(i))),!u||!u.length)return a;var c,l,f,m,d,p,h;for(c=0,p=u.length;p>c;c++)if(d=u[c],f=d.name,f&&!d.disabled)if(t&&o.clk&&"image"==d.type)o.clk==d&&(a.push({name:f,value:e(d).val(),type:d.type}),a.push({name:f+".x",value:o.clk_x},{name:f+".y",value:o.clk_y}));else if(m=e.fieldValue(d,!0),m&&m.constructor==Array)for(r&&r.push(d),l=0,h=m.length;h>l;l++)a.push({name:f,value:m[l]});else if(n.fileapi&&"file"==d.type){r&&r.push(d);var v=d.files;if(v.length)for(l=0;l<v.length;l++)a.push({name:f,value:v[l],type:d.type});else a.push({name:f,value:"",type:d.type})}else null!==m&&"undefined"!=typeof m&&(r&&r.push(d),a.push({name:f,value:m,type:d.type,required:d.required}));if(!t&&o.clk){var g=e(o.clk),x=g[0];f=x.name,f&&!x.disabled&&"image"==x.type&&(a.push({name:f,value:g.val()}),a.push({name:f+".x",value:o.clk_x},{name:f+".y",value:o.clk_y}))}return a},e.fn.formSerialize=function(t){return e.param(this.formToArray(t))},e.fn.fieldSerialize=function(t){var r=[];return this.each(function(){var a=this.name;if(a){var n=e.fieldValue(this,t);if(n&&n.constructor==Array)for(var i=0,o=n.length;o>i;i++)r.push({name:a,value:n[i]});else null!==n&&"undefined"!=typeof n&&r.push({name:this.name,value:n})}}),e.param(r)},e.fn.fieldValue=function(t){for(var r=[],a=0,n=this.length;n>a;a++){var i=this[a],o=e.fieldValue(i,t);null===o||"undefined"==typeof o||o.constructor==Array&&!o.length||(o.constructor==Array?e.merge(r,o):r.push(o))}return r},e.fieldValue=function(t,r){var a=t.name,n=t.type,i=t.tagName.toLowerCase();if(void 0===r&&(r=!0),r&&(!a||t.disabled||"reset"==n||"button"==n||("checkbox"==n||"radio"==n)&&!t.checked||("submit"==n||"image"==n)&&t.form&&t.form.clk!=t||"select"==i&&-1==t.selectedIndex))return null;if("select"==i){var o=t.selectedIndex;if(0>o)return null;for(var s=[],u=t.options,c="select-one"==n,l=c?o+1:u.length,f=c?o:0;l>f;f++){var m=u[f];if(m.selected){var d=m.value;if(d||(d=m.attributes&&m.attributes.value&&!m.attributes.value.specified?m.text:m.value),c)return d;s.push(d)}}return s}return e(t).val()},e.fn.clearForm=function(t){return this.each(function(){e("input,select,textarea",this).clearFields(t)})},e.fn.clearFields=e.fn.clearInputs=function(t){var r=/^(?:color|date|datetime|email|month|number|password|range|search|tel|text|time|url|week)$/i;return this.each(function(){var a=this.type,n=this.tagName.toLowerCase();r.test(a)||"textarea"==n?this.value="":"checkbox"==a||"radio"==a?this.checked=!1:"select"==n?this.selectedIndex=-1:"file"==a?/MSIE/.test(navigator.userAgent)?e(this).replaceWith(e(this).clone(!0)):e(this).val(""):t&&(t===!0&&/hidden/.test(a)||"string"==typeof t&&e(this).is(t))&&(this.value="")})},e.fn.resetForm=function(){return this.each(function(){("function"==typeof this.reset||"object"==typeof this.reset&&!this.reset.nodeType)&&this.reset()})},e.fn.enable=function(e){return void 0===e&&(e=!0),this.each(function(){this.disabled=!e})},e.fn.selected=function(t){return void 0===t&&(t=!0),this.each(function(){var r=this.type;if("checkbox"==r||"radio"==r)this.checked=t;else if("option"==this.tagName.toLowerCase()){var a=e(this).parent("select");t&&a[0]&&"select-one"==a[0].type&&a.find("option").selected(!1),this.selected=t}})},e.fn.ajaxSubmit.debug=!1}); |
-
Please register or login to post a comment