Description:
Merge pull request #14 from nattee/master
merge commit from nattee
Commit status:
[Not Reviewed]
References:
Diff options:
Comments:
0 Commit comments
0 Inline Comments
Unresolved TODOs:
There are no unresolved TODOs
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,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 | |
|
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 |
- |
|
|
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 |
- els |
|
|
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 |
|
|
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 |
- % |
|
|
18 |
- %div{:style => "border: 1px solid black; |
|
|
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. |
|
|
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 | |
|
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 => 201 |
|
|
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 |
|
109 | 108 |
t.string |
|
110 | 109 |
t.integer |
|
111 | 110 |
t.date |
|
112 | 111 |
t.boolean |
|
113 | 112 |
t.string |
|
114 | 113 |
t.integer |
|
115 | 114 |
t.boolean |
|
116 | 115 |
t.boolean |
|
117 | - t.integer "level", :default => 0 | |
|
118 | - t.datetime "updated_at" | |
|
119 | 116 |
t.string |
|
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", |
|
252 | 232 | t.string "full_name" |
|
253 | 233 | t.string "hashed_password" |
|
254 | 234 |
t.string "salt", |
|
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", |
|
260 | 240 | t.datetime "created_at" |
|
261 | 241 | t.datetime "updated_at" |
|
262 | - t.string "member1_full_name" | |
|
263 |
- t.string " |
|
|
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