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

r486:17754ce1a3d6 - - 67 files changed: 1558 inserted, 433 deleted

@@ -0,0 +1,14
1 + //= require jquery
2 + //= require jquery_ujs
3 + //= require jquery.ui.all
4 + //= require jquery.ui.datepicker
5 + //= require jquery.ui.slider
6 + //= require jquery-ui-timepicker-addon
7 + //= require jquery-tablesorter
8 + //= require best_in_place
9 + //= require best_in_place.jquery-ui
10 +
11 + $(document).ready(function() {
12 + /* Activating Best In Place */
13 + jQuery(".best_in_place").best_in_place();
14 + });
@@ -0,0 +1,3
1 + # Place all the behaviors and hooks related to the matching controller here.
2 + # All this logic will automatically be available in application.js.
3 + # You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/
@@ -0,0 +1,3
1 + // Place all the styles related to the report controller here.
2 + // They will automatically be included in application.css.
3 + // You can use Sass (SCSS) here: http://sass-lang.com/
@@ -0,0 +1,197
1 + /*************
2 + Metro Dark Theme
3 + *************/
4 + /* overall */
5 + .tablesorter-cafe {
6 + // font: 12px/18px 'Segoe UI Semilight', 'Open Sans', Verdana, Arial, Helvetica, sans-serif;
7 + color: #000;
8 + background-color: #777;
9 + margin: 10px 0 15px;
10 + text-align: left;
11 + border-collapse: collapse;
12 + border: #555 1px solid;
13 + }
14 +
15 + .tablesorter-cafe tr.dark-row th, .tablesorter-cafe tr.dark-row td {
16 + background-color: #222;
17 + color: #fff;
18 + text-align: left;
19 + font-size: 14px;
20 + }
21 +
22 + /* header/footer */
23 + .tablesorter-cafe caption,
24 + .tablesorter-cafe th,
25 + .tablesorter-cafe thead td,
26 + .tablesorter-cafe tfoot th,
27 + .tablesorter-cafe tfoot td {
28 + //font-weight: 300;
29 + //font-size: 15px;
30 + color: #fff;
31 + background-color: #777;
32 + padding: 2px;
33 + border: #555 1px solid;
34 + }
35 +
36 + .tablesorter-cafe .header,
37 + .tablesorter-cafe .tablesorter-header {
38 + background-image: url(data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAQBAMAAADQT4M0AAAAGFBMVEUAAADu7u7u7u7u7u7u7u7u7u7u7u7u7u5jNePWAAAACHRSTlMAMxIHKwEgMWD59H4AAABSSURBVAjXY2BgYFJgAAHzYhDJ6igSAKTYBAUTgJSioKAQAwNzoaCguAFDiCAQuDIkgigxBgiA8cJAVCpQt6AgSL+JoKAzA0gjUBsQqBcBCYhFAAE/CV4zeSzxAAAAAElFTkSuQmCC);
39 + background-position: center right;
40 + background-repeat: no-repeat;
41 + cursor: pointer;
42 + white-space: normal;
43 + }
44 + .tablesorter-cafe .tablesorter-header-inner {
45 + padding: 0 18px 0 4px;
46 + }
47 + .tablesorter-cafe thead .headerSortUp,
48 + .tablesorter-cafe thead .tablesorter-headerSortUp,
49 + .tablesorter-cafe thead .tablesorter-headerAsc {
50 + background-image: url(data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAQBAMAAADQT4M0AAAAIVBMVEUAAADu7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u5meJAOAAAACnRSTlMAMwsqXt+gIBUGxGoDMAAAAFlJREFUCNctzC0SQAAUReEzGNQ3AlHRiSRZFCVZYgeswRL8hLdK7834wj3tAlGP6y7fYHpKS6w6WwbVG0I1NZVnZPG8/DYxOYlnhUYkA06R1s9ESsxR4NIdPhkPFDFYuEnMAAAAAElFTkSuQmCC);
51 + }
52 + .tablesorter-cafe thead .headerSortDown,
53 + .tablesorter-cafe thead .tablesorter-headerSortDown,
54 + .tablesorter-cafe thead .tablesorter-headerDesc {
55 + background-image: url(data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAQBAMAAADQT4M0AAAALVBMVEUAAADu7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7i0NViAAAADnRSTlMAMiweCQITTvDctZZqaTlM310AAABcSURBVAjXY2BgYEtgAAFHERDJqigUAKSYBQUNgFSioKAYAwOLIBA4MASBKFUGQxAlzAAF+94BwWuGKBC1lIFl3rt3Lx0YGCzevWsGSjK9e6cAUlT3HKyW9wADAwDRrBiDy6bKzwAAAABJRU5ErkJggg==);
56 + }
57 + .tablesorter-cafe thead .sorter-false {
58 + background-image: none;
59 + cursor: default;
60 + padding: 4px;
61 + }
62 +
63 + /* tbody */
64 + .tablesorter-cafe td {
65 + background-color: #fff;
66 + padding: 1px 4px;
67 + vertical-align: top;
68 + border-style: solid;
69 + border-color: #666;
70 + border-collapse: collapse;
71 + border-width: 0px 1px;
72 +
73 + }
74 +
75 + /* hovered row colors */
76 + .tablesorter-cafe tbody > tr:hover > td,
77 + .tablesorter-cafe tbody > tr.even:hover > td,
78 + .tablesorter-cafe tbody > tr.odd:hover > td {
79 + background: #bbb;
80 + color: #000;
81 + }
82 +
83 + /* table processing indicator */
84 + .tablesorter-cafe .tablesorter-processing {
85 + background-position: center center !important;
86 + background-repeat: no-repeat !important;
87 + /* background-image: url(../addons/pager/icons/loading.gif) !important; */
88 + background-image: url(data:image/gif;base64,R0lGODlhFAAUAKEAAO7u7lpaWgAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQBCgACACwAAAAAFAAUAAACQZRvoIDtu1wLQUAlqKTVxqwhXIiBnDg6Y4eyx4lKW5XK7wrLeK3vbq8J2W4T4e1nMhpWrZCTt3xKZ8kgsggdJmUFACH5BAEKAAIALAcAAAALAAcAAAIUVB6ii7jajgCAuUmtovxtXnmdUAAAIfkEAQoAAgAsDQACAAcACwAAAhRUIpmHy/3gUVQAQO9NetuugCFWAAAh+QQBCgACACwNAAcABwALAAACE5QVcZjKbVo6ck2AF95m5/6BSwEAIfkEAQoAAgAsBwANAAsABwAAAhOUH3kr6QaAcSrGWe1VQl+mMUIBACH5BAEKAAIALAIADQALAAcAAAIUlICmh7ncTAgqijkruDiv7n2YUAAAIfkEAQoAAgAsAAAHAAcACwAAAhQUIGmHyedehIoqFXLKfPOAaZdWAAAh+QQFCgACACwAAAIABwALAAACFJQFcJiXb15zLYRl7cla8OtlGGgUADs=) !important;
89 + }
90 +
91 + /* pager */
92 + .tablesorter-cafe .tablesorter-pager button {
93 + background-color: #444;
94 + color: #eee;
95 + border: #555 1px solid;
96 + cursor: pointer;
97 + }
98 + .tablesorter-cafe .tablesorter-pager button:hover {
99 + background-color: #555;
100 + }
101 +
102 + /* Zebra Widget - row alternating colors */
103 + .tablesorter-cafe tr.odd td {
104 + background-color: #eee;
105 + }
106 + .tablesorter-cafe tr.even td {
107 + background-color: #fff;
108 + }
109 +
110 + /* Column Widget - column sort colors */
111 + .tablesorter-cafe tr.odd td.primary {
112 + background-color: #bfbfbf;
113 + }
114 + .tablesorter-cafe td.primary,
115 + .tablesorter-cafe tr.even td.primary {
116 + background-color: #d9d9d9;
117 + }
118 + .tablesorter-cafe tr.odd td.secondary {
119 + background-color: #d9d9d9;
120 + }
121 + .tablesorter-cafe td.secondary,
122 + .tablesorter-cafe tr.even td.secondary {
123 + background-color: #e6e6e6;
124 + }
125 + .tablesorter-cafe tr.odd td.tertiary {
126 + background-color: #e6e6e6;
127 + }
128 + .tablesorter-cafe td.tertiary,
129 + .tablesorter-cafe tr.even td.tertiary {
130 + background-color: #f2f2f2;
131 + }
132 +
133 + /* filter widget */
134 + .tablesorter-cafe .tablesorter-filter-row td {
135 + background: #eee;
136 + line-height: normal;
137 + text-align: center; /* center the input */
138 + -webkit-transition: line-height 0.1s ease;
139 + -moz-transition: line-height 0.1s ease;
140 + -o-transition: line-height 0.1s ease;
141 + transition: line-height 0.1s ease;
142 + }
143 + /* optional disabled input styling */
144 + .tablesorter-cafe .tablesorter-filter-row .disabled {
145 + opacity: 0.5;
146 + filter: alpha(opacity=50);
147 + cursor: not-allowed;
148 + }
149 + /* hidden filter row */
150 + .tablesorter-cafe .tablesorter-filter-row.hideme td {
151 + /*** *********************************************** ***/
152 + /*** change this padding to modify the thickness ***/
153 + /*** of the closed filter row (height = padding x 2) ***/
154 + padding: 2px;
155 + /*** *********************************************** ***/
156 + margin: 0;
157 + line-height: 0;
158 + cursor: pointer;
159 + }
160 + .tablesorter-cafe .tablesorter-filter-row.hideme .tablesorter-filter {
161 + height: 1px;
162 + min-height: 0;
163 + border: 0;
164 + padding: 0;
165 + margin: 0;
166 + /* don't use visibility: hidden because it disables tabbing */
167 + opacity: 0;
168 + filter: alpha(opacity=0);
169 + }
170 + /* filters */
171 + .tablesorter-cafe .tablesorter-filter {
172 + width: 95%;
173 + height: auto;
174 + margin: 4px;
175 + padding: 4px;
176 + background-color: #fff;
177 + border: 1px solid #bbb;
178 + color: #333;
179 + -webkit-box-sizing: border-box;
180 + -moz-box-sizing: border-box;
181 + box-sizing: border-box;
182 + -webkit-transition: height 0.1s ease;
183 + -moz-transition: height 0.1s ease;
184 + -o-transition: height 0.1s ease;
185 + transition: height 0.1s ease;
186 + }
187 + /* rows hidden by filtering (needed for child rows) */
188 + .tablesorter .filtered {
189 + display: none;
190 + }
191 +
192 + /* ajax error row */
193 + .tablesorter .tablesorter-errorRow td {
194 + text-align: center;
195 + cursor: pointer;
196 + background-color: #e6bf99;
197 + }
@@ -0,0 +1,218
1 + class ReportController < ApplicationController
2 +
3 + before_filter :admin_authorization, only: [:login_stat,:submission_stat, :stuck]
4 + before_filter(only: [:problem_hof]) { |c|
5 + return false unless authenticate
6 +
7 + if GraderConfiguration["right.user_view_submission"]
8 + return true;
9 + end
10 +
11 + admin_authorization
12 + }
13 +
14 + def login_stat
15 + @logins = Array.new
16 +
17 + date_and_time = '%Y-%m-%d %H:%M'
18 + begin
19 + md = params[:since_datetime].match(/(\d+)-(\d+)-(\d+) (\d+):(\d+)/)
20 + @since_time = Time.zone.local(md[1].to_i,md[2].to_i,md[3].to_i,md[4].to_i,md[5].to_i)
21 + rescue
22 + @since_time = DateTime.new(1000,1,1)
23 + end
24 + begin
25 + md = params[:until_datetime].match(/(\d+)-(\d+)-(\d+) (\d+):(\d+)/)
26 + @until_time = Time.zone.local(md[1].to_i,md[2].to_i,md[3].to_i,md[4].to_i,md[5].to_i)
27 + rescue
28 + @until_time = DateTime.new(3000,1,1)
29 + end
30 +
31 + User.all.each do |user|
32 + @logins << { id: user.id,
33 + login: user.login,
34 + full_name: user.full_name,
35 + count: Login.where("user_id = ? AND created_at >= ? AND created_at <= ?",
36 + user.id,@since_time,@until_time)
37 + .count(:id),
38 + min: Login.where("user_id = ? AND created_at >= ? AND created_at <= ?",
39 + user.id,@since_time,@until_time)
40 + .minimum(:created_at),
41 + max: Login.where("user_id = ? AND created_at >= ? AND created_at <= ?",
42 + user.id,@since_time,@until_time)
43 + .maximum(:created_at),
44 + ip: Login.where("user_id = ? AND created_at >= ? AND created_at <= ?",
45 + user.id,@since_time,@until_time)
46 + .select(:ip_address).uniq
47 +
48 + }
49 + end
50 + end
51 +
52 + def submission_stat
53 +
54 + date_and_time = '%Y-%m-%d %H:%M'
55 + begin
56 + @since_time = DateTime.strptime(params[:since_datetime],date_and_time)
57 + rescue
58 + @since_time = DateTime.new(1000,1,1)
59 + end
60 + begin
61 + @until_time = DateTime.strptime(params[:until_datetime],date_and_time)
62 + rescue
63 + @until_time = DateTime.new(3000,1,1)
64 + end
65 +
66 + @submissions = {}
67 +
68 + User.find_each do |user|
69 + @submissions[user.id] = { login: user.login, full_name: user.full_name, count: 0, sub: { } }
70 + end
71 +
72 + Submission.where("submitted_at >= ? AND submitted_at <= ?",@since_time,@until_time).find_each do |s|
73 + if @submissions[s.user_id]
74 + if not @submissions[s.user_id][:sub].has_key?(s.problem_id)
75 + a = nil
76 + begin
77 + a = Problem.find(s.problem_id)
78 + rescue
79 + a = nil
80 + end
81 + @submissions[s.user_id][:sub][s.problem_id] =
82 + { prob_name: (a ? a.full_name : '(NULL)'),
83 + sub_ids: [s.id] }
84 + else
85 + @submissions[s.user_id][:sub][s.problem_id][:sub_ids] << s.id
86 + end
87 + @submissions[s.user_id][:count] += 1
88 + end
89 + end
90 + end
91 +
92 + def problem_hof
93 + # gen problem list
94 + @user = User.find(session[:user_id])
95 + @problems = @user.available_problems
96 +
97 + # get selected problems or the default
98 + if params[:id]
99 + begin
100 + @problem = Problem.available.find(params[:id])
101 + rescue
102 + redirect_to action: :problem_hof
103 + flash[:notice] = 'Error: submissions for that problem are not viewable.'
104 + return
105 + end
106 + end
107 +
108 + return unless @problem
109 +
110 + @by_lang = {} #aggregrate by language
111 +
112 + range =65
113 + @histogram = { data: Array.new(range,0), summary: {} }
114 + @summary = {count: 0, solve: 0, attempt: 0}
115 + user = Hash.new(0)
116 + Submission.where(problem_id: @problem.id).find_each do |sub|
117 + #histogram
118 + d = (DateTime.now.in_time_zone - sub.submitted_at) / 24 / 60 / 60
119 + @histogram[:data][d.to_i] += 1 if d < range
120 +
121 + next unless sub.points
122 + @summary[:count] += 1
123 + user[sub.user_id] = [user[sub.user_id], (sub.points >= @problem.full_score) ? 1 : 0].max
124 +
125 + lang = Language.find_by_id(sub.language_id)
126 + next unless lang
127 + next unless sub.points >= @problem.full_score
128 +
129 + #initialize
130 + unless @by_lang.has_key?(lang.pretty_name)
131 + @by_lang[lang.pretty_name] = {
132 + runtime: { avail: false, value: 2**30-1 },
133 + memory: { avail: false, value: 2**30-1 },
134 + length: { avail: false, value: 2**30-1 },
135 + first: { avail: false, value: DateTime.new(3000,1,1) }
136 + }
137 + end
138 +
139 + if sub.max_runtime and sub.max_runtime < @by_lang[lang.pretty_name][:runtime][:value]
140 + @by_lang[lang.pretty_name][:runtime] = { avail: true, user_id: sub.user_id, value: sub.max_runtime, sub_id: sub.id }
141 + end
142 +
143 + if sub.peak_memory and sub.peak_memory < @by_lang[lang.pretty_name][:memory][:value]
144 + @by_lang[lang.pretty_name][:memory] = { avail: true, user_id: sub.user_id, value: sub.peak_memory, sub_id: sub.id }
145 + end
146 +
147 + if sub.submitted_at and sub.submitted_at < @by_lang[lang.pretty_name][:first][:value] and
148 + !sub.user.admin?
149 + @by_lang[lang.pretty_name][:first] = { avail: true, user_id: sub.user_id, value: sub.submitted_at, sub_id: sub.id }
150 + end
151 +
152 + if @by_lang[lang.pretty_name][:length][:value] > sub.effective_code_length
153 + @by_lang[lang.pretty_name][:length] = { avail: true, user_id: sub.user_id, value: sub.effective_code_length, sub_id: sub.id }
154 + end
155 + end
156 +
157 + #process user_id
158 + @by_lang.each do |lang,prop|
159 + prop.each do |k,v|
160 + v[:user] = User.exists?(v[:user_id]) ? User.find(v[:user_id]).full_name : "(NULL)"
161 + end
162 + end
163 +
164 + #sum into best
165 + if @by_lang and @by_lang.first
166 + @best = @by_lang.first[1].clone
167 + @by_lang.each do |lang,prop|
168 + if @best[:runtime][:value] >= prop[:runtime][:value]
169 + @best[:runtime] = prop[:runtime]
170 + @best[:runtime][:lang] = lang
171 + end
172 + if @best[:memory][:value] >= prop[:memory][:value]
173 + @best[:memory] = prop[:memory]
174 + @best[:memory][:lang] = lang
175 + end
176 + if @best[:length][:value] >= prop[:length][:value]
177 + @best[:length] = prop[:length]
178 + @best[:length][:lang] = lang
179 + end
180 + if @best[:first][:value] >= prop[:first][:value]
181 + @best[:first] = prop[:first]
182 + @best[:first][:lang] = lang
183 + end
184 + end
185 + end
186 +
187 + @histogram[:summary][:max] = [@histogram[:data].max,1].max
188 + @summary[:attempt] = user.count
189 + user.each_value { |v| @summary[:solve] += 1 if v == 1 }
190 + end
191 +
192 + def stuck #report struggling user,problem
193 + # init
194 + user,problem = nil
195 + solve = true
196 + tries = 0
197 + @struggle = Array.new
198 + record = {}
199 + Submission.includes(:problem,:user).order(:problem_id,:user_id).find_each do |sub|
200 + next unless sub.problem and sub.user
201 + if user != sub.user_id or problem != sub.problem_id
202 + @struggle << { user: record[:user], problem: record[:problem], tries: tries } unless solve
203 + record = {user: sub.user, problem: sub.problem}
204 + user,problem = sub.user_id, sub.problem_id
205 + solve = false
206 + tries = 0
207 + end
208 + if sub.points >= sub.problem.full_score
209 + solve = true
210 + else
211 + tries += 1
212 + end
213 + end
214 + @struggle.sort!{|a,b| b[:tries] <=> a[:tries] }
215 + @struggle = @struggle[0..50]
216 + end
217 +
218 + end
@@ -0,0 +1,2
1 + module ReportHelper
2 + end
@@ -0,0 +1,3
1 + class Login < ActiveRecord::Base
2 + attr_accessible :ip_address, :logged_in_at, :user_id
3 + end
@@ -0,0 +1,44
1 + - param = {} unless param
2 + - graph_height = param[:graph_height] || 100
3 + - bar_width = param[:bar_width] || 14
4 + - graph_width = (bar_width * histogram[:data].count) + 20
5 + :css
6 + .hist_bar {
7 + width: #{bar_width-1}px;
8 + position: absolute;
9 + background-color: lightblue;
10 + }
11 + .hist_fill {
12 + width: #{bar_width-1}px;
13 + position: absolute;
14 + background-color: #eee;
15 + }
16 + .hist_text {
17 + position: absolute;
18 + font-size:5px;
19 + }
20 +
21 + %div{style: "position: relative; width: #{graph_width}px; height: 125px; background-color:#fff;" }
22 + //draw background
23 + - histogram[:data].each_index do |i|
24 + - height = histogram[:data][i] * graph_height / histogram[:summary][:max]
25 + - top = graph_height - height
26 + - left = graph_width - (i+1)*bar_width
27 + %div.hist_fill{style: "top: 0px; height: #{graph_height - height}px; left: #{left}px;" }
28 + // draw horizontal line
29 + - line = 3
30 + - line.times do |i|
31 + - top = graph_height - graph_height * (i+0.5)/ line
32 + %div{style: "position:absolute;width: #{graph_width-21}px;height: 1px;left: 20px;top:#{top}px;background-color: #333;"}
33 + %div.hist_text{style: "position:absolute;left: 0px;top:#{top-6}px"}
34 + =((i+0.5) * histogram[:summary][:max] / line).to_i
35 + // draw the actual bar and text
36 + - @histogram[:data].each_index do |i|
37 + - height = histogram[:data][i] * graph_height / histogram[:summary][:max]
38 + - top = graph_height - height
39 + - left = graph_width - (i+1)*bar_width
40 + %div.hist_bar{style: "top: #{top}px; height: #{height}px; left: #{left}px; dae: #{histogram[:data][i]}" }
41 + - if i % 7 == 1
42 + %div.hist_text{style: "top:#{graph_height + 5}px;left: #{left}px;"} #{(Time.zone.today - i.day).strftime('%-d')}
43 + - if (Time.now.in_time_zone - i.day).day == 15
44 + %div.hist_text{style: "top:#{graph_height + 15}px;left: #{left}px;"} #{(Time.zone.today - i.day).strftime('%b')}
@@ -0,0 +1,51
1 + :css
2 + .fix-width {
3 + font-family: "Consolas, Monaco, Droid Sans Mono,Mono, Monospace,Courier"
4 + }
5 +
6 + %h1 Problem stat: #{@problem.name}
7 + %h2 Overview
8 +
9 +
10 + %table.info
11 + %thead
12 + %tr.info-head
13 + %th Stat
14 + %th Value
15 + %tbody
16 + %tr{class: cycle('info-even','info-odd')}
17 + %td Submissions
18 + %td= @submissions.count
19 + %tr{class: cycle('info-even','info-odd')}
20 + %td Solved/Attempted User
21 + %td #{@summary[:solve]}/#{@summary[:attempt]} (#{(@summary[:solve]*100.0/@summary[:attempt]).round(1)}%)
22 +
23 + %h2 Submissions Count
24 + = render partial: 'application/bar_graph', locals: { histogram: @histogram }
25 +
26 + %h2 Submissions
27 + - if @submissions and @submissions.count > 0
28 + %table.info#main_table
29 + %thead
30 + %tr.info-head
31 + %th ID
32 + %th Login
33 + %th Name
34 + %th Submitted_at
35 + %th Points
36 + %th comment
37 + %tbody
38 + - row_odd,curr = true,''
39 + - @submissions.each do |sub|
40 + - next unless sub.user
41 + - row_odd,curr = !row_odd, sub.user if curr != sub.user
42 + %tr{class: row_odd ? "info-odd" : "info-even"}
43 + %td= link_to sub.id, controller: 'graders', action: 'submission', id: sub.id
44 + %td= link_to sub.user.login, controller: :users, action: :profile, id: sub.user.id
45 + %td= sub.user.full_name
46 + %td= time_ago_in_words(sub.submitted_at) + " ago"
47 + %td= sub.points
48 + %td.fix-width= sub.grader_comment
49 + - else
50 + No submission
51 +
@@ -0,0 +1,8
1 + %h2 Paid in Full
2 + User with highest number of problem solved
3 +
4 + %h2 Polymaths
5 + User with highest number of problems each solved by more than 1 languages.
6 +
7 + %h2 Icebreakers
8 + If you solve the problem before 95% of your friends, you are an icebreaker.
@@ -0,0 +1,23
1 +
2 + = form_tag({session: :url }) do
3 + .submitbox
4 + %table
5 + %tr
6 + %td{colspan: 6, style: 'font-weight: bold'}= title
7 + %tr
8 + %td{style: 'width: 120px; font-weight: bold'}= param_text
9 + %td{align: 'right'} since:
10 + %td= text_field_tag 'since_datetime'
11 + %tr
12 + %td
13 + %td{align: 'right'} until:
14 + %td= text_field_tag 'until_datetime'
15 + %tr
16 + %td
17 + %td
18 + %td Blank mean no condition
19 + %tr
20 + %td
21 + %td
22 + %td= submit_tag 'query'
23 +
@@ -0,0 +1,7
1 +
2 + .task-menu
3 + Reports
4 + %br/
5 + = link_to '[Hall of Fame]', :action => 'problem_hof'
6 + = link_to '[Struggle]', :action => 'stuck'
7 + = link_to '[Login]', :action => 'login_stat'
@@ -0,0 +1,127
1 + - content_for :header do
2 + = javascript_include_tag 'local_jquery'
3 +
4 + :javascript
5 + $(document).ready( function() {
6 + $("#mem_remark").hover( function() {
7 + $("#mem_remark_box").show();
8 + }, function() {
9 + $("#mem_remark_box").hide();
10 + });
11 + });
12 + :css
13 + .hof_user { color: orangered; font-style: italic; }
14 + .hof_language { color: green; font-style: italic; }
15 + .hof_value { color: deeppink;font-style: italic; }
16 + .info_param { font-weight: bold;text-align: right; }
17 + .tooltip {
18 + font-family: Verdana,sans-serif;
19 + font-weight: normal;
20 + text-align: left;
21 + font-size: 1.0em;
22 + color: black;
23 + line-height: 1.1;
24 + display: none;
25 + min-width: 20em;
26 + position: absolute;
27 + left: 25px;
28 + bottom: 5px;
29 + border: 1px solid;
30 + padding: 5px;
31 + background-color: #FFF;
32 + word-wrap: break-word;
33 + z-index: 9999;
34 + overflow: auto;
35 + }
36 +
37 + %h1 (#{Problem.find(params[:id]).name}) #{Problem.find(params[:id]).full_name}
38 +
39 + %h2 Problem Stat
40 + %table.info
41 + %thead
42 + %tr.info-head
43 + %th Stat
44 + %th Value
45 + %tbody
46 + %tr{class: cycle('info-even','info-odd')}
47 + %td.info_param Submissions
48 + %td= @summary[:count]
49 + %tr{class: cycle('info-even','info-odd')}
50 + %td.info_param Solved/Attempted User
51 + %td #{@summary[:solve]}/#{@summary[:attempt]} (#{(@summary[:solve]*100.0/@summary[:attempt]).round(1)}%)
52 + - if @best
53 + %tr{class: cycle('info-even','info-odd')}
54 + %td.info_param Best Runtime
55 + %td
56 + by #{link_to @best[:runtime][:user], controller:'users', action:'profile', id:@best[:memory][:user_id]}
57 + using <span class="hof_language">#{@best[:runtime][:lang]}</span>
58 + with <span class="hof_value">#{@best[:runtime][:value] * 1000} milliseconds</span>
59 + at submission
60 + = link_to("#" + @best[:runtime][:sub_id].to_s, controller: 'graders', action: 'submission', id:@best[:runtime][:sub_id])
61 +
62 + %tr{class: cycle('info-even','info-odd')}
63 + %td.info_param
64 + Best Memory Usage
65 + %sup{ id: "mem_remark", style: "position:relative; color: blue;"}
66 + [?]
67 + %span.tooltip#mem_remark_box
68 + This counts only for submission with 100% score.
69 + Right now, java is excluded from memory usage competition. (Because it always uses 2GB memory...)
70 + %td
71 + by #{link_to @best[:memory][:user], controller:'users', action:'profile', id:@best[:memory][:user_id]}
72 + using <span class="hof_language">#{@best[:memory][:lang]}</span>
73 + with <span class="hof_value">#{number_with_delimiter(@best[:memory][:value])} kbytes </span>
74 + at submission
75 + = link_to("#" + @best[:memory][:sub_id].to_s, controller: 'graders' , action: 'submission', id:@best[:memory][:sub_id])
76 +
77 + %tr{class: cycle('info-even','info-odd')}
78 + %td.info_param Shortest Code
79 + %td
80 + by #{link_to @best[:length][:user], controller:'users', action:'profile', id:@best[:length][:user_id]}
81 + using <span class="hof_language">#{@best[:length][:lang]}</span>
82 + with <span class="hof_value">#{@best[:length][:value]} bytes</span>
83 + at submission
84 + = link_to("#" + @best[:length][:sub_id].to_s, controller: 'graders' , action: 'submission', id: @best[:length][:sub_id])
85 +
86 + %tr{class: cycle('info-even','info-odd')}
87 + %td.info_param First solver
88 + %td
89 + #{link_to @best[:first][:user], controller:'users', action:'profile', id:@best[:first][:user_id]} is the first solver
90 + using <span class="hof_language">#{@best[:first][:lang]}</span>
91 + on <span class="hof_value">#{@best[:first][:value]}</span>
92 + at submission
93 + = link_to("#" + @best[:first][:sub_id].to_s, controller: 'graders' , action: 'submission', id: @best[:first][:sub_id])
94 +
95 + - if @best
96 + %h2 By language
97 +
98 + %table.info
99 + %thead
100 + %tr.info-head
101 + %th Language
102 + %th Best runtime (ms)
103 + %th Best memory (kbytes)
104 + %th Shortest Code (bytes)
105 + %th First solver
106 + %tbody
107 + - @by_lang.each do |lang,value|
108 + %tr{class: cycle('info-even','info-odd')}
109 + %td= lang
110 + %td
111 + = link_to value[:runtime][:user], controller: 'users', action: 'profile', id: value[:runtime][:user_id]
112 + = "(#{(value[:runtime][:value] * 1000).to_i} @"
113 + = "#{link_to("#" + value[:runtime][:sub_id].to_s, controller: 'graders' , action: 'submission', id: value[:runtime][:sub_id])} )".html_safe
114 + %td
115 + = link_to value[:memory][:user], controller: 'users', action: 'profile', id: value[:memory][:user_id]
116 + = "(#{number_with_delimiter(value[:memory][:value])} @"
117 + = "#{link_to("#" + value[:memory][:sub_id].to_s, controller: 'graders' , action: 'submission', id: value[:memory][:sub_id])} )".html_safe
118 + %td
119 + = link_to value[:length][:user], controller: 'users', action: 'profile', id: value[:length][:user_id]
120 + = "(#{value[:length][:value]} @"
121 + = "#{link_to("#" + value[:length][:sub_id].to_s, controller: 'graders' , action: 'submission', id: value[:length][:sub_id])} )".html_safe
122 + %td
123 + - if value[:first][:user] != '(NULL)' #TODO: i know... this is wrong...
124 + = link_to value[:first][:user], controller: 'users', action: 'profile', id: value[:first][:user_id]
125 + = "(#{value[:first][:value]} @"
126 + = "#{link_to("#" + value[:first][:sub_id].to_s, controller: 'graders' , action: 'submission', id: value[:first][:sub_id])} )".html_safe
127 +
@@ -0,0 +1,36
1 + - content_for :header do
2 + = stylesheet_link_tag 'tablesorter-theme.cafe'
3 + = javascript_include_tag 'local_jquery'
4 +
5 + %script{:type=>"text/javascript"}
6 + $(function () {
7 + $('#since_datetime').datetimepicker({ showButtonPanel: true, dateFormat: "yy-mm-dd", controlType: "slider"} );
8 + $('#until_datetime').datetimepicker({ showButtonPanel: true, dateFormat: "yy-mm-dd", controlType: "slider"} );
9 + $('#my_table').tablesorter({widthFixed: true, widgets: ['zebra']});
10 + });
11 +
12 + %h1 Login status
13 +
14 + =render partial: 'report_menu'
15 + =render partial: 'date_range', locals: {param_text: 'Login date range:', title: 'Query login stat in the range' }
16 +
17 + %table.tablesorter-cafe#my_table
18 + %thead
19 + %tr
20 + %th login
21 + %th full name
22 + %th login count
23 + %th earliest
24 + %th latest
25 + %th IP
26 + %tbody
27 + - @logins.each do |l|
28 + %tr{class: cycle('info-even','info-odd')}
29 + %td= link_to l[:login], controller: 'users', action: 'profile', id: l[:id]
30 + %td= l[:full_name]
31 + %td= l[:count]
32 + %td= l[:min] ? l[:min].in_time_zone.strftime('%Y-%m-%d %H:%M') : ''
33 + %td= l[:max] ? "#{l[:max].in_time_zone.strftime('%Y-%m-%d %H:%M.%S')} (#{time_ago_in_words(l[:max].in_time_zone)} ago)" : ''
34 + %td
35 + - l[:ip].each do |ip|
36 + #{ip.ip_address} <br/>
@@ -0,0 +1,23
1 +
2 + /- if params[:id]
3 + / %h1 Tasks Hall of Fame
4 + / = link_to('[back to All-Time Hall of Fame]', action: 'problem_hof', id: nil )
5 + /- else
6 + / %h1 All-Time Hall of Fame
7 +
8 +
9 + %h1 Hall of Fame
10 + .task-menu
11 + Tasks
12 + %br/
13 + - @problems.each do |prob|
14 + = link_to( "[#{prob.name}]", {id: prob.id})
15 +
16 + - unless params[:id]
17 + /=render partial: 'all_time_hof'
18 + Please select a problem.
19 + - else
20 + =render partial: 'task_hof'
21 + %h2 Submission History
22 + =render partial: 'application/bar_graph', locals: { histogram: @histogram }
23 +
@@ -0,0 +1,17
1 + %table.info
2 + %thead
3 + %tr.info-head
4 + %th Problem
5 + %th User
6 + %th tries
7 + %tbody
8 + - @struggle.each do |s|
9 + %tr
10 + %td
11 + = link_to "(#{s[:problem].name})", controller: :problems, action: :stat, id: s[:problem]
12 + = s[:problem].full_name
13 + %td
14 + = link_to "(#{s[:user].login})", controller: :users, action: :profile, id: s[:user]
15 + = s[:user].full_name
16 + %td
17 + = s[:tries]
@@ -0,0 +1,37
1 + - content_for :header do
2 + = javascript_include_tag 'local_jquery'
3 +
4 + %script{:type=>"text/javascript"}
5 + $(function () {
6 + $('#since_datetime').datetimepicker({ showButtonPanel: true, dateFormat: "yy-mm-dd", controlType: "slider"} );
7 + $('#until_datetime').datetimepicker({ showButtonPanel: true, dateFormat: "yy-mm-dd", controlType: "slider"} );
8 + });
9 +
10 + %h1 Login status
11 +
12 + =render partial: 'report_menu'
13 + =render partial: 'date_range', locals: {param_text: 'Submission date range:', title: 'Query submission stat in the range' }
14 +
15 + %table.info
16 + %thead
17 + %tr.info-head
18 + %th login
19 + %th full name
20 + %th total submissions
21 + %th submissions
22 + %tbody
23 + - @submissions.each do |user_id,data|
24 + %tr{class: cycle('info-even','info-odd')}
25 + %td= data[:login]
26 + %td= data[:full_name]
27 + %td= data[:count]
28 + %td
29 + - data[:sub].each do |prob_id,sub_data|
30 + = "#{sub_data[:prob_name]}: ["
31 + - st = []
32 + - sub_data[:sub_ids].each do |id|
33 + - st << link_to(id, controller: 'graders' , action: 'submission', id: id)
34 + = raw st.join ', '
35 + = ']'
36 + %br/
37 +
@@ -0,0 +1,11
1 + %h1 Editing user
2 +
3 + = form_tag :action => 'update', :id => @user do
4 + = error_messages_for 'user'
5 + = render partial: "form"
6 + = submit_tag "Edit"
7 +
8 +
9 + = link_to 'Show', :action => 'show', :id => @user
10 + |
11 + = link_to 'Back', :action => 'list'
@@ -0,0 +1,59
1 + - content_for :header do
2 + = javascript_include_tag 'local_jquery'
3 + = stylesheet_link_tag 'tablesorter-theme.cafe'
4 +
5 + %script{:type=>"text/javascript"}
6 + $(function () {
7 + $('#since_datetime').datetimepicker({ showButtonPanel: true, dateFormat: "yy-mm-dd", controlType: "slider"} );
8 + $('#until_datetime').datetimepicker({ showButtonPanel: true, dateFormat: "yy-mm-dd", controlType: "slider"} );
9 + $('#my_table').tablesorter({widgets: ['zebra']});
10 + });
11 +
12 + %h1 User grading results
13 + %h2= params[:action] == 'user_stat' ? "Show scores from latest submission" : "Show max scores in submission range"
14 +
15 +
16 + - if @problem and @problem.errors
17 + =error_messages_for 'problem'
18 +
19 + = render partial: 'submission_range'
20 +
21 + - if params[:action] == 'user_stat'
22 + %h3 Latest score
23 + = link_to '[download csv with all problems]', controller: :user_admin, action: :user_stat, commit: 'download csv'
24 + - else
25 + %h3 Max score
26 + = link_to '[Show only latest submissions]', controller: :user_admin, action: :user_stat
27 + = link_to '[download csv with all problems]', controller: :user_admin, action: :user_stat_max, commit: 'download csv'
28 +
29 + %table.tablesorter-cafe#my_table
30 + %thead
31 + %tr
32 + %th User
33 + %th Name
34 + %th Activated?
35 + %th Logged in
36 + %th Contest(s)
37 + %th Remark
38 + - @problems.each do |p|
39 + %th= p.name
40 + %th Total
41 + %th Passed
42 + %tbody
43 + - @scorearray.each do |sc|
44 + %tr{class: cycle('info-even','info-odd')}
45 + - total,num_passed = 0,0
46 + - sc.each_index do |i|
47 + - if i == 0
48 + %td= link_to sc[i].login, controller: 'users', action: 'profile', id: sc[i]
49 + %td= sc[i].full_name
50 + %td= sc[i].activated
51 + %td= sc[i].try(:contest_stat).try(:started_at)!=nil ? 'yes' : 'no'
52 + %td= sc[i].contests.collect {|c| c.name}.join(', ')
53 + %td= sc[i].remark
54 + - else
55 + %td= sc[i][0]
56 + - total += sc[i][0]
57 + - num_passed += 1 if sc[i][1]
58 + %td= total
59 + %td= num_passed
@@ -0,0 +1,66
1 + - content_for :header do
2 + = javascript_include_tag 'local_jquery'
3 +
4 + :javascript
5 + $(function () {
6 + $('#submission_table').tablesorter({widgets: ['zebra']});
7 + });
8 +
9 + :css
10 + .fix-width {
11 + font-family: Droid Sans Mono,Consolas, monospace, mono, Courier New, Courier;
12 + }
13 +
14 + %h1= @user.full_name
15 +
16 + <b>Login:</b> #{@user.login} <br/>
17 + <b>Full name:</b> #{@user.full_name} <br />
18 +
19 +
20 + %h2 Problem Stat
21 + %table.info
22 + %thead
23 + %tr.info-head
24 + %th Stat
25 + %th Value
26 + %tbody
27 + %tr{class: cycle('info-even','info-odd')}
28 + %td.info_param Submissions
29 + %td= @summary[:count]
30 + %tr{class: cycle('info-even','info-odd')}
31 + %td.info_param Solved/Attempted Problem
32 + %td #{@summary[:solve]}/#{@summary[:attempt]} (#{(@summary[:solve]*100.0/@summary[:attempt]).round(1)}%)
33 +
34 + %h2 Submission History
35 +
36 + =render partial: 'application/bar_graph', locals: {histogram: @histogram, param: {bar_width: 7}}
37 +
38 +
39 + %table.tablesorter-cafe#submission_table
40 + %thead
41 + %tr
42 + %th ID
43 + %th Problem code
44 + %th Problem full name
45 + %th Language
46 + %th Submitted at
47 + %th Result
48 + %th Score
49 + - if session[:admin]
50 + %th IP
51 + %tbody
52 + - @submission.each do |s|
53 + - next unless s.problem
54 + %tr
55 + %td= link_to "#{s.id}", controller: "graders", action: "submission", id: s.id
56 + %td= link_to s.problem.name, controller: "problems", action: "stat", id: s.problem
57 + %td= s.problem.full_name
58 + %td= s.language.pretty_name
59 + %td #{s.submitted_at.strftime('%Y-%m-%d %H:%M')} (#{time_ago_in_words(s.submitted_at)} ago)
60 + %td.fix-width= s.grader_comment
61 + %td= (s.points*100)/s.problem.full_score
62 + - if session[:admin]
63 + %td= s.ip_address
64 +
65 +
66 +
@@ -0,0 +1,7
1 + class AddMoreDetailToSubmission < ActiveRecord::Migration
2 + def change
3 + add_column :submissions, :max_runtime, :float
4 + add_column :submissions, :peak_memory, :integer
5 + add_column :submissions, :effective_code_length, :integer
6 + end
7 + end
@@ -0,0 +1,10
1 + class CreateLogins < ActiveRecord::Migration
2 + def change
3 + create_table :logins do |t|
4 + t.string :user_id
5 + t.string :ip_address
6 +
7 + t.timestamps
8 + end
9 + end
10 + end
@@ -0,0 +1,5
1 + class AddIpToSubmissions < ActiveRecord::Migration
2 + def change
3 + add_column :submissions, :ip_address, :string
4 + end
5 + end
@@ -0,0 +1,6
1 + class AddMoreToUsers < ActiveRecord::Migration
2 + def change
3 + add_column :users, :enabled, :boolean, default: 1
4 + add_column :users, :remark, :string
5 + end
6 + end
@@ -0,0 +1,5
1 + require 'spec_helper'
2 +
3 + describe Login do
4 + pending "add some examples to (or delete) #{__FILE__}"
5 + end
@@ -8,12 +8,19
8 /.bundle
8 /.bundle
9
9
10 # Ignore the default SQLite database.
10 # Ignore the default SQLite database.
11 /db/*.sqlite3
11 /db/*.sqlite3
12
12
13 # Ignore all logfiles and tempfiles.
13 # Ignore all logfiles and tempfiles.
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
@@ -26,24 +26,37
26
26
27 # To use Jbuilder templates for JSON
27 # To use Jbuilder templates for JSON
28 # gem 'jbuilder'
28 # gem 'jbuilder'
29
29
30 # Use unicorn as the app server
30 # Use unicorn as the app server
31 # gem 'unicorn'
31 # gem 'unicorn'
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'
44 gem 'dynamic_form'
57 gem 'dynamic_form'
45 gem 'in_place_editing'
58 gem 'in_place_editing'
46 gem 'verification', :git => 'https://github.com/sikachu/verification.git'
59 gem 'verification', :git => 'https://github.com/sikachu/verification.git'
47
60
48 group :test, :development do
61 group :test, :development do
49 gem 'rspec-rails', '~> 2.99.0'
62 gem 'rspec-rails', '~> 2.99.0'
@@ -28,44 +28,63
28 activerecord (3.2.21)
28 activerecord (3.2.21)
29 activemodel (= 3.2.21)
29 activemodel (= 3.2.21)
30 activesupport (= 3.2.21)
30 activesupport (= 3.2.21)
31 arel (~> 3.0.2)
31 arel (~> 3.0.2)
32 tzinfo (~> 0.3.29)
32 tzinfo (~> 0.3.29)
33 activeresource (3.2.21)
33 activeresource (3.2.21)
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
46 execjs
49 execjs
47 coffee-script-source (1.8.0)
50 coffee-script-source (1.8.0)
48 diff-lcs (1.2.5)
51 diff-lcs (1.2.5)
49 dynamic_form (1.1.4)
52 dynamic_form (1.1.4)
50 erubis (2.7.0)
53 erubis (2.7.0)
51 execjs (2.2.2)
54 execjs (2.2.2)
52 haml (4.0.6)
55 haml (4.0.6)
53 tilt
56 tilt
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)
66 power_assert (0.2.2)
85 power_assert (0.2.2)
67 prototype-rails (3.2.1)
86 prototype-rails (3.2.1)
68 rails (~> 3.2)
87 rails (~> 3.2)
69 rack (1.4.5)
88 rack (1.4.5)
70 rack-cache (1.2)
89 rack-cache (1.2)
71 rack (>= 0.4)
90 rack (>= 0.4)
@@ -83,25 +102,26
83 railties (= 3.2.21)
102 railties (= 3.2.21)
84 railties (3.2.21)
103 railties (3.2.21)
85 actionpack (= 3.2.21)
104 actionpack (= 3.2.21)
86 activesupport (= 3.2.21)
105 activesupport (= 3.2.21)
87 rack-ssl (~> 1.3.2)
106 rack-ssl (~> 1.3.2)
88 rake (>= 0.8.7)
107 rake (>= 0.8.7)
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)
102 actionpack (>= 3.0)
122 actionpack (>= 3.0)
103 activemodel (>= 3.0)
123 activemodel (>= 3.0)
104 activesupport (>= 3.0)
124 activesupport (>= 3.0)
105 railties (>= 3.0)
125 railties (>= 3.0)
106 rspec-collection_matchers
126 rspec-collection_matchers
107 rspec-core (~> 2.99.0)
127 rspec-core (~> 2.99.0)
@@ -125,27 +145,34
125 polyglot
145 polyglot
126 polyglot (>= 0.3.1)
146 polyglot (>= 0.3.1)
127 tzinfo (0.3.42)
147 tzinfo (0.3.42)
128 uglifier (2.6.0)
148 uglifier (2.6.0)
129 execjs (>= 0.3.0)
149 execjs (>= 0.3.0)
130 json (>= 1.8.0)
150 json (>= 1.8.0)
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 mail
166 mail
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,12 +1,21
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
7
16
8
17
9 input
18 input
10 font-family: Tahoma, "sans-serif"
19 font-family: Tahoma, "sans-serif"
11
20
12
21
@@ -281,13 +290,13
281 line-height: 2em
290 line-height: 2em
282
291
283 &.registration-desc, &.test-desc
292 &.registration-desc, &.test-desc
284 border: 1px dotted gray
293 border: 1px dotted gray
285 background: #f5f5f5
294 background: #f5f5f5
286 padding: 5px
295 padding: 5px
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
@@ -1,37 +1,46
1 class ApplicationController < ActionController::Base
1 class ApplicationController < ActionController::Base
2 protect_from_forgery
2 protect_from_forgery
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 - redirect_to :controller => 'main', :action => 'login' unless user.admin?
9 + unless user.admin?
10 + flash[:notice] = 'You are not authorized to view the page you requested'
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) }
16 flash[:notice] = 'You are not authorized to view the page you requested'
21 flash[:notice] = 'You are not authorized to view the page you requested'
17 redirect_to :controller => 'main', :action => 'login'
22 redirect_to :controller => 'main', :action => 'login'
18 return false
23 return false
19 end
24 end
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]
32 user = User.find(session[:user_id])
41 user = User.find(session[:user_id])
33 if user==nil or (not user.admin?)
42 if user==nil or (not user.admin?)
34 flash[:notice] = 'You cannot log in at this time'
43 flash[:notice] = 'You cannot log in at this time'
35 redirect_to :controller => 'main', :action => 'login'
44 redirect_to :controller => 'main', :action => 'login'
36 return false
45 return false
37 end
46 end
@@ -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) }
24 + end
25 + end
26 + end
27 +
20 end
28 end
@@ -1,37 +1,47
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'],
10 :redirect_to => {:action => 'index'}
19 :redirect_to => {:action => 'index'}
11
20
12 def index
21 def index
13 redirect_to :action => 'list'
22 redirect_to :action => 'list'
14 end
23 end
15
24
16 def list
25 def list
17 @grader_processes = GraderProcess.find_running_graders
26 @grader_processes = GraderProcess.find_running_graders
18 @stalled_processes = GraderProcess.find_stalled_process
27 @stalled_processes = GraderProcess.find_stalled_process
19
28
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'
32 end
42 end
33
43
34 def clear_terminated
44 def clear_terminated
35 GraderProcess.find_terminated_graders.each do |p|
45 GraderProcess.find_terminated_graders.each do |p|
36 p.destroy
46 p.destroy
37 end
47 end
@@ -54,24 +64,37
54 end
64 end
55
65
56 def test_request
66 def test_request
57 @test_request = TestRequest.find(params[:id])
67 @test_request = TestRequest.find(params[:id])
58 end
68 end
59
69
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])
72 GraderScript.stop_grader(grader_proc.pid)
95 GraderScript.stop_grader(grader_proc.pid)
73 flash[:notice] = 'Grader stopped. It may not disappear now, but it should disappear shortly.'
96 flash[:notice] = 'Grader stopped. It may not disappear now, but it should disappear shortly.'
74 redirect_to :action => 'list'
97 redirect_to :action => 'list'
75 end
98 end
76
99
77 def stop_all
100 def stop_all
@@ -13,24 +13,27
13
13
14 # clear forced logout flag for multicontests contest change
14 # clear forced logout flag for multicontests contest change
15 if GraderConfiguration.multicontests?
15 if GraderConfiguration.multicontests?
16 contest_stat = user.contest_stat
16 contest_stat = user.contest_stat
17 if contest_stat.respond_to? :forced_logout
17 if contest_stat.respond_to? :forced_logout
18 if contest_stat.forced_logout
18 if contest_stat.forced_logout
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
31
34
32 def site_login
35 def site_login
33 begin
36 begin
34 site = Site.find(params[:login][:site_id])
37 site = Site.find(params[:login][:site_id])
35 rescue ActiveRecord::RecordNotFound
38 rescue ActiveRecord::RecordNotFound
36 site = nil
39 site = nil
@@ -54,28 +54,30
54 def help
54 def help
55 @user = User.find(session[:user_id])
55 @user = User.find(session[:user_id])
56 end
56 end
57
57
58 def submit
58 def submit
59 user = User.find(session[:user_id])
59 user = User.find(session[:user_id])
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
76
78
77 if @submission.valid?
79 if @submission.valid?
78 if @submission.save == false
80 if @submission.save == false
79 flash[:notice] = 'Error saving your submission'
81 flash[:notice] = 'Error saving your submission'
80 elsif Task.create(:submission_id => @submission.id,
82 elsif Task.create(:submission_id => @submission.id,
81 :status => Task::STATUS_INQUEUE) == false
83 :status => Task::STATUS_INQUEUE) == false
@@ -3,25 +3,25
3 before_filter :authenticate
3 before_filter :authenticate
4
4
5 verify :method => :post, :only => ['create'],
5 verify :method => :post, :only => ['create'],
6 :redirect_to => { :action => 'list' }
6 :redirect_to => { :action => 'list' }
7
7
8 before_filter :admin_authorization, :only => ['console','show',
8 before_filter :admin_authorization, :only => ['console','show',
9 'reply','hide','list_all']
9 'reply','hide','list_all']
10
10
11 def list
11 def list
12 @user = User.find(session[:user_id])
12 @user = User.find(session[:user_id])
13 @messages = Message.find_all_sent_by_user(@user)
13 @messages = Message.find_all_sent_by_user(@user)
14 end
14 end
15 -
15 +
16 def console
16 def console
17 @user = User.find(session[:user_id])
17 @user = User.find(session[:user_id])
18 @messages = Message.find_all_system_unreplied_messages
18 @messages = Message.find_all_system_unreplied_messages
19 end
19 end
20
20
21 def show
21 def show
22 @message = Message.find(params[:id])
22 @message = Message.find(params[:id])
23 end
23 end
24
24
25 def list_all
25 def list_all
26 @user = User.find(session[:user_id])
26 @user = User.find(session[:user_id])
27 @messages = Message.where(receiver_id: nil).order(:created_at)
27 @messages = Message.where(receiver_id: nil).order(:created_at)
@@ -141,40 +141,58
141
141
142 def turn_all_on
142 def turn_all_on
143 Problem.find(:all,
143 Problem.find(:all,
144 :conditions => "available = 0").each do |problem|
144 :conditions => "available = 0").each do |problem|
145 problem.available = true
145 problem.available = true
146 problem.save
146 problem.save
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 - if !@problem.available
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 - else params.has_key? 'add_to_contest'
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?
175 end
193 end
176
194
177 def do_import
195 def do_import
178 old_problem = Problem.find_by_name(params[:name])
196 old_problem = Problem.find_by_name(params[:name])
179 if !allow_test_pair_import? and params.has_key? :import_to_db
197 if !allow_test_pair_import? and params.has_key? :import_to_db
180 params.delete :import_to_db
198 params.delete :import_to_db
@@ -225,24 +243,35
225 end
243 end
226
244
227 def add_to_contest
245 def add_to_contest
228 problems = get_problems_from_params
246 problems = get_problems_from_params
229 contest = Contest.find(params[:contest][:id])
247 contest = Contest.find(params[:contest][:id])
230 if contest!=nil and contest.enabled
248 if contest!=nil and contest.enabled
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
275 + end
276 +
248 end
277 end
@@ -1,12 +1,14
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
7 # GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html)
9 # GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html)
8 verify :method => :post, :only => [ :destroy,
10 verify :method => :post, :only => [ :destroy,
9 :create, :create_from_list,
11 :create, :create_from_list,
10 :update,
12 :update,
11 :manage_contest,
13 :manage_contest,
12 :bulk_mail
14 :bulk_mail
@@ -72,29 +74,35
72 full_name = items[1]
74 full_name = items[1]
73
75
74 added_random_password = false
76 added_random_password = false
75 if items.length>=3
77 if items.length>=3
76 password = items[2].chomp(" ")
78 password = items[2].chomp(" ")
77 user_alias = (items.length>=4) ? items[3] : login
79 user_alias = (items.length>=4) ? items[3] : login
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
84 - user = User.new({:login => login,
86 + user = User.find_by_login(login)
85 - :full_name => full_name,
87 + if (user)
86 - :password => password,
88 + user.full_name = full_name
87 - :password_confirmation => password,
89 + user.password = password
88 - :alias => user_alias})
90 + else
91 + user = User.new({:login => login,
92 + :full_name => full_name,
93 + :password => password,
94 + :password_confirmation => password,
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
95 note << login
103 note << login
96 end
104 end
97 end
105 end
98 end
106 end
99 flash[:notice] = 'User(s) ' + note.join(', ') +
107 flash[:notice] = 'User(s) ' + note.join(', ') +
100 ' were successfully created. ' +
108 ' were successfully created. ' +
@@ -113,61 +121,76
113 redirect_to :action => 'show', :id => @user
121 redirect_to :action => 'show', :id => @user
114 else
122 else
115 render :action => 'edit'
123 render :action => 'edit'
116 end
124 end
117 end
125 end
118
126
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
125 - @problems = Problem.find_available_problems
133 + if params[:commit] == 'download csv'
134 + @problems = Problem.all
135 + else
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|
132 sub = Submission.find_last_by_user_and_problem(u.id,p.id)
144 sub = Submission.find_last_by_user_and_problem(u.id,p.id)
133 if (sub!=nil) and (sub.points!=nil)
145 if (sub!=nil) and (sub.points!=nil)
134 ustat << [(sub.points.to_f*100/p.full_score).round, (sub.points>=p.full_score)]
146 ustat << [(sub.points.to_f*100/p.full_score).round, (sub.points>=p.full_score)]
135 else
147 else
136 ustat << [0,false]
148 ustat << [0,false]
137 end
149 end
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
144 - @problems = Problem.find_available_problems
156 + if params[:commit] == 'download csv'
157 + @problems = Problem.all
158 + else
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|
151 ustat = Array.new
167 ustat = Array.new
152 ustat[0] = u
168 ustat[0] = u
153 @problems.each do |p|
169 @problems.each do |p|
154 max_points = 0
170 max_points = 0
155 Submission.find_in_range_by_user_and_problem(u.id,p.id,since_id,until_id).each do |sub|
171 Submission.find_in_range_by_user_and_problem(u.id,p.id,since_id,until_id).each do |sub|
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
168 end
191 end
169 import_from_file(params[:file])
192 import_from_file(params[:file])
170 end
193 end
171
194
172 def random_all_passwords
195 def random_all_passwords
173 users = User.find(:all)
196 users = User.find(:all)
@@ -464,13 +487,44
464 if id!='none'
487 if id!='none'
465 @contest = Contest.find(id)
488 @contest = Contest.find(id)
466 else
489 else
467 @contest = nil
490 @contest = nil
468 end
491 end
469 if @contest
492 if @contest
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]
522 + end
523 + end
524 + row << total
525 + row << num_passed
526 + csv << row
527 + end
528 + end
529 + end
476 end
530 end
@@ -5,24 +5,25
5 include MailHelperMethods
5 include MailHelperMethods
6
6
7 before_filter :authenticate, :except => [:new,
7 before_filter :authenticate, :except => [:new,
8 :register,
8 :register,
9 :confirm,
9 :confirm,
10 :forget,
10 :forget,
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
23
24
24 def index
25 def index
25 if !GraderConfiguration['system.user_setting_enabled']
26 if !GraderConfiguration['system.user_setting_enabled']
26 redirect_to :controller => 'main', :action => 'list'
27 redirect_to :controller => 'main', :action => 'list'
27 else
28 else
28 @user = User.find(session[:user_id])
29 @user = User.find(session[:user_id])
@@ -99,24 +100,48
99 else
100 else
100 user.password = user.password_confirmation = User.random_password
101 user.password = user.password_confirmation = User.random_password
101 user.save
102 user.save
102 send_new_password_email(user)
103 send_new_password_email(user)
103 flash[:notice] = 'New password has been mailed to you.'
104 flash[:notice] = 'New password has been mailed to you.'
104 end
105 end
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
117 end
142 end
118
143
119 def send_confirmation_email(user)
144 def send_confirmation_email(user)
120 contest_name = GraderConfiguration['contest.name']
145 contest_name = GraderConfiguration['contest.name']
121 activation_url = url_for(:action => 'confirm',
146 activation_url = url_for(:action => 'confirm',
122 :login => user.login,
147 :login => user.login,
@@ -143,14 +168,28
143 mail_body = t('registration.password_retrieval.email_body', {
168 mail_body = t('registration.password_retrieval.email_body', {
144 :full_name => user.full_name,
169 :full_name => user.full_name,
145 :contest_name => contest_name,
170 :contest_name => contest_name,
146 :login => user.login,
171 :login => user.login,
147 :password => user.password,
172 :password => user.password,
148 :admin_email => GraderConfiguration['system.admin_email']
173 :admin_email => GraderConfiguration['system.admin_email']
149 })
174 })
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
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
193 + end
155
194
156 end
195 end
@@ -4,40 +4,45
4 def user_header
4 def user_header
5 menu_items = ''
5 menu_items = ''
6 user = User.find(session[:user_id])
6 user = User.find(session[:user_id])
7
7
8 if (user!=nil) and (session[:admin])
8 if (user!=nil) and (session[:admin])
9 # admin menu
9 # admin menu
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
22
23
23 # main page
24 # main page
24 append_to menu_items, "[#{I18n.t 'menu.main'}]", 'main', 'list'
25 append_to menu_items, "[#{I18n.t 'menu.main'}]", 'main', 'list'
25 append_to menu_items, "[#{I18n.t 'menu.messages'}]", 'messages', 'list'
26 append_to menu_items, "[#{I18n.t 'menu.messages'}]", 'messages', 'list'
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'
38
43
39 menu_items.html_safe
44 menu_items.html_safe
40 end
45 end
41
46
42 def append_to(option,label, controller, action)
47 def append_to(option,label, controller, action)
43 option << ' ' if option!=''
48 option << ' ' if option!=''
@@ -12,25 +12,25
12 # :foreign_key => "replying_message_id"
12 # :foreign_key => "replying_message_id"
13 #}
13 #}
14 #
14 #
15
15
16 attr_accessor :replied_messages
16 attr_accessor :replied_messages
17
17
18 def self.find_all_sent_by_user(user)
18 def self.find_all_sent_by_user(user)
19 messages = user.messages
19 messages = user.messages
20 replied_messages = user.replied_messages
20 replied_messages = user.replied_messages
21 Message.build_replying_message_hierarchy messages, replied_messages
21 Message.build_replying_message_hierarchy messages, replied_messages
22 return messages
22 return messages
23 end
23 end
24 -
24 +
25 def self.find_all_system_unreplied_messages
25 def self.find_all_system_unreplied_messages
26 self.find(:all,
26 self.find(:all,
27 :conditions => 'ISNULL(receiver_id) ' +
27 :conditions => 'ISNULL(receiver_id) ' +
28 'AND (ISNULL(replied) OR replied=0)',
28 'AND (ISNULL(replied) OR replied=0)',
29 :order => 'created_at')
29 :order => 'created_at')
30 end
30 end
31
31
32 def self.build_replying_message_hierarchy(*args)
32 def self.build_replying_message_hierarchy(*args)
33 # manually build replies hierarchy (to improve efficiency)
33 # manually build replies hierarchy (to improve efficiency)
34 all_messages = {}
34 all_messages = {}
35
35
36 args.each do |collection|
36 args.each do |collection|
@@ -5,25 +5,25
5 has_many :test_pairs, :dependent => :delete_all
5 has_many :test_pairs, :dependent => :delete_all
6
6
7 validates_presence_of :name
7 validates_presence_of :name
8 validates_format_of :name, :with => /^\w+$/
8 validates_format_of :name, :with => /^\w+$/
9 validates_presence_of :full_name
9 validates_presence_of :full_name
10
10
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)
24
24
25 if !problem.errors.empty?
25 if !problem.errors.empty?
26 return problem, 'Error importing'
26 return problem, 'Error importing'
27 end
27 end
28
28
29 problem.full_score = 100
29 problem.full_score = 100
@@ -34,34 +34,42
34
34
35 if not problem.save
35 if not problem.save
36 return problem, 'Error importing'
36 return problem, 'Error importing'
37 end
37 end
38
38
39 import_to_db = params.has_key? :import_to_db
39 import_to_db = params.has_key? :import_to_db
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
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
56
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
63 result ||= default
71 result ||= default
64 end
72 end
65
73
66 def self.to_f_or_default(st, default)
74 def self.to_f_or_default(st, default)
67 if st!=''
75 if st!=''
@@ -81,34 +89,40
81 end
89 end
82
90
83 if memory_limit==0 and params[:memory_limit]!='0'
91 if memory_limit==0 and params[:memory_limit]!='0'
84 problem.errors.add(:base,'Memory limit format errors.')
92 problem.errors.add(:base,'Memory limit format errors.')
85 elsif memory_limit<=0 or memory_limit >512
93 elsif memory_limit<=0 or memory_limit >512
86 problem.errors.add(:base,'Memory limit out of range.')
94 problem.errors.add(:base,'Memory limit out of range.')
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
99 problem.name = params[:name]
112 problem.name = params[:name]
100 if params[:full_name]!=''
113 if params[:full_name]!=''
101 problem.full_name = params[:full_name]
114 problem.full_name = params[:full_name]
102 else
115 else
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
@@ -16,25 +16,25
16 before_save :assign_latest_number_if_new_recond
16 before_save :assign_latest_number_if_new_recond
17
17
18 def self.find_last_by_user_and_problem(user_id, problem_id)
18 def self.find_last_by_user_and_problem(user_id, problem_id)
19 last_sub = find(:first,
19 last_sub = find(:first,
20 :conditions => {:user_id => user_id,
20 :conditions => {:user_id => user_id,
21 :problem_id => problem_id},
21 :problem_id => problem_id},
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")
35 end
35 end
36
36
37 def self.find_in_range_by_user_and_problem(user_id, problem_id,since_id,until_id)
37 def self.find_in_range_by_user_and_problem(user_id, problem_id,since_id,until_id)
38 records = Submission.where(problem_id: problem_id,user_id: user_id)
38 records = Submission.where(problem_id: problem_id,user_id: user_id)
39 records = records.where('id >= ?',since_id) if since_id > 0
39 records = records.where('id >= ?',since_id) if since_id > 0
40 records = records.where('id <= ?',until_id) if until_id > 0
40 records = records.where('id <= ?',until_id) if until_id > 0
@@ -1,13 +1,17
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"
8
12
9 has_many :messages,
13 has_many :messages,
10 :class_name => "Message",
14 :class_name => "Message",
11 :foreign_key => "sender_id",
15 :foreign_key => "sender_id",
12 :order => 'created_at DESC'
16 :order => 'created_at DESC'
13
17
@@ -52,25 +56,27
52 attr_accessor :password
56 attr_accessor :password
53
57
54 before_save :encrypt_new_password
58 before_save :encrypt_new_password
55 before_save :assign_default_site
59 before_save :assign_default_site
56 before_save :assign_default_contest
60 before_save :assign_default_contest
57
61
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
71 false
77 false
72 end
78 end
73 end
79 end
74
80
75 def admin?
81 def admin?
76 self.roles.detect {|r| r.name == 'admin' }
82 self.roles.detect {|r| r.name == 'admin' }
@@ -1,29 +1,32
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
7 %th Value
10 %th Value
8 %th Description
11 %th Description
9 - @configurations.each do |conf|
12 - @configurations.each do |conf|
10 - @grader_configuration = conf
13 - @grader_configuration = conf
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_editor_field :grader_configuration, :value, {}, :rows=>1
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/
24 If you have one mongrel process running, you can
27 If you have one mongrel process running, you can
25 = link_to '[click]', :action => 'reload'
28 = link_to '[click]', :action => 'reload'
26 here to reload.
29 here to reload.
27 %br/
30 %br/
28 If you have more than one process running, you should restart
31 If you have more than one process running, you should restart
29 them manually.
32 them manually.
@@ -1,51 +1,73
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/
9
10
10 .submitbox
11 .submitbox
11 .item
12 .item
12 Grader control:
13 Grader control:
13 .item
14 .item
14 = form_for :clear, :url => {:action => 'start_grading'} do |f|
15 = form_for :clear, :url => {:action => 'start_grading'} do |f|
15 = submit_tag 'Start graders in grading env'
16 = submit_tag 'Start graders in grading env'
16 .item
17 .item
17 = form_for :clear, :url => {:action => 'start_exam'} do |f|
18 = form_for :clear, :url => {:action => 'start_exam'} do |f|
18 = submit_tag 'Start graders in exam env'
19 = submit_tag 'Start graders in exam env'
19 .item
20 .item
20 = form_for :clear, :url => {:action => 'stop_all'} do |f|
21 = form_for :clear, :url => {:action => 'stop_all'} do |f|
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
27 - - if @last_task
28 + %div{style: 'width:500px; float: left;'}
28 - Last task:
29 + - if @last_task
29 - = link_to "#{@last_task.id}", :action => 'view', :id => @last_task.id, :type => 'Task'
30 + Last task:
31 + = link_to "#{@last_task.id}", :action => 'view', :id => @last_task.id, :type => 'Task'
32 +
33 + %br/
34 +
35 + - if @last_test_request
36 + Last test_request:
37 + = link_to "#{@last_test_request.id}", :action => 'view', :id => @last_test_request.id, :type => 'TestRequest'
38 +
39 + %h2 Current graders
40 +
41 + = render :partial => 'grader_list', :locals => {:grader_list => @grader_processes}
42 +
43 + %h2 Stalled graders
44 +
45 + = render :partial => 'grader_list', :locals => {:grader_list => @stalled_processes}
46 +
47 + %h2 Terminated graders
30
48
31 - %br/
49 + = form_for :clear, :url => {:action => 'clear_terminated'} do |f|
50 + = submit_tag 'Clear data for terminated graders'
32
51
33 - - if @last_test_request
52 + = render :partial => 'grader_list', :locals => {:grader_list => @terminated_processes}
34 - Last test_request:
53 + %div{}
35 - = link_to "#{@last_test_request.id}", :action => 'view', :id => @last_test_request.id, :type => 'TestRequest'
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
36
72
37
73
38 - %h2 Current graders
39 -
40 - = render :partial => 'grader_list', :locals => {:grader_list => @grader_processes}
41 -
42 - %h2 Stalled graders
43 -
44 - = render :partial => 'grader_list', :locals => {:grader_list => @stalled_processes}
45 -
46 - %h2 Terminated graders
47 -
48 - = form_for :clear, :url => {:action => 'clear_terminated'} do |f|
49 - = submit_tag 'Clear data for terminated graders'
50 -
51 - = render :partial => 'grader_list', :locals => {:grader_list => @terminated_processes}
@@ -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}"
6 - %br/
7 - Problem:
8 - - if @submission.problem!=nil
9 - = "#{@submission.problem.full_name}"
10 - - else
11 - = "(n/a)"
12 - %br/
13 - = "Number: #{@submission.number}"
14 - %br/
15 - = "Submitted at: #{format_short_time(@submission.submitted_at)}"
16
15
17 - %b Source code (first 10kb)
16 + %table.info
18 - %div{:style => "border: 1px solid black; background: lightgrey"}
17 + %thead
19 - - if @submission.source
18 + %tr.info-head
20 - %pre
19 + %th Field
21 - =h truncate @submission.source, :length => 10240
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
28 + - else
29 + = "(n/a)"
30 + %tr{class: cycle('info-even','info-odd')}
31 + %td.field Problem:
32 + %td.value
33 + - if @submission.problem!=nil
34 + = link_to "(#{@submission.problem.name})", controller: "problems", action: "stat", id: @submission.problem
35 + = @submission.problem.full_name
36 + - else
37 + = "(n/a)"
38 + %tr{class: cycle('info-even','info-odd')}
39 + %td.field Tries:
40 + %td.value= @submission.number
41 + %tr{class: cycle('info-even','info-odd')}
42 + %td.field Submitted:
43 + %td.value #{time_ago_in_words(@submission.submitted_at)} ago (at #{@submission.submitted_at.to_formatted_s(:long)})
44 + %tr{class: cycle('info-even','info-odd')}
45 + %td.field Graded:
46 + %td.value #{time_ago_in_words(@submission.graded_at)} ago (at #{@submission.graded_at.to_formatted_s(:long)})
47 + %tr{class: cycle('info-even','info-odd')}
48 + %td.field Points:
49 + %td.value #{@submission.points}/#{@submission.problem.full_score}
50 + %tr{class: cycle('info-even','info-odd')}
51 + %td.field Comment:
52 + %td.value #{@submission.grader_comment}
53 + %tr{class: cycle('info-even','info-odd')}
54 + %td.field Runtime (s):
55 + %td.value #{@submission.max_runtime}
56 + %tr{class: cycle('info-even','info-odd')}
57 + %td.field Memory (kb):
58 + %td.value #{@submission.peak_memory}
59 + - if session[:admin]
60 + %tr{class: cycle('info-even','info-odd')}
61 + %td.field IP:
62 + %td.value #{@submission.ip_address}
22
63
64 + %h2 Source code
65 + //%div.highlight{:style => "border: 1px solid black;"}
66 + =@formatted_code.html_safe
67 +
@@ -1,20 +1,22
1 <!DOCTYPE html>
1 <!DOCTYPE html>
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>
15
17
16 <%= content_tag(:p,flash[:notice],:style => "color:green") if flash[:notice]!=nil %>
18 <%= content_tag(:p,flash[:notice],:style => "color:green") if flash[:notice]!=nil %>
17
19
18 <%= yield %>
20 <%= yield %>
19
21
20 </body>
22 </body>
@@ -1,18 +1,21
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.full_name} (#{problem.name})" %>
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>
13 <%= render :partial => 'submission_short',
16 <%= render :partial => 'submission_short',
14 :locals => {
17 :locals => {
15 :submission => @prob_submissions[problem.id][:submission],
18 :submission => @prob_submissions[problem.id][:submission],
16 :problem_name => problem.name }%>
19 :problem_name => problem.name }%>
17 </td>
20 </td>
18 </tr>
21 </tr>
@@ -1,18 +1,24
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']
13 = " ["
19 = " ["
14 %tt
20 %tt
15 = submission.grader_comment
21 = submission.grader_comment
16 = "]"
22 = "]"
17 %td.info
23 %td.info
18 = render :partial => 'compiler_message', :locals => {:compiler_message => submission.compiler_message }
24 = render :partial => 'compiler_message', :locals => {:compiler_message => submission.compiler_message }
@@ -16,36 +16,38
16
16
17
17
18 %hr/
18 %hr/
19
19
20 - if (GraderConfiguration.contest_mode?) and (@user.site!=nil) and (@user.site.started!=true)
20 - if (GraderConfiguration.contest_mode?) and (@user.site!=nil) and (@user.site.started!=true)
21 %p=t 'main.start_soon'
21 %p=t 'main.start_soon'
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/
47
49
48 %script{:type => 'text/javascript'}
50 %script{:type => 'text/javascript'}
49 = "Announcement.refreshUrl = '#{url_for :controller => 'main', :action => 'announcements'}';"
51 = "Announcement.refreshUrl = '#{url_for :controller => 'main', :action => 'announcements'}';"
50 Announcement.registerRefreshEventTimer();
52 Announcement.registerRefreshEventTimer();
51
53
@@ -4,21 +4,22
4 Task List
4 Task List
5 %br/
5 %br/
6 - @problems.each do |problem|
6 - @problems.each do |problem|
7 = link_to problem.name, :action => 'submission', :id => problem.name
7 = link_to problem.name, :action => 'submission', :id => problem.name
8
8
9 - if @problem!=nil
9 - if @problem!=nil
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
22 = render :partial => 'submission', :collection => @submissions
23 = render :partial => 'submission', :collection => @submissions
23 - else
24 - else
24 No submission
25 No submission
@@ -23,24 +23,37
23 %td Testdata file:
23 %td Testdata file:
24 %td= file_field_tag 'file'
24 %td= file_field_tag 'file'
25 %tr
25 %tr
26 %td
26 %td
27 %td
27 %td
28 %span{:class => 'help'}
28 %span{:class => 'help'}
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)
41 %tr
54 %tr
42 %td Time limit:
55 %td Time limit:
43 %td
56 %td
44 = text_field_tag 'time_limit'
57 = text_field_tag 'time_limit'
45 %span{:class => 'help'} In seconds. Leave blank to use 1 sec.
58 %span{:class => 'help'} In seconds. Leave blank to use 1 sec.
46 %tr
59 %tr
@@ -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 &nbsp;&nbsp;&nbsp;
50 &nbsp;&nbsp;&nbsp;
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 }])"
@@ -9,14 +9,17
9
9
10 <p><label for="password">Password</label><br/>
10 <p><label for="password">Password</label><br/>
11 <%= password_field 'user', 'password' %></p>
11 <%= password_field 'user', 'password' %></p>
12
12
13 <p><label for="password_confirmation">Password (confirm)</label><br/>
13 <p><label for="password_confirmation">Password (confirm)</label><br/>
14 <%= password_field 'user', 'password_confirmation' %></p>
14 <%= password_field 'user', 'password_confirmation' %></p>
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
@@ -59,26 +59,27
59 <% for column in User.content_columns %>
59 <% for column in User.content_columns %>
60 <% if !@hidden_columns.index(column.name) %>
60 <% if !@hidden_columns.index(column.name) %>
61 <th><%= column.human_name %></th>
61 <th><%= column.human_name %></th>
62 <% end %>
62 <% end %>
63 <% end %>
63 <% end %>
64 <th></th>
64 <th></th>
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>
79 </tr>
80 </tr>
80 <% end %>
81 <% end %>
81 </table>
82 </table>
82
83
83 <br />
84 <br />
84
85
@@ -51,14 +51,15
51 # This will create an empty whitelist of attributes available for mass-assignment for all models
51 # This will create an empty whitelist of attributes available for mass-assignment for all models
52 # in your app. As such, your models will need to explicitly whitelist or blacklist accessible
52 # in your app. As such, your models will need to explicitly whitelist or blacklist accessible
53 # parameters by using an attr_accessible or attr_protected declaration.
53 # parameters by using an attr_accessible or attr_protected declaration.
54 config.active_record.whitelist_attributes = false
54 config.active_record.whitelist_attributes = false
55
55
56 # Enable the asset pipeline
56 # Enable the asset pipeline
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
@@ -11,24 +11,25
11
11
12 go_ahead_to: "Go ahead to "
12 go_ahead_to: "Go ahead to "
13 go_back_to: "Go back to "
13 go_back_to: "Go back to "
14 login_page: "login page"
14 login_page: "login page"
15 home_page: "home page"
15 home_page: "home page"
16
16
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"
29 remaining_time: "Time left: "
30 remaining_time: "Time left: "
30 contest_not_started: "The contest has not started."
31 contest_not_started: "The contest has not started."
31
32
32 login:
33 login:
33 message: 'Please login to see the problem list'
34 message: 'Please login to see the problem list'
34 login_submit: 'Login'
35 login_submit: 'Login'
@@ -11,24 +11,25
11
11
12 go_ahead_to: "ไปยัง"
12 go_ahead_to: "ไปยัง"
13 go_back_to: "กลับไปยัง"
13 go_back_to: "กลับไปยัง"
14 login_page: "หน้าเข้าใช้ระบบ"
14 login_page: "หน้าเข้าใช้ระบบ"
15 home_page: "หน้าแรก"
15 home_page: "หน้าแรก"
16
16
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: "เวลาปัจจุบันคือ"
29 remaining_time: "เหลือเวลาอีก"
30 remaining_time: "เหลือเวลาอีก"
30 contest_not_started: "ยังไม่เริ่มแข่งขัน"
31 contest_not_started: "ยังไม่เริ่มแข่งขัน"
31
32
32 login:
33 login:
33 message: 'กรุณา login เพื่อเข้าสู่ระบบ'
34 message: 'กรุณา login เพื่อเข้าสู่ระบบ'
34 login_submit: 'เข้าใช้ระบบ'
35 login_submit: 'เข้าใช้ระบบ'
@@ -1,18 +1,22
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
13
17
14 # Sample of named route:
18 # Sample of named route:
15 # match 'products/:id/purchase' => 'catalog#purchase', :as => :purchase
19 # match 'products/:id/purchase' => 'catalog#purchase', :as => :purchase
16 # This route can be invoked with purchase_url(:id => product.id)
20 # This route can be invoked with purchase_url(:id => product.id)
17
21
18 # Sample resource route (maps HTTP verbs to controller actions automatically):
22 # Sample resource route (maps HTTP verbs to controller actions automatically):
@@ -2,130 +2,127
2 # This file is auto-generated from the current state of the database. Instead
2 # This file is auto-generated from the current state of the database. Instead
3 # of editing this file, please use the migrations feature of Active Record to
3 # of editing this file, please use the migrations feature of Active Record to
4 # incrementally modify your database, and then regenerate this schema definition.
4 # incrementally modify your database, and then regenerate this schema definition.
5 #
5 #
6 # Note that this schema.rb definition is the authoritative source for your
6 # Note that this schema.rb definition is the authoritative source for your
7 # database schema. If you need to create the application database on another
7 # database schema. If you need to create the application database on another
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 => 20121001033508) do
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"
47 end
39 end
48
40
49 create_table "contests_users", :id => false, :force => true do |t|
41 create_table "contests_users", :id => false, :force => true do |t|
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"
89
81
90 create_table "languages", :force => true do |t|
82 create_table "languages", :force => true do |t|
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 "name", :limit => 30
107 + t.string "name", :limit => 30
109 - t.string "full_name"
108 + t.string "full_name"
110 - t.integer "full_score"
109 + t.integer "full_score"
111 - t.date "date_added"
110 + t.date "date_added"
112 - t.boolean "available"
111 + t.boolean "available"
113 - t.string "url"
112 + t.string "url"
114 - t.integer "description_id"
113 + t.integer "description_id"
115 - t.boolean "test_allowed"
114 + t.boolean "test_allowed"
116 - t.boolean "output_only"
115 + t.boolean "output_only"
117 - t.integer "level", :default => 0
116 + t.string "description_filename"
118 - t.datetime "updated_at"
119 - 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"
125 t.string "action"
122 t.string "action"
126 end
123 end
127
124
128 create_table "rights_roles", :id => false, :force => true do |t|
125 create_table "rights_roles", :id => false, :force => true do |t|
129 t.integer "right_id"
126 t.integer "right_id"
130 t.integer "role_id"
127 t.integer "role_id"
131 end
128 end
@@ -147,127 +144,105
147 t.string "session_id"
144 t.string "session_id"
148 t.text "data"
145 t.text "data"
149 t.datetime "updated_at"
146 t.datetime "updated_at"
150 end
147 end
151
148
152 add_index "sessions", ["session_id"], :name => "index_sessions_on_session_id"
149 add_index "sessions", ["session_id"], :name => "index_sessions_on_session_id"
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"
180 t.datetime "submitted_at"
168 t.datetime "submitted_at"
181 t.datetime "compiled_at"
169 t.datetime "compiled_at"
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", :limit => 50
231 + t.string "login", :limit => 50
252 t.string "full_name"
232 t.string "full_name"
253 t.string "hashed_password"
233 t.string "hashed_password"
254 - t.string "salt", :limit => 5
234 + t.string "salt", :limit => 5
255 t.string "alias"
235 t.string "alias"
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", :default => false
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 "member2_full_name"
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
@@ -1,102 +1,116
1 CONFIGURATIONS =
1 CONFIGURATIONS =
2 [
2 [
3 {
3 {
4 :key => 'system.single_user_mode',
4 :key => 'system.single_user_mode',
5 :value_type => 'boolean',
5 :value_type => 'boolean',
6 :default_value => 'false',
6 :default_value => 'false',
7 :description => 'Only admins can log in to the system when running under single user mode.'
7 :description => 'Only admins can log in to the system when running under single user mode.'
8 },
8 },
9 -
9 +
10 {
10 {
11 :key => 'ui.front.title',
11 :key => 'ui.front.title',
12 :value_type => 'string',
12 :value_type => 'string',
13 :default_value => 'Grader'
13 :default_value => 'Grader'
14 },
14 },
15 -
15 +
16 {
16 {
17 :key => 'ui.front.welcome_message',
17 :key => 'ui.front.welcome_message',
18 :value_type => 'string',
18 :value_type => 'string',
19 :default_value => 'Welcome!'
19 :default_value => 'Welcome!'
20 },
20 },
21 -
21 +
22 {
22 {
23 :key => 'ui.show_score',
23 :key => 'ui.show_score',
24 :value_type => 'boolean',
24 :value_type => 'boolean',
25 :default_value => 'true'
25 :default_value => 'true'
26 },
26 },
27 -
27 +
28 {
28 {
29 :key => 'contest.time_limit',
29 :key => 'contest.time_limit',
30 :value_type => 'string',
30 :value_type => 'string',
31 :default_value => 'unlimited',
31 :default_value => 'unlimited',
32 :description => 'Time limit in format hh:mm, or "unlimited" for contests with no time limits. This config is CACHED. Restart the server before the change can take effect.'
32 :description => 'Time limit in format hh:mm, or "unlimited" for contests with no time limits. This config is CACHED. Restart the server before the change can take effect.'
33 },
33 },
34 -
34 +
35 {
35 {
36 :key => 'system.mode',
36 :key => 'system.mode',
37 :value_type => 'string',
37 :value_type => 'string',
38 :default_value => 'standard',
38 :default_value => 'standard',
39 :description => 'Current modes are "standard", "contest", "indv-contest", and "analysis".'
39 :description => 'Current modes are "standard", "contest", "indv-contest", and "analysis".'
40 },
40 },
41 -
41 +
42 {
42 {
43 :key => 'contest.name',
43 :key => 'contest.name',
44 :value_type => 'string',
44 :value_type => 'string',
45 :default_value => 'Grader',
45 :default_value => 'Grader',
46 :description => 'This name will be shown on the user header bar.'
46 :description => 'This name will be shown on the user header bar.'
47 },
47 },
48 -
48 +
49 {
49 {
50 :key => 'contest.multisites',
50 :key => 'contest.multisites',
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 },
61 },
62 -
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.'
68 + },
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 {
67 :key => 'system.online_registration.smtp',
74 :key => 'system.online_registration.smtp',
68 :value_type => 'string',
75 :value_type => 'string',
69 :default_value => 'smtp.somehost.com'
76 :default_value => 'smtp.somehost.com'
70 },
77 },
71 -
78 +
72 {
79 {
73 :key => 'system.online_registration.from',
80 :key => 'system.online_registration.from',
74 :value_type => 'string',
81 :value_type => 'string',
75 :default_value => 'your.email@address'
82 :default_value => 'your.email@address'
76 },
83 },
77 -
84 +
78 {
85 {
79 :key => 'system.admin_email',
86 :key => 'system.admin_email',
80 :value_type => 'string',
87 :value_type => 'string',
81 :default_value => 'admin@admin.email'
88 :default_value => 'admin@admin.email'
82 },
89 },
83 -
90 +
84 {
91 {
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',
97 :default_value => 'false'
111 :default_value => 'false'
98 },
112 },
99
113
100 {
114 {
101 :key => 'system.multicontests',
115 :key => 'system.multicontests',
102 :value_type => 'boolean',
116 :value_type => 'boolean',
@@ -106,25 +120,25
106 {
120 {
107 :key => 'contest.confirm_indv_contest_start',
121 :key => 'contest.confirm_indv_contest_start',
108 :value_type => 'boolean',
122 :value_type => 'boolean',
109 :default_value => 'false'
123 :default_value => 'false'
110 },
124 },
111
125
112 {
126 {
113 :key => 'contest.default_contest_name',
127 :key => 'contest.default_contest_name',
114 :value_type => 'string',
128 :value_type => 'string',
115 :default_value => 'none',
129 :default_value => 'none',
116 :description => "New user will be assigned to this contest automatically, if it exists. Set to 'none' if there is no default contest."
130 :description => "New user will be assigned to this contest automatically, if it exists. Set to 'none' if there is no default contest."
117 }
131 }
118 -
132 +
119 ]
133 ]
120
134
121
135
122 def create_configuration_key(key,
136 def create_configuration_key(key,
123 value_type,
137 value_type,
124 default_value,
138 default_value,
125 description='')
139 description='')
126 conf = (GraderConfiguration.find_by_key(key) ||
140 conf = (GraderConfiguration.find_by_key(key) ||
127 GraderConfiguration.new(:key => key,
141 GraderConfiguration.new(:key => key,
128 :value_type => value_type,
142 :value_type => value_type,
129 :value => default_value))
143 :value => default_value))
130 conf.description = description
144 conf.description = description
@@ -185,14 +199,25
185
199
186 root.roles << Role.find_by_name('admin')
200 root.roles << Role.find_by_name('admin')
187
201
188 root.activated = true
202 root.activated = true
189 root.save
203 root.save
190 end
204 end
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
@@ -20,39 +20,39
20 end
20 end
21
21
22 def self.stop_grader(pid)
22 def self.stop_grader(pid)
23 GraderScript.call_grader "stop #{pid}"
23 GraderScript.call_grader "stop #{pid}"
24 end
24 end
25
25
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,
40 checker_name='text')
40 checker_name='text')
41 if GraderScript.grader_control_enabled?
41 if GraderScript.grader_control_enabled?
42 cur_dir = `pwd`.chomp
42 cur_dir = `pwd`.chomp
43 Dir.chdir(GRADER_ROOT_DIR)
43 Dir.chdir(GRADER_ROOT_DIR)
44
44
45 script_name = File.join(GRADER_ROOT_DIR, "scripts/import_problem")
45 script_name = File.join(GRADER_ROOT_DIR, "scripts/import_problem")
46 cmd = "#{script_name} #{problem_name} #{problem_dir} #{checker_name}" +
46 cmd = "#{script_name} #{problem_name} #{problem_dir} #{checker_name}" +
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
@@ -2,33 +2,35
2
2
3 class TestdataImporter
3 class TestdataImporter
4
4
5 attr :log_msg
5 attr :log_msg
6
6
7 def initialize(problem)
7 def initialize(problem)
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}"
29 @log_msg = "Importing test pair successful. (#{test_pair_count} test pairs imported)"
31 @log_msg = "Importing test pair successful. (#{test_pair_count} test pairs imported)"
30 else
32 else
31 @log_msg = "Importing test pair failed. (0 test pairs imported)"
33 @log_msg = "Importing test pair failed. (0 test pairs imported)"
32 end
34 end
33 end
35 end
34
36
deleted file
deleted file
deleted file
You need to be logged in to leave comments. Login now