@ddj-v2/user-management 2.0.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.npmignore +20 -20
- package/LICENSE +21 -21
- package/README.md +80 -77
- package/index.ts +479 -341
- package/package.json +25 -25
- package/pages/user_manage_detail.page.js +267 -267
- package/pages/user_manage_main.page.js +199 -199
- package/templates/domain_user.html +109 -0
- package/templates/user_manage_detail.html +285 -285
- package/templates/user_manage_main.html +233 -183
|
@@ -1,200 +1,200 @@
|
|
|
1
|
-
import $ from 'jquery';
|
|
2
|
-
import { NamedPage } from 'vj/misc/Page';
|
|
3
|
-
import Notification from 'vj/components/notification';
|
|
4
|
-
import { request } from 'vj/utils';
|
|
5
|
-
import i18n from 'vj/utils/i18n';
|
|
6
|
-
import pjax from 'vj/utils/pjax';
|
|
7
|
-
|
|
8
|
-
const page = new NamedPage('user_manage_main', () => {
|
|
9
|
-
// 搜索功能
|
|
10
|
-
$('#search-form').on('submit', function(e) {
|
|
11
|
-
e.preventDefault();
|
|
12
|
-
const keyword = $('#search-keyword').val().trim();
|
|
13
|
-
const type = $('#search-type').val();
|
|
14
|
-
const sort = $('#sort-by').val();
|
|
15
|
-
const order = $('#sort-order').val();
|
|
16
|
-
|
|
17
|
-
let url = window.location.pathname + '?';
|
|
18
|
-
const params = [];
|
|
19
|
-
|
|
20
|
-
if (keyword) {
|
|
21
|
-
params.push(`${type}=${encodeURIComponent(keyword)}`);
|
|
22
|
-
}
|
|
23
|
-
if (sort) {
|
|
24
|
-
params.push(`sort=${sort}`);
|
|
25
|
-
}
|
|
26
|
-
if (order) {
|
|
27
|
-
params.push(`order=${order}`);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
url += params.join('&');
|
|
31
|
-
pjax.request({ url });
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
// 清空搜索
|
|
35
|
-
$('#clear-search').on('click', function() {
|
|
36
|
-
$('#search-keyword').val('');
|
|
37
|
-
$('#search-type').val('uname');
|
|
38
|
-
$('#sort-by').val('_id');
|
|
39
|
-
$('#sort-order').val('desc');
|
|
40
|
-
pjax.request({ url: window.location.pathname });
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
// 快速封禁/解封用户
|
|
44
|
-
$('.ban-user').on('click', async function(e) {
|
|
45
|
-
e.preventDefault();
|
|
46
|
-
const uid = $(this).data('uid');
|
|
47
|
-
const username = $(this).data('username');
|
|
48
|
-
const action = $(this).data('action');
|
|
49
|
-
|
|
50
|
-
const confirmMessage = action === 'ban'
|
|
51
|
-
? i18n('Are you sure to ban user {0}?', username)
|
|
52
|
-
: i18n('Are you sure to unban user {0}?', username);
|
|
53
|
-
|
|
54
|
-
if (!confirm(confirmMessage)) {
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
try {
|
|
59
|
-
const response = await request.post(`/manage/users/${uid}`, {
|
|
60
|
-
operation: action === 'ban' ? 'ban' : 'unban'
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
if (response.success) {
|
|
64
|
-
Notification.success(action === 'ban' ? i18n('User banned successfully') : i18n('User unbanned successfully'));
|
|
65
|
-
// 刷新页面
|
|
66
|
-
window.location.reload();
|
|
67
|
-
} else {
|
|
68
|
-
Notification.error(response.message || i18n('Operation failed'));
|
|
69
|
-
}
|
|
70
|
-
} catch (error) {
|
|
71
|
-
console.error('Error:', error);
|
|
72
|
-
Notification.error(i18n('Operation failed'));
|
|
73
|
-
}
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
// 快速设置权限
|
|
77
|
-
$('.set-priv').on('click', async function(e) {
|
|
78
|
-
e.preventDefault();
|
|
79
|
-
const uid = $(this).data('uid');
|
|
80
|
-
const username = $(this).data('username');
|
|
81
|
-
const currentPriv = $(this).data('priv');
|
|
82
|
-
|
|
83
|
-
const newPriv = prompt(i18n('Enter new privilege value for user {0}:', username), currentPriv);
|
|
84
|
-
if (newPriv === null || newPriv === '') {
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const privValue = parseInt(newPriv, 10);
|
|
89
|
-
if (isNaN(privValue)) {
|
|
90
|
-
Notification.error(i18n('Invalid privilege value'));
|
|
91
|
-
return;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
if (!confirm(i18n('Are you sure to set privilege of user {0} to {1}?', username, privValue))) {
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
try {
|
|
99
|
-
const response = await request.post(`/manage/users/${uid}`, {
|
|
100
|
-
operation: 'setPriv',
|
|
101
|
-
priv: privValue
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
if (response.success) {
|
|
105
|
-
Notification.success(i18n('Privilege updated successfully'));
|
|
106
|
-
// 刷新页面
|
|
107
|
-
window.location.reload();
|
|
108
|
-
} else {
|
|
109
|
-
Notification.error(response.message || i18n('Operation failed'));
|
|
110
|
-
}
|
|
111
|
-
} catch (error) {
|
|
112
|
-
console.error('Error:', error);
|
|
113
|
-
Notification.error(i18n('Operation failed'));
|
|
114
|
-
}
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
// 分页链接处理
|
|
118
|
-
$('.pagination a').on('click', function(e) {
|
|
119
|
-
e.preventDefault();
|
|
120
|
-
const url = $(this).attr('href');
|
|
121
|
-
if (url && url !== '#') {
|
|
122
|
-
pjax.request({ url });
|
|
123
|
-
}
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
// 排序链接处理
|
|
127
|
-
$('.sortable').on('click', function(e) {
|
|
128
|
-
e.preventDefault();
|
|
129
|
-
const sort = $(this).data('sort');
|
|
130
|
-
const currentSort = new URLSearchParams(window.location.search).get('sort');
|
|
131
|
-
const currentOrder = new URLSearchParams(window.location.search).get('order') || 'desc';
|
|
132
|
-
|
|
133
|
-
let newOrder = 'desc';
|
|
134
|
-
if (currentSort === sort && currentOrder === 'desc') {
|
|
135
|
-
newOrder = 'asc';
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
const url = new URL(window.location);
|
|
139
|
-
url.searchParams.set('sort', sort);
|
|
140
|
-
url.searchParams.set('order', newOrder);
|
|
141
|
-
|
|
142
|
-
pjax.request({ url: url.toString() });
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
// 批量操作按钮
|
|
146
|
-
$('#batch-operations').on('click', function() {
|
|
147
|
-
window.location.href = '/manage/users/batch';
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
// 导出用户数据
|
|
151
|
-
$('#export-users').on('click', async function() {
|
|
152
|
-
try {
|
|
153
|
-
const response = await request.get('/manage/users', {
|
|
154
|
-
export: 'csv'
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
// 创建下载链接
|
|
158
|
-
const blob = new Blob([response], { type: 'text/csv' });
|
|
159
|
-
const url = window.URL.createObjectURL(blob);
|
|
160
|
-
const a = document.createElement('a');
|
|
161
|
-
a.href = url;
|
|
162
|
-
a.download = `users_${new Date().toISOString().split('T')[0]}.csv`;
|
|
163
|
-
document.body.appendChild(a);
|
|
164
|
-
a.click();
|
|
165
|
-
document.body.removeChild(a);
|
|
166
|
-
window.URL.revokeObjectURL(url);
|
|
167
|
-
|
|
168
|
-
Notification.success(i18n('Export completed'));
|
|
169
|
-
} catch (error) {
|
|
170
|
-
console.error('Export error:', error);
|
|
171
|
-
Notification.error(i18n('Export failed'));
|
|
172
|
-
}
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
// 刷新按钮
|
|
176
|
-
$('#refresh-list').on('click', function() {
|
|
177
|
-
window.location.reload();
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
// 键盘快捷键
|
|
181
|
-
$(document).on('keydown', function(e) {
|
|
182
|
-
// Ctrl+F 聚焦搜索框
|
|
183
|
-
if (e.ctrlKey && e.key === 'f') {
|
|
184
|
-
e.preventDefault();
|
|
185
|
-
$('#search-keyword').focus();
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
// Enter 键提交搜索
|
|
189
|
-
if (e.key === 'Enter' && $('#search-keyword').is(':focus')) {
|
|
190
|
-
$('#search-form').submit();
|
|
191
|
-
}
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
// 初始化提示
|
|
195
|
-
$('[data-tooltip]').each(function() {
|
|
196
|
-
$(this).attr('title', $(this).data('tooltip'));
|
|
197
|
-
});
|
|
198
|
-
});
|
|
199
|
-
|
|
1
|
+
import $ from 'jquery';
|
|
2
|
+
import { NamedPage } from 'vj/misc/Page';
|
|
3
|
+
import Notification from 'vj/components/notification';
|
|
4
|
+
import { request } from 'vj/utils';
|
|
5
|
+
import i18n from 'vj/utils/i18n';
|
|
6
|
+
import pjax from 'vj/utils/pjax';
|
|
7
|
+
|
|
8
|
+
const page = new NamedPage('user_manage_main', () => {
|
|
9
|
+
// 搜索功能
|
|
10
|
+
$('#search-form').on('submit', function(e) {
|
|
11
|
+
e.preventDefault();
|
|
12
|
+
const keyword = $('#search-keyword').val().trim();
|
|
13
|
+
const type = $('#search-type').val();
|
|
14
|
+
const sort = $('#sort-by').val();
|
|
15
|
+
const order = $('#sort-order').val();
|
|
16
|
+
|
|
17
|
+
let url = window.location.pathname + '?';
|
|
18
|
+
const params = [];
|
|
19
|
+
|
|
20
|
+
if (keyword) {
|
|
21
|
+
params.push(`${type}=${encodeURIComponent(keyword)}`);
|
|
22
|
+
}
|
|
23
|
+
if (sort) {
|
|
24
|
+
params.push(`sort=${sort}`);
|
|
25
|
+
}
|
|
26
|
+
if (order) {
|
|
27
|
+
params.push(`order=${order}`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
url += params.join('&');
|
|
31
|
+
pjax.request({ url });
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// 清空搜索
|
|
35
|
+
$('#clear-search').on('click', function() {
|
|
36
|
+
$('#search-keyword').val('');
|
|
37
|
+
$('#search-type').val('uname');
|
|
38
|
+
$('#sort-by').val('_id');
|
|
39
|
+
$('#sort-order').val('desc');
|
|
40
|
+
pjax.request({ url: window.location.pathname });
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// 快速封禁/解封用户
|
|
44
|
+
$('.ban-user').on('click', async function(e) {
|
|
45
|
+
e.preventDefault();
|
|
46
|
+
const uid = $(this).data('uid');
|
|
47
|
+
const username = $(this).data('username');
|
|
48
|
+
const action = $(this).data('action');
|
|
49
|
+
|
|
50
|
+
const confirmMessage = action === 'ban'
|
|
51
|
+
? i18n('Are you sure to ban user {0}?', username)
|
|
52
|
+
: i18n('Are you sure to unban user {0}?', username);
|
|
53
|
+
|
|
54
|
+
if (!confirm(confirmMessage)) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
const response = await request.post(`/manage/users/${uid}`, {
|
|
60
|
+
operation: action === 'ban' ? 'ban' : 'unban'
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
if (response.success) {
|
|
64
|
+
Notification.success(action === 'ban' ? i18n('User banned successfully') : i18n('User unbanned successfully'));
|
|
65
|
+
// 刷新页面
|
|
66
|
+
window.location.reload();
|
|
67
|
+
} else {
|
|
68
|
+
Notification.error(response.message || i18n('Operation failed'));
|
|
69
|
+
}
|
|
70
|
+
} catch (error) {
|
|
71
|
+
console.error('Error:', error);
|
|
72
|
+
Notification.error(i18n('Operation failed'));
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// 快速设置权限
|
|
77
|
+
$('.set-priv').on('click', async function(e) {
|
|
78
|
+
e.preventDefault();
|
|
79
|
+
const uid = $(this).data('uid');
|
|
80
|
+
const username = $(this).data('username');
|
|
81
|
+
const currentPriv = $(this).data('priv');
|
|
82
|
+
|
|
83
|
+
const newPriv = prompt(i18n('Enter new privilege value for user {0}:', username), currentPriv);
|
|
84
|
+
if (newPriv === null || newPriv === '') {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const privValue = parseInt(newPriv, 10);
|
|
89
|
+
if (isNaN(privValue)) {
|
|
90
|
+
Notification.error(i18n('Invalid privilege value'));
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (!confirm(i18n('Are you sure to set privilege of user {0} to {1}?', username, privValue))) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
const response = await request.post(`/manage/users/${uid}`, {
|
|
100
|
+
operation: 'setPriv',
|
|
101
|
+
priv: privValue
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
if (response.success) {
|
|
105
|
+
Notification.success(i18n('Privilege updated successfully'));
|
|
106
|
+
// 刷新页面
|
|
107
|
+
window.location.reload();
|
|
108
|
+
} else {
|
|
109
|
+
Notification.error(response.message || i18n('Operation failed'));
|
|
110
|
+
}
|
|
111
|
+
} catch (error) {
|
|
112
|
+
console.error('Error:', error);
|
|
113
|
+
Notification.error(i18n('Operation failed'));
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// 分页链接处理
|
|
118
|
+
$('.pagination a').on('click', function(e) {
|
|
119
|
+
e.preventDefault();
|
|
120
|
+
const url = $(this).attr('href');
|
|
121
|
+
if (url && url !== '#') {
|
|
122
|
+
pjax.request({ url });
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// 排序链接处理
|
|
127
|
+
$('.sortable').on('click', function(e) {
|
|
128
|
+
e.preventDefault();
|
|
129
|
+
const sort = $(this).data('sort');
|
|
130
|
+
const currentSort = new URLSearchParams(window.location.search).get('sort');
|
|
131
|
+
const currentOrder = new URLSearchParams(window.location.search).get('order') || 'desc';
|
|
132
|
+
|
|
133
|
+
let newOrder = 'desc';
|
|
134
|
+
if (currentSort === sort && currentOrder === 'desc') {
|
|
135
|
+
newOrder = 'asc';
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const url = new URL(window.location);
|
|
139
|
+
url.searchParams.set('sort', sort);
|
|
140
|
+
url.searchParams.set('order', newOrder);
|
|
141
|
+
|
|
142
|
+
pjax.request({ url: url.toString() });
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// 批量操作按钮
|
|
146
|
+
$('#batch-operations').on('click', function() {
|
|
147
|
+
window.location.href = '/manage/users/batch';
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// 导出用户数据
|
|
151
|
+
$('#export-users').on('click', async function() {
|
|
152
|
+
try {
|
|
153
|
+
const response = await request.get('/manage/users', {
|
|
154
|
+
export: 'csv'
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// 创建下载链接
|
|
158
|
+
const blob = new Blob([response], { type: 'text/csv' });
|
|
159
|
+
const url = window.URL.createObjectURL(blob);
|
|
160
|
+
const a = document.createElement('a');
|
|
161
|
+
a.href = url;
|
|
162
|
+
a.download = `users_${new Date().toISOString().split('T')[0]}.csv`;
|
|
163
|
+
document.body.appendChild(a);
|
|
164
|
+
a.click();
|
|
165
|
+
document.body.removeChild(a);
|
|
166
|
+
window.URL.revokeObjectURL(url);
|
|
167
|
+
|
|
168
|
+
Notification.success(i18n('Export completed'));
|
|
169
|
+
} catch (error) {
|
|
170
|
+
console.error('Export error:', error);
|
|
171
|
+
Notification.error(i18n('Export failed'));
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
// 刷新按钮
|
|
176
|
+
$('#refresh-list').on('click', function() {
|
|
177
|
+
window.location.reload();
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
// 键盘快捷键
|
|
181
|
+
$(document).on('keydown', function(e) {
|
|
182
|
+
// Ctrl+F 聚焦搜索框
|
|
183
|
+
if (e.ctrlKey && e.key === 'f') {
|
|
184
|
+
e.preventDefault();
|
|
185
|
+
$('#search-keyword').focus();
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Enter 键提交搜索
|
|
189
|
+
if (e.key === 'Enter' && $('#search-keyword').is(':focus')) {
|
|
190
|
+
$('#search-form').submit();
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
// 初始化提示
|
|
195
|
+
$('[data-tooltip]').each(function() {
|
|
196
|
+
$(this).attr('title', $(this).data('tooltip'));
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
|
|
200
200
|
export default page;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
{% extends "domain_base.html" %}
|
|
2
|
+
{% block domain_content %}
|
|
3
|
+
{{ set(UiContext, 'roles', roles.map(eval('(i) => i._id'))) }}
|
|
4
|
+
{{ set(UiContext, 'canForceJoin', model.system.get('server.allowInvite') or handler.user.hasPriv(PRIV.PRIV_MANAGE_ALL_DOMAIN)) }}
|
|
5
|
+
{% set _rolesSelect = roles.filter(eval("(i) => i._id !== 'guest'")).map(eval('(role) => [role._id, role._id]')) %}
|
|
6
|
+
<div class="section">
|
|
7
|
+
<div class="section__header">
|
|
8
|
+
<h1 class="section__title">{{ _('{0}: Users').format(domain.name) }}</h1>
|
|
9
|
+
<div class="section__tools">
|
|
10
|
+
<button class="primary rounded button" name="add_user">{{ _('Add User') }}</button>
|
|
11
|
+
</div>
|
|
12
|
+
<!-- Search bar for Role -->
|
|
13
|
+
<div class="section__search" style="margin-top: 10px;">
|
|
14
|
+
<input type="text" id="roleSearch" placeholder="{{ _('Search by Role') }}" class="input" style="width: 200px;">
|
|
15
|
+
</div>
|
|
16
|
+
</div>
|
|
17
|
+
{{ noscript_note.render() }}
|
|
18
|
+
<div class="section__body no-padding domain-users">
|
|
19
|
+
<table class="data-table" id="userTable">
|
|
20
|
+
<colgroup>
|
|
21
|
+
<col class="col--checkbox">
|
|
22
|
+
<col class="col--uid">
|
|
23
|
+
<col class="col--user">
|
|
24
|
+
<col class="col--role">
|
|
25
|
+
</colgroup>
|
|
26
|
+
<thead>
|
|
27
|
+
<tr>
|
|
28
|
+
<th class="col--checkbox">
|
|
29
|
+
<label class="compact checkbox">
|
|
30
|
+
<input type="checkbox" name="select_all" data-checkbox-toggle="user">
|
|
31
|
+
</label>
|
|
32
|
+
</th>
|
|
33
|
+
<th class="col--uid">{{ _('User ID') }}</th>
|
|
34
|
+
<th class="col--user">{{ _('Username') }}</th>
|
|
35
|
+
<th class="col--role">{{ _('Role') }}</th>
|
|
36
|
+
</tr>
|
|
37
|
+
</thead>
|
|
38
|
+
<tbody>
|
|
39
|
+
{%- for role, udocs in rudocs -%}
|
|
40
|
+
{%- if udocs|length > 50 -%}
|
|
41
|
+
<tr data-role="{{ role }}">
|
|
42
|
+
<td colspan="3" style="text-wrap: wrap;"><div style="max-height: 300px; overflow-y: auto">
|
|
43
|
+
{%- for udoc in udocs -%}
|
|
44
|
+
{% set is_disabled=(rudoc._id == handler.user._id) %}
|
|
45
|
+
<input type="checkbox" data-uid="{{udoc._id}}" data-checkbox-group="user" {% if is_disabled %}disabled{% else %}data-checkbox-range{% endif %}>
|
|
46
|
+
[{{udoc._id}}]{{ user.render_inline(udoc, avatar=false, badge=false) }}{% if not udoc.join %}
|
|
47
|
+
<span class="not-joined">({{ _('Not joined yet') }})</span>
|
|
48
|
+
{% endif %}
|
|
49
|
+
{%- endfor -%}
|
|
50
|
+
</div></td>
|
|
51
|
+
<td>{{ role }}</td>
|
|
52
|
+
</tr>
|
|
53
|
+
{%- else -%}
|
|
54
|
+
{%- for rudoc in udocs -%}
|
|
55
|
+
{% set is_disabled=(rudoc._id == handler.user._id) %}
|
|
56
|
+
<tr data-role="{{ rudoc.role }}" data-uid="{{ rudoc._id }}">
|
|
57
|
+
<td class="col--checkbox">
|
|
58
|
+
<label class="compact checkbox">
|
|
59
|
+
<input type="checkbox" data-checkbox-group="user" {% if is_disabled %}disabled{% else %}data-checkbox-range{% endif %}>
|
|
60
|
+
</label>
|
|
61
|
+
</td>
|
|
62
|
+
<td class="col--uid">
|
|
63
|
+
{{ rudoc._id }}
|
|
64
|
+
</td>
|
|
65
|
+
<td class="col--user">
|
|
66
|
+
{{ user.render_inline(rudoc, badge=false) }}
|
|
67
|
+
{% if not rudoc.join %}<span class="text-orange">({{ _('Not joined yet') }})</span>{% endif %}
|
|
68
|
+
</td>
|
|
69
|
+
<td class="col--role">
|
|
70
|
+
{{ form.select({
|
|
71
|
+
options:_rolesSelect,
|
|
72
|
+
name:'role',
|
|
73
|
+
value:rudoc.role,
|
|
74
|
+
extra_class:'compact',
|
|
75
|
+
disabled:is_disabled
|
|
76
|
+
}) }}
|
|
77
|
+
</td>
|
|
78
|
+
</tr>
|
|
79
|
+
{%- endfor -%}
|
|
80
|
+
{%- endif -%}
|
|
81
|
+
{%- endfor -%}
|
|
82
|
+
</tbody>
|
|
83
|
+
</table>
|
|
84
|
+
</div>
|
|
85
|
+
<div class="section__body">
|
|
86
|
+
<button class="rounded button" name="remove_selected">{{ _('Remove Selected User') }}</button>
|
|
87
|
+
<button class="rounded button" name="set_roles">{{ _('Set Roles for Selected User') }}</button>
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
<script>
|
|
91
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
92
|
+
var searchInput = document.getElementById('roleSearch');
|
|
93
|
+
searchInput.addEventListener('input', function() {
|
|
94
|
+
var filter = searchInput.value.toLowerCase();
|
|
95
|
+
var rows = document.querySelectorAll('#userTable tbody tr');
|
|
96
|
+
rows.forEach(function(row) {
|
|
97
|
+
var role = row.getAttribute('data-role');
|
|
98
|
+
if (role && role.toLowerCase().indexOf(filter) !== -1) {
|
|
99
|
+
row.style.display = '';
|
|
100
|
+
} else if (filter === '') {
|
|
101
|
+
row.style.display = '';
|
|
102
|
+
} else {
|
|
103
|
+
row.style.display = 'none';
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
</script>
|
|
109
|
+
{% endblock %}
|