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,522 +1,564
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
56
58
57 #set up range from param
59 #set up range from param
58 @since_id = params.fetch(:from_id, 0).to_i
60 @since_id = params.fetch(:from_id, 0).to_i
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 -
124 - User.all.each do |user|
125 - @logins << { id: user.id,
126 - login: user.login,
127 - full_name: user.full_name,
128 - count: Login.where("user_id = ? AND created_at >= ? AND created_at <= ?",
129 - user.id,@since_time,@until_time)
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
171
141 - }
172 + @logins = Login.includes(:user).where("logins.created_at >= ? AND logins.created_at <= ?",@since_time, @until_time)
173 + case params[:users]
174 + when 'enabled'
175 + @logins = @logins.where(users: {enabled: true})
176 + when 'group'
177 + @logins = @logins.joins(user: :groups).where(user: {groups: {id: params[:groups]}}) if params[:groups]
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]
223 @by_lang[lang.pretty_name][:runtime] = { avail: true, user_id: sub.user_id, value: sub.max_runtime, sub_id: sub.id }
265 @by_lang[lang.pretty_name][:runtime] = { avail: true, user_id: sub.user_id, value: sub.max_runtime, sub_id: sub.id }
224 end
266 end
225
267
226 if sub.peak_memory and sub.peak_memory < @by_lang[lang.pretty_name][:memory][:value]
268 if sub.peak_memory and sub.peak_memory < @by_lang[lang.pretty_name][:memory][:value]
227 @by_lang[lang.pretty_name][:memory] = { avail: true, user_id: sub.user_id, value: sub.peak_memory, sub_id: sub.id }
269 @by_lang[lang.pretty_name][:memory] = { avail: true, user_id: sub.user_id, value: sub.peak_memory, sub_id: sub.id }
228 end
270 end
229
271
230 if sub.submitted_at and sub.submitted_at < @by_lang[lang.pretty_name][:first][:value] and sub.user and
272 if sub.submitted_at and sub.submitted_at < @by_lang[lang.pretty_name][:first][:value] and sub.user and
231 !sub.user.admin?
273 !sub.user.admin?
232 @by_lang[lang.pretty_name][:first] = { avail: true, user_id: sub.user_id, value: sub.submitted_at, sub_id: sub.id }
274 @by_lang[lang.pretty_name][:first] = { avail: true, user_id: sub.user_id, value: sub.submitted_at, sub_id: sub.id }
233 end
275 end
234
276
235 if @by_lang[lang.pretty_name][:length][:value] > sub.effective_code_length
277 if @by_lang[lang.pretty_name][:length][:value] > sub.effective_code_length
236 @by_lang[lang.pretty_name][:length] = { avail: true, user_id: sub.user_id, value: sub.effective_code_length, sub_id: sub.id }
278 @by_lang[lang.pretty_name][:length] = { avail: true, user_id: sub.user_id, value: sub.effective_code_length, sub_id: sub.id }
237 end
279 end
238 end
280 end
239
281
240 #process user_id
282 #process user_id
241 @by_lang.each do |lang,prop|
283 @by_lang.each do |lang,prop|
242 prop.each do |k,v|
284 prop.each do |k,v|
243 v[:user] = User.exists?(v[:user_id]) ? User.find(v[:user_id]).full_name : "(NULL)"
285 v[:user] = User.exists?(v[:user_id]) ? User.find(v[:user_id]).full_name : "(NULL)"
244 end
286 end
245 end
287 end
246
288
247 #sum into best
289 #sum into best
248 if @by_lang and @by_lang.first
290 if @by_lang and @by_lang.first
249 @best = @by_lang.first[1].clone
291 @best = @by_lang.first[1].clone
250 @by_lang.each do |lang,prop|
292 @by_lang.each do |lang,prop|
251 if @best[:runtime][:value] >= prop[:runtime][:value]
293 if @best[:runtime][:value] >= prop[:runtime][:value]
252 @best[:runtime] = prop[:runtime]
294 @best[:runtime] = prop[:runtime]
253 @best[:runtime][:lang] = lang
295 @best[:runtime][:lang] = lang
254 end
296 end
255 if @best[:memory][:value] >= prop[:memory][:value]
297 if @best[:memory][:value] >= prop[:memory][:value]
256 @best[:memory] = prop[:memory]
298 @best[:memory] = prop[:memory]
257 @best[:memory][:lang] = lang
299 @best[:memory][:lang] = lang
258 end
300 end
259 if @best[:length][:value] >= prop[:length][:value]
301 if @best[:length][:value] >= prop[:length][:value]
260 @best[:length] = prop[:length]
302 @best[:length] = prop[:length]
261 @best[:length][:lang] = lang
303 @best[:length][:lang] = lang
262 end
304 end
263 if @best[:first][:value] >= prop[:first][:value]
305 if @best[:first][:value] >= prop[:first][:value]
264 @best[:first] = prop[:first]
306 @best[:first] = prop[:first]
265 @best[:first][:lang] = lang
307 @best[:first][:lang] = lang
266 end
308 end
267 end
309 end
268 end
310 end
269
311
270 @histogram[:summary][:max] = [@histogram[:data].max,1].max
312 @histogram[:summary][:max] = [@histogram[:data].max,1].max
271 @summary[:attempt] = user.count
313 @summary[:attempt] = user.count
272 user.each_value { |v| @summary[:solve] += 1 if v == 1 }
314 user.each_value { |v| @summary[:solve] += 1 if v == 1 }
273 end
315 end
274
316
275 def stuck #report struggling user,problem
317 def stuck #report struggling user,problem
276 # init
318 # init
277 user,problem = nil
319 user,problem = nil
278 solve = true
320 solve = true
279 tries = 0
321 tries = 0
280 @struggle = Array.new
322 @struggle = Array.new
281 record = {}
323 record = {}
282 Submission.includes(:problem,:user).order(:problem_id,:user_id).find_each do |sub|
324 Submission.includes(:problem,:user).order(:problem_id,:user_id).find_each do |sub|
283 next unless sub.problem and sub.user
325 next unless sub.problem and sub.user
284 if user != sub.user_id or problem != sub.problem_id
326 if user != sub.user_id or problem != sub.problem_id
285 @struggle << { user: record[:user], problem: record[:problem], tries: tries } unless solve
327 @struggle << { user: record[:user], problem: record[:problem], tries: tries } unless solve
286 record = {user: sub.user, problem: sub.problem}
328 record = {user: sub.user, problem: sub.problem}
287 user,problem = sub.user_id, sub.problem_id
329 user,problem = sub.user_id, sub.problem_id
288 solve = false
330 solve = false
289 tries = 0
331 tries = 0
290 end
332 end
291 if sub.points >= sub.problem.full_score
333 if sub.points >= sub.problem.full_score
292 solve = true
334 solve = true
293 else
335 else
294 tries += 1
336 tries += 1
295 end
337 end
296 end
338 end
297 @struggle.sort!{|a,b| b[:tries] <=> a[:tries] }
339 @struggle.sort!{|a,b| b[:tries] <=> a[:tries] }
298 @struggle = @struggle[0..50]
340 @struggle = @struggle[0..50]
299 end
341 end
300
342
301
343
302 def multiple_login
344 def multiple_login
303 #user with multiple IP
345 #user with multiple IP
304 raw = Submission.joins(:user).joins(:problem).where("problems.available != 0").group("login,ip_address").order(:login)
346 raw = Submission.joins(:user).joins(:problem).where("problems.available != 0").group("login,ip_address").order(:login)
305 last,count = 0,0
347 last,count = 0,0
306 first = 0
348 first = 0
307 @users = []
349 @users = []
308 raw.each do |r|
350 raw.each do |r|
309 if last != r.user.login
351 if last != r.user.login
310 count = 1
352 count = 1
311 last = r.user.login
353 last = r.user.login
312 first = r
354 first = r
313 else
355 else
314 @users << first if count == 1
356 @users << first if count == 1
315 @users << r
357 @users << r
316 count += 1
358 count += 1
317 end
359 end
318 end
360 end
319
361
320 #IP with multiple user
362 #IP with multiple user
321 raw = Submission.joins(:user).joins(:problem).where("problems.available != 0").group("login,ip_address").order(:ip_address)
363 raw = Submission.joins(:user).joins(:problem).where("problems.available != 0").group("login,ip_address").order(:ip_address)
322 last,count = 0,0
364 last,count = 0,0
323 first = 0
365 first = 0
324 @ip = []
366 @ip = []
325 raw.each do |r|
367 raw.each do |r|
326 if last != r.ip_address
368 if last != r.ip_address
327 count = 1
369 count = 1
328 last = r.ip_address
370 last = r.ip_address
329 first = r
371 first = r
330 else
372 else
331 @ip << first if count == 1
373 @ip << first if count == 1
332 @ip << r
374 @ip << r
333 count += 1
375 count += 1
334 end
376 end
335 end
377 end
336 end
378 end
337
379
338 def cheat_report
380 def cheat_report
339 date_and_time = '%Y-%m-%d %H:%M'
381 date_and_time = '%Y-%m-%d %H:%M'
340 begin
382 begin
341 md = params[:since_datetime].match(/(\d+)-(\d+)-(\d+) (\d+):(\d+)/)
383 md = params[:since_datetime].match(/(\d+)-(\d+)-(\d+) (\d+):(\d+)/)
342 @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)
384 @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)
343 rescue
385 rescue
344 @since_time = Time.zone.now.ago( 90.minutes)
386 @since_time = Time.zone.now.ago( 90.minutes)
345 end
387 end
346 begin
388 begin
347 md = params[:until_datetime].match(/(\d+)-(\d+)-(\d+) (\d+):(\d+)/)
389 md = params[:until_datetime].match(/(\d+)-(\d+)-(\d+) (\d+):(\d+)/)
348 @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)
390 @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)
349 rescue
391 rescue
350 @until_time = Time.zone.now
392 @until_time = Time.zone.now
351 end
393 end
352
394
353 #multi login
395 #multi login
354 @ml = Login.joins(:user).where("logins.created_at >= ? and logins.created_at <= ?",@since_time,@until_time).select('users.login,count(distinct ip_address) as count,users.full_name').group("users.id").having("count > 1")
396 @ml = Login.joins(:user).where("logins.created_at >= ? and logins.created_at <= ?",@since_time,@until_time).select('users.login,count(distinct ip_address) as count,users.full_name').group("users.id").having("count > 1")
355
397
356 st = <<-SQL
398 st = <<-SQL
357 SELECT l2.*
399 SELECT l2.*
358 FROM logins l2 INNER JOIN
400 FROM logins l2 INNER JOIN
359 (SELECT u.id,COUNT(DISTINCT ip_address) as count,u.login,u.full_name
401 (SELECT u.id,COUNT(DISTINCT ip_address) as count,u.login,u.full_name
360 FROM logins l
402 FROM logins l
361 INNER JOIN users u ON l.user_id = u.id
403 INNER JOIN users u ON l.user_id = u.id
362 WHERE l.created_at >= '#{@since_time.in_time_zone("UTC")}' and l.created_at <= '#{@until_time.in_time_zone("UTC")}'
404 WHERE l.created_at >= '#{@since_time.in_time_zone("UTC")}' and l.created_at <= '#{@until_time.in_time_zone("UTC")}'
363 GROUP BY u.id
405 GROUP BY u.id
364 HAVING count > 1
406 HAVING count > 1
365 ) ml ON l2.user_id = ml.id
407 ) ml ON l2.user_id = ml.id
366 WHERE l2.created_at >= '#{@since_time.in_time_zone("UTC")}' and l2.created_at <= '#{@until_time.in_time_zone("UTC")}'
408 WHERE l2.created_at >= '#{@since_time.in_time_zone("UTC")}' and l2.created_at <= '#{@until_time.in_time_zone("UTC")}'
367 UNION
409 UNION
368 SELECT l2.*
410 SELECT l2.*
369 FROM logins l2 INNER JOIN
411 FROM logins l2 INNER JOIN
370 (SELECT l.ip_address,COUNT(DISTINCT u.id) as count
412 (SELECT l.ip_address,COUNT(DISTINCT u.id) as count
371 FROM logins l
413 FROM logins l
372 INNER JOIN users u ON l.user_id = u.id
414 INNER JOIN users u ON l.user_id = u.id
373 WHERE l.created_at >= '#{@since_time.in_time_zone("UTC")}' and l.created_at <= '#{@until_time.in_time_zone("UTC")}'
415 WHERE l.created_at >= '#{@since_time.in_time_zone("UTC")}' and l.created_at <= '#{@until_time.in_time_zone("UTC")}'
374 GROUP BY l.ip_address
416 GROUP BY l.ip_address
375 HAVING count > 1
417 HAVING count > 1
376 ) ml on ml.ip_address = l2.ip_address
418 ) ml on ml.ip_address = l2.ip_address
377 INNER JOIN users u ON l2.user_id = u.id
419 INNER JOIN users u ON l2.user_id = u.id
378 WHERE l2.created_at >= '#{@since_time.in_time_zone("UTC")}' and l2.created_at <= '#{@until_time.in_time_zone("UTC")}'
420 WHERE l2.created_at >= '#{@since_time.in_time_zone("UTC")}' and l2.created_at <= '#{@until_time.in_time_zone("UTC")}'
379 ORDER BY ip_address,created_at
421 ORDER BY ip_address,created_at
380 SQL
422 SQL
381 @mld = Login.find_by_sql(st)
423 @mld = Login.find_by_sql(st)
382
424
383 st = <<-SQL
425 st = <<-SQL
384 SELECT s.id,s.user_id,s.ip_address,s.submitted_at,s.problem_id
426 SELECT s.id,s.user_id,s.ip_address,s.submitted_at,s.problem_id
385 FROM submissions s INNER JOIN
427 FROM submissions s INNER JOIN
386 (SELECT u.id,COUNT(DISTINCT ip_address) as count,u.login,u.full_name
428 (SELECT u.id,COUNT(DISTINCT ip_address) as count,u.login,u.full_name
387 FROM logins l
429 FROM logins l
388 INNER JOIN users u ON l.user_id = u.id
430 INNER JOIN users u ON l.user_id = u.id
389 WHERE l.created_at >= ? and l.created_at <= ?
431 WHERE l.created_at >= ? and l.created_at <= ?
390 GROUP BY u.id
432 GROUP BY u.id
391 HAVING count > 1
433 HAVING count > 1
392 ) ml ON s.user_id = ml.id
434 ) ml ON s.user_id = ml.id
393 WHERE s.submitted_at >= ? and s.submitted_at <= ?
435 WHERE s.submitted_at >= ? and s.submitted_at <= ?
394 UNION
436 UNION
395 SELECT s.id,s.user_id,s.ip_address,s.submitted_at,s.problem_id
437 SELECT s.id,s.user_id,s.ip_address,s.submitted_at,s.problem_id
396 FROM submissions s INNER JOIN
438 FROM submissions s INNER JOIN
397 (SELECT l.ip_address,COUNT(DISTINCT u.id) as count
439 (SELECT l.ip_address,COUNT(DISTINCT u.id) as count
398 FROM logins l
440 FROM logins l
399 INNER JOIN users u ON l.user_id = u.id
441 INNER JOIN users u ON l.user_id = u.id
400 WHERE l.created_at >= ? and l.created_at <= ?
442 WHERE l.created_at >= ? and l.created_at <= ?
401 GROUP BY l.ip_address
443 GROUP BY l.ip_address
402 HAVING count > 1
444 HAVING count > 1
403 ) ml on ml.ip_address = s.ip_address
445 ) ml on ml.ip_address = s.ip_address
404 WHERE s.submitted_at >= ? and s.submitted_at <= ?
446 WHERE s.submitted_at >= ? and s.submitted_at <= ?
405 ORDER BY ip_address,submitted_at
447 ORDER BY ip_address,submitted_at
406 SQL
448 SQL
407 @subs = Submission.joins(:problem).find_by_sql([st,@since_time,@until_time,
449 @subs = Submission.joins(:problem).find_by_sql([st,@since_time,@until_time,
408 @since_time,@until_time,
450 @since_time,@until_time,
409 @since_time,@until_time,
451 @since_time,@until_time,
410 @since_time,@until_time])
452 @since_time,@until_time])
411
453
412 end
454 end
413
455
414 def cheat_scruntinize
456 def cheat_scruntinize
415 #convert date & time
457 #convert date & time
416 date_and_time = '%Y-%m-%d %H:%M'
458 date_and_time = '%Y-%m-%d %H:%M'
417 begin
459 begin
418 md = params[:since_datetime].match(/(\d+)-(\d+)-(\d+) (\d+):(\d+)/)
460 md = params[:since_datetime].match(/(\d+)-(\d+)-(\d+) (\d+):(\d+)/)
419 @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)
461 @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)
420 rescue
462 rescue
421 @since_time = Time.zone.now.ago( 90.minutes)
463 @since_time = Time.zone.now.ago( 90.minutes)
422 end
464 end
423 begin
465 begin
424 md = params[:until_datetime].match(/(\d+)-(\d+)-(\d+) (\d+):(\d+)/)
466 md = params[:until_datetime].match(/(\d+)-(\d+)-(\d+) (\d+):(\d+)/)
425 @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)
467 @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)
426 rescue
468 rescue
427 @until_time = Time.zone.now
469 @until_time = Time.zone.now
428 end
470 end
429
471
430 #convert sid
472 #convert sid
431 @sid = params[:SID].split(/[,\s]/) if params[:SID]
473 @sid = params[:SID].split(/[,\s]/) if params[:SID]
432 unless @sid and @sid.size > 0
474 unless @sid and @sid.size > 0
433 return
475 return
434 redirect_to actoin: :cheat_scruntinize
476 redirect_to actoin: :cheat_scruntinize
435 flash[:notice] = 'Please enter at least 1 student id'
477 flash[:notice] = 'Please enter at least 1 student id'
436 end
478 end
437 mark = Array.new(@sid.size,'?')
479 mark = Array.new(@sid.size,'?')
438 condition = "(u.login = " + mark.join(' OR u.login = ') + ')'
480 condition = "(u.login = " + mark.join(' OR u.login = ') + ')'
439
481
440 @st = <<-SQL
482 @st = <<-SQL
441 SELECT l.created_at as submitted_at ,-1 as id,u.login,u.full_name,l.ip_address,"" as problem_id,"" as points,l.user_id
483 SELECT l.created_at as submitted_at ,-1 as id,u.login,u.full_name,l.ip_address,"" as problem_id,"" as points,l.user_id
442 FROM logins l INNER JOIN users u on l.user_id = u.id
484 FROM logins l INNER JOIN users u on l.user_id = u.id
443 WHERE l.created_at >= ? AND l.created_at <= ? AND #{condition}
485 WHERE l.created_at >= ? AND l.created_at <= ? AND #{condition}
444 UNION
486 UNION
445 SELECT s.submitted_at,s.id,u.login,u.full_name,s.ip_address,s.problem_id,s.points,s.user_id
487 SELECT s.submitted_at,s.id,u.login,u.full_name,s.ip_address,s.problem_id,s.points,s.user_id
446 FROM submissions s INNER JOIN users u ON s.user_id = u.id
488 FROM submissions s INNER JOIN users u ON s.user_id = u.id
447 WHERE s.submitted_at >= ? AND s.submitted_at <= ? AND #{condition}
489 WHERE s.submitted_at >= ? AND s.submitted_at <= ? AND #{condition}
448 ORDER BY submitted_at
490 ORDER BY submitted_at
449 SQL
491 SQL
450
492
451 p = [@st,@since_time,@until_time] + @sid + [@since_time,@until_time] + @sid
493 p = [@st,@since_time,@until_time] + @sid + [@since_time,@until_time] + @sid
452 @logs = Submission.joins(:problem).find_by_sql(p)
494 @logs = Submission.joins(:problem).find_by_sql(p)
453
495
454
496
455
497
456
498
457
499
458 end
500 end
459
501
460 protected
502 protected
461
503
462 def calculate_max_score(problems, users,since_id,until_id, get_last_score = false)
504 def calculate_max_score(problems, users,since_id,until_id, get_last_score = false)
463 #scorearray[i] = user #i's user stat where i is the index (not id)
505 #scorearray[i] = user #i's user stat where i is the index (not id)
464 scorearray = Array.new
506 scorearray = Array.new
465 users.each do |u|
507 users.each do |u|
466 ustat = Array.new
508 ustat = Array.new
467 ustat[0] = u
509 ustat[0] = u
468 problems.each do |p|
510 problems.each do |p|
469 unless get_last_score
511 unless get_last_score
470 #get max score
512 #get max score
471 max_points = 0
513 max_points = 0
472 Submission.find_in_range_by_user_and_problem(u.id,p.id,since_id,until_id).each do |sub|
514 Submission.find_in_range_by_user_and_problem(u.id,p.id,since_id,until_id).each do |sub|
473 max_points = sub.points if sub and sub.points and (sub.points > max_points)
515 max_points = sub.points if sub and sub.points and (sub.points > max_points)
474 end
516 end
475 ustat << [(max_points.to_f*100/p.full_score).round, (max_points>=p.full_score)]
517 ustat << [(max_points.to_f*100/p.full_score).round, (max_points>=p.full_score)]
476 else
518 else
477 #get latest score
519 #get latest score
478 sub = Submission.find_last_by_user_and_problem(u.id,p.id)
520 sub = Submission.find_last_by_user_and_problem(u.id,p.id)
479 if (sub!=nil) and (sub.points!=nil) and p and p.full_score
521 if (sub!=nil) and (sub.points!=nil) and p and p.full_score
480 ustat << [(sub.points.to_f*100/p.full_score).round, (sub.points>=p.full_score)]
522 ustat << [(sub.points.to_f*100/p.full_score).round, (sub.points>=p.full_score)]
481 else
523 else
482 ustat << [0,false]
524 ustat << [0,false]
483 end
525 end
484 end
526 end
485 end
527 end
486 scorearray << ustat
528 scorearray << ustat
487 end
529 end
488 return scorearray
530 return scorearray
489 end
531 end
490
532
491 def gen_csv_from_scorearray(scorearray,problem)
533 def gen_csv_from_scorearray(scorearray,problem)
492 CSV.generate do |csv|
534 CSV.generate do |csv|
493 #add header
535 #add header
494 header = ['User','Name', 'Activated?', 'Logged in', 'Contest']
536 header = ['User','Name', 'Activated?', 'Logged in', 'Contest']
495 problem.each { |p| header << p.name }
537 problem.each { |p| header << p.name }
496 header += ['Total','Passed']
538 header += ['Total','Passed']
497 csv << header
539 csv << header
498 #add data
540 #add data
499 scorearray.each do |sc|
541 scorearray.each do |sc|
500 total = num_passed = 0
542 total = num_passed = 0
501 row = Array.new
543 row = Array.new
502 sc.each_index do |i|
544 sc.each_index do |i|
503 if i == 0
545 if i == 0
504 row << sc[i].login
546 row << sc[i].login
505 row << sc[i].full_name
547 row << sc[i].full_name
506 row << sc[i].activated
548 row << sc[i].activated
507 row << (sc[i].try(:contest_stat).try(:started_at)!=nil ? 'yes' : 'no')
549 row << (sc[i].try(:contest_stat).try(:started_at)!=nil ? 'yes' : 'no')
508 row << sc[i].contests.collect {|c| c.name}.join(', ')
550 row << sc[i].contests.collect {|c| c.name}.join(', ')
509 else
551 else
510 row << sc[i][0]
552 row << sc[i][0]
511 total += sc[i][0]
553 total += sc[i][0]
512 num_passed += 1 if sc[i][1]
554 num_passed += 1 if sc[i][1]
513 end
555 end
514 end
556 end
515 row << total
557 row << total
516 row << num_passed
558 row << num_passed
517 csv << row
559 csv << row
518 end
560 end
519 end
561 end
520 end
562 end
521
563
522 end
564 end
@@ -1,401 +1,394
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 = []
66
59
67 lines.split("\n").each do |line|
60 lines.split("\n").each do |line|
68 items = line.chomp.split(',')
61 items = line.chomp.split(',')
69 if items.length>=2
62 if items.length>=2
70 login = items[0]
63 login = items[0]
71 full_name = items[1]
64 full_name = items[1]
72 remark =''
65 remark =''
73 user_alias = ''
66 user_alias = ''
74
67
75 added_random_password = false
68 added_random_password = false
76 if items.length >= 3 and items[2].chomp(" ").length > 0;
69 if items.length >= 3 and items[2].chomp(" ").length > 0;
77 password = items[2].chomp(" ")
70 password = items[2].chomp(" ")
78 else
71 else
79 password = random_password
72 password = random_password
80 added_random_password=true;
73 added_random_password=true;
81 end
74 end
82
75
83 if items.length>= 4 and items[3].chomp(" ").length > 0;
76 if items.length>= 4 and items[3].chomp(" ").length > 0;
84 user_alias = items[3].chomp(" ")
77 user_alias = items[3].chomp(" ")
85 else
78 else
86 user_alias = login
79 user_alias = login
87 end
80 end
88
81
89 if items.length>=5
82 if items.length>=5
90 remark = items[4].strip;
83 remark = items[4].strip;
91 end
84 end
92
85
93 user = User.find_by_login(login)
86 user = User.find_by_login(login)
94 if (user)
87 if (user)
95 user.full_name = full_name
88 user.full_name = full_name
96 user.password = password
89 user.password = password
97 user.remark = remark
90 user.remark = remark
98 else
91 else
99 user = User.new({:login => login,
92 user = User.new({:login => login,
100 :full_name => full_name,
93 :full_name => full_name,
101 :password => password,
94 :password => password,
102 :password_confirmation => password,
95 :password_confirmation => password,
103 :alias => user_alias,
96 :alias => user_alias,
104 :remark => remark})
97 :remark => remark})
105 end
98 end
106 user.activated = true
99 user.activated = true
107
100
108 if user.save
101 if user.save
109 if added_random_password
102 if added_random_password
110 note << "'#{login}' (+)"
103 note << "'#{login}' (+)"
111 else
104 else
112 note << login
105 note << login
113 end
106 end
114 ok_user << user
107 ok_user << user
115 else
108 else
116 error_note << "'#{login}'"
109 error_note << "'#{login}'"
117 error_msg = user.errors.full_messages.to_sentence unless error_msg
110 error_msg = user.errors.full_messages.to_sentence unless error_msg
118 end
111 end
119
112
120 end
113 end
121 end
114 end
122
115
123 #add to group
116 #add to group
124 if params[:add_to_group]
117 if params[:add_to_group]
125 group = Group.where(id: params[:group_id]).first
118 group = Group.where(id: params[:group_id]).first
126 if group
119 if group
127 group.users << ok_user
120 group.users << ok_user
128 end
121 end
129 end
122 end
130
123
131 # show flash
124 # show flash
132 if note.size > 0
125 if note.size > 0
133 flash[:success] = 'User(s) ' + note.join(', ') +
126 flash[:success] = 'User(s) ' + note.join(', ') +
134 ' were successfully created. ' +
127 ' were successfully created. ' +
135 '( (+) - created with random passwords.)'
128 '( (+) - created with random passwords.)'
136 end
129 end
137 if error_note.size > 0
130 if error_note.size > 0
138 flash[:error] = "Following user(s) failed to be created: " + error_note.join(', ') + ". The error of the first failed one are: " + error_msg;
131 flash[:error] = "Following user(s) failed to be created: " + error_note.join(', ') + ". The error of the first failed one are: " + error_msg;
139 end
132 end
140 redirect_to :action => 'index'
133 redirect_to :action => 'index'
141 end
134 end
142
135
143 def edit
136 def edit
144 @user = User.find(params[:id])
137 @user = User.find(params[:id])
145 end
138 end
146
139
147 def update
140 def update
148 @user = User.find(params[:id])
141 @user = User.find(params[:id])
149 if @user.update_attributes(user_params)
142 if @user.update_attributes(user_params)
150 flash[:notice] = 'User was successfully updated.'
143 flash[:notice] = 'User was successfully updated.'
151 redirect_to :action => 'show', :id => @user
144 redirect_to :action => 'show', :id => @user
152 else
145 else
153 render :action => 'edit'
146 render :action => 'edit'
154 end
147 end
155 end
148 end
156
149
157 def destroy
150 def destroy
158 User.find(params[:id]).destroy
151 User.find(params[:id]).destroy
159 redirect_to :action => 'index'
152 redirect_to :action => 'index'
160 end
153 end
161
154
162 def user_stat
155 def user_stat
163 if params[:commit] == 'download csv'
156 if params[:commit] == 'download csv'
164 @problems = Problem.all
157 @problems = Problem.all
165 else
158 else
166 @problems = Problem.available_problems
159 @problems = Problem.available_problems
167 end
160 end
168 @users = User.includes(:contests, :contest_stat).where(enabled: true)
161 @users = User.includes(:contests, :contest_stat).where(enabled: true)
169 @scorearray = Array.new
162 @scorearray = Array.new
170 @users.each do |u|
163 @users.each do |u|
171 ustat = Array.new
164 ustat = Array.new
172 ustat[0] = u
165 ustat[0] = u
173 @problems.each do |p|
166 @problems.each do |p|
174 sub = Submission.find_last_by_user_and_problem(u.id,p.id)
167 sub = Submission.find_last_by_user_and_problem(u.id,p.id)
175 if (sub!=nil) and (sub.points!=nil) and p and p.full_score
168 if (sub!=nil) and (sub.points!=nil) and p and p.full_score
176 ustat << [(sub.points.to_f*100/p.full_score).round, (sub.points>=p.full_score)]
169 ustat << [(sub.points.to_f*100/p.full_score).round, (sub.points>=p.full_score)]
177 else
170 else
178 ustat << [0,false]
171 ustat << [0,false]
179 end
172 end
180 end
173 end
181 @scorearray << ustat
174 @scorearray << ustat
182 end
175 end
183 if params[:commit] == 'download csv' then
176 if params[:commit] == 'download csv' then
184 csv = gen_csv_from_scorearray(@scorearray,@problems)
177 csv = gen_csv_from_scorearray(@scorearray,@problems)
185 send_data csv, filename: 'last_score.csv'
178 send_data csv, filename: 'last_score.csv'
186 else
179 else
187 render template: 'user_admin/user_stat'
180 render template: 'user_admin/user_stat'
188 end
181 end
189 end
182 end
190
183
191 def user_stat_max
184 def user_stat_max
192 if params[:commit] == 'download csv'
185 if params[:commit] == 'download csv'
193 @problems = Problem.all
186 @problems = Problem.all
194 else
187 else
195 @problems = Problem.available_problems
188 @problems = Problem.available_problems
196 end
189 end
197 @users = User.includes(:contests).includes(:contest_stat).all
190 @users = User.includes(:contests).includes(:contest_stat).all
198 @scorearray = Array.new
191 @scorearray = Array.new
199 #set up range from param
192 #set up range from param
200 since_id = params.fetch(:since_id, 0).to_i
193 since_id = params.fetch(:since_id, 0).to_i
201 until_id = params.fetch(:until_id, 0).to_i
194 until_id = params.fetch(:until_id, 0).to_i
202 @users.each do |u|
195 @users.each do |u|
203 ustat = Array.new
196 ustat = Array.new
204 ustat[0] = u
197 ustat[0] = u
205 @problems.each do |p|
198 @problems.each do |p|
206 max_points = 0
199 max_points = 0
207 Submission.find_in_range_by_user_and_problem(u.id,p.id,since_id,until_id).each do |sub|
200 Submission.find_in_range_by_user_and_problem(u.id,p.id,since_id,until_id).each do |sub|
208 max_points = sub.points if sub and sub.points and (sub.points > max_points)
201 max_points = sub.points if sub and sub.points and (sub.points > max_points)
209 end
202 end
210 ustat << [(max_points.to_f*100/p.full_score).round, (max_points>=p.full_score)]
203 ustat << [(max_points.to_f*100/p.full_score).round, (max_points>=p.full_score)]
211 end
204 end
212 @scorearray << ustat
205 @scorearray << ustat
213 end
206 end
214
207
215 if params[:commit] == 'download csv' then
208 if params[:commit] == 'download csv' then
216 csv = gen_csv_from_scorearray(@scorearray,@problems)
209 csv = gen_csv_from_scorearray(@scorearray,@problems)
217 send_data csv, filename: 'max_score.csv'
210 send_data csv, filename: 'max_score.csv'
218 else
211 else
219 render template: 'user_admin/user_stat'
212 render template: 'user_admin/user_stat'
220 end
213 end
221 end
214 end
222
215
223 def import
216 def import
224 if params[:file]==''
217 if params[:file]==''
225 flash[:notice] = 'Error importing no file'
218 flash[:notice] = 'Error importing no file'
226 redirect_to :action => 'index' and return
219 redirect_to :action => 'index' and return
227 end
220 end
228 import_from_file(params[:file])
221 import_from_file(params[:file])
229 end
222 end
230
223
231 def random_all_passwords
224 def random_all_passwords
232 users = User.all
225 users = User.all
233 @prefix = params[:prefix] || ''
226 @prefix = params[:prefix] || ''
234 @non_admin_users = User.find_non_admin_with_prefix(@prefix)
227 @non_admin_users = User.find_non_admin_with_prefix(@prefix)
235 @changed = false
228 @changed = false
236 if params[:commit] == 'Go ahead'
229 if params[:commit] == 'Go ahead'
237 @non_admin_users.each do |user|
230 @non_admin_users.each do |user|
238 password = random_password
231 password = random_password
239 user.password = password
232 user.password = password
240 user.password_confirmation = password
233 user.password_confirmation = password
241 user.save
234 user.save
242 end
235 end
243 @changed = true
236 @changed = true
244 end
237 end
245 end
238 end
246
239
247 # contest management
240 # contest management
248
241
249 def contests
242 def contests
250 @contest, @users = find_contest_and_user_from_contest_id(params[:id])
243 @contest, @users = find_contest_and_user_from_contest_id(params[:id])
251 @contests = Contest.enabled
244 @contests = Contest.enabled
252 end
245 end
253
246
254 def assign_from_list
247 def assign_from_list
255 contest_id = params[:users_contest_id]
248 contest_id = params[:users_contest_id]
256 org_contest, users = find_contest_and_user_from_contest_id(contest_id)
249 org_contest, users = find_contest_and_user_from_contest_id(contest_id)
257 contest = Contest.find(params[:new_contest][:id])
250 contest = Contest.find(params[:new_contest][:id])
258 if !contest
251 if !contest
259 flash[:notice] = 'Error: no contest'
252 flash[:notice] = 'Error: no contest'
260 redirect_to :action => 'contests', :id =>contest_id
253 redirect_to :action => 'contests', :id =>contest_id
261 end
254 end
262
255
263 note = []
256 note = []
264 users.each do |u|
257 users.each do |u|
265 u.contests = [contest]
258 u.contests = [contest]
266 note << u.login
259 note << u.login
267 end
260 end
268 flash[:notice] = 'User(s) ' + note.join(', ') +
261 flash[:notice] = 'User(s) ' + note.join(', ') +
269 " were successfully reassigned to #{contest.title}."
262 " were successfully reassigned to #{contest.title}."
270 redirect_to :action => 'contests', :id =>contest.id
263 redirect_to :action => 'contests', :id =>contest.id
271 end
264 end
272
265
273 def add_to_contest
266 def add_to_contest
274 user = User.find(params[:id])
267 user = User.find(params[:id])
275 contest = Contest.find(params[:contest_id])
268 contest = Contest.find(params[:contest_id])
276 if user and contest
269 if user and contest
277 user.contests << contest
270 user.contests << contest
278 end
271 end
279 redirect_to :action => 'index'
272 redirect_to :action => 'index'
280 end
273 end
281
274
282 def remove_from_contest
275 def remove_from_contest
283 user = User.find(params[:id])
276 user = User.find(params[:id])
284 contest = Contest.find(params[:contest_id])
277 contest = Contest.find(params[:contest_id])
285 if user and contest
278 if user and contest
286 user.contests.delete(contest)
279 user.contests.delete(contest)
287 end
280 end
288 redirect_to :action => 'index'
281 redirect_to :action => 'index'
289 end
282 end
290
283
291 def contest_management
284 def contest_management
292 end
285 end
293
286
294 def manage_contest
287 def manage_contest
295 contest = Contest.find(params[:contest][:id])
288 contest = Contest.find(params[:contest][:id])
296 if !contest
289 if !contest
297 flash[:notice] = 'You did not choose the contest.'
290 flash[:notice] = 'You did not choose the contest.'
298 redirect_to :action => 'contest_management' and return
291 redirect_to :action => 'contest_management' and return
299 end
292 end
300
293
301 operation = params[:operation]
294 operation = params[:operation]
302
295
303 if not ['add','remove','assign'].include? operation
296 if not ['add','remove','assign'].include? operation
304 flash[:notice] = 'You did not choose the operation to perform.'
297 flash[:notice] = 'You did not choose the operation to perform.'
305 redirect_to :action => 'contest_management' and return
298 redirect_to :action => 'contest_management' and return
306 end
299 end
307
300
308 lines = params[:login_list]
301 lines = params[:login_list]
309 if !lines or lines.blank?
302 if !lines or lines.blank?
310 flash[:notice] = 'You entered an empty list.'
303 flash[:notice] = 'You entered an empty list.'
311 redirect_to :action => 'contest_management' and return
304 redirect_to :action => 'contest_management' and return
312 end
305 end
313
306
314 note = []
307 note = []
315 users = []
308 users = []
316 lines.split("\n").each do |line|
309 lines.split("\n").each do |line|
317 user = User.find_by_login(line.chomp)
310 user = User.find_by_login(line.chomp)
318 if user
311 if user
319 if operation=='add'
312 if operation=='add'
320 if ! user.contests.include? contest
313 if ! user.contests.include? contest
321 user.contests << contest
314 user.contests << contest
322 end
315 end
323 elsif operation=='remove'
316 elsif operation=='remove'
324 user.contests.delete(contest)
317 user.contests.delete(contest)
325 else
318 else
326 user.contests = [contest]
319 user.contests = [contest]
327 end
320 end
328
321
329 if params[:reset_timer]
322 if params[:reset_timer]
330 user.contest_stat.forced_logout = true
323 user.contest_stat.forced_logout = true
331 user.contest_stat.reset_timer_and_save
324 user.contest_stat.reset_timer_and_save
332 end
325 end
333
326
334 if params[:notification_emails]
327 if params[:notification_emails]
335 send_contest_update_notification_email(user, contest)
328 send_contest_update_notification_email(user, contest)
336 end
329 end
337
330
338 note << user.login
331 note << user.login
339 users << user
332 users << user
340 end
333 end
341 end
334 end
342
335
343 if params[:reset_timer]
336 if params[:reset_timer]
344 logout_users(users)
337 logout_users(users)
345 end
338 end
346
339
347 flash[:notice] = 'User(s) ' + note.join(', ') +
340 flash[:notice] = 'User(s) ' + note.join(', ') +
348 ' were successfully modified. '
341 ' were successfully modified. '
349 redirect_to :action => 'contest_management'
342 redirect_to :action => 'contest_management'
350 end
343 end
351
344
352 # admin management
345 # admin management
353
346
354 def admin
347 def admin
355 @admins = User.all.find_all {|user| user.admin? }
348 @admins = User.all.find_all {|user| user.admin? }
356 end
349 end
357
350
358 def grant_admin
351 def grant_admin
359 login = params[:login]
352 login = params[:login]
360 user = User.find_by_login(login)
353 user = User.find_by_login(login)
361 if user!=nil
354 if user!=nil
362 admin_role = Role.find_by_name('admin')
355 admin_role = Role.find_by_name('admin')
363 user.roles << admin_role
356 user.roles << admin_role
364 else
357 else
365 flash[:notice] = 'Unknown user'
358 flash[:notice] = 'Unknown user'
366 end
359 end
367 flash[:notice] = 'User added as admins'
360 flash[:notice] = 'User added as admins'
368 redirect_to :action => 'admin'
361 redirect_to :action => 'admin'
369 end
362 end
370
363
371 def revoke_admin
364 def revoke_admin
372 user = User.find(params[:id])
365 user = User.find(params[:id])
373 if user==nil
366 if user==nil
374 flash[:notice] = 'Unknown user'
367 flash[:notice] = 'Unknown user'
375 redirect_to :action => 'admin' and return
368 redirect_to :action => 'admin' and return
376 elsif user.login == 'root'
369 elsif user.login == 'root'
377 flash[:notice] = 'You cannot revoke admisnistrator permission from root.'
370 flash[:notice] = 'You cannot revoke admisnistrator permission from root.'
378 redirect_to :action => 'admin' and return
371 redirect_to :action => 'admin' and return
379 end
372 end
380
373
381 admin_role = Role.find_by_name('admin')
374 admin_role = Role.find_by_name('admin')
382 user.roles.delete(admin_role)
375 user.roles.delete(admin_role)
383 flash[:notice] = 'User permission revoked'
376 flash[:notice] = 'User permission revoked'
384 redirect_to :action => 'admin'
377 redirect_to :action => 'admin'
385 end
378 end
386
379
387 # mass mailing
380 # mass mailing
388
381
389 def mass_mailing
382 def mass_mailing
390 end
383 end
391
384
392 def bulk_mail
385 def bulk_mail
393 lines = params[:login_list]
386 lines = params[:login_list]
394 if !lines or lines.blank?
387 if !lines or lines.blank?
395 flash[:notice] = 'You entered an empty list.'
388 flash[:notice] = 'You entered an empty list.'
396 redirect_to :action => 'mass_mailing' and return
389 redirect_to :action => 'mass_mailing' and return
397 end
390 end
398
391
399 mail_subject = params[:subject]
392 mail_subject = params[:subject]
400 if !mail_subject or mail_subject.blank?
393 if !mail_subject or mail_subject.blank?
401 flash[:notice] = 'You entered an empty mail subject.'
394 flash[:notice] = 'You entered an empty mail subject.'
@@ -1,374 +1,376
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
73 end
75 end
74
76
75 def authenticated?(password)
77 def authenticated?(password)
76 if self.activated
78 if self.activated
77 hashed_password == User.encrypt(password,self.salt)
79 hashed_password == User.encrypt(password,self.salt)
78 else
80 else
79 false
81 false
80 end
82 end
81 end
83 end
82
84
83 def admin?
85 def admin?
84 self.roles.where(name: 'admin').count > 0
86 self.roles.where(name: 'admin').count > 0
85 end
87 end
86
88
87 def email_for_editing
89 def email_for_editing
88 if self.email==nil
90 if self.email==nil
89 "(unknown)"
91 "(unknown)"
90 elsif self.email==''
92 elsif self.email==''
91 "(blank)"
93 "(blank)"
92 else
94 else
93 self.email
95 self.email
94 end
96 end
95 end
97 end
96
98
97 def email_for_editing=(e)
99 def email_for_editing=(e)
98 self.email=e
100 self.email=e
99 end
101 end
100
102
101 def alias_for_editing
103 def alias_for_editing
102 if self.alias==nil
104 if self.alias==nil
103 "(unknown)"
105 "(unknown)"
104 elsif self.alias==''
106 elsif self.alias==''
105 "(blank)"
107 "(blank)"
106 else
108 else
107 self.alias
109 self.alias
108 end
110 end
109 end
111 end
110
112
111 def alias_for_editing=(e)
113 def alias_for_editing=(e)
112 self.alias=e
114 self.alias=e
113 end
115 end
114
116
115 def activation_key
117 def activation_key
116 if self.hashed_password==nil
118 if self.hashed_password==nil
117 encrypt_new_password
119 encrypt_new_password
118 end
120 end
119 Digest::SHA1.hexdigest(self.hashed_password)[0..7]
121 Digest::SHA1.hexdigest(self.hashed_password)[0..7]
120 end
122 end
121
123
122 def verify_activation_key(key)
124 def verify_activation_key(key)
123 key == activation_key
125 key == activation_key
124 end
126 end
125
127
126 def self.random_password(length=5)
128 def self.random_password(length=5)
127 chars = 'abcdefghjkmnopqrstuvwxyz'
129 chars = 'abcdefghjkmnopqrstuvwxyz'
128 password = ''
130 password = ''
129 length.times { password << chars[rand(chars.length - 1)] }
131 length.times { password << chars[rand(chars.length - 1)] }
130 password
132 password
131 end
133 end
132
134
133 def self.find_non_admin_with_prefix(prefix='')
135 def self.find_non_admin_with_prefix(prefix='')
134 users = User.all
136 users = User.all
135 return users.find_all { |u| !(u.admin?) and u.login.index(prefix)==0 }
137 return users.find_all { |u| !(u.admin?) and u.login.index(prefix)==0 }
136 end
138 end
137
139
138 # Contest information
140 # Contest information
139
141
140 def self.find_users_with_no_contest()
142 def self.find_users_with_no_contest()
141 users = User.all
143 users = User.all
142 return users.find_all { |u| u.contests.length == 0 }
144 return users.find_all { |u| u.contests.length == 0 }
143 end
145 end
144
146
145
147
146 def contest_time_left
148 def contest_time_left
147 if GraderConfiguration.contest_mode?
149 if GraderConfiguration.contest_mode?
148 return nil if site==nil
150 return nil if site==nil
149 return site.time_left
151 return site.time_left
150 elsif GraderConfiguration.indv_contest_mode?
152 elsif GraderConfiguration.indv_contest_mode?
151 time_limit = GraderConfiguration.contest_time_limit
153 time_limit = GraderConfiguration.contest_time_limit
152 if time_limit == nil
154 if time_limit == nil
153 return nil
155 return nil
154 end
156 end
155 if contest_stat==nil or contest_stat.started_at==nil
157 if contest_stat==nil or contest_stat.started_at==nil
156 return (Time.now.gmtime + time_limit) - Time.now.gmtime
158 return (Time.now.gmtime + time_limit) - Time.now.gmtime
157 else
159 else
158 finish_time = contest_stat.started_at + time_limit
160 finish_time = contest_stat.started_at + time_limit
159 current_time = Time.now.gmtime
161 current_time = Time.now.gmtime
160 if current_time > finish_time
162 if current_time > finish_time
161 return 0
163 return 0
162 else
164 else
163 return finish_time - current_time
165 return finish_time - current_time
164 end
166 end
165 end
167 end
166 else
168 else
167 return nil
169 return nil
168 end
170 end
169 end
171 end
170
172
171 def contest_finished?
173 def contest_finished?
172 if GraderConfiguration.contest_mode?
174 if GraderConfiguration.contest_mode?
173 return false if site==nil
175 return false if site==nil
174 return site.finished?
176 return site.finished?
175 elsif GraderConfiguration.indv_contest_mode?
177 elsif GraderConfiguration.indv_contest_mode?
176 return false if self.contest_stat==nil
178 return false if self.contest_stat==nil
177 return contest_time_left == 0
179 return contest_time_left == 0
178 else
180 else
179 return false
181 return false
180 end
182 end
181 end
183 end
182
184
183 def contest_started?
185 def contest_started?
184 if GraderConfiguration.indv_contest_mode?
186 if GraderConfiguration.indv_contest_mode?
185 stat = self.contest_stat
187 stat = self.contest_stat
186 return ((stat != nil) and (stat.started_at != nil))
188 return ((stat != nil) and (stat.started_at != nil))
187 elsif GraderConfiguration.contest_mode?
189 elsif GraderConfiguration.contest_mode?
188 return true if site==nil
190 return true if site==nil
189 return site.started
191 return site.started
190 else
192 else
191 return true
193 return true
192 end
194 end
193 end
195 end
194
196
195 def update_start_time
197 def update_start_time
196 stat = self.contest_stat
198 stat = self.contest_stat
197 if stat.nil? or stat.started_at.nil?
199 if stat.nil? or stat.started_at.nil?
198 stat ||= UserContestStat.new(:user => self)
200 stat ||= UserContestStat.new(:user => self)
199 stat.started_at = Time.now.gmtime
201 stat.started_at = Time.now.gmtime
200 stat.save
202 stat.save
201 end
203 end
202 end
204 end
203
205
204 def problem_in_user_contests?(problem)
206 def problem_in_user_contests?(problem)
205 problem_contests = problem.contests.all
207 problem_contests = problem.contests.all
206
208
207 if problem_contests.length == 0 # this is public contest
209 if problem_contests.length == 0 # this is public contest
208 return true
210 return true
209 end
211 end
210
212
211 contests.each do |contest|
213 contests.each do |contest|
212 if problem_contests.find {|c| c.id == contest.id }
214 if problem_contests.find {|c| c.id == contest.id }
213 return true
215 return true
214 end
216 end
215 end
217 end
216 return false
218 return false
217 end
219 end
218
220
219 def available_problems_group_by_contests
221 def available_problems_group_by_contests
220 contest_problems = []
222 contest_problems = []
221 pin = {}
223 pin = {}
222 contests.enabled.each do |contest|
224 contests.enabled.each do |contest|
223 available_problems = contest.problems.available
225 available_problems = contest.problems.available
224 contest_problems << {
226 contest_problems << {
225 :contest => contest,
227 :contest => contest,
226 :problems => available_problems
228 :problems => available_problems
227 }
229 }
228 available_problems.each {|p| pin[p.id] = true}
230 available_problems.each {|p| pin[p.id] = true}
229 end
231 end
230 other_avaiable_problems = Problem.available.find_all {|p| pin[p.id]==nil and p.contests.length==0}
232 other_avaiable_problems = Problem.available.find_all {|p| pin[p.id]==nil and p.contests.length==0}
231 contest_problems << {
233 contest_problems << {
232 :contest => nil,
234 :contest => nil,
233 :problems => other_avaiable_problems
235 :problems => other_avaiable_problems
234 }
236 }
235 return contest_problems
237 return contest_problems
236 end
238 end
237
239
238 def solve_all_available_problems?
240 def solve_all_available_problems?
239 available_problems.each do |p|
241 available_problems.each do |p|
240 u = self
242 u = self
241 sub = Submission.find_last_by_user_and_problem(u.id,p.id)
243 sub = Submission.find_last_by_user_and_problem(u.id,p.id)
242 return false if !p or !sub or sub.points < p.full_score
244 return false if !p or !sub or sub.points < p.full_score
243 end
245 end
244 return true
246 return true
245 end
247 end
246
248
247 #get a list of available problem
249 #get a list of available problem
248 def available_problems
250 def available_problems
249 # first, we check if this is normal mode
251 # first, we check if this is normal mode
250 if not GraderConfiguration.multicontests?
252 if not GraderConfiguration.multicontests?
251
253
252 #if this is a normal mode
254 #if this is a normal mode
253 #we show problem based on problem_group, if the config said so
255 #we show problem based on problem_group, if the config said so
254 if GraderConfiguration.use_problem_group?
256 if GraderConfiguration.use_problem_group?
255 return available_problems_in_group
257 return available_problems_in_group
256 else
258 else
257 return Problem.available_problems
259 return Problem.available_problems
258 end
260 end
259 else
261 else
260 #this is multi contest mode
262 #this is multi contest mode
261 contest_problems = []
263 contest_problems = []
262 pin = {}
264 pin = {}
263 contests.enabled.each do |contest|
265 contests.enabled.each do |contest|
264 contest.problems.available.each do |problem|
266 contest.problems.available.each do |problem|
265 if not pin.has_key? problem.id
267 if not pin.has_key? problem.id
266 contest_problems << problem
268 contest_problems << problem
267 end
269 end
268 pin[problem.id] = true
270 pin[problem.id] = true
269 end
271 end
270 end
272 end
271 other_avaiable_problems = Problem.available.find_all {|p| pin[p.id]==nil and p.contests.length==0}
273 other_avaiable_problems = Problem.available.find_all {|p| pin[p.id]==nil and p.contests.length==0}
272 return contest_problems + other_avaiable_problems
274 return contest_problems + other_avaiable_problems
273 end
275 end
274 end
276 end
275
277
276 def available_problems_in_group
278 def available_problems_in_group
277 problem = []
279 problem = []
278 self.groups.each do |group|
280 self.groups.each do |group|
279 group.problems.where(available: true).each { |p| problem << p }
281 group.problems.where(available: true).each { |p| problem << p }
280 end
282 end
281 problem.uniq!
283 problem.uniq!
282 if problem
284 if problem
283 problem.sort! do |a,b|
285 problem.sort! do |a,b|
284 case
286 case
285 when a.date_added < b.date_added
287 when a.date_added < b.date_added
286 1
288 1
287 when a.date_added > b.date_added
289 when a.date_added > b.date_added
288 -1
290 -1
289 else
291 else
290 a.name <=> b.name
292 a.name <=> b.name
291 end
293 end
292 end
294 end
293 return problem
295 return problem
294 else
296 else
295 return []
297 return []
296 end
298 end
297 end
299 end
298
300
299 def can_view_problem?(problem)
301 def can_view_problem?(problem)
300 return true if admin?
302 return true if admin?
301 return available_problems.include? problem
303 return available_problems.include? problem
302 end
304 end
303
305
304 def self.clear_last_login
306 def self.clear_last_login
305 User.update_all(:last_ip => nil)
307 User.update_all(:last_ip => nil)
306 end
308 end
307
309
308 protected
310 protected
309 def encrypt_new_password
311 def encrypt_new_password
310 return if password.blank?
312 return if password.blank?
311 self.salt = (10+rand(90)).to_s
313 self.salt = (10+rand(90)).to_s
312 self.hashed_password = User.encrypt(self.password,self.salt)
314 self.hashed_password = User.encrypt(self.password,self.salt)
313 end
315 end
314
316
315 def assign_default_site
317 def assign_default_site
316 # have to catch error when migrating (because self.site is not available).
318 # have to catch error when migrating (because self.site is not available).
317 begin
319 begin
318 if self.site==nil
320 if self.site==nil
319 self.site = Site.find_by_name('default')
321 self.site = Site.find_by_name('default')
320 if self.site==nil
322 if self.site==nil
321 self.site = Site.find(1) # when 'default has be renamed'
323 self.site = Site.find(1) # when 'default has be renamed'
322 end
324 end
323 end
325 end
324 rescue
326 rescue
325 end
327 end
326 end
328 end
327
329
328 def assign_default_contest
330 def assign_default_contest
329 # have to catch error when migrating (because self.site is not available).
331 # have to catch error when migrating (because self.site is not available).
330 begin
332 begin
331 if self.contests.length == 0
333 if self.contests.length == 0
332 default_contest = Contest.find_by_name(GraderConfiguration['contest.default_contest_name'])
334 default_contest = Contest.find_by_name(GraderConfiguration['contest.default_contest_name'])
333 if default_contest
335 if default_contest
334 self.contests = [default_contest]
336 self.contests = [default_contest]
335 end
337 end
336 end
338 end
337 rescue
339 rescue
338 end
340 end
339 end
341 end
340
342
341 def password_required?
343 def password_required?
342 self.hashed_password.blank? || !self.password.blank?
344 self.hashed_password.blank? || !self.password.blank?
343 end
345 end
344
346
345 def self.encrypt(string,salt)
347 def self.encrypt(string,salt)
346 Digest::SHA1.hexdigest(salt + string)
348 Digest::SHA1.hexdigest(salt + string)
347 end
349 end
348
350
349 def uniqueness_of_email_from_activated_users
351 def uniqueness_of_email_from_activated_users
350 user = User.activated_users.find_by_email(self.email)
352 user = User.activated_users.find_by_email(self.email)
351 if user and (user.login != self.login)
353 if user and (user.login != self.login)
352 self.errors.add(:base,"Email has already been taken")
354 self.errors.add(:base,"Email has already been taken")
353 end
355 end
354 end
356 end
355
357
356 def enough_time_interval_between_same_email_registrations
358 def enough_time_interval_between_same_email_registrations
357 return if !self.new_record?
359 return if !self.new_record?
358 return if self.activated
360 return if self.activated
359 open_user = User.find_by_email(self.email,
361 open_user = User.find_by_email(self.email,
360 :order => 'created_at DESC')
362 :order => 'created_at DESC')
361 if open_user and open_user.created_at and
363 if open_user and open_user.created_at and
362 (open_user.created_at > Time.now.gmtime - 5.minutes)
364 (open_user.created_at > Time.now.gmtime - 5.minutes)
363 self.errors.add(:base,"There are already unactivated registrations with this e-mail address (please wait for 5 minutes)")
365 self.errors.add(:base,"There are already unactivated registrations with this e-mail address (please wait for 5 minutes)")
364 end
366 end
365 end
367 end
366
368
367 def email_validation?
369 def email_validation?
368 begin
370 begin
369 return VALIDATE_USER_EMAILS
371 return VALIDATE_USER_EMAILS
370 rescue
372 rescue
371 return false
373 return false
372 end
374 end
373 end
375 end
374 end
376 end
@@ -1,95 +1,96
1 %header.navbar.navbar-default.navbar-fixed-top
1 %header.navbar.navbar-default.navbar-fixed-top
2 %nav
2 %nav
3 .container-fluid
3 .container-fluid
4 .navbar-header
4 .navbar-header
5 %button.navbar-toggle.collapsed{ data: {toggle: 'collapse', target: '#navbar-collapse'} }
5 %button.navbar-toggle.collapsed{ data: {toggle: 'collapse', target: '#navbar-collapse'} }
6 %span.sr-only Togggle Navigation
6 %span.sr-only Togggle Navigation
7 %span.icon-bar
7 %span.icon-bar
8 %span.icon-bar
8 %span.icon-bar
9 %span.icon-bar
9 %span.icon-bar
10 %a.navbar-brand{href: list_main_path}
10 %a.navbar-brand{href: list_main_path}
11 %span.glyphicon.glyphicon-home
11 %span.glyphicon.glyphicon-home
12 MAIN
12 MAIN
13 .collapse.navbar-collapse#navbar-collapse
13 .collapse.navbar-collapse#navbar-collapse
14 %ul.nav.navbar-nav
14 %ul.nav.navbar-nav
15 / submission
15 / submission
16 - if (@current_user!=nil) and (GraderConfiguration.show_tasks_to?(@current_user))
16 - if (@current_user!=nil) and (GraderConfiguration.show_tasks_to?(@current_user))
17 %li.dropdown
17 %li.dropdown
18 %a.dropdown-toggle{href: '#', data: {toggle:'dropdown'}, aria: {haspopup:"true", expanded:"false"}, role: "button"}
18 %a.dropdown-toggle{href: '#', data: {toggle:'dropdown'}, aria: {haspopup:"true", expanded:"false"}, role: "button"}
19 = "#{I18n.t 'menu.submissions'}"
19 = "#{I18n.t 'menu.submissions'}"
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"}
@@ -1,206 +1,211
1 Rails.application.routes.draw do
1 Rails.application.routes.draw do
2 resources :tags
2 resources :tags
3 get "sources/direct_edit"
3 get "sources/direct_edit"
4
4
5 root :to => 'main#login'
5 root :to => 'main#login'
6
6
7 #logins
7 #logins
8 match 'login/login', to: 'login#login', via: [:get,:post]
8 match 'login/login', to: 'login#login', via: [:get,:post]
9
9
10 resources :contests
10 resources :contests
11 resources :sites
11 resources :sites
12 resources :test
12 resources :test
13
13
14 resources :messages do
14 resources :messages do
15 member do
15 member do
16 get 'hide'
16 get 'hide'
17 post 'reply'
17 post 'reply'
18 end
18 end
19 collection do
19 collection do
20 get 'console'
20 get 'console'
21 get 'list_all'
21 get 'list_all'
22 end
22 end
23 end
23 end
24
24
25 resources :announcements do
25 resources :announcements do
26 member do
26 member do
27 get 'toggle','toggle_front'
27 get 'toggle','toggle_front'
28 end
28 end
29 end
29 end
30
30
31 resources :problems do
31 resources :problems do
32 member do
32 member do
33 get 'toggle'
33 get 'toggle'
34 get 'toggle_test'
34 get 'toggle_test'
35 get 'toggle_view_testcase'
35 get 'toggle_view_testcase'
36 get 'stat'
36 get 'stat'
37 end
37 end
38 collection do
38 collection do
39 get 'turn_all_off'
39 get 'turn_all_off'
40 get 'turn_all_on'
40 get 'turn_all_on'
41 get 'import'
41 get 'import'
42 get 'manage'
42 get 'manage'
43 get 'quick_create'
43 get 'quick_create'
44 post 'do_manage'
44 post 'do_manage'
45 post 'do_import'
45 post 'do_import'
46 end
46 end
47 end
47 end
48
48
49 resources :groups do
49 resources :groups do
50 member do
50 member do
51 post 'add_user', to: 'groups#add_user', as: 'add_user'
51 post 'add_user', to: 'groups#add_user', as: 'add_user'
52 delete 'remove_user/:user_id', to: 'groups#remove_user', as: 'remove_user'
52 delete 'remove_user/:user_id', to: 'groups#remove_user', as: 'remove_user'
53 delete 'remove_all_user', to: 'groups#remove_all_user', as: 'remove_all_user'
53 delete 'remove_all_user', to: 'groups#remove_all_user', as: 'remove_all_user'
54 post 'add_problem', to: 'groups#add_problem', as: 'add_problem'
54 post 'add_problem', to: 'groups#add_problem', as: 'add_problem'
55 delete 'remove_problem/:problem_id', to: 'groups#remove_problem', as: 'remove_problem'
55 delete 'remove_problem/:problem_id', to: 'groups#remove_problem', as: 'remove_problem'
56 delete 'remove_all_problem', to: 'groups#remove_all_problem', as: 'remove_all_problem'
56 delete 'remove_all_problem', to: 'groups#remove_all_problem', as: 'remove_all_problem'
57 end
57 end
58 collection do
58 collection do
59
59
60 end
60 end
61 end
61 end
62
62
63 resources :testcases, only: [] do
63 resources :testcases, only: [] do
64 member do
64 member do
65 get 'download_input'
65 get 'download_input'
66 get 'download_sol'
66 get 'download_sol'
67 end
67 end
68 collection do
68 collection do
69 get 'show_problem/:problem_id(/:test_num)' => 'testcases#show_problem', as: 'show_problem'
69 get 'show_problem/:problem_id(/:test_num)' => 'testcases#show_problem', as: 'show_problem'
70 end
70 end
71 end
71 end
72
72
73 resources :grader_configuration, controller: 'configurations' do
73 resources :grader_configuration, controller: 'configurations' do
74 collection do
74 collection do
75 get 'set_exam_right(/:value)', action: 'set_exam_right', as: 'set_exam_right'
75 get 'set_exam_right(/:value)', action: 'set_exam_right', as: 'set_exam_right'
76 end
76 end
77 end
77 end
78
78
79 resources :users do
79 resources :users do
80 member do
80 member do
81 get 'toggle_activate', 'toggle_enable'
81 get 'toggle_activate', 'toggle_enable'
82 get 'stat'
82 get 'stat'
83 end
83 end
84 collection do
84 collection do
85 get 'profile'
85 get 'profile'
86 post 'chg_passwd'
86 post 'chg_passwd'
87 end
87 end
88 end
88 end
89
89
90 resources :submissions do
90 resources :submissions do
91 member do
91 member do
92 get 'download'
92 get 'download'
93 get 'compiler_msg'
93 get 'compiler_msg'
94 get 'rejudge'
94 get 'rejudge'
95 get 'source'
95 get 'source'
96 end
96 end
97 collection do
97 collection do
98 get 'prob/:problem_id', to: 'submissions#index', as: 'problem'
98 get 'prob/:problem_id', to: 'submissions#index', as: 'problem'
99 get 'direct_edit_problem/:problem_id(/:user_id)', to: 'submissions#direct_edit_problem', as: 'direct_edit_problem'
99 get 'direct_edit_problem/:problem_id(/:user_id)', to: 'submissions#direct_edit_problem', as: 'direct_edit_problem'
100 get 'get_latest_submission_status/:uid/:pid', to: 'submissions#get_latest_submission_status', as: 'get_latest_submission_status'
100 get 'get_latest_submission_status/:uid/:pid', to: 'submissions#get_latest_submission_status', as: 'get_latest_submission_status'
101 end
101 end
102 end
102 end
103
103
104
104
105 #user admin
105 #user admin
106 resources :user_admin do
106 resources :user_admin do
107 collection do
107 collection do
108 match 'bulk_manage', via: [:get, :post]
108 match 'bulk_manage', via: [:get, :post]
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.
205 # match ':controller(/:action(/:id))(.:format)', via: [:get, :post]
210 # match ':controller(/:action(/:id))(.:format)', via: [:get, :post]
206 end
211 end
@@ -1,308 +1,309
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"
62 t.datetime "created_at", null: false
62 t.datetime "created_at", null: false
63 t.datetime "updated_at", null: false
63 t.datetime "updated_at", null: false
64 t.text "description", limit: 16777215
64 t.text "description", limit: 16777215
65 end
65 end
66
66
67 create_table "grader_processes", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
67 create_table "grader_processes", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
68 t.string "host"
68 t.string "host"
69 t.integer "pid"
69 t.integer "pid"
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"
166 end
167 end
167
168
168 create_table "roles_users", id: false, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
169 create_table "roles_users", id: false, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
169 t.integer "role_id"
170 t.integer "role_id"
170 t.integer "user_id"
171 t.integer "user_id"
171 t.index ["user_id"], name: "index_roles_users_on_user_id"
172 t.index ["user_id"], name: "index_roles_users_on_user_id"
172 end
173 end
173
174
174 create_table "sessions", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
175 create_table "sessions", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
175 t.string "session_id"
176 t.string "session_id"
176 t.text "data", limit: 16777215
177 t.text "data", limit: 16777215
177 t.datetime "updated_at"
178 t.datetime "updated_at"
178 t.index ["session_id"], name: "index_sessions_on_session_id"
179 t.index ["session_id"], name: "index_sessions_on_session_id"
179 t.index ["updated_at"], name: "index_sessions_on_updated_at"
180 t.index ["updated_at"], name: "index_sessions_on_updated_at"
180 end
181 end
181
182
182 create_table "sites", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
183 create_table "sites", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
183 t.string "name"
184 t.string "name"
184 t.boolean "started"
185 t.boolean "started"
185 t.datetime "start_time"
186 t.datetime "start_time"
186 t.datetime "created_at", null: false
187 t.datetime "created_at", null: false
187 t.datetime "updated_at", null: false
188 t.datetime "updated_at", null: false
188 t.integer "country_id"
189 t.integer "country_id"
189 t.string "password"
190 t.string "password"
190 end
191 end
191
192
192 create_table "submission_view_logs", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=latin1", force: :cascade do |t|
193 create_table "submission_view_logs", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=latin1", force: :cascade do |t|
193 t.integer "user_id"
194 t.integer "user_id"
194 t.integer "submission_id"
195 t.integer "submission_id"
195 t.datetime "created_at", null: false
196 t.datetime "created_at", null: false
196 t.datetime "updated_at", null: false
197 t.datetime "updated_at", null: false
197 end
198 end
198
199
199 create_table "submissions", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
200 create_table "submissions", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
200 t.integer "user_id"
201 t.integer "user_id"
201 t.integer "problem_id"
202 t.integer "problem_id"
202 t.integer "language_id"
203 t.integer "language_id"
203 t.text "source", limit: 16777215
204 t.text "source", limit: 16777215
204 t.binary "binary"
205 t.binary "binary"
205 t.datetime "submitted_at"
206 t.datetime "submitted_at"
206 t.datetime "compiled_at"
207 t.datetime "compiled_at"
207 t.text "compiler_message", limit: 16777215
208 t.text "compiler_message", limit: 16777215
208 t.datetime "graded_at"
209 t.datetime "graded_at"
209 t.integer "points"
210 t.integer "points"
210 t.text "grader_comment", limit: 16777215
211 t.text "grader_comment", limit: 16777215
211 t.integer "number"
212 t.integer "number"
212 t.string "source_filename"
213 t.string "source_filename"
213 t.float "max_runtime"
214 t.float "max_runtime"
214 t.integer "peak_memory"
215 t.integer "peak_memory"
215 t.integer "effective_code_length"
216 t.integer "effective_code_length"
216 t.string "ip_address"
217 t.string "ip_address"
217 t.index ["submitted_at"], name: "index_submissions_on_submitted_at"
218 t.index ["submitted_at"], name: "index_submissions_on_submitted_at"
218 t.index ["user_id", "problem_id", "number"], name: "index_submissions_on_user_id_and_problem_id_and_number", unique: true
219 t.index ["user_id", "problem_id", "number"], name: "index_submissions_on_user_id_and_problem_id_and_number", unique: true
219 t.index ["user_id", "problem_id"], name: "index_submissions_on_user_id_and_problem_id"
220 t.index ["user_id", "problem_id"], name: "index_submissions_on_user_id_and_problem_id"
220 end
221 end
221
222
222 create_table "tags", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=latin1", force: :cascade do |t|
223 create_table "tags", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=latin1", force: :cascade do |t|
223 t.string "name", null: false
224 t.string "name", null: false
224 t.text "description"
225 t.text "description"
225 t.boolean "public"
226 t.boolean "public"
226 t.datetime "created_at", null: false
227 t.datetime "created_at", null: false
227 t.datetime "updated_at", null: false
228 t.datetime "updated_at", null: false
228 end
229 end
229
230
230 create_table "tasks", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
231 create_table "tasks", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
231 t.integer "submission_id"
232 t.integer "submission_id"
232 t.datetime "created_at"
233 t.datetime "created_at"
233 t.integer "status"
234 t.integer "status"
234 t.datetime "updated_at"
235 t.datetime "updated_at"
235 t.index ["submission_id"], name: "index_tasks_on_submission_id"
236 t.index ["submission_id"], name: "index_tasks_on_submission_id"
236 end
237 end
237
238
238 create_table "test_pairs", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
239 create_table "test_pairs", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
239 t.integer "problem_id"
240 t.integer "problem_id"
240 t.text "input", limit: 4294967295
241 t.text "input", limit: 4294967295
241 t.text "solution", limit: 4294967295
242 t.text "solution", limit: 4294967295
242 t.datetime "created_at", null: false
243 t.datetime "created_at", null: false
243 t.datetime "updated_at", null: false
244 t.datetime "updated_at", null: false
244 end
245 end
245
246
246 create_table "test_requests", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
247 create_table "test_requests", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
247 t.integer "user_id"
248 t.integer "user_id"
248 t.integer "problem_id"
249 t.integer "problem_id"
249 t.integer "submission_id"
250 t.integer "submission_id"
250 t.string "input_file_name"
251 t.string "input_file_name"
251 t.string "output_file_name"
252 t.string "output_file_name"
252 t.string "running_stat"
253 t.string "running_stat"
253 t.integer "status"
254 t.integer "status"
254 t.datetime "updated_at", null: false
255 t.datetime "updated_at", null: false
255 t.datetime "submitted_at"
256 t.datetime "submitted_at"
256 t.datetime "compiled_at"
257 t.datetime "compiled_at"
257 t.text "compiler_message", limit: 16777215
258 t.text "compiler_message", limit: 16777215
258 t.datetime "graded_at"
259 t.datetime "graded_at"
259 t.string "grader_comment"
260 t.string "grader_comment"
260 t.datetime "created_at", null: false
261 t.datetime "created_at", null: false
261 t.float "running_time"
262 t.float "running_time"
262 t.string "exit_status"
263 t.string "exit_status"
263 t.integer "memory_usage"
264 t.integer "memory_usage"
264 t.index ["user_id", "problem_id"], name: "index_test_requests_on_user_id_and_problem_id"
265 t.index ["user_id", "problem_id"], name: "index_test_requests_on_user_id_and_problem_id"
265 end
266 end
266
267
267 create_table "testcases", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=latin1", force: :cascade do |t|
268 create_table "testcases", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=latin1", force: :cascade do |t|
268 t.integer "problem_id"
269 t.integer "problem_id"
269 t.integer "num"
270 t.integer "num"
270 t.integer "group"
271 t.integer "group"
271 t.integer "score"
272 t.integer "score"
272 t.text "input", limit: 4294967295
273 t.text "input", limit: 4294967295
273 t.text "sol", limit: 4294967295
274 t.text "sol", limit: 4294967295
274 t.datetime "created_at", null: false
275 t.datetime "created_at", null: false
275 t.datetime "updated_at", null: false
276 t.datetime "updated_at", null: false
276 t.index ["problem_id"], name: "index_testcases_on_problem_id"
277 t.index ["problem_id"], name: "index_testcases_on_problem_id"
277 end
278 end
278
279
279 create_table "user_contest_stats", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
280 create_table "user_contest_stats", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
280 t.integer "user_id"
281 t.integer "user_id"
281 t.datetime "started_at"
282 t.datetime "started_at"
282 t.datetime "created_at", null: false
283 t.datetime "created_at", null: false
283 t.datetime "updated_at", null: false
284 t.datetime "updated_at", null: false
284 t.boolean "forced_logout"
285 t.boolean "forced_logout"
285 end
286 end
286
287
287 create_table "users", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
288 create_table "users", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
288 t.string "login", limit: 50
289 t.string "login", limit: 50
289 t.string "full_name"
290 t.string "full_name"
290 t.string "hashed_password"
291 t.string "hashed_password"
291 t.string "salt", limit: 5
292 t.string "salt", limit: 5
292 t.string "alias"
293 t.string "alias"
293 t.string "email"
294 t.string "email"
294 t.integer "site_id"
295 t.integer "site_id"
295 t.integer "country_id"
296 t.integer "country_id"
296 t.boolean "activated", default: false
297 t.boolean "activated", default: false
297 t.datetime "created_at"
298 t.datetime "created_at"
298 t.datetime "updated_at"
299 t.datetime "updated_at"
299 t.string "section"
300 t.string "section"
300 t.boolean "enabled", default: true
301 t.boolean "enabled", default: true
301 t.string "remark"
302 t.string "remark"
302 t.string "last_ip"
303 t.string "last_ip"
303 t.index ["login"], name: "index_users_on_login", unique: true
304 t.index ["login"], name: "index_users_on_login", unique: true
304 end
305 end
305
306
306 add_foreign_key "problems_tags", "problems"
307 add_foreign_key "problems_tags", "problems"
307 add_foreign_key "problems_tags", "tags"
308 add_foreign_key "problems_tags", "tags"
308 end
309 end
deleted file
You need to be logged in to leave comments. Login now