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,31 +1,33
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'
@@ -83,116 +85,156
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)
@@ -1,41 +1,34
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
@@ -1,48 +1,50
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?
@@ -44,48 +44,49
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')
@@ -15,63 +15,68
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"}
@@ -133,48 +133,53
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 #
@@ -1,37 +1,37
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"
@@ -94,48 +94,49
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"
deleted file
You need to be logged in to leave comments. Login now