diff --git a/.gitignore b/.gitignore --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,10 @@ *~ /vendor/plugins/rails_upgrade + +#ignore public assets??? +/public/assets + +#ignore .orig and .swp +*.orig +*.swp diff --git a/Gemfile b/Gemfile --- a/Gemfile +++ b/Gemfile @@ -37,9 +37,11 @@ # gem 'debugger' # +# jquery addition gem 'jquery-rails' gem 'jquery-ui-sass-rails' gem 'jquery-timepicker-addon-rails' +gem 'jquery-tablesorter' #syntax highlighter gem 'rouge' diff --git a/Gemfile.lock b/Gemfile.lock --- a/Gemfile.lock +++ b/Gemfile.lock @@ -58,6 +58,8 @@ jquery-rails (3.1.1) railties (>= 3.0, < 5.0) thor (>= 0.14, < 2.0) + jquery-tablesorter (1.12.7) + railties (>= 3.1, < 5) jquery-timepicker-addon-rails (1.4.1) railties (>= 3.1) jquery-ui-rails (4.0.3) @@ -152,6 +154,7 @@ haml in_place_editing jquery-rails + jquery-tablesorter jquery-timepicker-addon-rails jquery-ui-sass-rails mail diff --git a/app/assets/javascripts/new.js b/app/assets/javascripts/new.js --- a/app/assets/javascripts/new.js +++ b/app/assets/javascripts/new.js @@ -4,3 +4,4 @@ //= require jquery.ui.datepicker //= require jquery.ui.slider //= require jquery-ui-timepicker-addon +//= require jquery-tablesorter diff --git a/app/assets/stylesheets/application.css.sass b/app/assets/stylesheets/application.css.sass --- a/app/assets/stylesheets/application.css.sass +++ b/app/assets/stylesheets/application.css.sass @@ -4,7 +4,8 @@ @import jquery.ui.datepicker @import jquery.ui.slider @import jquery-ui-timepicker-addon - +@import jquery-tablesorter/theme.metro-dark +@import tablesorter-theme.cafe body background: white image-url("topbg.jpg") repeat-x top center diff --git a/app/assets/stylesheets/tablesorter-theme.cafe.css b/app/assets/stylesheets/tablesorter-theme.cafe.css new file mode 100644 --- /dev/null +++ b/app/assets/stylesheets/tablesorter-theme.cafe.css @@ -0,0 +1,197 @@ +/************* +Metro Dark Theme +*************/ +/* overall */ +.tablesorter-cafe { + // font: 12px/18px 'Segoe UI Semilight', 'Open Sans', Verdana, Arial, Helvetica, sans-serif; + color: #000; + background-color: #777; + margin: 10px 0 15px; + text-align: left; + border-collapse: collapse; + border: #555 1px solid; +} + +.tablesorter-cafe tr.dark-row th, .tablesorter-cafe tr.dark-row td { + background-color: #222; + color: #fff; + text-align: left; + font-size: 14px; +} + +/* header/footer */ +.tablesorter-cafe caption, +.tablesorter-cafe th, +.tablesorter-cafe thead td, +.tablesorter-cafe tfoot th, +.tablesorter-cafe tfoot td { + //font-weight: 300; + //font-size: 15px; + color: #fff; + background-color: #777; + padding: 2px; + border: #555 1px solid; +} + +.tablesorter-cafe .header, +.tablesorter-cafe .tablesorter-header { + background-image: url(data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAQBAMAAADQT4M0AAAAGFBMVEUAAADu7u7u7u7u7u7u7u7u7u7u7u7u7u5jNePWAAAACHRSTlMAMxIHKwEgMWD59H4AAABSSURBVAjXY2BgYFJgAAHzYhDJ6igSAKTYBAUTgJSioKAQAwNzoaCguAFDiCAQuDIkgigxBgiA8cJAVCpQt6AgSL+JoKAzA0gjUBsQqBcBCYhFAAE/CV4zeSzxAAAAAElFTkSuQmCC); + background-position: center right; + background-repeat: no-repeat; + cursor: pointer; + white-space: normal; +} +.tablesorter-cafe .tablesorter-header-inner { + padding: 0 18px 0 4px; +} +.tablesorter-cafe thead .headerSortUp, +.tablesorter-cafe thead .tablesorter-headerSortUp, +.tablesorter-cafe thead .tablesorter-headerAsc { + background-image: url(data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAQBAMAAADQT4M0AAAAIVBMVEUAAADu7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u5meJAOAAAACnRSTlMAMwsqXt+gIBUGxGoDMAAAAFlJREFUCNctzC0SQAAUReEzGNQ3AlHRiSRZFCVZYgeswRL8hLdK7834wj3tAlGP6y7fYHpKS6w6WwbVG0I1NZVnZPG8/DYxOYlnhUYkA06R1s9ESsxR4NIdPhkPFDFYuEnMAAAAAElFTkSuQmCC); +} +.tablesorter-cafe thead .headerSortDown, +.tablesorter-cafe thead .tablesorter-headerSortDown, +.tablesorter-cafe thead .tablesorter-headerDesc { + background-image: url(data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAQBAMAAADQT4M0AAAALVBMVEUAAADu7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7i0NViAAAADnRSTlMAMiweCQITTvDctZZqaTlM310AAABcSURBVAjXY2BgYEtgAAFHERDJqigUAKSYBQUNgFSioKAYAwOLIBA4MASBKFUGQxAlzAAF+94BwWuGKBC1lIFl3rt3Lx0YGCzevWsGSjK9e6cAUlT3HKyW9wADAwDRrBiDy6bKzwAAAABJRU5ErkJggg==); +} +.tablesorter-cafe thead .sorter-false { + background-image: none; + cursor: default; + padding: 4px; +} + +/* tbody */ +.tablesorter-cafe td { + background-color: #fff; + padding: 1px 4px; + vertical-align: top; + border-style: solid; + border-color: #666; + border-collapse: collapse; + border-width: 0px 1px; + +} + +/* hovered row colors */ +.tablesorter-cafe tbody > tr:hover > td, +.tablesorter-cafe tbody > tr.even:hover > td, +.tablesorter-cafe tbody > tr.odd:hover > td { + background: #bbb; + color: #000; +} + +/* table processing indicator */ +.tablesorter-cafe .tablesorter-processing { + background-position: center center !important; + background-repeat: no-repeat !important; + /* background-image: url(../addons/pager/icons/loading.gif) !important; */ + background-image: url(data:image/gif;base64,R0lGODlhFAAUAKEAAO7u7lpaWgAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQBCgACACwAAAAAFAAUAAACQZRvoIDtu1wLQUAlqKTVxqwhXIiBnDg6Y4eyx4lKW5XK7wrLeK3vbq8J2W4T4e1nMhpWrZCTt3xKZ8kgsggdJmUFACH5BAEKAAIALAcAAAALAAcAAAIUVB6ii7jajgCAuUmtovxtXnmdUAAAIfkEAQoAAgAsDQACAAcACwAAAhRUIpmHy/3gUVQAQO9NetuugCFWAAAh+QQBCgACACwNAAcABwALAAACE5QVcZjKbVo6ck2AF95m5/6BSwEAIfkEAQoAAgAsBwANAAsABwAAAhOUH3kr6QaAcSrGWe1VQl+mMUIBACH5BAEKAAIALAIADQALAAcAAAIUlICmh7ncTAgqijkruDiv7n2YUAAAIfkEAQoAAgAsAAAHAAcACwAAAhQUIGmHyedehIoqFXLKfPOAaZdWAAAh+QQFCgACACwAAAIABwALAAACFJQFcJiXb15zLYRl7cla8OtlGGgUADs=) !important; +} + +/* pager */ +.tablesorter-cafe .tablesorter-pager button { + background-color: #444; + color: #eee; + border: #555 1px solid; + cursor: pointer; +} +.tablesorter-cafe .tablesorter-pager button:hover { + background-color: #555; +} + +/* Zebra Widget - row alternating colors */ +.tablesorter-cafe tr.odd td { + background-color: #eee; +} +.tablesorter-cafe tr.even td { + background-color: #fff; +} + +/* Column Widget - column sort colors */ +.tablesorter-cafe tr.odd td.primary { + background-color: #bfbfbf; +} +.tablesorter-cafe td.primary, +.tablesorter-cafe tr.even td.primary { + background-color: #d9d9d9; +} +.tablesorter-cafe tr.odd td.secondary { + background-color: #d9d9d9; +} +.tablesorter-cafe td.secondary, +.tablesorter-cafe tr.even td.secondary { + background-color: #e6e6e6; +} +.tablesorter-cafe tr.odd td.tertiary { + background-color: #e6e6e6; +} +.tablesorter-cafe td.tertiary, +.tablesorter-cafe tr.even td.tertiary { + background-color: #f2f2f2; +} + +/* filter widget */ +.tablesorter-cafe .tablesorter-filter-row td { + background: #eee; + line-height: normal; + text-align: center; /* center the input */ + -webkit-transition: line-height 0.1s ease; + -moz-transition: line-height 0.1s ease; + -o-transition: line-height 0.1s ease; + transition: line-height 0.1s ease; +} +/* optional disabled input styling */ +.tablesorter-cafe .tablesorter-filter-row .disabled { + opacity: 0.5; + filter: alpha(opacity=50); + cursor: not-allowed; +} +/* hidden filter row */ +.tablesorter-cafe .tablesorter-filter-row.hideme td { + /*** *********************************************** ***/ + /*** change this padding to modify the thickness ***/ + /*** of the closed filter row (height = padding x 2) ***/ + padding: 2px; + /*** *********************************************** ***/ + margin: 0; + line-height: 0; + cursor: pointer; +} +.tablesorter-cafe .tablesorter-filter-row.hideme .tablesorter-filter { + height: 1px; + min-height: 0; + border: 0; + padding: 0; + margin: 0; + /* don't use visibility: hidden because it disables tabbing */ + opacity: 0; + filter: alpha(opacity=0); +} +/* filters */ +.tablesorter-cafe .tablesorter-filter { + width: 95%; + height: auto; + margin: 4px; + padding: 4px; + background-color: #fff; + border: 1px solid #bbb; + color: #333; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + -webkit-transition: height 0.1s ease; + -moz-transition: height 0.1s ease; + -o-transition: height 0.1s ease; + transition: height 0.1s ease; +} +/* rows hidden by filtering (needed for child rows) */ +.tablesorter .filtered { + display: none; +} + +/* ajax error row */ +.tablesorter .tablesorter-errorRow td { + text-align: center; + cursor: pointer; + background-color: #e6bf99; +} 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 @@ -27,7 +27,8 @@ end User.all.each do |user| - @logins << { login: user.login, + @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) 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 @@ -159,6 +159,8 @@ end @scorearray << ustat end + + render template: 'user_admin/user_stat' end def import diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -14,6 +14,7 @@ :register, :forget, :retrieve_password] + before_filter :authenticate, :profile_authorization, only: [:profile] verify :method => :post, :only => [:chg_passwd], :redirect_to => { :action => :index } @@ -108,6 +109,11 @@ redirect_to :action => 'forget' end + def profile + @user = User.find(params[:id]) + @submission = Submission.where(user_id: params[:id]).all + end + protected def verify_online_registration @@ -152,5 +158,19 @@ send_mail(user.email, mail_subject, mail_body) end + + # allow viewing of regular user profile only when options allow so + # only admins can view admins profile + def profile_authorization + #if view admins' profile, allow only admin + return false unless(params[:id]) + user = User.find(params[:id]) + return false unless user + return admin_authorization if user.admin? + return true if GraderConfiguration["right.user_view_submission"] + + #finally, we allow only admin + admin_authorization + end end diff --git a/app/views/report/_task_hof.html.haml b/app/views/report/_task_hof.html.haml --- a/app/views/report/_task_hof.html.haml +++ b/app/views/report/_task_hof.html.haml @@ -7,22 +7,34 @@ - if @best %b Best Runtime: - by #{@best[:runtime][:user]} using #{@best[:runtime][:lang]} with #{@best[:runtime][:value] * 1000} milliseconds at submission + by #{link_to @best[:runtime][:user], controller:'users', action:'profile', id:@best[:memory][:user_id]} + using #{@best[:runtime][:lang]} + with #{@best[:runtime][:value] * 1000} milliseconds + at submission = link_to("#" + @best[:runtime][:sub_id].to_s, controller: 'graders', action: 'submission', id:@best[:runtime][:sub_id]) %br/ %b Best Memory Usage: - by #{@best[:memory][:user]} using #{@best[:memory][:lang]} with #{@best[:memory][:value]} kbytes at submission + by #{link_to @best[:memory][:user], controller:'users', action:'profile', id:@best[:memory][:user_id]} + using #{@best[:memory][:lang]} + with #{@best[:memory][:value]} kbytes + at submission = link_to("#" + @best[:memory][:sub_id].to_s, controller: 'graders' , action: 'submission', id:@best[:memory][:sub_id]) %br/ - %b Shortest Code: - by #{@best[:length][:user]} using #{@best[:length][:lang]} with #{@best[:length][:value]} bytes at submission + %b Shortest Code: + by #{link_to @best[:length][:user], controller:'users', action:'profile', id:@best[:length][:user_id]} + using #{@best[:length][:lang]} + with #{@best[:length][:value]} bytes + at submission = link_to("#" + @best[:length][:sub_id].to_s, controller: 'graders' , action: 'submission', id: @best[:length][:sub_id]) %br/ - %b First solver: - #{@best[:first][:user]} is the first solver using #{@best[:first][:lang]} on #{@best[:first][:value]} at submission + %b First solver: + #{link_to @best[:first][:user], controller:'users', action:'profile', id:@best[:first][:user_id]} is the first solver + using #{@best[:first][:lang]} + on #{@best[:first][:value]} + at submission = link_to("#" + @best[:first][:sub_id].to_s, controller: 'graders' , action: 'submission', id: @best[:first][:sub_id]) %br/ @@ -46,17 +58,22 @@ %tr{class: cycle('info-even','info-odd')} %td= lang %td - = "#{value[:runtime][:user]} (#{(value[:runtime][:value] * 1000).to_i} @" + = link_to value[:runtime][:user], controller: 'users', action: 'profile', id: value[:runtime][:user_id] + = "(#{(value[:runtime][:value] * 1000).to_i} @" = "#{link_to("#" + value[:runtime][:sub_id].to_s, controller: 'graders' , action: 'submission', id: value[:runtime][:sub_id])} )".html_safe %td - = "#{value[:memory][:user]} (#{value[:memory][:value]} @" + = link_to value[:memory][:user], controller: 'users', action: 'profile', id: value[:memory][:user_id] + = "(#{value[:memory][:value]} @" = "#{link_to("#" + value[:memory][:sub_id].to_s, controller: 'graders' , action: 'submission', id: value[:memory][:sub_id])} )".html_safe %td - = "#{value[:length][:user]} (#{value[:length][:value]} @" + = link_to value[:length][:user], controller: 'users', action: 'profile', id: value[:length][:user_id] + = "(#{value[:length][:value]} @" = "#{link_to("#" + value[:length][:sub_id].to_s, controller: 'graders' , action: 'submission', id: value[:length][:sub_id])} )".html_safe %td - = "#{value[:first][:user]} (#{value[:first][:value]} @" - = "#{link_to("#" + value[:first][:sub_id].to_s, controller: 'graders' , action: 'submission', id: value[:first][:sub_id])} )".html_safe + - if value[:first][:user] != '(NULL)' #TODO: i know... this is wrong... + = link_to value[:first][:user], controller: 'users', action: 'profile', id: value[:first][:user_id] + = "(#{value[:first][:value]} @" + = "#{link_to("#" + value[:first][:sub_id].to_s, controller: 'graders' , action: 'submission', id: value[:first][:sub_id])} )".html_safe - else %h3 No submissions diff --git a/app/views/report/login_stat.html.haml b/app/views/report/login_stat.html.haml --- a/app/views/report/login_stat.html.haml +++ b/app/views/report/login_stat.html.haml @@ -1,10 +1,12 @@ - content_for :header do + = stylesheet_link_tag 'tablesorter-theme.cafe' = javascript_include_tag 'new' %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 @@ -12,9 +14,9 @@ =render partial: 'report_menu' =render partial: 'date_range', locals: {param_text: 'Login date range:', title: 'Query login stat in the range' } -%table.info +%table.tablesorter-cafe#my_table %thead - %tr.info-head + %tr %th login %th full name %th login count @@ -23,9 +25,8 @@ %tbody - @logins.each do |l| %tr{class: cycle('info-even','info-odd')} - %td= l[:login] + %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') : '' - + %td= l[:max] ? time_ago_in_words(l[:max].in_time_zone) + ' ago' : '' diff --git a/app/views/user_admin/user_stat.html.erb b/app/views/user_admin/user_stat.html.erb deleted file mode 100644 --- a/app/views/user_admin/user_stat.html.erb +++ /dev/null @@ -1,48 +0,0 @@ -
Latest scores
- -User | -Name | -Activated? | -Logged in | -Contest(s) | -<% @problems.each do |p| %> -<%= p.name %> | -<% end %> -Total | -Passed | -
---|---|---|---|---|---|---|---|
<%= sc[i].login %> | -<%= sc[i].full_name %> | -<%= sc[i].activated %> | -- <%= sc[i].try(:contest_stat).try(:started_at)!=nil ? 'yes' : 'no' %> - | -- <%= sc[i].contests.collect {|c| c.name}.join(', ') %> - | - <% else %> -<%= sc[i][0] %> | - <% total += sc[i][0] %> - <% num_passed += 1 if sc[i][1] %> - <% end %> - <% end %> -<%= total %> | -<%= num_passed %> | -