Explorar el Código

用户管理:支持在线维护系统用户

xuxueli hace 6 años
padre
commit
b3b9ff80d4

+ 1 - 1
doc/XXL-JOB官方文档.md Ver fichero

@@ -1469,7 +1469,7 @@ Tips: 历史版本(V1.3.x)目前已经Release至稳定版本, 进入维护阶段
1469 1469
 - 1、[规划中] 移除quartz:精简底层实现,优化已知问题;
1470 1470
     - 触发:单节点周期性触发,运行事件如delayqueue;
1471 1471
     - 调度:集群竞争,负载方式协同处理,竞争-加入时间轮-释放-竞争;
1472
-- 2、[规划中] 用户管理:支持在线维护系统用户;
1472
+- 2、用户管理:支持在线维护系统用户;
1473 1473
 - 3、[规划中] 权限管理:执行器为粒度分配权限,核心操作校验权限,暂定管理员、普通用户两种角色;
1474 1474
 - 4、调度日志优化:支持设置日志保留天数,过期日志天维度记录报表,并清理;调度报表汇总实时数据和报表;
1475 1475
 - 5、调度线程池参数调优;

+ 10 - 0
doc/db/tables_xxl_job.sql Ver fichero

@@ -224,10 +224,20 @@ CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_GROUP` (
224 224
   PRIMARY KEY (`id`)
225 225
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
226 226
 
227
+CREATE TABLE `XXL_JOB_QRTZ_USER` (
228
+  `id` int(11) NOT NULL AUTO_INCREMENT,
229
+  `username` varchar(50) NOT NULL COMMENT '账号',
230
+  `password` varchar(50) NOT NULL COMMENT '密码',
231
+  `role` tinyint(4) NOT NULL COMMENT '角色:0-普通用户、1-管理员',
232
+  `permission` varchar(255) DEFAULT NULL COMMENT '权限:执行器ID列表,多个逗号分割',
233
+  PRIMARY KEY (`id`),
234
+  UNIQUE KEY `i_username` (`username`) USING BTREE
235
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
227 236
 
228 237
 
229 238
 INSERT INTO `XXL_JOB_QRTZ_TRIGGER_GROUP`(`id`, `app_name`, `title`, `order`, `address_type`, `address_list`) VALUES (1, 'xxl-job-executor-sample', '示例执行器', 1, 0, NULL);
230 239
 INSERT INTO `XXL_JOB_QRTZ_TRIGGER_INFO`(`id`, `job_group`, `job_cron`, `job_desc`, `add_time`, `update_time`, `author`, `alarm_email`, `executor_route_strategy`, `executor_handler`, `executor_param`, `executor_block_strategy`, `executor_timeout`, `executor_fail_retry_count`, `glue_type`, `glue_source`, `glue_remark`, `glue_updatetime`, `child_jobid`) VALUES (1, 1, '0 0 0 * * ? *', '测试任务1', '2018-11-03 22:21:31', '2018-11-03 22:21:31', 'XXL', '', 'FIRST', 'demoJobHandler', '', 'SERIAL_EXECUTION', 0, 0, 'BEAN', '', 'GLUE代码初始化', '2018-11-03 22:21:31', '');
240
+INSERT INTO `XXL_JOB_QRTZ_USER`(`id`, `username`, `password`, `role`, `permission`) VALUES (1, 'admin', '49ba59abbe56e057', 1, NULL);
231 241
 
232 242
 commit;
233 243
 

+ 124 - 0
xxl-job-admin/src/main/java/com/xxl/job/admin/controller/UserController.java Ver fichero

@@ -0,0 +1,124 @@
1
+package com.xxl.job.admin.controller;
2
+
3
+import com.xxl.job.admin.core.model.XxlJobGroup;
4
+import com.xxl.job.admin.core.model.XxlJobUser;
5
+import com.xxl.job.admin.core.util.I18nUtil;
6
+import com.xxl.job.admin.dao.XxlJobGroupDao;
7
+import com.xxl.job.admin.dao.XxlJobUserDao;
8
+import com.xxl.job.core.biz.model.ReturnT;
9
+import org.springframework.stereotype.Controller;
10
+import org.springframework.ui.Model;
11
+import org.springframework.util.DigestUtils;
12
+import org.springframework.util.StringUtils;
13
+import org.springframework.web.bind.annotation.RequestMapping;
14
+import org.springframework.web.bind.annotation.RequestParam;
15
+import org.springframework.web.bind.annotation.ResponseBody;
16
+
17
+import javax.annotation.Resource;
18
+import java.util.HashMap;
19
+import java.util.List;
20
+import java.util.Map;
21
+
22
+/**
23
+ * @author xuxueli 2019-05-04 16:39:50
24
+ */
25
+@Controller
26
+@RequestMapping("/user")
27
+public class UserController {
28
+
29
+    @Resource
30
+    private XxlJobUserDao xxlJobUserDao;
31
+    @Resource
32
+    private XxlJobGroupDao xxlJobGroupDao;
33
+
34
+    @RequestMapping
35
+    public String index(Model model) {
36
+
37
+        // 执行器列表
38
+        List<XxlJobGroup> groupList = xxlJobGroupDao.findAll();
39
+        model.addAttribute("groupList", groupList);
40
+
41
+        return "user/user.index";
42
+    }
43
+
44
+    @RequestMapping("/pageList")
45
+    @ResponseBody
46
+    public Map<String, Object> pageList(@RequestParam(required = false, defaultValue = "0") int start,
47
+                                        @RequestParam(required = false, defaultValue = "10") int length,
48
+                                        String username) {
49
+
50
+        // page list
51
+        List<XxlJobUser> list = xxlJobUserDao.pageList(start, length, username);
52
+        int list_count = xxlJobUserDao.pageListCount(start, length, username);
53
+
54
+        // package result
55
+        Map<String, Object> maps = new HashMap<String, Object>();
56
+        maps.put("recordsTotal", list_count);		// 总记录数
57
+        maps.put("recordsFiltered", list_count);	// 过滤后的总记录数
58
+        maps.put("data", list);  					// 分页列表
59
+        return maps;
60
+    }
61
+
62
+    @RequestMapping("/add")
63
+    @ResponseBody
64
+    public ReturnT<String> add(XxlJobUser xxlJobUser) {
65
+
66
+        // valid username
67
+        if (!StringUtils.hasText(xxlJobUser.getUsername())) {
68
+            return new ReturnT<String>(ReturnT.FAIL_CODE, I18nUtil.getString("system_please_input")+I18nUtil.getString("user_username") );
69
+        }
70
+        xxlJobUser.setUsername(xxlJobUser.getUsername().trim());
71
+        if (!(xxlJobUser.getUsername().length()>=4 && xxlJobUser.getUsername().length()<=20)) {
72
+            return new ReturnT<String>(ReturnT.FAIL_CODE, I18nUtil.getString("system_lengh_limit")+"[4-20]" );
73
+        }
74
+        // valid password
75
+        if (!StringUtils.hasText(xxlJobUser.getPassword())) {
76
+            return new ReturnT<String>(ReturnT.FAIL_CODE, I18nUtil.getString("system_please_input")+I18nUtil.getString("user_password") );
77
+        }
78
+        xxlJobUser.setPassword(xxlJobUser.getPassword().trim());
79
+        if (!(xxlJobUser.getPassword().length()>=4 && xxlJobUser.getPassword().length()<=20)) {
80
+            return new ReturnT<String>(ReturnT.FAIL_CODE, I18nUtil.getString("system_lengh_limit")+"[4-20]" );
81
+        }
82
+        // md5 password
83
+        xxlJobUser.setPassword(DigestUtils.md5DigestAsHex(xxlJobUser.getPassword().getBytes()));
84
+
85
+        // check repeat
86
+        XxlJobUser existUser = xxlJobUserDao.loadByUserName(xxlJobUser.getUsername());
87
+        if (existUser != null) {
88
+            return new ReturnT<String>(ReturnT.FAIL_CODE, I18nUtil.getString("user_username_repeat") );
89
+        }
90
+
91
+        // write
92
+        xxlJobUserDao.save(xxlJobUser);
93
+        return ReturnT.SUCCESS;
94
+    }
95
+
96
+    @RequestMapping("/update")
97
+    @ResponseBody
98
+    public ReturnT<String> update(XxlJobUser xxlJobUser) {
99
+
100
+        // valid password
101
+        if (StringUtils.hasText(xxlJobUser.getPassword())) {
102
+            xxlJobUser.setPassword(xxlJobUser.getPassword().trim());
103
+            if (!(xxlJobUser.getPassword().length()>=4 && xxlJobUser.getPassword().length()<=20)) {
104
+                return new ReturnT<String>(ReturnT.FAIL_CODE, I18nUtil.getString("system_lengh_limit")+"[4-20]" );
105
+            }
106
+            // md5 password
107
+            xxlJobUser.setPassword(DigestUtils.md5DigestAsHex(xxlJobUser.getPassword().getBytes()));
108
+        } else {
109
+            xxlJobUser.setPassword(null);
110
+        }
111
+
112
+        // write
113
+        xxlJobUserDao.update(xxlJobUser);
114
+        return ReturnT.SUCCESS;
115
+    }
116
+
117
+    @RequestMapping("/remove")
118
+    @ResponseBody
119
+    public ReturnT<String> remove(int id) {
120
+        xxlJobUserDao.delete(id);
121
+        return ReturnT.SUCCESS;
122
+    }
123
+
124
+}

+ 54 - 0
xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobUser.java Ver fichero

@@ -0,0 +1,54 @@
1
+package com.xxl.job.admin.core.model;
2
+
3
+/**
4
+ * @author xuxueli 2019-05-04 16:43:12
5
+ */
6
+public class XxlJobUser {
7
+	
8
+	private int id;
9
+	private String username;		// 账号
10
+	private String password;		// 密码
11
+	private int role;				// 角色:0-普通用户、1-管理员
12
+	private String permission;	// 权限:执行器ID列表,多个逗号分割
13
+
14
+	public int getId() {
15
+		return id;
16
+	}
17
+
18
+	public void setId(int id) {
19
+		this.id = id;
20
+	}
21
+
22
+	public String getUsername() {
23
+		return username;
24
+	}
25
+
26
+	public void setUsername(String username) {
27
+		this.username = username;
28
+	}
29
+
30
+	public String getPassword() {
31
+		return password;
32
+	}
33
+
34
+	public void setPassword(String password) {
35
+		this.password = password;
36
+	}
37
+
38
+	public int getRole() {
39
+		return role;
40
+	}
41
+
42
+	public void setRole(int role) {
43
+		this.role = role;
44
+	}
45
+
46
+	public String getPermission() {
47
+		return permission;
48
+	}
49
+
50
+	public void setPermission(String permission) {
51
+		this.permission = permission;
52
+	}
53
+
54
+}

+ 29 - 0
xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobUserDao.java Ver fichero

@@ -0,0 +1,29 @@
1
+package com.xxl.job.admin.dao;
2
+
3
+import com.xxl.job.admin.core.model.XxlJobUser;
4
+import org.apache.ibatis.annotations.Mapper;
5
+import org.apache.ibatis.annotations.Param;
6
+import java.util.List;
7
+
8
+/**
9
+ * @author xuxueli 2019-05-04 16:44:59
10
+ */
11
+@Mapper
12
+public interface XxlJobUserDao {
13
+
14
+	public List<XxlJobUser> pageList(@Param("offset") int offset,
15
+                                     @Param("pagesize") int pagesize,
16
+                                     @Param("username") String username);
17
+	public int pageListCount(@Param("offset") int offset,
18
+							 @Param("pagesize") int pagesize,
19
+							 @Param("username") String username);
20
+
21
+	public XxlJobUser loadByUserName(@Param("username") String username);
22
+
23
+	public int save(XxlJobUser xxlJobUser);
24
+
25
+	public int update(XxlJobUser xxlJobUser);
26
+	
27
+	public int delete(@Param("id") int id);
28
+
29
+}

+ 14 - 0
xxl-job-admin/src/main/resources/i18n/message.properties Ver fichero

@@ -31,6 +31,7 @@ system_unvalid=非法
31 31
 system_not_found=不存在
32 32
 system_nav=导航
33 33
 system_digits=整数
34
+system_lengh_limit=长度限制
34 35
 
35 36
 ## daterangepicker
36 37
 daterangepicker_ranges_recent_hour=最近一小时
@@ -229,6 +230,19 @@ jobconf_trigger_type_parent=父任务触发
229 230
 jobconf_trigger_type_api=API触发
230 231
 jobconf_trigger_type_retry=失败重试触发
231 232
 
233
+## user
234
+user_manage=用户管理
235
+user_username=账号
236
+user_password=密码
237
+user_role=角色
238
+user_role_admin=管理员
239
+user_role_normal=普通用户
240
+user_permission=权限
241
+user_add=新增用户
242
+user_update=更新用户
243
+user_username_repeat=账号重复
244
+user_password_update_placeholder=请输入新密码,为空则不更新密码
245
+
232 246
 ## help
233 247
 job_help=使用教程
234 248
 job_help_document=官方文档

+ 14 - 0
xxl-job-admin/src/main/resources/i18n/message_en.properties Ver fichero

@@ -31,6 +31,7 @@ system_unvalid=illegal
31 31
 system_not_found=not exist
32 32
 system_nav=Navigation
33 33
 system_digits=digits
34
+system_lengh_limit=Length limit
34 35
 
35 36
 ## daterangepicker
36 37
 daterangepicker_ranges_recent_hour=recent one hour
@@ -229,6 +230,19 @@ jobconf_trigger_type_parent=Parent job trigger
229 230
 jobconf_trigger_type_api=Api trigger
230 231
 jobconf_trigger_type_retry=Fail retry trigger
231 232
 
233
+## user
234
+user_manage==User Manage
235
+user_username=Username
236
+user_password=Password
237
+user_role=Role
238
+user_role_admin=Admin User
239
+user_role_normal=Normal User
240
+user_permission=Permission
241
+user_add=Add User
242
+user_update=Edit User
243
+user_username_repeat=Username Repeat
244
+user_password_update_placeholder=Please input password, empty means not update
245
+
232 246
 ## help
233 247
 job_help=Tutorial
234 248
 job_help_document=Official Document

+ 81 - 0
xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobUserMapper.xml Ver fichero

@@ -0,0 +1,81 @@
1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
3
+	"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
4
+<mapper namespace="com.xxl.job.admin.dao.XxlJobUserDao">
5
+
6
+	<resultMap id="XxlJobUser" type="com.xxl.job.admin.core.model.XxlJobUser" >
7
+		<result column="id" property="id" />
8
+		<result column="username" property="username" />
9
+	    <result column="password" property="password" />
10
+	    <result column="role" property="role" />
11
+	    <result column="permission" property="permission" />
12
+	</resultMap>
13
+
14
+	<sql id="Base_Column_List">
15
+		t.id,
16
+		t.username,
17
+		t.password,
18
+		t.role,
19
+		t.permission
20
+	</sql>
21
+
22
+	<select id="pageList" parameterType="java.util.HashMap" resultMap="XxlJobUser">
23
+		SELECT <include refid="Base_Column_List" />
24
+		FROM XXL_JOB_QRTZ_USER AS t
25
+		<trim prefix="WHERE" prefixOverrides="AND | OR" >
26
+			<if test="username != null and username != ''">
27
+				AND t.username like CONCAT(CONCAT('%', #{username}), '%')
28
+			</if>
29
+		</trim>
30
+		ORDER BY username ASC
31
+		LIMIT #{offset}, #{pagesize}
32
+	</select>
33
+
34
+	<select id="pageListCount" parameterType="java.util.HashMap" resultType="int">
35
+		SELECT count(1)
36
+		FROM XXL_JOB_QRTZ_USER AS t
37
+		<trim prefix="WHERE" prefixOverrides="AND | OR" >
38
+			<if test="username != null and username != ''">
39
+				AND t.username like CONCAT(CONCAT('%', #{username}), '%')
40
+			</if>
41
+		</trim>
42
+	</select>
43
+
44
+	<select id="loadByUserName" parameterType="java.util.HashMap" resultMap="XxlJobUser">
45
+		SELECT <include refid="Base_Column_List" />
46
+		FROM XXL_JOB_QRTZ_USER AS t
47
+		WHERE t.username = #{username}
48
+	</select>
49
+
50
+	<insert id="save" parameterType="com.xxl.job.admin.core.model.XxlJobUser" useGeneratedKeys="true" keyProperty="id" >
51
+		INSERT INTO XXL_JOB_QRTZ_USER (
52
+			username,
53
+			password,
54
+			role,
55
+			permission
56
+		) VALUES (
57
+			#{username},
58
+			#{password},
59
+			#{role},
60
+			#{permission}
61
+		);
62
+	</insert>
63
+
64
+	<update id="update" parameterType="com.xxl.job.admin.core.model.XxlJobUser" >
65
+		UPDATE XXL_JOB_QRTZ_USER
66
+		SET
67
+			<if test="password != null and password != ''">
68
+				password = #{password},
69
+			</if>
70
+			role = #{role},
71
+			permission = #{permission}
72
+		WHERE id = #{id}
73
+	</update>
74
+
75
+	<delete id="delete" parameterType="java.util.HashMap">
76
+		DELETE
77
+		FROM XXL_JOB_QRTZ_USER
78
+		WHERE id = #{id}
79
+	</delete>
80
+
81
+</mapper>

+ 298 - 0
xxl-job-admin/src/main/resources/static/js/user.index.1.js Ver fichero

@@ -0,0 +1,298 @@
1
+$(function() {
2
+
3
+	// init date tables
4
+	var userListTable = $("#user_list").dataTable({
5
+		"deferRender": true,
6
+		"processing" : true, 
7
+	    "serverSide": true,
8
+		"ajax": {
9
+			url: base_url + "/user/pageList",
10
+			type:"post",
11
+	        data : function ( d ) {
12
+	        	var obj = {};
13
+                obj.username = $('#username').val();
14
+	        	obj.start = d.start;
15
+	        	obj.length = d.length;
16
+                return obj;
17
+            }
18
+	    },
19
+	    "searching": false,
20
+	    "ordering": false,
21
+	    //"scrollX": true,	// scroll x,close self-adaption
22
+	    "columns": [
23
+	                {
24
+	                	"data": 'id',
25
+						"visible" : false,
26
+						"width":'10%'
27
+					},
28
+	                {
29
+	                	"data": 'username',
30
+						"visible" : true,
31
+						"width":'20%'
32
+					},
33
+	                {
34
+	                	"data": 'password',
35
+						"visible" : true,
36
+                        "width":'20%'
37
+					},
38
+					{
39
+						"data": 'role',
40
+						"visible" : true,
41
+						"width":'10%',
42
+                        "render": function ( data, type, row ) {
43
+                            if (data == 1) {
44
+                                return I18n.user_role_admin
45
+                            } else {
46
+                                return I18n.user_role_normal
47
+                            }
48
+                        }
49
+					},
50
+	                {
51
+	                	"data": 'permission',
52
+						"width":'10%',
53
+	                	"visible" : true
54
+	                },
55
+	                {
56
+						"data": I18n.system_opt ,
57
+						"width":'15%',
58
+	                	"render": function ( data, type, row ) {
59
+	                		return function(){
60
+								// html
61
+                                tableData['key'+row.id] = row;
62
+								var html = '<p id="'+ row.id +'" >'+
63
+									'<button class="btn btn-warning btn-xs update" type="button">'+ I18n.system_opt_edit +'</button>  '+
64
+									'<button class="btn btn-danger btn-xs delete" type="button">'+ I18n.system_opt_del +'</button>  '+
65
+									'</p>';
66
+
67
+	                			return html;
68
+							};
69
+	                	}
70
+	                }
71
+	            ],
72
+		"language" : {
73
+			"sProcessing" : I18n.dataTable_sProcessing ,
74
+			"sLengthMenu" : I18n.dataTable_sLengthMenu ,
75
+			"sZeroRecords" : I18n.dataTable_sZeroRecords ,
76
+			"sInfo" : I18n.dataTable_sInfo ,
77
+			"sInfoEmpty" : I18n.dataTable_sInfoEmpty ,
78
+			"sInfoFiltered" : I18n.dataTable_sInfoFiltered ,
79
+			"sInfoPostFix" : "",
80
+			"sSearch" : I18n.dataTable_sSearch ,
81
+			"sUrl" : "",
82
+			"sEmptyTable" : I18n.dataTable_sEmptyTable ,
83
+			"sLoadingRecords" : I18n.dataTable_sLoadingRecords ,
84
+			"sInfoThousands" : ",",
85
+			"oPaginate" : {
86
+				"sFirst" : I18n.dataTable_sFirst ,
87
+				"sPrevious" : I18n.dataTable_sPrevious ,
88
+				"sNext" : I18n.dataTable_sNext ,
89
+				"sLast" : I18n.dataTable_sLast
90
+			},
91
+			"oAria" : {
92
+				"sSortAscending" : I18n.dataTable_sSortAscending ,
93
+				"sSortDescending" : I18n.dataTable_sSortDescending
94
+			}
95
+		}
96
+	});
97
+
98
+    // table data
99
+    var tableData = {};
100
+
101
+	// search btn
102
+	$('#searchBtn').on('click', function(){
103
+        userListTable.fnDraw();
104
+	});
105
+	
106
+	// job operate
107
+	$("#user_list").on('click', '.delete',function() {
108
+		var id = $(this).parent('p').attr("id");
109
+
110
+		layer.confirm( I18n.system_ok + I18n.system_opt_del + '?', {
111
+			icon: 3,
112
+			title: I18n.system_tips ,
113
+            btn: [ I18n.system_ok, I18n.system_cancel ]
114
+		}, function(index){
115
+			layer.close(index);
116
+
117
+			$.ajax({
118
+				type : 'POST',
119
+				url : base_url + "/user/remove",
120
+				data : {
121
+					"id" : id
122
+				},
123
+				dataType : "json",
124
+				success : function(data){
125
+					if (data.code == 200) {
126
+                        layer.msg( I18n.system_success );
127
+						userListTable.fnDraw(false);
128
+					} else {
129
+                        layer.msg( data.msg || I18n.system_opt_del + I18n.system_fail );
130
+					}
131
+				}
132
+			});
133
+		});
134
+	});
135
+
136
+	// add
137
+	$(".add").click(function(){
138
+		$('#addModal').modal({backdrop: false, keyboard: false}).modal('show');
139
+	});
140
+	var addModalValidate = $("#addModal .form").validate({
141
+		errorElement : 'span',  
142
+        errorClass : 'help-block',
143
+        focusInvalid : true,  
144
+        rules : {
145
+            username : {
146
+				required : true,
147
+                rangelength:[4, 20]
148
+			},
149
+            password : {
150
+                required : true,
151
+                rangelength:[4, 20]
152
+            }
153
+        }, 
154
+        messages : {
155
+            username : {
156
+            	required : I18n.system_please_input + I18n.user_username,
157
+                rangelength: I18n.system_lengh_limit + "[4-20]"
158
+            },
159
+            password : {
160
+                required : I18n.system_please_input + I18n.user_password,
161
+                rangelength: I18n.system_lengh_limit + "[4-20]"
162
+            }
163
+        },
164
+		highlight : function(element) {  
165
+            $(element).closest('.form-group').addClass('has-error');  
166
+        },
167
+        success : function(label) {  
168
+            label.closest('.form-group').removeClass('has-error');  
169
+            label.remove();  
170
+        },
171
+        errorPlacement : function(error, element) {  
172
+            element.parent('div').append(error);  
173
+        },
174
+        submitHandler : function(form) {
175
+
176
+            var permissionArr = [];
177
+            $("#addModal .form input[name=permission]:checked").each(function(){
178
+                permissionArr.push($(this).val());
179
+            });
180
+
181
+			var paramData = {
182
+				"username": $("#addModal .form input[name=username]").val(),
183
+                "password": $("#addModal .form input[name=password]").val(),
184
+                "role": $("#addModal .form input[name=role]:checked").val(),
185
+                "permission": permissionArr.join(',')
186
+			};
187
+
188
+        	$.post(base_url + "/user/add", paramData, function(data, status) {
189
+    			if (data.code == "200") {
190
+					$('#addModal').modal('hide');
191
+
192
+                    layer.msg( I18n.system_add_suc );
193
+                    userListTable.fnDraw();
194
+    			} else {
195
+					layer.open({
196
+						title: I18n.system_tips ,
197
+                        btn: [ I18n.system_ok ],
198
+						content: (data.msg || I18n.system_add_fail),
199
+						icon: '2'
200
+					});
201
+    			}
202
+    		});
203
+		}
204
+	});
205
+	$("#addModal").on('hide.bs.modal', function () {
206
+		$("#addModal .form")[0].reset();
207
+		addModalValidate.resetForm();
208
+		$("#addModal .form .form-group").removeClass("has-error");
209
+		$(".remote_panel").show();	// remote
210
+	});
211
+
212
+	// update
213
+	$("#user_list").on('click', '.update',function() {
214
+
215
+        var id = $(this).parent('p').attr("id");
216
+        var row = tableData['key'+id];
217
+
218
+		// base data
219
+		$("#updateModal .form input[name='id']").val( row.id );
220
+		$("#updateModal .form input[name='username']").val( row.username );
221
+		$("#updateModal .form input[name='password']").val( '' );
222
+		$("#updateModal .form input[name='role']").each(function () {
223
+			if($(this).val() == row.role) {
224
+                $(this).prop("checked",true);
225
+			} else {
226
+                $(this).prop("checked",false);
227
+			}
228
+        });
229
+        var permissionArr = [];
230
+        if (row.permission) {
231
+            permissionArr = row.permission.split(",");
232
+		}
233
+        $("#updateModal .form input[name='permission']").removeProp('checked');
234
+        $("#updateModal .form input[name='permission']").each(function () {
235
+            if($.inArray($(this).val(), permissionArr) > -1) {
236
+                $(this).prop("checked",true);
237
+            } else {
238
+                $(this).prop("checked",false);
239
+            }
240
+        });
241
+
242
+		// show
243
+		$('#updateModal').modal({backdrop: false, keyboard: false}).modal('show');
244
+	});
245
+	var updateModalValidate = $("#updateModal .form").validate({
246
+		errorElement : 'span',  
247
+        errorClass : 'help-block',
248
+        focusInvalid : true,
249
+		highlight : function(element) {
250
+            $(element).closest('.form-group').addClass('has-error');  
251
+        },
252
+        success : function(label) {  
253
+            label.closest('.form-group').removeClass('has-error');  
254
+            label.remove();  
255
+        },
256
+        errorPlacement : function(error, element) {  
257
+            element.parent('div').append(error);  
258
+        },
259
+        submitHandler : function(form) {
260
+
261
+            var permissionArr =[];
262
+            $("#updateModal .form input[name=permission]:checked").each(function(){
263
+                permissionArr.push($(this).val());
264
+            });
265
+
266
+            var paramData = {
267
+                "id": $("#updateModal .form input[name=id]").val(),
268
+                "username": $("#updateModal .form input[name=username]").val(),
269
+                "password": $("#updateModal .form input[name=password]").val(),
270
+                "role": $("#updateModal .form input[name=role]:checked").val(),
271
+                "permission": permissionArr.join(',')
272
+            };
273
+
274
+            $.post(base_url + "/user/update", paramData, function(data, status) {
275
+                if (data.code == "200") {
276
+                    $('#updateModal').modal('hide');
277
+
278
+                    layer.msg( I18n.system_update_suc );
279
+                    userListTable.fnDraw();
280
+                } else {
281
+                    layer.open({
282
+                        title: I18n.system_tips ,
283
+                        btn: [ I18n.system_ok ],
284
+                        content: (data.msg || I18n.system_update_fail),
285
+                        icon: '2'
286
+                    });
287
+                }
288
+            });
289
+		}
290
+	});
291
+	$("#updateModal").on('hide.bs.modal', function () {
292
+        $("#updateModal .form")[0].reset();
293
+        updateModalValidate.resetForm();
294
+        $("#updateModal .form .form-group").removeClass("has-error");
295
+        $(".remote_panel").show();	// remote
296
+	});
297
+
298
+});

+ 1 - 0
xxl-job-admin/src/main/resources/templates/common/common.macro.ftl Ver fichero

@@ -103,6 +103,7 @@
103 103
 				<li class="nav-click <#if pageName == "jobinfo">active</#if>" ><a href="${request.contextPath}/jobinfo"><i class="fa fa-circle-o text-yellow"></i><span>${I18n.jobinfo_name}</span></a></li>
104 104
 				<li class="nav-click <#if pageName == "joblog">active</#if>" ><a href="${request.contextPath}/joblog"><i class="fa fa-circle-o text-green"></i><span>${I18n.joblog_name}</span></a></li>
105 105
                 <li class="nav-click <#if pageName == "jobgroup">active</#if>" ><a href="${request.contextPath}/jobgroup"><i class="fa fa-circle-o text-red"></i><span>${I18n.jobgroup_name}</span></a></li>
106
+                <li class="nav-click <#if pageName == "user">active</#if>" ><a href="${request.contextPath}/user"><i class="fa fa-circle-o text-purple"></i><span>${I18n.user_manage}</span></a></li>
106 107
 				<li class="nav-click <#if pageName == "help">active</#if>" ><a href="${request.contextPath}/help"><i class="fa fa-circle-o text-gray"></i><span>${I18n.job_help}</span></a></li>
107 108
 			</ul>
108 109
 		</section>

+ 179 - 0
xxl-job-admin/src/main/resources/templates/user/user.index.ftl Ver fichero

@@ -0,0 +1,179 @@
1
+<!DOCTYPE html>
2
+<html>
3
+<head>
4
+  	<#import "../common/common.macro.ftl" as netCommon>
5
+	<@netCommon.commonStyle />
6
+	<!-- DataTables -->
7
+  	<link rel="stylesheet" href="${request.contextPath}/static/adminlte/bower_components/datatables.net-bs/css/dataTables.bootstrap.min.css">
8
+    <title>${I18n.admin_name}</title>
9
+</head>
10
+<body class="hold-transition skin-blue sidebar-mini <#if cookieMap?exists && cookieMap["xxljob_adminlte_settings"]?exists && "off" == cookieMap["xxljob_adminlte_settings"].value >sidebar-collapse</#if>">
11
+<div class="wrapper">
12
+	<!-- header -->
13
+	<@netCommon.commonHeader />
14
+	<!-- left -->
15
+	<@netCommon.commonLeft "user" />
16
+	
17
+	<!-- Content Wrapper. Contains page content -->
18
+	<div class="content-wrapper">
19
+		<!-- Content Header (Page header) -->
20
+		<section class="content-header">
21
+			<h1>${I18n.user_manage}</h1>
22
+		</section>
23
+		
24
+		<!-- Main content -->
25
+	    <section class="content">
26
+	    
27
+	    	<div class="row">
28
+                <div class="col-xs-3">
29
+                    <div class="input-group">
30
+                        <span class="input-group-addon">${I18n.user_username}</span>
31
+                        <input type="text" class="form-control" id="username" autocomplete="on" >
32
+                    </div>
33
+                </div>
34
+	            <div class="col-xs-1">
35
+	            	<button class="btn btn-block btn-info" id="searchBtn">${I18n.system_search}</button>
36
+	            </div>
37
+	            <div class="col-xs-2">
38
+	            	<button class="btn btn-block btn-success add" type="button">${I18n.user_add}</button>
39
+	            </div>
40
+          	</div>
41
+	    	
42
+			<div class="row">
43
+				<div class="col-xs-12">
44
+					<div class="box">
45
+			            <div class="box-body" >
46
+			              	<table id="user_list" class="table table-bordered table-striped" width="100%" >
47
+				                <thead>
48
+					            	<tr>
49
+                                        <th name="id" >ID</th>
50
+                                        <th name="username" >${I18n.user_username}</th>
51
+					                  	<th name="password" >${I18n.user_password}</th>
52
+                                        <th name="role" >${I18n.user_role}</th>
53
+					                  	<th name="permission" >${I18n.user_permission}</th>
54
+					                  	<th>${I18n.system_opt}</th>
55
+					                </tr>
56
+				                </thead>
57
+				                <tbody></tbody>
58
+				                <tfoot></tfoot>
59
+							</table>
60
+						</div>
61
+					</div>
62
+				</div>
63
+			</div>
64
+	    </section>
65
+	</div>
66
+	
67
+	<!-- footer -->
68
+	<@netCommon.commonFooter />
69
+</div>
70
+
71
+<!-- 新增.模态框 -->
72
+<div class="modal fade" id="addModal" tabindex="-1" role="dialog"  aria-hidden="true">
73
+	<div class="modal-dialog">
74
+		<div class="modal-content">
75
+			<div class="modal-header">
76
+            	<h4 class="modal-title" >${I18n.user_add}</h4>
77
+         	</div>
78
+         	<div class="modal-body">
79
+				<form class="form-horizontal form" role="form" >
80
+                    <div class="form-group">
81
+                        <label for="lastname" class="col-sm-2 control-label">${I18n.user_username}<font color="red">*</font></label>
82
+                        <div class="col-sm-8"><input type="text" class="form-control" name="username" placeholder="${I18n.system_please_input}${I18n.user_username}" maxlength="50" ></div>
83
+                    </div>
84
+                    <div class="form-group">
85
+                        <label for="lastname" class="col-sm-2 control-label">${I18n.user_password}<font color="red">*</font></label>
86
+                        <div class="col-sm-8"><input type="text" class="form-control" name="password" placeholder="${I18n.system_please_input}${I18n.user_password}" maxlength="50" ></div>
87
+                    </div>
88
+                    <div class="form-group">
89
+                        <label for="lastname" class="col-sm-2 control-label">${I18n.user_role}<font color="red">*</font></label>
90
+                        <div class="col-sm-10">
91
+                            <input type="radio" name="role" value="0" checked />${I18n.user_role_normal}
92
+                            &nbsp;&nbsp;&nbsp;&nbsp;
93
+                            <input type="radio" name="role" value="1" />${I18n.user_role_admin}
94
+                        </div>
95
+                    </div>
96
+                    <div class="form-group">
97
+                        <label for="lastname" class="col-sm-2 control-label">${I18n.user_permission}<font color="black">*</font></label>
98
+                        <div class="col-sm-10">
99
+							<#if groupList?exists && groupList?size gt 0>
100
+								<#list groupList as item>
101
+                                    <input type="checkbox" name="permission" value="${item.id}" />${item.title}(${item.appName})<br>
102
+								</#list>
103
+							</#if>
104
+                        </div>
105
+                    </div>
106
+
107
+                    <hr>
108
+					<div class="form-group">
109
+						<div class="col-sm-offset-3 col-sm-6">
110
+							<button type="submit" class="btn btn-primary"  >${I18n.system_save}</button>
111
+							<button type="button" class="btn btn-default" data-dismiss="modal">${I18n.system_cancel}</button>
112
+						</div>
113
+					</div>
114
+
115
+				</form>
116
+         	</div>
117
+		</div>
118
+	</div>
119
+</div>
120
+
121
+<!-- 更新.模态框 -->
122
+<div class="modal fade" id="updateModal" tabindex="-1" role="dialog"  aria-hidden="true">
123
+	<div class="modal-dialog">
124
+		<div class="modal-content">
125
+			<div class="modal-header">
126
+            	<h4 class="modal-title" >${I18n.user_update}</h4>
127
+         	</div>
128
+         	<div class="modal-body">
129
+				<form class="form-horizontal form" role="form" >
130
+                    <div class="form-group">
131
+                        <label for="lastname" class="col-sm-2 control-label">${I18n.user_username}<font color="red">*</font></label>
132
+                        <div class="col-sm-8"><input type="text" class="form-control" name="username" placeholder="${I18n.system_please_input}${I18n.user_username}" maxlength="50" readonly ></div>
133
+                    </div>
134
+                    <div class="form-group">
135
+                        <label for="lastname" class="col-sm-2 control-label">${I18n.user_password}<font color="red">*</font></label>
136
+                        <div class="col-sm-8"><input type="text" class="form-control" name="password" placeholder="${I18n.user_password_update_placeholder}" maxlength="50" ></div>
137
+                    </div>
138
+                    <div class="form-group">
139
+                        <label for="lastname" class="col-sm-2 control-label">${I18n.user_role}<font color="red">*</font></label>
140
+                        <div class="col-sm-10">
141
+                            <input type="radio" name="role" value="0" />${I18n.user_role_normal}
142
+                            &nbsp;&nbsp;&nbsp;&nbsp;
143
+                            <input type="radio" name="role" value="1" />${I18n.user_role_admin}
144
+                        </div>
145
+                    </div>
146
+                    <div class="form-group">
147
+                        <label for="lastname" class="col-sm-2 control-label">${I18n.user_permission}<font color="black">*</font></label>
148
+                        <div class="col-sm-10">
149
+						<#if groupList?exists && groupList?size gt 0>
150
+							<#list groupList as item>
151
+                                <input type="checkbox" name="permission" value="${item.id}" />${item.title}(${item.appName})<br>
152
+							</#list>
153
+						</#if>
154
+                        </div>
155
+                    </div>
156
+
157
+					<hr>
158
+					<div class="form-group">
159
+                        <div class="col-sm-offset-3 col-sm-6">
160
+							<button type="submit" class="btn btn-primary"  >${I18n.system_save}</button>
161
+							<button type="button" class="btn btn-default" data-dismiss="modal">${I18n.system_cancel}</button>
162
+                            <input type="hidden" name="id" >
163
+						</div>
164
+					</div>
165
+
166
+				</form>
167
+         	</div>
168
+		</div>
169
+	</div>
170
+</div>
171
+
172
+<@netCommon.commonScript />
173
+<!-- DataTables -->
174
+<script src="${request.contextPath}/static/adminlte/bower_components/datatables.net/js/jquery.dataTables.min.js"></script>
175
+<script src="${request.contextPath}/static/adminlte/bower_components/datatables.net-bs/js/dataTables.bootstrap.min.js"></script>
176
+<script src="${request.contextPath}/static/plugins/jquery/jquery.validate.min.js"></script>
177
+<script src="${request.contextPath}/static/js/user.index.1.js"></script>
178
+</body>
179
+</html>