Description:
merge
Commit status:
[Not Reviewed]
References:
Diff options:
Comments:
0 Commit comments
0 Inline Comments
Unresolved TODOs:
There are no unresolved TODOs
r701:f26733606a76 - - 21 files changed: 104 inserted, 29 deleted
@@ -76,49 +76,49 | |||
|
76 | 76 | @submission.source_filename = "live_edit.#{language.ext}" |
|
77 | 77 | @submission.language = language |
|
78 | 78 | end |
|
79 | 79 | |
|
80 | 80 | @submission.submitted_at = Time.new.gmtime |
|
81 | 81 | @submission.ip_address = request.remote_ip |
|
82 | 82 | |
|
83 | 83 | if GraderConfiguration.time_limit_mode? and user.contest_finished? |
|
84 | 84 | @submission.errors.add(:base,"The contest is over.") |
|
85 | 85 | prepare_list_information |
|
86 | 86 | render :action => 'list' and return |
|
87 | 87 | end |
|
88 | 88 | |
|
89 | 89 | if @submission.valid?(@current_user) |
|
90 | 90 | if @submission.save == false |
|
91 | 91 | flash[:notice] = 'Error saving your submission' |
|
92 | 92 | elsif Task.create(:submission_id => @submission.id, |
|
93 | 93 | :status => Task::STATUS_INQUEUE) == false |
|
94 | 94 | flash[:notice] = 'Error adding your submission to task queue' |
|
95 | 95 | end |
|
96 | 96 | else |
|
97 | 97 | prepare_list_information |
|
98 | 98 | render :action => 'list' and return |
|
99 | 99 | end |
|
100 | - redirect_to :action => 'list' | |
|
100 | + redirect_to edit_submission_path(@submission) | |
|
101 | 101 | end |
|
102 | 102 | |
|
103 | 103 | def source |
|
104 | 104 | submission = Submission.find(params[:id]) |
|
105 | 105 | if ((submission.user_id == session[:user_id]) and |
|
106 | 106 | (submission.problem != nil) and |
|
107 | 107 | (submission.problem.available)) |
|
108 | 108 | send_data(submission.source, |
|
109 | 109 | {:filename => submission.download_filename, |
|
110 | 110 | :type => 'text/plain'}) |
|
111 | 111 | else |
|
112 | 112 | flash[:notice] = 'Error viewing source' |
|
113 | 113 | redirect_to :action => 'list' |
|
114 | 114 | end |
|
115 | 115 | end |
|
116 | 116 | |
|
117 | 117 | def compiler_msg |
|
118 | 118 | @submission = Submission.find(params[:id]) |
|
119 | 119 | if @submission.user_id == session[:user_id] |
|
120 | 120 | render :action => 'compiler_msg', :layout => 'empty' |
|
121 | 121 | else |
|
122 | 122 | flash[:notice] = 'Error viewing source' |
|
123 | 123 | redirect_to :action => 'list' |
|
124 | 124 | end |
@@ -144,92 +144,96 | |||
|
144 | 144 | end |
|
145 | 145 | |
|
146 | 146 | def turn_all_off |
|
147 | 147 | Problem.available.all.each do |problem| |
|
148 | 148 | problem.available = false |
|
149 | 149 | problem.save |
|
150 | 150 | end |
|
151 | 151 | redirect_to action: :index |
|
152 | 152 | end |
|
153 | 153 | |
|
154 | 154 | def turn_all_on |
|
155 | 155 | Problem.where.not(available: true).each do |problem| |
|
156 | 156 | problem.available = true |
|
157 | 157 | problem.save |
|
158 | 158 | end |
|
159 | 159 | redirect_to action: :index |
|
160 | 160 | end |
|
161 | 161 | |
|
162 | 162 | def stat |
|
163 | 163 | @problem = Problem.find(params[:id]) |
|
164 | 164 | unless @problem.available or session[:admin] |
|
165 | 165 | redirect_to :controller => 'main', :action => 'list' |
|
166 | 166 | return |
|
167 | 167 | end |
|
168 | - @submissions = Submission.includes(:user).where(problem_id: params[:id]).order(:user_id,:id) | |
|
168 | + @submissions = Submission.includes(:user).includes(:language).where(problem_id: params[:id]).order(:user_id,:id) | |
|
169 | 169 | |
|
170 | 170 | #stat summary |
|
171 | 171 | range =65 |
|
172 | 172 | @histogram = { data: Array.new(range,0), summary: {} } |
|
173 | 173 | user = Hash.new(0) |
|
174 | 174 | @submissions.find_each do |sub| |
|
175 | 175 | d = (DateTime.now.in_time_zone - sub.submitted_at) / 24 / 60 / 60 |
|
176 | 176 | @histogram[:data][d.to_i] += 1 if d < range |
|
177 | 177 | user[sub.user_id] = [user[sub.user_id], ((sub.try(:points) || 0) >= @problem.full_score) ? 1 : 0].max |
|
178 | 178 | end |
|
179 | 179 | @histogram[:summary][:max] = [@histogram[:data].max,1].max |
|
180 | 180 | |
|
181 | 181 | @summary = { attempt: user.count, solve: 0 } |
|
182 | 182 | user.each_value { |v| @summary[:solve] += 1 if v == 1 } |
|
183 | 183 | end |
|
184 | 184 | |
|
185 | 185 | def manage |
|
186 | 186 | @problems = Problem.order(date_added: :desc) |
|
187 | 187 | end |
|
188 | 188 | |
|
189 | 189 | def do_manage |
|
190 | 190 | if params.has_key? 'change_date_added' |
|
191 | 191 | change_date_added |
|
192 | 192 | elsif params.has_key? 'add_to_contest' |
|
193 | 193 | add_to_contest |
|
194 | 194 | elsif params.has_key? 'enable_problem' |
|
195 | 195 | set_available(true) |
|
196 | 196 | elsif params.has_key? 'disable_problem' |
|
197 | 197 | set_available(false) |
|
198 | 198 | elsif params.has_key? 'add_group' |
|
199 | 199 | group = Group.find(params[:group_id]) |
|
200 | 200 | ok = [] |
|
201 | 201 | failed = [] |
|
202 | 202 | get_problems_from_params.each do |p| |
|
203 | 203 | begin |
|
204 | 204 | group.problems << p |
|
205 | 205 | ok << p.full_name |
|
206 | 206 | rescue => e |
|
207 | 207 | failed << p.full_name |
|
208 | 208 | end |
|
209 | 209 | end |
|
210 | 210 | flash[:success] = "The following problems are added to the group #{group.name}: " + ok.join(', ') if ok.count > 0 |
|
211 | 211 | flash[:alert] = "The following problems are already in the group #{group.name}: " + failed.join(', ') if failed.count > 0 |
|
212 | + elsif params.has_key? 'add_tags' | |
|
213 | + get_problems_from_params.each do |p| | |
|
214 | + p.tag_ids += params[:tag_ids] | |
|
215 | + end | |
|
212 | 216 | end |
|
213 | 217 | |
|
214 | 218 | redirect_to :action => 'manage' |
|
215 | 219 | end |
|
216 | 220 | |
|
217 | 221 | def import |
|
218 | 222 | @allow_test_pair_import = allow_test_pair_import? |
|
219 | 223 | end |
|
220 | 224 | |
|
221 | 225 | def do_import |
|
222 | 226 | old_problem = Problem.find_by_name(params[:name]) |
|
223 | 227 | if !allow_test_pair_import? and params.has_key? :import_to_db |
|
224 | 228 | params.delete :import_to_db |
|
225 | 229 | end |
|
226 | 230 | @problem, import_log = Problem.create_from_import_form_params(params, |
|
227 | 231 | old_problem) |
|
228 | 232 | |
|
229 | 233 | if !@problem.errors.empty? |
|
230 | 234 | render :action => 'import' and return |
|
231 | 235 | end |
|
232 | 236 | |
|
233 | 237 | if old_problem!=nil |
|
234 | 238 | flash[:notice] = "The test data has been replaced for problem #{@problem.name}" |
|
235 | 239 | end |
@@ -279,28 +283,28 | |||
|
279 | 283 | problems = get_problems_from_params |
|
280 | 284 | problems.each do |p| |
|
281 | 285 | p.available = avail |
|
282 | 286 | p.save |
|
283 | 287 | end |
|
284 | 288 | end |
|
285 | 289 | |
|
286 | 290 | def get_problems_from_params |
|
287 | 291 | problems = [] |
|
288 | 292 | params.keys.each do |k| |
|
289 | 293 | if k.index('prob-')==0 |
|
290 | 294 | name, id, order = k.split('-') |
|
291 | 295 | problems << Problem.find(id) |
|
292 | 296 | end |
|
293 | 297 | end |
|
294 | 298 | problems |
|
295 | 299 | end |
|
296 | 300 | |
|
297 | 301 | def get_problems_stat |
|
298 | 302 | end |
|
299 | 303 | |
|
300 | 304 | private |
|
301 | 305 | |
|
302 | 306 | def problem_params |
|
303 | - params.require(:problem).permit(:name, :full_name, :full_score, :date_added, :available, :test_allowed,:output_only, :url, :description) | |
|
307 | + params.require(:problem).permit(:name, :full_name, :full_score, :date_added, :available, :test_allowed,:output_only, :url, :description, tag_ids:[]) | |
|
304 | 308 | end |
|
305 | 309 | |
|
306 | 310 | end |
@@ -31,48 +31,50 | |||
|
31 | 31 | end |
|
32 | 32 | |
|
33 | 33 | def show_max_score |
|
34 | 34 | #process parameters |
|
35 | 35 | #problems |
|
36 | 36 | @problems = [] |
|
37 | 37 | if params[:problem_id] |
|
38 | 38 | params[:problem_id].each do |id| |
|
39 | 39 | next unless id.strip != "" |
|
40 | 40 | pid = Problem.find_by_id(id.to_i) |
|
41 | 41 | @problems << pid if pid |
|
42 | 42 | end |
|
43 | 43 | end |
|
44 | 44 | |
|
45 | 45 | #users |
|
46 | 46 | @users = if params[:user] == "all" then |
|
47 | 47 | User.includes(:contests).includes(:contest_stat) |
|
48 | 48 | else |
|
49 | 49 | User.includes(:contests).includes(:contest_stat).where(enabled: true) |
|
50 | 50 | end |
|
51 | 51 | |
|
52 | 52 | #set up range from param |
|
53 | 53 | @since_id = params.fetch(:from_id, 0).to_i |
|
54 | 54 | @until_id = params.fetch(:to_id, 0).to_i |
|
55 | + @since_id = nil if @since_id == 0 | |
|
56 | + @until_id = nil if @until_id == 0 | |
|
55 | 57 | |
|
56 | 58 | #calculate the routine |
|
57 | 59 | @scorearray = calculate_max_score(@problems, @users, @since_id, @until_id) |
|
58 | 60 | |
|
59 | 61 | #rencer accordingly |
|
60 | 62 | if params[:button] == 'download' then |
|
61 | 63 | csv = gen_csv_from_scorearray(@scorearray,@problems) |
|
62 | 64 | send_data csv, filename: 'max_score.csv' |
|
63 | 65 | else |
|
64 | 66 | #render template: 'user_admin/user_stat' |
|
65 | 67 | render 'max_score' |
|
66 | 68 | end |
|
67 | 69 | |
|
68 | 70 | end |
|
69 | 71 | |
|
70 | 72 | def score |
|
71 | 73 | if params[:commit] == 'download csv' |
|
72 | 74 | @problems = Problem.all |
|
73 | 75 | else |
|
74 | 76 | @problems = Problem.available_problems |
|
75 | 77 | end |
|
76 | 78 | @users = User.includes(:contests, :contest_stat).where(enabled: true) |
|
77 | 79 | @scorearray = Array.new |
|
78 | 80 | @users.each do |u| |
@@ -20,39 +20,41 | |||
|
20 | 20 | end |
|
21 | 21 | |
|
22 | 22 | # POST /tags |
|
23 | 23 | def create |
|
24 | 24 | @tag = Tag.new(tag_params) |
|
25 | 25 | |
|
26 | 26 | if @tag.save |
|
27 | 27 | redirect_to @tag, notice: 'Tag was successfully created.' |
|
28 | 28 | else |
|
29 | 29 | render :new |
|
30 | 30 | end |
|
31 | 31 | end |
|
32 | 32 | |
|
33 | 33 | # PATCH/PUT /tags/1 |
|
34 | 34 | def update |
|
35 | 35 | if @tag.update(tag_params) |
|
36 | 36 | redirect_to @tag, notice: 'Tag was successfully updated.' |
|
37 | 37 | else |
|
38 | 38 | render :edit |
|
39 | 39 | end |
|
40 | 40 | end |
|
41 | 41 | |
|
42 | 42 | # DELETE /tags/1 |
|
43 | 43 | def destroy |
|
44 | + #remove any association | |
|
45 | + ProblemTag.where(tag_id: @tag.id).destroy_all | |
|
44 | 46 | @tag.destroy |
|
45 | 47 | redirect_to tags_url, notice: 'Tag was successfully destroyed.' |
|
46 | 48 | end |
|
47 | 49 | |
|
48 | 50 | private |
|
49 | 51 | # Use callbacks to share common setup or constraints between actions. |
|
50 | 52 | def set_tag |
|
51 | 53 | @tag = Tag.find(params[:id]) |
|
52 | 54 | end |
|
53 | 55 | |
|
54 | 56 | # Only allow a trusted parameter "white list" through. |
|
55 | 57 | def tag_params |
|
56 | 58 | params.require(:tag).permit(:name, :description, :public) |
|
57 | 59 | end |
|
58 | 60 | end |
@@ -64,63 +64,66 | |||
|
64 | 64 | append_to menu_items, "[#{I18n.t 'menu.test'}]", 'test', 'index' |
|
65 | 65 | end |
|
66 | 66 | |
|
67 | 67 | if GraderConfiguration['right.user_hall_of_fame'] |
|
68 | 68 | append_to menu_items, "[#{I18n.t 'menu.hall_of_fame'}]", 'report', 'problem_hof' |
|
69 | 69 | end |
|
70 | 70 | append_to menu_items, "[#{I18n.t 'menu.help'}]", 'main', 'help' |
|
71 | 71 | |
|
72 | 72 | if GraderConfiguration['system.user_setting_enabled'] |
|
73 | 73 | append_to menu_items, "[#{I18n.t 'menu.settings'}]", 'users', 'index' |
|
74 | 74 | end |
|
75 | 75 | append_to menu_items, "[#{I18n.t 'menu.log_out'}]", 'main', 'login' |
|
76 | 76 | |
|
77 | 77 | menu_items.html_safe |
|
78 | 78 | end |
|
79 | 79 | |
|
80 | 80 | def append_to(option,label, controller, action) |
|
81 | 81 | option << ' ' if option!='' |
|
82 | 82 | option << link_to_unless_current(label, |
|
83 | 83 | :controller => controller, |
|
84 | 84 | :action => action) |
|
85 | 85 | end |
|
86 | 86 | |
|
87 | 87 | def format_short_time(time) |
|
88 |
- now = Time.now |
|
|
88 | + now = Time.zone.now | |
|
89 | 89 | st = '' |
|
90 | - if (time.yday != now.yday) or | |
|
91 | - (time.year != now.year) | |
|
92 | - st = time.strftime("%x ") | |
|
90 | + if (time.yday != now.yday) or (time.year != now.year) | |
|
91 | + st = time.strftime("%d/%m/%y ") | |
|
93 | 92 | end |
|
94 | 93 | st + time.strftime("%X") |
|
95 | 94 | end |
|
96 | 95 | |
|
97 | 96 | def format_short_duration(duration) |
|
98 | 97 | return '' if duration==nil |
|
99 | 98 | d = duration.to_f |
|
100 | 99 | return Time.at(d).gmtime.strftime("%X") |
|
101 | 100 | end |
|
102 | 101 | |
|
102 | + def format_full_time_ago(time) | |
|
103 | + st = time_ago_in_words(time) + ' ago (' + format_short_time(time) + ')' | |
|
104 | + end | |
|
105 | + | |
|
103 | 106 | def read_textfile(fname,max_size=2048) |
|
104 | 107 | begin |
|
105 | 108 | File.open(fname).read(max_size) |
|
106 | 109 | rescue |
|
107 | 110 | nil |
|
108 | 111 | end |
|
109 | 112 | end |
|
110 | 113 | |
|
111 | 114 | def toggle_button(on,toggle_url,id, option={}) |
|
112 | 115 | btn_size = option[:size] || 'btn-xs' |
|
113 | 116 | link_to (on ? "Yes" : "No"), toggle_url, |
|
114 | 117 | {class: "btn btn-block #{btn_size} btn-#{on ? 'success' : 'default'} ajax-toggle", |
|
115 | 118 | id: id, |
|
116 | 119 | data: {remote: true, method: 'get'}} |
|
117 | 120 | end |
|
118 | 121 | |
|
119 | 122 | def get_ace_mode(language) |
|
120 | 123 | # return ace mode string from Language |
|
121 | 124 | |
|
122 | 125 | case language.pretty_name |
|
123 | 126 | when 'Pascal' |
|
124 | 127 | 'ace/mode/pascal' |
|
125 | 128 | when 'C++','C' |
|
126 | 129 | 'ace/mode/c_cpp' |
@@ -13,50 +13,50 | |||
|
13 | 13 | validate :must_have_valid_problem |
|
14 | 14 | validate :must_specify_language |
|
15 | 15 | |
|
16 | 16 | has_one :task |
|
17 | 17 | |
|
18 | 18 | before_save :assign_latest_number_if_new_recond |
|
19 | 19 | |
|
20 | 20 | def self.find_last_by_user_and_problem(user_id, problem_id) |
|
21 | 21 | where("user_id = ? AND problem_id = ?",user_id,problem_id).last |
|
22 | 22 | end |
|
23 | 23 | |
|
24 | 24 | def self.find_all_last_by_problem(problem_id) |
|
25 | 25 | # need to put in SQL command, maybe there's a better way |
|
26 | 26 | Submission.includes(:user).find_by_sql("SELECT * FROM submissions " + |
|
27 | 27 | "WHERE id = " + |
|
28 | 28 | "(SELECT MAX(id) FROM submissions AS subs " + |
|
29 | 29 | "WHERE subs.user_id = submissions.user_id AND " + |
|
30 | 30 | "problem_id = " + problem_id.to_s + " " + |
|
31 | 31 | "GROUP BY user_id) " + |
|
32 | 32 | "ORDER BY user_id") |
|
33 | 33 | end |
|
34 | 34 | |
|
35 | 35 | def self.find_in_range_by_user_and_problem(user_id, problem_id,since_id,until_id) |
|
36 | 36 | records = Submission.where(problem_id: problem_id,user_id: user_id) |
|
37 | - records = records.where('id >= ?',since_id) if since_id > 0 | |
|
38 | - records = records.where('id <= ?',until_id) if until_id > 0 | |
|
37 | + records = records.where('id >= ?',since_id) if since_id and since_id > 0 | |
|
38 | + records = records.where('id <= ?',until_id) if until_id and until_id > 0 | |
|
39 | 39 | records.all |
|
40 | 40 | end |
|
41 | 41 | |
|
42 | 42 | def self.find_last_for_all_available_problems(user_id) |
|
43 | 43 | submissions = Array.new |
|
44 | 44 | problems = Problem.available_problems |
|
45 | 45 | problems.each do |problem| |
|
46 | 46 | sub = Submission.find_last_by_user_and_problem(user_id, problem.id) |
|
47 | 47 | submissions << sub if sub!=nil |
|
48 | 48 | end |
|
49 | 49 | submissions |
|
50 | 50 | end |
|
51 | 51 | |
|
52 | 52 | def self.find_by_user_problem_number(user_id, problem_id, number) |
|
53 | 53 | where("user_id = ? AND problem_id = ? AND number = ?",user_id,problem_id,number).first |
|
54 | 54 | end |
|
55 | 55 | |
|
56 | 56 | def self.find_all_by_user_problem(user_id, problem_id) |
|
57 | 57 | where("user_id = ? AND problem_id = ?",user_id,problem_id) |
|
58 | 58 | end |
|
59 | 59 | |
|
60 | 60 | def download_filename |
|
61 | 61 | if self.problem.output_only |
|
62 | 62 | return self.source_filename |
@@ -1,26 +1,28 | |||
|
1 | - | |
|
2 | 1 |
|
|
3 | 2 | = "-" |
|
4 | 3 | - else |
|
4 | + %strong= "Submission ID:" | |
|
5 | + = submission.id | |
|
6 | + %br | |
|
5 | 7 | - unless submission.graded_at |
|
6 | - = t 'main.submitted_at' | |
|
7 |
- = format_ |
|
|
8 | + %strong= t 'main.submitted_at:' | |
|
9 | + = format_full_time_ago(submission.submitted_at.localtime) | |
|
8 | 10 | - else |
|
9 | - %strong= t 'main.graded_at' | |
|
10 |
- = |
|
|
11 | + %strong= t 'main.graded_at:' | |
|
12 | + = format_full_time_ago(submission.graded_at.localtime) | |
|
11 | 13 | %br |
|
12 | 14 | - if GraderConfiguration['ui.show_score'] |
|
13 | 15 | %strong=t 'main.score' |
|
14 | 16 | = "#{(submission.points*100/submission.problem.full_score).to_i} " |
|
15 | 17 | = " [" |
|
16 | 18 | %tt |
|
17 | 19 | = submission.grader_comment |
|
18 | 20 | = "]" |
|
19 | 21 | %br |
|
20 | 22 |
|
|
21 | 23 |
|
|
22 | 24 |
|
|
23 |
- |
|
|
25 | + = link_to "#{t 'main.cmp_msg'}", {:action => 'compiler_msg', :id => submission.id}, {popup: true,class: 'btn btn-xs btn-info'} if submission.graded_at | |
|
24 | 26 | = link_to "#{t 'main.src_link'}", download_submission_path(submission.id), class: 'btn btn-xs btn-info' |
|
25 | 27 | = link_to "#{t 'main.submissions_link'}", problem_submissions_path(problem_id), class: 'btn btn-xs btn-info' |
|
26 | 28 |
@@ -25,48 +25,49 | |||
|
25 | 25 | - if GraderConfiguration['right.user_hall_of_fame'] |
|
26 | 26 | = add_menu("#{I18n.t 'menu.hall_of_fame'}", 'report', 'problem_hof') |
|
27 | 27 | / display MODE button (with countdown in contest mode) |
|
28 | 28 | - if GraderConfiguration.analysis_mode? |
|
29 | 29 | %div.navbar-btn.btn.btn-success#countdown= "ANALYSIS MODE" |
|
30 | 30 | - elsif GraderConfiguration.time_limit_mode? |
|
31 | 31 | - if @current_user.contest_finished? |
|
32 | 32 | %div.navbar-btn.btn.btn-danger#countdown= "Contest is over" |
|
33 | 33 | - elsif !@current_user.contest_started? |
|
34 | 34 | %div.navbar-btn.btn.btn-primary#countdown= (t 'title_bar.contest_not_started') |
|
35 | 35 | - else |
|
36 | 36 | %div.navbar-btn.btn.btn-primary#countdown asdf |
|
37 | 37 | :javascript |
|
38 | 38 | $("#countdown").countdown({until: "+#{@current_user.contest_time_left.to_i}s", layout: 'Time left: {hnn}:{mnn}:{snn}'}); |
|
39 | 39 | / admin section |
|
40 | 40 | - if (@current_user!=nil) and (session[:admin]) |
|
41 | 41 | / management |
|
42 | 42 | %li.dropdown |
|
43 | 43 | %a.dropdown-toggle{href: '#', data: {toggle:'dropdown'}, aria: {haspopup:"true", expanded:"false"}, role: "button"} |
|
44 | 44 | Manage |
|
45 | 45 | %span.caret |
|
46 | 46 | %ul.dropdown-menu |
|
47 | 47 | = add_menu( 'Announcements', 'announcements', 'index') |
|
48 | 48 | = add_menu( 'Problems', 'problems', 'index') |
|
49 | + = add_menu( 'Tags', 'tags', 'index') | |
|
49 | 50 | = add_menu( 'Users', 'user_admin', 'index') |
|
50 | 51 | = add_menu( 'User Groups', 'groups', 'index') |
|
51 | 52 | = add_menu( 'Graders', 'graders', 'list') |
|
52 | 53 | = add_menu( 'Message ', 'messages', 'console') |
|
53 | 54 | %li.divider{role: 'separator'} |
|
54 | 55 | = add_menu( 'System config', 'configurations', 'index') |
|
55 | 56 | %li.divider{role: 'separator'} |
|
56 | 57 | = add_menu( 'Sites', 'sites', 'index') |
|
57 | 58 | = add_menu( 'Contests', 'contest_management', 'index') |
|
58 | 59 | / report |
|
59 | 60 | %li.dropdown |
|
60 | 61 | %a.dropdown-toggle{href: '#', data: {toggle:'dropdown'}, aria: {haspopup:"true", expanded:"false"}, role: "button"} |
|
61 | 62 | Report |
|
62 | 63 | %span.caret |
|
63 | 64 | %ul.dropdown-menu |
|
64 | 65 | = add_menu( 'Current Score', 'report', 'current_score') |
|
65 | 66 | = add_menu( 'Score Report', 'report', 'max_score') |
|
66 | 67 | = add_menu( 'Report', 'report', 'multiple_login') |
|
67 | 68 | - if (ungraded = Submission.where('graded_at is null').where('submitted_at < ?', 1.minutes.ago).count) > 0 |
|
68 | 69 | =link_to "#{ungraded} backlogs!", |
|
69 | 70 | grader_list_path, |
|
70 | 71 | class: 'navbar-btn btn btn-default btn-warning', data: {toggle: 'tooltip'},title: 'Number of ungraded submission' |
|
71 | 72 | |
|
72 | 73 | %ul.nav.navbar-nav.navbar-right |
@@ -1,38 +1,41 | |||
|
1 | 1 | = error_messages_for 'problem' |
|
2 | 2 | / [form:problem] |
|
3 | 3 | .form-group |
|
4 | 4 | %label{:for => "problem_name"} Name |
|
5 | 5 | = text_field 'problem', 'name', class: 'form-control' |
|
6 | 6 | %small |
|
7 | 7 | Do not directly edit the problem name, unless you know what you are doing. If you want to change the name, use the name change button in the problem management menu instead. |
|
8 | 8 | .form-group |
|
9 | 9 | %label{:for => "problem_full_name"} Full name |
|
10 | 10 | = text_field 'problem', 'full_name', class: 'form-control' |
|
11 | 11 | .form-group |
|
12 | 12 | %label{:for => "problem_full_score"} Full score |
|
13 | 13 | = text_field 'problem', 'full_score', class: 'form-control' |
|
14 | 14 | .form-group |
|
15 | + %label{:for => "problem_full_score"} Tags | |
|
16 | + = collection_select(:problem, :tag_ids, Tag.all, :id, :name, {}, {multiple: true, class: 'form-control select2'}) | |
|
17 | + .form-group | |
|
15 | 18 | %label{:for => "problem_date_added"} Date added |
|
16 | 19 | = date_select 'problem', 'date_added', class: 'form-control' |
|
17 | 20 | - # TODO: these should be put in model Problem, but I can't think of |
|
18 | 21 | - # nice default values for them. These values look fine only |
|
19 | 22 | - # in this case (of lazily adding new problems). |
|
20 | 23 | - @problem.available = true if @problem!=nil and @problem.available==nil |
|
21 | 24 | - @problem.test_allowed = true if @problem!=nil and @problem.test_allowed==nil |
|
22 | 25 | - @problem.output_only = false if @problem!=nil and @problem.output_only==nil |
|
23 | 26 | .checkbox |
|
24 | 27 | %label{:for => "problem_available"} |
|
25 | 28 | = check_box :problem, :available |
|
26 | 29 | Available? |
|
27 | 30 | .checkbox |
|
28 | 31 | %label{:for => "problem_test_allowed"} |
|
29 | 32 | = check_box :problem, :test_allowed |
|
30 | 33 | Test allowed? |
|
31 | 34 | .checkbox |
|
32 | 35 | %label{:for => "problem_output_only"} |
|
33 | 36 | = check_box :problem, :output_only |
|
34 | 37 | Output only? |
|
35 | 38 | = error_messages_for 'description' |
|
36 | 39 | .form-group |
|
37 | 40 | %label{:for => "description_body"} Description |
|
38 | 41 | %br/ |
@@ -1,60 +1,65 | |||
|
1 | 1 | - content_for :head do |
|
2 | 2 | = stylesheet_link_tag 'problems' |
|
3 | 3 | %h1 Problems |
|
4 | 4 | %p |
|
5 | 5 | = link_to 'Import problems', {:action => 'import'}, class: 'btn btn-success btn-sm' |
|
6 | 6 | = link_to 'New problem', new_problem_path, class: 'btn btn-success btn-sm' |
|
7 | 7 | = link_to 'Bulk Manage', { action: 'manage'}, class: 'btn btn-info btn-sm' |
|
8 | 8 | = link_to 'Turn off all problems', {:action => 'turn_all_off'}, class: 'btn btn-default btn-sm' |
|
9 | 9 | = link_to 'Turn on all problems', {:action => 'turn_all_on'}, class: 'btn btn-default btn-sm' |
|
10 | 10 | .submitbox |
|
11 | 11 | = form_tag :action => 'quick_create' do |
|
12 | 12 | %b Quick New: |
|
13 | 13 | %label{:for => "problem_name"} Name |
|
14 | 14 | = text_field 'problem', 'name' |
|
15 | 15 | | |
|
16 | 16 | %label{:for => "problem_full_name"} Full name |
|
17 | 17 | = text_field 'problem', 'full_name' |
|
18 | 18 | = submit_tag "Create" |
|
19 | 19 | %table.table.table-condense.table-hover |
|
20 | 20 | %thead |
|
21 | 21 | %th Name |
|
22 | 22 | %th Full name |
|
23 | 23 | %th.text-right Full score |
|
24 | + %th Tags | |
|
24 | 25 | %th |
|
25 | 26 | Submit |
|
26 | 27 | %sup{class: 'text-primary',data: {toggle: 'tooltip'}, title: 'Admin can always submit to any problem' } [?] |
|
27 | 28 | %th Date added |
|
28 | 29 | %th.text-center |
|
29 | 30 | Avail? |
|
30 | 31 | %sup{class: 'text-primary',data: {toggle: 'tooltip'}, title: 'Let user submits to this problem?' } [?] |
|
31 | 32 | %th.text-center |
|
32 | 33 | View Data? |
|
33 | 34 | %sup{class: 'text-primary',data: {toggle: 'tooltip'}, title: 'Let user view the testcase of this problem?' } [?] |
|
34 | 35 | %th.text-center |
|
35 | 36 | Test? |
|
36 | 37 | %sup{class: 'text-primary',data: {toggle: 'tooltip'}, title: 'Let user uses test interface on this problem?' } [?] |
|
37 | 38 | - if GraderConfiguration.multicontests? |
|
38 | 39 | %th Contests |
|
39 | 40 | - for problem in @problems |
|
40 | 41 | %tr{:class => "#{(problem.available) ? "success" : "danger"}", :id => "prob-#{problem.id}", :name => "prob-#{problem.id}"} |
|
41 | 42 | - @problem=problem |
|
42 | 43 | %td= problem.name #in_place_editor_field :problem, :name, {}, :rows=>1 |
|
43 | 44 | %td |
|
44 | 45 | = problem.full_name #in_place_editor_field :problem, :full_name, {}, :rows=>1 |
|
45 | 46 | = link_to_description_if_any "[#{t 'main.problem_desc'}] <span class='glyphicon glyphicon-file'></span>".html_safe, problem |
|
46 | 47 | %td.text-right= problem.full_score #in_place_editor_field :problem, :full_score, {}, :rows=>1 |
|
48 | + %td | |
|
49 | + - problem.tags.each do |t| | |
|
50 | + - #%button.btn.btn-default.btn-xs= t.name | |
|
51 | + %span.label.label-default= t.name | |
|
47 | 52 | %td= link_to "Submit", direct_edit_problem_submissions_path(problem,@current_user.id), class: 'btn btn-xs btn-primary' |
|
48 | 53 | %td= problem.date_added |
|
49 | 54 | %td= toggle_button(@problem.available?, toggle_problem_path(@problem), "problem-avail-#{@problem.id}") |
|
50 | 55 | %td= toggle_button(@problem.view_testcase?, toggle_view_testcase_problem_path(@problem), "problem-view-testcase-#{@problem.id}") |
|
51 | 56 | %td= toggle_button(@problem.test_allowed?, toggle_test_problem_path(@problem), "problem-test-#{@problem.id}") |
|
52 | 57 | - if GraderConfiguration.multicontests? |
|
53 | 58 | %td |
|
54 | 59 | = problem.contests.collect { |c| c.name }.join(', ') |
|
55 | 60 | %td= link_to 'Stat', {:action => 'stat', :id => problem.id}, class: 'btn btn-info btn-xs btn-block' |
|
56 | 61 | %td= link_to 'Show', {:action => 'show', :id => problem}, class: 'btn btn-info btn-xs btn-block' |
|
57 | 62 | %td= link_to 'Edit', {:action => 'edit', :id => problem}, class: 'btn btn-info btn-xs btn-block' |
|
58 | 63 | %td= link_to 'Destroy', { :action => 'destroy', :id => problem }, :confirm => 'Are you sure?', :method => :delete, class: 'btn btn-danger btn-xs btn-block' |
|
59 | 64 | %br/ |
|
60 | 65 | = link_to '[New problem]', :action => 'new' |
@@ -28,80 +28,91 | |||
|
28 | 28 | } |
|
29 | 29 | start = orig_stop |
|
30 | 30 | } else { |
|
31 | 31 | start = parseInt($(this).attr('id').split('-')[2]); |
|
32 | 32 | } |
|
33 | 33 | }); |
|
34 | 34 | }); |
|
35 | 35 | |
|
36 | 36 | |
|
37 | 37 | %h1 Manage problems |
|
38 | 38 | |
|
39 | 39 | %p= link_to '[Back to problem list]', problems_path |
|
40 | 40 | |
|
41 | 41 | = form_tag :action=>'do_manage' do |
|
42 | 42 | .panel.panel-primary |
|
43 | 43 | .panel-heading |
|
44 | 44 | Action |
|
45 | 45 | .panel-body |
|
46 | 46 | .submit-box |
|
47 | 47 | What do you want to do to the selected problem? |
|
48 | 48 | %br/ |
|
49 | 49 | (You can shift-click to select a range of problems) |
|
50 | 50 | %ul.form-inline |
|
51 | 51 | %li |
|
52 |
- Change |
|
|
52 | + Change "Date added" to | |
|
53 | 53 | .input-group.date |
|
54 | 54 | = text_field_tag :date_added, class: 'form-control' |
|
55 | 55 | %span.input-group-addon |
|
56 | 56 | %span.glyphicon.glyphicon-calendar |
|
57 | 57 | -# = select_date Date.current, :prefix => 'date_added' |
|
58 | 58 | |
|
59 | 59 | = submit_tag 'Change', :name => 'change_date_added', class: 'btn btn-primary btn-sm' |
|
60 | 60 | %li |
|
61 |
- Set |
|
|
61 | + Set "Available" to | |
|
62 | 62 | = submit_tag 'True', :name => 'enable_problem', class: 'btn btn-primary btn-sm' |
|
63 | 63 | = submit_tag 'False', :name => 'disable_problem', class: 'btn btn-primary btn-sm' |
|
64 | 64 | |
|
65 | 65 | - if GraderConfiguration.multicontests? |
|
66 | 66 | %li |
|
67 | - Add to | |
|
67 | + Add selected problems to contest | |
|
68 | 68 | = select("contest","id",Contest.all.collect {|c| [c.title, c.id]}) |
|
69 | 69 | = submit_tag 'Add', :name => 'add_to_contest', class: 'btn btn-primary btn-sm' |
|
70 | 70 | %li |
|
71 | - Add problems to group | |
|
71 | + Add selected problems to user group | |
|
72 | 72 | = select_tag "group_id", options_from_collection_for_select( Group.all, 'id','name',params[:group_name]), id: 'group_name',class: 'select2' |
|
73 |
- = submit_tag 'Add', name: 'add_group', class: 'btn btn- |
|
|
73 | + = submit_tag 'Add', name: 'add_group', class: 'btn btn-primary' | |
|
74 | + %li | |
|
75 | + Add the following tags to the selected problems | |
|
76 | + = select_tag "tag_ids", options_from_collection_for_select( Tag.all, 'id','name'), id: 'tags_name',class: 'select2', multiple: true, data: {placeholder: 'Select tags by clicking', width: "200px"} | |
|
77 | + = submit_tag 'Add', name: 'add_tags', class: 'btn btn-primary' | |
|
74 | 78 | |
|
75 | - | |
|
76 | - %table.table.table-hover | |
|
79 | + %table.table.table-hover.datatable | |
|
80 | + %thead | |
|
77 | 81 | %tr{style: "text-align: left;"} |
|
78 | 82 | %th= check_box_tag 'select_all' |
|
79 | 83 | %th Name |
|
80 | 84 | %th Full name |
|
85 | + %th Tags | |
|
81 | 86 | %th Available |
|
82 | 87 | %th Date added |
|
83 | 88 | - if GraderConfiguration.multicontests? |
|
84 | 89 | %th Contests |
|
85 | 90 | |
|
91 | + %tbody | |
|
86 | 92 | - num = 0 |
|
87 | 93 | - for problem in @problems |
|
88 | 94 | - num += 1 |
|
89 | 95 | %tr{:id => "row-prob-#{problem.id}", :name=> "prob-#{problem.id}"} |
|
90 | 96 | %td= check_box_tag "prob-#{problem.id}-#{num}" |
|
91 | 97 | %td= problem.name |
|
92 | 98 | %td= problem.full_name |
|
99 | + %td | |
|
100 | + - problem.tags.each do |t| | |
|
101 | + %span.label.label-default= t.name | |
|
93 | 102 | %td= problem.available |
|
94 | 103 | %td= problem.date_added |
|
95 | 104 | - if GraderConfiguration.multicontests? |
|
96 | 105 | %td |
|
97 | 106 | - problem.contests.each do |contest| |
|
98 | 107 | = "(#{contest.name} [#{link_to 'x', :action => 'remove_contest', :id => problem.id, :contest_id => contest.id }])" |
|
99 | 108 | |
|
100 | 109 | :javascript |
|
101 | 110 | $('.input-group.date').datetimepicker({ |
|
102 | 111 | format: 'DD/MMM/YYYY', |
|
103 | 112 | showTodayButton: true, |
|
104 | 113 | widgetPositioning: {horizontal: 'auto', vertical: 'bottom'}, |
|
105 | 114 | |
|
106 | 115 | }); |
|
107 | - | |
|
116 | + $('.datatable').DataTable({ | |
|
117 | + paging: false | |
|
118 | + }); |
@@ -11,47 +11,49 | |||
|
11 | 11 | %thead |
|
12 | 12 | %tr.info-head |
|
13 | 13 | %th Stat |
|
14 | 14 | %th Value |
|
15 | 15 | %tbody |
|
16 | 16 | %tr{class: cycle('info-even','info-odd')} |
|
17 | 17 | %td Submissions |
|
18 | 18 | %td= @submissions.count |
|
19 | 19 | %tr{class: cycle('info-even','info-odd')} |
|
20 | 20 | %td Solved/Attempted User |
|
21 | 21 | %td #{@summary[:solve]}/#{@summary[:attempt]} (#{(@summary[:solve]*100.0/@summary[:attempt]).round(1)}%) |
|
22 | 22 | |
|
23 | 23 | %h2 Submissions Count |
|
24 | 24 | = render partial: 'application/bar_graph', locals: { histogram: @histogram } |
|
25 | 25 | |
|
26 | 26 | %h2 Submissions |
|
27 | 27 | - if @submissions and @submissions.count > 0 |
|
28 | 28 | %table#main_table.table.table-condensed.table-striped |
|
29 | 29 | %thead |
|
30 | 30 | %tr |
|
31 | 31 | %th ID |
|
32 | 32 | %th Login |
|
33 | 33 | %th Name |
|
34 | 34 | %th Submitted_at |
|
35 | + %th language | |
|
35 | 36 | %th Points |
|
36 | 37 | %th comment |
|
37 | 38 | %th IP |
|
38 | 39 | %tbody |
|
39 | 40 | - row_odd,curr = true,'' |
|
40 | 41 | - @submissions.each do |sub| |
|
41 | 42 | - next unless sub.user |
|
42 | 43 | - row_odd,curr = !row_odd, sub.user if curr != sub.user |
|
43 | 44 | %tr |
|
44 | 45 | %td= link_to sub.id, submission_path(sub) |
|
45 | 46 | %td= link_to sub.user.login, stat_user_path(sub.user) |
|
46 | 47 | %td= sub.user.full_name |
|
47 | 48 | %td{data: {order: sub.submitted_at}}= time_ago_in_words(sub.submitted_at) + " ago" |
|
49 | + %td= sub.language.name | |
|
48 | 50 | %td= sub.points |
|
49 | 51 | %td.fix-width= sub.grader_comment |
|
50 | 52 | %td= sub.ip_address |
|
51 | 53 | - else |
|
52 | 54 | No submission |
|
53 | 55 | |
|
54 | 56 | :javascript |
|
55 | 57 | $("#main_table").DataTable({ |
|
56 | 58 | paging: false |
|
57 | 59 | }); |
@@ -1,34 +1,69 | |||
|
1 | 1 | %table.table.sortable.table-striped.table-bordered.table-condensed |
|
2 | 2 | %thead |
|
3 | 3 | %tr |
|
4 | 4 | %th Login |
|
5 | 5 | %th Name |
|
6 | 6 | / %th Activated? |
|
7 | 7 | / %th Logged_in |
|
8 | 8 | / %th Contest(s) |
|
9 | 9 | %th Remark |
|
10 | 10 | - @problems.each do |p| |
|
11 | 11 | %th.text-right= p.name.gsub('_',' ') |
|
12 | 12 | %th.text-right Total |
|
13 | 13 | %th.text-right Passed |
|
14 | 14 | %tbody |
|
15 | + - sum = Array.new(@scorearray[0].count,0) | |
|
16 | + - nonzero = Array.new(@scorearray[0].count,0) | |
|
17 | + - full = Array.new(@scorearray[0].count,0) | |
|
15 | 18 | - @scorearray.each do |sc| |
|
16 | 19 | %tr |
|
17 | 20 | - total,num_passed = 0,0 |
|
18 | 21 | - sc.each_index do |i| |
|
19 | 22 | - if i == 0 |
|
20 | 23 | %td= link_to sc[i].login, stat_user_path(sc[i]) |
|
21 | 24 | %td= sc[i].full_name |
|
22 | 25 | / %td= sc[i].activated |
|
23 | 26 | / %td= sc[i].try(:contest_stat).try(:started_at) ? 'yes' : 'no' |
|
24 | 27 | / %td= sc[i].contests.collect {|c| c.name}.join(', ') |
|
25 | 28 | %td= sc[i].remark |
|
26 | 29 | - else |
|
27 | 30 | %td.text-right= sc[i][0] |
|
28 | 31 | - total += sc[i][0] |
|
29 | 32 | - num_passed += 1 if sc[i][1] |
|
33 | + - sum[i] += sc[i][0] | |
|
34 | + - nonzero[i] += 1 if sc[i][0] > 0 | |
|
35 | + - full[i] += 1 if sc[i][1] | |
|
30 | 36 | %td.text-right= total |
|
31 | 37 | %td.text-right= num_passed |
|
38 | + %tfoot | |
|
39 | + %tr | |
|
40 | + %td Summation | |
|
41 | + %td | |
|
42 | + %td | |
|
43 | + - sum.each.with_index do |s,i| | |
|
44 | + - next if i == 0 | |
|
45 | + %td.text-right= number_with_delimiter(s) | |
|
46 | + %td | |
|
47 | + %td | |
|
48 | + %tr | |
|
49 | + %td partial solver | |
|
50 | + %td | |
|
51 | + %td | |
|
52 | + - nonzero.each.with_index do |s,i| | |
|
53 | + - next if i == 0 | |
|
54 | + %td.text-right= number_with_delimiter(s) | |
|
55 | + %td | |
|
56 | + %td | |
|
57 | + %tr | |
|
58 | + %td Full solver | |
|
59 | + %td | |
|
60 | + %td | |
|
61 | + - full.each.with_index do |s,i| | |
|
62 | + - next if i == 0 | |
|
63 | + %td.text-right= number_with_delimiter(s) | |
|
64 | + %td | |
|
65 | + %td | |
|
66 | + | |
|
32 | 67 | |
|
33 | 68 | :javascript |
|
34 | 69 | $.bootstrapSortable(true,'reversed') |
@@ -12,38 +12,38 | |||
|
12 | 12 | = label_tag :problem_id, "Problems" |
|
13 | 13 | = select_tag 'problem_id[]', |
|
14 | 14 | options_for_select(Problem.all.collect {|p| ["[#{p.name}] #{p.full_name}", p.id]},params[:problem_id]), |
|
15 | 15 | { class: 'select2 form-control', multiple: "true" } |
|
16 | 16 | .col-md-4 |
|
17 | 17 | .panel.panel-primary |
|
18 | 18 | .panel-heading |
|
19 | 19 | Submission range |
|
20 | 20 | .panel-body |
|
21 | 21 | %p |
|
22 | 22 | Input minimum and maximum range of submission ID that should be included. A blank value for min and max means -1 and infinity, respectively. |
|
23 | 23 | .form-group |
|
24 | 24 | = label_tag :from, "Min" |
|
25 | 25 | = text_field_tag 'from_id', @since_id, class: "form-control" |
|
26 | 26 | .form-group |
|
27 | 27 | = label_tag :from, "Max" |
|
28 | 28 | = text_field_tag 'to_id', @until_id, class: "form-control" |
|
29 | 29 | .col-md-4 |
|
30 | 30 | .panel.panel-primary |
|
31 | 31 | .panel-heading |
|
32 | 32 | Users |
|
33 | 33 | .panel-body |
|
34 | 34 | .radio |
|
35 | 35 | %label |
|
36 |
- = radio_button_tag 'users', 'all', |
|
|
36 | + = radio_button_tag 'users', 'all', (params[:users] == "all") | |
|
37 | 37 | All users |
|
38 | 38 | .radio |
|
39 | 39 | %label |
|
40 | - = radio_button_tag 'users', 'enabled' | |
|
40 | + = radio_button_tag 'users', 'enabled', (params[:users] == "enabled") | |
|
41 | 41 | Only enabled users |
|
42 | 42 | .row |
|
43 | 43 | .col-md-12 |
|
44 | 44 | = button_tag 'Show', class: "btn btn-primary btn-large", value: "show" |
|
45 | 45 | = button_tag 'Download CSV', class: "btn btn-primary btn-large", value: "download" |
|
46 | 46 | |
|
47 | 47 | - if @scorearray |
|
48 | 48 | %h2 Result |
|
49 | 49 | =render "score_table" |
@@ -10,26 +10,27 | |||
|
10 | 10 | = f.label :password, "Password" |
|
11 | 11 | = f.text_field :password, class: 'form-control' |
|
12 | 12 | .form-group.field |
|
13 | 13 | = f.label :started, "Started" |
|
14 | 14 | = f.check_box :started, class: 'form-control' |
|
15 | 15 | .form-group.field |
|
16 | 16 | = f.label :start_time, "Start time" |
|
17 | 17 | -# = f.datetime_select :start_time, :include_blank => true |
|
18 | 18 | .input-group.date |
|
19 | 19 | = f.text_field :start_time, class:'form-control' , value: (@site.start_time ? @site.start_time.strftime('%d/%b/%Y %H:%M') : '') |
|
20 | 20 | %span.input-group-addon |
|
21 | 21 | %span.glyphicon.glyphicon-calendar |
|
22 | 22 | .actions |
|
23 | 23 | = f.submit "Update", class: 'btn btn-primary' |
|
24 | 24 | .col-md-8 |
|
25 | 25 | |
|
26 | 26 | = link_to 'Show', @site |
|
27 | 27 | | |
|
28 | 28 | = link_to 'Back', sites_path |
|
29 | 29 | |
|
30 | 30 | |
|
31 | 31 | :javascript |
|
32 | 32 | $('.input-group.date').datetimepicker({ |
|
33 | 33 | format: 'DD/MMM/YYYY HH:mm', |
|
34 | + showTodayButton: true, | |
|
34 | 35 | }); |
|
35 | 36 |
@@ -6,53 +6,54 | |||
|
6 | 6 | .row |
|
7 | 7 | .col-md-12 |
|
8 | 8 | .alert.alert-info |
|
9 | 9 | Write your code in the following box, choose language, and click submit button when finished |
|
10 | 10 | .row |
|
11 | 11 | .col-md-8 |
|
12 | 12 | %div#editor{style: 'height: 500px; border-radius: 7px; font-size: 14px;'} |
|
13 | 13 | .col-md-4 |
|
14 | 14 | - # submission form |
|
15 | 15 | = form_tag({controller: :main, :action => 'submit'}, :multipart => true, class: 'form') do |
|
16 | 16 | |
|
17 | 17 | = hidden_field_tag 'editor_text', @source |
|
18 | 18 | = hidden_field_tag 'submission[problem_id]', @problem.id |
|
19 | 19 | .form-group |
|
20 | 20 | = label_tag "Task:" |
|
21 | 21 | = text_field_tag 'asdf', "#{@problem.long_name}", class: 'form-control', disabled: true |
|
22 | 22 | |
|
23 | 23 | .form-group |
|
24 | 24 | = label_tag 'Language' |
|
25 | 25 | = select_tag 'language_id', options_from_collection_for_select(Language.all, 'id', 'pretty_name', @lang_id || Language.find_by_pretty_name("Python").id || Language.first.id), class: 'form-control select', style: "width: 100px" |
|
26 | 26 | .form-group |
|
27 | 27 | = submit_tag 'Submit', class: 'btn btn-success', id: 'live_submit', |
|
28 | 28 | data: {confirm: "Submitting this source code for task #{@problem.long_name}?"} |
|
29 | 29 | - # latest submission status |
|
30 | - .panel.panel-info | |
|
30 | + .panel{class: (@submission && @submission.graded_at) ? "panel-info" : "panel-warning"} | |
|
31 | 31 | .panel-heading |
|
32 | 32 | Latest Submission Status |
|
33 | 33 | = link_to "Refresh",get_latest_submission_status_submissions_path(@submission.user,@problem), class: "btn btn-default btn-sm", remote: true if @submission |
|
34 | 34 | .panel-body |
|
35 | + %div#latest_status | |
|
35 | 36 | - if @submission |
|
36 | 37 | = render :partial => 'submission_short', |
|
37 | 38 | :locals => {submission: @submission, problem_name: @problem.name, problem_id: @problem.id } |
|
38 | 39 | .row |
|
39 | 40 | .col-md-12 |
|
40 | 41 | %h2 Console |
|
41 | 42 | %textarea#console{style: 'height: 100%; width: 100%;background-color:#000;color:#fff;font-family: consolas, monaco, "Droid Sans Mono";',rows: 20} |
|
42 | 43 | |
|
43 | 44 | :javascript |
|
44 | 45 | $(document).ready(function() { |
|
45 | 46 | e = ace.edit("editor") |
|
46 | 47 | e.setValue($("#text_sourcecode").val()); |
|
47 | 48 | e.gotoLine(1); |
|
48 | 49 | $("#language_id").trigger('change'); |
|
49 | 50 | brython(); |
|
50 | 51 | }); |
|
51 | 52 | |
|
52 | 53 | |
|
53 | 54 | %script#__main__{type:'text/python3'} |
|
54 | 55 | :plain |
|
55 | 56 | import sys |
|
56 | 57 | import traceback |
|
57 | 58 | |
|
58 | 59 | from browser import document as doc |
@@ -1,2 +1,2 | |||
|
1 | 1 | :plain |
|
2 | - $("#latest_status").html("#{j render({partial: 'submission_short', locals: {submission: @submission, problem_name: @problem.name}})}") | |
|
2 | + $("#latest_status").html("#{j render({partial: 'submission_short', locals: {submission: @submission, problem_name: @problem.name, problem_id: @problem.id}})}") |
@@ -44,29 +44,29 | |||
|
44 | 44 | |
|
45 | 45 | # Use SQL instead of Active Record's schema dumper when creating the database. |
|
46 | 46 | # This is necessary if your schema can't be completely dumped by the schema dumper, |
|
47 | 47 | # like if you have constraints or database-specific column types |
|
48 | 48 | # config.active_record.schema_format = :sql |
|
49 | 49 | |
|
50 | 50 | # Enable the asset pipeline |
|
51 | 51 | config.assets.enabled = true |
|
52 | 52 | |
|
53 | 53 | # Version of your assets, change this if you want to expire all your assets |
|
54 | 54 | config.assets.version = '1.0' |
|
55 | 55 | |
|
56 | 56 | # ---------------- IMPORTANT ---------------------- |
|
57 | 57 | # If we deploy the app into a subdir name "grader", be sure to do "rake assets:precompile RAILS_RELATIVE_URL_ROOT=/grader" |
|
58 | 58 | # moreover, using the following line instead also known to works |
|
59 | 59 | #config.action_controller.relative_url_root = '/grader' |
|
60 | 60 | |
|
61 | 61 | #font path |
|
62 | 62 | config.assets.paths << "#{Rails}/vendor/assets/fonts" |
|
63 | 63 | |
|
64 | 64 | config.assets.precompile += ['announcement_refresh.js','effects.js','site_update.js'] |
|
65 | 65 | config.assets.precompile += ['local_jquery.js','tablesorter-theme.cafe.css'] |
|
66 | 66 | %w( announcements submissions configurations contests contest_management graders heartbeat |
|
67 | 67 | login main messages problems report site sites sources tasks |
|
68 | - test user_admin users ).each do |controller| | |
|
68 | + test user_admin users testcases).each do |controller| | |
|
69 | 69 | config.assets.precompile += ["#{controller}.js", "#{controller}.css"] |
|
70 | 70 | end |
|
71 | 71 | end |
|
72 | 72 | end |
@@ -1,23 +1,23 | |||
|
1 | 1 | # Be sure to restart your server when you modify this file. |
|
2 | 2 | |
|
3 | 3 | # Version of your assets, change this if you want to expire all your assets. |
|
4 | 4 | Rails.application.config.assets.version = '1.0' |
|
5 | 5 | |
|
6 | 6 | # Add additional assets to the asset load path. |
|
7 | 7 | # Rails.application.config.assets.paths << Emoji.images_path |
|
8 | 8 | # Add Yarn node_modules folder to the asset load path. |
|
9 | 9 | Rails.application.config.assets.paths << Rails.root.join('node_modules') |
|
10 | 10 | Rails.application.config.assets.paths << Rails.root.join('vendor/assets/fonts') |
|
11 | 11 | |
|
12 | 12 | # Precompile additional assets. |
|
13 | 13 | # application.js, application.css, and all non-JS/CSS in the app/assets |
|
14 | 14 | # folder are already added. |
|
15 | 15 | # Rails.application.config.assets.precompile += %w( admin.js admin.css ) |
|
16 | 16 | |
|
17 | 17 | Rails.application.config.assets.precompile += ['announcement_refresh.js','effects.js','site_update.js'] |
|
18 | 18 | Rails.application.config.assets.precompile += ['local_jquery.js','tablesorter-theme.cafe.css'] |
|
19 | 19 | %w( announcements submissions configurations contests contest_management graders heartbeat |
|
20 | 20 | login main messages problems report site sites sources tasks groups |
|
21 | - test user_admin users tags).each do |controller| | |
|
21 | + test user_admin users tags testcases).each do |controller| | |
|
22 | 22 | Rails.application.config.assets.precompile += ["#{controller}.js", "#{controller}.css"] |
|
23 | 23 | end |
@@ -12,48 +12,49 | |||
|
12 | 12 | File.join GRADER_ROOT_DIR, "raw" |
|
13 | 13 | end |
|
14 | 14 | |
|
15 | 15 | def self.call_grader(params) |
|
16 | 16 | if GraderScript.grader_control_enabled? |
|
17 | 17 | cmd = File.join(GRADER_ROOT_DIR, "scripts/grader") + " " + params |
|
18 | 18 | system(cmd) |
|
19 | 19 | end |
|
20 | 20 | end |
|
21 | 21 | |
|
22 | 22 | def self.stop_grader(pid) |
|
23 | 23 | GraderScript.call_grader "stop #{pid}" |
|
24 | 24 | end |
|
25 | 25 | |
|
26 | 26 | def self.stop_graders(pids) |
|
27 | 27 | pid_str = (pids.map { |process| process.pid.to_s }).join ' ' |
|
28 | 28 | GraderScript.call_grader "stop #{pid_str}" |
|
29 | 29 | end |
|
30 | 30 | |
|
31 | 31 | def self.start_grader(env) |
|
32 | 32 | GraderScript.call_grader "#{env} queue --err-log &" |
|
33 | 33 | GraderScript.call_grader "#{env} test_request -err-log &" |
|
34 | 34 | end |
|
35 | 35 | |
|
36 | + #call the import problem script | |
|
36 | 37 | def self.call_import_problem(problem_name, |
|
37 | 38 | problem_dir, |
|
38 | 39 | time_limit=1, |
|
39 | 40 | memory_limit=32, |
|
40 | 41 | checker_name='text') |
|
41 | 42 | if GraderScript.grader_control_enabled? |
|
42 | 43 | cur_dir = `pwd`.chomp |
|
43 | 44 | Dir.chdir(GRADER_ROOT_DIR) |
|
44 | 45 | |
|
45 | 46 | script_name = File.join(GRADER_ROOT_DIR, "scripts/import_problem") |
|
46 | 47 | cmd = "#{script_name} #{problem_name} #{problem_dir} #{checker_name}" + |
|
47 | 48 | " -t #{time_limit} -m #{memory_limit}" |
|
48 | 49 | |
|
49 | 50 | output = `#{cmd}` |
|
50 | 51 | |
|
51 | 52 | Dir.chdir(cur_dir) |
|
52 | 53 | |
|
53 | 54 | return "import CMD: #{cmd}\n" + output |
|
54 | 55 | end |
|
55 | 56 | return '' |
|
56 | 57 | end |
|
57 | 58 | |
|
58 | 59 | def self.call_import_testcase(problem_name) |
|
59 | 60 | if GraderScript.grader_control_enabled? |
@@ -1,78 +1,80 | |||
|
1 | 1 | require 'tmpdir' |
|
2 | 2 | |
|
3 | 3 | class TestdataImporter |
|
4 | 4 | |
|
5 | 5 | attr :log_msg |
|
6 | 6 | |
|
7 | 7 | def initialize(problem) |
|
8 | 8 | @problem = problem |
|
9 | 9 | end |
|
10 | 10 | |
|
11 | + #Create or update problem according to the parameter | |
|
11 | 12 |
def import_from_file(tempfile, |
|
12 | 13 |
time_limit, |
|
13 | 14 | memory_limit, |
|
14 | 15 | checker_name='text', |
|
15 | 16 | import_to_db=false) |
|
16 | 17 | |
|
17 | 18 | dirname = extract(tempfile) |
|
18 | 19 | return false if not dirname |
|
19 | 20 | if not import_to_db |
|
20 | 21 | @log_msg = GraderScript.call_import_problem(@problem.name, |
|
21 | 22 | dirname, |
|
22 | 23 | time_limit, |
|
23 | 24 | memory_limit, |
|
24 | 25 | checker_name) |
|
25 | 26 | else |
|
26 | 27 | # Import test data to test pairs. |
|
27 | 28 | |
|
28 | 29 | @problem.test_pairs.clear |
|
29 | 30 | if import_test_pairs(dirname) |
|
30 | 31 | test_pair_count = TestPair.count :conditions => "problem_id = #{@problem.id}" |
|
31 | 32 | @log_msg = "Importing test pair successful. (#{test_pair_count} test pairs imported)" |
|
32 | 33 | else |
|
33 | 34 | @log_msg = "Importing test pair failed. (0 test pairs imported)" |
|
34 | 35 | end |
|
35 | 36 | end |
|
36 | 37 | |
|
37 | 38 | @log_msg << import_problem_description(dirname) |
|
38 | 39 | @log_msg << import_problem_pdf(dirname) |
|
39 | 40 | @log_msg << import_full_score(dirname) |
|
40 | 41 | |
|
41 | 42 | #import test data |
|
42 | 43 | @log_msg << GraderScript.call_import_testcase(@problem.name) |
|
43 | 44 | |
|
44 | 45 | return true |
|
45 | 46 | end |
|
46 | 47 | |
|
47 | 48 | protected |
|
48 | 49 | |
|
49 | 50 | def self.long_ext(filename) |
|
50 | 51 | i = filename.index('.') |
|
51 | 52 | len = filename.length |
|
52 | 53 | return filename.slice(i..len) |
|
53 | 54 | end |
|
54 | 55 | |
|
56 | + # extract an archive file located at +tempfile+ to the +raw_dir+ | |
|
55 | 57 | def extract(tempfile) |
|
56 | 58 | testdata_filename = save_testdata_file(tempfile) |
|
57 | 59 | ext = TestdataImporter.long_ext(tempfile.original_filename) |
|
58 | 60 | |
|
59 | 61 | extract_dir = File.join(GraderScript.raw_dir, @problem.name) |
|
60 | 62 | if File.exists? extract_dir |
|
61 | 63 | backup_count = 0 |
|
62 | 64 | begin |
|
63 | 65 | backup_count += 1 |
|
64 | 66 | backup_dirname = "#{extract_dir}.backup.#{backup_count}" |
|
65 | 67 | end while File.exists? backup_dirname |
|
66 | 68 | File.rename(extract_dir, backup_dirname) |
|
67 | 69 | end |
|
68 | 70 | Dir.mkdir extract_dir |
|
69 | 71 | |
|
70 | 72 | if ext=='.tar.gz' or ext=='.tgz' |
|
71 | 73 | cmd = "tar -zxvf #{testdata_filename} -C #{extract_dir}" |
|
72 | 74 | elsif ext=='.tar' |
|
73 | 75 | cmd = "tar -xvf #{testdata_filename} -C #{extract_dir}" |
|
74 | 76 | elsif ext=='.zip' |
|
75 | 77 | cmd = "unzip -o #{testdata_filename} -d #{extract_dir}" |
|
76 | 78 | else |
|
77 | 79 | return nil |
|
78 | 80 | end |
You need to be logged in to leave comments.
Login now