Description:
user admin
Commit status:
[Not Reviewed]
References:
Comments:
0 Commit comments 0 Inline Comments
Unresolved TODOs:
There are no unresolved TODOs
Add another comment

r875:567f1ebe063b - - 8 files changed: 80 inserted, 78 deleted

@@ -1,397 +1,398
1 1 require 'csv'
2 2
3 3 class UserAdminController < ApplicationController
4 4
5 5 include MailHelperMethods
6 6
7 7 before_action :admin_authorization
8 8
9 9 def index
10 10 @user_count = User.count
11 11 @users = User.all
12 12 @hidden_columns = ['hashed_password', 'salt', 'created_at', 'updated_at']
13 13 @contests = Contest.enabled
14 + @user = User.new
14 15 end
15 16
16 17 def active
17 18 sessions = ActiveRecord::SessionStore::Session.where("updated_at >= ?", 60.minutes.ago)
18 19 @users = []
19 20 sessions.each do |session|
20 21 if session.data[:user_id]
21 22 @users << User.find(session.data[:user_id])
22 23 end
23 24 end
24 25 end
25 26
26 27 def show
27 28 @user = User.find(params[:id])
28 29 end
29 30
30 31 def new
31 32 @user = User.new
32 33 end
33 34
34 35 def create
35 36 @user = User.new(user_params)
36 37 @user.activated = true
37 38 if @user.save
38 39 flash[:notice] = 'User was successfully created.'
39 40 redirect_to :action => 'index'
40 41 else
41 42 render :action => 'new'
42 43 end
43 44 end
44 45
45 46 def clear_last_ip
46 47 @user = User.find(params[:id])
47 48 @user.last_ip = nil
48 49 @user.save
49 50 redirect_to action: 'index', page: params[:page]
50 51 end
51 52
52 53 def create_from_list
53 54 lines = params[:user_list]
54 55
55 56
56 57 res = User.create_from_list(lines)
57 58 error_logins = res[:error_logins]
58 59 error_msg = res[:first_error]
59 60 ok_user = res[:created_users]
60 61
61 62
62 63 #add to group
63 64 if params[:add_to_group]
64 65 group = Group.find_by(id: params[:group_id])&.add_users_skip_existing(ok_user)
65 66 end
66 67
67 68 # show flash
68 69 if ok_user.count > 0
69 70 flash[:success] = "#{ok_user.count} user(s) was created or updated successfully"
70 71 end
71 72 if error_logins.size > 0
72 73 flash[:error] = "Following user(s) failed to be created: " + error_logins.join(', ') + ". The error of the first failed one are: " + error_msg;
73 74 end
74 75 redirect_to :action => 'index'
75 76 end
76 77
77 78 def edit
78 79 @user = User.find(params[:id])
79 80 end
80 81
81 82 def update
82 83 @user = User.find(params[:id])
83 84 if @user.update_attributes(user_params)
84 85 flash[:notice] = 'User was successfully updated.'
85 86 redirect_to :action => 'show', :id => @user
86 87 else
87 88 render :action => 'edit'
88 89 end
89 90 end
90 91
91 92 def destroy
92 93 User.find(params[:id]).destroy
93 94 redirect_to :action => 'index'
94 95 end
95 96
96 97 def user_stat
97 98 if params[:commit] == 'download csv'
98 99 @problems = Problem.all
99 100 else
100 101 @problems = Problem.available_problems
101 102 end
102 103 @users = User.includes(:contests, :contest_stat).where(enabled: true)
103 104 @scorearray = Array.new
104 105 @users.each do |u|
105 106 ustat = Array.new
106 107 ustat[0] = u
107 108 @problems.each do |p|
108 109 sub = Submission.find_last_by_user_and_problem(u.id,p.id)
109 110 if (sub!=nil) and (sub.points!=nil) and p and p.full_score
110 111 ustat << [(sub.points.to_f*100/p.full_score).round, (sub.points>=p.full_score)]
111 112 else
112 113 ustat << [0,false]
113 114 end
114 115 end
115 116 @scorearray << ustat
116 117 end
117 118 if params[:commit] == 'download csv' then
118 119 csv = gen_csv_from_scorearray(@scorearray,@problems)
119 120 send_data csv, filename: 'last_score.csv'
120 121 else
121 122 render template: 'user_admin/user_stat'
122 123 end
123 124 end
124 125
125 126 def user_stat_max
126 127 if params[:commit] == 'download csv'
127 128 @problems = Problem.all
128 129 else
129 130 @problems = Problem.available_problems
130 131 end
131 132 @users = User.includes(:contests).includes(:contest_stat).all
132 133 @scorearray = Array.new
133 134 #set up range from param
134 135 since_id = params.fetch(:since_id, 0).to_i
135 136 until_id = params.fetch(:until_id, 0).to_i
136 137 @users.each do |u|
137 138 ustat = Array.new
138 139 ustat[0] = u
139 140 @problems.each do |p|
140 141 max_points = 0
141 142 Submission.find_in_range_by_user_and_problem(u.id,p.id,since_id,until_id).each do |sub|
142 143 max_points = sub.points if sub and sub.points and (sub.points > max_points)
143 144 end
144 145 ustat << [(max_points.to_f*100/p.full_score).round, (max_points>=p.full_score)]
145 146 end
146 147 @scorearray << ustat
147 148 end
148 149
149 150 if params[:commit] == 'download csv' then
150 151 csv = gen_csv_from_scorearray(@scorearray,@problems)
151 152 send_data csv, filename: 'max_score.csv'
152 153 else
153 154 render template: 'user_admin/user_stat'
154 155 end
155 156 end
156 157
157 158 def import
158 159 if params[:file]==''
159 160 flash[:notice] = 'Error importing no file'
160 161 redirect_to :action => 'index' and return
161 162 end
162 163 import_from_file(params[:file])
163 164 end
164 165
165 166 def random_all_passwords
166 167 users = User.all
167 168 @prefix = params[:prefix] || ''
168 169 @non_admin_users = User.find_non_admin_with_prefix(@prefix)
169 170 @changed = false
170 171 if params[:commit] == 'Go ahead'
171 172 @non_admin_users.each do |user|
172 173 password = random_password
173 174 user.password = password
174 175 user.password_confirmation = password
175 176 user.save
176 177 end
177 178 @changed = true
178 179 end
179 180 end
180 181
181 182 # contest management
182 183
183 184 def contests
184 185 @contest, @users = find_contest_and_user_from_contest_id(params[:id])
185 186 @contests = Contest.enabled
186 187 end
187 188
188 189 def assign_from_list
189 190 contest_id = params[:users_contest_id]
190 191 org_contest, users = find_contest_and_user_from_contest_id(contest_id)
191 192 contest = Contest.find(params[:new_contest][:id])
192 193 if !contest
193 194 flash[:notice] = 'Error: no contest'
194 195 redirect_to :action => 'contests', :id =>contest_id
195 196 end
196 197
197 198 note = []
198 199 users.each do |u|
199 200 u.contests = [contest]
200 201 note << u.login
201 202 end
202 203 flash[:notice] = 'User(s) ' + note.join(', ') +
203 204 " were successfully reassigned to #{contest.title}."
204 205 redirect_to :action => 'contests', :id =>contest.id
205 206 end
206 207
207 208 def add_to_contest
208 209 user = User.find(params[:id])
209 210 contest = Contest.find(params[:contest_id])
210 211 if user and contest
211 212 user.contests << contest
212 213 end
213 214 redirect_to :action => 'index'
214 215 end
215 216
216 217 def remove_from_contest
217 218 user = User.find(params[:id])
218 219 contest = Contest.find(params[:contest_id])
219 220 if user and contest
220 221 user.contests.delete(contest)
221 222 end
222 223 redirect_to :action => 'index'
223 224 end
224 225
225 226 def contest_management
226 227 end
227 228
228 229 def manage_contest
229 230 contest = Contest.find(params[:contest][:id])
230 231 if !contest
231 232 flash[:notice] = 'You did not choose the contest.'
232 233 redirect_to :action => 'contest_management' and return
233 234 end
234 235
235 236 operation = params[:operation]
236 237
237 238 if not ['add','remove','assign'].include? operation
238 239 flash[:notice] = 'You did not choose the operation to perform.'
239 240 redirect_to :action => 'contest_management' and return
240 241 end
241 242
242 243 lines = params[:login_list]
243 244 if !lines or lines.blank?
244 245 flash[:notice] = 'You entered an empty list.'
245 246 redirect_to :action => 'contest_management' and return
246 247 end
247 248
248 249 note = []
249 250 users = []
250 251 lines.split("\n").each do |line|
251 252 user = User.find_by_login(line.chomp)
252 253 if user
253 254 if operation=='add'
254 255 if ! user.contests.include? contest
255 256 user.contests << contest
256 257 end
257 258 elsif operation=='remove'
258 259 user.contests.delete(contest)
259 260 else
260 261 user.contests = [contest]
261 262 end
262 263
263 264 if params[:reset_timer]
264 265 user.contest_stat.forced_logout = true
265 266 user.contest_stat.reset_timer_and_save
266 267 end
267 268
268 269 if params[:notification_emails]
269 270 send_contest_update_notification_email(user, contest)
270 271 end
271 272
272 273 note << user.login
273 274 users << user
274 275 end
275 276 end
276 277
277 278 if params[:reset_timer]
278 279 logout_users(users)
279 280 end
280 281
281 282 flash[:notice] = 'User(s) ' + note.join(', ') +
282 283 ' were successfully modified. '
283 284 redirect_to :action => 'contest_management'
284 285 end
285 286
286 287 # admin management
287 288
288 289 def admin
289 290 @admins = Role.where(name: 'admin').take.users
290 291 @tas = Role.where(name: 'ta').take.users
291 292 end
292 293
293 294 def modify_role
294 295 user = User.find_by_login(params[:login])
295 296 role = Role.find_by_name(params[:role])
296 297 unless user && role
297 298 flash[:error] = 'Unknown user or role'
298 299 redirect_to admin_user_admin_index_path
299 300 return
300 301 end
301 302 if params[:commit] == 'Grant'
302 303 #grant role
303 304 user.roles << role
304 305 flash[:notice] = "User '#{user.login}' has been granted the role '#{role.name}'"
305 306 else
306 307 #revoke role
307 308 if user.login == 'root' && role.name == 'admin'
308 309 flash[:error] = 'You cannot revoke admisnistrator permission from root.'
309 310 redirect_to admin_user_admin_index_path
310 311 return
311 312 end
312 313 user.roles.delete(role)
313 314 flash[:notice] = "The role '#{role.name}' has been revoked from User '#{user.login}'"
314 315 end
315 316 redirect_to admin_user_admin_index_path
316 317 end
317 318
318 319 # mass mailing
319 320
320 321 def mass_mailing
321 322 end
322 323
323 324 def bulk_mail
324 325 lines = params[:login_list]
325 326 if !lines or lines.blank?
326 327 flash[:notice] = 'You entered an empty list.'
327 328 redirect_to :action => 'mass_mailing' and return
328 329 end
329 330
330 331 mail_subject = params[:subject]
331 332 if !mail_subject or mail_subject.blank?
332 333 flash[:notice] = 'You entered an empty mail subject.'
333 334 redirect_to :action => 'mass_mailing' and return
334 335 end
335 336
336 337 mail_body = params[:email_body]
337 338 if !mail_body or mail_body.blank?
338 339 flash[:notice] = 'You entered an empty mail body.'
339 340 redirect_to :action => 'mass_mailing' and return
340 341 end
341 342
342 343 note = []
343 344 users = []
344 345 lines.split("\n").each do |line|
345 346 user = User.find_by_login(line.chomp)
346 347 if user
347 348 send_mail(user.email, mail_subject, mail_body)
348 349 note << user.login
349 350 end
350 351 end
351 352
352 353 flash[:notice] = 'User(s) ' + note.join(', ') +
353 354 ' were successfully modified. '
354 355 redirect_to :action => 'mass_mailing'
355 356 end
356 357
357 358 #bulk manage
358 359 def bulk_manage
359 360
360 361 begin
361 362 if params[:filter_group]
362 363 @users = Group.find_by(id: params[:filter_group_id]).users
363 364 else
364 365 @users = User.all
365 366 end
366 367 @users = @users.where('(login REGEXP ?) OR (remark REGEXP ?)',params[:regex],params[:regex]) unless params[:regex].blank?
367 368 @users.count if @users #test the sql
368 369 rescue Exception
369 370 flash[:error] = 'Regular Expression is malformed'
370 371 @users = nil
371 372 end
372 373
373 374 if params[:commit]
374 375 @action = {}
375 376 @action[:set_enable] = params[:enabled]
376 377 @action[:enabled] = params[:enable] == "1"
377 378 @action[:gen_password] = params[:gen_password]
378 379 @action[:add_group] = params[:add_group]
379 380 @action[:group_name] = params[:group_name]
380 381 end
381 382
382 383 if params[:commit] == "Perform"
383 384 if @action[:set_enable]
384 385 @users.update_all(enabled: @action[:enabled])
385 386 end
386 387 if @action[:gen_password]
387 388 @users.each do |u|
388 389 password = random_password
389 390 u.password = password
390 391 u.password_confirmation = password
391 392 u.save
392 393 end
393 394 end
394 395 if @action[:add_group] and @action[:group_name]
395 396 @group = Group.find(@action[:group_name])
396 397 ok = []
397 398 failed = []
@@ -1,222 +1,212
1 1 # Methods added to this helper will be available to all templates in the application.
2 2 module ApplicationHelper
3 3
4 4 #new bootstrap header
5 5 def navbar_user_header
6 6 left_menu = ''
7 7 right_menu = ''
8 8 user = User.find(session[:user_id])
9 9
10 10 if (user!=nil) and (GraderConfiguration.show_tasks_to?(user))
11 11 left_menu << add_menu("#{I18n.t 'menu.tasks'}", 'tasks', 'list')
12 12 left_menu << add_menu("#{I18n.t 'menu.submissions'}", 'main', 'submission')
13 13 left_menu << add_menu("#{I18n.t 'menu.test'}", 'test', 'index')
14 14 end
15 15
16 16 if GraderConfiguration['right.user_hall_of_fame']
17 17 left_menu << add_menu("#{I18n.t 'menu.hall_of_fame'}", 'report', 'problem_hof')
18 18 end
19 19
20 20 right_menu << add_menu("#{content_tag(:span,'',class: 'glyphicon glyphicon-question-sign')}".html_safe, 'main', 'help')
21 21 right_menu << add_menu("#{content_tag(:span,'',class: 'glyphicon glyphicon-comment')}".html_safe, 'messages', 'list', {title: I18n.t('menu.messages'), data: {toggle: 'tooltip'}})
22 22 if GraderConfiguration['system.user_setting_enabled']
23 23 right_menu << add_menu("#{content_tag(:span,'',class: 'glyphicon glyphicon-cog')}".html_safe, 'users', 'index', {title: I18n.t('menu.settings'), data: {toggle: 'tooltip'}})
24 24 end
25 25 right_menu << add_menu("#{content_tag(:span,'',class: 'glyphicon glyphicon-log-out')} #{user.full_name}".html_safe, 'main', 'login', {title: I18n.t('menu.log_out'), data: {toggle: 'tooltip'}})
26 26
27 27
28 28 result = content_tag(:ul,left_menu.html_safe,class: 'nav navbar-nav') + content_tag(:ul,right_menu.html_safe,class: 'nav navbar-nav navbar-right')
29 29 end
30 30
31 31 def add_menu(title, controller, action, html_option = {})
32 32 link_option = {controller: controller, action: action}
33 33 html_option[:class] = (html_option[:class] || '') + " active" if current_page?(link_option)
34 34 content_tag(:li, link_to(title,link_option),html_option)
35 35 end
36 36
37 37 def user_header
38 38 menu_items = ''
39 39 user = User.find(session[:user_id])
40 40
41 41 if (user!=nil) and (session[:admin])
42 42 # admin menu
43 43 menu_items << "<b>Administrative task:</b> "
44 44 append_to menu_items, '[Announcements]', 'announcements', 'index'
45 45 append_to menu_items, '[Msg console]', 'messages', 'console'
46 46 append_to menu_items, '[Problems]', 'problems', 'index'
47 47 append_to menu_items, '[Users]', 'user_admin', 'index'
48 48 append_to menu_items, '[Results]', 'user_admin', 'user_stat'
49 49 append_to menu_items, '[Report]', 'report', 'multiple_login'
50 50 append_to menu_items, '[Graders]', 'graders', 'list'
51 51 append_to menu_items, '[Contests]', 'contest_management', 'index'
52 52 append_to menu_items, '[Sites]', 'sites', 'index'
53 53 append_to menu_items, '[System config]', 'configurations', 'index'
54 54 menu_items << "<br/>"
55 55 end
56 56
57 57 # main page
58 58 append_to menu_items, "[#{I18n.t 'menu.main'}]", 'main', 'list'
59 59 append_to menu_items, "[#{I18n.t 'menu.messages'}]", 'messages', 'list'
60 60
61 61 if (user!=nil) and (GraderConfiguration.show_tasks_to?(user))
62 62 append_to menu_items, "[#{I18n.t 'menu.tasks'}]", 'tasks', 'list'
63 63 append_to menu_items, "[#{I18n.t 'menu.submissions'}]", 'main', 'submission'
64 64 append_to menu_items, "[#{I18n.t 'menu.test'}]", 'test', 'index'
65 65 end
66 66
67 67 if GraderConfiguration['right.user_hall_of_fame']
68 68 append_to menu_items, "[#{I18n.t 'menu.hall_of_fame'}]", 'report', 'problem_hof'
69 69 end
70 70 append_to menu_items, "[#{I18n.t 'menu.help'}]", 'main', 'help'
71 71
72 72 if GraderConfiguration['system.user_setting_enabled']
73 73 append_to menu_items, "[#{I18n.t 'menu.settings'}]", 'users', 'index'
74 74 end
75 75 append_to menu_items, "[#{I18n.t 'menu.log_out'}]", 'main', 'login'
76 76
77 77 menu_items.html_safe
78 78 end
79 79
80 80 def append_to(option,label, controller, action)
81 81 option << ' ' if option!=''
82 82 option << link_to_unless_current(label,
83 83 :controller => controller,
84 84 :action => action)
85 85 end
86 86
87 87 def format_short_time(time)
88 88 now = Time.zone.now
89 89 st = ''
90 90 if (time.yday != now.yday) or (time.year != now.year)
91 91 st = time.strftime("%d/%m/%y ")
92 92 end
93 93 st + time.strftime("%X")
94 94 end
95 95
96 96 def format_short_duration(duration)
97 97 return '' if duration==nil
98 98 d = duration.to_f
99 99 return Time.at(d).gmtime.strftime("%X")
100 100 end
101 101
102 102 def format_full_time_ago(time)
103 103 st = time_ago_in_words(time) + ' ago (' + format_short_time(time) + ')'
104 104 end
105 105
106 106 def read_textfile(fname,max_size=2048)
107 107 begin
108 108 File.open(fname).read(max_size)
109 109 rescue
110 110 nil
111 111 end
112 112 end
113 113
114 114 def toggle_button(on,toggle_url,id, option={})
115 115 btn_size = option[:size] || 'btn-sm'
116 116 btn_block = option[:block] || 'btn-block'
117 117 link_to (on ? "Yes" : "No"), toggle_url,
118 118 {class: "btn #{btn_block} #{btn_size} btn-#{on ? 'success' : 'outline-secondary'} ajax-toggle",
119 119 id: id,
120 120 data: {remote: true, method: 'get'}}
121 121 end
122 122
123 123 def get_ace_mode(language)
124 124 # return ace mode string from Language
125 125
126 126 case language.pretty_name
127 127 when 'Pascal'
128 128 'ace/mode/pascal'
129 129 when 'C++','C'
130 130 'ace/mode/c_cpp'
131 131 when 'Ruby'
132 132 'ace/mode/ruby'
133 133 when 'Python'
134 134 'ace/mode/python'
135 135 when 'Java'
136 136 'ace/mode/java'
137 137 else
138 138 'ace/mode/c_cpp'
139 139 end
140 140 end
141 141
142 142
143 143 def user_title_bar(user)
144 144 header = ''
145 145 time_left = ''
146 146
147 147 #
148 148 # if the contest is over
149 149 if GraderConfiguration.time_limit_mode?
150 150 if user.contest_finished?
151 151 header = <<CONTEST_OVER
152 152 <tr><td colspan="2" align="center">
153 153 <span class="contest-over-msg">THE CONTEST IS OVER</span>
154 154 </td></tr>
155 155 CONTEST_OVER
156 156 end
157 157 if !user.contest_started?
158 158 time_left = "&nbsp;&nbsp;" + (t 'title_bar.contest_not_started')
159 159 else
160 160 time_left = "&nbsp;&nbsp;" + (t 'title_bar.remaining_time') +
161 161 " #{format_short_duration(user.contest_time_left)}"
162 162 end
163 163 end
164 164
165 165 #
166 166 # if the contest is in the anaysis mode
167 167 if GraderConfiguration.analysis_mode?
168 168 header = <<ANALYSISMODE
169 169 <tr><td colspan="2" align="center">
170 170 <span class="contest-over-msg">ANALYSIS MODE</span>
171 171 </td></tr>
172 172 ANALYSISMODE
173 173 end
174 174
175 175 contest_name = GraderConfiguration['contest.name']
176 176
177 177 #
178 178 # build real title bar
179 179 result = <<TITLEBAR
180 180 <div class="title">
181 181 <table>
182 182 #{header}
183 183 <tr>
184 184 <td class="left-col">
185 185 <br/>
186 186 </td>
187 187 <td class="right-col">#{contest_name}</td>
188 188 </tr>
189 189 </table>
190 190 </div>
191 191 TITLEBAR
192 192 result.html_safe
193 193 end
194 194
195 195 def markdown(text)
196 196 markdown = RDiscount.new(text)
197 197 markdown.to_html.html_safe
198 198 end
199 199
200 200
201 201 BOOTSTRAP_FLASH_MSG = {
202 202 success: 'alert-success',
203 203 error: 'alert-danger',
204 204 alert: 'alert-danger',
205 205 notice: 'alert-info'
206 206 }
207 207
208 208 def bootstrap_class_for(flash_type)
209 209 BOOTSTRAP_FLASH_MSG.fetch(flash_type.to_sym, flash_type.to_s)
210 210 end
211 211
212 - def flash_messages
213 - flash.each do |msg_type, message|
214 - concat(content_tag(:div, message, class: "alert #{bootstrap_class_for(msg_type)} fade in") do
215 - concat content_tag(:button, 'x', class: "close", data: { dismiss: 'alert' })
216 - concat message
217 - end)
218 - end
219 - nil
220 - end
221 -
222 212 end
@@ -1,53 +1,56
1 1 // Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails
2 2 //import "@hotwired/turbo-rails"
3 3 //import "controllers"
4 4 //
5 5
6 6
7 7 //bootstrap
8 8 //import "bootstrap"
9 9 //window.bootstrap = bootstrap
10 10
11 11 //datatable
12 12 //import 'datatables-bundle'
13 13 import "pdfmake"
14 14 import "pdfmake-vfs"
15 15 import "jszip"
16 16 import "datatables"
17 17 import "datatables-bs5"
18 18 import "datatables-editor"
19 19 import "datatables-editor-bs5"
20 20 import "datatables-autofill"
21 21 import "datatables-autofill-bs5"
22 22 import "datatables-button"
23 23 import "datatables-button-bs5"
24 24 import "datatables-button-colvis"
25 25 import "datatables-button-html5"
26 26 import "datatables-button-print"
27 27 import "datatables-colrecorder"
28 28 import "datatables-datetime"
29 29 import "datatables-fixedcolumns"
30 30 import "datatables-fixedheader"
31 31 import "datatables-keytable"
32 32 import "datatables-responsive"
33 33 import "datatables-responsive-bs5"
34 34 import "datatables-rowgroup"
35 35 import "datatables-rowreorder"
36 36 import "datatables-scroller"
37 37 import "datatables-searchbuilder"
38 38 import "datatables-searchbuilder-bs5"
39 39 import "datatables-searchpanes"
40 40 import "datatables-searchpanes-bs5"
41 41 import "datatables-select"
42 42 import "datatables-staterestore"
43 43 import "datatables-staterestore-bs5"
44 44 /* */
45 45
46 46 import "select2"
47 47
48 48 //my own customization
49 49 import 'custom'
50 50
51 51
52 + //trigger import map ready
52 53 console.log('application.js ready')
53 -
54 + window.importmapScriptsLoaded = true
55 + const import_map_loaded = new CustomEvent('import-map-loaded', { });
56 + document.dispatchEvent(import_map_loaded);
@@ -1,34 +1,37
1 1 <!DOCTYPE html>
2 2 %html
3 3 %head
4 4 %title= GraderConfiguration['contest.name']
5 5 = stylesheet_link_tag "application", params[:controller], :media => "all"
6 6 = csrf_meta_tags
7 7 = csp_meta_tag
8 8 = javascript_include_tag :my_app
9 9 -# = javascript_import_module_tag('prepend_jquery')
10 10 = javascript_importmap_tags
11 11 = content_for :header
12 12 = yield :head
13 13 -# %link{href:"https://cdn.jsdelivr.net/npm/bootstrap@5.2.1/dist/css/bootstrap.min.css",rel:"stylesheet",integrity:"sha384-iYQeCzEYFbKjA/T2uDLTpkwGzCiq6soy8tYaI1GyVh/UjpbCx/TYkiZhlZB6+fzT",crossorigin:"anonymous"}
14 14 -# %script{src:"https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.6/dist/umd/popper.min.js",integrity:"sha384-oBqDVmMz9ATKxIep9tiCxS/Z9fNfEXiDAYTujMAeBAsjFuCZSmKbSSUnQlmh/jp3",crossorigin:"anonymous"}
15 15 -# %script{src:"https://cdn.jsdelivr.net/npm/bootstrap@5.2.1/dist/js/bootstrap.bundle.min.js",integrity:"sha384-7VPbUDkoPSGFnVtYi0QogXtr74QeVeeIs99Qfg5YCF+TidwNdjvaKZX19NZ/e6oz",crossorigin:"anonymous"}
16 16
17 17 <link rel="preconnect" href="https://fonts.googleapis.com">
18 18 <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
19 19 -#
20 20 <link href="https://fonts.googleapis.com/css2?family=Bai+Jamjuree:ital,wght@0,200;0,400;0,600;1,200;1,400;1,600&family=Krub:ital,wght@0,200;0,400;0,600;1,200;1,400;1,600&family=Sarabun:ital,wght@0,200;0,400;0,600;1,200;1,400;1,600&display=swap" rel="stylesheet">
21 21 <link href="https://fonts.googleapis.com/css2?family=Mitr:ital,wght@0,300;1,300&family=Kodchasan:ital,wght@0,200;0,400;0,600;1,200;1,400;1,600&family=Noto+Serif+Thai:ital,wght@0,200;0,400;0,600;1,200;1,400;1,600&family=Noto+Sans+Thai:ital,wght@0,200;0,400;0,600;1,200;1,400;1,600&display=swap" rel="stylesheet">
22 22 <link href="https://fonts.googleapis.com/css2?family=Noto+Serif+Thai:ital,wght@0,200;0,400;0,600;1,200;1,400;1,600&family=Noto+Sans+Thai:ital,wght@0,200;0,400;0,600;1,200;1,400;1,600&display=swap" rel="stylesheet">
23 23 <link href="https://fonts.googleapis.com/css2?family=Noto+Serif+Thai:ital,wght@0,300;0,400;0,500;1,300;1,400;1,500&display=swap" rel="stylesheet">
24 24 <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+Thai:ital,wght@0,300;0,400;0,500;1,300;1,400;1,500&display=swap" rel="stylesheet">
25 25 <link href="https://fonts.googleapis.com/css2?family=Sarabun:ital,wght@0,300;0,400;0,500;1,300;1,400;1,500&display=swap" rel="stylesheet">
26 26
27 27 %body
28 28 - unless local_assigns[:skip_header]
29 29 = render 'layouts/header'
30 30
31 31 /= content_tag(:p,flash[:notice],class: 'alert alert-success') if flash[:notice]!=nil
32 32 .container-fluid
33 - = flash_messages
33 + - flash.each do |msg_type, message|
34 + .alert.alert-dismissible.fade.show{class: bootstrap_class_for(msg_type)}
35 + = message
36 + %button.btn-close{type: 'button', 'data-bs-dismiss': :alert}
34 37 = yield
@@ -1,12 +1,20
1 1 = simple_form_for(@user) do |f|
2 2 = f.error_notification
3 - = f.input :login, label: 'Login'
4 - = f.input :full_name, label: 'Full name'
5 - = f.input :password
6 - = f.input :password_confirmation
7 - = f.input :email
8 - = f.input :alias
9 - = f.input :remark
10 - = f.button :submit, class: 'btn btn-primary'
11 - = link_to 'Cancel', :back, class: 'btn btn-default'
3 + .mb-2
4 + = f.input :login, label: 'Login'
5 + .mb-2
6 + = f.input :full_name, label: 'Full name'
7 + .mb-2
8 + = f.input :password
9 + .mb-2
10 + = f.input :password_confirmation
11 + .mb-2
12 + = f.input :email
13 + .mb-2
14 + = f.input :alias
15 + .mb-2
16 + = f.input :remark
17 + .mb-2
18 + = f.button :submit, class: 'btn btn-primary'
19 + = link_to 'Cancel', :back, class: 'btn btn-secondary'
12 20
@@ -1,106 +1,101
1 1 %h1 Users
2 2
3 - .card.border-primary
4 - .card-header.text-bg-primary.border-primary
3 + .card.border-success.mb-3
4 + .card-header.text-bg-success.border-success
5 5 Quick Add
6 6 .card-body
7 - = form_with url: 'asd', class: 'row row-cols-lg-auto g-3 align-items-center' do |f|
7 + = form_with url: user_admin_index_path, scope: :user, class: 'row row-cols-lg-auto g-3 align-items-center' do |f|
8 8 .col-12
9 - = f.label 'user_login', 'Login'
10 - = f.text_field 'login', :size => 10,class: 'form-control'
9 + = f.text_field 'login', :size => 10,class: 'form-control', placeholder: 'login'
11 10 .form-group
12 - = f.label 'user_full_name', 'Full Name'
13 - = f.text_field 'full_name', :size => 10,class: 'form-control'
11 + = f.text_field 'full_name', :size => 10,class: 'form-control', placeholder: 'full name'
14 12 .form-group
15 - = f.label 'user_password', 'Password'
16 - = f.text_field 'password', :size => 10,class: 'form-control'
13 + = f.password_field 'password', :size => 10,class: 'form-control', placeholder: 'password'
17 14 .form-group
18 - = f.label 'user_password_confirmation', 'Confirm'
19 - = f.text_field 'password_confirmation', :size => 10,class: 'form-control'
15 + = f.password_field 'password_confirmation', :size => 10,class: 'form-control', placeholder: 'password confirmation'
20 16 .form-group
21 - = f.label 'user_email', 'email'
22 - = f.text_field 'email', :size => 10,class: 'form-control'
23 - =submit_tag "Create", class: 'btn btn-primary align-items-bottom'
17 + = f.text_field 'email', :size => 10,class: 'form-control', placeholder: 'email'
18 + =submit_tag "Create", class: 'btn btn-success align-items-bottom'
24 19
25 - .panel.panel-primary
26 - .panel-title.panel-heading
20 + .card.border-success.mb-3
21 + .card-header.text-bg-success.border-success
27 22 Import from site management
28 - .panel-body
29 - = form_tag({:action => 'import'}, :multipart => true,class: 'form form-inline') do
30 - .form-group
31 - = label_tag :file, 'File:'
32 - .input-group
33 - %span.input-group-btn
34 - %span.btn.btn-default.btn-file
35 - Browse
36 - = file_field_tag 'file'
37 - = text_field_tag '' , nil, {readonly: true, class: 'form-control'}
38 - = submit_tag 'Submit', class: 'btn btn-default'
23 + .card-body
24 + = form_with url: import_user_admin_index_path, :multipart => true do |f|
25 + .row
26 + .col-auto
27 + = f.label :file, 'File:', class: 'col-form-label'
28 + .col-auto
29 + = f.file_field :file, class: 'form-control'
30 + .col-auto
31 + = f.submit 'Submit', class: 'btn btn-secondary'
39 32
40 33
41 34 %p
42 35 = link_to '+ New user', { :action => 'new' }, { class: 'btn btn-success '}
43 36 = link_to '+ New list of users', { :action => 'new_list' }, { class: 'btn btn-success '}
44 - = link_to 'Bulk Manage', { action: :bulk_manage} , { class: 'btn btn-default btn-info'}
45 - = link_to 'View administrators',{ :action => 'admin'}, { class: 'btn btn-default '}
46 - = link_to 'Random passwords',{ :action => 'random_all_passwords'}, { class: 'btn btn-default '}
47 - = link_to 'View active users',{ :action => 'active'}, { class: 'btn btn-default '}
48 - = link_to 'Mass mailing',{ :action => 'mass_mailing'}, { class: 'btn btn-default '}
37 + = link_to 'Bulk Manage', { action: :bulk_manage} , { class: 'btn btn-secondary btn-info'}
38 + = link_to 'View administrators',{ :action => 'admin'}, { class: 'btn btn-secondary '}
39 + = link_to 'Random passwords',{ :action => 'random_all_passwords'}, { class: 'btn btn-secondary '}
40 + = link_to 'View active users',{ :action => 'active'}, { class: 'btn btn-secondary '}
41 + = link_to 'Mass mailing',{ :action => 'mass_mailing'}, { class: 'btn btn-secondary '}
49 42
50 43 - if GraderConfiguration.multicontests?
51 44 %br/
52 45 %b Multi-contest:
53 46 = link_to '[Manage bulk users in contests]', :action => 'contest_management'
54 47 View users in:
55 48 - @contests.each do |contest|
56 49 = link_to "[#{contest.name}]", :action => 'contests', :id => contest.id
57 50 = link_to "[no contest]", :action => 'contests', :id => 'none'
58 51
59 52 -# Total #{@user_count} users |
60 53 -# - if !@paginated
61 54 -# Display all users.
62 55 -# \#{link_to '[show in pages]', :action => 'index', :page => '1'}
63 56 -# - else
64 57 -# Display in pages.
65 58 -# \#{link_to '[display all]', :action => 'index', :page => 'all'} |
66 59 -# \#{will_paginate @users, :container => false}
67 60
68 61
69 62 %table.table.table-hover.table-condense.datatable
70 63 %thead
71 64 %th Login
72 65 %th Full name
73 66 %th email
74 67 %th Remark
75 68 %th
76 69 Activated
77 70 %sup{class: 'text-primary',data: {toggle: 'tooltip', placement: 'top'}, title: 'User has already confirmed the email?' } [?]
78 71 %th
79 72 Enabled
80 73 %sup{class: 'text-primary',data: {toggle: 'tooltip', placement: 'top'}, title: 'Allow the user to login?' } [?]
81 74 %th Last IP
82 75 %th
83 76 %th
84 77 %th
85 78 %th
86 79 - for user in @users
87 80 %tr
88 81 %td= link_to user.login, stat_user_path(user)
89 82 %td= user.full_name
90 83 %td= user.email
91 84 %td= user.remark
92 85 %td= toggle_button(user.activated?, toggle_activate_user_path(user),"toggle_activate_user_#{user.id}")
93 86 %td= toggle_button(user.enabled?, toggle_enable_user_path(user),"toggle_enable_user_#{user.id}")
94 87 %td= user.last_ip
95 88 %td= link_to 'Clear IP', {:action => 'clear_last_ip', :id => user, :page=>params[:page]}, :confirm => 'This will reset last logging in ip of the user, are you sure?', class: 'btn btn-default btn-xs btn-block'
96 89 %td= link_to 'Show', {:action => 'show', :id => user}, class: 'btn btn-default btn-xs btn-block'
97 90 %td= link_to 'Edit', {:action => 'edit', :id => user}, class: 'btn btn-default btn-xs btn-block'
98 91 %td= link_to 'Destroy', {action: :destroy, id: user}, data: {confirm: 'Are you sure?'}, method: :delete, class: 'btn btn-danger btn-xs btn-block'
99 92 %br/
100 93 = link_to '+ New user', { :action => 'new' }, { class: 'btn btn-success '}
101 94 = link_to '+ New list of users', { :action => 'new_list' }, { class: 'btn btn-success '}
102 95
103 96 :javascript
104 - $('.datatable').DataTable({
105 - 'pageLength': 50
106 - });
97 + $(document).on('import-map-loaded',(e) => {
98 + $('.datatable').DataTable({
99 + 'pageLength': 50
100 + });
101 + })
@@ -1,54 +1,56
1 1 .container-fluid
2 2 .row
3 3 .col-md-6
4 4 %h1 Adding list of users
5 - .row
5 + .row.my-3
6 6 .col-md-6
7 - .panel.panel-default
8 - .panel-heading
9 - .panel-title Info
10 - .panel-body
7 + = form_with url: create_from_list_user_admin_index_path do |f|
8 + .row.align-items-center.mb-3
9 + .col-auto
10 + = f.submit 'Create following users',class: 'btn btn-success'
11 + .col-auto
12 + .form-check
13 + = f.check_box :add_to_group, class: 'form-check-input'
14 + = f.label :add_to_group, 'Also add these users to the following group', class: 'form-check-label'
15 + .col-4
16 + = f.select "group_id", options_from_collection_for_select( Group.all, 'id','name',params[:group_name]), {}, class: 'select2 form-control'
17 + .row.mb-3
18 + .col-12
19 + = f.text_area :user_list, value: nil, class: 'form-control', style: 'height: 30rem'
20 + .col-md-6
21 + .card.card-default
22 + .card-header
23 + .card-title Info
24 + .card-body
11 25 %ul
12 26 %li
13 27 List of user information in this format:
14 28 %tt user_id,name(,passwd(,alias(,remark)))
15 29 %li
16 30 Note that
17 31 %tt passwd, alias
18 32 and
19 33 %tt remark
20 34 is optional.
21 35 %li
22 36 When
23 37 %tt passwd
24 38 or
25 39 %tt alias
26 40 is empty, the original value will be used instead.
27 41 %li
28 42 If the users with the same user_id already exists, existing information will be overwritten.
29 43 Example:
30 44 %ol
31 45 %li
32 46 %pre user1,Somchai Jaidee
33 47 will create (or update) a user with login "user1" and setting the fullname to "Somchai Jaidee", also setting a random password.
34 48 %li
35 49 %pre user1,Somchai Jaidee,
36 50 will create (or update) a user with login "user1" and and setting the fullname "Somchai Jaidee". No change is made to the password unless this is a new user. If this is a new user, a random password will be generated.
37 51
52 + :javascript
53 + $(document).on('import-map-loaded',(e) => {
54 + $('.select2').select2()
55 + });
38 56
39 - .row
40 - .col-md-6
41 - = form_tag :action => 'create_from_list' do
42 - .form-group
43 - = submit_tag 'Create following users',class: 'btn btn-success'
44 - .form-group
45 - .div.checkbox
46 - %label
47 - = check_box_tag :add_to_group
48 - Also add these users to the following group
49 - = select_tag "group_id", options_from_collection_for_select( Group.all, 'id','name',params[:group_name]), id: 'group_name',class: 'select2'
50 - .form-group
51 - = text_area_tag 'user_list', nil, :rows => 50, :cols => 80
52 - .col-md-6
53 -
54 -
@@ -1,38 +1,38
1 1 # Be sure to restart your server when you modify this file.
2 2 #
3 3 # This file contains migration options to ease your Rails 5.2 upgrade.
4 4 #
5 5 # Once upgraded flip defaults one by one to migrate to the new default.
6 6 #
7 7 # Read the Guide for Upgrading Ruby on Rails for more info on each option.
8 8
9 9 # Make Active Record use stable #cache_key alongside new #cache_version method.
10 10 # This is needed for recyclable cache keys.
11 11 # Rails.application.config.active_record.cache_versioning = true
12 12
13 13 # Use AES-256-GCM authenticated encryption for encrypted cookies.
14 14 # Also, embed cookie expiry in signed or encrypted cookies for increased security.
15 15 #
16 16 # This option is not backwards compatible with earlier Rails versions.
17 17 # It's best enabled when your entire app is migrated and stable on 5.2.
18 18 #
19 19 # Existing cookies will be converted on read then written with the new scheme.
20 20 # Rails.application.config.action_dispatch.use_authenticated_cookie_encryption = true
21 21
22 22 # Use AES-256-GCM authenticated encryption as default cipher for encrypting messages
23 23 # instead of AES-256-CBC, when use_authenticated_message_encryption is set to true.
24 24 # Rails.application.config.active_support.use_authenticated_message_encryption = true
25 25
26 26 # Add default protection from forgery to ActionController::Base instead of in
27 27 # ApplicationController.
28 28 # Rails.application.config.action_controller.default_protect_from_forgery = true
29 29
30 30 # Store boolean values are in sqlite3 databases as 1 and 0 instead of 't' and
31 31 # 'f' after migrating old data.
32 32 # Rails.application.config.active_record.sqlite3.represent_boolean_as_integer = true
33 33
34 34 # Use SHA-1 instead of MD5 to generate non-sensitive digests, such as the ETag header.
35 35 # Rails.application.config.active_support.use_sha1_digests = true
36 36
37 37 # Make `form_with` generate id attributes for any generated HTML tags.
38 - # Rails.application.config.action_view.form_with_generates_ids = true
38 + Rails.application.config.action_view.form_with_generates_ids = true
You need to be logged in to leave comments. Login now