diff --git a/app/controllers/report_controller.rb b/app/controllers/report_controller.rb
--- a/app/controllers/report_controller.rb
+++ b/app/controllers/report_controller.rb
@@ -4,7 +4,9 @@
before_action :check_valid_login
- before_action :admin_authorization, only: [:login_stat,:submission, :submission_query, :stuck, :cheat_report, :cheat_scruntinize, :show_max_score, :current_score]
+ before_action :admin_authorization, only: [:login_stat,:submission, :submission_query,
+ :login, :login_detail_query, :login_summary_query,
+ :stuck, :cheat_report, :cheat_scruntinize, :show_max_score, :current_score]
before_action(only: [:problem_hof]) { |c|
return false unless check_valid_login
@@ -104,7 +106,53 @@
end
- def login_stat
+ def login
+ end
+
+ def login_summary_query
+ @users = Array.new
+
+ date_and_time = '%Y-%m-%d %H:%M'
+ begin
+ md = params[:since_datetime].match(/(\d+)-(\d+)-(\d+) (\d+):(\d+)/)
+ @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)
+ rescue
+ @since_time = DateTime.new(1000,1,1)
+ end
+ begin
+ md = params[:until_datetime].match(/(\d+)-(\d+)-(\d+) (\d+):(\d+)/)
+ @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)
+ rescue
+ @until_time = DateTime.new(3000,1,1)
+ end
+
+ record = User
+ .left_outer_joins(:logins).group('users.id')
+ .where("logins.created_at >= ? AND logins.created_at <= ?",@since_time, @until_time)
+ case params[:users]
+ when 'enabled'
+ record = record.where(enabled: true)
+ when 'group'
+ record = record.joins(:groups).where(groups: {id: params[:groups]}) if params[:groups]
+ end
+
+ record = record.pluck("users.id,users.login,users.full_name,count(logins.created_at),min(logins.created_at),max(logins.created_at)")
+ record.each do |user|
+ x = Login.where("user_id = ? AND created_at >= ? AND created_at <= ?",
+ user[0],@since_time,@until_time)
+ .pluck(:ip_address).uniq
+ @users << { id: user[0],
+ login: user[1],
+ full_name: user[2],
+ count: user[3],
+ min: user[4],
+ max: user[5],
+ ip: x
+ }
+ end
+ end
+
+ def login_detail_query
@logins = Array.new
date_and_time = '%Y-%m-%d %H:%M'
@@ -120,25 +168,13 @@
rescue
@until_time = DateTime.new(3000,1,1)
end
-
- User.all.each do |user|
- @logins << { id: user.id,
- login: user.login,
- full_name: user.full_name,
- count: Login.where("user_id = ? AND created_at >= ? AND created_at <= ?",
- user.id,@since_time,@until_time)
- .count(:id),
- min: Login.where("user_id = ? AND created_at >= ? AND created_at <= ?",
- user.id,@since_time,@until_time)
- .minimum(:created_at),
- max: Login.where("user_id = ? AND created_at >= ? AND created_at <= ?",
- user.id,@since_time,@until_time)
- .maximum(:created_at),
- ip: Login.where("user_id = ? AND created_at >= ? AND created_at <= ?",
- user.id,@since_time,@until_time)
- .select(:ip_address).uniq
- }
+ @logins = Login.includes(:user).where("logins.created_at >= ? AND logins.created_at <= ?",@since_time, @until_time)
+ case params[:users]
+ when 'enabled'
+ @logins = @logins.where(users: {enabled: true})
+ when 'group'
+ @logins = @logins.joins(user: :groups).where(user: {groups: {id: params[:groups]}}) if params[:groups]
end
end
@@ -149,15 +185,18 @@
@submissions = Submission
.includes(:problem).includes(:user).includes(:language)
- if params[:problem]
- @submission = @submission.where(problem_id: params[:problem])
+ case params[:users]
+ when 'enabled'
+ @submissions = @submissions.where(users: {enabled: true})
+ when 'group'
+ @submissions = @submissions.joins(user: :groups).where(user: {groups: {id: params[:groups]}}) if params[:groups]
end
- case params[:users]
+ case params[:problems]
when 'enabled'
- @submissions = @submissions.where('user.enabled': true)
- when 'group'
- @submissions = @submissions.joins(user: :groups).where(user: {groups: {id: params[:groups]}}) if params[:groups]
+ @submissions = @submissions.where(problems: {available: true})
+ when 'selected'
+ @submissions = @submissions.where(problem_id: params[:problem_id])
end
#set default
@@ -172,6 +211,9 @@
)
end
+ def login
+ end
+
def problem_hof
# gen problem list
@user = User.find(session[:user_id])
diff --git a/app/controllers/user_admin_controller.rb b/app/controllers/user_admin_controller.rb
--- a/app/controllers/user_admin_controller.rb
+++ b/app/controllers/user_admin_controller.rb
@@ -8,13 +8,6 @@
def index
@user_count = User.count
- if params[:page] == 'all'
- @users = User.all
- @paginated = false
- else
- @users = User.paginate :page => params[:page]
- @paginated = true
- end
@users = User.all
@hidden_columns = ['hashed_password', 'salt', 'created_at', 'updated_at']
@contests = Contest.enabled
diff --git a/app/models/user.rb b/app/models/user.rb
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -22,6 +22,8 @@
:class_name => "Message",
:foreign_key => "receiver_id"
+ has_many :logins
+
has_one :contest_stat, :class_name => "UserContestStat", :dependent => :destroy
belongs_to :site
diff --git a/app/views/layouts/_header.html.haml b/app/views/layouts/_header.html.haml
--- a/app/views/layouts/_header.html.haml
+++ b/app/views/layouts/_header.html.haml
@@ -65,6 +65,7 @@
= add_menu( 'Current Score', 'report', 'current_score')
= add_menu( 'Score Report', 'report', 'max_score')
= add_menu( 'Submission Report', 'report', 'submission')
+ = add_menu( 'Login Report', 'report', 'login')
- if (ungraded = Submission.where('graded_at is null').where('submitted_at < ?', 1.minutes.ago).count) > 0
=link_to "#{ungraded} backlogs!",
grader_list_path,
diff --git a/app/views/report/login.html.haml b/app/views/report/login.html.haml
new file mode 100644
--- /dev/null
+++ b/app/views/report/login.html.haml
@@ -0,0 +1,130 @@
+- content_for :header do
+ = javascript_include_tag 'local_jquery'
+
+%h1 Logins detail
+
+.row
+ .col-md-4
+ .alert.alert-info
+ %ul
+ %li You have to click refresh when changing the filter above
+ %li Detail tab shows each logins separately
+ %li Summary tab shows logins summary of each user
+ .col-md-4
+ = render partial: 'shared/date_filter'
+ .col-md-4
+ = render partial: 'shared/user_select'
+
+.row.form-group
+ .col-sm-12
+ %ul.nav.nav-tabs
+ %li.active
+ %a{href: '#detail', data: {toggle: :tab}} Detail
+ %li
+ %a{href: '#summary', data: {toggle: :tab}} Summary
+.row
+ .col-sm-12
+ .tab-content
+ .tab-pane.active#detail
+ %table#detail-table.table.table-hover.table-condense.datatable{style: 'width: 100%'}
+ .tab-pane#summary
+ %table#summary-table.table.table-hover.table-condense.datatable{style: 'width: 100%'}
+
+
+
+:javascript
+ $(function() {
+ detail_table = $('#detail-table').DataTable({
+ 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>>",
+ autoWidth: true,
+ buttons: [
+ {
+ text: 'Refresh',
+ action: (e,dt,node,config) => {
+ detail_table.clear().draw()
+ detail_table.ajax.reload( () => { detail_table.columns.adjust().draw() } )
+ summary_table.clear().draw()
+ summary_table.ajax.reload( () => { summary_table.columns.adjust().draw() } )
+ }
+ },
+ 'copy',
+ {
+ extend: 'excel',
+ title: 'Login detail',
+ }
+ ],
+ columns: [
+ {title: 'User', data: 'login_text'},
+ {title: 'Time', data: 'created_at'},
+ {title: 'IP', data: 'ip_address'},
+ ],
+ ajax: {
+ url: '#{login_detail_query_report_path}',
+ type: 'POST',
+ data: (d) => {
+ d.since_datetime = $('#since_datetime').val()
+ d.until_datetime = $('#until_datetime').val()
+ d.users = $("input[name='users']:checked").val()
+ d.groups = $("#group_id").select2('val')
+ },
+ dataType: 'json',
+ beforeSend: (request) => {
+ request.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').attr('content'));
+ },
+ }, //end ajax
+ pageLength: 25,
+ processing: true,
+ });
+
+ summary_table = $('#summary-table').DataTable({
+ 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>>",
+ autoWidth: true,
+ buttons: [
+ {
+ text: 'Refresh',
+ action: (e,dt,node,config) => {
+ summary_table.clear().draw()
+ summary_table.ajax.reload( () => { summary_table.columns.adjust().draw() } )
+ detail_table.clear().draw()
+ detail_table.ajax.reload( () => { detail_table.columns.adjust().draw() } )
+ }
+ },
+ 'copy',
+ {
+ extend: 'excel',
+ title: 'Login summary',
+ }
+ ],
+ columns: [
+ {title: 'User', data: 'login_text'},
+ {title: 'Login Count', data: 'count'},
+ {title: 'Earliest', data: 'earliest'},
+ {title: 'Latest', data: 'latest'},
+ {title: 'IP', data: 'ip_address'},
+ ],
+ ajax: {
+ url: '#{login_summary_query_report_path}',
+ type: 'POST',
+ data: (d) => {
+ d.since_datetime = $('#since_datetime').val()
+ d.until_datetime = $('#until_datetime').val()
+ d.users = $("input[name='users']:checked").val()
+ d.groups = $("#group_id").select2('val')
+ },
+ dataType: 'json',
+ beforeSend: (request) => {
+ request.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').attr('content'));
+ },
+ }, //end ajax
+ pageLength: 25,
+ processing: true,
+ });
+
+ $('.input-group.date').datetimepicker({
+ format: 'YYYY-MM-DD HH:mm',
+ showTodayButton: true,
+ locale: 'en',
+ widgetPositioning: {horizontal: 'auto', vertical: 'bottom'},
+ defaultDate: moment()
+ });
+ });
diff --git a/app/views/report/login_detail_query.json.jbuilder b/app/views/report/login_detail_query.json.jbuilder
new file mode 100644
--- /dev/null
+++ b/app/views/report/login_detail_query.json.jbuilder
@@ -0,0 +1,10 @@
+json.draw params['draw']&.to_i
+json.recordsTotal @recordsTotal
+json.recordsFiltered @recordsFiltered
+json.data do
+ json.array! @logins do |login|
+ json.login_text login.user ? "(#{login.user.login}) #{login.user.full_name}" : '-- deletec user --'
+ json.created_at login.created_at.strftime('%Y-%m-%d %H:%M')
+ json.ip_address login.ip_address
+ end
+end
diff --git a/app/views/report/login_stat.html.haml b/app/views/report/login_stat.html.haml
deleted file mode 100644
--- a/app/views/report/login_stat.html.haml
+++ /dev/null
@@ -1,36 +0,0 @@
-- content_for :header do
- = stylesheet_link_tag 'tablesorter-theme.cafe'
- = javascript_include_tag 'local_jquery'
-
-%script{:type=>"text/javascript"}
- $(function () {
- $('#since_datetime').datetimepicker({ showButtonPanel: true, dateFormat: "yy-mm-dd", controlType: "slider"} );
- $('#until_datetime').datetimepicker({ showButtonPanel: true, dateFormat: "yy-mm-dd", controlType: "slider"} );
- $('#my_table').tablesorter({widthFixed: true, widgets: ['zebra']});
- });
-
-%h1 Login status
-
-=render partial: 'report_menu'
-=render partial: 'date_range', locals: {param_text: 'Login date range:', title: 'Query login stat in the range' }
-
-%table.tablesorter-cafe#my_table
- %thead
- %tr
- %th login
- %th full name
- %th login count
- %th earliest
- %th latest
- %th IP
- %tbody
- - @logins.each do |l|
- %tr{class: cycle('info-even','info-odd')}
- %td= link_to l[:login], controller: 'users', action: 'profile', id: l[:id]
- %td= l[:full_name]
- %td= l[:count]
- %td= l[:min] ? l[:min].in_time_zone.strftime('%Y-%m-%d %H:%M') : ''
- %td= l[:max] ? "#{l[:max].in_time_zone.strftime('%Y-%m-%d %H:%M.%S')} (#{time_ago_in_words(l[:max].in_time_zone)} ago)" : ''
- %td
- - l[:ip].each do |ip|
- #{ip.ip_address}
diff --git a/app/views/report/login_summary_query.json.jbuilder b/app/views/report/login_summary_query.json.jbuilder
new file mode 100644
--- /dev/null
+++ b/app/views/report/login_summary_query.json.jbuilder
@@ -0,0 +1,12 @@
+json.draw params['draw']&.to_i
+json.recordsTotal @recordsTotal
+json.recordsFiltered @recordsFiltered
+json.data do
+ json.array! @users do |user|
+ json.login_text "(#{user[:login]}) #{user[:full_name]}"
+ json.count user[:count]
+ json.earliest user[:min].strftime('%Y-%m-%d %H:%M')
+ json.latest user[:max].strftime('%Y-%m-%d %H:%M')
+ json.ip_address user[:ip].join('
')
+ end
+end
diff --git a/app/views/report/submission.html.haml b/app/views/report/submission.html.haml
--- a/app/views/report/submission.html.haml
+++ b/app/views/report/submission.html.haml
@@ -36,7 +36,11 @@
submission_table.ajax.reload( () => { submission_table.columns.adjust().draw() } )
}
},
- 'copy', 'excel'
+ 'copy',
+ {
+ extend: 'excel',
+ title: 'Submission detail'
+ }
],
columns: [
{title: 'Sub ID', data: 'id'},
@@ -55,8 +59,9 @@
d.since_datetime = $('#since_datetime').val()
d.until_datetime = $('#until_datetime').val()
d.users = $("input[name='users']:checked").val()
- d.problems = $("#problem_id").select2('val')
d.groups = $("#group_id").select2('val')
+ d.problems = $("input[name='problems']:checked").val()
+ d.problem_id = $("#problem_id").select2('val')
},
dataType: 'json',
beforeSend: (request) => {
diff --git a/app/views/shared/_problem_select.html.haml b/app/views/shared/_problem_select.html.haml
--- a/app/views/shared/_problem_select.html.haml
+++ b/app/views/shared/_problem_select.html.haml
@@ -2,9 +2,18 @@
.panel-heading
Problems
.panel-body
- %p
- Select problem(s) to be included in the report
- = label_tag :problem_id, "Problems"
+ .radio
+ %label
+ = radio_button_tag 'problems', 'all', (params[:users] == "all")
+ All problems
+ .radio
+ %label
+ = radio_button_tag 'problems', 'enabled', (params[:users] == "enabled"), checked: true
+ Only problems with available = "yes"
+ .radio
+ %label
+ = radio_button_tag 'problems', 'selected', (params[:users] == "group")
+ Only these selected problems
= select_tag 'problem_id[]',
options_for_select(Problem.all.collect {|p| ["[#{p.name}] #{p.full_name}", p.id]},params[:problem_id]),
{ id: :problem_id, class: 'select2 form-control', multiple: "true" }
diff --git a/app/views/shared/_user_select.html.haml b/app/views/shared/_user_select.html.haml
--- a/app/views/shared/_user_select.html.haml
+++ b/app/views/shared/_user_select.html.haml
@@ -8,7 +8,7 @@
All users
.radio
%label
- = radio_button_tag 'users', 'enabled', (params[:users] == "enabled")
+ = radio_button_tag 'users', 'enabled', (params[:users] == "enabled"), checked: true
Only enabled users
.radio
%label
diff --git a/config/routes.rb b/config/routes.rb
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -154,6 +154,11 @@
post 'cheat_scruntinize'
get 'submission'
post 'submission_query'
+ get 'login_stat'
+ post 'login_stat'
+ get 'login'
+ post 'login_summary_query'
+ post 'login_detail_query'
end
#get 'report/current_score', to: 'report#current_score', as: 'report_current_score'
#get 'report/problem_hof(/:id)', to: 'report#problem_hof', as: 'report_problem_hof'
diff --git a/db/migrate/20200405112919_add_index_to_login.rb b/db/migrate/20200405112919_add_index_to_login.rb
new file mode 100644
--- /dev/null
+++ b/db/migrate/20200405112919_add_index_to_login.rb
@@ -0,0 +1,5 @@
+class AddIndexToLogin < ActiveRecord::Migration[5.2]
+ def change
+ add_index :logins, :user_id
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 2020_04_04_142959) do
+ActiveRecord::Schema.define(version: 2020_04_05_112919) do
create_table "announcements", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
t.string "author"
@@ -115,6 +115,7 @@
t.string "ip_address"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
+ t.index ["user_id"], name: "index_logins_on_user_id"
end
create_table "messages", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|