Description:
- login report - fix bugs on pagination on user manage
Commit status:
[Not Reviewed]
References:
Comments:
0 Commit comments 0 Inline Comments
Unresolved TODOs:
There are no unresolved TODOs
Add another comment

r792:6ae18fbc4b91 - - 14 files changed: 255 inserted, 76 deleted

@@ -0,0 +1,130
1 + - content_for :header do
2 + = javascript_include_tag 'local_jquery'
3 +
4 + %h1 Logins detail
5 +
6 + .row
7 + .col-md-4
8 + .alert.alert-info
9 + %ul
10 + %li You have to click refresh when changing the filter above
11 + %li Detail tab shows each logins separately
12 + %li Summary tab shows logins summary of each user
13 + .col-md-4
14 + = render partial: 'shared/date_filter'
15 + .col-md-4
16 + = render partial: 'shared/user_select'
17 +
18 + .row.form-group
19 + .col-sm-12
20 + %ul.nav.nav-tabs
21 + %li.active
22 + %a{href: '#detail', data: {toggle: :tab}} Detail
23 + %li
24 + %a{href: '#summary', data: {toggle: :tab}} Summary
25 + .row
26 + .col-sm-12
27 + .tab-content
28 + .tab-pane.active#detail
29 + %table#detail-table.table.table-hover.table-condense.datatable{style: 'width: 100%'}
30 + .tab-pane#summary
31 + %table#summary-table.table.table-hover.table-condense.datatable{style: 'width: 100%'}
32 +
33 +
34 +
35 + :javascript
36 + $(function() {
37 + detail_table = $('#detail-table').DataTable({
38 + dom: "<'row'<'col-sm-3'B><'col-sm-3'l><'col-sm-6'f>>" + "<'row'<'col-sm-12'tr>>" + "<'row'<'col-sm-5'i><'col-sm-7'p>>",
39 + autoWidth: true,
40 + buttons: [
41 + {
42 + text: 'Refresh',
43 + action: (e,dt,node,config) => {
44 + detail_table.clear().draw()
45 + detail_table.ajax.reload( () => { detail_table.columns.adjust().draw() } )
46 + summary_table.clear().draw()
47 + summary_table.ajax.reload( () => { summary_table.columns.adjust().draw() } )
48 + }
49 + },
50 + 'copy',
51 + {
52 + extend: 'excel',
53 + title: 'Login detail',
54 + }
55 + ],
56 + columns: [
57 + {title: 'User', data: 'login_text'},
58 + {title: 'Time', data: 'created_at'},
59 + {title: 'IP', data: 'ip_address'},
60 + ],
61 + ajax: {
62 + url: '#{login_detail_query_report_path}',
63 + type: 'POST',
64 + data: (d) => {
65 + d.since_datetime = $('#since_datetime').val()
66 + d.until_datetime = $('#until_datetime').val()
67 + d.users = $("input[name='users']:checked").val()
68 + d.groups = $("#group_id").select2('val')
69 + },
70 + dataType: 'json',
71 + beforeSend: (request) => {
72 + request.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').attr('content'));
73 + },
74 + }, //end ajax
75 + pageLength: 25,
76 + processing: true,
77 + });
78 +
79 + summary_table = $('#summary-table').DataTable({
80 + dom: "<'row'<'col-sm-3'B><'col-sm-3'l><'col-sm-6'f>>" + "<'row'<'col-sm-12'tr>>" + "<'row'<'col-sm-5'i><'col-sm-7'p>>",
81 + autoWidth: true,
82 + buttons: [
83 + {
84 + text: 'Refresh',
85 + action: (e,dt,node,config) => {
86 + summary_table.clear().draw()
87 + summary_table.ajax.reload( () => { summary_table.columns.adjust().draw() } )
88 + detail_table.clear().draw()
89 + detail_table.ajax.reload( () => { detail_table.columns.adjust().draw() } )
90 + }
91 + },
92 + 'copy',
93 + {
94 + extend: 'excel',
95 + title: 'Login summary',
96 + }
97 + ],
98 + columns: [
99 + {title: 'User', data: 'login_text'},
100 + {title: 'Login Count', data: 'count'},
101 + {title: 'Earliest', data: 'earliest'},
102 + {title: 'Latest', data: 'latest'},
103 + {title: 'IP', data: 'ip_address'},
104 + ],
105 + ajax: {
106 + url: '#{login_summary_query_report_path}',
107 + type: 'POST',
108 + data: (d) => {
109 + d.since_datetime = $('#since_datetime').val()
110 + d.until_datetime = $('#until_datetime').val()
111 + d.users = $("input[name='users']:checked").val()
112 + d.groups = $("#group_id").select2('val')
113 + },
114 + dataType: 'json',
115 + beforeSend: (request) => {
116 + request.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').attr('content'));
117 + },
118 + }, //end ajax
119 + pageLength: 25,
120 + processing: true,
121 + });
122 +
123 + $('.input-group.date').datetimepicker({
124 + format: 'YYYY-MM-DD HH:mm',
125 + showTodayButton: true,
126 + locale: 'en',
127 + widgetPositioning: {horizontal: 'auto', vertical: 'bottom'},
128 + defaultDate: moment()
129 + });
130 + });
@@ -0,0 +1,10
1 + json.draw params['draw']&.to_i
2 + json.recordsTotal @recordsTotal
3 + json.recordsFiltered @recordsFiltered
4 + json.data do
5 + json.array! @logins do |login|
6 + json.login_text login.user ? "<a href='#{stat_user_path(login.user_id)}'>(#{login.user.login})</a> #{login.user.full_name}" : '-- deletec user --'
7 + json.created_at login.created_at.strftime('%Y-%m-%d %H:%M')
8 + json.ip_address login.ip_address
9 + end
10 + end
@@ -0,0 +1,12
1 + json.draw params['draw']&.to_i
2 + json.recordsTotal @recordsTotal
3 + json.recordsFiltered @recordsFiltered
4 + json.data do
5 + json.array! @users do |user|
6 + json.login_text "<a href='#{stat_user_path(user[:id])}'>(#{user[:login]})</a> #{user[:full_name]}"
7 + json.count user[:count]
8 + json.earliest user[:min].strftime('%Y-%m-%d %H:%M')
9 + json.latest user[:max].strftime('%Y-%m-%d %H:%M')
10 + json.ip_address user[:ip].join('<br/>')
11 + end
12 + end
@@ -0,0 +1,5
1 + class AddIndexToLogin < ActiveRecord::Migration[5.2]
2 + def change
3 + add_index :logins, :user_id
4 + end
5 + end
@@ -1,55 +1,57
1 require 'csv'
1 require 'csv'
2
2
3 class ReportController < ApplicationController
3 class ReportController < ApplicationController
4
4
5 before_action :check_valid_login
5 before_action :check_valid_login
6
6
7 - before_action :admin_authorization, only: [:login_stat,:submission, :submission_query, :stuck, :cheat_report, :cheat_scruntinize, :show_max_score, :current_score]
7 + before_action :admin_authorization, only: [:login_stat,:submission, :submission_query,
8 + :login, :login_detail_query, :login_summary_query,
9 + :stuck, :cheat_report, :cheat_scruntinize, :show_max_score, :current_score]
8
10
9 before_action(only: [:problem_hof]) { |c|
11 before_action(only: [:problem_hof]) { |c|
10 return false unless check_valid_login
12 return false unless check_valid_login
11
13
12 admin_authorization unless GraderConfiguration["right.user_view_submission"]
14 admin_authorization unless GraderConfiguration["right.user_view_submission"]
13 }
15 }
14
16
15 def max_score
17 def max_score
16 end
18 end
17
19
18 def current_score
20 def current_score
19 @problems = Problem.available_problems
21 @problems = Problem.available_problems
20 if params[:group_id]
22 if params[:group_id]
21 @group = Group.find(params[:group_id])
23 @group = Group.find(params[:group_id])
22 @users = @group.users.where(enabled: true)
24 @users = @group.users.where(enabled: true)
23 else
25 else
24 @users = User.includes(:contests).includes(:contest_stat).where(enabled: true)
26 @users = User.includes(:contests).includes(:contest_stat).where(enabled: true)
25 end
27 end
26 @scorearray = calculate_max_score(@problems, @users,0,0,true)
28 @scorearray = calculate_max_score(@problems, @users,0,0,true)
27
29
28 #rencer accordingly
30 #rencer accordingly
29 if params[:button] == 'download' then
31 if params[:button] == 'download' then
30 csv = gen_csv_from_scorearray(@scorearray,@problems)
32 csv = gen_csv_from_scorearray(@scorearray,@problems)
31 send_data csv, filename: 'max_score.csv'
33 send_data csv, filename: 'max_score.csv'
32 else
34 else
33 #render template: 'user_admin/user_stat'
35 #render template: 'user_admin/user_stat'
34 render 'current_score'
36 render 'current_score'
35 end
37 end
36 end
38 end
37
39
38 def show_max_score
40 def show_max_score
39 #process parameters
41 #process parameters
40 #problems
42 #problems
41 @problems = []
43 @problems = []
42 if params[:problem_id]
44 if params[:problem_id]
43 params[:problem_id].each do |id|
45 params[:problem_id].each do |id|
44 next unless id.strip != ""
46 next unless id.strip != ""
45 pid = Problem.find_by_id(id.to_i)
47 pid = Problem.find_by_id(id.to_i)
46 @problems << pid if pid
48 @problems << pid if pid
47 end
49 end
48 end
50 end
49
51
50 #users
52 #users
51 @users = if params[:users] == "all" then
53 @users = if params[:users] == "all" then
52 User.includes(:contests).includes(:contest_stat)
54 User.includes(:contests).includes(:contest_stat)
53 else
55 else
54 User.includes(:contests).includes(:contest_stat).where(enabled: true)
56 User.includes(:contests).includes(:contest_stat).where(enabled: true)
55 end
57 end
@@ -59,164 +61,204
59 @until_id = params.fetch(:to_id, 0).to_i
61 @until_id = params.fetch(:to_id, 0).to_i
60 @since_id = nil if @since_id == 0
62 @since_id = nil if @since_id == 0
61 @until_id = nil if @until_id == 0
63 @until_id = nil if @until_id == 0
62
64
63 #calculate the routine
65 #calculate the routine
64 @scorearray = calculate_max_score(@problems, @users, @since_id, @until_id)
66 @scorearray = calculate_max_score(@problems, @users, @since_id, @until_id)
65
67
66 #rencer accordingly
68 #rencer accordingly
67 if params[:button] == 'download' then
69 if params[:button] == 'download' then
68 csv = gen_csv_from_scorearray(@scorearray,@problems)
70 csv = gen_csv_from_scorearray(@scorearray,@problems)
69 send_data csv, filename: 'max_score.csv'
71 send_data csv, filename: 'max_score.csv'
70 else
72 else
71 #render template: 'user_admin/user_stat'
73 #render template: 'user_admin/user_stat'
72 render 'max_score'
74 render 'max_score'
73 end
75 end
74
76
75 end
77 end
76
78
77 def score
79 def score
78 if params[:commit] == 'download csv'
80 if params[:commit] == 'download csv'
79 @problems = Problem.all
81 @problems = Problem.all
80 else
82 else
81 @problems = Problem.available_problems
83 @problems = Problem.available_problems
82 end
84 end
83 @users = User.includes(:contests, :contest_stat).where(enabled: true)
85 @users = User.includes(:contests, :contest_stat).where(enabled: true)
84 @scorearray = Array.new
86 @scorearray = Array.new
85 @users.each do |u|
87 @users.each do |u|
86 ustat = Array.new
88 ustat = Array.new
87 ustat[0] = u
89 ustat[0] = u
88 @problems.each do |p|
90 @problems.each do |p|
89 sub = Submission.find_last_by_user_and_problem(u.id,p.id)
91 sub = Submission.find_last_by_user_and_problem(u.id,p.id)
90 if (sub!=nil) and (sub.points!=nil) and p and p.full_score
92 if (sub!=nil) and (sub.points!=nil) and p and p.full_score
91 ustat << [(sub.points.to_f*100/p.full_score).round, (sub.points>=p.full_score)]
93 ustat << [(sub.points.to_f*100/p.full_score).round, (sub.points>=p.full_score)]
92 else
94 else
93 ustat << [0,false]
95 ustat << [0,false]
94 end
96 end
95 end
97 end
96 @scorearray << ustat
98 @scorearray << ustat
97 end
99 end
98 if params[:commit] == 'download csv' then
100 if params[:commit] == 'download csv' then
99 csv = gen_csv_from_scorearray(@scorearray,@problems)
101 csv = gen_csv_from_scorearray(@scorearray,@problems)
100 send_data csv, filename: 'last_score.csv'
102 send_data csv, filename: 'last_score.csv'
101 else
103 else
102 render template: 'user_admin/user_stat'
104 render template: 'user_admin/user_stat'
103 end
105 end
104
106
105 end
107 end
106
108
107 - def login_stat
109 + def login
110 + end
111 +
112 + def login_summary_query
113 + @users = Array.new
114 +
115 + date_and_time = '%Y-%m-%d %H:%M'
116 + begin
117 + md = params[:since_datetime].match(/(\d+)-(\d+)-(\d+) (\d+):(\d+)/)
118 + @since_time = Time.zone.local(md[1].to_i,md[2].to_i,md[3].to_i,md[4].to_i,md[5].to_i)
119 + rescue
120 + @since_time = DateTime.new(1000,1,1)
121 + end
122 + begin
123 + md = params[:until_datetime].match(/(\d+)-(\d+)-(\d+) (\d+):(\d+)/)
124 + @until_time = Time.zone.local(md[1].to_i,md[2].to_i,md[3].to_i,md[4].to_i,md[5].to_i)
125 + rescue
126 + @until_time = DateTime.new(3000,1,1)
127 + end
128 +
129 + record = User
130 + .left_outer_joins(:logins).group('users.id')
131 + .where("logins.created_at >= ? AND logins.created_at <= ?",@since_time, @until_time)
132 + case params[:users]
133 + when 'enabled'
134 + record = record.where(enabled: true)
135 + when 'group'
136 + record = record.joins(:groups).where(groups: {id: params[:groups]}) if params[:groups]
137 + end
138 +
139 + record = record.pluck("users.id,users.login,users.full_name,count(logins.created_at),min(logins.created_at),max(logins.created_at)")
140 + record.each do |user|
141 + x = Login.where("user_id = ? AND created_at >= ? AND created_at <= ?",
142 + user[0],@since_time,@until_time)
143 + .pluck(:ip_address).uniq
144 + @users << { id: user[0],
145 + login: user[1],
146 + full_name: user[2],
147 + count: user[3],
148 + min: user[4],
149 + max: user[5],
150 + ip: x
151 + }
152 + end
153 + end
154 +
155 + def login_detail_query
108 @logins = Array.new
156 @logins = Array.new
109
157
110 date_and_time = '%Y-%m-%d %H:%M'
158 date_and_time = '%Y-%m-%d %H:%M'
111 begin
159 begin
112 md = params[:since_datetime].match(/(\d+)-(\d+)-(\d+) (\d+):(\d+)/)
160 md = params[:since_datetime].match(/(\d+)-(\d+)-(\d+) (\d+):(\d+)/)
113 @since_time = Time.zone.local(md[1].to_i,md[2].to_i,md[3].to_i,md[4].to_i,md[5].to_i)
161 @since_time = Time.zone.local(md[1].to_i,md[2].to_i,md[3].to_i,md[4].to_i,md[5].to_i)
114 rescue
162 rescue
115 @since_time = DateTime.new(1000,1,1)
163 @since_time = DateTime.new(1000,1,1)
116 end
164 end
117 begin
165 begin
118 md = params[:until_datetime].match(/(\d+)-(\d+)-(\d+) (\d+):(\d+)/)
166 md = params[:until_datetime].match(/(\d+)-(\d+)-(\d+) (\d+):(\d+)/)
119 @until_time = Time.zone.local(md[1].to_i,md[2].to_i,md[3].to_i,md[4].to_i,md[5].to_i)
167 @until_time = Time.zone.local(md[1].to_i,md[2].to_i,md[3].to_i,md[4].to_i,md[5].to_i)
120 rescue
168 rescue
121 @until_time = DateTime.new(3000,1,1)
169 @until_time = DateTime.new(3000,1,1)
122 end
170 end
123
171
124 - User.all.each do |user|
172 + @logins = Login.includes(:user).where("logins.created_at >= ? AND logins.created_at <= ?",@since_time, @until_time)
125 - @logins << { id: user.id,
173 + case params[:users]
126 - login: user.login,
174 + when 'enabled'
127 - full_name: user.full_name,
175 + @logins = @logins.where(users: {enabled: true})
128 - count: Login.where("user_id = ? AND created_at >= ? AND created_at <= ?",
176 + when 'group'
129 - user.id,@since_time,@until_time)
177 + @logins = @logins.joins(user: :groups).where(user: {groups: {id: params[:groups]}}) if params[:groups]
130 - .count(:id),
131 - min: Login.where("user_id = ? AND created_at >= ? AND created_at <= ?",
132 - user.id,@since_time,@until_time)
133 - .minimum(:created_at),
134 - max: Login.where("user_id = ? AND created_at >= ? AND created_at <= ?",
135 - user.id,@since_time,@until_time)
136 - .maximum(:created_at),
137 - ip: Login.where("user_id = ? AND created_at >= ? AND created_at <= ?",
138 - user.id,@since_time,@until_time)
139 - .select(:ip_address).uniq
140 -
141 - }
142 end
178 end
143 end
179 end
144
180
145 def submission
181 def submission
146 end
182 end
147
183
148 def submission_query
184 def submission_query
149 @submissions = Submission
185 @submissions = Submission
150 .includes(:problem).includes(:user).includes(:language)
186 .includes(:problem).includes(:user).includes(:language)
151
187
152 - if params[:problem]
188 + case params[:users]
153 - @submission = @submission.where(problem_id: params[:problem])
189 + when 'enabled'
190 + @submissions = @submissions.where(users: {enabled: true})
191 + when 'group'
192 + @submissions = @submissions.joins(user: :groups).where(user: {groups: {id: params[:groups]}}) if params[:groups]
154 end
193 end
155
194
156 - case params[:users]
195 + case params[:problems]
157 when 'enabled'
196 when 'enabled'
158 - @submissions = @submissions.where('user.enabled': true)
197 + @submissions = @submissions.where(problems: {available: true})
159 - when 'group'
198 + when 'selected'
160 - @submissions = @submissions.joins(user: :groups).where(user: {groups: {id: params[:groups]}}) if params[:groups]
199 + @submissions = @submissions.where(problem_id: params[:problem_id])
161 end
200 end
162
201
163 #set default
202 #set default
164 params[:since_datetime] = Date.today.to_s if params[:since_datetime].blank?
203 params[:since_datetime] = Date.today.to_s if params[:since_datetime].blank?
165
204
166 @submissions, @recordsTotal, @recordsFiltered = process_query_record( @submissions,
205 @submissions, @recordsTotal, @recordsFiltered = process_query_record( @submissions,
167 global_search: ['user.login','user.full_name','problem.name','problem.full_name','points'],
206 global_search: ['user.login','user.full_name','problem.name','problem.full_name','points'],
168 date_filter: 'submitted_at',
207 date_filter: 'submitted_at',
169 date_param_since: 'since_datetime',
208 date_param_since: 'since_datetime',
170 date_param_until: 'until_datetime',
209 date_param_until: 'until_datetime',
171 hard_limit: 100_000
210 hard_limit: 100_000
172 )
211 )
173 end
212 end
174
213
214 + def login
215 + end
216 +
175 def problem_hof
217 def problem_hof
176 # gen problem list
218 # gen problem list
177 @user = User.find(session[:user_id])
219 @user = User.find(session[:user_id])
178 @problems = @user.available_problems
220 @problems = @user.available_problems
179
221
180 # get selected problems or the default
222 # get selected problems or the default
181 if params[:id]
223 if params[:id]
182 begin
224 begin
183 @problem = Problem.available.find(params[:id])
225 @problem = Problem.available.find(params[:id])
184 rescue
226 rescue
185 redirect_to action: :problem_hof
227 redirect_to action: :problem_hof
186 flash[:notice] = 'Error: submissions for that problem are not viewable.'
228 flash[:notice] = 'Error: submissions for that problem are not viewable.'
187 return
229 return
188 end
230 end
189 end
231 end
190
232
191 return unless @problem
233 return unless @problem
192
234
193 @by_lang = {} #aggregrate by language
235 @by_lang = {} #aggregrate by language
194
236
195 range =65
237 range =65
196 @histogram = { data: Array.new(range,0), summary: {} }
238 @histogram = { data: Array.new(range,0), summary: {} }
197 @summary = {count: 0, solve: 0, attempt: 0}
239 @summary = {count: 0, solve: 0, attempt: 0}
198 user = Hash.new(0)
240 user = Hash.new(0)
199 Submission.where(problem_id: @problem.id).find_each do |sub|
241 Submission.where(problem_id: @problem.id).find_each do |sub|
200 #histogram
242 #histogram
201 d = (DateTime.now.in_time_zone - sub.submitted_at) / 24 / 60 / 60
243 d = (DateTime.now.in_time_zone - sub.submitted_at) / 24 / 60 / 60
202 @histogram[:data][d.to_i] += 1 if d < range
244 @histogram[:data][d.to_i] += 1 if d < range
203
245
204 next unless sub.points
246 next unless sub.points
205 @summary[:count] += 1
247 @summary[:count] += 1
206 user[sub.user_id] = [user[sub.user_id], (sub.points >= @problem.full_score) ? 1 : 0].max
248 user[sub.user_id] = [user[sub.user_id], (sub.points >= @problem.full_score) ? 1 : 0].max
207
249
208 lang = Language.find_by_id(sub.language_id)
250 lang = Language.find_by_id(sub.language_id)
209 next unless lang
251 next unless lang
210 next unless sub.points >= @problem.full_score
252 next unless sub.points >= @problem.full_score
211
253
212 #initialize
254 #initialize
213 unless @by_lang.has_key?(lang.pretty_name)
255 unless @by_lang.has_key?(lang.pretty_name)
214 @by_lang[lang.pretty_name] = {
256 @by_lang[lang.pretty_name] = {
215 runtime: { avail: false, value: 2**30-1 },
257 runtime: { avail: false, value: 2**30-1 },
216 memory: { avail: false, value: 2**30-1 },
258 memory: { avail: false, value: 2**30-1 },
217 length: { avail: false, value: 2**30-1 },
259 length: { avail: false, value: 2**30-1 },
218 first: { avail: false, value: DateTime.new(3000,1,1) }
260 first: { avail: false, value: DateTime.new(3000,1,1) }
219 }
261 }
220 end
262 end
221
263
222 if sub.max_runtime and sub.max_runtime < @by_lang[lang.pretty_name][:runtime][:value]
264 if sub.max_runtime and sub.max_runtime < @by_lang[lang.pretty_name][:runtime][:value]
@@ -1,65 +1,58
1 require 'csv'
1 require 'csv'
2
2
3 class UserAdminController < ApplicationController
3 class UserAdminController < ApplicationController
4
4
5 include MailHelperMethods
5 include MailHelperMethods
6
6
7 before_action :admin_authorization
7 before_action :admin_authorization
8
8
9 def index
9 def index
10 @user_count = User.count
10 @user_count = User.count
11 - if params[:page] == 'all'
12 - @users = User.all
13 - @paginated = false
14 - else
15 - @users = User.paginate :page => params[:page]
16 - @paginated = true
17 - end
18 @users = User.all
11 @users = User.all
19 @hidden_columns = ['hashed_password', 'salt', 'created_at', 'updated_at']
12 @hidden_columns = ['hashed_password', 'salt', 'created_at', 'updated_at']
20 @contests = Contest.enabled
13 @contests = Contest.enabled
21 end
14 end
22
15
23 def active
16 def active
24 sessions = ActiveRecord::SessionStore::Session.where("updated_at >= ?", 60.minutes.ago)
17 sessions = ActiveRecord::SessionStore::Session.where("updated_at >= ?", 60.minutes.ago)
25 @users = []
18 @users = []
26 sessions.each do |session|
19 sessions.each do |session|
27 if session.data[:user_id]
20 if session.data[:user_id]
28 @users << User.find(session.data[:user_id])
21 @users << User.find(session.data[:user_id])
29 end
22 end
30 end
23 end
31 end
24 end
32
25
33 def show
26 def show
34 @user = User.find(params[:id])
27 @user = User.find(params[:id])
35 end
28 end
36
29
37 def new
30 def new
38 @user = User.new
31 @user = User.new
39 end
32 end
40
33
41 def create
34 def create
42 @user = User.new(user_params)
35 @user = User.new(user_params)
43 @user.activated = true
36 @user.activated = true
44 if @user.save
37 if @user.save
45 flash[:notice] = 'User was successfully created.'
38 flash[:notice] = 'User was successfully created.'
46 redirect_to :action => 'index'
39 redirect_to :action => 'index'
47 else
40 else
48 render :action => 'new'
41 render :action => 'new'
49 end
42 end
50 end
43 end
51
44
52 def clear_last_ip
45 def clear_last_ip
53 @user = User.find(params[:id])
46 @user = User.find(params[:id])
54 @user.last_ip = nil
47 @user.last_ip = nil
55 @user.save
48 @user.save
56 redirect_to action: 'index', page: params[:page]
49 redirect_to action: 'index', page: params[:page]
57 end
50 end
58
51
59 def create_from_list
52 def create_from_list
60 lines = params[:user_list]
53 lines = params[:user_list]
61
54
62 note = []
55 note = []
63 error_note = []
56 error_note = []
64 error_msg = nil
57 error_msg = nil
65 ok_user = []
58 ok_user = []
@@ -1,72 +1,74
1 require 'digest/sha1'
1 require 'digest/sha1'
2 require 'net/pop'
2 require 'net/pop'
3 require 'net/https'
3 require 'net/https'
4 require 'net/http'
4 require 'net/http'
5 require 'json'
5 require 'json'
6
6
7 class User < ActiveRecord::Base
7 class User < ActiveRecord::Base
8
8
9 has_and_belongs_to_many :roles
9 has_and_belongs_to_many :roles
10
10
11 #has_and_belongs_to_many :groups
11 #has_and_belongs_to_many :groups
12 has_many :groups_users, class_name: 'GroupUser'
12 has_many :groups_users, class_name: 'GroupUser'
13 has_many :groups, :through => :groups_users
13 has_many :groups, :through => :groups_users
14
14
15 has_many :test_requests, -> {order(submitted_at: :desc)}
15 has_many :test_requests, -> {order(submitted_at: :desc)}
16
16
17 has_many :messages, -> { order(created_at: :desc) },
17 has_many :messages, -> { order(created_at: :desc) },
18 :class_name => "Message",
18 :class_name => "Message",
19 :foreign_key => "sender_id"
19 :foreign_key => "sender_id"
20
20
21 has_many :replied_messages, -> { order(created_at: :desc) },
21 has_many :replied_messages, -> { order(created_at: :desc) },
22 :class_name => "Message",
22 :class_name => "Message",
23 :foreign_key => "receiver_id"
23 :foreign_key => "receiver_id"
24
24
25 + has_many :logins
26 +
25 has_one :contest_stat, :class_name => "UserContestStat", :dependent => :destroy
27 has_one :contest_stat, :class_name => "UserContestStat", :dependent => :destroy
26
28
27 belongs_to :site
29 belongs_to :site
28 belongs_to :country
30 belongs_to :country
29
31
30 has_and_belongs_to_many :contests, -> { order(:name)}
32 has_and_belongs_to_many :contests, -> { order(:name)}
31
33
32 scope :activated_users, -> {where activated: true}
34 scope :activated_users, -> {where activated: true}
33
35
34 validates_presence_of :login
36 validates_presence_of :login
35 validates_uniqueness_of :login
37 validates_uniqueness_of :login
36 validates_format_of :login, :with => /\A[\_A-Za-z0-9]+\z/
38 validates_format_of :login, :with => /\A[\_A-Za-z0-9]+\z/
37 validates_length_of :login, :within => 3..30
39 validates_length_of :login, :within => 3..30
38
40
39 validates_presence_of :full_name
41 validates_presence_of :full_name
40 validates_length_of :full_name, :minimum => 1
42 validates_length_of :full_name, :minimum => 1
41
43
42 validates_presence_of :password, :if => :password_required?
44 validates_presence_of :password, :if => :password_required?
43 validates_length_of :password, :within => 4..50, :if => :password_required?
45 validates_length_of :password, :within => 4..50, :if => :password_required?
44 validates_confirmation_of :password, :if => :password_required?
46 validates_confirmation_of :password, :if => :password_required?
45
47
46 validates_format_of :email,
48 validates_format_of :email,
47 :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i,
49 :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i,
48 :if => :email_validation?
50 :if => :email_validation?
49 validate :uniqueness_of_email_from_activated_users,
51 validate :uniqueness_of_email_from_activated_users,
50 :if => :email_validation?
52 :if => :email_validation?
51 validate :enough_time_interval_between_same_email_registrations,
53 validate :enough_time_interval_between_same_email_registrations,
52 :if => :email_validation?
54 :if => :email_validation?
53
55
54 # these are for ytopc
56 # these are for ytopc
55 # disable for now
57 # disable for now
56 #validates_presence_of :province
58 #validates_presence_of :province
57
59
58 attr_accessor :password
60 attr_accessor :password
59
61
60 before_save :encrypt_new_password
62 before_save :encrypt_new_password
61 before_save :assign_default_site
63 before_save :assign_default_site
62 before_save :assign_default_contest
64 before_save :assign_default_contest
63
65
64 # this is for will_paginate
66 # this is for will_paginate
65 cattr_reader :per_page
67 cattr_reader :per_page
66 @@per_page = 50
68 @@per_page = 50
67
69
68 def self.authenticate(login, password)
70 def self.authenticate(login, password)
69 user = find_by_login(login)
71 user = find_by_login(login)
70 if user
72 if user
71 return user if user.authenticated?(password)
73 return user if user.authenticated?(password)
72 end
74 end
@@ -20,76 +20,77
20 %span.caret
20 %span.caret
21 %ul.dropdown-menu
21 %ul.dropdown-menu
22 = add_menu("View", 'submissions', 'index')
22 = add_menu("View", 'submissions', 'index')
23 = add_menu("Self Test", 'test', 'index')
23 = add_menu("Self Test", 'test', 'index')
24 / hall of fame
24 / hall of fame
25 - if GraderConfiguration['right.user_hall_of_fame']
25 - if GraderConfiguration['right.user_hall_of_fame']
26 = add_menu("#{I18n.t 'menu.hall_of_fame'}", 'report', 'problem_hof')
26 = add_menu("#{I18n.t 'menu.hall_of_fame'}", 'report', 'problem_hof')
27 / display MODE button (with countdown in contest mode)
27 / display MODE button (with countdown in contest mode)
28 - if GraderConfiguration.analysis_mode?
28 - if GraderConfiguration.analysis_mode?
29 %div.navbar-btn.btn.btn-success#countdown= "ANALYSIS MODE"
29 %div.navbar-btn.btn.btn-success#countdown= "ANALYSIS MODE"
30 - elsif GraderConfiguration.time_limit_mode?
30 - elsif GraderConfiguration.time_limit_mode?
31 - if @current_user.contest_finished?
31 - if @current_user.contest_finished?
32 %div.navbar-btn.btn.btn-danger#countdown= "Contest is over"
32 %div.navbar-btn.btn.btn-danger#countdown= "Contest is over"
33 - elsif !@current_user.contest_started?
33 - elsif !@current_user.contest_started?
34 %div.navbar-btn.btn.btn-primary#countdown= (t 'title_bar.contest_not_started')
34 %div.navbar-btn.btn.btn-primary#countdown= (t 'title_bar.contest_not_started')
35 - else
35 - else
36 %div.navbar-btn.btn.btn-primary#countdown asdf
36 %div.navbar-btn.btn.btn-primary#countdown asdf
37 :javascript
37 :javascript
38 $("#countdown").countdown({until: "+#{@current_user.contest_time_left.to_i}s", layout: 'Time left: {hnn}:{mnn}:{snn}'});
38 $("#countdown").countdown({until: "+#{@current_user.contest_time_left.to_i}s", layout: 'Time left: {hnn}:{mnn}:{snn}'});
39 / admin section
39 / admin section
40 - if (@current_user!=nil) and (session[:admin])
40 - if (@current_user!=nil) and (session[:admin])
41 / management
41 / management
42 %li.dropdown
42 %li.dropdown
43 %a.dropdown-toggle{href: '#', data: {toggle:'dropdown'}, aria: {haspopup:"true", expanded:"false"}, role: "button"}
43 %a.dropdown-toggle{href: '#', data: {toggle:'dropdown'}, aria: {haspopup:"true", expanded:"false"}, role: "button"}
44 Manage
44 Manage
45 %span.caret
45 %span.caret
46 %ul.dropdown-menu
46 %ul.dropdown-menu
47 = add_menu( 'Announcements', 'announcements', 'index')
47 = add_menu( 'Announcements', 'announcements', 'index')
48 = add_menu( 'Problems', 'problems', 'index')
48 = add_menu( 'Problems', 'problems', 'index')
49 = add_menu( 'Tags', 'tags', 'index')
49 = add_menu( 'Tags', 'tags', 'index')
50 = add_menu( 'Users', 'user_admin', 'index')
50 = add_menu( 'Users', 'user_admin', 'index')
51 = add_menu( 'User Groups', 'groups', 'index')
51 = add_menu( 'User Groups', 'groups', 'index')
52 = add_menu( 'Graders', 'graders', 'list')
52 = add_menu( 'Graders', 'graders', 'list')
53 = add_menu( 'Message ', 'messages', 'console')
53 = add_menu( 'Message ', 'messages', 'console')
54 %li.divider{role: 'separator'}
54 %li.divider{role: 'separator'}
55 = add_menu( 'System config', 'configurations', 'index')
55 = add_menu( 'System config', 'configurations', 'index')
56 %li.divider{role: 'separator'}
56 %li.divider{role: 'separator'}
57 = add_menu( 'Sites', 'sites', 'index')
57 = add_menu( 'Sites', 'sites', 'index')
58 = add_menu( 'Contests', 'contest_management', 'index')
58 = add_menu( 'Contests', 'contest_management', 'index')
59 / report
59 / report
60 %li.dropdown
60 %li.dropdown
61 %a.dropdown-toggle{href: '#', data: {toggle:'dropdown'}, aria: {haspopup:"true", expanded:"false"}, role: "button"}
61 %a.dropdown-toggle{href: '#', data: {toggle:'dropdown'}, aria: {haspopup:"true", expanded:"false"}, role: "button"}
62 Report
62 Report
63 %span.caret
63 %span.caret
64 %ul.dropdown-menu
64 %ul.dropdown-menu
65 = add_menu( 'Current Score', 'report', 'current_score')
65 = add_menu( 'Current Score', 'report', 'current_score')
66 = add_menu( 'Score Report', 'report', 'max_score')
66 = add_menu( 'Score Report', 'report', 'max_score')
67 = add_menu( 'Submission Report', 'report', 'submission')
67 = add_menu( 'Submission Report', 'report', 'submission')
68 + = add_menu( 'Login Report', 'report', 'login')
68 - if (ungraded = Submission.where('graded_at is null').where('submitted_at < ?', 1.minutes.ago).count) > 0
69 - if (ungraded = Submission.where('graded_at is null').where('submitted_at < ?', 1.minutes.ago).count) > 0
69 =link_to "#{ungraded} backlogs!",
70 =link_to "#{ungraded} backlogs!",
70 grader_list_path,
71 grader_list_path,
71 class: 'navbar-btn btn btn-default btn-warning', data: {toggle: 'tooltip'},title: 'Number of ungraded submission'
72 class: 'navbar-btn btn btn-default btn-warning', data: {toggle: 'tooltip'},title: 'Number of ungraded submission'
72
73
73 %ul.nav.navbar-nav.navbar-right
74 %ul.nav.navbar-nav.navbar-right
74 = add_menu("#{content_tag(:span,'',class: 'glyphicon glyphicon-question-sign')}".html_safe, 'main', 'help')
75 = add_menu("#{content_tag(:span,'',class: 'glyphicon glyphicon-question-sign')}".html_safe, 'main', 'help')
75 = add_menu("#{content_tag(:span,'',class: 'glyphicon glyphicon-comment')}".html_safe, 'messages', 'index', {title: I18n.t('menu.messages'), data: {toggle: 'tooltip'}})
76 = add_menu("#{content_tag(:span,'',class: 'glyphicon glyphicon-comment')}".html_safe, 'messages', 'index', {title: I18n.t('menu.messages'), data: {toggle: 'tooltip'}})
76 - if GraderConfiguration['system.user_setting_enabled']
77 - if GraderConfiguration['system.user_setting_enabled']
77 = add_menu("#{content_tag(:span,'',class: 'glyphicon glyphicon-cog', id: 'user_profile')}".html_safe, 'users', 'profile', {title: I18n.t('menu.settings'), data: {toggle: 'tooltip'}})
78 = add_menu("#{content_tag(:span,'',class: 'glyphicon glyphicon-cog', id: 'user_profile')}".html_safe, 'users', 'profile', {title: I18n.t('menu.settings'), data: {toggle: 'tooltip'}})
78 = add_menu("#{content_tag(:span,'',class: 'glyphicon glyphicon-log-out')} #{@current_user.full_name}".html_safe, 'main', 'login', {title: I18n.t('menu.log_out'), data: {toggle: 'tooltip'}})
79 = add_menu("#{content_tag(:span,'',class: 'glyphicon glyphicon-log-out')} #{@current_user.full_name}".html_safe, 'main', 'login', {title: I18n.t('menu.log_out'), data: {toggle: 'tooltip'}})
79
80
80 /
81 /
81 - if (@current_user!=nil) and (session[:admin])
82 - if (@current_user!=nil) and (session[:admin])
82 %nav.navbar.navbar-fixed-top.navbar-inverse.secondnavbar
83 %nav.navbar.navbar-fixed-top.navbar-inverse.secondnavbar
83 .container-fluid
84 .container-fluid
84 .collapse.navbar-collapse
85 .collapse.navbar-collapse
85 %ul.nav.navbar-nav
86 %ul.nav.navbar-nav
86 = add_menu( '[Announcements]', 'announcements', 'index')
87 = add_menu( '[Announcements]', 'announcements', 'index')
87 = add_menu( '[Msg console]', 'messages', 'console')
88 = add_menu( '[Msg console]', 'messages', 'console')
88 = add_menu( '[Problems]', 'problems', 'index')
89 = add_menu( '[Problems]', 'problems', 'index')
89 = add_menu( '[Users]', 'user_admin', 'index')
90 = add_menu( '[Users]', 'user_admin', 'index')
90 = add_menu( '[Results]', 'user_admin', 'user_stat')
91 = add_menu( '[Results]', 'user_admin', 'user_stat')
91 = add_menu( '[Report]', 'report', 'multiple_login')
92 = add_menu( '[Report]', 'report', 'multiple_login')
92 = add_menu( '[Graders]', 'graders', 'list')
93 = add_menu( '[Graders]', 'graders', 'list')
93 = add_menu( '[Contests]', 'contest_management', 'index')
94 = add_menu( '[Contests]', 'contest_management', 'index')
94 = add_menu( '[Sites]', 'sites', 'index')
95 = add_menu( '[Sites]', 'sites', 'index')
95 = add_menu( '[System config]', 'configurations', 'index')
96 = add_menu( '[System config]', 'configurations', 'index')
@@ -1,77 +1,82
1 - content_for :header do
1 - content_for :header do
2 = javascript_include_tag 'local_jquery'
2 = javascript_include_tag 'local_jquery'
3
3
4 %h1 Submissions detail
4 %h1 Submissions detail
5
5
6 .row
6 .row
7 .col-md-4
7 .col-md-4
8 = render partial: 'shared/problem_select'
8 = render partial: 'shared/problem_select'
9 .col-md-4
9 .col-md-4
10 = render partial: 'shared/date_filter'
10 = render partial: 'shared/date_filter'
11 .col-md-4
11 .col-md-4
12 = render partial: 'shared/user_select'
12 = render partial: 'shared/user_select'
13
13
14 .row
14 .row
15 .col-md-6
15 .col-md-6
16 .alert.alert-info
16 .alert.alert-info
17 %ul
17 %ul
18 %li Display a maximum of 100,000 entries to save computation power
18 %li Display a maximum of 100,000 entries to save computation power
19 %li You have to click refresh when changing the filter above
19 %li You have to click refresh when changing the filter above
20
20
21 .row
21 .row
22 .col-sm-12
22 .col-sm-12
23 %table.table.table-hover.table-condense.datatable{style: 'width: 100%'}
23 %table.table.table-hover.table-condense.datatable{style: 'width: 100%'}
24
24
25
25
26 :javascript
26 :javascript
27 $(function() {
27 $(function() {
28 submission_table = $('.datatable').DataTable({
28 submission_table = $('.datatable').DataTable({
29 dom: "<'row'<'col-sm-3'B><'col-sm-3'l><'col-sm-6'f>>" + "<'row'<'col-sm-12'tr>>" + "<'row'<'col-sm-5'i><'col-sm-7'p>>",
29 dom: "<'row'<'col-sm-3'B><'col-sm-3'l><'col-sm-6'f>>" + "<'row'<'col-sm-12'tr>>" + "<'row'<'col-sm-5'i><'col-sm-7'p>>",
30 autoWidth: true,
30 autoWidth: true,
31 buttons: [
31 buttons: [
32 {
32 {
33 text: 'Refresh',
33 text: 'Refresh',
34 action: (e,dt,node,config) => {
34 action: (e,dt,node,config) => {
35 submission_table.clear().draw()
35 submission_table.clear().draw()
36 submission_table.ajax.reload( () => { submission_table.columns.adjust().draw() } )
36 submission_table.ajax.reload( () => { submission_table.columns.adjust().draw() } )
37 }
37 }
38 },
38 },
39 - 'copy', 'excel'
39 + 'copy',
40 + {
41 + extend: 'excel',
42 + title: 'Submission detail'
43 + }
40 ],
44 ],
41 columns: [
45 columns: [
42 {title: 'Sub ID', data: 'id'},
46 {title: 'Sub ID', data: 'id'},
43 {title: 'User', data: 'user.login'},
47 {title: 'User', data: 'user.login'},
44 {title: 'Problem', data: 'problem.long_name'},
48 {title: 'Problem', data: 'problem.long_name'},
45 {title: 'Language', data: 'language.pretty_name'},
49 {title: 'Language', data: 'language.pretty_name'},
46 {title: 'Submit at', data: 'submitted_at'},
50 {title: 'Submit at', data: 'submitted_at'},
47 {title: 'Result', data: 'grader_comment'},
51 {title: 'Result', data: 'grader_comment'},
48 {title: 'Score', data: 'points'},
52 {title: 'Score', data: 'points'},
49 {title: 'IP', data: 'ip_address'},
53 {title: 'IP', data: 'ip_address'},
50 ],
54 ],
51 ajax: {
55 ajax: {
52 url: '#{submission_query_report_path}',
56 url: '#{submission_query_report_path}',
53 type: 'POST',
57 type: 'POST',
54 data: (d) => {
58 data: (d) => {
55 d.since_datetime = $('#since_datetime').val()
59 d.since_datetime = $('#since_datetime').val()
56 d.until_datetime = $('#until_datetime').val()
60 d.until_datetime = $('#until_datetime').val()
57 d.users = $("input[name='users']:checked").val()
61 d.users = $("input[name='users']:checked").val()
58 - d.problems = $("#problem_id").select2('val')
59 d.groups = $("#group_id").select2('val')
62 d.groups = $("#group_id").select2('val')
63 + d.problems = $("input[name='problems']:checked").val()
64 + d.problem_id = $("#problem_id").select2('val')
60 },
65 },
61 dataType: 'json',
66 dataType: 'json',
62 beforeSend: (request) => {
67 beforeSend: (request) => {
63 request.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').attr('content'));
68 request.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').attr('content'));
64 },
69 },
65 }, //end ajax
70 }, //end ajax
66 'pageLength': 50,
71 'pageLength': 50,
67 processing: true,
72 processing: true,
68 });
73 });
69
74
70 $('.input-group.date').datetimepicker({
75 $('.input-group.date').datetimepicker({
71 format: 'YYYY-MM-DD',
76 format: 'YYYY-MM-DD',
72 showTodayButton: true,
77 showTodayButton: true,
73 locale: 'en',
78 locale: 'en',
74 widgetPositioning: {horizontal: 'auto', vertical: 'bottom'},
79 widgetPositioning: {horizontal: 'auto', vertical: 'bottom'},
75 defaultDate: moment()
80 defaultDate: moment()
76 });
81 });
77 });
82 });
@@ -1,10 +1,19
1 .panel.panel-primary
1 .panel.panel-primary
2 .panel-heading
2 .panel-heading
3 Problems
3 Problems
4 .panel-body
4 .panel-body
5 - %p
5 + .radio
6 - Select problem(s) to be included in the report
6 + %label
7 - = label_tag :problem_id, "Problems"
7 + = radio_button_tag 'problems', 'all', (params[:users] == "all")
8 + All problems
9 + .radio
10 + %label
11 + = radio_button_tag 'problems', 'enabled', (params[:users] == "enabled"), checked: true
12 + Only problems with available = "yes"
13 + .radio
14 + %label
15 + = radio_button_tag 'problems', 'selected', (params[:users] == "group")
16 + Only these selected problems
8 = select_tag 'problem_id[]',
17 = select_tag 'problem_id[]',
9 options_for_select(Problem.all.collect {|p| ["[#{p.name}] #{p.full_name}", p.id]},params[:problem_id]),
18 options_for_select(Problem.all.collect {|p| ["[#{p.name}] #{p.full_name}", p.id]},params[:problem_id]),
10 { id: :problem_id, class: 'select2 form-control', multiple: "true" }
19 { id: :problem_id, class: 'select2 form-control', multiple: "true" }
@@ -1,19 +1,19
1 .panel.panel-primary
1 .panel.panel-primary
2 .panel-heading
2 .panel-heading
3 Users
3 Users
4 .panel-body
4 .panel-body
5 .radio
5 .radio
6 %label
6 %label
7 = radio_button_tag 'users', 'all', (params[:users] == "all")
7 = radio_button_tag 'users', 'all', (params[:users] == "all")
8 All users
8 All users
9 .radio
9 .radio
10 %label
10 %label
11 - = radio_button_tag 'users', 'enabled', (params[:users] == "enabled")
11 + = radio_button_tag 'users', 'enabled', (params[:users] == "enabled"), checked: true
12 Only enabled users
12 Only enabled users
13 .radio
13 .radio
14 %label
14 %label
15 = radio_button_tag 'users', 'group', (params[:users] == "group")
15 = radio_button_tag 'users', 'group', (params[:users] == "group")
16 Only these groups
16 Only these groups
17 = select_tag 'group_id[]',
17 = select_tag 'group_id[]',
18 options_for_select(Group.all.collect {|g| ["[#{g.name}] #{g.description}", g.id]}),
18 options_for_select(Group.all.collect {|g| ["[#{g.name}] #{g.description}", g.id]}),
19 { id: 'group_id', class: 'select2 form-control', multiple: "true"}
19 { id: 'group_id', class: 'select2 form-control', multiple: "true"}
@@ -109,96 +109,101
109 get 'bulk_mail'
109 get 'bulk_mail'
110 get 'user_stat'
110 get 'user_stat'
111 get 'import'
111 get 'import'
112 get 'new_list'
112 get 'new_list'
113 get 'admin'
113 get 'admin'
114 get 'active'
114 get 'active'
115 get 'mass_mailing'
115 get 'mass_mailing'
116 get 'revoke_admin'
116 get 'revoke_admin'
117 post 'grant_admin'
117 post 'grant_admin'
118 match 'create_from_list', via: [:get, :post]
118 match 'create_from_list', via: [:get, :post]
119 match 'random_all_passwords', via: [:get, :post]
119 match 'random_all_passwords', via: [:get, :post]
120 end
120 end
121 member do
121 member do
122 get 'clear_last_ip'
122 get 'clear_last_ip'
123 end
123 end
124 end
124 end
125
125
126 resources :contest_management, only: [:index] do
126 resources :contest_management, only: [:index] do
127 collection do
127 collection do
128 get 'user_stat'
128 get 'user_stat'
129 get 'clear_stat'
129 get 'clear_stat'
130 get 'clear_all_stat'
130 get 'clear_all_stat'
131 get 'change_contest_mode'
131 get 'change_contest_mode'
132 end
132 end
133 end
133 end
134
134
135 #get 'user_admin', to: 'user_admin#index'
135 #get 'user_admin', to: 'user_admin#index'
136 #get 'user_admin/bulk_manage', to: 'user_admin#bulk_manage', as: 'bulk_manage_user_admin'
136 #get 'user_admin/bulk_manage', to: 'user_admin#bulk_manage', as: 'bulk_manage_user_admin'
137 #post 'user_admin', to: 'user_admin#create'
137 #post 'user_admin', to: 'user_admin#create'
138 #delete 'user_admin/:id', to: 'user_admin#destroy', as: 'user_admin_destroy'
138 #delete 'user_admin/:id', to: 'user_admin#destroy', as: 'user_admin_destroy'
139
139
140 #singular resource
140 #singular resource
141 #---- BEWARE ---- singular resource maps to plural controller by default, we can override by provide controller name directly
141 #---- BEWARE ---- singular resource maps to plural controller by default, we can override by provide controller name directly
142 #report
142 #report
143 resource :report, only: [], controller: 'report' do
143 resource :report, only: [], controller: 'report' do
144 get 'login'
144 get 'login'
145 get 'multiple_login'
145 get 'multiple_login'
146 get 'problem_hof(/:id)', action: 'problem_hof', as: 'problem_hof'
146 get 'problem_hof(/:id)', action: 'problem_hof', as: 'problem_hof'
147 get 'current_score(/:group_id)', action: 'current_score', as: 'current_score'
147 get 'current_score(/:group_id)', action: 'current_score', as: 'current_score'
148 get 'max_score'
148 get 'max_score'
149 post 'show_max_score'
149 post 'show_max_score'
150 get 'stuck'
150 get 'stuck'
151 get 'cheat_report'
151 get 'cheat_report'
152 post 'cheat_report'
152 post 'cheat_report'
153 get 'cheat_scruntinize'
153 get 'cheat_scruntinize'
154 post 'cheat_scruntinize'
154 post 'cheat_scruntinize'
155 get 'submission'
155 get 'submission'
156 post 'submission_query'
156 post 'submission_query'
157 + get 'login_stat'
158 + post 'login_stat'
159 + get 'login'
160 + post 'login_summary_query'
161 + post 'login_detail_query'
157 end
162 end
158 #get 'report/current_score', to: 'report#current_score', as: 'report_current_score'
163 #get 'report/current_score', to: 'report#current_score', as: 'report_current_score'
159 #get 'report/problem_hof(/:id)', to: 'report#problem_hof', as: 'report_problem_hof'
164 #get 'report/problem_hof(/:id)', to: 'report#problem_hof', as: 'report_problem_hof'
160 #get "report/login"
165 #get "report/login"
161 #get 'report/max_score', to: 'report#max_score', as: 'report_max_score'
166 #get 'report/max_score', to: 'report#max_score', as: 'report_max_score'
162 #post 'report/show_max_score', to: 'report#show_max_score', as: 'report_show_max_score'
167 #post 'report/show_max_score', to: 'report#show_max_score', as: 'report_show_max_score'
163
168
164 resource :main, only: [], controller: 'main' do
169 resource :main, only: [], controller: 'main' do
165 get 'login'
170 get 'login'
166 get 'logout'
171 get 'logout'
167 get 'list'
172 get 'list'
168 get 'submission(/:id)', action: 'submission', as: 'main_submission'
173 get 'submission(/:id)', action: 'submission', as: 'main_submission'
169 get 'announcements'
174 get 'announcements'
170 get 'help'
175 get 'help'
171 post 'submit'
176 post 'submit'
172 end
177 end
173 #main
178 #main
174 #get "main/list"
179 #get "main/list"
175 #get 'main/submission(/:id)', to: 'main#submission', as: 'main_submission'
180 #get 'main/submission(/:id)', to: 'main#submission', as: 'main_submission'
176 #post 'main/submit', to: 'main#submit'
181 #post 'main/submit', to: 'main#submit'
177 #get 'main/announcements', to: 'main#announcements'
182 #get 'main/announcements', to: 'main#announcements'
178
183
179
184
180 #
185 #
181 get 'tasks/view/:file.:ext' => 'tasks#view'
186 get 'tasks/view/:file.:ext' => 'tasks#view'
182 get 'tasks/download/:id/:file.:ext' => 'tasks#download'
187 get 'tasks/download/:id/:file.:ext' => 'tasks#download'
183 get 'heartbeat/:id/edit' => 'heartbeat#edit'
188 get 'heartbeat/:id/edit' => 'heartbeat#edit'
184
189
185 #grader
190 #grader
186 get 'graders/list', to: 'graders#list', as: 'grader_list'
191 get 'graders/list', to: 'graders#list', as: 'grader_list'
187 namespace :graders do
192 namespace :graders do
188 get 'task/:id/:type', action: 'task', as: 'task'
193 get 'task/:id/:type', action: 'task', as: 'task'
189 get 'view/:id/:type', action: 'view', as: 'view'
194 get 'view/:id/:type', action: 'view', as: 'view'
190 get 'clear/:id', action: 'clear', as: 'clear'
195 get 'clear/:id', action: 'clear', as: 'clear'
191 get 'stop'
196 get 'stop'
192 get 'stop_all'
197 get 'stop_all'
193 get 'clear_all'
198 get 'clear_all'
194 get 'clear_terminated'
199 get 'clear_terminated'
195 get 'start_grading'
200 get 'start_grading'
196 get 'start_exam'
201 get 'start_exam'
197
202
198 end
203 end
199
204
200
205
201 # See how all your routes lay out with "rake routes"
206 # See how all your routes lay out with "rake routes"
202
207
203 # This is a legacy wild controller route that's not recommended for RESTful applications.
208 # This is a legacy wild controller route that's not recommended for RESTful applications.
204 # Note: This route will make all actions in every controller accessible via GET requests.
209 # Note: This route will make all actions in every controller accessible via GET requests.
@@ -1,61 +1,61
1 # This file is auto-generated from the current state of the database. Instead
1 # This file is auto-generated from the current state of the database. Instead
2 # of editing this file, please use the migrations feature of Active Record to
2 # of editing this file, please use the migrations feature of Active Record to
3 # incrementally modify your database, and then regenerate this schema definition.
3 # incrementally modify your database, and then regenerate this schema definition.
4 #
4 #
5 # Note that this schema.rb definition is the authoritative source for your
5 # Note that this schema.rb definition is the authoritative source for your
6 # database schema. If you need to create the application database on another
6 # database schema. If you need to create the application database on another
7 # system, you should be using db:schema:load, not running all the migrations
7 # system, you should be using db:schema:load, not running all the migrations
8 # from scratch. The latter is a flawed and unsustainable approach (the more migrations
8 # from scratch. The latter is a flawed and unsustainable approach (the more migrations
9 # you'll amass, the slower it'll run and the greater likelihood for issues).
9 # you'll amass, the slower it'll run and the greater likelihood for issues).
10 #
10 #
11 # It's strongly recommended that you check this file into your version control system.
11 # It's strongly recommended that you check this file into your version control system.
12
12
13 - ActiveRecord::Schema.define(version: 2020_04_04_142959) do
13 + ActiveRecord::Schema.define(version: 2020_04_05_112919) do
14
14
15 create_table "announcements", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
15 create_table "announcements", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
16 t.string "author"
16 t.string "author"
17 t.text "body", limit: 16777215
17 t.text "body", limit: 16777215
18 t.boolean "published"
18 t.boolean "published"
19 t.datetime "created_at", null: false
19 t.datetime "created_at", null: false
20 t.datetime "updated_at", null: false
20 t.datetime "updated_at", null: false
21 t.boolean "frontpage", default: false
21 t.boolean "frontpage", default: false
22 t.boolean "contest_only", default: false
22 t.boolean "contest_only", default: false
23 t.string "title"
23 t.string "title"
24 t.string "notes"
24 t.string "notes"
25 end
25 end
26
26
27 create_table "contests", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
27 create_table "contests", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
28 t.string "title"
28 t.string "title"
29 t.boolean "enabled"
29 t.boolean "enabled"
30 t.datetime "created_at", null: false
30 t.datetime "created_at", null: false
31 t.datetime "updated_at", null: false
31 t.datetime "updated_at", null: false
32 t.string "name"
32 t.string "name"
33 end
33 end
34
34
35 create_table "contests_problems", id: false, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
35 create_table "contests_problems", id: false, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
36 t.integer "contest_id"
36 t.integer "contest_id"
37 t.integer "problem_id"
37 t.integer "problem_id"
38 end
38 end
39
39
40 create_table "contests_users", id: false, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
40 create_table "contests_users", id: false, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
41 t.integer "contest_id"
41 t.integer "contest_id"
42 t.integer "user_id"
42 t.integer "user_id"
43 end
43 end
44
44
45 create_table "countries", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
45 create_table "countries", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
46 t.string "name"
46 t.string "name"
47 t.datetime "created_at", null: false
47 t.datetime "created_at", null: false
48 t.datetime "updated_at", null: false
48 t.datetime "updated_at", null: false
49 end
49 end
50
50
51 create_table "descriptions", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
51 create_table "descriptions", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
52 t.text "body", limit: 16777215
52 t.text "body", limit: 16777215
53 t.boolean "markdowned"
53 t.boolean "markdowned"
54 t.datetime "created_at", null: false
54 t.datetime "created_at", null: false
55 t.datetime "updated_at", null: false
55 t.datetime "updated_at", null: false
56 end
56 end
57
57
58 create_table "grader_configurations", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
58 create_table "grader_configurations", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
59 t.string "key"
59 t.string "key"
60 t.string "value_type"
60 t.string "value_type"
61 t.string "value"
61 t.string "value"
@@ -70,96 +70,97
70 t.string "mode"
70 t.string "mode"
71 t.boolean "active"
71 t.boolean "active"
72 t.datetime "created_at", null: false
72 t.datetime "created_at", null: false
73 t.datetime "updated_at", null: false
73 t.datetime "updated_at", null: false
74 t.integer "task_id"
74 t.integer "task_id"
75 t.string "task_type"
75 t.string "task_type"
76 t.boolean "terminated"
76 t.boolean "terminated"
77 t.index ["host", "pid"], name: "index_grader_processes_on_ip_and_pid"
77 t.index ["host", "pid"], name: "index_grader_processes_on_ip_and_pid"
78 end
78 end
79
79
80 create_table "groups", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=latin1", force: :cascade do |t|
80 create_table "groups", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=latin1", force: :cascade do |t|
81 t.string "name"
81 t.string "name"
82 t.string "description"
82 t.string "description"
83 end
83 end
84
84
85 create_table "groups_problems", id: false, options: "ENGINE=InnoDB DEFAULT CHARSET=latin1", force: :cascade do |t|
85 create_table "groups_problems", id: false, options: "ENGINE=InnoDB DEFAULT CHARSET=latin1", force: :cascade do |t|
86 t.integer "problem_id", null: false
86 t.integer "problem_id", null: false
87 t.integer "group_id", null: false
87 t.integer "group_id", null: false
88 t.index ["group_id", "problem_id"], name: "index_groups_problems_on_group_id_and_problem_id"
88 t.index ["group_id", "problem_id"], name: "index_groups_problems_on_group_id_and_problem_id"
89 end
89 end
90
90
91 create_table "groups_users", options: "ENGINE=InnoDB DEFAULT CHARSET=latin1", force: :cascade do |t|
91 create_table "groups_users", options: "ENGINE=InnoDB DEFAULT CHARSET=latin1", force: :cascade do |t|
92 t.integer "group_id", null: false
92 t.integer "group_id", null: false
93 t.integer "user_id", null: false
93 t.integer "user_id", null: false
94 t.index ["user_id", "group_id"], name: "index_groups_users_on_user_id_and_group_id"
94 t.index ["user_id", "group_id"], name: "index_groups_users_on_user_id_and_group_id"
95 end
95 end
96
96
97 create_table "heart_beats", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=latin1", force: :cascade do |t|
97 create_table "heart_beats", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=latin1", force: :cascade do |t|
98 t.integer "user_id"
98 t.integer "user_id"
99 t.string "ip_address"
99 t.string "ip_address"
100 t.datetime "created_at", null: false
100 t.datetime "created_at", null: false
101 t.datetime "updated_at", null: false
101 t.datetime "updated_at", null: false
102 t.string "status"
102 t.string "status"
103 t.index ["updated_at"], name: "index_heart_beats_on_updated_at"
103 t.index ["updated_at"], name: "index_heart_beats_on_updated_at"
104 end
104 end
105
105
106 create_table "languages", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
106 create_table "languages", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
107 t.string "name", limit: 10
107 t.string "name", limit: 10
108 t.string "pretty_name"
108 t.string "pretty_name"
109 t.string "ext", limit: 10
109 t.string "ext", limit: 10
110 t.string "common_ext"
110 t.string "common_ext"
111 end
111 end
112
112
113 create_table "logins", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=latin1", force: :cascade do |t|
113 create_table "logins", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=latin1", force: :cascade do |t|
114 t.integer "user_id"
114 t.integer "user_id"
115 t.string "ip_address"
115 t.string "ip_address"
116 t.datetime "created_at", null: false
116 t.datetime "created_at", null: false
117 t.datetime "updated_at", null: false
117 t.datetime "updated_at", null: false
118 + t.index ["user_id"], name: "index_logins_on_user_id"
118 end
119 end
119
120
120 create_table "messages", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
121 create_table "messages", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
121 t.integer "sender_id"
122 t.integer "sender_id"
122 t.integer "receiver_id"
123 t.integer "receiver_id"
123 t.integer "replying_message_id"
124 t.integer "replying_message_id"
124 t.text "body", limit: 16777215
125 t.text "body", limit: 16777215
125 t.boolean "replied"
126 t.boolean "replied"
126 t.datetime "created_at", null: false
127 t.datetime "created_at", null: false
127 t.datetime "updated_at", null: false
128 t.datetime "updated_at", null: false
128 end
129 end
129
130
130 create_table "problems", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
131 create_table "problems", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
131 t.string "name", limit: 100
132 t.string "name", limit: 100
132 t.string "full_name"
133 t.string "full_name"
133 t.integer "full_score"
134 t.integer "full_score"
134 t.date "date_added"
135 t.date "date_added"
135 t.boolean "available"
136 t.boolean "available"
136 t.string "url"
137 t.string "url"
137 t.integer "description_id"
138 t.integer "description_id"
138 t.boolean "test_allowed"
139 t.boolean "test_allowed"
139 t.boolean "output_only"
140 t.boolean "output_only"
140 t.string "description_filename"
141 t.string "description_filename"
141 t.boolean "view_testcase"
142 t.boolean "view_testcase"
142 end
143 end
143
144
144 create_table "problems_tags", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=latin1", force: :cascade do |t|
145 create_table "problems_tags", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=latin1", force: :cascade do |t|
145 t.integer "problem_id"
146 t.integer "problem_id"
146 t.integer "tag_id"
147 t.integer "tag_id"
147 t.index ["problem_id", "tag_id"], name: "index_problems_tags_on_problem_id_and_tag_id", unique: true
148 t.index ["problem_id", "tag_id"], name: "index_problems_tags_on_problem_id_and_tag_id", unique: true
148 t.index ["problem_id"], name: "index_problems_tags_on_problem_id"
149 t.index ["problem_id"], name: "index_problems_tags_on_problem_id"
149 t.index ["tag_id"], name: "index_problems_tags_on_tag_id"
150 t.index ["tag_id"], name: "index_problems_tags_on_tag_id"
150 end
151 end
151
152
152 create_table "rights", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
153 create_table "rights", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
153 t.string "name"
154 t.string "name"
154 t.string "controller"
155 t.string "controller"
155 t.string "action"
156 t.string "action"
156 end
157 end
157
158
158 create_table "rights_roles", id: false, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
159 create_table "rights_roles", id: false, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
159 t.integer "right_id"
160 t.integer "right_id"
160 t.integer "role_id"
161 t.integer "role_id"
161 t.index ["role_id"], name: "index_rights_roles_on_role_id"
162 t.index ["role_id"], name: "index_rights_roles_on_role_id"
162 end
163 end
163
164
164 create_table "roles", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
165 create_table "roles", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
165 t.string "name"
166 t.string "name"
deleted file
You need to be logged in to leave comments. Login now