Description:
Merge pull request #14 from nattee/master merge commit from nattee
Commit status:
[Not Reviewed]
References:
merge default
Comments:
0 Commit comments 0 Inline Comments
Unresolved TODOs:
There are no unresolved TODOs
Add another comment

r486:17754ce1a3d6 - - 65 files changed: 1499 inserted, 374 deleted

@@ -0,0 +1,14
1 + //= require jquery
2 + //= require jquery_ujs
3 + //= require jquery.ui.all
4 + //= require jquery.ui.datepicker
5 + //= require jquery.ui.slider
6 + //= require jquery-ui-timepicker-addon
7 + //= require jquery-tablesorter
8 + //= require best_in_place
9 + //= require best_in_place.jquery-ui
10 +
11 + $(document).ready(function() {
12 + /* Activating Best In Place */
13 + jQuery(".best_in_place").best_in_place();
14 + });
@@ -0,0 +1,3
1 + # Place all the behaviors and hooks related to the matching controller here.
2 + # All this logic will automatically be available in application.js.
3 + # You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/
@@ -0,0 +1,3
1 + // Place all the styles related to the report controller here.
2 + // They will automatically be included in application.css.
3 + // You can use Sass (SCSS) here: http://sass-lang.com/
@@ -0,0 +1,197
1 + /*************
2 + Metro Dark Theme
3 + *************/
4 + /* overall */
5 + .tablesorter-cafe {
6 + // font: 12px/18px 'Segoe UI Semilight', 'Open Sans', Verdana, Arial, Helvetica, sans-serif;
7 + color: #000;
8 + background-color: #777;
9 + margin: 10px 0 15px;
10 + text-align: left;
11 + border-collapse: collapse;
12 + border: #555 1px solid;
13 + }
14 +
15 + .tablesorter-cafe tr.dark-row th, .tablesorter-cafe tr.dark-row td {
16 + background-color: #222;
17 + color: #fff;
18 + text-align: left;
19 + font-size: 14px;
20 + }
21 +
22 + /* header/footer */
23 + .tablesorter-cafe caption,
24 + .tablesorter-cafe th,
25 + .tablesorter-cafe thead td,
26 + .tablesorter-cafe tfoot th,
27 + .tablesorter-cafe tfoot td {
28 + //font-weight: 300;
29 + //font-size: 15px;
30 + color: #fff;
31 + background-color: #777;
32 + padding: 2px;
33 + border: #555 1px solid;
34 + }
35 +
36 + .tablesorter-cafe .header,
37 + .tablesorter-cafe .tablesorter-header {
38 + background-image: url(data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAQBAMAAADQT4M0AAAAGFBMVEUAAADu7u7u7u7u7u7u7u7u7u7u7u7u7u5jNePWAAAACHRSTlMAMxIHKwEgMWD59H4AAABSSURBVAjXY2BgYFJgAAHzYhDJ6igSAKTYBAUTgJSioKAQAwNzoaCguAFDiCAQuDIkgigxBgiA8cJAVCpQt6AgSL+JoKAzA0gjUBsQqBcBCYhFAAE/CV4zeSzxAAAAAElFTkSuQmCC);
39 + background-position: center right;
40 + background-repeat: no-repeat;
41 + cursor: pointer;
42 + white-space: normal;
43 + }
44 + .tablesorter-cafe .tablesorter-header-inner {
45 + padding: 0 18px 0 4px;
46 + }
47 + .tablesorter-cafe thead .headerSortUp,
48 + .tablesorter-cafe thead .tablesorter-headerSortUp,
49 + .tablesorter-cafe thead .tablesorter-headerAsc {
50 + background-image: url(data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAQBAMAAADQT4M0AAAAIVBMVEUAAADu7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u5meJAOAAAACnRSTlMAMwsqXt+gIBUGxGoDMAAAAFlJREFUCNctzC0SQAAUReEzGNQ3AlHRiSRZFCVZYgeswRL8hLdK7834wj3tAlGP6y7fYHpKS6w6WwbVG0I1NZVnZPG8/DYxOYlnhUYkA06R1s9ESsxR4NIdPhkPFDFYuEnMAAAAAElFTkSuQmCC);
51 + }
52 + .tablesorter-cafe thead .headerSortDown,
53 + .tablesorter-cafe thead .tablesorter-headerSortDown,
54 + .tablesorter-cafe thead .tablesorter-headerDesc {
55 + background-image: url(data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAQBAMAAADQT4M0AAAALVBMVEUAAADu7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7i0NViAAAADnRSTlMAMiweCQITTvDctZZqaTlM310AAABcSURBVAjXY2BgYEtgAAFHERDJqigUAKSYBQUNgFSioKAYAwOLIBA4MASBKFUGQxAlzAAF+94BwWuGKBC1lIFl3rt3Lx0YGCzevWsGSjK9e6cAUlT3HKyW9wADAwDRrBiDy6bKzwAAAABJRU5ErkJggg==);
56 + }
57 + .tablesorter-cafe thead .sorter-false {
58 + background-image: none;
59 + cursor: default;
60 + padding: 4px;
61 + }
62 +
63 + /* tbody */
64 + .tablesorter-cafe td {
65 + background-color: #fff;
66 + padding: 1px 4px;
67 + vertical-align: top;
68 + border-style: solid;
69 + border-color: #666;
70 + border-collapse: collapse;
71 + border-width: 0px 1px;
72 +
73 + }
74 +
75 + /* hovered row colors */
76 + .tablesorter-cafe tbody > tr:hover > td,
77 + .tablesorter-cafe tbody > tr.even:hover > td,
78 + .tablesorter-cafe tbody > tr.odd:hover > td {
79 + background: #bbb;
80 + color: #000;
81 + }
82 +
83 + /* table processing indicator */
84 + .tablesorter-cafe .tablesorter-processing {
85 + background-position: center center !important;
86 + background-repeat: no-repeat !important;
87 + /* background-image: url(../addons/pager/icons/loading.gif) !important; */
88 + background-image: url(data:image/gif;base64,R0lGODlhFAAUAKEAAO7u7lpaWgAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQBCgACACwAAAAAFAAUAAACQZRvoIDtu1wLQUAlqKTVxqwhXIiBnDg6Y4eyx4lKW5XK7wrLeK3vbq8J2W4T4e1nMhpWrZCTt3xKZ8kgsggdJmUFACH5BAEKAAIALAcAAAALAAcAAAIUVB6ii7jajgCAuUmtovxtXnmdUAAAIfkEAQoAAgAsDQACAAcACwAAAhRUIpmHy/3gUVQAQO9NetuugCFWAAAh+QQBCgACACwNAAcABwALAAACE5QVcZjKbVo6ck2AF95m5/6BSwEAIfkEAQoAAgAsBwANAAsABwAAAhOUH3kr6QaAcSrGWe1VQl+mMUIBACH5BAEKAAIALAIADQALAAcAAAIUlICmh7ncTAgqijkruDiv7n2YUAAAIfkEAQoAAgAsAAAHAAcACwAAAhQUIGmHyedehIoqFXLKfPOAaZdWAAAh+QQFCgACACwAAAIABwALAAACFJQFcJiXb15zLYRl7cla8OtlGGgUADs=) !important;
89 + }
90 +
91 + /* pager */
92 + .tablesorter-cafe .tablesorter-pager button {
93 + background-color: #444;
94 + color: #eee;
95 + border: #555 1px solid;
96 + cursor: pointer;
97 + }
98 + .tablesorter-cafe .tablesorter-pager button:hover {
99 + background-color: #555;
100 + }
101 +
102 + /* Zebra Widget - row alternating colors */
103 + .tablesorter-cafe tr.odd td {
104 + background-color: #eee;
105 + }
106 + .tablesorter-cafe tr.even td {
107 + background-color: #fff;
108 + }
109 +
110 + /* Column Widget - column sort colors */
111 + .tablesorter-cafe tr.odd td.primary {
112 + background-color: #bfbfbf;
113 + }
114 + .tablesorter-cafe td.primary,
115 + .tablesorter-cafe tr.even td.primary {
116 + background-color: #d9d9d9;
117 + }
118 + .tablesorter-cafe tr.odd td.secondary {
119 + background-color: #d9d9d9;
120 + }
121 + .tablesorter-cafe td.secondary,
122 + .tablesorter-cafe tr.even td.secondary {
123 + background-color: #e6e6e6;
124 + }
125 + .tablesorter-cafe tr.odd td.tertiary {
126 + background-color: #e6e6e6;
127 + }
128 + .tablesorter-cafe td.tertiary,
129 + .tablesorter-cafe tr.even td.tertiary {
130 + background-color: #f2f2f2;
131 + }
132 +
133 + /* filter widget */
134 + .tablesorter-cafe .tablesorter-filter-row td {
135 + background: #eee;
136 + line-height: normal;
137 + text-align: center; /* center the input */
138 + -webkit-transition: line-height 0.1s ease;
139 + -moz-transition: line-height 0.1s ease;
140 + -o-transition: line-height 0.1s ease;
141 + transition: line-height 0.1s ease;
142 + }
143 + /* optional disabled input styling */
144 + .tablesorter-cafe .tablesorter-filter-row .disabled {
145 + opacity: 0.5;
146 + filter: alpha(opacity=50);
147 + cursor: not-allowed;
148 + }
149 + /* hidden filter row */
150 + .tablesorter-cafe .tablesorter-filter-row.hideme td {
151 + /*** *********************************************** ***/
152 + /*** change this padding to modify the thickness ***/
153 + /*** of the closed filter row (height = padding x 2) ***/
154 + padding: 2px;
155 + /*** *********************************************** ***/
156 + margin: 0;
157 + line-height: 0;
158 + cursor: pointer;
159 + }
160 + .tablesorter-cafe .tablesorter-filter-row.hideme .tablesorter-filter {
161 + height: 1px;
162 + min-height: 0;
163 + border: 0;
164 + padding: 0;
165 + margin: 0;
166 + /* don't use visibility: hidden because it disables tabbing */
167 + opacity: 0;
168 + filter: alpha(opacity=0);
169 + }
170 + /* filters */
171 + .tablesorter-cafe .tablesorter-filter {
172 + width: 95%;
173 + height: auto;
174 + margin: 4px;
175 + padding: 4px;
176 + background-color: #fff;
177 + border: 1px solid #bbb;
178 + color: #333;
179 + -webkit-box-sizing: border-box;
180 + -moz-box-sizing: border-box;
181 + box-sizing: border-box;
182 + -webkit-transition: height 0.1s ease;
183 + -moz-transition: height 0.1s ease;
184 + -o-transition: height 0.1s ease;
185 + transition: height 0.1s ease;
186 + }
187 + /* rows hidden by filtering (needed for child rows) */
188 + .tablesorter .filtered {
189 + display: none;
190 + }
191 +
192 + /* ajax error row */
193 + .tablesorter .tablesorter-errorRow td {
194 + text-align: center;
195 + cursor: pointer;
196 + background-color: #e6bf99;
197 + }
@@ -0,0 +1,218
1 + class ReportController < ApplicationController
2 +
3 + before_filter :admin_authorization, only: [:login_stat,:submission_stat, :stuck]
4 + before_filter(only: [:problem_hof]) { |c|
5 + return false unless authenticate
6 +
7 + if GraderConfiguration["right.user_view_submission"]
8 + return true;
9 + end
10 +
11 + admin_authorization
12 + }
13 +
14 + def login_stat
15 + @logins = Array.new
16 +
17 + date_and_time = '%Y-%m-%d %H:%M'
18 + begin
19 + md = params[:since_datetime].match(/(\d+)-(\d+)-(\d+) (\d+):(\d+)/)
20 + @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)
21 + rescue
22 + @since_time = DateTime.new(1000,1,1)
23 + end
24 + begin
25 + md = params[:until_datetime].match(/(\d+)-(\d+)-(\d+) (\d+):(\d+)/)
26 + @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)
27 + rescue
28 + @until_time = DateTime.new(3000,1,1)
29 + end
30 +
31 + User.all.each do |user|
32 + @logins << { id: user.id,
33 + login: user.login,
34 + full_name: user.full_name,
35 + count: Login.where("user_id = ? AND created_at >= ? AND created_at <= ?",
36 + user.id,@since_time,@until_time)
37 + .count(:id),
38 + min: Login.where("user_id = ? AND created_at >= ? AND created_at <= ?",
39 + user.id,@since_time,@until_time)
40 + .minimum(:created_at),
41 + max: Login.where("user_id = ? AND created_at >= ? AND created_at <= ?",
42 + user.id,@since_time,@until_time)
43 + .maximum(:created_at),
44 + ip: Login.where("user_id = ? AND created_at >= ? AND created_at <= ?",
45 + user.id,@since_time,@until_time)
46 + .select(:ip_address).uniq
47 +
48 + }
49 + end
50 + end
51 +
52 + def submission_stat
53 +
54 + date_and_time = '%Y-%m-%d %H:%M'
55 + begin
56 + @since_time = DateTime.strptime(params[:since_datetime],date_and_time)
57 + rescue
58 + @since_time = DateTime.new(1000,1,1)
59 + end
60 + begin
61 + @until_time = DateTime.strptime(params[:until_datetime],date_and_time)
62 + rescue
63 + @until_time = DateTime.new(3000,1,1)
64 + end
65 +
66 + @submissions = {}
67 +
68 + User.find_each do |user|
69 + @submissions[user.id] = { login: user.login, full_name: user.full_name, count: 0, sub: { } }
70 + end
71 +
72 + Submission.where("submitted_at >= ? AND submitted_at <= ?",@since_time,@until_time).find_each do |s|
73 + if @submissions[s.user_id]
74 + if not @submissions[s.user_id][:sub].has_key?(s.problem_id)
75 + a = nil
76 + begin
77 + a = Problem.find(s.problem_id)
78 + rescue
79 + a = nil
80 + end
81 + @submissions[s.user_id][:sub][s.problem_id] =
82 + { prob_name: (a ? a.full_name : '(NULL)'),
83 + sub_ids: [s.id] }
84 + else
85 + @submissions[s.user_id][:sub][s.problem_id][:sub_ids] << s.id
86 + end
87 + @submissions[s.user_id][:count] += 1
88 + end
89 + end
90 + end
91 +
92 + def problem_hof
93 + # gen problem list
94 + @user = User.find(session[:user_id])
95 + @problems = @user.available_problems
96 +
97 + # get selected problems or the default
98 + if params[:id]
99 + begin
100 + @problem = Problem.available.find(params[:id])
101 + rescue
102 + redirect_to action: :problem_hof
103 + flash[:notice] = 'Error: submissions for that problem are not viewable.'
104 + return
105 + end
106 + end
107 +
108 + return unless @problem
109 +
110 + @by_lang = {} #aggregrate by language
111 +
112 + range =65
113 + @histogram = { data: Array.new(range,0), summary: {} }
114 + @summary = {count: 0, solve: 0, attempt: 0}
115 + user = Hash.new(0)
116 + Submission.where(problem_id: @problem.id).find_each do |sub|
117 + #histogram
118 + d = (DateTime.now.in_time_zone - sub.submitted_at) / 24 / 60 / 60
119 + @histogram[:data][d.to_i] += 1 if d < range
120 +
121 + next unless sub.points
122 + @summary[:count] += 1
123 + user[sub.user_id] = [user[sub.user_id], (sub.points >= @problem.full_score) ? 1 : 0].max
124 +
125 + lang = Language.find_by_id(sub.language_id)
126 + next unless lang
127 + next unless sub.points >= @problem.full_score
128 +
129 + #initialize
130 + unless @by_lang.has_key?(lang.pretty_name)
131 + @by_lang[lang.pretty_name] = {
132 + runtime: { avail: false, value: 2**30-1 },
133 + memory: { avail: false, value: 2**30-1 },
134 + length: { avail: false, value: 2**30-1 },
135 + first: { avail: false, value: DateTime.new(3000,1,1) }
136 + }
137 + end
138 +
139 + if sub.max_runtime and sub.max_runtime < @by_lang[lang.pretty_name][:runtime][:value]
140 + @by_lang[lang.pretty_name][:runtime] = { avail: true, user_id: sub.user_id, value: sub.max_runtime, sub_id: sub.id }
141 + end
142 +
143 + if sub.peak_memory and sub.peak_memory < @by_lang[lang.pretty_name][:memory][:value]
144 + @by_lang[lang.pretty_name][:memory] = { avail: true, user_id: sub.user_id, value: sub.peak_memory, sub_id: sub.id }
145 + end
146 +
147 + if sub.submitted_at and sub.submitted_at < @by_lang[lang.pretty_name][:first][:value] and
148 + !sub.user.admin?
149 + @by_lang[lang.pretty_name][:first] = { avail: true, user_id: sub.user_id, value: sub.submitted_at, sub_id: sub.id }
150 + end
151 +
152 + if @by_lang[lang.pretty_name][:length][:value] > sub.effective_code_length
153 + @by_lang[lang.pretty_name][:length] = { avail: true, user_id: sub.user_id, value: sub.effective_code_length, sub_id: sub.id }
154 + end
155 + end
156 +
157 + #process user_id
158 + @by_lang.each do |lang,prop|
159 + prop.each do |k,v|
160 + v[:user] = User.exists?(v[:user_id]) ? User.find(v[:user_id]).full_name : "(NULL)"
161 + end
162 + end
163 +
164 + #sum into best
165 + if @by_lang and @by_lang.first
166 + @best = @by_lang.first[1].clone
167 + @by_lang.each do |lang,prop|
168 + if @best[:runtime][:value] >= prop[:runtime][:value]
169 + @best[:runtime] = prop[:runtime]
170 + @best[:runtime][:lang] = lang
171 + end
172 + if @best[:memory][:value] >= prop[:memory][:value]
173 + @best[:memory] = prop[:memory]
174 + @best[:memory][:lang] = lang
175 + end
176 + if @best[:length][:value] >= prop[:length][:value]
177 + @best[:length] = prop[:length]
178 + @best[:length][:lang] = lang
179 + end
180 + if @best[:first][:value] >= prop[:first][:value]
181 + @best[:first] = prop[:first]
182 + @best[:first][:lang] = lang
183 + end
184 + end
185 + end
186 +
187 + @histogram[:summary][:max] = [@histogram[:data].max,1].max
188 + @summary[:attempt] = user.count
189 + user.each_value { |v| @summary[:solve] += 1 if v == 1 }
190 + end
191 +
192 + def stuck #report struggling user,problem
193 + # init
194 + user,problem = nil
195 + solve = true
196 + tries = 0
197 + @struggle = Array.new
198 + record = {}
199 + Submission.includes(:problem,:user).order(:problem_id,:user_id).find_each do |sub|
200 + next unless sub.problem and sub.user
201 + if user != sub.user_id or problem != sub.problem_id
202 + @struggle << { user: record[:user], problem: record[:problem], tries: tries } unless solve
203 + record = {user: sub.user, problem: sub.problem}
204 + user,problem = sub.user_id, sub.problem_id
205 + solve = false
206 + tries = 0
207 + end
208 + if sub.points >= sub.problem.full_score
209 + solve = true
210 + else
211 + tries += 1
212 + end
213 + end
214 + @struggle.sort!{|a,b| b[:tries] <=> a[:tries] }
215 + @struggle = @struggle[0..50]
216 + end
217 +
218 + end
@@ -0,0 +1,2
1 + module ReportHelper
2 + end
@@ -0,0 +1,3
1 + class Login < ActiveRecord::Base
2 + attr_accessible :ip_address, :logged_in_at, :user_id
3 + end
@@ -0,0 +1,44
1 + - param = {} unless param
2 + - graph_height = param[:graph_height] || 100
3 + - bar_width = param[:bar_width] || 14
4 + - graph_width = (bar_width * histogram[:data].count) + 20
5 + :css
6 + .hist_bar {
7 + width: #{bar_width-1}px;
8 + position: absolute;
9 + background-color: lightblue;
10 + }
11 + .hist_fill {
12 + width: #{bar_width-1}px;
13 + position: absolute;
14 + background-color: #eee;
15 + }
16 + .hist_text {
17 + position: absolute;
18 + font-size:5px;
19 + }
20 +
21 + %div{style: "position: relative; width: #{graph_width}px; height: 125px; background-color:#fff;" }
22 + //draw background
23 + - histogram[:data].each_index do |i|
24 + - height = histogram[:data][i] * graph_height / histogram[:summary][:max]
25 + - top = graph_height - height
26 + - left = graph_width - (i+1)*bar_width
27 + %div.hist_fill{style: "top: 0px; height: #{graph_height - height}px; left: #{left}px;" }
28 + // draw horizontal line
29 + - line = 3
30 + - line.times do |i|
31 + - top = graph_height - graph_height * (i+0.5)/ line
32 + %div{style: "position:absolute;width: #{graph_width-21}px;height: 1px;left: 20px;top:#{top}px;background-color: #333;"}
33 + %div.hist_text{style: "position:absolute;left: 0px;top:#{top-6}px"}
34 + =((i+0.5) * histogram[:summary][:max] / line).to_i
35 + // draw the actual bar and text
36 + - @histogram[:data].each_index do |i|
37 + - height = histogram[:data][i] * graph_height / histogram[:summary][:max]
38 + - top = graph_height - height
39 + - left = graph_width - (i+1)*bar_width
40 + %div.hist_bar{style: "top: #{top}px; height: #{height}px; left: #{left}px; dae: #{histogram[:data][i]}" }
41 + - if i % 7 == 1
42 + %div.hist_text{style: "top:#{graph_height + 5}px;left: #{left}px;"} #{(Time.zone.today - i.day).strftime('%-d')}
43 + - if (Time.now.in_time_zone - i.day).day == 15
44 + %div.hist_text{style: "top:#{graph_height + 15}px;left: #{left}px;"} #{(Time.zone.today - i.day).strftime('%b')}
@@ -0,0 +1,51
1 + :css
2 + .fix-width {
3 + font-family: "Consolas, Monaco, Droid Sans Mono,Mono, Monospace,Courier"
4 + }
5 +
6 + %h1 Problem stat: #{@problem.name}
7 + %h2 Overview
8 +
9 +
10 + %table.info
11 + %thead
12 + %tr.info-head
13 + %th Stat
14 + %th Value
15 + %tbody
16 + %tr{class: cycle('info-even','info-odd')}
17 + %td Submissions
18 + %td= @submissions.count
19 + %tr{class: cycle('info-even','info-odd')}
20 + %td Solved/Attempted User
21 + %td #{@summary[:solve]}/#{@summary[:attempt]} (#{(@summary[:solve]*100.0/@summary[:attempt]).round(1)}%)
22 +
23 + %h2 Submissions Count
24 + = render partial: 'application/bar_graph', locals: { histogram: @histogram }
25 +
26 + %h2 Submissions
27 + - if @submissions and @submissions.count > 0
28 + %table.info#main_table
29 + %thead
30 + %tr.info-head
31 + %th ID
32 + %th Login
33 + %th Name
34 + %th Submitted_at
35 + %th Points
36 + %th comment
37 + %tbody
38 + - row_odd,curr = true,''
39 + - @submissions.each do |sub|
40 + - next unless sub.user
41 + - row_odd,curr = !row_odd, sub.user if curr != sub.user
42 + %tr{class: row_odd ? "info-odd" : "info-even"}
43 + %td= link_to sub.id, controller: 'graders', action: 'submission', id: sub.id
44 + %td= link_to sub.user.login, controller: :users, action: :profile, id: sub.user.id
45 + %td= sub.user.full_name
46 + %td= time_ago_in_words(sub.submitted_at) + " ago"
47 + %td= sub.points
48 + %td.fix-width= sub.grader_comment
49 + - else
50 + No submission
51 +
@@ -0,0 +1,8
1 + %h2 Paid in Full
2 + User with highest number of problem solved
3 +
4 + %h2 Polymaths
5 + User with highest number of problems each solved by more than 1 languages.
6 +
7 + %h2 Icebreakers
8 + If you solve the problem before 95% of your friends, you are an icebreaker.
@@ -0,0 +1,23
1 +
2 + = form_tag({session: :url }) do
3 + .submitbox
4 + %table
5 + %tr
6 + %td{colspan: 6, style: 'font-weight: bold'}= title
7 + %tr
8 + %td{style: 'width: 120px; font-weight: bold'}= param_text
9 + %td{align: 'right'} since:
10 + %td= text_field_tag 'since_datetime'
11 + %tr
12 + %td
13 + %td{align: 'right'} until:
14 + %td= text_field_tag 'until_datetime'
15 + %tr
16 + %td
17 + %td
18 + %td Blank mean no condition
19 + %tr
20 + %td
21 + %td
22 + %td= submit_tag 'query'
23 +
@@ -0,0 +1,7
1 +
2 + .task-menu
3 + Reports
4 + %br/
5 + = link_to '[Hall of Fame]', :action => 'problem_hof'
6 + = link_to '[Struggle]', :action => 'stuck'
7 + = link_to '[Login]', :action => 'login_stat'
@@ -0,0 +1,127
1 + - content_for :header do
2 + = javascript_include_tag 'local_jquery'
3 +
4 + :javascript
5 + $(document).ready( function() {
6 + $("#mem_remark").hover( function() {
7 + $("#mem_remark_box").show();
8 + }, function() {
9 + $("#mem_remark_box").hide();
10 + });
11 + });
12 + :css
13 + .hof_user { color: orangered; font-style: italic; }
14 + .hof_language { color: green; font-style: italic; }
15 + .hof_value { color: deeppink;font-style: italic; }
16 + .info_param { font-weight: bold;text-align: right; }
17 + .tooltip {
18 + font-family: Verdana,sans-serif;
19 + font-weight: normal;
20 + text-align: left;
21 + font-size: 1.0em;
22 + color: black;
23 + line-height: 1.1;
24 + display: none;
25 + min-width: 20em;
26 + position: absolute;
27 + left: 25px;
28 + bottom: 5px;
29 + border: 1px solid;
30 + padding: 5px;
31 + background-color: #FFF;
32 + word-wrap: break-word;
33 + z-index: 9999;
34 + overflow: auto;
35 + }
36 +
37 + %h1 (#{Problem.find(params[:id]).name}) #{Problem.find(params[:id]).full_name}
38 +
39 + %h2 Problem Stat
40 + %table.info
41 + %thead
42 + %tr.info-head
43 + %th Stat
44 + %th Value
45 + %tbody
46 + %tr{class: cycle('info-even','info-odd')}
47 + %td.info_param Submissions
48 + %td= @summary[:count]
49 + %tr{class: cycle('info-even','info-odd')}
50 + %td.info_param Solved/Attempted User
51 + %td #{@summary[:solve]}/#{@summary[:attempt]} (#{(@summary[:solve]*100.0/@summary[:attempt]).round(1)}%)
52 + - if @best
53 + %tr{class: cycle('info-even','info-odd')}
54 + %td.info_param Best Runtime
55 + %td
56 + by #{link_to @best[:runtime][:user], controller:'users', action:'profile', id:@best[:memory][:user_id]}
57 + using <span class="hof_language">#{@best[:runtime][:lang]}</span>
58 + with <span class="hof_value">#{@best[:runtime][:value] * 1000} milliseconds</span>
59 + at submission
60 + = link_to("#" + @best[:runtime][:sub_id].to_s, controller: 'graders', action: 'submission', id:@best[:runtime][:sub_id])
61 +
62 + %tr{class: cycle('info-even','info-odd')}
63 + %td.info_param
64 + Best Memory Usage
65 + %sup{ id: "mem_remark", style: "position:relative; color: blue;"}
66 + [?]
67 + %span.tooltip#mem_remark_box
68 + This counts only for submission with 100% score.
69 + Right now, java is excluded from memory usage competition. (Because it always uses 2GB memory...)
70 + %td
71 + by #{link_to @best[:memory][:user], controller:'users', action:'profile', id:@best[:memory][:user_id]}
72 + using <span class="hof_language">#{@best[:memory][:lang]}</span>
73 + with <span class="hof_value">#{number_with_delimiter(@best[:memory][:value])} kbytes </span>
74 + at submission
75 + = link_to("#" + @best[:memory][:sub_id].to_s, controller: 'graders' , action: 'submission', id:@best[:memory][:sub_id])
76 +
77 + %tr{class: cycle('info-even','info-odd')}
78 + %td.info_param Shortest Code
79 + %td
80 + by #{link_to @best[:length][:user], controller:'users', action:'profile', id:@best[:length][:user_id]}
81 + using <span class="hof_language">#{@best[:length][:lang]}</span>
82 + with <span class="hof_value">#{@best[:length][:value]} bytes</span>
83 + at submission
84 + = link_to("#" + @best[:length][:sub_id].to_s, controller: 'graders' , action: 'submission', id: @best[:length][:sub_id])
85 +
86 + %tr{class: cycle('info-even','info-odd')}
87 + %td.info_param First solver
88 + %td
89 + #{link_to @best[:first][:user], controller:'users', action:'profile', id:@best[:first][:user_id]} is the first solver
90 + using <span class="hof_language">#{@best[:first][:lang]}</span>
91 + on <span class="hof_value">#{@best[:first][:value]}</span>
92 + at submission
93 + = link_to("#" + @best[:first][:sub_id].to_s, controller: 'graders' , action: 'submission', id: @best[:first][:sub_id])
94 +
95 + - if @best
96 + %h2 By language
97 +
98 + %table.info
99 + %thead
100 + %tr.info-head
101 + %th Language
102 + %th Best runtime (ms)
103 + %th Best memory (kbytes)
104 + %th Shortest Code (bytes)
105 + %th First solver
106 + %tbody
107 + - @by_lang.each do |lang,value|
108 + %tr{class: cycle('info-even','info-odd')}
109 + %td= lang
110 + %td
111 + = link_to value[:runtime][:user], controller: 'users', action: 'profile', id: value[:runtime][:user_id]
112 + = "(#{(value[:runtime][:value] * 1000).to_i} @"
113 + = "#{link_to("#" + value[:runtime][:sub_id].to_s, controller: 'graders' , action: 'submission', id: value[:runtime][:sub_id])} )".html_safe
114 + %td
115 + = link_to value[:memory][:user], controller: 'users', action: 'profile', id: value[:memory][:user_id]
116 + = "(#{number_with_delimiter(value[:memory][:value])} @"
117 + = "#{link_to("#" + value[:memory][:sub_id].to_s, controller: 'graders' , action: 'submission', id: value[:memory][:sub_id])} )".html_safe
118 + %td
119 + = link_to value[:length][:user], controller: 'users', action: 'profile', id: value[:length][:user_id]
120 + = "(#{value[:length][:value]} @"
121 + = "#{link_to("#" + value[:length][:sub_id].to_s, controller: 'graders' , action: 'submission', id: value[:length][:sub_id])} )".html_safe
122 + %td
123 + - if value[:first][:user] != '(NULL)' #TODO: i know... this is wrong...
124 + = link_to value[:first][:user], controller: 'users', action: 'profile', id: value[:first][:user_id]
125 + = "(#{value[:first][:value]} @"
126 + = "#{link_to("#" + value[:first][:sub_id].to_s, controller: 'graders' , action: 'submission', id: value[:first][:sub_id])} )".html_safe
127 +
@@ -0,0 +1,36
1 + - content_for :header do
2 + = stylesheet_link_tag 'tablesorter-theme.cafe'
3 + = javascript_include_tag 'local_jquery'
4 +
5 + %script{:type=>"text/javascript"}
6 + $(function () {
7 + $('#since_datetime').datetimepicker({ showButtonPanel: true, dateFormat: "yy-mm-dd", controlType: "slider"} );
8 + $('#until_datetime').datetimepicker({ showButtonPanel: true, dateFormat: "yy-mm-dd", controlType: "slider"} );
9 + $('#my_table').tablesorter({widthFixed: true, widgets: ['zebra']});
10 + });
11 +
12 + %h1 Login status
13 +
14 + =render partial: 'report_menu'
15 + =render partial: 'date_range', locals: {param_text: 'Login date range:', title: 'Query login stat in the range' }
16 +
17 + %table.tablesorter-cafe#my_table
18 + %thead
19 + %tr
20 + %th login
21 + %th full name
22 + %th login count
23 + %th earliest
24 + %th latest
25 + %th IP
26 + %tbody
27 + - @logins.each do |l|
28 + %tr{class: cycle('info-even','info-odd')}
29 + %td= link_to l[:login], controller: 'users', action: 'profile', id: l[:id]
30 + %td= l[:full_name]
31 + %td= l[:count]
32 + %td= l[:min] ? l[:min].in_time_zone.strftime('%Y-%m-%d %H:%M') : ''
33 + %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)" : ''
34 + %td
35 + - l[:ip].each do |ip|
36 + #{ip.ip_address} <br/>
@@ -0,0 +1,23
1 +
2 + /- if params[:id]
3 + / %h1 Tasks Hall of Fame
4 + / = link_to('[back to All-Time Hall of Fame]', action: 'problem_hof', id: nil )
5 + /- else
6 + / %h1 All-Time Hall of Fame
7 +
8 +
9 + %h1 Hall of Fame
10 + .task-menu
11 + Tasks
12 + %br/
13 + - @problems.each do |prob|
14 + = link_to( "[#{prob.name}]", {id: prob.id})
15 +
16 + - unless params[:id]
17 + /=render partial: 'all_time_hof'
18 + Please select a problem.
19 + - else
20 + =render partial: 'task_hof'
21 + %h2 Submission History
22 + =render partial: 'application/bar_graph', locals: { histogram: @histogram }
23 +
@@ -0,0 +1,17
1 + %table.info
2 + %thead
3 + %tr.info-head
4 + %th Problem
5 + %th User
6 + %th tries
7 + %tbody
8 + - @struggle.each do |s|
9 + %tr
10 + %td
11 + = link_to "(#{s[:problem].name})", controller: :problems, action: :stat, id: s[:problem]
12 + = s[:problem].full_name
13 + %td
14 + = link_to "(#{s[:user].login})", controller: :users, action: :profile, id: s[:user]
15 + = s[:user].full_name
16 + %td
17 + = s[:tries]
@@ -0,0 +1,37
1 + - content_for :header do
2 + = javascript_include_tag 'local_jquery'
3 +
4 + %script{:type=>"text/javascript"}
5 + $(function () {
6 + $('#since_datetime').datetimepicker({ showButtonPanel: true, dateFormat: "yy-mm-dd", controlType: "slider"} );
7 + $('#until_datetime').datetimepicker({ showButtonPanel: true, dateFormat: "yy-mm-dd", controlType: "slider"} );
8 + });
9 +
10 + %h1 Login status
11 +
12 + =render partial: 'report_menu'
13 + =render partial: 'date_range', locals: {param_text: 'Submission date range:', title: 'Query submission stat in the range' }
14 +
15 + %table.info
16 + %thead
17 + %tr.info-head
18 + %th login
19 + %th full name
20 + %th total submissions
21 + %th submissions
22 + %tbody
23 + - @submissions.each do |user_id,data|
24 + %tr{class: cycle('info-even','info-odd')}
25 + %td= data[:login]
26 + %td= data[:full_name]
27 + %td= data[:count]
28 + %td
29 + - data[:sub].each do |prob_id,sub_data|
30 + = "#{sub_data[:prob_name]}: ["
31 + - st = []
32 + - sub_data[:sub_ids].each do |id|
33 + - st << link_to(id, controller: 'graders' , action: 'submission', id: id)
34 + = raw st.join ', '
35 + = ']'
36 + %br/
37 +
@@ -0,0 +1,11
1 + %h1 Editing user
2 +
3 + = form_tag :action => 'update', :id => @user do
4 + = error_messages_for 'user'
5 + = render partial: "form"
6 + = submit_tag "Edit"
7 +
8 +
9 + = link_to 'Show', :action => 'show', :id => @user
10 + |
11 + = link_to 'Back', :action => 'list'
@@ -0,0 +1,59
1 + - content_for :header do
2 + = javascript_include_tag 'local_jquery'
3 + = stylesheet_link_tag 'tablesorter-theme.cafe'
4 +
5 + %script{:type=>"text/javascript"}
6 + $(function () {
7 + $('#since_datetime').datetimepicker({ showButtonPanel: true, dateFormat: "yy-mm-dd", controlType: "slider"} );
8 + $('#until_datetime').datetimepicker({ showButtonPanel: true, dateFormat: "yy-mm-dd", controlType: "slider"} );
9 + $('#my_table').tablesorter({widgets: ['zebra']});
10 + });
11 +
12 + %h1 User grading results
13 + %h2= params[:action] == 'user_stat' ? "Show scores from latest submission" : "Show max scores in submission range"
14 +
15 +
16 + - if @problem and @problem.errors
17 + =error_messages_for 'problem'
18 +
19 + = render partial: 'submission_range'
20 +
21 + - if params[:action] == 'user_stat'
22 + %h3 Latest score
23 + = link_to '[download csv with all problems]', controller: :user_admin, action: :user_stat, commit: 'download csv'
24 + - else
25 + %h3 Max score
26 + = link_to '[Show only latest submissions]', controller: :user_admin, action: :user_stat
27 + = link_to '[download csv with all problems]', controller: :user_admin, action: :user_stat_max, commit: 'download csv'
28 +
29 + %table.tablesorter-cafe#my_table
30 + %thead
31 + %tr
32 + %th User
33 + %th Name
34 + %th Activated?
35 + %th Logged in
36 + %th Contest(s)
37 + %th Remark
38 + - @problems.each do |p|
39 + %th= p.name
40 + %th Total
41 + %th Passed
42 + %tbody
43 + - @scorearray.each do |sc|
44 + %tr{class: cycle('info-even','info-odd')}
45 + - total,num_passed = 0,0
46 + - sc.each_index do |i|
47 + - if i == 0
48 + %td= link_to sc[i].login, controller: 'users', action: 'profile', id: sc[i]
49 + %td= sc[i].full_name
50 + %td= sc[i].activated
51 + %td= sc[i].try(:contest_stat).try(:started_at)!=nil ? 'yes' : 'no'
52 + %td= sc[i].contests.collect {|c| c.name}.join(', ')
53 + %td= sc[i].remark
54 + - else
55 + %td= sc[i][0]
56 + - total += sc[i][0]
57 + - num_passed += 1 if sc[i][1]
58 + %td= total
59 + %td= num_passed
@@ -0,0 +1,66
1 + - content_for :header do
2 + = javascript_include_tag 'local_jquery'
3 +
4 + :javascript
5 + $(function () {
6 + $('#submission_table').tablesorter({widgets: ['zebra']});
7 + });
8 +
9 + :css
10 + .fix-width {
11 + font-family: Droid Sans Mono,Consolas, monospace, mono, Courier New, Courier;
12 + }
13 +
14 + %h1= @user.full_name
15 +
16 + <b>Login:</b> #{@user.login} <br/>
17 + <b>Full name:</b> #{@user.full_name} <br />
18 +
19 +
20 + %h2 Problem Stat
21 + %table.info
22 + %thead
23 + %tr.info-head
24 + %th Stat
25 + %th Value
26 + %tbody
27 + %tr{class: cycle('info-even','info-odd')}
28 + %td.info_param Submissions
29 + %td= @summary[:count]
30 + %tr{class: cycle('info-even','info-odd')}
31 + %td.info_param Solved/Attempted Problem
32 + %td #{@summary[:solve]}/#{@summary[:attempt]} (#{(@summary[:solve]*100.0/@summary[:attempt]).round(1)}%)
33 +
34 + %h2 Submission History
35 +
36 + =render partial: 'application/bar_graph', locals: {histogram: @histogram, param: {bar_width: 7}}
37 +
38 +
39 + %table.tablesorter-cafe#submission_table
40 + %thead
41 + %tr
42 + %th ID
43 + %th Problem code
44 + %th Problem full name
45 + %th Language
46 + %th Submitted at
47 + %th Result
48 + %th Score
49 + - if session[:admin]
50 + %th IP
51 + %tbody
52 + - @submission.each do |s|
53 + - next unless s.problem
54 + %tr
55 + %td= link_to "#{s.id}", controller: "graders", action: "submission", id: s.id
56 + %td= link_to s.problem.name, controller: "problems", action: "stat", id: s.problem
57 + %td= s.problem.full_name
58 + %td= s.language.pretty_name
59 + %td #{s.submitted_at.strftime('%Y-%m-%d %H:%M')} (#{time_ago_in_words(s.submitted_at)} ago)
60 + %td.fix-width= s.grader_comment
61 + %td= (s.points*100)/s.problem.full_score
62 + - if session[:admin]
63 + %td= s.ip_address
64 +
65 +
66 +
@@ -0,0 +1,7
1 + class AddMoreDetailToSubmission < ActiveRecord::Migration
2 + def change
3 + add_column :submissions, :max_runtime, :float
4 + add_column :submissions, :peak_memory, :integer
5 + add_column :submissions, :effective_code_length, :integer
6 + end
7 + end
@@ -0,0 +1,10
1 + class CreateLogins < ActiveRecord::Migration
2 + def change
3 + create_table :logins do |t|
4 + t.string :user_id
5 + t.string :ip_address
6 +
7 + t.timestamps
8 + end
9 + end
10 + end
@@ -0,0 +1,5
1 + class AddIpToSubmissions < ActiveRecord::Migration
2 + def change
3 + add_column :submissions, :ip_address, :string
4 + end
5 + end
@@ -0,0 +1,6
1 + class AddMoreToUsers < ActiveRecord::Migration
2 + def change
3 + add_column :users, :enabled, :boolean, default: 1
4 + add_column :users, :remark, :string
5 + end
6 + end
@@ -0,0 +1,5
1 + require 'spec_helper'
2 +
3 + describe Login do
4 + pending "add some examples to (or delete) #{__FILE__}"
5 + end
@@ -1,19 +1,26
1 1 # See http://help.github.com/ignore-files/ for more about ignoring files.
2 2 #
3 3 # If you find yourself ignoring temporary files generated by your text editor
4 4 # or operating system, you probably want to add a global ignore instead:
5 5 # git config --global core.excludesfile ~/.gitignore_global
6 6
7 7 # Ignore bundler config
8 8 /.bundle
9 9
10 10 # Ignore the default SQLite database.
11 11 /db/*.sqlite3
12 12
13 13 # Ignore all logfiles and tempfiles.
14 14 /log/*.log
15 15 /tmp
16 16
17 17 *~
18 18
19 19 /vendor/plugins/rails_upgrade
20 +
21 + #ignore public assets???
22 + /public/assets
23 +
24 + #ignore .orig and .swp
25 + *.orig
26 + *.swp
@@ -14,37 +14,50
14 14 gem 'coffee-rails', '~> 3.2.2'
15 15
16 16 # See https://github.com/sstephenson/execjs#readme for more supported runtimes
17 17 # gem 'therubyracer', :platforms => :ruby
18 18
19 19 gem 'uglifier'
20 20 end
21 21
22 22 gem 'prototype-rails'
23 23
24 24 # To use ActiveModel has_secure_password
25 25 # gem 'bcrypt-ruby', '~> 3.0.0'
26 26
27 27 # To use Jbuilder templates for JSON
28 28 # gem 'jbuilder'
29 29
30 30 # Use unicorn as the app server
31 31 # gem 'unicorn'
32 32
33 33 # Deploy with Capistrano
34 34 # gem 'capistrano'
35 35
36 36 # To use debugger
37 37 # gem 'debugger'
38 + #
39 +
40 + #in-place editor
41 + gem 'best_in_place', '~> 3.0.1'
42 +
43 + # jquery addition
44 + gem 'jquery-rails'
45 + gem 'jquery-ui-sass-rails'
46 + gem 'jquery-timepicker-addon-rails'
47 + gem 'jquery-tablesorter'
48 +
49 + #syntax highlighter
50 + gem 'rouge'
38 51
39 52 gem 'haml'
40 53 gem 'mail'
41 54 gem 'rdiscount'
42 55 gem 'test-unit'
43 56 gem 'will_paginate', '~> 3.0.7'
44 57 gem 'dynamic_form'
45 58 gem 'in_place_editing'
46 59 gem 'verification', :git => 'https://github.com/sikachu/verification.git'
47 60
48 61 group :test, :development do
49 62 gem 'rspec-rails', '~> 2.99.0'
50 63 end
@@ -16,136 +16,163
16 16 activemodel (= 3.2.21)
17 17 activesupport (= 3.2.21)
18 18 builder (~> 3.0.0)
19 19 erubis (~> 2.7.0)
20 20 journey (~> 1.0.4)
21 21 rack (~> 1.4.5)
22 22 rack-cache (~> 1.2)
23 23 rack-test (~> 0.6.1)
24 24 sprockets (~> 2.2.1)
25 25 activemodel (3.2.21)
26 26 activesupport (= 3.2.21)
27 27 builder (~> 3.0.0)
28 28 activerecord (3.2.21)
29 29 activemodel (= 3.2.21)
30 30 activesupport (= 3.2.21)
31 31 arel (~> 3.0.2)
32 32 tzinfo (~> 0.3.29)
33 33 activeresource (3.2.21)
34 34 activemodel (= 3.2.21)
35 35 activesupport (= 3.2.21)
36 36 activesupport (3.2.21)
37 37 i18n (~> 0.6, >= 0.6.4)
38 38 multi_json (~> 1.0)
39 39 arel (3.0.3)
40 + best_in_place (3.0.2)
41 + actionpack (>= 3.2)
42 + railties (>= 3.2)
40 43 builder (3.0.4)
41 44 coffee-rails (3.2.2)
42 45 coffee-script (>= 2.2.0)
43 46 railties (~> 3.2.0)
44 47 coffee-script (2.3.0)
45 48 coffee-script-source
46 49 execjs
47 50 coffee-script-source (1.8.0)
48 51 diff-lcs (1.2.5)
49 52 dynamic_form (1.1.4)
50 53 erubis (2.7.0)
51 54 execjs (2.2.2)
52 55 haml (4.0.6)
53 56 tilt
54 57 hike (1.2.3)
55 58 i18n (0.7.0)
56 59 in_place_editing (1.2.0)
57 60 journey (1.0.4)
58 61 json (1.8.1)
59 62 mail (2.5.4)
63 + jquery-rails (3.1.1)
64 + railties (>= 3.0, < 5.0)
65 + thor (>= 0.14, < 2.0)
66 + jquery-tablesorter (1.12.7)
67 + railties (>= 3.1, < 5)
68 + jquery-timepicker-addon-rails (1.4.1)
69 + railties (>= 3.1)
70 + jquery-ui-rails (4.0.3)
71 + jquery-rails
72 + railties (>= 3.1.0)
73 + jquery-ui-sass-rails (4.0.3.0)
74 + jquery-rails
75 + jquery-ui-rails (= 4.0.3)
76 + railties (>= 3.1.0)
77 + json (1.8.1)
78 + mail (2.5.4)
60 79 mime-types (~> 1.16)
61 80 treetop (~> 1.4.8)
62 81 mime-types (1.25.1)
63 82 multi_json (1.10.1)
64 83 mysql2 (0.3.17)
65 84 polyglot (0.3.5)
66 85 power_assert (0.2.2)
67 86 prototype-rails (3.2.1)
68 87 rails (~> 3.2)
69 88 rack (1.4.5)
70 89 rack-cache (1.2)
71 90 rack (>= 0.4)
72 91 rack-ssl (1.3.4)
73 92 rack
74 93 rack-test (0.6.2)
75 94 rack (>= 1.0)
76 95 rails (3.2.21)
77 96 actionmailer (= 3.2.21)
78 97 actionpack (= 3.2.21)
79 98 activerecord (= 3.2.21)
80 99 activeresource (= 3.2.21)
81 100 activesupport (= 3.2.21)
82 101 bundler (~> 1.0)
83 102 railties (= 3.2.21)
84 103 railties (3.2.21)
85 104 actionpack (= 3.2.21)
86 105 activesupport (= 3.2.21)
87 106 rack-ssl (~> 1.3.2)
88 107 rake (>= 0.8.7)
89 108 rdoc (~> 3.4)
90 109 thor (>= 0.14.6, < 2.0)
91 110 rake (10.4.2)
92 111 rdiscount (2.1.7.1)
93 112 rdoc (3.12.2)
94 113 json (~> 1.4)
95 - rspec-collection_matchers (1.1.2)
114 + rouge (1.6.2)
115 + rspec-collection_matchers (1.0.0)
96 116 rspec-expectations (>= 2.99.0.beta1)
97 117 rspec-core (2.99.2)
98 118 rspec-expectations (2.99.2)
99 119 diff-lcs (>= 1.1.3, < 2.0)
100 120 rspec-mocks (2.99.2)
101 121 rspec-rails (2.99.0)
102 122 actionpack (>= 3.0)
103 123 activemodel (>= 3.0)
104 124 activesupport (>= 3.0)
105 125 railties (>= 3.0)
106 126 rspec-collection_matchers
107 127 rspec-core (~> 2.99.0)
108 128 rspec-expectations (~> 2.99.0)
109 129 rspec-mocks (~> 2.99.0)
110 130 sass (3.4.9)
111 131 sass-rails (3.2.6)
112 132 railties (~> 3.2.0)
113 133 sass (>= 3.1.10)
114 134 tilt (~> 1.3)
115 135 sprockets (2.2.3)
116 136 hike (~> 1.2)
117 137 multi_json (~> 1.0)
118 138 rack (~> 1.0)
119 139 tilt (~> 1.1, != 1.3.0)
120 140 test-unit (3.0.9)
121 141 power_assert
122 142 thor (0.19.1)
123 143 tilt (1.4.1)
124 144 treetop (1.4.15)
125 145 polyglot
126 146 polyglot (>= 0.3.1)
127 147 tzinfo (0.3.42)
128 148 uglifier (2.6.0)
129 149 execjs (>= 0.3.0)
130 150 json (>= 1.8.0)
131 151 will_paginate (3.0.7)
132 152
133 153 PLATFORMS
134 154 ruby
135 155
136 156 DEPENDENCIES
157 + best_in_place (~> 3.0.1)
137 158 coffee-rails (~> 3.2.2)
138 159 dynamic_form
139 160 haml
140 161 in_place_editing
162 + jquery-rails
163 + jquery-tablesorter
164 + jquery-timepicker-addon-rails
165 + jquery-ui-sass-rails
141 166 mail
142 167 mysql2
143 168 prototype-rails
144 169 rails (= 3.2.21)
145 170 rdiscount
146 - rspec-rails (~> 2.99.0)
171 + rouge
147 172 sass-rails (~> 3.2.6)
173 + rspec-rails (~> 2.0)
174 +
148 175 test-unit
149 176 uglifier
150 177 verification!
151 178 will_paginate (~> 3.0.7)
@@ -1,182 +1,10
1 - == Welcome to Rails
2 -
3 - Rails is a web-application and persistence framework that includes everything
4 - needed to create database-backed web-applications according to the
5 - Model-View-Control pattern of separation. This pattern splits the view (also
6 - called the presentation) into "dumb" templates that are primarily responsible
7 - for inserting pre-built data in between HTML tags. The model contains the
8 - "smart" domain objects (such as Account, Product, Person, Post) that holds all
9 - the business logic and knows how to persist themselves to a database. The
10 - controller handles the incoming requests (such as Save New Account, Update
11 - Product, Show Post) by manipulating the model and directing data to the view.
12 -
13 - In Rails, the model is handled by what's called an object-relational mapping
14 - layer entitled Active Record. This layer allows you to present the data from
15 - database rows as objects and embellish these data objects with business logic
16 - methods. You can read more about Active Record in
17 - link:files/vendor/rails/activerecord/README.html.
18 -
19 - The controller and view are handled by the Action Pack, which handles both
20 - layers by its two parts: Action View and Action Controller. These two layers
21 - are bundled in a single package due to their heavy interdependence. This is
22 - unlike the relationship between the Active Record and Action Pack that is much
23 - more separate. Each of these packages can be used independently outside of
24 - Rails. You can read more about Action Pack in
25 - link:files/vendor/rails/actionpack/README.html.
26 -
27 -
28 - == Getting started
29 -
30 - 1. At the command prompt, start a new rails application using the rails command
31 - and your application name. Ex: rails myapp
32 - (If you've downloaded rails in a complete tgz or zip, this step is already done)
33 - 2. Change directory into myapp and start the web server: <tt>script/server</tt> (run with --help for options)
34 - 3. Go to http://localhost:3000/ and get "Welcome aboard: You’re riding the Rails!"
35 - 4. Follow the guidelines to start developing your application
36 -
37 -
38 - == Web Servers
39 -
40 - By default, Rails will try to use Mongrel and lighttpd if they are installed, otherwise
41 - Rails will use the WEBrick, the webserver that ships with Ruby. When you run script/server,
42 - Rails will check if Mongrel exists, then lighttpd and finally fall back to WEBrick. This ensures
43 - that you can always get up and running quickly.
1 + == cafe grader
44 2
45 - Mongrel is a Ruby-based webserver with a C-component (which requires compilation) that is
46 - suitable for development and deployment of Rails applications. If you have Ruby Gems installed,
47 - getting up and running with mongrel is as easy as: <tt>gem install mongrel</tt>.
48 - More info at: http://mongrel.rubyforge.org
49 -
50 - If Mongrel is not installed, Rails will look for lighttpd. It's considerably faster than
51 - Mongrel and WEBrick and also suited for production use, but requires additional
52 - installation and currently only works well on OS X/Unix (Windows users are encouraged
53 - to start with Mongrel). We recommend version 1.4.11 and higher. You can download it from
54 - http://www.lighttpd.net.
55 -
56 - And finally, if neither Mongrel or lighttpd are installed, Rails will use the built-in Ruby
57 - web server, WEBrick. WEBrick is a small Ruby web server suitable for development, but not
58 - for production.
59 -
60 - But of course its also possible to run Rails on any platform that supports FCGI.
61 - Apache, LiteSpeed, IIS are just a few. For more information on FCGI,
62 - please visit: http://wiki.rubyonrails.com/rails/pages/FastCGI
63 -
64 -
65 - == Debugging Rails
66 -
67 - Have "tail -f" commands running on the server.log and development.log. Rails will
68 - automatically display debugging and runtime information to these files. Debugging
69 - info will also be shown in the browser on requests from 127.0.0.1.
70 -
71 -
72 - == Breakpoints
73 -
74 - Breakpoint support is available through the script/breakpointer client. This
75 - means that you can break out of execution at any point in the code, investigate
76 - and change the model, AND then resume execution! Example:
77 -
78 - class WeblogController < ActionController::Base
79 - def index
80 - @posts = Post.find(:all)
81 - breakpoint "Breaking out from the list"
82 - end
83 - end
84 -
85 - So the controller will accept the action, run the first line, then present you
86 - with a IRB prompt in the breakpointer window. Here you can do things like:
87 -
88 - Executing breakpoint "Breaking out from the list" at .../webrick_server.rb:16 in 'breakpoint'
3 + cafe grader is a programming contest platform used in Thailand IOI training.
4 + The package includes 2 repositories, jittat/cafe-grader-web and jittat/cafe-grader-judge-scripts.
89 5
90 - >> @posts.inspect
91 - => "[#<Post:0x14a6be8 @attributes={\"title\"=>nil, \"body\"=>nil, \"id\"=>\"1\"}>,
92 - #<Post:0x14a6620 @attributes={\"title\"=>\"Rails you know!\", \"body\"=>\"Only ten..\", \"id\"=>\"2\"}>]"
93 - >> @posts.first.title = "hello from a breakpoint"
94 - => "hello from a breakpoint"
95 -
96 - ...and even better is that you can examine how your runtime objects actually work:
97 -
98 - >> f = @posts.first
99 - => #<Post:0x13630c4 @attributes={"title"=>nil, "body"=>nil, "id"=>"1"}>
100 - >> f.
101 - Display all 152 possibilities? (y or n)
102 -
103 - Finally, when you're ready to resume execution, you press CTRL-D
104 -
105 -
106 - == Console
107 -
108 - You can interact with the domain model by starting the console through <tt>script/console</tt>.
109 - Here you'll have all parts of the application configured, just like it is when the
110 - application is running. You can inspect domain models, change values, and save to the
111 - database. Starting the script without arguments will launch it in the development environment.
112 - Passing an argument will specify a different environment, like <tt>script/console production</tt>.
113 -
114 - To reload your controllers and models after launching the console run <tt>reload!</tt>
115 -
116 - To reload your controllers and models after launching the console run <tt>reload!</tt>
117 -
118 -
119 -
120 - == Description of contents
121 -
122 - app
123 - Holds all the code that's specific to this particular application.
124 -
125 - app/controllers
126 - Holds controllers that should be named like weblogs_controller.rb for
127 - automated URL mapping. All controllers should descend from ApplicationController
128 - which itself descends from ActionController::Base.
129 -
130 - app/models
131 - Holds models that should be named like post.rb.
132 - Most models will descend from ActiveRecord::Base.
6 + === Installation
133 7
134 - app/views
135 - Holds the template files for the view that should be named like
136 - weblogs/index.rhtml for the WeblogsController#index action. All views use eRuby
137 - syntax.
138 -
139 - app/views/layouts
140 - Holds the template files for layouts to be used with views. This models the common
141 - header/footer method of wrapping views. In your views, define a layout using the
142 - <tt>layout :default</tt> and create a file named default.rhtml. Inside default.rhtml,
143 - call <% yield %> to render the view using this layout.
144 -
145 - app/helpers
146 - Holds view helpers that should be named like weblogs_helper.rb. These are generated
147 - for you automatically when using script/generate for controllers. Helpers can be used to
148 - wrap functionality for your views into methods.
149 -
150 - config
151 - Configuration files for the Rails environment, the routing map, the database, and other dependencies.
152 -
153 - components
154 - Self-contained mini-applications that can bundle together controllers, models, and views.
8 + The system is tested on ubuntu 14.04 LTS. Use the installation script in
9 + cafe-grader-judge-scripts/installer/install.sh . See http://theory.cpe.ku.ac.th/wiki/index.php/%E0%B8%81%E0%B8%B2%E0%B8%A3%E0%B8%95%E0%B8%B4%E0%B8%94%E0%B8%95%E0%B8%B1%E0%B9%89%E0%B8%87_Cafe_grader for the detail.
155 10
156 - db
157 - Contains the database schema in schema.rb. db/migrate contains all
158 - the sequence of Migrations for your schema.
159 -
160 - doc
161 - This directory is where your application documentation will be stored when generated
162 - using <tt>rake doc:app</tt>
163 -
164 - lib
165 - Application specific libraries. Basically, any kind of custom code that doesn't
166 - belong under controllers, models, or helpers. This directory is in the load path.
167 -
168 - public
169 - The directory available for the web server. Contains subdirectories for images, stylesheets,
170 - and javascripts. Also contains the dispatchers and the default HTML files. This should be
171 - set as the DOCUMENT_ROOT of your web server.
172 -
173 - script
174 - Helper scripts for automation and generation.
175 -
176 - test
177 - Unit and functional tests along with fixtures. When using the script/generate scripts, template
178 - test files will be generated for you and placed in this directory.
179 -
180 - vendor
181 - External libraries that the application depends on. Also includes the plugins subdirectory.
182 - This directory is in the load path.
@@ -1,24 +1,33
1 +
2 + @import jquery.ui.core
3 + @import jquery.ui.theme
4 + @import jquery.ui.datepicker
5 + @import jquery.ui.slider
6 + @import jquery-ui-timepicker-addon
7 + @import jquery-tablesorter/theme.metro-dark
8 + @import tablesorter-theme.cafe
9 +
1 10 body
2 11 background: white image-url("topbg.jpg") repeat-x top center
3 12 font-size: 13px
4 13 font-family: Tahoma, "sans-serif"
5 14 margin: 10px
6 15 padding: 10px
7 16
8 17
9 18 input
10 19 font-family: Tahoma, "sans-serif"
11 20
12 21
13 22 h1
14 23 font-size: 24px
15 24 color: #334488
16 25 line-height: 2em
17 26
18 27
19 28 h2
20 29 font-size: 18px
21 30 color: #5566bb
22 31 line-height: 1.5em
23 32
24 33
@@ -269,25 +278,25
269 278
270 279 &.message div.stat
271 280 font-size: 10px
272 281 line-height: 1.75em
273 282 padding: 0 5px
274 283 color: #444444
275 284 background: #bbbbbb
276 285 font-weight: bold
277 286
278 287 &.contest-title
279 288 color: white
280 289 text-align: center
281 290 line-height: 2em
282 291
283 292 &.registration-desc, &.test-desc
284 293 border: 1px dotted gray
285 294 background: #f5f5f5
286 295 padding: 5px
287 296 margin: 10px 0
288 297 font-size: 12px
289 298 line-height: 1.5em
290 299
291 300 h2.contest-title
292 301 margin-top: 5px
293 - margin-bottom: 5px No newline at end of file
302 + margin-bottom: 5px
@@ -1,49 +1,58
1 1 class ApplicationController < ActionController::Base
2 2 protect_from_forgery
3 3
4 4 SINGLE_USER_MODE_CONF_KEY = 'system.single_user_mode'
5 5
6 6 def admin_authorization
7 7 return false unless authenticate
8 8 user = User.find(session[:user_id], :include => ['roles'])
9 + unless user.admin?
10 + flash[:notice] = 'You are not authorized to view the page you requested'
9 11 redirect_to :controller => 'main', :action => 'login' unless user.admin?
12 + return false
13 + end
14 + return true
10 15 end
11 16
12 17 def authorization_by_roles(allowed_roles)
13 18 return false unless authenticate
14 19 user = User.find(session[:user_id])
15 20 unless user.roles.detect { |role| allowed_roles.member?(role.name) }
16 21 flash[:notice] = 'You are not authorized to view the page you requested'
17 22 redirect_to :controller => 'main', :action => 'login'
18 23 return false
19 24 end
20 25 end
21 26
22 27 protected
23 28
24 29 def authenticate
25 30 unless session[:user_id]
31 + flash[:notice] = 'You need to login'
32 + if GraderConfiguration[SINGLE_USER_MODE_CONF_KEY]
33 + flash[:notice] = 'You need to login but you cannot log in at this time'
34 + end
26 35 redirect_to :controller => 'main', :action => 'login'
27 36 return false
28 37 end
29 38
30 39 # check if run in single user mode
31 40 if GraderConfiguration[SINGLE_USER_MODE_CONF_KEY]
32 41 user = User.find(session[:user_id])
33 42 if user==nil or (not user.admin?)
34 43 flash[:notice] = 'You cannot log in at this time'
35 44 redirect_to :controller => 'main', :action => 'login'
36 45 return false
37 46 end
38 47 return true
39 48 end
40 49
41 50 if GraderConfiguration.multicontests?
42 51 user = User.find(session[:user_id])
43 52 return true if user.admin?
44 53 begin
45 54 if user.contest_stat(true).forced_logout
46 55 flash[:notice] = 'You have been automatically logged out.'
47 56 redirect_to :controller => 'main', :action => 'index'
48 57 end
49 58 rescue
@@ -1,20 +1,28
1 1 class ConfigurationsController < ApplicationController
2 2
3 3 before_filter :authenticate
4 4 before_filter { |controller| controller.authorization_by_roles(['admin'])}
5 5
6 - in_place_edit_for :grader_configuration, :key
7 - in_place_edit_for :grader_configuration, :type
8 - in_place_edit_for :grader_configuration, :value
9 6
10 7 def index
11 8 @configurations = GraderConfiguration.find(:all,
12 9 :order => '`key`')
13 10 end
14 11
15 12 def reload
16 13 GraderConfiguration.reload
17 14 redirect_to :action => 'index'
18 15 end
19 16
17 + def update
18 + @config = GraderConfiguration.find(params[:id])
19 + respond_to do |format|
20 + if @config.update_attributes(params[:grader_configuration])
21 + format.json { head :ok }
22 + else
23 + format.json { respond_with_bip(@config) }
20 24 end
25 + end
26 + end
27 +
28 + end
@@ -1,89 +1,112
1 1 class GradersController < ApplicationController
2 2
3 - before_filter :admin_authorization
3 + before_filter :admin_authorization, except: [ :submission ]
4 + before_filter(only: [:submission]) {
5 + return false unless authenticate
6 +
7 + if GraderConfiguration["right.user_view_submission"]
8 + return true;
9 + end
10 +
11 + admin_authorization
12 + }
4 13
5 14 verify :method => :post, :only => ['clear_all',
6 15 'start_exam',
7 16 'start_grading',
8 17 'stop_all',
9 18 'clear_terminated'],
10 19 :redirect_to => {:action => 'index'}
11 20
12 21 def index
13 22 redirect_to :action => 'list'
14 23 end
15 24
16 25 def list
17 26 @grader_processes = GraderProcess.find_running_graders
18 27 @stalled_processes = GraderProcess.find_stalled_process
19 28
20 29 @terminated_processes = GraderProcess.find_terminated_graders
21 30
22 31 @last_task = Task.find(:first,
23 32 :order => 'created_at DESC')
24 33 @last_test_request = TestRequest.find(:first,
25 34 :order => 'created_at DESC')
35 + @submission = Submission.order("id desc").limit(20)
26 36 end
27 37
28 38 def clear
29 39 grader_proc = GraderProcess.find(params[:id])
30 40 grader_proc.destroy if grader_proc!=nil
31 41 redirect_to :action => 'list'
32 42 end
33 43
34 44 def clear_terminated
35 45 GraderProcess.find_terminated_graders.each do |p|
36 46 p.destroy
37 47 end
38 48 redirect_to :action => 'list'
39 49 end
40 50
41 51 def clear_all
42 52 GraderProcess.find(:all).each do |p|
43 53 p.destroy
44 54 end
45 55 redirect_to :action => 'list'
46 56 end
47 57
48 58 def view
49 59 if params[:type]=='Task'
50 60 redirect_to :action => 'task', :id => params[:id]
51 61 else
52 62 redirect_to :action => 'test_request', :id => params[:id]
53 63 end
54 64 end
55 65
56 66 def test_request
57 67 @test_request = TestRequest.find(params[:id])
58 68 end
59 69
60 70 def task
61 71 @task = Task.find(params[:id])
62 72 end
63 73
64 74 def submission
65 75 @submission = Submission.find(params[:id])
76 + formatter = Rouge::Formatters::HTML.new(css_class: 'highlight', line_numbers: true )
77 + lexer = case @submission.language.name
78 + when "c" then Rouge::Lexers::C.new
79 + when "cpp" then Rouge::Lexers::Cpp.new
80 + when "pas" then Rouge::Lexers::Pas.new
81 + when "ruby" then Rouge::Lexers::Ruby.new
82 + when "python" then Rouge::Lexers::Python.new
83 + when "java" then Rouge::Lexers::Java.new
84 + when "php" then Rouge::Lexers::PHP.new
85 + end
86 + @formatted_code = formatter.format(lexer.lex(@submission.source))
87 + @css_style = Rouge::Themes::ThankfulEyes.render(scope: '.highlight')
88 +
66 89 end
67 90
68 91 # various grader controls
69 92
70 93 def stop
71 94 grader_proc = GraderProcess.find(params[:id])
72 95 GraderScript.stop_grader(grader_proc.pid)
73 96 flash[:notice] = 'Grader stopped. It may not disappear now, but it should disappear shortly.'
74 97 redirect_to :action => 'list'
75 98 end
76 99
77 100 def stop_all
78 101 GraderScript.stop_graders(GraderProcess.find_running_graders +
79 102 GraderProcess.find_stalled_process)
80 103 flash[:notice] = 'Graders stopped. They may not disappear now, but they should disappear shortly.'
81 104 redirect_to :action => 'list'
82 105 end
83 106
84 107 def start_grading
85 108 GraderScript.start_grader('grading')
86 109 flash[:notice] = '2 graders in grading env started, one for grading queue tasks, another for grading test request'
87 110 redirect_to :action => 'list'
88 111 end
89 112
@@ -1,48 +1,51
1 1 class LoginController < ApplicationController
2 2
3 3 def index
4 4 # show login screen
5 5 reset_session
6 6 redirect_to :controller => 'main', :action => 'login'
7 7 end
8 8
9 9 def login
10 10 if user = User.authenticate(params[:login], params[:password])
11 11 session[:user_id] = user.id
12 12 session[:admin] = user.admin?
13 13
14 14 # clear forced logout flag for multicontests contest change
15 15 if GraderConfiguration.multicontests?
16 16 contest_stat = user.contest_stat
17 17 if contest_stat.respond_to? :forced_logout
18 18 if contest_stat.forced_logout
19 19 contest_stat.forced_logout = false
20 20 contest_stat.save
21 21 end
22 22 end
23 23 end
24 24
25 + #save login information
26 + Login.create(user_id: user.id, ip_address: request.remote_ip)
27 +
25 28 redirect_to :controller => 'main', :action => 'list'
26 29 else
27 30 flash[:notice] = 'Wrong password'
28 31 redirect_to :controller => 'main', :action => 'login'
29 32 end
30 33 end
31 34
32 35 def site_login
33 36 begin
34 37 site = Site.find(params[:login][:site_id])
35 38 rescue ActiveRecord::RecordNotFound
36 39 site = nil
37 40 end
38 41 if site==nil
39 42 flash[:notice] = 'Wrong site'
40 43 redirect_to :controller => 'main', :action => 'login' and return
41 44 end
42 45 if (site.password) and (site.password == params[:login][:password])
43 46 session[:site_id] = site.id
44 47 redirect_to :controller => 'site', :action => 'index'
45 48 else
46 49 flash[:notice] = 'Wrong site password'
47 50 redirect_to :controller => 'site', :action => 'login'
48 51 end
@@ -42,52 +42,54
42 42 # request.path!='/main/login'
43 43 # @hidelogin = true
44 44 # end
45 45
46 46 @announcements = Announcement.find_for_frontpage
47 47 render :action => 'login', :layout => 'empty'
48 48 end
49 49
50 50 def list
51 51 prepare_list_information
52 52 end
53 53
54 54 def help
55 55 @user = User.find(session[:user_id])
56 56 end
57 57
58 58 def submit
59 59 user = User.find(session[:user_id])
60 60
61 61 @submission = Submission.new
62 62 @submission.problem_id = params[:submission][:problem_id]
63 63 @submission.user = user
64 64 @submission.language_id = 0
65 65 if (params['file']) and (params['file']!='')
66 - @submission.source = params['file'].read
66 + @submission.source = File.open(params['file'].path,'r:UTF-8',&:read)
67 + @submission.source.encode!('UTF-8','UTF-8',invalid: :replace, replace: '')
67 68 @submission.source_filename = params['file'].original_filename
68 69 end
69 70 @submission.submitted_at = Time.new.gmtime
71 + @submission.ip_address = request.remote_ip
70 72
71 73 if GraderConfiguration.time_limit_mode? and user.contest_finished?
72 74 @submission.errors.add(:base,"The contest is over.")
73 75 prepare_list_information
74 76 render :action => 'list' and return
75 77 end
76 78
77 79 if @submission.valid?
78 80 if @submission.save == false
79 81 flash[:notice] = 'Error saving your submission'
80 82 elsif Task.create(:submission_id => @submission.id,
81 83 :status => Task::STATUS_INQUEUE) == false
82 84 flash[:notice] = 'Error adding your submission to task queue'
83 85 end
84 86 else
85 87 prepare_list_information
86 88 render :action => 'list' and return
87 89 end
88 90 redirect_to :action => 'list'
89 91 end
90 92
91 93 def source
92 94 submission = Submission.find(params[:id])
93 95 if ((submission.user_id == session[:user_id]) and
@@ -129,64 +129,82
129 129 @problem.available = !(@problem.available)
130 130 @problem.save
131 131 end
132 132
133 133 def turn_all_off
134 134 Problem.find(:all,
135 135 :conditions => "available = 1").each do |problem|
136 136 problem.available = false
137 137 problem.save
138 138 end
139 139 redirect_to :action => 'list'
140 140 end
141 141
142 142 def turn_all_on
143 143 Problem.find(:all,
144 144 :conditions => "available = 0").each do |problem|
145 145 problem.available = true
146 146 problem.save
147 147 end
148 148 redirect_to :action => 'list'
149 149 end
150 150
151 151 def stat
152 152 @problem = Problem.find(params[:id])
153 - if !@problem.available
153 + unless @problem.available or session[:admin]
154 154 redirect_to :controller => 'main', :action => 'list'
155 - else
156 - @submissions = Submission.find_all_last_by_problem(params[:id])
155 + return
157 156 end
157 + @submissions = Submission.includes(:user).where(problem_id: params[:id]).order(:user_id,:id)
158 +
159 + #stat summary
160 + range =65
161 + @histogram = { data: Array.new(range,0), summary: {} }
162 + user = Hash.new(0)
163 + @submissions.find_each do |sub|
164 + d = (DateTime.now.in_time_zone - sub.submitted_at) / 24 / 60 / 60
165 + @histogram[:data][d.to_i] += 1 if d < range
166 + user[sub.user_id] = [user[sub.user_id], (sub.points >= @problem.full_score) ? 1 : 0].max
167 + end
168 + @histogram[:summary][:max] = [@histogram[:data].max,1].max
169 +
170 + @summary = { attempt: user.count, solve: 0 }
171 + user.each_value { |v| @summary[:solve] += 1 if v == 1 }
158 172 end
159 173
160 174 def manage
161 175 @problems = Problem.find(:all, :order => 'date_added DESC')
162 176 end
163 177
164 178 def do_manage
165 179 if params.has_key? 'change_date_added'
166 180 change_date_added
167 - else params.has_key? 'add_to_contest'
181 + elsif params.has_key? 'add_to_contest'
168 182 add_to_contest
183 + elsif params.has_key? 'enable_problem'
184 + set_available(true)
185 + elsif params.has_key? 'disable_problem'
186 + set_available(false)
169 187 end
170 188 redirect_to :action => 'manage'
171 189 end
172 190
173 191 def import
174 192 @allow_test_pair_import = allow_test_pair_import?
175 193 end
176 194
177 195 def do_import
178 196 old_problem = Problem.find_by_name(params[:name])
179 197 if !allow_test_pair_import? and params.has_key? :import_to_db
180 198 params.delete :import_to_db
181 199 end
182 200 @problem, import_log = Problem.create_from_import_form_params(params,
183 201 old_problem)
184 202
185 203 if !@problem.errors.empty?
186 204 render :action => 'import' and return
187 205 end
188 206
189 207 if old_problem!=nil
190 208 flash[:notice] = "The test data has been replaced for problem #{@problem.name}"
191 209 end
192 210 @log = import_log
@@ -213,36 +231,47
213 231 end
214 232
215 233 def change_date_added
216 234 problems = get_problems_from_params
217 235 year = params[:date_added][:year].to_i
218 236 month = params[:date_added][:month].to_i
219 237 day = params[:date_added][:day].to_i
220 238 date = Date.new(year,month,day)
221 239 problems.each do |p|
222 240 p.date_added = date
223 241 p.save
224 242 end
225 243 end
226 244
227 245 def add_to_contest
228 246 problems = get_problems_from_params
229 247 contest = Contest.find(params[:contest][:id])
230 248 if contest!=nil and contest.enabled
231 249 problems.each do |p|
232 250 p.contests << contest
233 251 end
234 252 end
235 253 end
236 254
255 + def set_available(avail)
256 + problems = get_problems_from_params
257 + problems.each do |p|
258 + p.available = avail
259 + p.save
260 + end
261 + end
262 +
237 263 def get_problems_from_params
238 264 problems = []
239 265 params.keys.each do |k|
240 266 if k.index('prob-')==0
241 - name, id = k.split('-')
267 + name, id, order = k.split('-')
242 268 problems << Problem.find(id)
243 269 end
244 270 end
245 271 problems
246 272 end
247 273
274 + def get_problems_stat
248 275 end
276 +
277 + end
@@ -1,24 +1,26
1 + require 'csv'
2 +
1 3 class UserAdminController < ApplicationController
2 4
3 5 include MailHelperMethods
4 6
5 7 before_filter :admin_authorization
6 8
7 9 # GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html)
8 10 verify :method => :post, :only => [ :destroy,
9 11 :create, :create_from_list,
10 12 :update,
11 13 :manage_contest,
12 14 :bulk_mail
13 15 ],
14 16 :redirect_to => { :action => :list }
15 17
16 18 def index
17 19 list
18 20 render :action => 'list'
19 21 end
20 22
21 23 def list
22 24 @user_count = User.count
23 25 if params[:page] == 'all'
24 26 @users = User.all
@@ -60,126 +62,147
60 62 end
61 63 end
62 64
63 65 def create_from_list
64 66 lines = params[:user_list]
65 67
66 68 note = []
67 69
68 70 lines.split("\n").each do |line|
69 71 items = line.chomp.split(',')
70 72 if items.length>=2
71 73 login = items[0]
72 74 full_name = items[1]
73 75
74 76 added_random_password = false
75 77 if items.length>=3
76 78 password = items[2].chomp(" ")
77 79 user_alias = (items.length>=4) ? items[3] : login
78 80 else
79 81 password = random_password
80 82 user_alias = (items.length>=4) ? items[3] : login
81 83 added_random_password = true
82 84 end
83 85
86 + user = User.find_by_login(login)
87 + if (user)
88 + user.full_name = full_name
89 + user.password = password
90 + else
84 91 user = User.new({:login => login,
85 92 :full_name => full_name,
86 93 :password => password,
87 94 :password_confirmation => password,
88 95 :alias => user_alias})
96 + end
89 97 user.activated = true
90 98 user.save
91 99
92 100 if added_random_password
93 101 note << "'#{login}' (+)"
94 102 else
95 103 note << login
96 104 end
97 105 end
98 106 end
99 107 flash[:notice] = 'User(s) ' + note.join(', ') +
100 108 ' were successfully created. ' +
101 109 '( (+) - created with random passwords.)'
102 110 redirect_to :action => 'list'
103 111 end
104 112
105 113 def edit
106 114 @user = User.find(params[:id])
107 115 end
108 116
109 117 def update
110 118 @user = User.find(params[:id])
111 119 if @user.update_attributes(params[:user])
112 120 flash[:notice] = 'User was successfully updated.'
113 121 redirect_to :action => 'show', :id => @user
114 122 else
115 123 render :action => 'edit'
116 124 end
117 125 end
118 126
119 127 def destroy
120 128 User.find(params[:id]).destroy
121 129 redirect_to :action => 'list'
122 130 end
123 131
124 132 def user_stat
133 + if params[:commit] == 'download csv'
134 + @problems = Problem.all
135 + else
125 136 @problems = Problem.find_available_problems
137 + end
126 138 @users = User.find(:all, :include => [:contests, :contest_stat])
127 139 @scorearray = Array.new
128 140 @users.each do |u|
129 141 ustat = Array.new
130 142 ustat[0] = u
131 143 @problems.each do |p|
132 144 sub = Submission.find_last_by_user_and_problem(u.id,p.id)
133 145 if (sub!=nil) and (sub.points!=nil)
134 146 ustat << [(sub.points.to_f*100/p.full_score).round, (sub.points>=p.full_score)]
135 147 else
136 148 ustat << [0,false]
137 149 end
138 150 end
139 151 @scorearray << ustat
140 152 end
141 153 end
142 154
143 155 def user_stat_max
156 + if params[:commit] == 'download csv'
157 + @problems = Problem.all
158 + else
144 159 @problems = Problem.find_available_problems
160 + end
145 161 @users = User.find(:all, :include => [:contests, :contest_stat])
146 162 @scorearray = Array.new
147 163 #set up range from param
148 164 since_id = params.fetch(:since_id, 0).to_i
149 165 until_id = params.fetch(:until_id, 0).to_i
150 166 @users.each do |u|
151 167 ustat = Array.new
152 168 ustat[0] = u
153 169 @problems.each do |p|
154 170 max_points = 0
155 171 Submission.find_in_range_by_user_and_problem(u.id,p.id,since_id,until_id).each do |sub|
156 172 max_points = sub.points if sub and sub.points and (sub.points > max_points)
157 173 end
158 174 ustat << [(max_points.to_f*100/p.full_score).round, (max_points>=p.full_score)]
159 175 end
160 176 @scorearray << ustat
161 177 end
178 +
179 + if params[:commit] == 'download csv' then
180 + csv = gen_csv_from_scorearray(@scorearray,@problems)
181 + send_data csv, filename: 'max_score.csv'
182 + else
183 + render template: 'user_admin/user_stat'
184 + end
162 185 end
163 186
164 187 def import
165 188 if params[:file]==''
166 189 flash[:notice] = 'Error importing no file'
167 190 redirect_to :action => 'list' and return
168 191 end
169 192 import_from_file(params[:file])
170 193 end
171 194
172 195 def random_all_passwords
173 196 users = User.find(:all)
174 197 @prefix = params[:prefix] || ''
175 198 @non_admin_users = User.find_non_admin_with_prefix(@prefix)
176 199 @changed = false
177 200 if request.request_method == 'POST'
178 201 @non_admin_users.each do |user|
179 202 password = random_password
180 203 user.password = password
181 204 user.password_confirmation = password
182 205 user.save
183 206 end
184 207 @changed = true
185 208 end
@@ -452,25 +475,56
452 475 :contest_name => contest_name })
453 476 mail_body = t('contest.notification.email_body', {
454 477 :full_name => user.full_name,
455 478 :contest_title_name => contest_title_name,
456 479 :contest_name => contest.name,
457 480 })
458 481
459 482 logger.info mail_body
460 483 send_mail(user.email, mail_subject, mail_body)
461 484 end
462 485
463 486 def find_contest_and_user_from_contest_id(id)
464 487 if id!='none'
465 488 @contest = Contest.find(id)
466 489 else
467 490 @contest = nil
468 491 end
469 492 if @contest
470 493 @users = @contest.users
471 494 else
472 495 @users = User.find_users_with_no_contest
473 496 end
474 497 return [@contest, @users]
475 498 end
499 +
500 + def gen_csv_from_scorearray(scorearray,problem)
501 + CSV.generate do |csv|
502 + #add header
503 + header = ['User','Name', 'Activated?', 'Logged in', 'Contest']
504 + problem.each { |p| header << p.name }
505 + header += ['Total','Passed']
506 + csv << header
507 + #add data
508 + scorearray.each do |sc|
509 + total = num_passed = 0
510 + row = Array.new
511 + sc.each_index do |i|
512 + if i == 0
513 + row << sc[i].login
514 + row << sc[i].full_name
515 + row << sc[i].activated
516 + row << (sc[i].try(:contest_stat).try(:started_at)!=nil ? 'yes' : 'no')
517 + row << sc[i].contests.collect {|c| c.name}.join(', ')
518 + else
519 + row << sc[i][0]
520 + total += sc[i][0]
521 + num_passed += 1 if sc[i][1]
476 522 end
523 + end
524 + row << total
525 + row << num_passed
526 + csv << row
527 + end
528 + end
529 + end
530 + end
@@ -1,40 +1,41
1 1 require 'net/smtp'
2 2
3 3 class UsersController < ApplicationController
4 4
5 5 include MailHelperMethods
6 6
7 7 before_filter :authenticate, :except => [:new,
8 8 :register,
9 9 :confirm,
10 10 :forget,
11 11 :retrieve_password]
12 12
13 13 before_filter :verify_online_registration, :only => [:new,
14 14 :register,
15 15 :forget,
16 16 :retrieve_password]
17 + before_filter :authenticate, :profile_authorization, only: [:profile]
17 18
18 19 verify :method => :post, :only => [:chg_passwd],
19 20 :redirect_to => { :action => :index }
20 21
21 22 #in_place_edit_for :user, :alias_for_editing
22 23 #in_place_edit_for :user, :email_for_editing
23 24
24 25 def index
25 26 if !GraderConfiguration['system.user_setting_enabled']
26 27 redirect_to :controller => 'main', :action => 'list'
27 28 else
28 29 @user = User.find(session[:user_id])
29 30 end
30 31 end
31 32
32 33 def chg_passwd
33 34 user = User.find(session[:user_id])
34 35 user.password = params[:passwd]
35 36 user.password_confirmation = params[:passwd_verify]
36 37 if user.save
37 38 flash[:notice] = 'password changed'
38 39 else
39 40 flash[:notice] = 'Error: password changing failed'
40 41 end
@@ -87,70 +88,108
87 88
88 89 def forget
89 90 render :action => 'forget', :layout => 'empty'
90 91 end
91 92
92 93 def retrieve_password
93 94 email = params[:email]
94 95 user = User.find_by_email(email)
95 96 if user
96 97 last_updated_time = user.updated_at || user.created_at || (Time.now.gmtime - 1.hour)
97 98 if last_updated_time > Time.now.gmtime - 5.minutes
98 99 flash[:notice] = 'The account has recently created or new password has recently been requested. Please wait for 5 minutes'
99 100 else
100 101 user.password = user.password_confirmation = User.random_password
101 102 user.save
102 103 send_new_password_email(user)
103 104 flash[:notice] = 'New password has been mailed to you.'
104 105 end
105 106 else
106 107 flash[:notice] = I18n.t 'registration.password_retrieval.no_email'
107 108 end
108 109 redirect_to :action => 'forget'
109 110 end
110 111
112 + def profile
113 + @user = User.find(params[:id])
114 + @submission = Submission.includes(:problem).where(user_id: params[:id])
115 +
116 + range = 120
117 + @histogram = { data: Array.new(range,0), summary: {} }
118 + @summary = {count: 0, solve: 0, attempt: 0}
119 + problem = Hash.new(0)
120 +
121 + @submission.find_each do |sub|
122 + #histogram
123 + d = (DateTime.now.in_time_zone - sub.submitted_at) / 24 / 60 / 60
124 + @histogram[:data][d.to_i] += 1 if d < range
125 +
126 + @summary[:count] += 1
127 + next unless sub.problem
128 + problem[sub.problem] = [problem[sub.problem], (sub.points >= sub.problem.full_score) ? 1 : 0].max
129 + end
130 +
131 + @histogram[:summary][:max] = [@histogram[:data].max,1].max
132 + @summary[:attempt] = problem.count
133 + problem.each_value { |v| @summary[:solve] += 1 if v == 1 }
134 + end
135 +
111 136 protected
112 137
113 138 def verify_online_registration
114 139 if !GraderConfiguration['system.online_registration']
115 140 redirect_to :controller => 'main', :action => 'login'
116 141 end
117 142 end
118 143
119 144 def send_confirmation_email(user)
120 145 contest_name = GraderConfiguration['contest.name']
121 146 activation_url = url_for(:action => 'confirm',
122 147 :login => user.login,
123 148 :activation => user.activation_key)
124 149 home_url = url_for(:controller => 'main', :action => 'index')
125 150 mail_subject = "[#{contest_name}] Confirmation"
126 151 mail_body = t('registration.email_body', {
127 152 :full_name => user.full_name,
128 153 :contest_name => contest_name,
129 154 :login => user.login,
130 155 :password => user.password,
131 156 :activation_url => activation_url,
132 157 :admin_email => GraderConfiguration['system.admin_email']
133 158 })
134 159
135 160 logger.info mail_body
136 161
137 162 send_mail(user.email, mail_subject, mail_body)
138 163 end
139 164
140 165 def send_new_password_email(user)
141 166 contest_name = GraderConfiguration['contest.name']
142 167 mail_subject = "[#{contest_name}] Password recovery"
143 168 mail_body = t('registration.password_retrieval.email_body', {
144 169 :full_name => user.full_name,
145 170 :contest_name => contest_name,
146 171 :login => user.login,
147 172 :password => user.password,
148 173 :admin_email => GraderConfiguration['system.admin_email']
149 174 })
150 175
151 176 logger.info mail_body
152 177
153 178 send_mail(user.email, mail_subject, mail_body)
154 179 end
155 180
181 + # allow viewing of regular user profile only when options allow so
182 + # only admins can view admins profile
183 + def profile_authorization
184 + #if view admins' profile, allow only admin
185 + return false unless(params[:id])
186 + user = User.find(params[:id])
187 + return false unless user
188 + return admin_authorization if user.admin?
189 + return true if GraderConfiguration["right.user_view_submission"]
190 +
191 + #finally, we allow only admin
192 + admin_authorization
156 193 end
194 +
195 + end
@@ -1,55 +1,60
1 1 # Methods added to this helper will be available to all templates in the application.
2 2 module ApplicationHelper
3 3
4 4 def user_header
5 5 menu_items = ''
6 6 user = User.find(session[:user_id])
7 7
8 8 if (user!=nil) and (session[:admin])
9 9 # admin menu
10 10 menu_items << "<b>Administrative task:</b> "
11 11 append_to menu_items, '[Announcements]', 'announcements', 'index'
12 12 append_to menu_items, '[Msg console]', 'messages', 'console'
13 13 append_to menu_items, '[Problems]', 'problems', 'index'
14 14 append_to menu_items, '[Users]', 'user_admin', 'index'
15 15 append_to menu_items, '[Results]', 'user_admin', 'user_stat'
16 + append_to menu_items, '[Report]', 'report', 'login_stat'
16 17 append_to menu_items, '[Graders]', 'graders', 'list'
17 18 append_to menu_items, '[Contests]', 'contest_management', 'index'
18 19 append_to menu_items, '[Sites]', 'sites', 'index'
19 20 append_to menu_items, '[System config]', 'configurations', 'index'
20 21 menu_items << "<br/>"
21 22 end
22 23
23 24 # main page
24 25 append_to menu_items, "[#{I18n.t 'menu.main'}]", 'main', 'list'
25 26 append_to menu_items, "[#{I18n.t 'menu.messages'}]", 'messages', 'list'
26 27
27 28 if (user!=nil) and (GraderConfiguration.show_tasks_to?(user))
28 29 append_to menu_items, "[#{I18n.t 'menu.tasks'}]", 'tasks', 'list'
29 30 append_to menu_items, "[#{I18n.t 'menu.submissions'}]", 'main', 'submission'
30 31 append_to menu_items, "[#{I18n.t 'menu.test'}]", 'test', 'index'
31 32 end
33 +
34 + if GraderConfiguration['right.user_hall_of_fame']
35 + append_to menu_items, "[#{I18n.t 'menu.hall_of_fame'}]", 'report', 'problem_hof'
36 + end
32 37 append_to menu_items, "[#{I18n.t 'menu.help'}]", 'main', 'help'
33 38
34 39 if GraderConfiguration['system.user_setting_enabled']
35 40 append_to menu_items, "[#{I18n.t 'menu.settings'}]", 'users', 'index'
36 41 end
37 42 append_to menu_items, "[#{I18n.t 'menu.log_out'}]", 'main', 'login'
38 43
39 44 menu_items.html_safe
40 45 end
41 46
42 47 def append_to(option,label, controller, action)
43 48 option << ' ' if option!=''
44 49 option << link_to_unless_current(label,
45 50 :controller => controller,
46 51 :action => action)
47 52 end
48 53
49 54 def format_short_time(time)
50 55 now = Time.now.gmtime
51 56 st = ''
52 57 if (time.yday != now.yday) or
53 58 (time.year != now.year)
54 59 st = time.strftime("%x ")
55 60 end
@@ -1,114 +1,128
1 1 class Problem < ActiveRecord::Base
2 2
3 3 belongs_to :description
4 4 has_and_belongs_to_many :contests, :uniq => true
5 5 has_many :test_pairs, :dependent => :delete_all
6 6
7 7 validates_presence_of :name
8 8 validates_format_of :name, :with => /^\w+$/
9 9 validates_presence_of :full_name
10 10
11 11 scope :available, :conditions => {:available => true}
12 12
13 13 DEFAULT_TIME_LIMIT = 1
14 14 DEFAULT_MEMORY_LIMIT = 32
15 15
16 16 def self.find_available_problems
17 - Problem.available.all(:order => "date_added DESC")
17 + Problem.available.all(:order => "date_added DESC, name ASC")
18 18 end
19 19
20 20 def self.create_from_import_form_params(params, old_problem=nil)
21 21 org_problem = old_problem || Problem.new
22 22 import_params, problem = Problem.extract_params_and_check(params,
23 23 org_problem)
24 24
25 25 if !problem.errors.empty?
26 26 return problem, 'Error importing'
27 27 end
28 28
29 29 problem.full_score = 100
30 30 problem.date_added = Time.new
31 31 problem.test_allowed = true
32 32 problem.output_only = false
33 33 problem.available = false
34 34
35 35 if not problem.save
36 36 return problem, 'Error importing'
37 37 end
38 38
39 39 import_to_db = params.has_key? :import_to_db
40 40
41 41 importer = TestdataImporter.new(problem)
42 42
43 43 if not importer.import_from_file(import_params[:file],
44 44 import_params[:time_limit],
45 45 import_params[:memory_limit],
46 + import_params[:checker_name],
46 47 import_to_db)
47 48 problem.errors.add(:base,'Import error.')
48 49 end
49 50
50 51 return problem, importer.log_msg
51 52 end
52 53
53 54 def self.download_file_basedir
54 55 return "#{Rails.root}/data/tasks"
55 56 end
56 57
58 + def get_submission_stat
59 + result = Hash.new
60 + #total number of submission
61 + result[:total_sub] = Submission.where(problem_id: self.id).count
62 + result[:attempted_user] = Submission.where(problem_id: self.id).group_by(:user_id)
63 + end
64 +
57 65 protected
58 66
59 67 def self.to_i_or_default(st, default)
60 68 if st!=''
61 69 result = st.to_i
62 70 end
63 71 result ||= default
64 72 end
65 73
66 74 def self.to_f_or_default(st, default)
67 75 if st!=''
68 76 result = st.to_f
69 77 end
70 78 result ||= default
71 79 end
72 80
73 81 def self.extract_params_and_check(params, problem)
74 82 time_limit = Problem.to_f_or_default(params[:time_limit],
75 83 DEFAULT_TIME_LIMIT)
76 84 memory_limit = Problem.to_i_or_default(params[:memory_limit],
77 85 DEFAULT_MEMORY_LIMIT)
78 86
79 87 if time_limit<=0 or time_limit >60
80 88 problem.errors.add(:base,'Time limit out of range.')
81 89 end
82 90
83 91 if memory_limit==0 and params[:memory_limit]!='0'
84 92 problem.errors.add(:base,'Memory limit format errors.')
85 93 elsif memory_limit<=0 or memory_limit >512
86 94 problem.errors.add(:base,'Memory limit out of range.')
87 95 end
88 96
89 97 if params[:file]==nil or params[:file]==''
90 98 problem.errors.add(:base,'No testdata file.')
91 99 end
92 100
101 + checker_name = 'text'
102 + if ['text','float'].include? params[:checker]
103 + checker_name = params[:checker]
104 + end
105 +
93 106 file = params[:file]
94 107
95 108 if !problem.errors.empty?
96 109 return nil, problem
97 110 end
98 111
99 112 problem.name = params[:name]
100 113 if params[:full_name]!=''
101 114 problem.full_name = params[:full_name]
102 115 else
103 116 problem.full_name = params[:name]
104 117 end
105 118
106 119 return [{
107 120 :time_limit => time_limit,
108 121 :memory_limit => memory_limit,
109 - :file => file
122 + :file => file,
123 + :checker_name => checker_name
110 124 },
111 125 problem]
112 126 end
113 127
114 128 end
@@ -4,49 +4,49
4 4 belongs_to :problem
5 5 belongs_to :user
6 6
7 7 before_validation :assign_problem
8 8 before_validation :assign_language
9 9
10 10 validates_presence_of :source
11 11 validates_length_of :source, :maximum => 100_000, :allow_blank => true, :message => 'too long'
12 12 validates_length_of :source, :minimum => 1, :allow_blank => true, :message => 'too short'
13 13 validate :must_have_valid_problem
14 14 validate :must_specify_language
15 15
16 16 before_save :assign_latest_number_if_new_recond
17 17
18 18 def self.find_last_by_user_and_problem(user_id, problem_id)
19 19 last_sub = find(:first,
20 20 :conditions => {:user_id => user_id,
21 21 :problem_id => problem_id},
22 22 :order => 'number DESC')
23 23 return last_sub
24 24 end
25 25
26 26 def self.find_all_last_by_problem(problem_id)
27 27 # need to put in SQL command, maybe there's a better way
28 - Submission.find_by_sql("SELECT * FROM submissions " +
28 + Submission.includes(:user).find_by_sql("SELECT * FROM submissions " +
29 29 "WHERE id = " +
30 30 "(SELECT MAX(id) FROM submissions AS subs " +
31 31 "WHERE subs.user_id = submissions.user_id AND " +
32 32 "problem_id = " + problem_id.to_s + " " +
33 33 "GROUP BY user_id) " +
34 34 "ORDER BY user_id")
35 35 end
36 36
37 37 def self.find_in_range_by_user_and_problem(user_id, problem_id,since_id,until_id)
38 38 records = Submission.where(problem_id: problem_id,user_id: user_id)
39 39 records = records.where('id >= ?',since_id) if since_id > 0
40 40 records = records.where('id <= ?',until_id) if until_id > 0
41 41 records.all
42 42 end
43 43
44 44 def self.find_last_for_all_available_problems(user_id)
45 45 submissions = Array.new
46 46 problems = Problem.find_available_problems
47 47 problems.each do |problem|
48 48 sub = Submission.find_last_by_user_and_problem(user_id, problem.id)
49 49 submissions << sub if sub!=nil
50 50 end
51 51 submissions
52 52 end
@@ -1,25 +1,29
1 1 require 'digest/sha1'
2 + require 'net/pop'
3 + require 'net/https'
4 + require 'net/http'
5 + require 'json'
2 6
3 7 class User < ActiveRecord::Base
4 8
5 9 has_and_belongs_to_many :roles
6 10
7 11 has_many :test_requests, :order => "submitted_at DESC"
8 12
9 13 has_many :messages,
10 14 :class_name => "Message",
11 15 :foreign_key => "sender_id",
12 16 :order => 'created_at DESC'
13 17
14 18 has_many :replied_messages,
15 19 :class_name => "Message",
16 20 :foreign_key => "receiver_id",
17 21 :order => 'created_at DESC'
18 22
19 23 has_one :contest_stat, :class_name => "UserContestStat", :dependent => :destroy
20 24
21 25 belongs_to :site
22 26 belongs_to :country
23 27
24 28 has_and_belongs_to_many :contests, :uniq => true, :order => 'name'
25 29
@@ -40,49 +44,51
40 44 validates_format_of :email,
41 45 :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i,
42 46 :if => :email_validation?
43 47 validate :uniqueness_of_email_from_activated_users,
44 48 :if => :email_validation?
45 49 validate :enough_time_interval_between_same_email_registrations,
46 50 :if => :email_validation?
47 51
48 52 # these are for ytopc
49 53 # disable for now
50 54 #validates_presence_of :province
51 55
52 56 attr_accessor :password
53 57
54 58 before_save :encrypt_new_password
55 59 before_save :assign_default_site
56 60 before_save :assign_default_contest
57 61
58 62 # this is for will_paginate
59 63 cattr_reader :per_page
60 64 @@per_page = 50
61 65
62 66 def self.authenticate(login, password)
63 67 user = find_by_login(login)
64 - return user if user && user.authenticated?(password)
68 + if user
69 + return user if user.authenticated?(password)
70 + end
65 71 end
66 72
67 73 def authenticated?(password)
68 74 if self.activated
69 75 hashed_password == User.encrypt(password,self.salt)
70 76 else
71 77 false
72 78 end
73 79 end
74 80
75 81 def admin?
76 82 self.roles.detect {|r| r.name == 'admin' }
77 83 end
78 84
79 85 def email_for_editing
80 86 if self.email==nil
81 87 "(unknown)"
82 88 elsif self.email==''
83 89 "(blank)"
84 90 else
85 91 self.email
86 92 end
87 93 end
88 94
@@ -1,29 +1,32
1 + - content_for :header do
2 + = javascript_include_tag 'local_jquery'
3 +
1 4 %h1 System configuration
2 5
3 6 %table.info
4 7 %tr.info-head
5 8 %th Key
6 9 %th Type
7 10 %th Value
8 11 %th Description
9 12 - @configurations.each do |conf|
10 13 - @grader_configuration = conf
11 14 %tr{:class => cycle("info-odd", "info-even")}
12 15 %td
13 16 = in_place_editor_field :grader_configuration, :key, {}, :rows=>1
14 17 %td
15 18 = in_place_editor_field :grader_configuration, :value_type, {}, :rows=>1
16 19 %td
17 - = in_place_editor_field :grader_configuration, :value, {}, :rows=>1
20 + = best_in_place @grader_configuration, :value, ok_button: "ok", cancel_button: "cancel"
18 21 %td= conf.description
19 22
20 23 - if GraderConfiguration.config_cached?
21 24 %br/
22 25 Your config is saved, but it does not automatically take effect.
23 26 %br/
24 27 If you have one mongrel process running, you can
25 28 = link_to '[click]', :action => 'reload'
26 29 here to reload.
27 30 %br/
28 31 If you have more than one process running, you should restart
29 32 them manually.
@@ -1,51 +1,73
1 1 - content_for :head do
2 2 = stylesheet_link_tag 'graders'
3 + = javascript_include_tag 'local_jquery'
3 4 <meta http-equiv ="refresh" content="60"/>
4 5
5 6 %h1 Grader information
6 7
7 8 = link_to '[Refresh]', :action => 'list'
8 9 %br/
9 10
10 11 .submitbox
11 12 .item
12 13 Grader control:
13 14 .item
14 15 = form_for :clear, :url => {:action => 'start_grading'} do |f|
15 16 = submit_tag 'Start graders in grading env'
16 17 .item
17 18 = form_for :clear, :url => {:action => 'start_exam'} do |f|
18 19 = submit_tag 'Start graders in exam env'
19 20 .item
20 21 = form_for :clear, :url => {:action => 'stop_all'} do |f|
21 22 = submit_tag 'Stop all running graders'
22 23 .item
23 24 = form_for :clear, :url => {:action => 'clear_all'} do |f|
24 25 = submit_tag 'Clear all data'
25 26 %br{:style => 'clear:both'}/
26 27
28 + %div{style: 'width:500px; float: left;'}
27 29 - if @last_task
28 30 Last task:
29 31 = link_to "#{@last_task.id}", :action => 'view', :id => @last_task.id, :type => 'Task'
30 32
31 33 %br/
32 34
33 35 - if @last_test_request
34 36 Last test_request:
35 37 = link_to "#{@last_test_request.id}", :action => 'view', :id => @last_test_request.id, :type => 'TestRequest'
36 38
37 -
38 39 %h2 Current graders
39 40
40 41 = render :partial => 'grader_list', :locals => {:grader_list => @grader_processes}
41 42
42 43 %h2 Stalled graders
43 44
44 45 = render :partial => 'grader_list', :locals => {:grader_list => @stalled_processes}
45 46
46 47 %h2 Terminated graders
47 48
48 49 = form_for :clear, :url => {:action => 'clear_terminated'} do |f|
49 50 = submit_tag 'Clear data for terminated graders'
50 51
51 52 = render :partial => 'grader_list', :locals => {:grader_list => @terminated_processes}
53 + %div{}
54 + %h2 Last 20 submissions
55 + %table.graders
56 + %thead
57 + %th ID
58 + %th User
59 + %th Problem
60 + %th Submitted
61 + %th Graded
62 + %th Result
63 + %tbody
64 + - @submission.each do |sub|
65 + %tr.inactive
66 + %td= link_to sub.id, controller: 'graders' ,action: 'submission', id: sub.id
67 + %td= sub.try(:user).try(:full_name)
68 + %td= sub.try(:problem).try(:full_name)
69 + %td= "#{time_ago_in_words(sub.submitted_at)} ago"
70 + %td= sub.graded_at ? "#{time_ago_in_words(sub.graded_at)} ago" : " "
71 + %td= sub.grader_comment
72 +
73 +
@@ -1,22 +1,67
1 + %style{type: "text/css"}
2 + = @css_style
3 + :css
4 + .field {
5 + font-weight: bold;
6 + text-align: right;
7 + padding: 3px;
8 + }
9 +
10 +
1 11 %h1= "Submission: #{@submission.id}"
2 12
3 - %p
4 - User:
5 - = "#{@submission.user.login}"
6 - %br/
7 - Problem:
8 - - if @submission.problem!=nil
9 - = "#{@submission.problem.full_name}"
13 +
14 + %h2 Stat
15 +
16 + %table.info
17 + %thead
18 + %tr.info-head
19 + %th Field
20 + %th Value
21 + %tbody
22 + %tr{class: cycle('info-even','info-odd')}
23 + %td.field User:
24 + %td.value
25 + - if @submission.user
26 + = link_to "(#{@submission.user.login})", controller: "users", action: "profile", id: @submission.user
27 + = @submission.user.full_name
10 28 - else
11 29 = "(n/a)"
12 - %br/
13 - = "Number: #{@submission.number}"
14 - %br/
15 - = "Submitted at: #{format_short_time(@submission.submitted_at)}"
30 + %tr{class: cycle('info-even','info-odd')}
31 + %td.field Problem:
32 + %td.value
33 + - if @submission.problem!=nil
34 + = link_to "(#{@submission.problem.name})", controller: "problems", action: "stat", id: @submission.problem
35 + = @submission.problem.full_name
36 + - else
37 + = "(n/a)"
38 + %tr{class: cycle('info-even','info-odd')}
39 + %td.field Tries:
40 + %td.value= @submission.number
41 + %tr{class: cycle('info-even','info-odd')}
42 + %td.field Submitted:
43 + %td.value #{time_ago_in_words(@submission.submitted_at)} ago (at #{@submission.submitted_at.to_formatted_s(:long)})
44 + %tr{class: cycle('info-even','info-odd')}
45 + %td.field Graded:
46 + %td.value #{time_ago_in_words(@submission.graded_at)} ago (at #{@submission.graded_at.to_formatted_s(:long)})
47 + %tr{class: cycle('info-even','info-odd')}
48 + %td.field Points:
49 + %td.value #{@submission.points}/#{@submission.problem.full_score}
50 + %tr{class: cycle('info-even','info-odd')}
51 + %td.field Comment:
52 + %td.value #{@submission.grader_comment}
53 + %tr{class: cycle('info-even','info-odd')}
54 + %td.field Runtime (s):
55 + %td.value #{@submission.max_runtime}
56 + %tr{class: cycle('info-even','info-odd')}
57 + %td.field Memory (kb):
58 + %td.value #{@submission.peak_memory}
59 + - if session[:admin]
60 + %tr{class: cycle('info-even','info-odd')}
61 + %td.field IP:
62 + %td.value #{@submission.ip_address}
16 63
17 - %b Source code (first 10kb)
18 - %div{:style => "border: 1px solid black; background: lightgrey"}
19 - - if @submission.source
20 - %pre
21 - =h truncate @submission.source, :length => 10240
64 + %h2 Source code
65 + //%div.highlight{:style => "border: 1px solid black;"}
66 + =@formatted_code.html_safe
22 67
@@ -1,21 +1,23
1 1 <!DOCTYPE html>
2 2 <html>
3 3 <head>
4 4 <title><%= GraderConfiguration['contest.name'] %></title>
5 5 <%= stylesheet_link_tag "application", :media => "all" %>
6 6 <%= javascript_include_tag "application" %>
7 7 <%= csrf_meta_tags %>
8 + <%= content_for :header %>
8 9 <%= yield :head %>
10 +
9 11 </head>
10 12 <body>
11 13
12 14 <div class="userbar">
13 15 <%= user_header %>
14 16 </div>
15 17
16 18 <%= content_tag(:p,flash[:notice],:style => "color:green") if flash[:notice]!=nil %>
17 19
18 20 <%= yield %>
19 21
20 22 </body>
21 23 </html>
@@ -1,18 +1,21
1 1 <tr class="info-<%= (problem_counter%2==0) ? "even" : "odd" %>">
2 2 <td>
3 3 <%= "#{problem_counter+1}" %>
4 4 </td>
5 5 <td>
6 - <%= "#{problem.full_name} (#{problem.name})" %>
6 + <%= "#{problem.name}"%>
7 + </td>
8 + <td>
9 + <%= "#{problem.full_name}" %>
7 10 <%= link_to_description_if_any "[#{t 'main.problem_desc'}]", problem %>
8 11 </td>
9 12 <td align="center">
10 13 <%= @prob_submissions[problem.id][:count] %>
11 14 </td>
12 15 <td>
13 16 <%= render :partial => 'submission_short',
14 17 :locals => {
15 18 :submission => @prob_submissions[problem.id][:submission],
16 19 :problem_name => problem.name }%>
17 20 </td>
18 21 </tr>
@@ -1,18 +1,24
1 1
2 2 %tr{:class => ((submission_counter%2==0) ? "info-even" : "info-odd")}
3 3 %td.info{:align => "center"}
4 4 = submission_counter+1
5 - %td.info= format_short_time(submission.submitted_at)
6 5 %td.info{:align => "center"}
6 + = link_to "##{submission.id}", controller: :graders, action: :submission, id: submission.id
7 + %td.info
8 + = l submission.submitted_at, format: :long
9 + = "( #{time_ago_in_words(submission.submitted_at)} ago)"
10 + %td.info{:align => "center"}
11 + = submission.source_filename
12 + = " (#{submission.language.pretty_name}) "
7 13 = link_to('[load]',{:action => 'source', :id => submission.id})
8 14 %td.info
9 15 - if submission.graded_at!=nil
10 16 = "Graded at #{format_short_time(submission.graded_at)}."
11 17 %br/
12 18 = "Score: #{(submission.points*100/submission.problem.full_score).to_i} " if GraderConfiguration['ui.show_score']
13 19 = " ["
14 20 %tt
15 21 = submission.grader_comment
16 22 = "]"
17 23 %td.info
18 24 = render :partial => 'compiler_message', :locals => {:compiler_message => submission.compiler_message }
@@ -4,48 +4,50
4 4 = user_title_bar(@user)
5 5
6 6 .announcementbox{:style => (@announcements.length==0 ? "display:none" : "")}
7 7 %span{:class => 'title'}
8 8 Announcements
9 9 #announcementbox-body
10 10 = render :partial => 'announcement', :collection => @announcements
11 11
12 12 - if GraderConfiguration.show_submitbox_to?(@user)
13 13 .submitbox
14 14 = error_messages_for 'submission'
15 15 = render :partial => 'submission_box'
16 16
17 17
18 18 %hr/
19 19
20 20 - if (GraderConfiguration.contest_mode?) and (@user.site!=nil) and (@user.site.started!=true)
21 21 %p=t 'main.start_soon'
22 22
23 23 - if GraderConfiguration.show_tasks_to?(@user)
24 24 - if not GraderConfiguration.multicontests?
25 25 %table.info
26 26 %tr.info-head
27 27 %th
28 - %th Tasks
28 + %th Tasks name
29 + %th Full name
29 30 %th # of sub(s)
30 31 %th Results
31 32 = render :partial => 'problem', :collection => @problems
32 33 - else
33 34 - @contest_problems.each do |cp|
34 35 - if cp[:problems].length > 0
35 36 %h2{:class =>'contest-title'}
36 37 = "#{cp[:contest] ? cp[:contest].title : 'Public problems'}"
37 38 %table.info
38 39 %tr.info-head
39 40 %th
40 - %th Tasks
41 + %th Tasks name
42 + %th Full name
41 43 %th # of sub(s)
42 44 %th Results
43 45 = render :partial => 'problem', :collection => cp[:problems]
44 46
45 47
46 48 %hr/
47 49
48 50 %script{:type => 'text/javascript'}
49 51 = "Announcement.refreshUrl = '#{url_for :controller => 'main', :action => 'announcements'}';"
50 52 Announcement.registerRefreshEventTimer();
51 53
@@ -1,24 +1,25
1 1 = user_title_bar(@user)
2 2
3 3 .task-menu
4 4 Task List
5 5 %br/
6 6 - @problems.each do |problem|
7 7 = link_to problem.name, :action => 'submission', :id => problem.name
8 8
9 9 - if @problem!=nil
10 10 %h2= "Task: #{@problem.full_name} (#{@problem.name})"
11 11
12 12 - if @submissions!=nil
13 13 - if @submissions.length>0
14 14 %table.info
15 15 %tr.info-head
16 + %th.info No.
16 17 %th.info #
17 18 %th.info At
18 19 %th.info Source
19 20 %th.info Result
20 21 %th.info{:width => "300px"}
21 22 Compiler message
22 23 = render :partial => 'submission', :collection => @submissions
23 24 - else
24 25 No submission
@@ -11,48 +11,61
11 11 = form_tag({:action => 'do_import'}, :multipart => true) do
12 12 .submitbox
13 13 %table
14 14 %tr
15 15 %td Name:
16 16 %td= text_field_tag 'name'
17 17 %tr
18 18 %td Full name:
19 19 %td
20 20 = text_field_tag 'full_name'
21 21 %span{:class => 'help'} Leave blank to use the same value as the name above.
22 22 %tr
23 23 %td Testdata file:
24 24 %td= file_field_tag 'file'
25 25 %tr
26 26 %td
27 27 %td
28 28 %span{:class => 'help'}
29 29 In .zip, .tgz, tar.gz, .tar format.
30 30 It should includes inputs (e.g., 1.in, 2a.in, 2b.in)
31 31 and solutions (e.g., 1.sol, 2a.sol, 2b.sol).
32 32 %br/
33 33 You may put task description in *.html for raw html
34 34 and *.md or *.markdown for markdown.
35 + %br/
36 + You may also put a pdf file for the task description
37 + %tr
38 + %td Checker:
39 + %td= select_tag 'checker', options_for_select([['Text checker','text'],['Float checker','float']], 'text')
40 + %tr
41 + %td
42 + %td
43 + %span{:class => 'help'}
44 + "Text" checker checks if the text (including numbers) is the same, ignoring any whitespace
45 + %br/
46 + "Float" checker checks if all numbers is within EPSILON error using formula |a-b| < EPSILON * max(|a|,|b|)
47 +
35 48 - if @allow_test_pair_import
36 49 %tr
37 50 %td
38 51 %td
39 52 = check_box_tag 'import_to_db'
40 53 Import test data to database (for a test-pair task)
41 54 %tr
42 55 %td Time limit:
43 56 %td
44 57 = text_field_tag 'time_limit'
45 58 %span{:class => 'help'} In seconds. Leave blank to use 1 sec.
46 59 %tr
47 60 %td Memory limit:
48 61 %td
49 62 = text_field_tag 'memory_limit'
50 63 %span{:class => 'help'} In MB. Leave blank to use 32MB.
51 64 %tr
52 65 %td
53 66 %td= submit_tag 'Import problem'
54 67
55 68 - if @log
56 69 %h3 Import log
57 70 %pre.import-log
58 71 = @log
@@ -1,43 +1,85
1 1 - content_for :head do
2 2 = stylesheet_link_tag 'problems'
3 + = javascript_include_tag 'local_jquery'
4 +
5 + :javascript
6 + $(document).ready( function() {
7 + function shiftclick(start,stop,value) {
8 + $('tr input').each( function(id,input) {
9 + var $input=$(input);
10 + var iid=parseInt($input.attr('id').split('-')[2]);
11 + if(iid>=start&&iid<=stop){
12 + $input.prop('checked',value)
13 + }
14 + });
15 + }
16 +
17 + $('tr input').click( function(e) {
18 + if (e.shiftKey) {
19 + stop = parseInt($(this).attr('id').split('-')[2]);
20 + var orig_stop = stop
21 + if (typeof start !== 'undefined') {
22 + if (start > stop) {
23 + var tmp = start;
24 + start = stop;
25 + stop = tmp;
26 + }
27 + shiftclick(start,stop,$(this).is(':checked') )
28 + }
29 + start = orig_stop
30 + } else {
31 + start = parseInt($(this).attr('id').split('-')[2]);
32 + }
33 + });
34 + });
35 +
3 36
4 37 %h1 Manage problems
5 38
6 39 %p= link_to '[Back to problem list]', :action => 'list'
7 40
8 41 = form_tag :action=>'do_manage' do
9 42 .submitbox
10 - What do you want to do?
43 + What do you want to do to the selected problem?
11 44 %br/
45 + (You can shift-click to select a range of problems)
12 46 %ul
13 47 %li
14 48 Change date added to
15 49 = select_date Date.current, :prefix => 'date_added'
16 50 &nbsp;&nbsp;&nbsp;
17 51 = submit_tag 'Change', :name => 'change_date_added'
52 + %li
53 + Set available to
54 + = submit_tag 'True', :name => 'enable_problem'
55 + = submit_tag 'False', :name => 'disable_problem'
18 56
19 57 - if GraderConfiguration.multicontests?
20 58 %li
21 59 Add to
22 60 = select("contest","id",Contest.all.collect {|c| [c.title, c.id]})
23 61 = submit_tag 'Add', :name => 'add_to_contest'
24 62
25 63 %table
26 - %tr
27 - %th/
64 + %tr{style: "text-align: left;"}
65 + %th= check_box_tag 'select_all'
28 66 %th Name
29 67 %th Full name
68 + %th Available
30 69 %th Date added
31 70 - if GraderConfiguration.multicontests?
32 71 %th Contests
33 72
73 + - num = 0
34 74 - for problem in @problems
75 + - num += 1
35 76 %tr{:id => "row-prob-#{problem.id}", :name=> "prob-#{problem.id}"}
36 - %td= check_box_tag "prob-#{problem.id}"
77 + %td= check_box_tag "prob-#{problem.id}-#{num}"
37 78 %td= problem.name
38 79 %td= problem.full_name
80 + %td= problem.available
39 81 %td= problem.date_added
40 82 - if GraderConfiguration.multicontests?
41 83 %td
42 84 - problem.contests.each do |contest|
43 85 = "(#{contest.name} [#{link_to 'x', :action => 'remove_contest', :id => problem.id, :contest_id => contest.id }])"
@@ -1,22 +1,25
1 1 <%= error_messages_for 'user' %>
2 2
3 3 <!--[form:user]-->
4 4 <p><label for="user_name">Login</label><br/>
5 5 <%= text_field 'user', 'login' %></p>
6 6
7 7 <p><label for="user_name">Full name</label><br/>
8 8 <%= text_field 'user', 'full_name' %></p>
9 9
10 10 <p><label for="password">Password</label><br/>
11 11 <%= password_field 'user', 'password' %></p>
12 12
13 13 <p><label for="password_confirmation">Password (confirm)</label><br/>
14 14 <%= password_field 'user', 'password_confirmation' %></p>
15 15
16 16 <p><label for="user_email">E-mail</label><br/>
17 17 <%= email_field 'user', 'email' %></p>
18 18
19 19 <p><label for="user_alias">Alias</label><br/>
20 20 <%= text_field 'user', 'alias' %></p>
21 +
22 + <p><label for="user_remark">Remark</label><br/>
23 + <%= text_field 'user', 'remark' %></p>
21 24 <!--[eoform:user]-->
22 25
@@ -47,40 +47,41
47 47
48 48 Total <%= @user_count %> users |
49 49 <% if !@paginated %>
50 50 Display all users.
51 51 <%= link_to '[show in pages]', :action => 'list', :page => '1' %>
52 52 <% else %>
53 53 Display in pages.
54 54 <%= link_to '[display all]', :action => 'list', :page => 'all' %> |
55 55 <%= will_paginate @users, :container => false %>
56 56 <% end %>
57 57 <table class="info">
58 58 <tr class="info-head">
59 59 <% for column in User.content_columns %>
60 60 <% if !@hidden_columns.index(column.name) %>
61 61 <th><%= column.human_name %></th>
62 62 <% end %>
63 63 <% end %>
64 64 <th></th>
65 65 <th></th>
66 66 <th></th>
67 67 </tr>
68 68
69 69 <% for user in @users %>
70 70 <tr class="info-<%= cycle("odd","even") %>">
71 + <td><%= link_to user.login, controller: :users, :action => 'profile', :id => user %></td>
71 72 <% for column in User.content_columns %>
72 - <% if !@hidden_columns.index(column.name) %>
73 + <% if !@hidden_columns.index(column.name) and column.name != 'login' %>
73 74 <td><%=h user.send(column.name) %></td>
74 75 <% end %>
75 76 <% end %>
76 77 <td><%= link_to 'Show', :action => 'show', :id => user %></td>
77 78 <td><%= link_to 'Edit', :action => 'edit', :id => user %></td>
78 79 <td><%= link_to 'Destroy', { :action => 'destroy', :id => user }, :confirm => 'Are you sure?', :method => :post %></td>
79 80 </tr>
80 81 <% end %>
81 82 </table>
82 83
83 84 <br />
84 85
85 86 <%= link_to '[New user]', :action => 'new' %>
86 87 <%= link_to '[New list of users]', :action => 'new_list' %>
@@ -39,26 +39,27
39 39 # Configure sensitive parameters which will be filtered from the log file.
40 40 config.filter_parameters += [:password]
41 41
42 42 # Enable escaping HTML in JSON.
43 43 config.active_support.escape_html_entities_in_json = true
44 44
45 45 # Use SQL instead of Active Record's schema dumper when creating the database.
46 46 # This is necessary if your schema can't be completely dumped by the schema dumper,
47 47 # like if you have constraints or database-specific column types
48 48 # config.active_record.schema_format = :sql
49 49
50 50 # Enforce whitelist mode for mass assignment.
51 51 # This will create an empty whitelist of attributes available for mass-assignment for all models
52 52 # in your app. As such, your models will need to explicitly whitelist or blacklist accessible
53 53 # parameters by using an attr_accessible or attr_protected declaration.
54 54 config.active_record.whitelist_attributes = false
55 55
56 56 # Enable the asset pipeline
57 57 config.assets.enabled = true
58 58
59 59 # Version of your assets, change this if you want to expire all your assets
60 60 config.assets.version = '1.0'
61 61
62 62 config.assets.precompile += ['announcement_refresh.js','effects.js','site_update.js','graders.css','problems.css']
63 + config.assets.precompile += ['local_jquery.js','tablesorter-theme.cafe.css']
63 64 end
64 65 end
@@ -1,46 +1,47
1 1 # Sample localization file for English. Add more files in this directory for other locales.
2 2 # See http://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points.
3 3
4 4 en:
5 5 cancel: 'Cancel'
6 6
7 7 login_label: 'Login'
8 8 full_name_label: 'Full name'
9 9 email_label: 'E-mail'
10 10 password_label: 'Password'
11 11
12 12 go_ahead_to: "Go ahead to "
13 13 go_back_to: "Go back to "
14 14 login_page: "login page"
15 15 home_page: "home page"
16 16
17 17 menu:
18 18 main: 'Main'
19 19 messages: 'Messages'
20 20 tasks: 'Tasks'
21 21 submissions: 'Submissions'
22 22 test: 'Test Interface'
23 + hall_of_fame: 'Hall of Fame'
23 24 help: 'Help'
24 25 settings: 'Settings'
25 26 log_out: 'Log out'
26 27
27 28 title_bar:
28 29 current_time: "Current time is"
29 30 remaining_time: "Time left: "
30 31 contest_not_started: "The contest has not started."
31 32
32 33 login:
33 34 message: 'Please login to see the problem list'
34 35 login_submit: 'Login'
35 36 participation: 'Want to participate?'
36 37 please: 'Please'
37 38 register: 'register'
38 39 forget_password: 'Forget password?'
39 40
40 41 main:
41 42 start_soon: "The contest at your site will start soon. Please wait."
42 43 specified_in_header: "Specified in header"
43 44
44 45 problem_desc: "desc"
45 46 submitted_at: "Submitted at"
46 47 graded_at: "Graded at"
@@ -1,46 +1,47
1 1 # Sample localization file for English. Add more files in this directory for other locales.
2 2 # See http://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points.
3 3
4 4 th:
5 5 cancel: 'ยกเลิก'
6 6
7 7 login_label: 'ชื่อเข้าใช้ระบบ (login)'
8 8 full_name_label: 'ชื่อเต็ม'
9 9 email_label: 'E-mail'
10 10 password_label: 'รหัสผ่าน'
11 11
12 12 go_ahead_to: "ไปยัง"
13 13 go_back_to: "กลับไปยัง"
14 14 login_page: "หน้าเข้าใช้ระบบ"
15 15 home_page: "หน้าแรก"
16 16
17 17 menu:
18 18 main: 'หน้าหลัก'
19 19 messages: 'ข้อความ'
20 20 tasks: 'โจทย์'
21 21 submissions: 'โปรแกรมที่ส่ง'
22 22 test: 'ทดสอบโปรแกรม'
23 + hall_of_fame: 'หอเกียรติยศ'
23 24 help: 'ความช่วยเหลือ'
24 25 settings: 'เปลี่ยนรหัสผ่าน'
25 26 log_out: 'ออกจากระบบ'
26 27
27 28 title_bar:
28 29 current_time: "เวลาปัจจุบันคือ"
29 30 remaining_time: "เหลือเวลาอีก"
30 31 contest_not_started: "ยังไม่เริ่มแข่งขัน"
31 32
32 33 login:
33 34 message: 'กรุณา login เพื่อเข้าสู่ระบบ'
34 35 login_submit: 'เข้าใช้ระบบ'
35 36 participation: 'ต้องการเข้าร่วม?'
36 37 please: 'กรุณา'
37 38 register: 'ลงทะเบียน'
38 39 forget_password: 'ลืมรหัสผ่าน?'
39 40
40 41 main:
41 42 start_soon: "การแข่งขันกำลังจะเริ่ม กรุณารอก่อน"
42 43 specified_in_header: "ระบุที่หัวโปรแกรมแล้ว"
43 44
44 45 problem_desc: "อ่าน"
45 46 submitted_at: "ส่งเมื่อเวลา"
46 47 graded_at: "ตรวจเมื่อเวลา"
@@ -1,30 +1,34
1 1 CafeGrader::Application.routes.draw do
2 + get "report/login"
3 +
2 4 resources :contests
3 5
4 6 resources :announcements
5 7 resources :sites
6 8
9 + resources :grader_configuration, controller: 'configurations'
10 +
7 11 # The priority is based upon order of creation:
8 12 # first created -> highest priority.
9 13
10 14 # Sample of regular route:
11 15 # match 'products/:id' => 'catalog#view'
12 16 # Keep in mind you can assign values other than :controller and :action
13 17
14 18 # Sample of named route:
15 19 # match 'products/:id/purchase' => 'catalog#purchase', :as => :purchase
16 20 # This route can be invoked with purchase_url(:id => product.id)
17 21
18 22 # Sample resource route (maps HTTP verbs to controller actions automatically):
19 23 # resources :products
20 24
21 25 # Sample resource route with options:
22 26 # resources :products do
23 27 # member do
24 28 # get 'short'
25 29 # post 'toggle'
26 30 # end
27 31 #
28 32 # collection do
29 33 # get 'sold'
30 34 # end
@@ -1,273 +1,248
1 1 # encoding: UTF-8
2 2 # This file is auto-generated from the current state of the database. Instead
3 3 # of editing this file, please use the migrations feature of Active Record to
4 4 # incrementally modify your database, and then regenerate this schema definition.
5 5 #
6 6 # Note that this schema.rb definition is the authoritative source for your
7 7 # database schema. If you need to create the application database on another
8 8 # system, you should be using db:schema:load, not running all the migrations
9 9 # from scratch. The latter is a flawed and unsustainable approach (the more migrations
10 10 # you'll amass, the slower it'll run and the greater likelihood for issues).
11 11 #
12 12 # It's strongly recommended to check this file into your version control system.
13 13
14 - ActiveRecord::Schema.define(:version => 20121001033508) do
14 + ActiveRecord::Schema.define(:version => 20150203153534) do
15 15
16 16 create_table "announcements", :force => true do |t|
17 17 t.string "author"
18 18 t.text "body"
19 19 t.boolean "published"
20 - t.datetime "created_at"
21 - t.datetime "updated_at"
20 + t.datetime "created_at", :null => false
21 + t.datetime "updated_at", :null => false
22 22 t.boolean "frontpage", :default => false
23 23 t.boolean "contest_only", :default => false
24 24 t.string "title"
25 25 t.string "notes"
26 26 end
27 27
28 - create_table "codejom_statuses", :force => true do |t|
29 - t.integer "user_id"
30 - t.boolean "alive"
31 - t.integer "num_problems_passed"
32 - t.datetime "created_at"
33 - t.datetime "updated_at"
34 - end
35 -
36 28 create_table "contests", :force => true do |t|
37 29 t.string "title"
38 30 t.boolean "enabled"
39 - t.datetime "created_at"
40 - t.datetime "updated_at"
31 + t.datetime "created_at", :null => false
32 + t.datetime "updated_at", :null => false
41 33 t.string "name"
42 34 end
43 35
44 36 create_table "contests_problems", :id => false, :force => true do |t|
45 37 t.integer "contest_id"
46 38 t.integer "problem_id"
47 39 end
48 40
49 41 create_table "contests_users", :id => false, :force => true do |t|
50 42 t.integer "contest_id"
51 43 t.integer "user_id"
52 44 end
53 45
54 46 create_table "countries", :force => true do |t|
55 47 t.string "name"
56 - t.datetime "created_at"
57 - t.datetime "updated_at"
48 + t.datetime "created_at", :null => false
49 + t.datetime "updated_at", :null => false
58 50 end
59 51
60 52 create_table "descriptions", :force => true do |t|
61 53 t.text "body"
62 54 t.boolean "markdowned"
63 - t.datetime "created_at"
64 - t.datetime "updated_at"
55 + t.datetime "created_at", :null => false
56 + t.datetime "updated_at", :null => false
65 57 end
66 58
67 59 create_table "grader_configurations", :force => true do |t|
68 60 t.string "key"
69 61 t.string "value_type"
70 62 t.string "value"
71 - t.datetime "created_at"
72 - t.datetime "updated_at"
63 + t.datetime "created_at", :null => false
64 + t.datetime "updated_at", :null => false
73 65 t.text "description"
74 66 end
75 67
76 68 create_table "grader_processes", :force => true do |t|
77 69 t.string "host", :limit => 20
78 70 t.integer "pid"
79 71 t.string "mode"
80 72 t.boolean "active"
81 - t.datetime "created_at"
82 - t.datetime "updated_at"
73 + t.datetime "created_at", :null => false
74 + t.datetime "updated_at", :null => false
83 75 t.integer "task_id"
84 76 t.string "task_type"
85 77 t.boolean "terminated"
86 78 end
87 79
88 80 add_index "grader_processes", ["host", "pid"], :name => "index_grader_processes_on_ip_and_pid"
89 81
90 82 create_table "languages", :force => true do |t|
91 83 t.string "name", :limit => 10
92 84 t.string "pretty_name"
93 85 t.string "ext", :limit => 10
94 86 t.string "common_ext"
95 87 end
96 88
89 + create_table "logins", :force => true do |t|
90 + t.string "user_id"
91 + t.string "ip_address"
92 + t.datetime "created_at", :null => false
93 + t.datetime "updated_at", :null => false
94 + end
95 +
97 96 create_table "messages", :force => true do |t|
98 97 t.integer "sender_id"
99 98 t.integer "receiver_id"
100 99 t.integer "replying_message_id"
101 100 t.text "body"
102 101 t.boolean "replied"
103 - t.datetime "created_at"
104 - t.datetime "updated_at"
102 + t.datetime "created_at", :null => false
103 + t.datetime "updated_at", :null => false
105 104 end
106 105
107 106 create_table "problems", :force => true do |t|
108 107 t.string "name", :limit => 30
109 108 t.string "full_name"
110 109 t.integer "full_score"
111 110 t.date "date_added"
112 111 t.boolean "available"
113 112 t.string "url"
114 113 t.integer "description_id"
115 114 t.boolean "test_allowed"
116 115 t.boolean "output_only"
117 - t.integer "level", :default => 0
118 - t.datetime "updated_at"
119 116 t.string "description_filename"
120 117 end
121 118
122 119 create_table "rights", :force => true do |t|
123 120 t.string "name"
124 121 t.string "controller"
125 122 t.string "action"
126 123 end
127 124
128 125 create_table "rights_roles", :id => false, :force => true do |t|
129 126 t.integer "right_id"
130 127 t.integer "role_id"
131 128 end
132 129
133 130 add_index "rights_roles", ["role_id"], :name => "index_rights_roles_on_role_id"
134 131
135 132 create_table "roles", :force => true do |t|
136 133 t.string "name"
137 134 end
138 135
139 136 create_table "roles_users", :id => false, :force => true do |t|
140 137 t.integer "role_id"
141 138 t.integer "user_id"
142 139 end
143 140
144 141 add_index "roles_users", ["user_id"], :name => "index_roles_users_on_user_id"
145 142
146 143 create_table "sessions", :force => true do |t|
147 144 t.string "session_id"
148 145 t.text "data"
149 146 t.datetime "updated_at"
150 147 end
151 148
152 149 add_index "sessions", ["session_id"], :name => "index_sessions_on_session_id"
153 150 add_index "sessions", ["updated_at"], :name => "index_sessions_on_updated_at"
154 151
155 152 create_table "sites", :force => true do |t|
156 153 t.string "name"
157 154 t.boolean "started"
158 155 t.datetime "start_time"
159 - t.datetime "created_at"
160 - t.datetime "updated_at"
156 + t.datetime "created_at", :null => false
157 + t.datetime "updated_at", :null => false
161 158 t.integer "country_id"
162 159 t.string "password"
163 160 end
164 161
165 - create_table "submission_statuses", :force => true do |t|
166 - t.integer "user_id"
167 - t.integer "problem_id"
168 - t.boolean "passed"
169 - t.integer "submission_count"
170 - t.datetime "created_at"
171 - t.datetime "updated_at"
172 - end
173 -
174 162 create_table "submissions", :force => true do |t|
175 163 t.integer "user_id"
176 164 t.integer "problem_id"
177 165 t.integer "language_id"
178 166 t.text "source"
179 167 t.binary "binary"
180 168 t.datetime "submitted_at"
181 169 t.datetime "compiled_at"
182 170 t.text "compiler_message"
183 171 t.datetime "graded_at"
184 172 t.integer "points"
185 173 t.text "grader_comment"
186 174 t.integer "number"
187 175 t.string "source_filename"
176 + t.float "max_runtime"
177 + t.integer "peak_memory"
178 + t.integer "effective_code_length"
179 + t.string "ip_address"
188 180 end
189 181
190 182 add_index "submissions", ["user_id", "problem_id", "number"], :name => "index_submissions_on_user_id_and_problem_id_and_number", :unique => true
191 183 add_index "submissions", ["user_id", "problem_id"], :name => "index_submissions_on_user_id_and_problem_id"
192 184
193 185 create_table "tasks", :force => true do |t|
194 186 t.integer "submission_id"
195 187 t.datetime "created_at"
196 188 t.integer "status"
197 189 t.datetime "updated_at"
198 190 end
199 191
200 - create_table "test_pair_assignments", :force => true do |t|
201 - t.integer "user_id"
202 - t.integer "problem_id"
203 - t.integer "test_pair_id"
204 - t.integer "test_pair_number"
205 - t.integer "request_number"
206 - t.datetime "created_at"
207 - t.datetime "updated_at"
208 - t.boolean "submitted"
209 - end
210 -
211 192 create_table "test_pairs", :force => true do |t|
212 193 t.integer "problem_id"
213 194 t.text "input", :limit => 16777215
214 195 t.text "solution", :limit => 16777215
215 - t.datetime "created_at"
216 - t.datetime "updated_at"
217 - t.integer "number"
196 + t.datetime "created_at", :null => false
197 + t.datetime "updated_at", :null => false
218 198 end
219 199
220 200 create_table "test_requests", :force => true do |t|
221 201 t.integer "user_id"
222 202 t.integer "problem_id"
223 203 t.integer "submission_id"
224 204 t.string "input_file_name"
225 205 t.string "output_file_name"
226 206 t.string "running_stat"
227 207 t.integer "status"
228 - t.datetime "updated_at"
208 + t.datetime "updated_at", :null => false
229 209 t.datetime "submitted_at"
230 210 t.datetime "compiled_at"
231 211 t.text "compiler_message"
232 212 t.datetime "graded_at"
233 213 t.string "grader_comment"
234 - t.datetime "created_at"
214 + t.datetime "created_at", :null => false
235 215 t.float "running_time"
236 216 t.string "exit_status"
237 217 t.integer "memory_usage"
238 218 end
239 219
240 220 add_index "test_requests", ["user_id", "problem_id"], :name => "index_test_requests_on_user_id_and_problem_id"
241 221
242 222 create_table "user_contest_stats", :force => true do |t|
243 223 t.integer "user_id"
244 224 t.datetime "started_at"
245 - t.datetime "created_at"
246 - t.datetime "updated_at"
225 + t.datetime "created_at", :null => false
226 + t.datetime "updated_at", :null => false
247 227 t.boolean "forced_logout"
248 228 end
249 229
250 230 create_table "users", :force => true do |t|
251 231 t.string "login", :limit => 50
252 232 t.string "full_name"
253 233 t.string "hashed_password"
254 234 t.string "salt", :limit => 5
255 235 t.string "alias"
256 236 t.string "email"
257 237 t.integer "site_id"
258 238 t.integer "country_id"
259 239 t.boolean "activated", :default => false
260 240 t.datetime "created_at"
261 241 t.datetime "updated_at"
262 - t.string "member1_full_name"
263 - t.string "member2_full_name"
264 - t.string "member3_full_name"
265 - t.boolean "high_school"
266 - t.string "member1_school_name"
267 - t.string "member2_school_name"
268 - t.string "member3_school_name"
242 + t.boolean "enabled", :default => true
243 + t.string "remark"
269 244 end
270 245
271 246 add_index "users", ["login"], :name => "index_users_on_login", :unique => true
272 247
273 248 end
@@ -33,82 +33,96
33 33 },
34 34
35 35 {
36 36 :key => 'system.mode',
37 37 :value_type => 'string',
38 38 :default_value => 'standard',
39 39 :description => 'Current modes are "standard", "contest", "indv-contest", and "analysis".'
40 40 },
41 41
42 42 {
43 43 :key => 'contest.name',
44 44 :value_type => 'string',
45 45 :default_value => 'Grader',
46 46 :description => 'This name will be shown on the user header bar.'
47 47 },
48 48
49 49 {
50 50 :key => 'contest.multisites',
51 51 :value_type => 'boolean',
52 52 :default_value => 'false',
53 53 :description => 'If the server is in contest mode and this option is true, on the log in of the admin a menu for site selections is shown.'
54 54 },
55 55
56 56 {
57 - :key => 'system.online_registration',
57 + :key => 'right.user_hall_of_fame',
58 58 :value_type => 'boolean',
59 59 :default_value => 'false',
60 - :description => 'This option enables online registration.'
60 + :description => 'If true, any user can access hall of fame page.'
61 + },
62 +
63 + {
64 + :key => 'right.user_view_submission',
65 + :value_type => 'boolean',
66 + :default_value => 'false',
67 + :description => 'If true, any user can view submissions of every one.'
61 68 },
62 69
63 70 # If Configuration['system.online_registration'] is true, the
64 71 # system allows online registration, and will use these
65 72 # information for sending confirmation emails.
66 73 {
67 74 :key => 'system.online_registration.smtp',
68 75 :value_type => 'string',
69 76 :default_value => 'smtp.somehost.com'
70 77 },
71 78
72 79 {
73 80 :key => 'system.online_registration.from',
74 81 :value_type => 'string',
75 82 :default_value => 'your.email@address'
76 83 },
77 84
78 85 {
79 86 :key => 'system.admin_email',
80 87 :value_type => 'string',
81 88 :default_value => 'admin@admin.email'
82 89 },
83 90
84 91 {
85 92 :key => 'system.user_setting_enabled',
86 93 :value_type => 'boolean',
87 94 :default_value => 'true',
88 95 :description => 'If this option is true, users can change their settings'
89 96 },
90 97
98 + {
99 + :key => 'system.user_setting_enabled',
100 + :value_type => 'boolean',
101 + :default_value => 'true',
102 + :description => 'If this option is true, users can change their settings'
103 + },
104 +
91 105 # If Configuration['contest.test_request.early_timeout'] is true
92 106 # the user will not be able to use test request at 30 minutes
93 107 # before the contest ends.
94 108 {
95 109 :key => 'contest.test_request.early_timeout',
96 110 :value_type => 'boolean',
97 111 :default_value => 'false'
98 112 },
99 113
100 114 {
101 115 :key => 'system.multicontests',
102 116 :value_type => 'boolean',
103 117 :default_value => 'false'
104 118 },
105 119
106 120 {
107 121 :key => 'contest.confirm_indv_contest_start',
108 122 :value_type => 'boolean',
109 123 :default_value => 'false'
110 124 },
111 125
112 126 {
113 127 :key => 'contest.default_contest_name',
114 128 :value_type => 'string',
@@ -173,26 +187,37
173 187 :full_name => 'Administrator',
174 188 :alias => 'root')
175 189 root.password = 'ioionrails';
176 190
177 191 class << root
178 192 public :encrypt_new_password
179 193 def valid?(context=nil)
180 194 true
181 195 end
182 196 end
183 197
184 198 root.encrypt_new_password
185 199
186 200 root.roles << Role.find_by_name('admin')
187 201
188 202 root.activated = true
189 203 root.save
190 204 end
191 205
192 206 def seed_users_and_roles
193 207 seed_roles
194 208 seed_root
195 209 end
196 210
211 + def seed_more_languages
212 + Language.delete_all
213 + Language.create( name: 'c', pretty_name: 'C', ext: 'c', common_ext: 'c' )
214 + Language.create( name: 'cpp', pretty_name: 'C++', ext: 'cpp', common_ext: 'cpp,cc' )
215 + Language.create( name: 'pas', pretty_name: 'Pascal', ext: 'pas', common_ext: 'pas' )
216 + Language.create( name: 'ruby', pretty_name: 'Ruby', ext: 'rb', common_ext: 'rb' )
217 + Language.create( name: 'python', pretty_name: 'Python', ext: 'py', common_ext: 'py' )
218 + Language.create( name: 'java', pretty_name: 'Java', ext: 'java', common_ext: 'java' )
219 + end
220 +
197 221 seed_config
198 222 seed_users_and_roles
223 + seed_more_languages
@@ -8,51 +8,51
8 8 end
9 9 end
10 10
11 11 def self.raw_dir
12 12 File.join GRADER_ROOT_DIR, "raw"
13 13 end
14 14
15 15 def self.call_grader(params)
16 16 if GraderScript.grader_control_enabled?
17 17 cmd = File.join(GRADER_ROOT_DIR, "scripts/grader") + " " + params
18 18 system(cmd)
19 19 end
20 20 end
21 21
22 22 def self.stop_grader(pid)
23 23 GraderScript.call_grader "stop #{pid}"
24 24 end
25 25
26 26 def self.stop_graders(pids)
27 27 pid_str = (pids.map { |process| process.pid.to_s }).join ' '
28 28 GraderScript.call_grader "stop #{pid_str}"
29 29 end
30 30
31 31 def self.start_grader(env)
32 - GraderScript.call_grader "#{env} queue &"
33 - GraderScript.call_grader "#{env} test_request &"
32 + GraderScript.call_grader "#{env} queue --err-log &"
33 + GraderScript.call_grader "#{env} test_request -err-log &"
34 34 end
35 35
36 36 def self.call_import_problem(problem_name,
37 37 problem_dir,
38 38 time_limit=1,
39 39 memory_limit=32,
40 40 checker_name='text')
41 41 if GraderScript.grader_control_enabled?
42 42 cur_dir = `pwd`.chomp
43 43 Dir.chdir(GRADER_ROOT_DIR)
44 44
45 45 script_name = File.join(GRADER_ROOT_DIR, "scripts/import_problem")
46 46 cmd = "#{script_name} #{problem_name} #{problem_dir} #{checker_name}" +
47 47 " -t #{time_limit} -m #{memory_limit}"
48 48
49 49 output = `#{cmd}`
50 50
51 51 Dir.chdir(cur_dir)
52 52
53 - return output
53 + return "import CMD: #{cmd}\n" + output
54 54 end
55 55 return ''
56 56 end
57 57
58 58 end
@@ -1,46 +1,48
1 1 require 'tmpdir'
2 2
3 3 class TestdataImporter
4 4
5 5 attr :log_msg
6 6
7 7 def initialize(problem)
8 8 @problem = problem
9 9 end
10 10
11 11 def import_from_file(tempfile,
12 12 time_limit,
13 13 memory_limit,
14 + checker_name='text',
14 15 import_to_db=false)
15 16
16 17 dirname = extract(tempfile)
17 18 return false if not dirname
18 19 if not import_to_db
19 20 @log_msg = GraderScript.call_import_problem(@problem.name,
20 21 dirname,
21 22 time_limit,
22 - memory_limit)
23 + memory_limit,
24 + checker_name)
23 25 else
24 26 # Import test data to test pairs.
25 27
26 28 @problem.test_pairs.clear
27 29 if import_test_pairs(dirname)
28 30 test_pair_count = TestPair.count :conditions => "problem_id = #{@problem.id}"
29 31 @log_msg = "Importing test pair successful. (#{test_pair_count} test pairs imported)"
30 32 else
31 33 @log_msg = "Importing test pair failed. (0 test pairs imported)"
32 34 end
33 35 end
34 36
35 37 @log_msg << import_problem_description(dirname)
36 38 @log_msg << import_problem_pdf(dirname)
37 39 @log_msg << import_full_score(dirname)
38 40
39 41 return true
40 42 end
41 43
42 44 protected
43 45
44 46 def self.long_ext(filename)
45 47 i = filename.index('.')
46 48 len = filename.length
deleted file
deleted file
deleted file
You need to be logged in to leave comments. Login now