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,366 +1,408
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")}'
@@ -1,209 +1,202
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
@@ -1,216 +1,218
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
@@ -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