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 |
@@ -14,6 +14,13 | |||||
|
14 | /log/*.log |
|
14 | /log/*.log |
|
15 | /tmp |
|
15 | /tmp |
|
16 |
|
16 | ||
|
17 | *~ |
|
17 | *~ |
|
18 |
|
18 | ||
|
19 | /vendor/plugins/rails_upgrade |
|
19 | /vendor/plugins/rails_upgrade |
|
|
20 | + | ||
|
|
21 | + #ignore public assets??? | ||
|
|
22 | + /public/assets | ||
|
|
23 | + | ||
|
|
24 | + #ignore .orig and .swp | ||
|
|
25 | + *.orig | ||
|
|
26 | + *.swp |
@@ -32,12 +32,25 | |||||
|
32 |
|
32 | ||
|
33 | # Deploy with Capistrano |
|
33 | # Deploy with Capistrano |
|
34 | # gem 'capistrano' |
|
34 | # gem 'capistrano' |
|
35 |
|
35 | ||
|
36 | # To use debugger |
|
36 | # To use debugger |
|
37 | # gem 'debugger' |
|
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 | gem 'haml' |
|
52 | gem 'haml' |
|
40 | gem 'mail' |
|
53 | gem 'mail' |
|
41 | gem 'rdiscount' |
|
54 | gem 'rdiscount' |
|
42 | gem 'test-unit' |
|
55 | gem 'test-unit' |
|
43 | gem 'will_paginate', '~> 3.0.7' |
|
56 | gem 'will_paginate', '~> 3.0.7' |
@@ -34,12 +34,15 | |||||
|
34 | activemodel (= 3.2.21) |
|
34 | activemodel (= 3.2.21) |
|
35 | activesupport (= 3.2.21) |
|
35 | activesupport (= 3.2.21) |
|
36 | activesupport (3.2.21) |
|
36 | activesupport (3.2.21) |
|
37 | i18n (~> 0.6, >= 0.6.4) |
|
37 | i18n (~> 0.6, >= 0.6.4) |
|
38 | multi_json (~> 1.0) |
|
38 | multi_json (~> 1.0) |
|
39 | arel (3.0.3) |
|
39 | arel (3.0.3) |
|
|
40 | + best_in_place (3.0.2) | ||
|
|
41 | + actionpack (>= 3.2) | ||
|
|
42 | + railties (>= 3.2) | ||
|
40 | builder (3.0.4) |
|
43 | builder (3.0.4) |
|
41 | coffee-rails (3.2.2) |
|
44 | coffee-rails (3.2.2) |
|
42 | coffee-script (>= 2.2.0) |
|
45 | coffee-script (>= 2.2.0) |
|
43 | railties (~> 3.2.0) |
|
46 | railties (~> 3.2.0) |
|
44 | coffee-script (2.3.0) |
|
47 | coffee-script (2.3.0) |
|
45 | coffee-script-source |
|
48 | coffee-script-source |
@@ -54,12 +57,28 | |||||
|
54 | hike (1.2.3) |
|
57 | hike (1.2.3) |
|
55 | i18n (0.7.0) |
|
58 | i18n (0.7.0) |
|
56 | in_place_editing (1.2.0) |
|
59 | in_place_editing (1.2.0) |
|
57 | journey (1.0.4) |
|
60 | journey (1.0.4) |
|
58 | json (1.8.1) |
|
61 | json (1.8.1) |
|
59 | mail (2.5.4) |
|
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 | mime-types (~> 1.16) |
|
79 | mime-types (~> 1.16) |
|
61 | treetop (~> 1.4.8) |
|
80 | treetop (~> 1.4.8) |
|
62 | mime-types (1.25.1) |
|
81 | mime-types (1.25.1) |
|
63 | multi_json (1.10.1) |
|
82 | multi_json (1.10.1) |
|
64 | mysql2 (0.3.17) |
|
83 | mysql2 (0.3.17) |
|
65 | polyglot (0.3.5) |
|
84 | polyglot (0.3.5) |
@@ -89,13 +108,14 | |||||
|
89 | rdoc (~> 3.4) |
|
108 | rdoc (~> 3.4) |
|
90 | thor (>= 0.14.6, < 2.0) |
|
109 | thor (>= 0.14.6, < 2.0) |
|
91 | rake (10.4.2) |
|
110 | rake (10.4.2) |
|
92 | rdiscount (2.1.7.1) |
|
111 | rdiscount (2.1.7.1) |
|
93 | rdoc (3.12.2) |
|
112 | rdoc (3.12.2) |
|
94 | json (~> 1.4) |
|
113 | json (~> 1.4) |
|
95 | - rspec-collection_matchers (1.1.2) |
|
114 | + rouge (1.6.2) |
|
|
115 | + rspec-collection_matchers (1.0.0) | ||
|
96 | rspec-expectations (>= 2.99.0.beta1) |
|
116 | rspec-expectations (>= 2.99.0.beta1) |
|
97 | rspec-core (2.99.2) |
|
117 | rspec-core (2.99.2) |
|
98 | rspec-expectations (2.99.2) |
|
118 | rspec-expectations (2.99.2) |
|
99 | diff-lcs (>= 1.1.3, < 2.0) |
|
119 | diff-lcs (>= 1.1.3, < 2.0) |
|
100 | rspec-mocks (2.99.2) |
|
120 | rspec-mocks (2.99.2) |
|
101 | rspec-rails (2.99.0) |
|
121 | rspec-rails (2.99.0) |
@@ -131,21 +151,28 | |||||
|
131 | will_paginate (3.0.7) |
|
151 | will_paginate (3.0.7) |
|
132 |
|
152 | ||
|
133 | PLATFORMS |
|
153 | PLATFORMS |
|
134 | ruby |
|
154 | ruby |
|
135 |
|
155 | ||
|
136 | DEPENDENCIES |
|
156 | DEPENDENCIES |
|
|
157 | + best_in_place (~> 3.0.1) | ||
|
137 | coffee-rails (~> 3.2.2) |
|
158 | coffee-rails (~> 3.2.2) |
|
138 | dynamic_form |
|
159 | dynamic_form |
|
139 | haml |
|
160 | haml |
|
140 | in_place_editing |
|
161 | in_place_editing |
|
|
162 | + jquery-rails | ||
|
|
163 | + jquery-tablesorter | ||
|
|
164 | + jquery-timepicker-addon-rails | ||
|
|
165 | + jquery-ui-sass-rails | ||
|
141 |
|
166 | ||
|
142 | mysql2 |
|
167 | mysql2 |
|
143 | prototype-rails |
|
168 | prototype-rails |
|
144 | rails (= 3.2.21) |
|
169 | rails (= 3.2.21) |
|
145 | rdiscount |
|
170 | rdiscount |
|
146 | - rspec-rails (~> 2.99.0) |
|
171 | + rouge |
|
147 | sass-rails (~> 3.2.6) |
|
172 | sass-rails (~> 3.2.6) |
|
|
173 | + rspec-rails (~> 2.0) | ||
|
|
174 | + | ||
|
148 | test-unit |
|
175 | test-unit |
|
149 | uglifier |
|
176 | uglifier |
|
150 | verification! |
|
177 | verification! |
|
151 | will_paginate (~> 3.0.7) |
|
178 | will_paginate (~> 3.0.7) |
@@ -1,182 +1,10 | |||||
|
1 | - == Welcome to Rails |
|
1 | + == cafe grader |
|
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. |
|
||
|
44 |
|
2 | ||
|
45 | - Mongrel is a Ruby-based webserver with a C-component (which requires compilation) that is |
|
3 | + cafe grader is a programming contest platform used in Thailand IOI training. |
|
46 | - suitable for development and deployment of Rails applications. If you have Ruby Gems installed, |
|
4 | + The package includes 2 repositories, jittat/cafe-grader-web and jittat/cafe-grader-judge-scripts. |
|
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' |
|
||
|
89 |
|
5 | ||
|
90 | - >> @posts.inspect |
|
6 | + === Installation |
|
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. |
|
||
|
133 |
|
7 | ||
|
134 | - app/views |
|
8 | + The system is tested on ubuntu 14.04 LTS. Use the installation script in |
|
135 | - Holds the template files for the view that should be named like |
|
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. |
|
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. |
|
||
|
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,6 +1,15 | |||||
|
|
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 | body |
|
10 | body |
|
2 | background: white image-url("topbg.jpg") repeat-x top center |
|
11 | background: white image-url("topbg.jpg") repeat-x top center |
|
3 | font-size: 13px |
|
12 | font-size: 13px |
|
4 | font-family: Tahoma, "sans-serif" |
|
13 | font-family: Tahoma, "sans-serif" |
|
5 | margin: 10px |
|
14 | margin: 10px |
|
6 | padding: 10px |
|
15 | padding: 10px |
@@ -287,7 +296,7 | |||||
|
287 | margin: 10px 0 |
|
296 | margin: 10px 0 |
|
288 | font-size: 12px |
|
297 | font-size: 12px |
|
289 | line-height: 1.5em |
|
298 | line-height: 1.5em |
|
290 |
|
299 | ||
|
291 | h2.contest-title |
|
300 | h2.contest-title |
|
292 | margin-top: 5px |
|
301 | margin-top: 5px |
|
293 | - margin-bottom: 5px No newline at end of file |
|
302 | + margin-bottom: 5px |
@@ -3,13 +3,18 | |||||
|
3 |
|
3 | ||
|
4 | SINGLE_USER_MODE_CONF_KEY = 'system.single_user_mode' |
|
4 | SINGLE_USER_MODE_CONF_KEY = 'system.single_user_mode' |
|
5 |
|
5 | ||
|
6 | def admin_authorization |
|
6 | def admin_authorization |
|
7 | return false unless authenticate |
|
7 | return false unless authenticate |
|
8 | user = User.find(session[:user_id], :include => ['roles']) |
|
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 | redirect_to :controller => 'main', :action => 'login' unless user.admin? |
|
11 | redirect_to :controller => 'main', :action => 'login' unless user.admin? |
|
|
12 | + return false | ||
|
|
13 | + end | ||
|
|
14 | + return true | ||
|
10 | end |
|
15 | end |
|
11 |
|
16 | ||
|
12 | def authorization_by_roles(allowed_roles) |
|
17 | def authorization_by_roles(allowed_roles) |
|
13 | return false unless authenticate |
|
18 | return false unless authenticate |
|
14 | user = User.find(session[:user_id]) |
|
19 | user = User.find(session[:user_id]) |
|
15 | unless user.roles.detect { |role| allowed_roles.member?(role.name) } |
|
20 | unless user.roles.detect { |role| allowed_roles.member?(role.name) } |
@@ -20,12 +25,16 | |||||
|
20 | end |
|
25 | end |
|
21 |
|
26 | ||
|
22 | protected |
|
27 | protected |
|
23 |
|
28 | ||
|
24 | def authenticate |
|
29 | def authenticate |
|
25 | unless session[:user_id] |
|
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 | redirect_to :controller => 'main', :action => 'login' |
|
35 | redirect_to :controller => 'main', :action => 'login' |
|
27 | return false |
|
36 | return false |
|
28 | end |
|
37 | end |
|
29 |
|
38 | ||
|
30 | # check if run in single user mode |
|
39 | # check if run in single user mode |
|
31 | if GraderConfiguration[SINGLE_USER_MODE_CONF_KEY] |
|
40 | if GraderConfiguration[SINGLE_USER_MODE_CONF_KEY] |
@@ -1,20 +1,28 | |||||
|
1 | class ConfigurationsController < ApplicationController |
|
1 | class ConfigurationsController < ApplicationController |
|
2 |
|
2 | ||
|
3 | before_filter :authenticate |
|
3 | before_filter :authenticate |
|
4 | before_filter { |controller| controller.authorization_by_roles(['admin'])} |
|
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 | def index |
|
7 | def index |
|
11 | @configurations = GraderConfiguration.find(:all, |
|
8 | @configurations = GraderConfiguration.find(:all, |
|
12 | :order => '`key`') |
|
9 | :order => '`key`') |
|
13 | end |
|
10 | end |
|
14 |
|
11 | ||
|
15 | def reload |
|
12 | def reload |
|
16 | GraderConfiguration.reload |
|
13 | GraderConfiguration.reload |
|
17 | redirect_to :action => 'index' |
|
14 | redirect_to :action => 'index' |
|
18 | end |
|
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 | end |
|
24 | end |
|
|
25 | + end | ||
|
|
26 | + end | ||
|
|
27 | + | ||
|
|
28 | + end |
@@ -1,9 +1,18 | |||||
|
1 | class GradersController < ApplicationController |
|
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 | verify :method => :post, :only => ['clear_all', |
|
14 | verify :method => :post, :only => ['clear_all', |
|
6 | 'start_exam', |
|
15 | 'start_exam', |
|
7 | 'start_grading', |
|
16 | 'start_grading', |
|
8 | 'stop_all', |
|
17 | 'stop_all', |
|
9 | 'clear_terminated'], |
|
18 | 'clear_terminated'], |
@@ -20,12 +29,13 | |||||
|
20 | @terminated_processes = GraderProcess.find_terminated_graders |
|
29 | @terminated_processes = GraderProcess.find_terminated_graders |
|
21 |
|
30 | ||
|
22 | @last_task = Task.find(:first, |
|
31 | @last_task = Task.find(:first, |
|
23 | :order => 'created_at DESC') |
|
32 | :order => 'created_at DESC') |
|
24 | @last_test_request = TestRequest.find(:first, |
|
33 | @last_test_request = TestRequest.find(:first, |
|
25 | :order => 'created_at DESC') |
|
34 | :order => 'created_at DESC') |
|
|
35 | + @submission = Submission.order("id desc").limit(20) | ||
|
26 | end |
|
36 | end |
|
27 |
|
37 | ||
|
28 | def clear |
|
38 | def clear |
|
29 | grader_proc = GraderProcess.find(params[:id]) |
|
39 | grader_proc = GraderProcess.find(params[:id]) |
|
30 | grader_proc.destroy if grader_proc!=nil |
|
40 | grader_proc.destroy if grader_proc!=nil |
|
31 | redirect_to :action => 'list' |
|
41 | redirect_to :action => 'list' |
@@ -60,12 +70,25 | |||||
|
60 | def task |
|
70 | def task |
|
61 | @task = Task.find(params[:id]) |
|
71 | @task = Task.find(params[:id]) |
|
62 | end |
|
72 | end |
|
63 |
|
73 | ||
|
64 | def submission |
|
74 | def submission |
|
65 | @submission = Submission.find(params[:id]) |
|
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 | end |
|
89 | end |
|
67 |
|
90 | ||
|
68 | # various grader controls |
|
91 | # various grader controls |
|
69 |
|
92 | ||
|
70 | def stop |
|
93 | def stop |
|
71 | grader_proc = GraderProcess.find(params[:id]) |
|
94 | grader_proc = GraderProcess.find(params[:id]) |
@@ -19,12 +19,15 | |||||
|
19 | contest_stat.forced_logout = false |
|
19 | contest_stat.forced_logout = false |
|
20 | contest_stat.save |
|
20 | contest_stat.save |
|
21 | end |
|
21 | end |
|
22 | end |
|
22 | end |
|
23 | end |
|
23 | end |
|
24 |
|
24 | ||
|
|
25 | + #save login information | ||
|
|
26 | + Login.create(user_id: user.id, ip_address: request.remote_ip) | ||
|
|
27 | + | ||
|
25 | redirect_to :controller => 'main', :action => 'list' |
|
28 | redirect_to :controller => 'main', :action => 'list' |
|
26 | else |
|
29 | else |
|
27 | flash[:notice] = 'Wrong password' |
|
30 | flash[:notice] = 'Wrong password' |
|
28 | redirect_to :controller => 'main', :action => 'login' |
|
31 | redirect_to :controller => 'main', :action => 'login' |
|
29 | end |
|
32 | end |
|
30 | end |
|
33 | end |
@@ -60,16 +60,18 | |||||
|
60 |
|
60 | ||
|
61 | @submission = Submission.new |
|
61 | @submission = Submission.new |
|
62 | @submission.problem_id = params[:submission][:problem_id] |
|
62 | @submission.problem_id = params[:submission][:problem_id] |
|
63 | @submission.user = user |
|
63 | @submission.user = user |
|
64 | @submission.language_id = 0 |
|
64 | @submission.language_id = 0 |
|
65 | if (params['file']) and (params['file']!='') |
|
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 | @submission.source_filename = params['file'].original_filename |
|
68 | @submission.source_filename = params['file'].original_filename |
|
68 | end |
|
69 | end |
|
69 | @submission.submitted_at = Time.new.gmtime |
|
70 | @submission.submitted_at = Time.new.gmtime |
|
|
71 | + @submission.ip_address = request.remote_ip | ||
|
70 |
|
72 | ||
|
71 | if GraderConfiguration.time_limit_mode? and user.contest_finished? |
|
73 | if GraderConfiguration.time_limit_mode? and user.contest_finished? |
|
72 | @submission.errors.add(:base,"The contest is over.") |
|
74 | @submission.errors.add(:base,"The contest is over.") |
|
73 | prepare_list_information |
|
75 | prepare_list_information |
|
74 | render :action => 'list' and return |
|
76 | render :action => 'list' and return |
|
75 | end |
|
77 | end |
@@ -147,28 +147,46 | |||||
|
147 | end |
|
147 | end |
|
148 | redirect_to :action => 'list' |
|
148 | redirect_to :action => 'list' |
|
149 | end |
|
149 | end |
|
150 |
|
150 | ||
|
151 | def stat |
|
151 | def stat |
|
152 | @problem = Problem.find(params[:id]) |
|
152 | @problem = Problem.find(params[:id]) |
|
153 |
- |
|
153 | + unless @problem.available or session[:admin] |
|
154 | redirect_to :controller => 'main', :action => 'list' |
|
154 | redirect_to :controller => 'main', :action => 'list' |
|
155 | - else |
|
155 | + return |
|
156 | - @submissions = Submission.find_all_last_by_problem(params[:id]) |
|
||
|
157 | end |
|
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 | end |
|
172 | end |
|
159 |
|
173 | ||
|
160 | def manage |
|
174 | def manage |
|
161 | @problems = Problem.find(:all, :order => 'date_added DESC') |
|
175 | @problems = Problem.find(:all, :order => 'date_added DESC') |
|
162 | end |
|
176 | end |
|
163 |
|
177 | ||
|
164 | def do_manage |
|
178 | def do_manage |
|
165 | if params.has_key? 'change_date_added' |
|
179 | if params.has_key? 'change_date_added' |
|
166 | change_date_added |
|
180 | change_date_added |
|
167 |
- els |
|
181 | + elsif params.has_key? 'add_to_contest' |
|
168 | add_to_contest |
|
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 | end |
|
187 | end |
|
170 | redirect_to :action => 'manage' |
|
188 | redirect_to :action => 'manage' |
|
171 | end |
|
189 | end |
|
172 |
|
190 | ||
|
173 | def import |
|
191 | def import |
|
174 | @allow_test_pair_import = allow_test_pair_import? |
|
192 | @allow_test_pair_import = allow_test_pair_import? |
@@ -231,18 +249,29 | |||||
|
231 | problems.each do |p| |
|
249 | problems.each do |p| |
|
232 | p.contests << contest |
|
250 | p.contests << contest |
|
233 | end |
|
251 | end |
|
234 | end |
|
252 | end |
|
235 | end |
|
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 | def get_problems_from_params |
|
263 | def get_problems_from_params |
|
238 | problems = [] |
|
264 | problems = [] |
|
239 | params.keys.each do |k| |
|
265 | params.keys.each do |k| |
|
240 | if k.index('prob-')==0 |
|
266 | if k.index('prob-')==0 |
|
241 | - name, id = k.split('-') |
|
267 | + name, id, order = k.split('-') |
|
242 | problems << Problem.find(id) |
|
268 | problems << Problem.find(id) |
|
243 | end |
|
269 | end |
|
244 | end |
|
270 | end |
|
245 | problems |
|
271 | problems |
|
246 | end |
|
272 | end |
|
247 |
|
273 | ||
|
|
274 | + def get_problems_stat | ||
|
248 | end |
|
275 | end |
|
|
276 | + | ||
|
|
277 | + end |
@@ -1,6 +1,8 | |||||
|
|
1 | + require 'csv' | ||
|
|
2 | + | ||
|
1 | class UserAdminController < ApplicationController |
|
3 | class UserAdminController < ApplicationController |
|
2 |
|
4 | ||
|
3 | include MailHelperMethods |
|
5 | include MailHelperMethods |
|
4 |
|
6 | ||
|
5 | before_filter :admin_authorization |
|
7 | before_filter :admin_authorization |
|
6 |
|
8 | ||
@@ -78,17 +80,23 | |||||
|
78 | else |
|
80 | else |
|
79 | password = random_password |
|
81 | password = random_password |
|
80 | user_alias = (items.length>=4) ? items[3] : login |
|
82 | user_alias = (items.length>=4) ? items[3] : login |
|
81 | added_random_password = true |
|
83 | added_random_password = true |
|
82 | end |
|
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 | user = User.new({:login => login, |
|
91 | user = User.new({:login => login, |
|
85 | :full_name => full_name, |
|
92 | :full_name => full_name, |
|
86 | :password => password, |
|
93 | :password => password, |
|
87 | :password_confirmation => password, |
|
94 | :password_confirmation => password, |
|
88 | :alias => user_alias}) |
|
95 | :alias => user_alias}) |
|
|
96 | + end | ||
|
89 | user.activated = true |
|
97 | user.activated = true |
|
90 | user.save |
|
98 | user.save |
|
91 |
|
99 | ||
|
92 | if added_random_password |
|
100 | if added_random_password |
|
93 | note << "'#{login}' (+)" |
|
101 | note << "'#{login}' (+)" |
|
94 | else |
|
102 | else |
@@ -119,13 +127,17 | |||||
|
119 | def destroy |
|
127 | def destroy |
|
120 | User.find(params[:id]).destroy |
|
128 | User.find(params[:id]).destroy |
|
121 | redirect_to :action => 'list' |
|
129 | redirect_to :action => 'list' |
|
122 | end |
|
130 | end |
|
123 |
|
131 | ||
|
124 | def user_stat |
|
132 | def user_stat |
|
|
133 | + if params[:commit] == 'download csv' | ||
|
|
134 | + @problems = Problem.all | ||
|
|
135 | + else | ||
|
125 | @problems = Problem.find_available_problems |
|
136 | @problems = Problem.find_available_problems |
|
|
137 | + end | ||
|
126 | @users = User.find(:all, :include => [:contests, :contest_stat]) |
|
138 | @users = User.find(:all, :include => [:contests, :contest_stat]) |
|
127 | @scorearray = Array.new |
|
139 | @scorearray = Array.new |
|
128 | @users.each do |u| |
|
140 | @users.each do |u| |
|
129 | ustat = Array.new |
|
141 | ustat = Array.new |
|
130 | ustat[0] = u |
|
142 | ustat[0] = u |
|
131 | @problems.each do |p| |
|
143 | @problems.each do |p| |
@@ -138,13 +150,17 | |||||
|
138 | end |
|
150 | end |
|
139 | @scorearray << ustat |
|
151 | @scorearray << ustat |
|
140 | end |
|
152 | end |
|
141 | end |
|
153 | end |
|
142 |
|
154 | ||
|
143 | def user_stat_max |
|
155 | def user_stat_max |
|
|
156 | + if params[:commit] == 'download csv' | ||
|
|
157 | + @problems = Problem.all | ||
|
|
158 | + else | ||
|
144 | @problems = Problem.find_available_problems |
|
159 | @problems = Problem.find_available_problems |
|
|
160 | + end | ||
|
145 | @users = User.find(:all, :include => [:contests, :contest_stat]) |
|
161 | @users = User.find(:all, :include => [:contests, :contest_stat]) |
|
146 | @scorearray = Array.new |
|
162 | @scorearray = Array.new |
|
147 | #set up range from param |
|
163 | #set up range from param |
|
148 | since_id = params.fetch(:since_id, 0).to_i |
|
164 | since_id = params.fetch(:since_id, 0).to_i |
|
149 | until_id = params.fetch(:until_id, 0).to_i |
|
165 | until_id = params.fetch(:until_id, 0).to_i |
|
150 | @users.each do |u| |
|
166 | @users.each do |u| |
@@ -156,12 +172,19 | |||||
|
156 | max_points = sub.points if sub and sub.points and (sub.points > max_points) |
|
172 | max_points = sub.points if sub and sub.points and (sub.points > max_points) |
|
157 | end |
|
173 | end |
|
158 | ustat << [(max_points.to_f*100/p.full_score).round, (max_points>=p.full_score)] |
|
174 | ustat << [(max_points.to_f*100/p.full_score).round, (max_points>=p.full_score)] |
|
159 | end |
|
175 | end |
|
160 | @scorearray << ustat |
|
176 | @scorearray << ustat |
|
161 | end |
|
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 | end |
|
185 | end |
|
163 |
|
186 | ||
|
164 | def import |
|
187 | def import |
|
165 | if params[:file]=='' |
|
188 | if params[:file]=='' |
|
166 | flash[:notice] = 'Error importing no file' |
|
189 | flash[:notice] = 'Error importing no file' |
|
167 | redirect_to :action => 'list' and return |
|
190 | redirect_to :action => 'list' and return |
@@ -470,7 +493,38 | |||||
|
470 | @users = @contest.users |
|
493 | @users = @contest.users |
|
471 | else |
|
494 | else |
|
472 | @users = User.find_users_with_no_contest |
|
495 | @users = User.find_users_with_no_contest |
|
473 | end |
|
496 | end |
|
474 | return [@contest, @users] |
|
497 | return [@contest, @users] |
|
475 | end |
|
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 | end |
|
522 | end |
|
|
523 | + end | ||
|
|
524 | + row << total | ||
|
|
525 | + row << num_passed | ||
|
|
526 | + csv << row | ||
|
|
527 | + end | ||
|
|
528 | + end | ||
|
|
529 | + end | ||
|
|
530 | + end |
@@ -11,12 +11,13 | |||||
|
11 | :retrieve_password] |
|
11 | :retrieve_password] |
|
12 |
|
12 | ||
|
13 | before_filter :verify_online_registration, :only => [:new, |
|
13 | before_filter :verify_online_registration, :only => [:new, |
|
14 | :register, |
|
14 | :register, |
|
15 | :forget, |
|
15 | :forget, |
|
16 | :retrieve_password] |
|
16 | :retrieve_password] |
|
|
17 | + before_filter :authenticate, :profile_authorization, only: [:profile] | ||
|
17 |
|
18 | ||
|
18 | verify :method => :post, :only => [:chg_passwd], |
|
19 | verify :method => :post, :only => [:chg_passwd], |
|
19 | :redirect_to => { :action => :index } |
|
20 | :redirect_to => { :action => :index } |
|
20 |
|
21 | ||
|
21 | #in_place_edit_for :user, :alias_for_editing |
|
22 | #in_place_edit_for :user, :alias_for_editing |
|
22 | #in_place_edit_for :user, :email_for_editing |
|
23 | #in_place_edit_for :user, :email_for_editing |
@@ -105,12 +106,36 | |||||
|
105 | else |
|
106 | else |
|
106 | flash[:notice] = I18n.t 'registration.password_retrieval.no_email' |
|
107 | flash[:notice] = I18n.t 'registration.password_retrieval.no_email' |
|
107 | end |
|
108 | end |
|
108 | redirect_to :action => 'forget' |
|
109 | redirect_to :action => 'forget' |
|
109 | end |
|
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 | protected |
|
136 | protected |
|
112 |
|
137 | ||
|
113 | def verify_online_registration |
|
138 | def verify_online_registration |
|
114 | if !GraderConfiguration['system.online_registration'] |
|
139 | if !GraderConfiguration['system.online_registration'] |
|
115 | redirect_to :controller => 'main', :action => 'login' |
|
140 | redirect_to :controller => 'main', :action => 'login' |
|
116 | end |
|
141 | end |
@@ -150,7 +175,21 | |||||
|
150 |
|
175 | ||
|
151 | logger.info mail_body |
|
176 | logger.info mail_body |
|
152 |
|
177 | ||
|
153 | send_mail(user.email, mail_subject, mail_body) |
|
178 | send_mail(user.email, mail_subject, mail_body) |
|
154 | end |
|
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 | end |
|
193 | end |
|
|
194 | + | ||
|
|
195 | + end |
@@ -10,12 +10,13 | |||||
|
10 | menu_items << "<b>Administrative task:</b> " |
|
10 | menu_items << "<b>Administrative task:</b> " |
|
11 | append_to menu_items, '[Announcements]', 'announcements', 'index' |
|
11 | append_to menu_items, '[Announcements]', 'announcements', 'index' |
|
12 | append_to menu_items, '[Msg console]', 'messages', 'console' |
|
12 | append_to menu_items, '[Msg console]', 'messages', 'console' |
|
13 | append_to menu_items, '[Problems]', 'problems', 'index' |
|
13 | append_to menu_items, '[Problems]', 'problems', 'index' |
|
14 | append_to menu_items, '[Users]', 'user_admin', 'index' |
|
14 | append_to menu_items, '[Users]', 'user_admin', 'index' |
|
15 | append_to menu_items, '[Results]', 'user_admin', 'user_stat' |
|
15 | append_to menu_items, '[Results]', 'user_admin', 'user_stat' |
|
|
16 | + append_to menu_items, '[Report]', 'report', 'login_stat' | ||
|
16 | append_to menu_items, '[Graders]', 'graders', 'list' |
|
17 | append_to menu_items, '[Graders]', 'graders', 'list' |
|
17 | append_to menu_items, '[Contests]', 'contest_management', 'index' |
|
18 | append_to menu_items, '[Contests]', 'contest_management', 'index' |
|
18 | append_to menu_items, '[Sites]', 'sites', 'index' |
|
19 | append_to menu_items, '[Sites]', 'sites', 'index' |
|
19 | append_to menu_items, '[System config]', 'configurations', 'index' |
|
20 | append_to menu_items, '[System config]', 'configurations', 'index' |
|
20 | menu_items << "<br/>" |
|
21 | menu_items << "<br/>" |
|
21 | end |
|
22 | end |
@@ -26,12 +27,16 | |||||
|
26 |
|
27 | ||
|
27 | if (user!=nil) and (GraderConfiguration.show_tasks_to?(user)) |
|
28 | if (user!=nil) and (GraderConfiguration.show_tasks_to?(user)) |
|
28 | append_to menu_items, "[#{I18n.t 'menu.tasks'}]", 'tasks', 'list' |
|
29 | append_to menu_items, "[#{I18n.t 'menu.tasks'}]", 'tasks', 'list' |
|
29 | append_to menu_items, "[#{I18n.t 'menu.submissions'}]", 'main', 'submission' |
|
30 | append_to menu_items, "[#{I18n.t 'menu.submissions'}]", 'main', 'submission' |
|
30 | append_to menu_items, "[#{I18n.t 'menu.test'}]", 'test', 'index' |
|
31 | append_to menu_items, "[#{I18n.t 'menu.test'}]", 'test', 'index' |
|
31 | end |
|
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 | append_to menu_items, "[#{I18n.t 'menu.help'}]", 'main', 'help' |
|
37 | append_to menu_items, "[#{I18n.t 'menu.help'}]", 'main', 'help' |
|
33 |
|
38 | ||
|
34 | if GraderConfiguration['system.user_setting_enabled'] |
|
39 | if GraderConfiguration['system.user_setting_enabled'] |
|
35 | append_to menu_items, "[#{I18n.t 'menu.settings'}]", 'users', 'index' |
|
40 | append_to menu_items, "[#{I18n.t 'menu.settings'}]", 'users', 'index' |
|
36 | end |
|
41 | end |
|
37 | append_to menu_items, "[#{I18n.t 'menu.log_out'}]", 'main', 'login' |
|
42 | append_to menu_items, "[#{I18n.t 'menu.log_out'}]", 'main', 'login' |
@@ -11,13 +11,13 | |||||
|
11 | scope :available, :conditions => {:available => true} |
|
11 | scope :available, :conditions => {:available => true} |
|
12 |
|
12 | ||
|
13 | DEFAULT_TIME_LIMIT = 1 |
|
13 | DEFAULT_TIME_LIMIT = 1 |
|
14 | DEFAULT_MEMORY_LIMIT = 32 |
|
14 | DEFAULT_MEMORY_LIMIT = 32 |
|
15 |
|
15 | ||
|
16 | def self.find_available_problems |
|
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 | end |
|
18 | end |
|
19 |
|
19 | ||
|
20 | def self.create_from_import_form_params(params, old_problem=nil) |
|
20 | def self.create_from_import_form_params(params, old_problem=nil) |
|
21 | org_problem = old_problem || Problem.new |
|
21 | org_problem = old_problem || Problem.new |
|
22 | import_params, problem = Problem.extract_params_and_check(params, |
|
22 | import_params, problem = Problem.extract_params_and_check(params, |
|
23 | org_problem) |
|
23 | org_problem) |
@@ -40,23 +40,31 | |||||
|
40 |
|
40 | ||
|
41 | importer = TestdataImporter.new(problem) |
|
41 | importer = TestdataImporter.new(problem) |
|
42 |
|
42 | ||
|
43 | if not importer.import_from_file(import_params[:file], |
|
43 | if not importer.import_from_file(import_params[:file], |
|
44 | import_params[:time_limit], |
|
44 | import_params[:time_limit], |
|
45 | import_params[:memory_limit], |
|
45 | import_params[:memory_limit], |
|
|
46 | + import_params[:checker_name], | ||
|
46 | import_to_db) |
|
47 | import_to_db) |
|
47 | problem.errors.add(:base,'Import error.') |
|
48 | problem.errors.add(:base,'Import error.') |
|
48 | end |
|
49 | end |
|
49 |
|
50 | ||
|
50 | return problem, importer.log_msg |
|
51 | return problem, importer.log_msg |
|
51 | end |
|
52 | end |
|
52 |
|
53 | ||
|
53 | def self.download_file_basedir |
|
54 | def self.download_file_basedir |
|
54 | return "#{Rails.root}/data/tasks" |
|
55 | return "#{Rails.root}/data/tasks" |
|
55 | end |
|
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 | protected |
|
65 | protected |
|
58 |
|
66 | ||
|
59 | def self.to_i_or_default(st, default) |
|
67 | def self.to_i_or_default(st, default) |
|
60 | if st!='' |
|
68 | if st!='' |
|
61 | result = st.to_i |
|
69 | result = st.to_i |
|
62 | end |
|
70 | end |
@@ -87,12 +95,17 | |||||
|
87 | end |
|
95 | end |
|
88 |
|
96 | ||
|
89 | if params[:file]==nil or params[:file]=='' |
|
97 | if params[:file]==nil or params[:file]=='' |
|
90 | problem.errors.add(:base,'No testdata file.') |
|
98 | problem.errors.add(:base,'No testdata file.') |
|
91 | end |
|
99 | end |
|
92 |
|
100 | ||
|
|
101 | + checker_name = 'text' | ||
|
|
102 | + if ['text','float'].include? params[:checker] | ||
|
|
103 | + checker_name = params[:checker] | ||
|
|
104 | + end | ||
|
|
105 | + | ||
|
93 | file = params[:file] |
|
106 | file = params[:file] |
|
94 |
|
107 | ||
|
95 | if !problem.errors.empty? |
|
108 | if !problem.errors.empty? |
|
96 | return nil, problem |
|
109 | return nil, problem |
|
97 | end |
|
110 | end |
|
98 |
|
111 | ||
@@ -103,12 +116,13 | |||||
|
103 | problem.full_name = params[:name] |
|
116 | problem.full_name = params[:name] |
|
104 | end |
|
117 | end |
|
105 |
|
118 | ||
|
106 | return [{ |
|
119 | return [{ |
|
107 | :time_limit => time_limit, |
|
120 | :time_limit => time_limit, |
|
108 | :memory_limit => memory_limit, |
|
121 | :memory_limit => memory_limit, |
|
109 | - :file => file |
|
122 | + :file => file, |
|
|
123 | + :checker_name => checker_name | ||
|
110 | }, |
|
124 | }, |
|
111 | problem] |
|
125 | problem] |
|
112 | end |
|
126 | end |
|
113 |
|
127 | ||
|
114 | end |
|
128 | end |
@@ -22,13 +22,13 | |||||
|
22 | :order => 'number DESC') |
|
22 | :order => 'number DESC') |
|
23 | return last_sub |
|
23 | return last_sub |
|
24 | end |
|
24 | end |
|
25 |
|
25 | ||
|
26 | def self.find_all_last_by_problem(problem_id) |
|
26 | def self.find_all_last_by_problem(problem_id) |
|
27 | # need to put in SQL command, maybe there's a better way |
|
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 | "WHERE id = " + |
|
29 | "WHERE id = " + |
|
30 | "(SELECT MAX(id) FROM submissions AS subs " + |
|
30 | "(SELECT MAX(id) FROM submissions AS subs " + |
|
31 | "WHERE subs.user_id = submissions.user_id AND " + |
|
31 | "WHERE subs.user_id = submissions.user_id AND " + |
|
32 | "problem_id = " + problem_id.to_s + " " + |
|
32 | "problem_id = " + problem_id.to_s + " " + |
|
33 | "GROUP BY user_id) " + |
|
33 | "GROUP BY user_id) " + |
|
34 | "ORDER BY user_id") |
|
34 | "ORDER BY user_id") |
@@ -1,7 +1,11 | |||||
|
1 | require 'digest/sha1' |
|
1 | require 'digest/sha1' |
|
|
2 | + require 'net/pop' | ||
|
|
3 | + require 'net/https' | ||
|
|
4 | + require 'net/http' | ||
|
|
5 | + require 'json' | ||
|
2 |
|
6 | ||
|
3 | class User < ActiveRecord::Base |
|
7 | class User < ActiveRecord::Base |
|
4 |
|
8 | ||
|
5 | has_and_belongs_to_many :roles |
|
9 | has_and_belongs_to_many :roles |
|
6 |
|
10 | ||
|
7 | has_many :test_requests, :order => "submitted_at DESC" |
|
11 | has_many :test_requests, :order => "submitted_at DESC" |
@@ -58,13 +62,15 | |||||
|
58 | # this is for will_paginate |
|
62 | # this is for will_paginate |
|
59 | cattr_reader :per_page |
|
63 | cattr_reader :per_page |
|
60 | @@per_page = 50 |
|
64 | @@per_page = 50 |
|
61 |
|
65 | ||
|
62 | def self.authenticate(login, password) |
|
66 | def self.authenticate(login, password) |
|
63 | user = find_by_login(login) |
|
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 | end |
|
71 | end |
|
66 |
|
72 | ||
|
67 | def authenticated?(password) |
|
73 | def authenticated?(password) |
|
68 | if self.activated |
|
74 | if self.activated |
|
69 | hashed_password == User.encrypt(password,self.salt) |
|
75 | hashed_password == User.encrypt(password,self.salt) |
|
70 | else |
|
76 | else |
@@ -1,6 +1,9 | |||||
|
|
1 | + - content_for :header do | ||
|
|
2 | + = javascript_include_tag 'local_jquery' | ||
|
|
3 | + | ||
|
1 | %h1 System configuration |
|
4 | %h1 System configuration |
|
2 |
|
5 | ||
|
3 | %table.info |
|
6 | %table.info |
|
4 | %tr.info-head |
|
7 | %tr.info-head |
|
5 | %th Key |
|
8 | %th Key |
|
6 | %th Type |
|
9 | %th Type |
@@ -11,13 +14,13 | |||||
|
11 | %tr{:class => cycle("info-odd", "info-even")} |
|
14 | %tr{:class => cycle("info-odd", "info-even")} |
|
12 | %td |
|
15 | %td |
|
13 | = in_place_editor_field :grader_configuration, :key, {}, :rows=>1 |
|
16 | = in_place_editor_field :grader_configuration, :key, {}, :rows=>1 |
|
14 | %td |
|
17 | %td |
|
15 | = in_place_editor_field :grader_configuration, :value_type, {}, :rows=>1 |
|
18 | = in_place_editor_field :grader_configuration, :value_type, {}, :rows=>1 |
|
16 | %td |
|
19 | %td |
|
17 |
- = in_place |
|
20 | + = best_in_place @grader_configuration, :value, ok_button: "ok", cancel_button: "cancel" |
|
18 | %td= conf.description |
|
21 | %td= conf.description |
|
19 |
|
22 | ||
|
20 | - if GraderConfiguration.config_cached? |
|
23 | - if GraderConfiguration.config_cached? |
|
21 | %br/ |
|
24 | %br/ |
|
22 | Your config is saved, but it does not automatically take effect. |
|
25 | Your config is saved, but it does not automatically take effect. |
|
23 | %br/ |
|
26 | %br/ |
@@ -1,8 +1,9 | |||||
|
1 | - content_for :head do |
|
1 | - content_for :head do |
|
2 | = stylesheet_link_tag 'graders' |
|
2 | = stylesheet_link_tag 'graders' |
|
|
3 | + = javascript_include_tag 'local_jquery' | ||
|
3 | <meta http-equiv ="refresh" content="60"/> |
|
4 | <meta http-equiv ="refresh" content="60"/> |
|
4 |
|
5 | ||
|
5 | %h1 Grader information |
|
6 | %h1 Grader information |
|
6 |
|
7 | ||
|
7 | = link_to '[Refresh]', :action => 'list' |
|
8 | = link_to '[Refresh]', :action => 'list' |
|
8 | %br/ |
|
9 | %br/ |
@@ -21,23 +22,23 | |||||
|
21 | = submit_tag 'Stop all running graders' |
|
22 | = submit_tag 'Stop all running graders' |
|
22 | .item |
|
23 | .item |
|
23 | = form_for :clear, :url => {:action => 'clear_all'} do |f| |
|
24 | = form_for :clear, :url => {:action => 'clear_all'} do |f| |
|
24 | = submit_tag 'Clear all data' |
|
25 | = submit_tag 'Clear all data' |
|
25 | %br{:style => 'clear:both'}/ |
|
26 | %br{:style => 'clear:both'}/ |
|
26 |
|
27 | ||
|
|
28 | + %div{style: 'width:500px; float: left;'} | ||
|
27 | - if @last_task |
|
29 | - if @last_task |
|
28 | Last task: |
|
30 | Last task: |
|
29 | = link_to "#{@last_task.id}", :action => 'view', :id => @last_task.id, :type => 'Task' |
|
31 | = link_to "#{@last_task.id}", :action => 'view', :id => @last_task.id, :type => 'Task' |
|
30 |
|
32 | ||
|
31 | %br/ |
|
33 | %br/ |
|
32 |
|
34 | ||
|
33 | - if @last_test_request |
|
35 | - if @last_test_request |
|
34 | Last test_request: |
|
36 | Last test_request: |
|
35 | = link_to "#{@last_test_request.id}", :action => 'view', :id => @last_test_request.id, :type => 'TestRequest' |
|
37 | = link_to "#{@last_test_request.id}", :action => 'view', :id => @last_test_request.id, :type => 'TestRequest' |
|
36 |
|
38 | ||
|
37 | - |
|
||
|
38 | %h2 Current graders |
|
39 | %h2 Current graders |
|
39 |
|
40 | ||
|
40 | = render :partial => 'grader_list', :locals => {:grader_list => @grader_processes} |
|
41 | = render :partial => 'grader_list', :locals => {:grader_list => @grader_processes} |
|
41 |
|
42 | ||
|
42 | %h2 Stalled graders |
|
43 | %h2 Stalled graders |
|
43 |
|
44 | ||
@@ -46,6 +47,27 | |||||
|
46 | %h2 Terminated graders |
|
47 | %h2 Terminated graders |
|
47 |
|
48 | ||
|
48 | = form_for :clear, :url => {:action => 'clear_terminated'} do |f| |
|
49 | = form_for :clear, :url => {:action => 'clear_terminated'} do |f| |
|
49 | = submit_tag 'Clear data for terminated graders' |
|
50 | = submit_tag 'Clear data for terminated graders' |
|
50 |
|
51 | ||
|
51 | = render :partial => 'grader_list', :locals => {:grader_list => @terminated_processes} |
|
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 | %h1= "Submission: #{@submission.id}" |
|
11 | %h1= "Submission: #{@submission.id}" |
|
2 |
|
12 | ||
|
3 | - %p |
|
13 | + |
|
4 | - User: |
|
14 | + %h2 Stat |
|
5 | - = "#{@submission.user.login}" |
|
15 | + |
|
6 | - %br/ |
|
16 | + %table.info |
|
7 | - Problem: |
|
17 | + %thead |
|
8 | - - if @submission.problem!=nil |
|
18 | + %tr.info-head |
|
9 | - = "#{@submission.problem.full_name}" |
|
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 | - else |
|
28 | - else |
|
11 | = "(n/a)" |
|
29 | = "(n/a)" |
|
12 | - %br/ |
|
30 | + %tr{class: cycle('info-even','info-odd')} |
|
13 | - = "Number: #{@submission.number}" |
|
31 | + %td.field Problem: |
|
14 | - %br/ |
|
32 | + %td.value |
|
15 | - = "Submitted at: #{format_short_time(@submission.submitted_at)}" |
|
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 |
- % |
|
64 | + %h2 Source code |
|
18 |
- %div{:style => "border: 1px solid black; |
|
65 | + //%div.highlight{:style => "border: 1px solid black;"} |
|
19 | - - if @submission.source |
|
66 | + =@formatted_code.html_safe |
|
20 | - %pre |
|
||
|
21 | - =h truncate @submission.source, :length => 10240 |
|
||
|
22 |
|
67 |
@@ -2,13 +2,15 | |||||
|
2 | <html> |
|
2 | <html> |
|
3 | <head> |
|
3 | <head> |
|
4 | <title><%= GraderConfiguration['contest.name'] %></title> |
|
4 | <title><%= GraderConfiguration['contest.name'] %></title> |
|
5 | <%= stylesheet_link_tag "application", :media => "all" %> |
|
5 | <%= stylesheet_link_tag "application", :media => "all" %> |
|
6 | <%= javascript_include_tag "application" %> |
|
6 | <%= javascript_include_tag "application" %> |
|
7 | <%= csrf_meta_tags %> |
|
7 | <%= csrf_meta_tags %> |
|
|
8 | + <%= content_for :header %> | ||
|
8 | <%= yield :head %> |
|
9 | <%= yield :head %> |
|
|
10 | + | ||
|
9 | </head> |
|
11 | </head> |
|
10 | <body> |
|
12 | <body> |
|
11 |
|
13 | ||
|
12 | <div class="userbar"> |
|
14 | <div class="userbar"> |
|
13 | <%= user_header %> |
|
15 | <%= user_header %> |
|
14 | </div> |
|
16 | </div> |
@@ -1,12 +1,15 | |||||
|
1 | <tr class="info-<%= (problem_counter%2==0) ? "even" : "odd" %>"> |
|
1 | <tr class="info-<%= (problem_counter%2==0) ? "even" : "odd" %>"> |
|
2 | <td> |
|
2 | <td> |
|
3 | <%= "#{problem_counter+1}" %> |
|
3 | <%= "#{problem_counter+1}" %> |
|
4 | </td> |
|
4 | </td> |
|
5 | <td> |
|
5 | <td> |
|
6 |
- <%= "#{problem. |
|
6 | + <%= "#{problem.name}"%> |
|
|
7 | + </td> | ||
|
|
8 | + <td> | ||
|
|
9 | + <%= "#{problem.full_name}" %> | ||
|
7 | <%= link_to_description_if_any "[#{t 'main.problem_desc'}]", problem %> |
|
10 | <%= link_to_description_if_any "[#{t 'main.problem_desc'}]", problem %> |
|
8 | </td> |
|
11 | </td> |
|
9 | <td align="center"> |
|
12 | <td align="center"> |
|
10 | <%= @prob_submissions[problem.id][:count] %> |
|
13 | <%= @prob_submissions[problem.id][:count] %> |
|
11 | </td> |
|
14 | </td> |
|
12 | <td> |
|
15 | <td> |
@@ -1,12 +1,18 | |||||
|
1 |
|
1 | ||
|
2 | %tr{:class => ((submission_counter%2==0) ? "info-even" : "info-odd")} |
|
2 | %tr{:class => ((submission_counter%2==0) ? "info-even" : "info-odd")} |
|
3 | %td.info{:align => "center"} |
|
3 | %td.info{:align => "center"} |
|
4 | = submission_counter+1 |
|
4 | = submission_counter+1 |
|
5 | - %td.info= format_short_time(submission.submitted_at) |
|
||
|
6 | %td.info{:align => "center"} |
|
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 | = link_to('[load]',{:action => 'source', :id => submission.id}) |
|
13 | = link_to('[load]',{:action => 'source', :id => submission.id}) |
|
8 | %td.info |
|
14 | %td.info |
|
9 | - if submission.graded_at!=nil |
|
15 | - if submission.graded_at!=nil |
|
10 | = "Graded at #{format_short_time(submission.graded_at)}." |
|
16 | = "Graded at #{format_short_time(submission.graded_at)}." |
|
11 | %br/ |
|
17 | %br/ |
|
12 | = "Score: #{(submission.points*100/submission.problem.full_score).to_i} " if GraderConfiguration['ui.show_score'] |
|
18 | = "Score: #{(submission.points*100/submission.problem.full_score).to_i} " if GraderConfiguration['ui.show_score'] |
@@ -22,25 +22,27 | |||||
|
22 |
|
22 | ||
|
23 | - if GraderConfiguration.show_tasks_to?(@user) |
|
23 | - if GraderConfiguration.show_tasks_to?(@user) |
|
24 | - if not GraderConfiguration.multicontests? |
|
24 | - if not GraderConfiguration.multicontests? |
|
25 | %table.info |
|
25 | %table.info |
|
26 | %tr.info-head |
|
26 | %tr.info-head |
|
27 | %th |
|
27 | %th |
|
28 | - %th Tasks |
|
28 | + %th Tasks name |
|
|
29 | + %th Full name | ||
|
29 | %th # of sub(s) |
|
30 | %th # of sub(s) |
|
30 | %th Results |
|
31 | %th Results |
|
31 | = render :partial => 'problem', :collection => @problems |
|
32 | = render :partial => 'problem', :collection => @problems |
|
32 | - else |
|
33 | - else |
|
33 | - @contest_problems.each do |cp| |
|
34 | - @contest_problems.each do |cp| |
|
34 | - if cp[:problems].length > 0 |
|
35 | - if cp[:problems].length > 0 |
|
35 | %h2{:class =>'contest-title'} |
|
36 | %h2{:class =>'contest-title'} |
|
36 | = "#{cp[:contest] ? cp[:contest].title : 'Public problems'}" |
|
37 | = "#{cp[:contest] ? cp[:contest].title : 'Public problems'}" |
|
37 | %table.info |
|
38 | %table.info |
|
38 | %tr.info-head |
|
39 | %tr.info-head |
|
39 | %th |
|
40 | %th |
|
40 | - %th Tasks |
|
41 | + %th Tasks name |
|
|
42 | + %th Full name | ||
|
41 | %th # of sub(s) |
|
43 | %th # of sub(s) |
|
42 | %th Results |
|
44 | %th Results |
|
43 | = render :partial => 'problem', :collection => cp[:problems] |
|
45 | = render :partial => 'problem', :collection => cp[:problems] |
|
44 |
|
46 | ||
|
45 |
|
47 | ||
|
46 | %hr/ |
|
48 | %hr/ |
@@ -10,12 +10,13 | |||||
|
10 | %h2= "Task: #{@problem.full_name} (#{@problem.name})" |
|
10 | %h2= "Task: #{@problem.full_name} (#{@problem.name})" |
|
11 |
|
11 | ||
|
12 | - if @submissions!=nil |
|
12 | - if @submissions!=nil |
|
13 | - if @submissions.length>0 |
|
13 | - if @submissions.length>0 |
|
14 | %table.info |
|
14 | %table.info |
|
15 | %tr.info-head |
|
15 | %tr.info-head |
|
|
16 | + %th.info No. | ||
|
16 | %th.info # |
|
17 | %th.info # |
|
17 | %th.info At |
|
18 | %th.info At |
|
18 | %th.info Source |
|
19 | %th.info Source |
|
19 | %th.info Result |
|
20 | %th.info Result |
|
20 | %th.info{:width => "300px"} |
|
21 | %th.info{:width => "300px"} |
|
21 | Compiler message |
|
22 | Compiler message |
@@ -29,12 +29,25 | |||||
|
29 | In .zip, .tgz, tar.gz, .tar format. |
|
29 | In .zip, .tgz, tar.gz, .tar format. |
|
30 | It should includes inputs (e.g., 1.in, 2a.in, 2b.in) |
|
30 | It should includes inputs (e.g., 1.in, 2a.in, 2b.in) |
|
31 | and solutions (e.g., 1.sol, 2a.sol, 2b.sol). |
|
31 | and solutions (e.g., 1.sol, 2a.sol, 2b.sol). |
|
32 | %br/ |
|
32 | %br/ |
|
33 | You may put task description in *.html for raw html |
|
33 | You may put task description in *.html for raw html |
|
34 | and *.md or *.markdown for markdown. |
|
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 | - if @allow_test_pair_import |
|
48 | - if @allow_test_pair_import |
|
36 | %tr |
|
49 | %tr |
|
37 | %td |
|
50 | %td |
|
38 | %td |
|
51 | %td |
|
39 | = check_box_tag 'import_to_db' |
|
52 | = check_box_tag 'import_to_db' |
|
40 | Import test data to database (for a test-pair task) |
|
53 | Import test data to database (for a test-pair task) |
@@ -1,43 +1,85 | |||||
|
1 | - content_for :head do |
|
1 | - content_for :head do |
|
2 | = stylesheet_link_tag 'problems' |
|
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 | %h1 Manage problems |
|
37 | %h1 Manage problems |
|
5 |
|
38 | ||
|
6 | %p= link_to '[Back to problem list]', :action => 'list' |
|
39 | %p= link_to '[Back to problem list]', :action => 'list' |
|
7 |
|
40 | ||
|
8 | = form_tag :action=>'do_manage' do |
|
41 | = form_tag :action=>'do_manage' do |
|
9 | .submitbox |
|
42 | .submitbox |
|
10 | - What do you want to do? |
|
43 | + What do you want to do to the selected problem? |
|
11 | %br/ |
|
44 | %br/ |
|
|
45 | + (You can shift-click to select a range of problems) | ||
|
12 | %ul |
|
46 | %ul |
|
13 | %li |
|
47 | %li |
|
14 | Change date added to |
|
48 | Change date added to |
|
15 | = select_date Date.current, :prefix => 'date_added' |
|
49 | = select_date Date.current, :prefix => 'date_added' |
|
16 | |
|
50 | |
|
17 | = submit_tag 'Change', :name => 'change_date_added' |
|
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 | - if GraderConfiguration.multicontests? |
|
57 | - if GraderConfiguration.multicontests? |
|
20 | %li |
|
58 | %li |
|
21 | Add to |
|
59 | Add to |
|
22 | = select("contest","id",Contest.all.collect {|c| [c.title, c.id]}) |
|
60 | = select("contest","id",Contest.all.collect {|c| [c.title, c.id]}) |
|
23 | = submit_tag 'Add', :name => 'add_to_contest' |
|
61 | = submit_tag 'Add', :name => 'add_to_contest' |
|
24 |
|
62 | ||
|
25 | %table |
|
63 | %table |
|
26 | - %tr |
|
64 | + %tr{style: "text-align: left;"} |
|
27 | - %th/ |
|
65 | + %th= check_box_tag 'select_all' |
|
28 | %th Name |
|
66 | %th Name |
|
29 | %th Full name |
|
67 | %th Full name |
|
|
68 | + %th Available | ||
|
30 | %th Date added |
|
69 | %th Date added |
|
31 | - if GraderConfiguration.multicontests? |
|
70 | - if GraderConfiguration.multicontests? |
|
32 | %th Contests |
|
71 | %th Contests |
|
33 |
|
72 | ||
|
|
73 | + - num = 0 | ||
|
34 | - for problem in @problems |
|
74 | - for problem in @problems |
|
|
75 | + - num += 1 | ||
|
35 | %tr{:id => "row-prob-#{problem.id}", :name=> "prob-#{problem.id}"} |
|
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 | %td= problem.name |
|
78 | %td= problem.name |
|
38 | %td= problem.full_name |
|
79 | %td= problem.full_name |
|
|
80 | + %td= problem.available | ||
|
39 | %td= problem.date_added |
|
81 | %td= problem.date_added |
|
40 | - if GraderConfiguration.multicontests? |
|
82 | - if GraderConfiguration.multicontests? |
|
41 | %td |
|
83 | %td |
|
42 | - problem.contests.each do |contest| |
|
84 | - problem.contests.each do |contest| |
|
43 | = "(#{contest.name} [#{link_to 'x', :action => 'remove_contest', :id => problem.id, :contest_id => contest.id }])" |
|
85 | = "(#{contest.name} [#{link_to 'x', :action => 'remove_contest', :id => problem.id, :contest_id => contest.id }])" |
@@ -15,8 +15,11 | |||||
|
15 |
|
15 | ||
|
16 | <p><label for="user_email">E-mail</label><br/> |
|
16 | <p><label for="user_email">E-mail</label><br/> |
|
17 | <%= email_field 'user', 'email' %></p> |
|
17 | <%= email_field 'user', 'email' %></p> |
|
18 |
|
18 | ||
|
19 | <p><label for="user_alias">Alias</label><br/> |
|
19 | <p><label for="user_alias">Alias</label><br/> |
|
20 | <%= text_field 'user', 'alias' %></p> |
|
20 | <%= text_field 'user', 'alias' %></p> |
|
|
21 | + | ||
|
|
22 | + <p><label for="user_remark">Remark</label><br/> | ||
|
|
23 | + <%= text_field 'user', 'remark' %></p> | ||
|
21 | <!--[eoform:user]--> |
|
24 | <!--[eoform:user]--> |
|
22 |
|
25 |
@@ -65,14 +65,15 | |||||
|
65 | <th></th> |
|
65 | <th></th> |
|
66 | <th></th> |
|
66 | <th></th> |
|
67 | </tr> |
|
67 | </tr> |
|
68 |
|
68 | ||
|
69 | <% for user in @users %> |
|
69 | <% for user in @users %> |
|
70 | <tr class="info-<%= cycle("odd","even") %>"> |
|
70 | <tr class="info-<%= cycle("odd","even") %>"> |
|
|
71 | + <td><%= link_to user.login, controller: :users, :action => 'profile', :id => user %></td> | ||
|
71 | <% for column in User.content_columns %> |
|
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 | <td><%=h user.send(column.name) %></td> |
|
74 | <td><%=h user.send(column.name) %></td> |
|
74 | <% end %> |
|
75 | <% end %> |
|
75 | <% end %> |
|
76 | <% end %> |
|
76 | <td><%= link_to 'Show', :action => 'show', :id => user %></td> |
|
77 | <td><%= link_to 'Show', :action => 'show', :id => user %></td> |
|
77 | <td><%= link_to 'Edit', :action => 'edit', :id => user %></td> |
|
78 | <td><%= link_to 'Edit', :action => 'edit', :id => user %></td> |
|
78 | <td><%= link_to 'Destroy', { :action => 'destroy', :id => user }, :confirm => 'Are you sure?', :method => :post %></td> |
|
79 | <td><%= link_to 'Destroy', { :action => 'destroy', :id => user }, :confirm => 'Are you sure?', :method => :post %></td> |
@@ -57,8 +57,9 | |||||
|
57 | config.assets.enabled = true |
|
57 | config.assets.enabled = true |
|
58 |
|
58 | ||
|
59 | # Version of your assets, change this if you want to expire all your assets |
|
59 | # Version of your assets, change this if you want to expire all your assets |
|
60 | config.assets.version = '1.0' |
|
60 | config.assets.version = '1.0' |
|
61 |
|
61 | ||
|
62 | config.assets.precompile += ['announcement_refresh.js','effects.js','site_update.js','graders.css','problems.css'] |
|
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 | end |
|
64 | end |
|
64 | end |
|
65 | end |
@@ -17,12 +17,13 | |||||
|
17 | menu: |
|
17 | menu: |
|
18 | main: 'Main' |
|
18 | main: 'Main' |
|
19 | messages: 'Messages' |
|
19 | messages: 'Messages' |
|
20 | tasks: 'Tasks' |
|
20 | tasks: 'Tasks' |
|
21 | submissions: 'Submissions' |
|
21 | submissions: 'Submissions' |
|
22 | test: 'Test Interface' |
|
22 | test: 'Test Interface' |
|
|
23 | + hall_of_fame: 'Hall of Fame' | ||
|
23 | help: 'Help' |
|
24 | help: 'Help' |
|
24 | settings: 'Settings' |
|
25 | settings: 'Settings' |
|
25 | log_out: 'Log out' |
|
26 | log_out: 'Log out' |
|
26 |
|
27 | ||
|
27 | title_bar: |
|
28 | title_bar: |
|
28 | current_time: "Current time is" |
|
29 | current_time: "Current time is" |
@@ -17,12 +17,13 | |||||
|
17 | menu: |
|
17 | menu: |
|
18 | main: 'หน้าหลัก' |
|
18 | main: 'หน้าหลัก' |
|
19 | messages: 'ข้อความ' |
|
19 | messages: 'ข้อความ' |
|
20 | tasks: 'โจทย์' |
|
20 | tasks: 'โจทย์' |
|
21 | submissions: 'โปรแกรมที่ส่ง' |
|
21 | submissions: 'โปรแกรมที่ส่ง' |
|
22 | test: 'ทดสอบโปรแกรม' |
|
22 | test: 'ทดสอบโปรแกรม' |
|
|
23 | + hall_of_fame: 'หอเกียรติยศ' | ||
|
23 | help: 'ความช่วยเหลือ' |
|
24 | help: 'ความช่วยเหลือ' |
|
24 | settings: 'เปลี่ยนรหัสผ่าน' |
|
25 | settings: 'เปลี่ยนรหัสผ่าน' |
|
25 | log_out: 'ออกจากระบบ' |
|
26 | log_out: 'ออกจากระบบ' |
|
26 |
|
27 | ||
|
27 | title_bar: |
|
28 | title_bar: |
|
28 | current_time: "เวลาปัจจุบันคือ" |
|
29 | current_time: "เวลาปัจจุบันคือ" |
@@ -1,12 +1,16 | |||||
|
1 | CafeGrader::Application.routes.draw do |
|
1 | CafeGrader::Application.routes.draw do |
|
|
2 | + get "report/login" | ||
|
|
3 | + | ||
|
2 | resources :contests |
|
4 | resources :contests |
|
3 |
|
5 | ||
|
4 | resources :announcements |
|
6 | resources :announcements |
|
5 | resources :sites |
|
7 | resources :sites |
|
6 |
|
8 | ||
|
|
9 | + resources :grader_configuration, controller: 'configurations' | ||
|
|
10 | + | ||
|
7 | # The priority is based upon order of creation: |
|
11 | # The priority is based upon order of creation: |
|
8 | # first created -> highest priority. |
|
12 | # first created -> highest priority. |
|
9 |
|
13 | ||
|
10 | # Sample of regular route: |
|
14 | # Sample of regular route: |
|
11 | # match 'products/:id' => 'catalog#view' |
|
15 | # match 'products/:id' => 'catalog#view' |
|
12 | # Keep in mind you can assign values other than :controller and :action |
|
16 | # Keep in mind you can assign values other than :controller and :action |
@@ -8,39 +8,31 | |||||
|
8 | # system, you should be using db:schema:load, not running all the migrations |
|
8 | # system, you should be using db:schema:load, not running all the migrations |
|
9 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations |
|
9 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations |
|
10 | # you'll amass, the slower it'll run and the greater likelihood for issues). |
|
10 | # you'll amass, the slower it'll run and the greater likelihood for issues). |
|
11 | # |
|
11 | # |
|
12 | # It's strongly recommended to check this file into your version control system. |
|
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 | create_table "announcements", :force => true do |t| |
|
16 | create_table "announcements", :force => true do |t| |
|
17 | t.string "author" |
|
17 | t.string "author" |
|
18 | t.text "body" |
|
18 | t.text "body" |
|
19 | t.boolean "published" |
|
19 | t.boolean "published" |
|
20 | - t.datetime "created_at" |
|
20 | + t.datetime "created_at", :null => false |
|
21 | - t.datetime "updated_at" |
|
21 | + t.datetime "updated_at", :null => false |
|
22 | t.boolean "frontpage", :default => false |
|
22 | t.boolean "frontpage", :default => false |
|
23 | t.boolean "contest_only", :default => false |
|
23 | t.boolean "contest_only", :default => false |
|
24 | t.string "title" |
|
24 | t.string "title" |
|
25 | t.string "notes" |
|
25 | t.string "notes" |
|
26 | end |
|
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 | create_table "contests", :force => true do |t| |
|
28 | create_table "contests", :force => true do |t| |
|
37 | t.string "title" |
|
29 | t.string "title" |
|
38 | t.boolean "enabled" |
|
30 | t.boolean "enabled" |
|
39 | - t.datetime "created_at" |
|
31 | + t.datetime "created_at", :null => false |
|
40 | - t.datetime "updated_at" |
|
32 | + t.datetime "updated_at", :null => false |
|
41 | t.string "name" |
|
33 | t.string "name" |
|
42 | end |
|
34 | end |
|
43 |
|
35 | ||
|
44 | create_table "contests_problems", :id => false, :force => true do |t| |
|
36 | create_table "contests_problems", :id => false, :force => true do |t| |
|
45 | t.integer "contest_id" |
|
37 | t.integer "contest_id" |
|
46 | t.integer "problem_id" |
|
38 | t.integer "problem_id" |
@@ -50,39 +42,39 | |||||
|
50 | t.integer "contest_id" |
|
42 | t.integer "contest_id" |
|
51 | t.integer "user_id" |
|
43 | t.integer "user_id" |
|
52 | end |
|
44 | end |
|
53 |
|
45 | ||
|
54 | create_table "countries", :force => true do |t| |
|
46 | create_table "countries", :force => true do |t| |
|
55 | t.string "name" |
|
47 | t.string "name" |
|
56 | - t.datetime "created_at" |
|
48 | + t.datetime "created_at", :null => false |
|
57 | - t.datetime "updated_at" |
|
49 | + t.datetime "updated_at", :null => false |
|
58 | end |
|
50 | end |
|
59 |
|
51 | ||
|
60 | create_table "descriptions", :force => true do |t| |
|
52 | create_table "descriptions", :force => true do |t| |
|
61 | t.text "body" |
|
53 | t.text "body" |
|
62 | t.boolean "markdowned" |
|
54 | t.boolean "markdowned" |
|
63 | - t.datetime "created_at" |
|
55 | + t.datetime "created_at", :null => false |
|
64 | - t.datetime "updated_at" |
|
56 | + t.datetime "updated_at", :null => false |
|
65 | end |
|
57 | end |
|
66 |
|
58 | ||
|
67 | create_table "grader_configurations", :force => true do |t| |
|
59 | create_table "grader_configurations", :force => true do |t| |
|
68 | t.string "key" |
|
60 | t.string "key" |
|
69 | t.string "value_type" |
|
61 | t.string "value_type" |
|
70 | t.string "value" |
|
62 | t.string "value" |
|
71 | - t.datetime "created_at" |
|
63 | + t.datetime "created_at", :null => false |
|
72 | - t.datetime "updated_at" |
|
64 | + t.datetime "updated_at", :null => false |
|
73 | t.text "description" |
|
65 | t.text "description" |
|
74 | end |
|
66 | end |
|
75 |
|
67 | ||
|
76 | create_table "grader_processes", :force => true do |t| |
|
68 | create_table "grader_processes", :force => true do |t| |
|
77 | t.string "host", :limit => 20 |
|
69 | t.string "host", :limit => 20 |
|
78 | t.integer "pid" |
|
70 | t.integer "pid" |
|
79 | t.string "mode" |
|
71 | t.string "mode" |
|
80 | t.boolean "active" |
|
72 | t.boolean "active" |
|
81 | - t.datetime "created_at" |
|
73 | + t.datetime "created_at", :null => false |
|
82 | - t.datetime "updated_at" |
|
74 | + t.datetime "updated_at", :null => false |
|
83 | t.integer "task_id" |
|
75 | t.integer "task_id" |
|
84 | t.string "task_type" |
|
76 | t.string "task_type" |
|
85 | t.boolean "terminated" |
|
77 | t.boolean "terminated" |
|
86 | end |
|
78 | end |
|
87 |
|
79 | ||
|
88 | add_index "grader_processes", ["host", "pid"], :name => "index_grader_processes_on_ip_and_pid" |
|
80 | add_index "grader_processes", ["host", "pid"], :name => "index_grader_processes_on_ip_and_pid" |
@@ -91,34 +83,39 | |||||
|
91 | t.string "name", :limit => 10 |
|
83 | t.string "name", :limit => 10 |
|
92 | t.string "pretty_name" |
|
84 | t.string "pretty_name" |
|
93 | t.string "ext", :limit => 10 |
|
85 | t.string "ext", :limit => 10 |
|
94 | t.string "common_ext" |
|
86 | t.string "common_ext" |
|
95 | end |
|
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 | create_table "messages", :force => true do |t| |
|
96 | create_table "messages", :force => true do |t| |
|
98 | t.integer "sender_id" |
|
97 | t.integer "sender_id" |
|
99 | t.integer "receiver_id" |
|
98 | t.integer "receiver_id" |
|
100 | t.integer "replying_message_id" |
|
99 | t.integer "replying_message_id" |
|
101 | t.text "body" |
|
100 | t.text "body" |
|
102 | t.boolean "replied" |
|
101 | t.boolean "replied" |
|
103 | - t.datetime "created_at" |
|
102 | + t.datetime "created_at", :null => false |
|
104 | - t.datetime "updated_at" |
|
103 | + t.datetime "updated_at", :null => false |
|
105 | end |
|
104 | end |
|
106 |
|
105 | ||
|
107 | create_table "problems", :force => true do |t| |
|
106 | create_table "problems", :force => true do |t| |
|
108 |
t.string |
|
107 | t.string "name", :limit => 30 |
|
109 |
t.string |
|
108 | t.string "full_name" |
|
110 |
t.integer |
|
109 | t.integer "full_score" |
|
111 |
t.date |
|
110 | t.date "date_added" |
|
112 |
t.boolean |
|
111 | t.boolean "available" |
|
113 |
t.string |
|
112 | t.string "url" |
|
114 |
t.integer |
|
113 | t.integer "description_id" |
|
115 |
t.boolean |
|
114 | t.boolean "test_allowed" |
|
116 |
t.boolean |
|
115 | t.boolean "output_only" |
|
117 | - t.integer "level", :default => 0 |
|
||
|
118 | - t.datetime "updated_at" |
|
||
|
119 |
t.string |
|
116 | t.string "description_filename" |
|
120 | end |
|
117 | end |
|
121 |
|
118 | ||
|
122 | create_table "rights", :force => true do |t| |
|
119 | create_table "rights", :force => true do |t| |
|
123 | t.string "name" |
|
120 | t.string "name" |
|
124 | t.string "controller" |
|
121 | t.string "controller" |
@@ -153,27 +150,18 | |||||
|
153 | add_index "sessions", ["updated_at"], :name => "index_sessions_on_updated_at" |
|
150 | add_index "sessions", ["updated_at"], :name => "index_sessions_on_updated_at" |
|
154 |
|
151 | ||
|
155 | create_table "sites", :force => true do |t| |
|
152 | create_table "sites", :force => true do |t| |
|
156 | t.string "name" |
|
153 | t.string "name" |
|
157 | t.boolean "started" |
|
154 | t.boolean "started" |
|
158 | t.datetime "start_time" |
|
155 | t.datetime "start_time" |
|
159 | - t.datetime "created_at" |
|
156 | + t.datetime "created_at", :null => false |
|
160 | - t.datetime "updated_at" |
|
157 | + t.datetime "updated_at", :null => false |
|
161 | t.integer "country_id" |
|
158 | t.integer "country_id" |
|
162 | t.string "password" |
|
159 | t.string "password" |
|
163 | end |
|
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 | create_table "submissions", :force => true do |t| |
|
162 | create_table "submissions", :force => true do |t| |
|
175 | t.integer "user_id" |
|
163 | t.integer "user_id" |
|
176 | t.integer "problem_id" |
|
164 | t.integer "problem_id" |
|
177 | t.integer "language_id" |
|
165 | t.integer "language_id" |
|
178 | t.text "source" |
|
166 | t.text "source" |
|
179 | t.binary "binary" |
|
167 | t.binary "binary" |
@@ -182,71 +170,63 | |||||
|
182 | t.text "compiler_message" |
|
170 | t.text "compiler_message" |
|
183 | t.datetime "graded_at" |
|
171 | t.datetime "graded_at" |
|
184 | t.integer "points" |
|
172 | t.integer "points" |
|
185 | t.text "grader_comment" |
|
173 | t.text "grader_comment" |
|
186 | t.integer "number" |
|
174 | t.integer "number" |
|
187 | t.string "source_filename" |
|
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 | end |
|
180 | end |
|
189 |
|
181 | ||
|
190 | add_index "submissions", ["user_id", "problem_id", "number"], :name => "index_submissions_on_user_id_and_problem_id_and_number", :unique => true |
|
182 | add_index "submissions", ["user_id", "problem_id", "number"], :name => "index_submissions_on_user_id_and_problem_id_and_number", :unique => true |
|
191 | add_index "submissions", ["user_id", "problem_id"], :name => "index_submissions_on_user_id_and_problem_id" |
|
183 | add_index "submissions", ["user_id", "problem_id"], :name => "index_submissions_on_user_id_and_problem_id" |
|
192 |
|
184 | ||
|
193 | create_table "tasks", :force => true do |t| |
|
185 | create_table "tasks", :force => true do |t| |
|
194 | t.integer "submission_id" |
|
186 | t.integer "submission_id" |
|
195 | t.datetime "created_at" |
|
187 | t.datetime "created_at" |
|
196 | t.integer "status" |
|
188 | t.integer "status" |
|
197 | t.datetime "updated_at" |
|
189 | t.datetime "updated_at" |
|
198 | end |
|
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 | create_table "test_pairs", :force => true do |t| |
|
192 | create_table "test_pairs", :force => true do |t| |
|
212 | t.integer "problem_id" |
|
193 | t.integer "problem_id" |
|
213 | t.text "input", :limit => 16777215 |
|
194 | t.text "input", :limit => 16777215 |
|
214 | t.text "solution", :limit => 16777215 |
|
195 | t.text "solution", :limit => 16777215 |
|
215 | - t.datetime "created_at" |
|
196 | + t.datetime "created_at", :null => false |
|
216 | - t.datetime "updated_at" |
|
197 | + t.datetime "updated_at", :null => false |
|
217 | - t.integer "number" |
|
||
|
218 | end |
|
198 | end |
|
219 |
|
199 | ||
|
220 | create_table "test_requests", :force => true do |t| |
|
200 | create_table "test_requests", :force => true do |t| |
|
221 | t.integer "user_id" |
|
201 | t.integer "user_id" |
|
222 | t.integer "problem_id" |
|
202 | t.integer "problem_id" |
|
223 | t.integer "submission_id" |
|
203 | t.integer "submission_id" |
|
224 | t.string "input_file_name" |
|
204 | t.string "input_file_name" |
|
225 | t.string "output_file_name" |
|
205 | t.string "output_file_name" |
|
226 | t.string "running_stat" |
|
206 | t.string "running_stat" |
|
227 | t.integer "status" |
|
207 | t.integer "status" |
|
228 | - t.datetime "updated_at" |
|
208 | + t.datetime "updated_at", :null => false |
|
229 | t.datetime "submitted_at" |
|
209 | t.datetime "submitted_at" |
|
230 | t.datetime "compiled_at" |
|
210 | t.datetime "compiled_at" |
|
231 | t.text "compiler_message" |
|
211 | t.text "compiler_message" |
|
232 | t.datetime "graded_at" |
|
212 | t.datetime "graded_at" |
|
233 | t.string "grader_comment" |
|
213 | t.string "grader_comment" |
|
234 | - t.datetime "created_at" |
|
214 | + t.datetime "created_at", :null => false |
|
235 | t.float "running_time" |
|
215 | t.float "running_time" |
|
236 | t.string "exit_status" |
|
216 | t.string "exit_status" |
|
237 | t.integer "memory_usage" |
|
217 | t.integer "memory_usage" |
|
238 | end |
|
218 | end |
|
239 |
|
219 | ||
|
240 | add_index "test_requests", ["user_id", "problem_id"], :name => "index_test_requests_on_user_id_and_problem_id" |
|
220 | add_index "test_requests", ["user_id", "problem_id"], :name => "index_test_requests_on_user_id_and_problem_id" |
|
241 |
|
221 | ||
|
242 | create_table "user_contest_stats", :force => true do |t| |
|
222 | create_table "user_contest_stats", :force => true do |t| |
|
243 | t.integer "user_id" |
|
223 | t.integer "user_id" |
|
244 | t.datetime "started_at" |
|
224 | t.datetime "started_at" |
|
245 | - t.datetime "created_at" |
|
225 | + t.datetime "created_at", :null => false |
|
246 | - t.datetime "updated_at" |
|
226 | + t.datetime "updated_at", :null => false |
|
247 | t.boolean "forced_logout" |
|
227 | t.boolean "forced_logout" |
|
248 | end |
|
228 | end |
|
249 |
|
229 | ||
|
250 | create_table "users", :force => true do |t| |
|
230 | create_table "users", :force => true do |t| |
|
251 |
t.string "login", |
|
231 | t.string "login", :limit => 50 |
|
252 | t.string "full_name" |
|
232 | t.string "full_name" |
@@ -256,18 +236,13 | |||||
|
256 | t.string "email" |
|
236 | t.string "email" |
|
257 | t.integer "site_id" |
|
237 | t.integer "site_id" |
|
258 | t.integer "country_id" |
|
238 | t.integer "country_id" |
|
259 |
t.boolean "activated", |
|
239 | t.boolean "activated", :default => false |
|
260 | t.datetime "created_at" |
|
240 | t.datetime "created_at" |
|
261 | t.datetime "updated_at" |
|
241 | t.datetime "updated_at" |
|
262 | - t.string "member1_full_name" |
|
242 | + t.boolean "enabled", :default => true |
|
263 |
- t.string " |
|
243 | + t.string "remark" |
|
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" |
|
||
|
269 | end |
|
244 | end |
|
270 |
|
245 | ||
|
271 | add_index "users", ["login"], :name => "index_users_on_login", :unique => true |
|
246 | add_index "users", ["login"], :name => "index_users_on_login", :unique => true |
|
272 |
|
247 | ||
|
273 | end |
|
248 | end |
@@ -51,16 +51,23 | |||||
|
51 | :value_type => 'boolean', |
|
51 | :value_type => 'boolean', |
|
52 | :default_value => 'false', |
|
52 | :default_value => 'false', |
|
53 | :description => 'If the server is in contest mode and this option is true, on the log in of the admin a menu for site selections is shown.' |
|
53 | :description => 'If the server is in contest mode and this option is true, on the log in of the admin a menu for site selections is shown.' |
|
54 | }, |
|
54 | }, |
|
55 |
|
55 | ||
|
56 | { |
|
56 | { |
|
57 | - :key => 'system.online_registration', |
|
57 | + :key => 'right.user_hall_of_fame', |
|
58 | :value_type => 'boolean', |
|
58 | :value_type => 'boolean', |
|
59 | :default_value => 'false', |
|
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 | # If Configuration['system.online_registration'] is true, the |
|
70 | # If Configuration['system.online_registration'] is true, the |
|
64 | # system allows online registration, and will use these |
|
71 | # system allows online registration, and will use these |
|
65 | # information for sending confirmation emails. |
|
72 | # information for sending confirmation emails. |
|
66 | { |
|
73 | { |
@@ -85,12 +92,19 | |||||
|
85 | :key => 'system.user_setting_enabled', |
|
92 | :key => 'system.user_setting_enabled', |
|
86 | :value_type => 'boolean', |
|
93 | :value_type => 'boolean', |
|
87 | :default_value => 'true', |
|
94 | :default_value => 'true', |
|
88 | :description => 'If this option is true, users can change their settings' |
|
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 | # If Configuration['contest.test_request.early_timeout'] is true |
|
105 | # If Configuration['contest.test_request.early_timeout'] is true |
|
92 | # the user will not be able to use test request at 30 minutes |
|
106 | # the user will not be able to use test request at 30 minutes |
|
93 | # before the contest ends. |
|
107 | # before the contest ends. |
|
94 | { |
|
108 | { |
|
95 | :key => 'contest.test_request.early_timeout', |
|
109 | :key => 'contest.test_request.early_timeout', |
|
96 | :value_type => 'boolean', |
|
110 | :value_type => 'boolean', |
@@ -191,8 +205,19 | |||||
|
191 |
|
205 | ||
|
192 | def seed_users_and_roles |
|
206 | def seed_users_and_roles |
|
193 | seed_roles |
|
207 | seed_roles |
|
194 | seed_root |
|
208 | seed_root |
|
195 | end |
|
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 | seed_config |
|
221 | seed_config |
|
198 | seed_users_and_roles |
|
222 | seed_users_and_roles |
|
|
223 | + seed_more_languages |
@@ -26,14 +26,14 | |||||
|
26 | def self.stop_graders(pids) |
|
26 | def self.stop_graders(pids) |
|
27 | pid_str = (pids.map { |process| process.pid.to_s }).join ' ' |
|
27 | pid_str = (pids.map { |process| process.pid.to_s }).join ' ' |
|
28 | GraderScript.call_grader "stop #{pid_str}" |
|
28 | GraderScript.call_grader "stop #{pid_str}" |
|
29 | end |
|
29 | end |
|
30 |
|
30 | ||
|
31 | def self.start_grader(env) |
|
31 | def self.start_grader(env) |
|
32 | - GraderScript.call_grader "#{env} queue &" |
|
32 | + GraderScript.call_grader "#{env} queue --err-log &" |
|
33 | - GraderScript.call_grader "#{env} test_request &" |
|
33 | + GraderScript.call_grader "#{env} test_request -err-log &" |
|
34 | end |
|
34 | end |
|
35 |
|
35 | ||
|
36 | def self.call_import_problem(problem_name, |
|
36 | def self.call_import_problem(problem_name, |
|
37 | problem_dir, |
|
37 | problem_dir, |
|
38 | time_limit=1, |
|
38 | time_limit=1, |
|
39 | memory_limit=32, |
|
39 | memory_limit=32, |
@@ -47,12 +47,12 | |||||
|
47 | " -t #{time_limit} -m #{memory_limit}" |
|
47 | " -t #{time_limit} -m #{memory_limit}" |
|
48 |
|
48 | ||
|
49 | output = `#{cmd}` |
|
49 | output = `#{cmd}` |
|
50 |
|
50 | ||
|
51 | Dir.chdir(cur_dir) |
|
51 | Dir.chdir(cur_dir) |
|
52 |
|
52 | ||
|
53 | - return output |
|
53 | + return "import CMD: #{cmd}\n" + output |
|
54 | end |
|
54 | end |
|
55 | return '' |
|
55 | return '' |
|
56 | end |
|
56 | end |
|
57 |
|
57 | ||
|
58 | end |
|
58 | end |
@@ -8,21 +8,23 | |||||
|
8 | @problem = problem |
|
8 | @problem = problem |
|
9 | end |
|
9 | end |
|
10 |
|
10 | ||
|
11 | def import_from_file(tempfile, |
|
11 | def import_from_file(tempfile, |
|
12 | time_limit, |
|
12 | time_limit, |
|
13 | memory_limit, |
|
13 | memory_limit, |
|
|
14 | + checker_name='text', | ||
|
14 | import_to_db=false) |
|
15 | import_to_db=false) |
|
15 |
|
16 | ||
|
16 | dirname = extract(tempfile) |
|
17 | dirname = extract(tempfile) |
|
17 | return false if not dirname |
|
18 | return false if not dirname |
|
18 | if not import_to_db |
|
19 | if not import_to_db |
|
19 | @log_msg = GraderScript.call_import_problem(@problem.name, |
|
20 | @log_msg = GraderScript.call_import_problem(@problem.name, |
|
20 | dirname, |
|
21 | dirname, |
|
21 | time_limit, |
|
22 | time_limit, |
|
22 |
- memory_limit |
|
23 | + memory_limit, |
|
|
24 | + checker_name) | ||
|
23 | else |
|
25 | else |
|
24 | # Import test data to test pairs. |
|
26 | # Import test data to test pairs. |
|
25 |
|
27 | ||
|
26 | @problem.test_pairs.clear |
|
28 | @problem.test_pairs.clear |
|
27 | if import_test_pairs(dirname) |
|
29 | if import_test_pairs(dirname) |
|
28 | test_pair_count = TestPair.count :conditions => "problem_id = #{@problem.id}" |
|
30 | test_pair_count = TestPair.count :conditions => "problem_id = #{@problem.id}" |
deleted file |
deleted file |
deleted file |
You need to be logged in to leave comments.
Login now