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 |
@@ -17,3 +17,10 | |||
|
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 |
@@ -35,6 +35,19 | |||
|
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' |
@@ -37,6 +37,9 | |||
|
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) |
@@ -57,6 +60,22 | |||
|
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) |
@@ -92,7 +111,8 | |||
|
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) |
@@ -134,17 +154,24 | |||
|
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! |
@@ -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,3 +1,12 | |||
|
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 |
@@ -290,4 +299,4 | |||
|
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 |
@@ -6,7 +6,12 | |||
|
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) |
@@ -23,6 +28,10 | |||
|
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 |
@@ -3,9 +3,6 | |||
|
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, |
@@ -17,4 +14,15 | |||
|
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,6 +1,15 | |||
|
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', |
@@ -23,6 +32,7 | |||
|
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 |
@@ -63,6 +73,19 | |||
|
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 |
@@ -22,6 +22,9 | |||
|
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' |
@@ -63,10 +63,12 | |||
|
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.") |
@@ -150,11 +150,25 | |||
|
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 |
@@ -164,8 +178,12 | |||
|
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 |
@@ -234,15 +252,26 | |||
|
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,3 +1,5 | |||
|
1 | + require 'csv' | |
|
2 | + | |
|
1 | 3 | class UserAdminController < ApplicationController |
|
2 | 4 | |
|
3 | 5 | include MailHelperMethods |
@@ -81,11 +83,17 | |||
|
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 | |
@@ -122,7 +130,11 | |||
|
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| |
@@ -141,7 +153,11 | |||
|
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 |
@@ -159,6 +175,13 | |||
|
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 |
@@ -473,4 +496,35 | |||
|
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 |
@@ -14,6 +14,7 | |||
|
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 } |
@@ -108,6 +109,30 | |||
|
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 |
@@ -153,4 +178,18 | |||
|
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 |
@@ -13,6 +13,7 | |||
|
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' |
@@ -29,6 +30,10 | |||
|
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'] |
@@ -14,7 +14,7 | |||
|
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) |
@@ -43,6 +43,7 | |||
|
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 |
@@ -54,6 +55,13 | |||
|
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) |
@@ -90,6 +98,11 | |||
|
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? |
@@ -106,7 +119,8 | |||
|
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 |
@@ -25,7 +25,7 | |||
|
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 " + |
@@ -1,4 +1,8 | |||
|
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 | |
@@ -61,7 +65,9 | |||
|
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) |
@@ -1,3 +1,6 | |||
|
1 | + - content_for :header do | |
|
2 | + = javascript_include_tag 'local_jquery' | |
|
3 | + | |
|
1 | 4 | %h1 System configuration |
|
2 | 5 | |
|
3 | 6 | %table.info |
@@ -14,7 +17,7 | |||
|
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? |
@@ -1,5 +1,6 | |||
|
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 |
@@ -24,6 +25,7 | |||
|
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' |
@@ -34,7 +36,6 | |||
|
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} |
@@ -49,3 +50,24 | |||
|
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 |
@@ -5,7 +5,9 | |||
|
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 |
@@ -3,7 +3,10 | |||
|
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"> |
@@ -2,8 +2,14 | |||
|
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 |
@@ -25,7 +25,8 | |||
|
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 |
@@ -37,7 +38,8 | |||
|
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] |
@@ -13,6 +13,7 | |||
|
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 |
@@ -32,6 +32,19 | |||
|
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 |
@@ -1,5 +1,38 | |||
|
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 | |
@@ -7,14 +40,19 | |||
|
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 |
@@ -23,19 +61,23 | |||
|
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 |
@@ -18,5 +18,8 | |||
|
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 |
@@ -68,8 +68,9 | |||
|
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 %> |
@@ -60,5 +60,6 | |||
|
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 |
@@ -20,6 +20,7 | |||
|
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' |
@@ -20,6 +20,7 | |||
|
20 | 20 | tasks: 'โจทย์' |
|
21 | 21 | submissions: 'โปรแกรมที่ส่ง' |
|
22 | 22 | test: 'ทดสอบโปรแกรม' |
|
23 | + hall_of_fame: 'หอเกียรติยศ' | |
|
23 | 24 | help: 'ความช่วยเหลือ' |
|
24 | 25 | settings: 'เปลี่ยนรหัสผ่าน' |
|
25 | 26 | log_out: 'ออกจากระบบ' |
@@ -1,9 +1,13 | |||
|
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 |
@@ -11,33 +11,25 | |||
|
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 | |
@@ -53,23 +45,23 | |||
|
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 | |
@@ -78,8 +70,8 | |||
|
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" |
@@ -94,14 +86,21 | |||
|
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| |
@@ -114,8 +113,6 | |||
|
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 | |
@@ -156,21 +153,12 | |||
|
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" |
@@ -185,6 +173,10 | |||
|
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 |
@@ -197,24 +189,12 | |||
|
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| |
@@ -225,13 +205,13 | |||
|
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" |
@@ -242,8 +222,8 | |||
|
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 | |
@@ -259,13 +239,8 | |||
|
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 |
@@ -54,10 +54,17 | |||
|
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 |
@@ -88,6 +95,13 | |||
|
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. |
@@ -194,5 +208,16 | |||
|
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 |
@@ -29,8 +29,8 | |||
|
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, |
@@ -50,7 +50,7 | |||
|
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 |
@@ -11,6 +11,7 | |||
|
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) |
@@ -19,7 +20,8 | |||
|
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 |
deleted file |
deleted file |
deleted file |
You need to be logged in to leave comments.
Login now