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,270 +1,312
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
171
124 - User.all.each do |user|
172 + @logins = Login.includes(:user).where("logins.created_at >= ? AND logins.created_at <= ?",@since_time, @until_time)
125 - @logins << { id: user.id,
173 + case params[:users]
126 - login: user.login,
174 + when 'enabled'
127 - full_name: user.full_name,
175 + @logins = @logins.where(users: {enabled: true})
128 - count: Login.where("user_id = ? AND created_at >= ? AND created_at <= ?",
176 + when 'group'
129 - user.id,@since_time,@until_time)
177 + @logins = @logins.joins(user: :groups).where(user: {groups: {id: params[:groups]}}) if params[:groups]
130 - .count(:id),
131 - min: Login.where("user_id = ? AND created_at >= ? AND created_at <= ?",
132 - user.id,@since_time,@until_time)
133 - .minimum(:created_at),
134 - max: Login.where("user_id = ? AND created_at >= ? AND created_at <= ?",
135 - user.id,@since_time,@until_time)
136 - .maximum(:created_at),
137 - ip: Login.where("user_id = ? AND created_at >= ? AND created_at <= ?",
138 - user.id,@since_time,@until_time)
139 - .select(:ip_address).uniq
140 -
141 - }
142 end
178 end
143 end
179 end
144
180
145 def submission
181 def submission
146 end
182 end
147
183
148 def submission_query
184 def submission_query
149 @submissions = Submission
185 @submissions = Submission
150 .includes(:problem).includes(:user).includes(:language)
186 .includes(:problem).includes(:user).includes(:language)
151
187
152 - if params[:problem]
188 + case params[:users]
153 - @submission = @submission.where(problem_id: params[:problem])
189 + when 'enabled'
190 + @submissions = @submissions.where(users: {enabled: true})
191 + when 'group'
192 + @submissions = @submissions.joins(user: :groups).where(user: {groups: {id: params[:groups]}}) if params[:groups]
154 end
193 end
155
194
156 - case params[:users]
195 + case params[:problems]
157 when 'enabled'
196 when 'enabled'
158 - @submissions = @submissions.where('user.enabled': true)
197 + @submissions = @submissions.where(problems: {available: true})
159 - when 'group'
198 + when 'selected'
160 - @submissions = @submissions.joins(user: :groups).where(user: {groups: {id: params[:groups]}}) if params[:groups]
199 + @submissions = @submissions.where(problem_id: params[:problem_id])
161 end
200 end
162
201
163 #set default
202 #set default
164 params[:since_datetime] = Date.today.to_s if params[:since_datetime].blank?
203 params[:since_datetime] = Date.today.to_s if params[:since_datetime].blank?
165
204
166 @submissions, @recordsTotal, @recordsFiltered = process_query_record( @submissions,
205 @submissions, @recordsTotal, @recordsFiltered = process_query_record( @submissions,
167 global_search: ['user.login','user.full_name','problem.name','problem.full_name','points'],
206 global_search: ['user.login','user.full_name','problem.name','problem.full_name','points'],
168 date_filter: 'submitted_at',
207 date_filter: 'submitted_at',
169 date_param_since: 'since_datetime',
208 date_param_since: 'since_datetime',
170 date_param_until: 'until_datetime',
209 date_param_until: 'until_datetime',
171 hard_limit: 100_000
210 hard_limit: 100_000
172 )
211 )
173 end
212 end
174
213
214 + def login
215 + end
216 +
175 def problem_hof
217 def problem_hof
176 # gen problem list
218 # gen problem list
177 @user = User.find(session[:user_id])
219 @user = User.find(session[:user_id])
178 @problems = @user.available_problems
220 @problems = @user.available_problems
179
221
180 # get selected problems or the default
222 # get selected problems or the default
181 if params[:id]
223 if params[:id]
182 begin
224 begin
183 @problem = Problem.available.find(params[:id])
225 @problem = Problem.available.find(params[:id])
184 rescue
226 rescue
185 redirect_to action: :problem_hof
227 redirect_to action: :problem_hof
186 flash[:notice] = 'Error: submissions for that problem are not viewable.'
228 flash[:notice] = 'Error: submissions for that problem are not viewable.'
187 return
229 return
188 end
230 end
189 end
231 end
190
232
191 return unless @problem
233 return unless @problem
192
234
193 @by_lang = {} #aggregrate by language
235 @by_lang = {} #aggregrate by language
194
236
195 range =65
237 range =65
196 @histogram = { data: Array.new(range,0), summary: {} }
238 @histogram = { data: Array.new(range,0), summary: {} }
197 @summary = {count: 0, solve: 0, attempt: 0}
239 @summary = {count: 0, solve: 0, attempt: 0}
198 user = Hash.new(0)
240 user = Hash.new(0)
199 Submission.where(problem_id: @problem.id).find_each do |sub|
241 Submission.where(problem_id: @problem.id).find_each do |sub|
200 #histogram
242 #histogram
201 d = (DateTime.now.in_time_zone - sub.submitted_at) / 24 / 60 / 60
243 d = (DateTime.now.in_time_zone - sub.submitted_at) / 24 / 60 / 60
202 @histogram[:data][d.to_i] += 1 if d < range
244 @histogram[:data][d.to_i] += 1 if d < range
203
245
204 next unless sub.points
246 next unless sub.points
205 @summary[:count] += 1
247 @summary[:count] += 1
206 user[sub.user_id] = [user[sub.user_id], (sub.points >= @problem.full_score) ? 1 : 0].max
248 user[sub.user_id] = [user[sub.user_id], (sub.points >= @problem.full_score) ? 1 : 0].max
207
249
208 lang = Language.find_by_id(sub.language_id)
250 lang = Language.find_by_id(sub.language_id)
209 next unless lang
251 next unless lang
210 next unless sub.points >= @problem.full_score
252 next unless sub.points >= @problem.full_score
211
253
212 #initialize
254 #initialize
213 unless @by_lang.has_key?(lang.pretty_name)
255 unless @by_lang.has_key?(lang.pretty_name)
214 @by_lang[lang.pretty_name] = {
256 @by_lang[lang.pretty_name] = {
215 runtime: { avail: false, value: 2**30-1 },
257 runtime: { avail: false, value: 2**30-1 },
216 memory: { avail: false, value: 2**30-1 },
258 memory: { avail: false, value: 2**30-1 },
217 length: { avail: false, value: 2**30-1 },
259 length: { avail: false, value: 2**30-1 },
218 first: { avail: false, value: DateTime.new(3000,1,1) }
260 first: { avail: false, value: DateTime.new(3000,1,1) }
219 }
261 }
220 end
262 end
221
263
222 if sub.max_runtime and sub.max_runtime < @by_lang[lang.pretty_name][:runtime][:value]
264 if sub.max_runtime and sub.max_runtime < @by_lang[lang.pretty_name][:runtime][:value]
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
@@ -1,113 +1,106
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
@@ -1,120 +1,122
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
@@ -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"}
@@ -61,146 +61,151
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,213 +1,214
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"
deleted file
You need to be logged in to leave comments. Login now