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

r719:19bd5319581a - - 95 files changed: 1214 inserted, 401 deleted

new file 100644
@@ -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://coffeescript.org/
new file 100644
new file 100644
@@ -0,0 +1,3
1 + // Place all the styles related to the tags controller here.
2 + // They will automatically be included in application.css.
3 + // You can use Sass (SCSS) here: http://sass-lang.com/
new file 100644
@@ -0,0 +1,104
1 + class GroupsController < ApplicationController
2 + before_action :set_group, only: [:show, :edit, :update, :destroy,
3 + :add_user, :remove_user,:remove_all_user,
4 + :add_problem, :remove_problem,:remove_all_problem,
5 + ]
6 + before_action :authenticate, :admin_authorization
7 +
8 + # GET /groups
9 + def index
10 + @groups = Group.all
11 + end
12 +
13 + # GET /groups/1
14 + def show
15 + end
16 +
17 + # GET /groups/new
18 + def new
19 + @group = Group.new
20 + end
21 +
22 + # GET /groups/1/edit
23 + def edit
24 + end
25 +
26 + # POST /groups
27 + def create
28 + @group = Group.new(group_params)
29 +
30 + if @group.save
31 + redirect_to @group, notice: 'Group was successfully created.'
32 + else
33 + render :new
34 + end
35 + end
36 +
37 + # PATCH/PUT /groups/1
38 + def update
39 + if @group.update(group_params)
40 + redirect_to @group, notice: 'Group was successfully updated.'
41 + else
42 + render :edit
43 + end
44 + end
45 +
46 + # DELETE /groups/1
47 + def destroy
48 + @group.destroy
49 + redirect_to groups_url, notice: 'Group was successfully destroyed.'
50 + end
51 +
52 + def remove_user
53 + user = User.find(params[:user_id])
54 + @group.users.delete(user)
55 + redirect_to group_path(@group), flash: {success: "User #{user.login} was removed from the group #{@group.name}"}
56 + end
57 +
58 + def remove_all_user
59 + @group.users.clear
60 + redirect_to group_path(@group), alert: 'All users removed'
61 + end
62 +
63 + def remove_all_problem
64 + @group.problems.clear
65 + redirect_to group_path(@group), alert: 'All problems removed'
66 + end
67 +
68 + def add_user
69 + user = User.find(params[:user_id])
70 + begin
71 + @group.users << user
72 + redirect_to group_path(@group), flash: { success: "User #{user.login} was add to the group #{@group.name}"}
73 + rescue => e
74 + redirect_to group_path(@group), alert: e.message
75 + end
76 + end
77 +
78 + def remove_problem
79 + problem = Problem.find(params[:problem_id])
80 + @group.problems.delete(problem)
81 + redirect_to group_path(@group), flash: {success: "Problem #{problem.name} was removed from the group #{@group.name}" }
82 + end
83 +
84 + def add_problem
85 + problem = Problem.find(params[:problem_id])
86 + begin
87 + @group.problems << problem
88 + redirect_to group_path(@group), flash: {success: "Problem #{problem.name} was add to the group #{@group.name}" }
89 + rescue => e
90 + redirect_to group_path(@group), alert: e.message
91 + end
92 + end
93 +
94 + private
95 + # Use callbacks to share common setup or constraints between actions.
96 + def set_group
97 + @group = Group.find(params[:id])
98 + end
99 +
100 + # Only allow a trusted parameter "white list" through.
101 + def group_params
102 + params.require(:group).permit(:name, :description)
103 + end
104 + end
@@ -0,0 +1,60
1 + class TagsController < ApplicationController
2 + before_action :set_tag, only: [:show, :edit, :update, :destroy]
3 +
4 + # GET /tags
5 + def index
6 + @tags = Tag.all
7 + end
8 +
9 + # GET /tags/1
10 + def show
11 + end
12 +
13 + # GET /tags/new
14 + def new
15 + @tag = Tag.new
16 + end
17 +
18 + # GET /tags/1/edit
19 + def edit
20 + end
21 +
22 + # POST /tags
23 + def create
24 + @tag = Tag.new(tag_params)
25 +
26 + if @tag.save
27 + redirect_to @tag, notice: 'Tag was successfully created.'
28 + else
29 + render :new
30 + end
31 + end
32 +
33 + # PATCH/PUT /tags/1
34 + def update
35 + if @tag.update(tag_params)
36 + redirect_to @tag, notice: 'Tag was successfully updated.'
37 + else
38 + render :edit
39 + end
40 + end
41 +
42 + # DELETE /tags/1
43 + def destroy
44 + #remove any association
45 + ProblemTag.where(tag_id: @tag.id).destroy_all
46 + @tag.destroy
47 + redirect_to tags_url, notice: 'Tag was successfully destroyed.'
48 + end
49 +
50 + private
51 + # Use callbacks to share common setup or constraints between actions.
52 + def set_tag
53 + @tag = Tag.find(params[:id])
54 + end
55 +
56 + # Only allow a trusted parameter "white list" through.
57 + def tag_params
58 + params.require(:tag).permit(:name, :description, :public)
59 + end
60 + end
@@ -0,0 +1,2
1 + module GroupsHelper
2 + end
@@ -0,0 +1,2
1 + module TagsHelper
2 + end
@@ -0,0 +1,13
1 + class Group < ActiveRecord::Base
2 + has_many :groups_problems, class_name: GroupProblem
3 + has_many :problems, :through => :groups_problems
4 +
5 + has_many :groups_users, class_name: GroupUser
6 + has_many :users, :through => :groups_users
7 +
8 + #has_and_belongs_to_many :problems
9 + #has_and_belongs_to_many :users
10 +
11 +
12 + end
13 +
@@ -0,0 +1,7
1 + class GroupProblem < ActiveRecord::Base
2 + self.table_name = 'groups_problems'
3 +
4 + belongs_to :problem
5 + belongs_to :group
6 + validates_uniqueness_of :problem_id, scope: :group_id, message: ->(object, data) { "'#{Problem.find(data[:value]).full_name}' is already in the group" }
7 + end
@@ -0,0 +1,7
1 + class GroupUser < ActiveRecord::Base
2 + self.table_name = 'groups_users'
3 +
4 + belongs_to :user
5 + belongs_to :group
6 + validates_uniqueness_of :user_id, scope: :group_id, message: ->(object, data) { "'#{User.find(data[:value]).full_name}' is already in the group" }
7 + end
@@ -0,0 +1,8
1 + class ProblemTag < ActiveRecord::Base
2 + self.table_name = 'problems_tags'
3 +
4 + belongs_to :problem
5 + belongs_to :tag
6 +
7 + validates_uniqueness_of :problem_id, scope: :tag_id, message: ->(object, data) { "'#{Problem.find(data[:value]).full_name}' is already has this tag" }
8 + end
@@ -0,0 +1,4
1 + class Tag < ActiveRecord::Base
2 + has_many :problems_tags, class_name: ProblemTag
3 + has_many :problems, through: :problems_tags
4 + end
@@ -0,0 +1,20
1 + %h1 Editing contest
2 + = form_for(@contest) do |f|
3 + = f.error_messages
4 + %table
5 + %tr
6 + %td= f.label :name
7 + %td= f.text_field :name
8 + %tr
9 + %td= f.label :title
10 + %td= f.text_field :title
11 + %tr
12 + %td
13 + %td
14 + = f.check_box :enabled
15 + = f.label :enabled
16 + %p
17 + = f.submit 'Update'
18 + = link_to 'Show', @contest
19 + |
20 + = link_to 'Back', contests_path
@@ -0,0 +1,27
1 + %h1 Listing contests
2 + .infobox
3 + %b Go back to:
4 + [#{link_to 'contest management', :controller => 'contest_management', :action => 'index'}]
5 + %p= link_to 'New contest', new_contest_path, class: 'btn btn-success'
6 + %table.table.table-striped
7 + %tr
8 + %th Name
9 + %th Title
10 + %th Enabled
11 + %th
12 + %th
13 + %th
14 +
15 + - @contests.each do |contest|
16 + - @contest = contest
17 + %tr
18 + -#%td= in_place_editor_field :contest, :name, {}, :rows => 1
19 + -#%td= in_place_editor_field :contest, :title, {}, :rows => 1
20 + -#%td= in_place_editor_field :contest, :enabled, {}, :rows => 1
21 + %td= best_in_place @contest, :name
22 + %td= best_in_place @contest, :title
23 + %td= best_in_place @contest, :enabled
24 + %td= link_to 'Show', contest
25 + %td= link_to 'Edit', edit_contest_path(contest)
26 + %td= link_to 'Destroy', contest, :confirm => 'Are you sure?', :method => :delete
27 + %br/
@@ -0,0 +1,18
1 + %h1 New contest
2 + = form_for(@contest) do |f|
3 + = f.error_messages
4 + %p
5 + = f.label :name
6 + %br/
7 + = f.text_field :name
8 + %p
9 + = f.label :title
10 + %br/
11 + = f.text_field :title
12 + %p
13 + = f.label :enabled
14 + %br/
15 + = f.check_box :enabled
16 + %p
17 + = f.submit 'Create'
18 + = link_to 'Back', contests_path
@@ -0,0 +1,11
1 + %h1
2 + Contest: #{h @contest.title}
3 + .infobox
4 + %b Go back to:
5 + [#{link_to 'contest management', :controller => 'contest_management', :action => 'index'}]
6 + %p
7 + %b Enabled:
8 + = h @contest.enabled
9 + = link_to 'Edit', edit_contest_path(@contest)
10 + |
11 + = link_to 'Back', contests_path
@@ -0,0 +1,16
1 + = form_for @group do |f|
2 + - if @group.errors.any?
3 + #error_explanation
4 + %h2= "#{pluralize(@group.errors.count, "error")} prohibited this group from being saved:"
5 + %ul
6 + - @group.errors.full_messages.each do |msg|
7 + %li= msg
8 +
9 + .form-group.field
10 + = f.label :name
11 + = f.text_field :name, class: 'form-control'
12 + .form-group.field
13 + = f.label :description
14 + = f.text_field :description, class: 'form-control'
15 + .form-group.actions
16 + = f.submit 'Save', class: 'btn btn-primary'
@@ -0,0 +1,7
1 + %h1 Editing group
2 +
3 + = render 'form'
4 +
5 + = link_to 'Show', @group
6 + \|
7 + = link_to 'Back', groups_path
@@ -0,0 +1,22
1 + %h1 Groups
2 +
3 + %p
4 + = link_to 'New Group', new_group_path, class: 'btn btn-primary'
5 + %table.table.table-hover
6 + %thead
7 + %tr
8 + %th Name
9 + %th Description
10 + %th
11 + %th
12 +
13 + %tbody
14 + - @groups.each do |group|
15 + %tr
16 + %td= group.name
17 + %td= group.description
18 + %td= link_to 'View', group, class: 'btn btn-default'
19 + %td= link_to 'Destroy', group, :method => :delete, :data => { :confirm => 'Are you sure?' }, class: 'btn btn-danger'
20 +
21 + %br
22 +
@@ -0,0 +1,5
1 + %h1 New group
2 +
3 + = render 'form'
4 +
5 + = link_to 'Back', groups_path
@@ -0,0 +1,73
1 + %p
2 + %b Name:
3 + = @group.name
4 + %p
5 + %b Description:
6 + = @group.description
7 +
8 + %br
9 + = link_to 'Edit', edit_group_path(@group)
10 + \|
11 + = link_to 'Back', groups_path
12 +
13 + .row
14 + .col-md-12
15 + %h1 Group details
16 + .row
17 + .col-md-6
18 + .panel.panel-default
19 + .panel-heading
20 + .panel-title Users in this group
21 + .panel-body
22 + =form_tag add_user_group_path(@group), class: 'form-inline' do
23 + .form-group
24 + =label_tag :user_id, "User"
25 + =select_tag :user_id, options_from_collection_for_select(User.all,'id','full_name'), class: 'select2'
26 + =submit_tag "Add",class: 'btn btn-primary'
27 +
28 +
29 + %table.table.table-hover
30 + %thead
31 + %tr
32 + %th Login
33 + %th Full name
34 + %th Remark
35 + %th= link_to 'Remove All', remove_all_user_group_path(@group), method: :delete, :data => { :confirm => "Remove ALL USERS from group?" }, class: 'btn btn-danger btn-sm'
36 +
37 + %tbody
38 + - @group.users.each do |user|
39 + %tr
40 + %td= user.login
41 + %td= user.full_name
42 + %td= user.remark
43 + %td= link_to 'Remove', remove_user_group_path(@group,user), :method => :delete, :data => { :confirm => "Remove #{user.full_name}?" }, class: 'btn btn-danger btn-sm'
44 + .col-md-6
45 + .panel.panel-default
46 + .panel-heading
47 + .panel-title Problems
48 + .panel-body
49 +
50 + =form_tag add_problem_group_path(@group), class: 'form-inline' do
51 + .form-group
52 + =label_tag :problem_id, "Problem"
53 + =select_tag :problem_id, options_from_collection_for_select(Problem.all,'id','full_name'), class: 'select2'
54 + =submit_tag "Add",class: 'btn btn-primary'
55 +
56 +
57 + %table.table.table-hover
58 + %thead
59 + %tr
60 + %th name
61 + %th Full name
62 + %th Full score
63 + %th= link_to 'Remove All', remove_all_problem_group_path(@group), method: :delete, :data => { :confirm => "Remove ALL PROBLEMS from group?" }, class: 'btn btn-danger btn-sm'
64 +
65 + %tbody
66 + - @group.problems.each do |problem|
67 + %tr
68 + %td= problem.name
69 + %td= problem.full_name
70 + %td= problem.full_score
71 + %td= link_to 'Remove', remove_problem_group_path(@group,problem), :method => :delete, :data => { :confirm => "Remove #{problem.full_name}?" }, class: 'btn btn-danger btn-sm'
72 +
73 +
@@ -0,0 +1,22
1 + = form_for @tag do |f|
2 + - if @tag.errors.any?
3 + #error_explanation
4 + %h2= "#{pluralize(@tag.errors.count, "error")} prohibited this tag from being saved:"
5 + %ul
6 + - @tag.errors.full_messages.each do |msg|
7 + %li= msg
8 +
9 + .row
10 + .col-md-6
11 + .form-group.field
12 + = f.label :name
13 + = f.text_field :name, class: 'form-control'
14 + .form-group.field
15 + = f.label :description
16 + = f.text_area :description, class: 'form-control'
17 + .form-group.field
18 + = f.label :public
19 + = f.text_field :public, class: 'form-control'
20 + .actions
21 + = f.submit 'Save', class: 'btn btn-primary'
22 + .col-md-6
@@ -0,0 +1,7
1 + %h1 Editing tag
2 +
3 + = render 'form'
4 +
5 + = link_to 'Show', @tag
6 + \|
7 + = link_to 'Back', tags_path
@@ -0,0 +1,26
1 + %h1 Tags
2 +
3 + = link_to 'New Tag', new_tag_path, class: 'btn btn-success'
4 +
5 + %table.table.table-hover
6 + %thead
7 + %tr
8 + %th Name
9 + %th Description
10 + %th Public
11 + %th
12 + %th
13 + %th
14 +
15 + %tbody
16 + - @tags.each do |tag|
17 + %tr
18 + %td= tag.name
19 + %td= tag.description
20 + %td= tag.public
21 + %td= link_to 'Show', tag
22 + %td= link_to 'Edit', edit_tag_path(tag)
23 + %td= link_to 'Destroy', tag, :method => :delete, :data => { :confirm => 'Are you sure?' }
24 +
25 + %br
26 +
@@ -0,0 +1,5
1 + %h1 New tag
2 +
3 + = render 'form'
4 +
5 + = link_to 'Back', tags_path
@@ -0,0 +1,15
1 + %p#notice= notice
2 +
3 + %p
4 + %b Name:
5 + = @tag.name
6 + %p
7 + %b Description:
8 + = @tag.description
9 + %p
10 + %b Public:
11 + = @tag.public
12 +
13 + = link_to 'Edit', edit_tag_path(@tag)
14 + \|
15 + = link_to 'Back', tags_path
@@ -0,0 +1,38
1 + = error_messages_for 'user'
2 + / [form:user]
3 + .form-group
4 + %label.col-md-2.control-label{for: :login} Login
5 + .col-md-4
6 + = text_field 'user', 'login', class: 'form-control'
7 + .col-md-6
8 + .form-group
9 + %label.col-md-2.control-label{for: :full_name} Full name
10 + .col-md-4
11 + = text_field 'user', 'full_name', class: 'form-control'
12 + .col-md-6
13 + .form-group
14 + %label.col-md-2.control-label{for: :password} Password
15 + .col-md-4
16 + = password_field 'user', 'password', class: 'form-control'
17 + .col-md-6
18 + .form-group
19 + %label.col-md-2.control-label{for: :password_confirmation} Password (confirm)
20 + .col-md-4
21 + = password_field 'user', 'password_confirmation', class: 'form-control'
22 + .col-md-6
23 + .form-group
24 + %label.col-md-2.control-label{for: :email} E-mail
25 + .col-md-4
26 + = email_field 'user', 'email', class: 'form-control'
27 + .col-md-6
28 + .form-group
29 + %label.col-md-2.control-label{for: :alias} Alias
30 + .col-md-4
31 + = text_field 'user', 'alias', class: 'form-control'
32 + .col-md-6
33 + .form-group
34 + %label.col-md-2.control-label{for: :remark} Remark
35 + .col-md-4
36 + = text_field 'user', 'remark', class: 'form-control'
37 + .col-md-6
38 + / [eoform:user]
@@ -0,0 +1,7
1 + %h1 New user
2 + = form_tag( {action: 'create'}, { class: 'form-horizontal'}) do
3 + = render :partial => 'form'
4 + .form-group
5 + .col-md-offset-2.col-md-10
6 + = submit_tag "Create", class: 'btn btn-primary'
7 + = link_to 'Back', :action => 'index'
@@ -0,0 +1,14
1 + %h1 User information
2 + - for column in User.content_columns
3 + %p
4 + %b
5 + = column.human_name
6 + \:
7 + = h @user.send(column.name)
8 + %p
9 + %strong Group
10 + \:
11 + = @user.groups.map{ |x| link_to(x.name,group_path(x)).html_safe}.join(', ').html_safe
12 + = link_to 'Edit', :action => 'edit', :id => @user
13 + |
14 + = link_to 'Back', :action => 'index'
@@ -0,0 +1,23
1 + # Be sure to restart your server when you modify this file.
2 +
3 + # Version of your assets, change this if you want to expire all your assets.
4 + Rails.application.config.assets.version = '1.0'
5 +
6 + # Add additional assets to the asset load path.
7 + # Rails.application.config.assets.paths << Emoji.images_path
8 + # Add Yarn node_modules folder to the asset load path.
9 + Rails.application.config.assets.paths << Rails.root.join('node_modules')
10 + Rails.application.config.assets.paths << Rails.root.join('vendor/assets/fonts')
11 +
12 + # Precompile additional assets.
13 + # application.js, application.css, and all non-JS/CSS in the app/assets
14 + # folder are already added.
15 + # Rails.application.config.assets.precompile += %w( admin.js admin.css )
16 +
17 + Rails.application.config.assets.precompile += ['announcement_refresh.js','effects.js','site_update.js']
18 + Rails.application.config.assets.precompile += ['local_jquery.js','tablesorter-theme.cafe.css']
19 + %w( announcements submissions configurations contests contest_management graders heartbeat
20 + login main messages problems report site sites sources tasks groups
21 + test user_admin users tags testcases).each do |controller|
22 + Rails.application.config.assets.precompile += ["#{controller}.js", "#{controller}.css"]
23 + end
@@ -0,0 +1,30
1 + class CreateGroups < ActiveRecord::Migration
2 +
3 + def change
4 + create_table :groups do |t|
5 + t.string :name
6 + t.string :description
7 + end
8 +
9 + create_join_table :groups, :users do |t|
10 + # t.index [:group_id, :user_id]
11 + t.index [:user_id, :group_id]
12 + end
13 +
14 + create_join_table :problems, :groups do |t|
15 + # t.index [:problem_id, :group_id]
16 + t.index [:group_id, :problem_id]
17 + end
18 +
19 + reversible do |change|
20 + change.up do
21 + GraderConfiguration.where(key: 'system.use_problem_group').first_or_create(value_type: 'boolean', value: 'false',
22 + description: 'If true, available problem to the user will be only ones associated with the group of the user');
23 + end
24 +
25 + change.down do
26 + GraderConfiguration.where(key: 'system.use_problem_group').destroy_all
27 + end
28 + end
29 + end
30 + end
@@ -0,0 +1,11
1 + class CreateTags < ActiveRecord::Migration
2 + def change
3 + create_table :tags do |t|
4 + t.string :name, null: false
5 + t.text :description
6 + t.boolean :public
7 +
8 + t.timestamps null: false
9 + end
10 + end
11 + end
@@ -0,0 +1,10
1 + class CreateProblemTags < ActiveRecord::Migration
2 + def change
3 + create_table :problems_tags do |t|
4 + t.references :problem, index: true, foreign_key: true
5 + t.references :tag, index: true, foreign_key: true
6 +
7 + t.index [:problem_id,:tag_id], unique: true
8 + end
9 + end
10 + end
@@ -0,0 +1,49
1 + require 'test_helper'
2 +
3 + class GroupsControllerTest < ActionController::TestCase
4 + setup do
5 + @group = groups(:one)
6 + end
7 +
8 + test "should get index" do
9 + get :index
10 + assert_response :success
11 + assert_not_nil assigns(:groups)
12 + end
13 +
14 + test "should get new" do
15 + get :new
16 + assert_response :success
17 + end
18 +
19 + test "should create group" do
20 + assert_difference('Group.count') do
21 + post :create, group: { description: @group.description, name: @group.name }
22 + end
23 +
24 + assert_redirected_to group_path(assigns(:group))
25 + end
26 +
27 + test "should show group" do
28 + get :show, id: @group
29 + assert_response :success
30 + end
31 +
32 + test "should get edit" do
33 + get :edit, id: @group
34 + assert_response :success
35 + end
36 +
37 + test "should update group" do
38 + patch :update, id: @group, group: { description: @group.description, name: @group.name }
39 + assert_redirected_to group_path(assigns(:group))
40 + end
41 +
42 + test "should destroy group" do
43 + assert_difference('Group.count', -1) do
44 + delete :destroy, id: @group
45 + end
46 +
47 + assert_redirected_to groups_path
48 + end
49 + end
@@ -0,0 +1,49
1 + require 'test_helper'
2 +
3 + class TagsControllerTest < ActionController::TestCase
4 + setup do
5 + @tag = tags(:one)
6 + end
7 +
8 + test "should get index" do
9 + get :index
10 + assert_response :success
11 + assert_not_nil assigns(:tags)
12 + end
13 +
14 + test "should get new" do
15 + get :new
16 + assert_response :success
17 + end
18 +
19 + test "should create tag" do
20 + assert_difference('Tag.count') do
21 + post :create, tag: { description: @tag.description, name: @tag.name, public: @tag.public }
22 + end
23 +
24 + assert_redirected_to tag_path(assigns(:tag))
25 + end
26 +
27 + test "should show tag" do
28 + get :show, id: @tag
29 + assert_response :success
30 + end
31 +
32 + test "should get edit" do
33 + get :edit, id: @tag
34 + assert_response :success
35 + end
36 +
37 + test "should update tag" do
38 + patch :update, id: @tag, tag: { description: @tag.description, name: @tag.name, public: @tag.public }
39 + assert_redirected_to tag_path(assigns(:tag))
40 + end
41 +
42 + test "should destroy tag" do
43 + assert_difference('Tag.count', -1) do
44 + delete :destroy, id: @tag
45 + end
46 +
47 + assert_redirected_to tags_path
48 + end
49 + end
@@ -0,0 +1,9
1 + # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
2 +
3 + one:
4 + problem_id:
5 + tag_id:
6 +
7 + two:
8 + problem_id:
9 + tag_id:
@@ -0,0 +1,11
1 + # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
2 +
3 + one:
4 + name: MyString
5 + description: MyString
6 + public:
7 +
8 + two:
9 + name: MyString
10 + description: MyString
11 + public:
@@ -0,0 +1,7
1 + require 'test_helper'
2 +
3 + class ProblemTagTest < ActiveSupport::TestCase
4 + # test "the truth" do
5 + # assert true
6 + # end
7 + end
@@ -0,0 +1,7
1 + require 'test_helper'
2 +
3 + class TagTest < ActiveSupport::TestCase
4 + # test "the truth" do
5 + # assert true
6 + # end
7 + end
@@ -38,54 +38,55
38 38
39 39 # Use unicorn as the app server
40 40 # gem 'unicorn'
41 41
42 42 # Deploy with Capistrano
43 43 # gem 'capistrano'
44 44
45 45 # To use debugger
46 46 # gem 'debugger'
47 47 #
48 48
49 49 #in-place editor
50 50 gem 'best_in_place', '~> 3.0.1'
51 51
52 52 # jquery addition
53 53 gem 'jquery-rails'
54 54 gem 'jquery-ui-rails'
55 55 gem 'jquery-timepicker-addon-rails'
56 56 gem 'jquery-tablesorter'
57 57 gem 'jquery-countdown-rails'
58 58
59 59 #syntax highlighter
60 60 gem 'rouge'
61 61
62 - #add bootstrap
62 + #bootstrap add-ons
63 63 gem 'bootstrap-sass', '~> 3.2.0'
64 64 gem 'bootstrap-switch-rails'
65 65 gem 'bootstrap-toggle-rails'
66 66 gem 'autoprefixer-rails'
67 -
68 - #bootstrap sortable
69 67 gem 'momentjs-rails'
70 68 gem 'rails_bootstrap_sortable'
69 + gem 'bootstrap-datepicker-rails'
70 + gem 'bootstrap3-datetimepicker-rails'
71 + gem 'jquery-datatables-rails'
71 72
72 73 #----------- user interface -----------------
73 74 #select 2
74 75 gem 'select2-rails'
75 76 #ace editor
76 77 gem 'ace-rails-ap'
77 78 #paginator
78 79 gem 'will_paginate', '~> 3.0.7'
79 80
80 81 gem 'mail'
81 82 gem 'rdiscount'
82 83 gem 'dynamic_form'
83 84 gem 'in_place_editing'
84 85 gem 'verification', :git => 'https://github.com/sikachu/verification.git'
85 86
86 87
87 88 #---------------- testiing -----------------------
88 89 gem 'minitest-reporters'
89 90
90 91 #---------------- for console --------------------
91 92 gem 'fuzzy-string-match'
@@ -40,84 +40,93
40 40 builder (~> 3.1)
41 41 activerecord (4.2.7.1)
42 42 activemodel (= 4.2.7.1)
43 43 activesupport (= 4.2.7.1)
44 44 arel (~> 6.0)
45 45 activerecord-session_store (1.0.0)
46 46 actionpack (>= 4.0, < 5.1)
47 47 activerecord (>= 4.0, < 5.1)
48 48 multi_json (~> 1.11, >= 1.11.2)
49 49 rack (>= 1.5.2, < 3)
50 50 railties (>= 4.0, < 5.1)
51 51 activesupport (4.2.7.1)
52 52 i18n (~> 0.7)
53 53 json (~> 1.7, >= 1.7.7)
54 54 minitest (~> 5.1)
55 55 thread_safe (~> 0.3, >= 0.3.4)
56 56 tzinfo (~> 1.1)
57 57 ansi (1.5.0)
58 58 arel (6.0.4)
59 59 autoprefixer-rails (6.6.0)
60 60 execjs
61 61 best_in_place (3.0.3)
62 62 actionpack (>= 3.2)
63 63 railties (>= 3.2)
64 + bootstrap-datepicker-rails (1.7.1.1)
65 + railties (>= 3.0)
64 66 bootstrap-sass (3.2.0.2)
65 67 sass (~> 3.2)
66 68 bootstrap-switch-rails (3.3.3)
67 69 bootstrap-toggle-rails (2.2.1.0)
70 + bootstrap3-datetimepicker-rails (4.17.47)
71 + momentjs-rails (>= 2.8.1)
68 72 builder (3.2.2)
69 73 coffee-rails (4.2.1)
70 74 coffee-script (>= 2.2.0)
71 75 railties (>= 4.0.0, < 5.2.x)
72 76 coffee-script (2.4.1)
73 77 coffee-script-source
74 78 execjs
75 79 coffee-script-source (1.12.2)
76 80 concurrent-ruby (1.0.4)
77 81 dynamic_form (1.1.4)
78 82 erubis (2.7.0)
79 83 execjs (2.7.0)
80 84 fuzzy-string-match (1.0.0)
81 85 RubyInline (>= 3.8.6)
82 86 globalid (0.3.7)
83 87 activesupport (>= 4.1.0)
84 88 haml (4.0.7)
85 89 tilt
86 90 haml-rails (0.9.0)
87 91 actionpack (>= 4.0.1)
88 92 activesupport (>= 4.0.1)
89 93 haml (>= 4.0.6, < 5.0)
90 94 html2haml (>= 1.0.1)
91 95 railties (>= 4.0.1)
92 96 html2haml (2.0.0)
93 97 erubis (~> 2.7.0)
94 98 haml (~> 4.0.0)
95 99 nokogiri (~> 1.6.0)
96 100 ruby_parser (~> 3.5)
97 101 i18n (0.7.0)
98 102 in_place_editing (1.2.0)
99 103 jquery-countdown-rails (2.0.2)
104 + jquery-datatables-rails (3.4.0)
105 + actionpack (>= 3.1)
106 + jquery-rails
107 + railties (>= 3.1)
108 + sass-rails
100 109 jquery-rails (4.2.1)
101 110 rails-dom-testing (>= 1, < 3)
102 111 railties (>= 4.2.0)
103 112 thor (>= 0.14, < 2.0)
104 113 jquery-tablesorter (1.23.3)
105 114 railties (>= 3.2, < 6)
106 115 jquery-timepicker-addon-rails (1.4.1)
107 116 railties (>= 3.1)
108 117 jquery-ui-rails (6.0.1)
109 118 railties (>= 3.2.16)
110 119 json (1.8.3)
111 120 loofah (2.0.3)
112 121 nokogiri (>= 1.5.9)
113 122 mail (2.6.4)
114 123 mime-types (>= 1.16, < 4)
115 124 mime-types (3.1)
116 125 mime-types-data (~> 3.2015)
117 126 mime-types-data (3.2016.0521)
118 127 mini_portile2 (2.1.0)
119 128 minitest (5.10.1)
120 129 minitest-reporters (1.1.13)
121 130 ansi
122 131 builder
123 132 minitest (>= 5.0)
@@ -180,56 +189,59
180 189 actionpack (>= 4.0)
181 190 activesupport (>= 4.0)
182 191 sprockets (>= 3.0.0)
183 192 sqlite3 (1.3.12)
184 193 thor (0.19.4)
185 194 thread_safe (0.3.5)
186 195 tilt (2.0.5)
187 196 tzinfo (1.2.2)
188 197 thread_safe (~> 0.1)
189 198 uglifier (3.0.4)
190 199 execjs (>= 0.3.0, < 3)
191 200 will_paginate (3.0.12)
192 201 yaml_db (0.4.2)
193 202 rails (>= 3.0, < 5.1)
194 203 rake (>= 0.8.7)
195 204
196 205 PLATFORMS
197 206 ruby
198 207
199 208 DEPENDENCIES
200 209 ace-rails-ap
201 210 activerecord-session_store
202 211 autoprefixer-rails
203 212 best_in_place (~> 3.0.1)
213 + bootstrap-datepicker-rails
204 214 bootstrap-sass (~> 3.2.0)
205 215 bootstrap-switch-rails
206 216 bootstrap-toggle-rails
217 + bootstrap3-datetimepicker-rails
207 218 coffee-rails
208 219 dynamic_form
209 220 fuzzy-string-match
210 221 haml
211 222 haml-rails
212 223 in_place_editing
213 224 jquery-countdown-rails
225 + jquery-datatables-rails
214 226 jquery-rails
215 227 jquery-tablesorter
216 228 jquery-timepicker-addon-rails
217 229 jquery-ui-rails
218 230 mail
219 231 minitest-reporters
220 232 momentjs-rails
221 233 mysql2
222 234 rails (~> 4.2.0)
223 235 rails_bootstrap_sortable
224 236 rdiscount
225 237 rouge
226 238 sass-rails
227 239 select2-rails
228 240 sqlite3
229 241 uglifier
230 242 verification!
231 243 will_paginate (~> 3.0.7)
232 244 yaml_db
233 245
234 246 BUNDLED WITH
235 - 1.13.6
247 + 1.15.4
@@ -1,41 +1,47
1 1 // This is a manifest file that'll be compiled into application.js, which will include all the files
2 2 // listed below.
3 3 //
4 4 // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5 5 // or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
6 6 //
7 7 // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8 8 // the compiled file.
9 9 //
10 10 // WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD
11 11 // GO AFTER THE REQUIRES BELOW.
12 12 //
13 13 //= require jquery
14 14 //= require jquery_ujs
15 + //= require dataTables/jquery.dataTables
16 + //= require dataTables/bootstrap/3/jquery.dataTables.bootstrap
15 17 //= require jquery-ui
16 18 //= require bootstrap-sprockets
17 19 //= require moment
20 + //= require moment/th
18 21 //= require bootstrap-sortable
22 + //= require bootstrap-datetimepicker
19 23 //= require select2
20 24 //= require ace-rails-ap
21 25 //= require ace/mode-c_cpp
22 26 //= require ace/mode-python
23 27 //= require ace/mode-ruby
24 28 //= require ace/mode-pascal
25 29 //= require ace/mode-javascript
26 30 //= require ace/mode-java
27 31 //= require ace/theme-merbivore
28 32 //= require custom
29 33 //= require jquery.countdown
30 34 //-------------- addition from local_jquery -----------
31 35 //= require jquery-tablesorter
32 36 //= require best_in_place
33 37 //= require best_in_place.jquery-ui
34 38 //= require brython
39 + //= require bootstrap-datepicker
40 + //= require bootstrap-datetimepicker
35 41
36 42 // since this is after blank line, it is not downloaded
37 43 //x= require prototype
38 44 //x= require prototype_ujs
39 45 //x= require effects
40 46 //x= require dragdrop
41 47 //x= require controls
@@ -12,48 +12,51
12 12 * // bootstrap says that we should not do this, but @import each file instead
13 13 * # *= require_tree .
14 14 * # *= require_self
15 15 */
16 16
17 17 @import "jquery-ui";
18 18 //@import "jquery.ui.core";
19 19 //@import "jquery.ui.theme";
20 20 //@import "jquery.ui.datepicker";
21 21 //@import "jquery.ui.slider";
22 22 @import "jquery-ui-timepicker-addon";
23 23 @import "jquery-tablesorter/theme.metro-dark";
24 24 @import "jquery.countdown";
25 25 @import "tablesorter-theme.cafe";
26 26
27 27 //bootstrap
28 28 @import "bootstrap-sprockets";
29 29 @import "bootstrap";
30 30 @import "select2";
31 31 @import "select2-bootstrap";
32 32
33 33 //@import bootstrap3-switch
34 34 @import "bootstrap-toggle";
35 35 @import "bootstrap-sortable";
36 + @import "bootstrap-datepicker3";
37 + @import "bootstrap-datetimepicker";
38 + @import "dataTables/bootstrap/3/jquery.dataTables.bootstrap";
36 39
37 40 //bootstrap navbar color (from)
38 41 $bgDefault: #19197b;
39 42 $bgHighlight: #06064b;
40 43 $colDefault: #8e8eb4;
41 44 $colHighlight: #ffffff;
42 45 $dropDown: false;
43 46
44 47 @font-face {
45 48 font-family: 'Glyphicons Halflings';
46 49 src: font-path('bootstrap/glyphicons-halflings-regular.eot');
47 50 src: font-path('bootstrap/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'),
48 51 font-path('bootstrap/glyphicons-halflings-regular.woff') format('woff'),
49 52 font-path('bootstrap/glyphicons-halflings-regular.ttf') format('truetype'),
50 53 font-path('bootstrap/glyphicons-halflings-regular.svg#glyphicons-halflingsregular') format('svg');
51 54 }
52 55
53 56
54 57 .navbar-default {
55 58 background-color: $bgDefault;
56 59 border-color: $bgHighlight;
57 60
58 61 .navbar-brand {
59 62 color: $colDefault;
@@ -525,24 +528,30
525 528 }
526 529 }
527 530 }
528 531
529 532 &.contest-title {
530 533 color: white;
531 534 text-align: center;
532 535 line-height: 2em;
533 536 }
534 537
535 538 &.registration-desc, &.test-desc {
536 539 border: 1px dotted gray;
537 540 background: #f5f5f5;
538 541 padding: 5px;
539 542 margin: 10px 0;
540 543 font-size: 12px;
541 544 line-height: 1.5em;
542 545 }
543 546 }
544 547
545 548 h2.contest-title {
546 549 margin-top: 5px;
547 550 margin-bottom: 5px;
548 551 }
552 +
553 +
554 +
555 + .grader-comment {
556 + word-wrap: break-word;
557 + }
@@ -18,91 +18,89
18 18 @current_user ||= User.find(session[:user_id])
19 19 end
20 20
21 21 def admin_authorization
22 22 return false unless authenticate
23 23 user = User.includes(:roles).find(session[:user_id])
24 24 unless user.admin?
25 25 unauthorized_redirect
26 26 return false
27 27 end
28 28 return true
29 29 end
30 30
31 31 def authorization_by_roles(allowed_roles)
32 32 return false unless authenticate
33 33 user = User.find(session[:user_id])
34 34 unless user.roles.detect { |role| allowed_roles.member?(role.name) }
35 35 unauthorized_redirect
36 36 return false
37 37 end
38 38 end
39 39
40 40 def testcase_authorization
41 41 #admin always has privileged
42 - puts "haha"
43 42 if @current_user.admin?
44 43 return true
45 44 end
46 45
47 - puts "hehe"
48 - puts GraderConfiguration["right.view_testcase"]
49 46 unauthorized_redirect unless GraderConfiguration["right.view_testcase"]
50 47 end
51 48
52 49 protected
53 50
54 51 def authenticate
55 52 unless session[:user_id]
56 53 flash[:notice] = 'You need to login'
57 54 if GraderConfiguration[SINGLE_USER_MODE_CONF_KEY]
58 55 flash[:notice] = 'You need to login but you cannot log in at this time'
59 56 end
60 57 redirect_to :controller => 'main', :action => 'login'
61 58 return false
62 59 end
63 60
61 +
64 62 # check if run in single user mode
65 63 if GraderConfiguration[SINGLE_USER_MODE_CONF_KEY]
66 - user = User.find_by_id(session[:user_id])
67 - if user==nil or (not user.admin?)
64 + if @current_user==nil or (not @current_user.admin?)
68 65 flash[:notice] = 'You cannot log in at this time'
69 66 redirect_to :controller => 'main', :action => 'login'
70 67 return false
71 68 end
72 - unless user.enabled?
73 - flash[:notice] = 'Your account is disabled'
74 - redirect_to :controller => 'main', :action => 'login'
75 - return false
76 - end
77 69 return true
78 70 end
79 71
72 + # check if the user is enabled
73 + unless @current_user.enabled? or @current_user.admin?
74 + flash[:notice] = 'Your account is disabled'
75 + redirect_to :controller => 'main', :action => 'login'
76 + return false
77 + end
78 +
80 79 if GraderConfiguration.multicontests?
81 - user = User.find(session[:user_id])
82 - return true if user.admin?
80 + return true if @current_user.admin?
83 81 begin
84 - if user.contest_stat(true).forced_logout
82 + if @current_user.contest_stat(true).forced_logout
85 83 flash[:notice] = 'You have been automatically logged out.'
86 84 redirect_to :controller => 'main', :action => 'index'
87 85 end
88 86 rescue
89 87 end
90 88 end
91 89 return true
92 90 end
93 91
94 92 def authenticate_by_ip_address
95 93 #this assume that we have already authenticate normally
96 94 unless GraderConfiguration[MULTIPLE_IP_LOGIN_CONF_KEY]
97 95 user = User.find(session[:user_id])
98 96 if (not user.admin? and user.last_ip and user.last_ip != request.remote_ip)
99 97 flash[:notice] = "You cannot use the system from #{request.remote_ip}. Your last ip is #{user.last_ip}"
100 98 redirect_to :controller => 'main', :action => 'login'
101 99 puts "CHEAT: user #{user.login} tried to login from '#{request.remote_ip}' while last ip is '#{user.last_ip}' at #{Time.zone.now}"
102 100 return false
103 101 end
104 102 unless user.last_ip
105 103 user.last_ip = request.remote_ip
106 104 user.save
107 105 end
108 106 end
@@ -1,43 +1,27
1 1 class GradersController < ApplicationController
2 2
3 - before_filter :admin_authorization, except: [ :submission ]
4 - before_filter(only: [:submission]) {
5 - #check if authenticated
6 - return false unless authenticate
7 -
8 - #admin always has privileged
9 - if @current_user.admin?
10 - return true
11 - end
12 -
13 - if GraderConfiguration["right.user_view_submission"] and Submission.find(params[:id]).problem.available?
14 - return true
15 - else
16 - unauthorized_redirect
17 - return false
18 - end
19 - }
3 + before_filter :admin_authorization
20 4
21 5 verify :method => :post, :only => ['clear_all',
22 6 'start_exam',
23 7 'start_grading',
24 8 'stop_all',
25 9 'clear_terminated'],
26 10 :redirect_to => {:action => 'index'}
27 11
28 12 def index
29 13 redirect_to :action => 'list'
30 14 end
31 15
32 16 def list
33 17 @grader_processes = GraderProcess.find_running_graders
34 18 @stalled_processes = GraderProcess.find_stalled_process
35 19
36 20 @terminated_processes = GraderProcess.find_terminated_graders
37 21
38 22 @last_task = Task.last
39 23 @last_test_request = TestRequest.last
40 24 @submission = Submission.order("id desc").limit(20)
41 25 @backlog_submission = Submission.where('graded_at is null')
42 26 end
43 27
@@ -56,67 +40,48
56 40
57 41 def clear_all
58 42 GraderProcess.all.each do |p|
59 43 p.destroy
60 44 end
61 45 redirect_to :action => 'list'
62 46 end
63 47
64 48 def view
65 49 if params[:type]=='Task'
66 50 redirect_to :action => 'task', :id => params[:id]
67 51 else
68 52 redirect_to :action => 'test_request', :id => params[:id]
69 53 end
70 54 end
71 55
72 56 def test_request
73 57 @test_request = TestRequest.find(params[:id])
74 58 end
75 59
76 60 def task
77 61 @task = Task.find(params[:id])
78 62 end
79 63
80 - def submission
81 - @submission = Submission.find(params[:id])
82 - formatter = Rouge::Formatters::HTML.new(css_class: 'highlight', line_numbers: true )
83 - lexer = case @submission.language.name
84 - when "c" then Rouge::Lexers::C.new
85 - when "cpp" then Rouge::Lexers::Cpp.new
86 - when "pas" then Rouge::Lexers::Pas.new
87 - when "ruby" then Rouge::Lexers::Ruby.new
88 - when "python" then Rouge::Lexers::Python.new
89 - when "java" then Rouge::Lexers::Java.new
90 - when "php" then Rouge::Lexers::PHP.new
91 - end
92 - @formatted_code = formatter.format(lexer.lex(@submission.source))
93 - @css_style = Rouge::Themes::ThankfulEyes.render(scope: '.highlight')
94 -
95 - user = User.find(session[:user_id])
96 - SubmissionViewLog.create(user_id: session[:user_id],submission_id: @submission.id) unless user.admin?
97 -
98 - end
99 64
100 65 # various grader controls
101 66
102 67 def stop
103 68 grader_proc = GraderProcess.find(params[:id])
104 69 GraderScript.stop_grader(grader_proc.pid)
105 70 flash[:notice] = 'Grader stopped. It may not disappear now, but it should disappear shortly.'
106 71 redirect_to :action => 'list'
107 72 end
108 73
109 74 def stop_all
110 75 GraderScript.stop_graders(GraderProcess.find_running_graders +
111 76 GraderProcess.find_stalled_process)
112 77 flash[:notice] = 'Graders stopped. They may not disappear now, but they should disappear shortly.'
113 78 redirect_to :action => 'list'
114 79 end
115 80
116 81 def start_grading
117 82 GraderScript.start_grader('grading')
118 83 flash[:notice] = '2 graders in grading env started, one for grading queue tasks, another for grading test request'
119 84 redirect_to :action => 'list'
120 85 end
121 86
122 87 def start_exam
@@ -1,57 +1,63
1 1 class LoginController < ApplicationController
2 2
3 3 def index
4 4 # show login screen
5 5 reset_session
6 6 redirect_to :controller => 'main', :action => 'login'
7 7 end
8 8
9 9 def login
10 - if (!GraderConfiguration['right.bypass_agreement']) and (!params[:accept_agree])
10 + user = User.authenticate(params[:login], params[:password])
11 + unless user
12 + flash[:notice] = 'Wrong password'
13 + redirect_to :controller => 'main', :action => 'login'
14 + return
15 + end
16 +
17 + if (!GraderConfiguration['right.bypass_agreement']) and (!params[:accept_agree]) and !user.admin?
11 18 flash[:notice] = 'You must accept the agreement before logging in'
12 19 redirect_to :controller => 'main', :action => 'login'
13 - elsif user = User.authenticate(params[:login], params[:password])
20 + return
21 + end
22 +
23 + #process logging in
14 24 session[:user_id] = user.id
15 25 session[:admin] = user.admin?
16 26
17 27 # clear forced logout flag for multicontests contest change
18 28 if GraderConfiguration.multicontests?
19 29 contest_stat = user.contest_stat
20 30 if contest_stat.respond_to? :forced_logout
21 31 if contest_stat.forced_logout
22 32 contest_stat.forced_logout = false
23 33 contest_stat.save
24 34 end
25 35 end
26 36 end
27 37
28 38 #save login information
29 39 Login.create(user_id: user.id, ip_address: request.remote_ip)
30 40
31 41 redirect_to :controller => 'main', :action => 'list'
32 - else
33 - flash[:notice] = 'Wrong password'
34 - redirect_to :controller => 'main', :action => 'login'
35 - end
36 42 end
37 43
38 44 def site_login
39 45 begin
40 46 site = Site.find(params[:login][:site_id])
41 47 rescue ActiveRecord::RecordNotFound
42 48 site = nil
43 49 end
44 50 if site==nil
45 51 flash[:notice] = 'Wrong site'
46 52 redirect_to :controller => 'main', :action => 'login' and return
47 53 end
48 54 if (site.password) and (site.password == params[:login][:password])
49 55 session[:site_id] = site.id
50 56 redirect_to :controller => 'site', :action => 'index'
51 57 else
52 58 flash[:notice] = 'Wrong site password'
53 59 redirect_to :controller => 'site', :action => 'login'
54 60 end
55 61 end
56 62
57 63 end
@@ -65,60 +65,60
65 65 @submission.user = user
66 66 @submission.language_id = 0
67 67 if (params['file']) and (params['file']!='')
68 68 @submission.source = File.open(params['file'].path,'r:UTF-8',&:read)
69 69 @submission.source.encode!('UTF-8','UTF-8',invalid: :replace, replace: '')
70 70 @submission.source_filename = params['file'].original_filename
71 71 end
72 72
73 73 if (params[:editor_text])
74 74 language = Language.find_by_id(params[:language_id])
75 75 @submission.source = params[:editor_text]
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 - if @submission.valid?
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,151 +144,167
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 + elsif params.has_key? 'add_group'
199 + group = Group.find(params[:group_id])
200 + ok = []
201 + failed = []
202 + get_problems_from_params.each do |p|
203 + begin
204 + group.problems << p
205 + ok << p.full_name
206 + rescue => e
207 + failed << p.full_name
198 208 end
209 + end
210 + flash[:success] = "The following problems are added to the group #{group.name}: " + ok.join(', ') if ok.count > 0
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
216 + end
217 +
199 218 redirect_to :action => 'manage'
200 219 end
201 220
202 221 def import
203 222 @allow_test_pair_import = allow_test_pair_import?
204 223 end
205 224
206 225 def do_import
207 226 old_problem = Problem.find_by_name(params[:name])
208 227 if !allow_test_pair_import? and params.has_key? :import_to_db
209 228 params.delete :import_to_db
210 229 end
211 230 @problem, import_log = Problem.create_from_import_form_params(params,
212 231 old_problem)
213 232
214 233 if !@problem.errors.empty?
215 234 render :action => 'import' and return
216 235 end
217 236
218 237 if old_problem!=nil
219 238 flash[:notice] = "The test data has been replaced for problem #{@problem.name}"
220 239 end
221 240 @log = import_log
222 241 end
223 242
224 243 def remove_contest
225 244 problem = Problem.find(params[:id])
226 245 contest = Contest.find(params[:contest_id])
227 246 if problem!=nil and contest!=nil
228 247 problem.contests.delete(contest)
229 248 end
230 249 redirect_to :action => 'manage'
231 250 end
232 251
233 252 ##################################
234 253 protected
235 254
236 255 def allow_test_pair_import?
237 256 if defined? ALLOW_TEST_PAIR_IMPORT
238 257 return ALLOW_TEST_PAIR_IMPORT
239 258 else
240 259 return false
241 260 end
242 261 end
243 262
244 263 def change_date_added
245 264 problems = get_problems_from_params
246 - year = params[:date_added][:year].to_i
247 - month = params[:date_added][:month].to_i
248 - day = params[:date_added][:day].to_i
249 - date = Date.new(year,month,day)
265 + date = Date.parse(params[:date_added])
250 266 problems.each do |p|
251 267 p.date_added = date
252 268 p.save
253 269 end
254 270 end
255 271
256 272 def add_to_contest
257 273 problems = get_problems_from_params
258 274 contest = Contest.find(params[:contest][:id])
259 275 if contest!=nil and contest.enabled
260 276 problems.each do |p|
261 277 p.contests << contest
262 278 end
263 279 end
264 280 end
265 281
266 282 def set_available(avail)
267 283 problems = get_problems_from_params
268 284 problems.each do |p|
269 285 p.available = avail
270 286 p.save
271 287 end
272 288 end
273 289
274 290 def get_problems_from_params
275 291 problems = []
276 292 params.keys.each do |k|
277 293 if k.index('prob-')==0
278 294 name, id, order = k.split('-')
279 295 problems << Problem.find(id)
280 296 end
281 297 end
282 298 problems
283 299 end
284 300
285 301 def get_problems_stat
286 302 end
287 303
288 304 private
289 305
290 306 def problem_params
291 - 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:[])
292 308 end
293 309
294 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|
@@ -1,27 +1,27
1 1 class SubmissionsController < ApplicationController
2 2 before_action :authenticate
3 - before_action :submission_authorization, only: [:show, :direct_edit_submission, :download, :edit]
3 + before_action :submission_authorization, only: [:show, :download, :edit]
4 4 before_action :admin_authorization, only: [:rejudge]
5 5
6 6 # GET /submissions
7 7 # GET /submissions.json
8 8 # Show problem selection and user's submission of that problem
9 9 def index
10 10 @user = @current_user
11 11 @problems = @user.available_problems
12 12
13 13 if params[:problem_id]==nil
14 14 @problem = nil
15 15 @submissions = nil
16 16 else
17 17 @problem = Problem.find_by_id(params[:problem_id])
18 18 if (@problem == nil) or (not @problem.available)
19 19 redirect_to main_list_path
20 20 flash[:notice] = 'Error: submissions for that problem are not viewable.'
21 21 return
22 22 end
23 23 @submissions = Submission.find_all_by_user_problem(@user.id, @problem.id).order(id: :desc)
24 24 end
25 25 end
26 26
27 27 # GET /submissions/1
@@ -30,79 +30,86
30 30 @submission = Submission.find(params[:id])
31 31
32 32 #log the viewing
33 33 user = User.find(session[:user_id])
34 34 SubmissionViewLog.create(user_id: session[:user_id],submission_id: @submission.id) unless user.admin?
35 35
36 36 @task = @submission.task
37 37 end
38 38
39 39 def download
40 40 @submission = Submission.find(params[:id])
41 41 send_data(@submission.source, {:filename => @submission.download_filename, :type => 'text/plain'})
42 42 end
43 43
44 44 def compiler_msg
45 45 @submission = Submission.find(params[:id])
46 46 respond_to do |format|
47 47 format.js
48 48 end
49 49 end
50 50
51 51 #on-site new submission on specific problem
52 52 def direct_edit_problem
53 53 @problem = Problem.find(params[:problem_id])
54 + unless @current_user.can_view_problem?(@problem)
55 + unauthorized_redirect
56 + return
57 + end
54 58 @source = ''
59 + if (params[:view_latest])
60 + sub = Submission.find_last_by_user_and_problem(@current_user.id,@problem.id)
61 + @source = @submission.source.to_s if @submission and @submission.source
62 + end
55 63 render 'edit'
56 64 end
57 65
58 66 # GET /submissions/1/edit
59 67 def edit
60 68 @submission = Submission.find(params[:id])
61 69 @source = @submission.source.to_s
62 70 @problem = @submission.problem
63 71 @lang_id = @submission.language.id
64 72 end
65 73
66 74
67 75 def get_latest_submission_status
68 76 @problem = Problem.find(params[:pid])
69 77 @submission = Submission.find_last_by_user_and_problem(params[:uid],params[:pid])
70 78 puts User.find(params[:uid]).login
71 79 puts Problem.find(params[:pid]).name
72 80 puts 'nil' unless @submission
73 81 respond_to do |format|
74 82 format.js
75 83 end
76 84 end
77 85
78 86 # GET /submissions/:id/rejudge
79 87 def rejudge
80 88 @submission = Submission.find(params[:id])
81 89 @task = @submission.task
82 90 @task.status_inqueue! if @task
83 91 respond_to do |format|
84 92 format.js
85 93 end
86 94 end
87 95
88 96 protected
89 97
90 98 def submission_authorization
91 99 #admin always has privileged
92 100 if @current_user.admin?
93 101 return true
94 102 end
95 103
96 104 sub = Submission.find(params[:id])
97 - if sub.problem.available?
98 - puts "sub = #{sub.user.id}, current = #{@current_user.id}"
105 + if @current_user.available_problems.include? sub.problem
99 106 return true if GraderConfiguration["right.user_view_submission"] or sub.user == @current_user
100 107 end
101 108
102 109 #default to NO
103 110 unauthorized_redirect
104 111 return false
105 112 end
106 113
107 114
108 115 end
@@ -5,49 +5,49
5 5 def index
6 6 redirect_to :action => 'list'
7 7 end
8 8
9 9 def list
10 10 @problems = @user.available_problems
11 11 end
12 12
13 13 # this has contest-wide access control
14 14 def view
15 15 base_name = params[:file]
16 16 base_filename = File.basename("#{base_name}.#{params[:ext]}")
17 17 filename = "#{Problem.download_file_basedir}/#{base_filename}"
18 18
19 19 if !FileTest.exists?(filename)
20 20 redirect_to :action => 'index' and return
21 21 end
22 22
23 23 send_file_to_user(filename, base_filename)
24 24 end
25 25
26 26 # this has problem-level access control
27 27 def download
28 28 problem = Problem.find(params[:id])
29 - if !problem or !problem.available or !@user.can_view_problem? problem
29 + unless @current_user.can_view_problem? problem
30 30 redirect_to :action => 'index' and return
31 31 end
32 32
33 33 base_name = params[:file]
34 34 base_filename = File.basename("#{base_name}.#{params[:ext]}")
35 35 filename = "#{Problem.download_file_basedir}/#{params[:id]}/#{base_filename}"
36 36 puts "SENDING: #{filename}"
37 37
38 38 if !FileTest.exists?(filename)
39 39 redirect_to :action => 'index' and return
40 40 end
41 41
42 42 puts "SENDING: #{filename}"
43 43
44 44 send_file_to_user(filename, base_filename)
45 45 end
46 46
47 47 protected
48 48
49 49 def send_file_to_user(filename, base_filename)
50 50 if defined?(USE_APACHE_XSENDFILE) and USE_APACHE_XSENDFILE
51 51 response.headers['Content-Type'] = "application/force-download"
52 52 response.headers['Content-Disposition'] = "attachment; filename=\"#{File.basename(filename)}\""
53 53 response.headers["X-Sendfile"] = filename
@@ -3,48 +3,49
3 3 class UserAdminController < ApplicationController
4 4
5 5 include MailHelperMethods
6 6
7 7 before_filter :admin_authorization
8 8
9 9 # GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html)
10 10 verify :method => :post, :only => [
11 11 :create, :create_from_list,
12 12 :update,
13 13 :manage_contest,
14 14 :bulk_mail
15 15 ],
16 16 :redirect_to => { :action => :list }
17 17
18 18 def index
19 19 @user_count = User.count
20 20 if params[:page] == 'all'
21 21 @users = User.all
22 22 @paginated = false
23 23 else
24 24 @users = User.paginate :page => params[:page]
25 25 @paginated = true
26 26 end
27 + @users = User.all
27 28 @hidden_columns = ['hashed_password', 'salt', 'created_at', 'updated_at']
28 29 @contests = Contest.enabled
29 30 end
30 31
31 32 def active
32 33 sessions = ActiveRecord::SessionStore::Session.where("updated_at >= ?", 60.minutes.ago)
33 34 @users = []
34 35 sessions.each do |session|
35 36 if session.data[:user_id]
36 37 @users << User.find(session.data[:user_id])
37 38 end
38 39 end
39 40 end
40 41
41 42 def show
42 43 @user = User.find(params[:id])
43 44 end
44 45
45 46 def new
46 47 @user = User.new
47 48 end
48 49
49 50 def create
50 51 @user = User.new(user_params)
@@ -207,48 +208,49
207 208 def import
208 209 if params[:file]==''
209 210 flash[:notice] = 'Error importing no file'
210 211 redirect_to :action => 'index' and return
211 212 end
212 213 import_from_file(params[:file])
213 214 end
214 215
215 216 def random_all_passwords
216 217 users = User.all
217 218 @prefix = params[:prefix] || ''
218 219 @non_admin_users = User.find_non_admin_with_prefix(@prefix)
219 220 @changed = false
220 221 if request.request_method == 'POST'
221 222 @non_admin_users.each do |user|
222 223 password = random_password
223 224 user.password = password
224 225 user.password_confirmation = password
225 226 user.save
226 227 end
227 228 @changed = true
228 229 end
229 230 end
230 231
232 +
231 233 # contest management
232 234
233 235 def contests
234 236 @contest, @users = find_contest_and_user_from_contest_id(params[:id])
235 237 @contests = Contest.enabled
236 238 end
237 239
238 240 def assign_from_list
239 241 contest_id = params[:users_contest_id]
240 242 org_contest, users = find_contest_and_user_from_contest_id(contest_id)
241 243 contest = Contest.find(params[:new_contest][:id])
242 244 if !contest
243 245 flash[:notice] = 'Error: no contest'
244 246 redirect_to :action => 'contests', :id =>contest_id
245 247 end
246 248
247 249 note = []
248 250 users.each do |u|
249 251 u.contests = [contest]
250 252 note << u.login
251 253 end
252 254 flash[:notice] = 'User(s) ' + note.join(', ') +
253 255 " were successfully reassigned to #{contest.title}."
254 256 redirect_to :action => 'contests', :id =>contest.id
@@ -390,74 +392,91
390 392 if !mail_body or mail_body.blank?
391 393 flash[:notice] = 'You entered an empty mail body.'
392 394 redirect_to :action => 'mass_mailing' and return
393 395 end
394 396
395 397 note = []
396 398 users = []
397 399 lines.split("\n").each do |line|
398 400 user = User.find_by_login(line.chomp)
399 401 if user
400 402 send_mail(user.email, mail_subject, mail_body)
401 403 note << user.login
402 404 end
403 405 end
404 406
405 407 flash[:notice] = 'User(s) ' + note.join(', ') +
406 408 ' were successfully modified. '
407 409 redirect_to :action => 'mass_mailing'
408 410 end
409 411
410 412 #bulk manage
411 413 def bulk_manage
412 414
413 415 begin
414 - @users = User.where('login REGEXP ?',params[:regex]) if params[:regex]
416 + @users = User.where('(login REGEXP ?) OR (remark REGEXP ?)',params[:regex],params[:regex]) if params[:regex]
415 417 @users.count if @users #i don't know why I have to call count, but if I won't exception is not raised
416 418 rescue Exception
417 419 flash[:error] = 'Regular Expression is malformed'
418 420 @users = nil
419 421 end
420 422
421 423 if params[:commit]
422 424 @action = {}
423 425 @action[:set_enable] = params[:enabled]
424 426 @action[:enabled] = params[:enable] == "1"
425 427 @action[:gen_password] = params[:gen_password]
428 + @action[:add_group] = params[:add_group]
429 + @action[:group_name] = params[:group_name]
426 430 end
427 431
428 432 if params[:commit] == "Perform"
429 433 if @action[:set_enable]
430 434 @users.update_all(enabled: @action[:enabled])
431 435 end
432 436 if @action[:gen_password]
433 437 @users.each do |u|
434 438 password = random_password
435 439 u.password = password
436 440 u.password_confirmation = password
437 441 u.save
438 442 end
439 443 end
444 + if @action[:add_group] and @action[:group_name]
445 + @group = Group.find(@action[:group_name])
446 + ok = []
447 + failed = []
448 + @users.each do |user|
449 + begin
450 + @group.users << user
451 + ok << user.login
452 + rescue => e
453 + failed << user.login
454 + end
455 + end
456 + flash[:success] = "The following users are added to the 'group #{@group.name}': " + ok.join(', ') if ok.count > 0
457 + flash[:alert] = "The following users are already in the 'group #{@group.name}': " + failed.join(', ') if failed.count > 0
458 + end
440 459 end
441 460 end
442 461
443 462 protected
444 463
445 464 def random_password(length=5)
446 465 chars = 'abcdefghijkmnopqrstuvwxyz23456789'
447 466 newpass = ""
448 467 length.times { newpass << chars[rand(chars.size-1)] }
449 468 return newpass
450 469 end
451 470
452 471 def import_from_file(f)
453 472 data_hash = YAML.load(f)
454 473 @import_log = ""
455 474
456 475 country_data = data_hash[:countries]
457 476 site_data = data_hash[:sites]
458 477 user_data = data_hash[:users]
459 478
460 479 # import country
461 480 countries = {}
462 481 country_data.each_pair do |id,country|
463 482 c = Country.find_by_name(country[:name])
@@ -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.gmtime
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'
@@ -179,43 +182,43
179 182 <tr>
180 183 <td class="left-col">
181 184 #{user.full_name}<br/>
182 185 #{t 'title_bar.current_time'} #{format_short_time(Time.zone.now)}
183 186 #{time_left}
184 187 <br/>
185 188 </td>
186 189 <td class="right-col">#{contest_name}</td>
187 190 </tr>
188 191 </table>
189 192 </div>
190 193 TITLEBAR
191 194 result.html_safe
192 195 end
193 196
194 197 def markdown(text)
195 198 markdown = RDiscount.new(text)
196 199 markdown.to_html.html_safe
197 200 end
198 201
199 202
200 203 BOOTSTRAP_FLASH_MSG = {
201 204 success: 'alert-success',
202 205 error: 'alert-danger',
203 - alert: 'alert-block',
206 + alert: 'alert-danger',
204 207 notice: 'alert-info'
205 208 }
206 209
207 210 def bootstrap_class_for(flash_type)
208 211 BOOTSTRAP_FLASH_MSG.fetch(flash_type.to_sym, flash_type.to_s)
209 212 end
210 213
211 214 def flash_messages
212 215 flash.each do |msg_type, message|
213 216 concat(content_tag(:div, message, class: "alert #{bootstrap_class_for(msg_type)} fade in") do
214 217 concat content_tag(:button, 'x', class: "close", data: { dismiss: 'alert' })
215 218 concat message
216 219 end)
217 220 end
218 221 nil
219 222 end
220 223
221 224 end
@@ -1,38 +1,39
1 1 require 'yaml'
2 2
3 3 #
4 4 # This class also contains various login of the system.
5 5 #
6 6 class GraderConfiguration < ActiveRecord::Base
7 7
8 8 SYSTEM_MODE_CONF_KEY = 'system.mode'
9 9 TEST_REQUEST_EARLY_TIMEOUT_KEY = 'contest.test_request.early_timeout'
10 10 MULTICONTESTS_KEY = 'system.multicontests'
11 11 CONTEST_TIME_LIMIT_KEY = 'contest.time_limit'
12 12 MULTIPLE_IP_LOGIN_KEY = 'right.multiple_ip_login'
13 13 VIEW_TESTCASE = 'right.view_testcase'
14 14 SINGLE_USER_KEY = 'system.single_user_mode'
15 + SYSTEM_USE_PROBLEM_GROUP = 'system.use_problem_group'
15 16
16 17 cattr_accessor :config_cache
17 18 cattr_accessor :task_grading_info_cache
18 19 cattr_accessor :contest_time_str
19 20 cattr_accessor :contest_time
20 21
21 22 GraderConfiguration.config_cache = nil
22 23 GraderConfiguration.task_grading_info_cache = nil
23 24
24 25 def self.config_cached?
25 26 (defined? CONFIGURATION_CACHE_ENABLED) and (CONFIGURATION_CACHE_ENABLED)
26 27 end
27 28
28 29 def self.get(key)
29 30 if GraderConfiguration.config_cached?
30 31 if GraderConfiguration.config_cache == nil
31 32 self.read_config
32 33 end
33 34 return GraderConfiguration.config_cache[key]
34 35 else
35 36 return GraderConfiguration.read_one_key(key)
36 37 end
37 38 end
38 39
@@ -99,48 +100,52
99 100 return get(SYSTEM_MODE_CONF_KEY) == 'standard'
100 101 end
101 102
102 103 def self.contest_mode?
103 104 return get(SYSTEM_MODE_CONF_KEY) == 'contest'
104 105 end
105 106
106 107 def self.indv_contest_mode?
107 108 return get(SYSTEM_MODE_CONF_KEY) == 'indv-contest'
108 109 end
109 110
110 111 def self.multicontests?
111 112 return get(MULTICONTESTS_KEY) == true
112 113 end
113 114
114 115 def self.time_limit_mode?
115 116 mode = get(SYSTEM_MODE_CONF_KEY)
116 117 return ((mode == 'contest') or (mode == 'indv-contest'))
117 118 end
118 119
119 120 def self.analysis_mode?
120 121 return get(SYSTEM_MODE_CONF_KEY) == 'analysis'
121 122 end
122 123
124 + def self.use_problem_group?
125 + return get(SYSTEM_USE_PROBLEM_GROUP)
126 + end
127 +
123 128 def self.contest_time_limit
124 129 contest_time_str = GraderConfiguration[CONTEST_TIME_LIMIT_KEY]
125 130
126 131 if not defined? GraderConfiguration.contest_time_str
127 132 GraderConfiguration.contest_time_str = nil
128 133 end
129 134
130 135 if GraderConfiguration.contest_time_str != contest_time_str
131 136 GraderConfiguration.contest_time_str = contest_time_str
132 137 if tmatch = /(\d+):(\d+)/.match(contest_time_str)
133 138 h = tmatch[1].to_i
134 139 m = tmatch[2].to_i
135 140
136 141 GraderConfiguration.contest_time = h.hour + m.minute
137 142 else
138 143 GraderConfiguration.contest_time = nil
139 144 end
140 145 end
141 146 return GraderConfiguration.contest_time
142 147 end
143 148
144 149 protected
145 150
146 151 def self.convert_type(val,type)
@@ -1,28 +1,36
1 1 class Problem < ActiveRecord::Base
2 2
3 3 belongs_to :description
4 4 has_and_belongs_to_many :contests, :uniq => true
5 +
6 + #has_and_belongs_to_many :groups
7 + has_many :groups_problems, class_name: GroupProblem
8 + has_many :groups, :through => :groups_problems
9 +
10 + has_many :problems_tags, class_name: ProblemTag
11 + has_many :tags, through: :problems_tags
12 +
5 13 has_many :test_pairs, :dependent => :delete_all
6 14 has_many :testcases, :dependent => :destroy
7 15
8 16 validates_presence_of :name
9 17 validates_format_of :name, :with => /\A\w+\z/
10 18 validates_presence_of :full_name
11 19
12 20 scope :available, -> { where(available: true) }
13 21
14 22 DEFAULT_TIME_LIMIT = 1
15 23 DEFAULT_MEMORY_LIMIT = 32
16 24
17 25 def self.available_problems
18 26 available.order(date_added: :desc).order(:name)
19 27 #Problem.available.all(:order => "date_added DESC, name ASC")
20 28 end
21 29
22 30 def self.create_from_import_form_params(params, old_problem=nil)
23 31 org_problem = old_problem || Problem.new
24 32 import_params, problem = Problem.extract_params_and_check(params,
25 33 org_problem)
26 34
27 35 if !problem.errors.empty?
28 36 return problem, 'Error importing'
@@ -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
@@ -126,37 +126,41
126 126 end
127 127 end
128 128
129 129 def assign_language
130 130 self.language = Submission.find_language_in_source(self.source,
131 131 self.source_filename)
132 132 end
133 133
134 134 # validation codes
135 135 def must_specify_language
136 136 return if self.source==nil
137 137
138 138 # for output_only tasks
139 139 return if self.problem!=nil and self.problem.output_only
140 140
141 141 if self.language==nil
142 142 errors.add('source',"Cannot detect language. Did you submit a correct source file?") unless self.language!=nil
143 143 end
144 144 end
145 145
146 146 def must_have_valid_problem
147 147 return if self.source==nil
148 148 if self.problem==nil
149 149 errors.add('problem',"must be specified.")
150 - elsif (!self.problem.available) and (self.new_record?)
151 - errors.add('problem',"must be valid.")
150 + else
151 + #admin always have right
152 + return if self.user.admin?
153 +
154 + #check if user has the right to submit the problem
155 + errors.add('problem',"must be valid.") if (!self.user.available_problems.include?(self.problem)) and (self.new_record?)
152 156 end
153 157 end
154 158
155 159 # callbacks
156 160 def assign_latest_number_if_new_recond
157 161 return if !self.new_record?
158 162 latest = Submission.find_last_by_user_and_problem(self.user_id, self.problem_id)
159 163 self.number = (latest==nil) ? 1 : latest.number + 1;
160 164 end
161 165
162 166 end
@@ -1,34 +1,38
1 1 require 'digest/sha1'
2 2 require 'net/pop'
3 3 require 'net/https'
4 4 require 'net/http'
5 5 require 'json'
6 6
7 7 class User < ActiveRecord::Base
8 8
9 9 has_and_belongs_to_many :roles
10 10
11 + #has_and_belongs_to_many :groups
12 + has_many :groups_users, class_name: GroupUser
13 + has_many :groups, :through => :groups_users
14 +
11 15 has_many :test_requests, -> {order(submitted_at: DESC)}
12 16
13 17 has_many :messages, -> { order(created_at: DESC) },
14 18 :class_name => "Message",
15 19 :foreign_key => "sender_id"
16 20
17 21 has_many :replied_messages, -> { order(created_at: DESC) },
18 22 :class_name => "Message",
19 23 :foreign_key => "receiver_id"
20 24
21 25 has_one :contest_stat, :class_name => "UserContestStat", :dependent => :destroy
22 26
23 27 belongs_to :site
24 28 belongs_to :country
25 29
26 30 has_and_belongs_to_many :contests, -> { order(:name); uniq}
27 31
28 32 scope :activated_users, -> {where activated: true}
29 33
30 34 validates_presence_of :login
31 35 validates_uniqueness_of :login
32 36 validates_format_of :login, :with => /\A[\_A-Za-z0-9]+\z/
33 37 validates_length_of :login, :within => 3..30
34 38
@@ -219,73 +223,98
219 223 available_problems = contest.problems.available
220 224 contest_problems << {
221 225 :contest => contest,
222 226 :problems => available_problems
223 227 }
224 228 available_problems.each {|p| pin[p.id] = true}
225 229 end
226 230 other_avaiable_problems = Problem.available.find_all {|p| pin[p.id]==nil and p.contests.length==0}
227 231 contest_problems << {
228 232 :contest => nil,
229 233 :problems => other_avaiable_problems
230 234 }
231 235 return contest_problems
232 236 end
233 237
234 238 def solve_all_available_problems?
235 239 available_problems.each do |p|
236 240 u = self
237 241 sub = Submission.find_last_by_user_and_problem(u.id,p.id)
238 242 return false if !p or !sub or sub.points < p.full_score
239 243 end
240 244 return true
241 245 end
242 246
247 + #get a list of available problem
243 248 def available_problems
244 249 if not GraderConfiguration.multicontests?
250 + if GraderConfiguration.use_problem_group?
251 + return available_problems_in_group
252 + else
245 253 return Problem.available_problems
254 + end
246 255 else
247 256 contest_problems = []
248 257 pin = {}
249 258 contests.enabled.each do |contest|
250 259 contest.problems.available.each do |problem|
251 260 if not pin.has_key? problem.id
252 261 contest_problems << problem
253 262 end
254 263 pin[problem.id] = true
255 264 end
256 265 end
257 266 other_avaiable_problems = Problem.available.find_all {|p| pin[p.id]==nil and p.contests.length==0}
258 267 return contest_problems + other_avaiable_problems
259 268 end
260 269 end
261 270
271 + def available_problems_in_group
272 + problem = []
273 + self.groups.each do |group|
274 + group.problems.where(available: true).each { |p| problem << p }
275 + end
276 + problem.uniq!
277 + if problem
278 + problem.sort! do |a,b|
279 + case
280 + when a.date_added < b.date_added
281 + 1
282 + when a.date_added > b.date_added
283 + -1
284 + else
285 + a.name <=> b.name
286 + end
287 + end
288 + return problem
289 + else
290 + return []
291 + end
292 + end
293 +
262 294 def can_view_problem?(problem)
263 - if not GraderConfiguration.multicontests?
264 - return problem.available
265 - else
266 - return problem_in_user_contests? problem
267 - end
295 + return true if admin?
296 + return available_problems.include? problem
268 297 end
269 298
270 299 def self.clear_last_login
271 300 User.update_all(:last_ip => nil)
272 301 end
273 302
274 303 protected
275 304 def encrypt_new_password
276 305 return if password.blank?
277 306 self.salt = (10+rand(90)).to_s
278 307 self.hashed_password = User.encrypt(self.password,self.salt)
279 308 end
280 309
281 310 def assign_default_site
282 311 # have to catch error when migrating (because self.site is not available).
283 312 begin
284 313 if self.site==nil
285 314 self.site = Site.find_by_name('default')
286 315 if self.site==nil
287 316 self.site = Site.find(1) # when 'default has be renamed'
288 317 end
289 318 end
290 319 rescue
291 320 end
@@ -1,37 +1,37
1 1 <p>
2 2 <b>Author:</b>
3 3 <%=h @announcement.author %>
4 4 </p>
5 5
6 6 <p>
7 7 <b>Title:</b>
8 8 <%=h @announcement.title %>
9 9 </p>
10 10
11 11 <p>
12 12 <b>Notes:</b>
13 13 <%=h @announcement.notes %>
14 14 </p>
15 15
16 16 <p>
17 17 <b>Body:</b>
18 - <%=h @announcement.body %>
18 + <%=h markdown(@announcement.body) %>
19 19 </p>
20 20
21 21 <p>
22 22 <b>Published:</b>
23 23 <%=h @announcement.published %>
24 24 </p>
25 25
26 26 <p>
27 27 <b>Show on front page:</b>
28 28 <%=h @announcement.frontpage %>
29 29 </p>
30 30
31 31 <p>
32 32 <b>Show only in contest:</b>
33 33 <%=h @announcement.contest_only %>
34 34 </p>
35 35
36 36 <%= link_to 'Edit', edit_announcement_path(@announcement) %> |
37 37 <%= link_to 'Back', announcements_path %>
@@ -1,26 +1,28
1 -
2 1 - if submission.nil?
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_short_time(submission.submitted_at.localtime)
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 - = "#{format_short_time(submission.graded_at.localtime)} "
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 %strong View:
21 23 - if GraderConfiguration.show_grading_result
22 24 = link_to '[detailed result]', :action => 'result', :id => submission.id
23 - = link_to "#{t 'main.cmp_msg'}", {:action => 'compiler_msg', :id => submission.id}, {popup: true,class: 'btn btn-xs btn-info'}
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
@@ -1,15 +1,15
1 1 %h1= "Task: #{@task.id}"
2 2
3 3 %p
4 4 User:
5 5 = "#{@task.submission.user.login}"
6 6 %br/
7 7 Status:
8 8 = "#{@task.status_str} (at #{format_short_time(@task.updated_at)})"
9 9 %br/
10 10 = "Submission: #{@task.submission_id}"
11 11 - if @task.submission !=nil
12 - = link_to '[view submission]', :action => 'submission', :id => @task.submission.id
12 + = link_to '[view submission]', submission_path( @task.submission.id )
13 13 %br/
14 14 = "Submitted at: #{format_short_time(@task.created_at)}"
15 15 %br/
@@ -25,49 +25,51
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')
51 + = add_menu( 'User Groups', 'groups', 'index')
50 52 = add_menu( 'Graders', 'graders', 'list')
51 53 = add_menu( 'Message ', 'messages', 'console')
52 54 %li.divider{role: 'separator'}
53 55 = add_menu( 'System config', 'configurations', 'index')
54 56 %li.divider{role: 'separator'}
55 57 = add_menu( 'Sites', 'sites', 'index')
56 58 = add_menu( 'Contests', 'contest_management', 'index')
57 59 / report
58 60 %li.dropdown
59 61 %a.dropdown-toggle{href: '#', data: {toggle:'dropdown'}, aria: {haspopup:"true", expanded:"false"}, role: "button"}
60 62 Report
61 63 %span.caret
62 64 %ul.dropdown-menu
63 65 = add_menu( 'Current Score', 'report', 'current_score')
64 66 = add_menu( 'Score Report', 'report', 'max_score')
65 67 = add_menu( 'Report', 'report', 'multiple_login')
66 68 - if (ungraded = Submission.where('graded_at is null').where('submitted_at < ?', 1.minutes.ago).count) > 0
67 69 =link_to "#{ungraded} backlogs!",
68 70 grader_list_path,
69 71 class: 'navbar-btn btn btn-default btn-warning', data: {toggle: 'tooltip'},title: 'Number of ungraded submission'
70 72
71 73 %ul.nav.navbar-nav.navbar-right
72 74 = add_menu("#{content_tag(:span,'',class: 'glyphicon glyphicon-question-sign')}".html_safe, 'main', 'help')
73 75 = add_menu("#{content_tag(:span,'',class: 'glyphicon glyphicon-comment')}".html_safe, 'messages', 'list', {title: I18n.t('menu.messages'), data: {toggle: 'tooltip'}})
@@ -1,39 +1,43
1 1 %b= GraderConfiguration['ui.front.welcome_message']
2 2 %br/
3 3
4 4 - if !@hidelogin
5 5 =t 'login.message'
6 6 %br/
7 7 %br/
8 8
9 9 - if flash[:notice]
10 10 %hr/
11 11 %b= flash[:notice]
12 12 %hr/
13 13
14 14 %div{ :style => "border: solid 1px gray; padding: 4px; background: #eeeeff;"}
15 - = form_tag login_login_path do
16 - %table
17 - %tr
18 - %td{:align => "right"}
19 - ="#{t 'login_label'}:"
20 - %td= text_field_tag 'login'
21 - %tr
22 - %td{:align => "right"}
23 - ="#{t 'password_label'}:"
24 - %td= password_field_tag
15 + = form_tag login_login_path, {class: 'form-horizontal'} do
16 + .form-group
17 + =label_tag :login, "Login",class: 'col-sm-3 control-label'
18 + .col-sm-9
19 + =text_field_tag :login, nil, class: 'form-control'
20 + .form-group
21 + =label_tag :password, "Password", class: 'col-sm-3 control-label'
22 + .col-sm-9
23 + =password_field_tag :password, nil, class: 'form-control'
25 24 - unless GraderConfiguration['right.bypass_agreement']
26 - %tr
27 - %td{:align => "right"}= check_box_tag 'accept_agree'
28 - %td ยอมรับข้อตกลงการใช้งาน
25 + .form-group
26 + .col-sm-offset-3.col-sm-9
27 + .checkbox
28 + %label
29 + = check_box_tag 'accept_agree'
30 + ยอมรับข้อตกลงการใช้งาน
29 31
30 - = submit_tag t('login.login_submit')
32 + .form-group
33 + .col-sm-offset-3.col-sm-9
34 + = submit_tag t('login.login_submit'), class: 'btn btn-primary'
31 35 %br/
32 36
33 37 - if GraderConfiguration['system.online_registration']
34 38 =t 'login.participation'
35 39 %b
36 40 = "#{t 'login.please'} "
37 41 = link_to "#{t 'login.register'}", :controller => :users, :action => :new
38 42 %br/
39 43 = link_to "#{t 'login.forget_password'}", :controller => :users, :action => :forget
@@ -1,22 +1,22
1 1 %tr
2 2 %td
3 3 - if @current_user and @current_user.admin?
4 4 = link_to problem.name, stat_problem_path(problem)
5 5 - else
6 6 = "#{problem.name}"
7 7 %td
8 8 = "#{problem.full_name}"
9 9
10 10 %br
11 11 = link_to_description_if_any "[#{t 'main.problem_desc'}] <span class='glyphicon glyphicon-file'></span>".html_safe, problem
12 12 %td
13 13 = @prob_submissions[problem.id][:count]
14 - = link_to "[subs]", main_submission_path(problem.id)
14 + -#= link_to "[subs]", main_submission_path(problem.id)
15 15 %td
16 16 = render :partial => 'submission_short',
17 17 :locals => {:submission => @prob_submissions[problem.id][:submission], :problem_name => problem.name, :problem_id => problem.id }
18 18 %td
19 19 - if @prob_submissions[problem.id][:submission]
20 20 = link_to 'Edit', edit_submission_path(@prob_submissions[problem.id][:submission]), class: 'btn btn-success'
21 21 - else
22 22 = link_to 'New', direct_edit_problem_submissions_path(problem.id), class: 'btn btn-success'
@@ -1,29 +1,29
1 1
2 2 - if submission.nil?
3 3 = "-"
4 4 - else
5 5 - unless submission.graded_at
6 6 = t 'main.submitted_at'
7 7 = format_short_time(submission.submitted_at.localtime)
8 8 - else
9 9 %strong= t 'main.graded_at'
10 10 = "#{format_short_time(submission.graded_at.localtime)} "
11 11 %br
12 12 - if GraderConfiguration['ui.show_score']
13 13 %strong=t 'main.score'
14 14 = "#{(submission.points*100/submission.problem.full_score).to_i} "
15 15 = " ["
16 - %tt
16 + %tt.grader-comment
17 17 = submission.grader_comment
18 18 = "]"
19 19 %br
20 20 %strong View:
21 21 - if GraderConfiguration.show_grading_result
22 22 = link_to '[detailed result]', :action => 'result', :id => submission.id
23 23 /= link_to "#{t 'main.cmp_msg'}", {:action => 'compiler_msg', :id => submission.id}, {popup: true,class: 'btn btn-xs btn-info'}
24 24 = link_to "#{t 'main.cmp_msg'}", compiler_msg_submission_path(submission.id), {popup: true,remote: true,class: 'btn btn-xs btn-info'}
25 25 = link_to "#{t 'main.src_link'}",{:action => 'source', :id => submission.id}, class: 'btn btn-xs btn-info'
26 26 = link_to "#{t 'main.submissions_link'}", problem_submissions_path(problem_id), class: 'btn btn-xs btn-info'
27 27 - if GraderConfiguration.show_testcase
28 28 = link_to "testcases", show_problem_testcases_path(problem_id), class: 'btn btn-xs btn-info'
29 29
@@ -1,12 +1,11
1 1 %h1= GraderConfiguration['ui.front.title']
2 2
3 - %table
4 - %tr
5 - %td
3 + .row
4 + .col-md-6
6 5 - if @announcements.length!=0
7 6 .announcementbox{:style => 'margin-top: 0px'}
8 7 %span{:class => 'title'}
9 8 Announcements
10 9 = render :partial => 'announcement', :collection => @announcements
11 - %td{:style => 'vertical-align: top; width: 40%; padding-left: 20px;'}
10 + .col-md-4{style: "padding-left: 20px;"}
12 11 = render :partial => 'login_box'
@@ -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,54 +1,65
1 1 - content_for :head do
2 2 = stylesheet_link_tag 'problems'
3 - %h1 Listing problems
3 + %h1 Problems
4 4 %p
5 - = link_to 'New problem', new_problem_path, class: 'btn btn-default btn-sm'
6 - = link_to 'Manage problems', { action: 'manage'}, class: 'btn btn-default btn-sm'
7 - = link_to 'Import problems', {:action => 'import'}, class: 'btn btn-default btn-sm'
5 + = link_to 'Import problems', {:action => 'import'}, class: 'btn btn-success btn-sm'
6 + = link_to 'New problem', new_problem_path, class: 'btn btn-success btn-sm'
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
25 + %th
26 + Submit
27 + %sup{class: 'text-primary',data: {toggle: 'tooltip'}, title: 'Admin can always submit to any problem' } [?]
24 28 %th Date added
25 29 %th.text-center
26 30 Avail?
27 31 %sup{class: 'text-primary',data: {toggle: 'tooltip'}, title: 'Let user submits to this problem?' } [?]
28 32 %th.text-center
29 33 View Data?
30 34 %sup{class: 'text-primary',data: {toggle: 'tooltip'}, title: 'Let user view the testcase of this problem?' } [?]
31 35 %th.text-center
32 36 Test?
33 37 %sup{class: 'text-primary',data: {toggle: 'tooltip'}, title: 'Let user uses test interface on this problem?' } [?]
34 38 - if GraderConfiguration.multicontests?
35 39 %th Contests
36 40 - for problem in @problems
37 41 %tr{:class => "#{(problem.available) ? "success" : "danger"}", :id => "prob-#{problem.id}", :name => "prob-#{problem.id}"}
38 42 - @problem=problem
39 43 %td= problem.name #in_place_editor_field :problem, :name, {}, :rows=>1
40 - %td= problem.full_name #in_place_editor_field :problem, :full_name, {}, :rows=>1
44 + %td
45 + = problem.full_name #in_place_editor_field :problem, :full_name, {}, :rows=>1
46 + = link_to_description_if_any "[#{t 'main.problem_desc'}] <span class='glyphicon glyphicon-file'></span>".html_safe, problem
41 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
52 + %td= link_to "Submit", direct_edit_problem_submissions_path(problem,@current_user.id), class: 'btn btn-xs btn-primary'
42 53 %td= problem.date_added
43 54 %td= toggle_button(@problem.available?, toggle_problem_path(@problem), "problem-avail-#{@problem.id}")
44 55 %td= toggle_button(@problem.view_testcase?, toggle_view_testcase_problem_path(@problem), "problem-view-testcase-#{@problem.id}")
45 56 %td= toggle_button(@problem.test_allowed?, toggle_test_problem_path(@problem), "problem-test-#{@problem.id}")
46 57 - if GraderConfiguration.multicontests?
47 58 %td
48 59 = problem.contests.collect { |c| c.name }.join(', ')
49 60 %td= link_to 'Stat', {:action => 'stat', :id => problem.id}, class: 'btn btn-info btn-xs btn-block'
50 61 %td= link_to 'Show', {:action => 'show', :id => problem}, class: 'btn btn-info btn-xs btn-block'
51 62 %td= link_to 'Edit', {:action => 'edit', :id => problem}, class: 'btn btn-info btn-xs btn-block'
52 63 %td= link_to 'Destroy', { :action => 'destroy', :id => problem }, :confirm => 'Are you sure?', :method => :delete, class: 'btn btn-danger btn-xs btn-block'
53 64 %br/
54 65 = link_to '[New problem]', :action => 'new'
@@ -15,71 +15,104
15 15 }
16 16
17 17 $('tr input').click( function(e) {
18 18 if (e.shiftKey) {
19 19 stop = parseInt($(this).attr('id').split('-')[2]);
20 20 var orig_stop = stop
21 21 if (typeof start !== 'undefined') {
22 22 if (start > stop) {
23 23 var tmp = start;
24 24 start = stop;
25 25 stop = tmp;
26 26 }
27 27 shiftclick(start,stop,$(this).is(':checked') )
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 - %p= link_to '[Back to problem list]', :action => 'list'
39 + %p= link_to '[Back to problem list]', problems_path
40 40
41 41 = form_tag :action=>'do_manage' do
42 - .submitbox
42 + .panel.panel-primary
43 + .panel-heading
44 + Action
45 + .panel-body
46 + .submit-box
43 47 What do you want to do to the selected problem?
44 48 %br/
45 49 (You can shift-click to select a range of problems)
46 - %ul
50 + %ul.form-inline
47 51 %li
48 - Change date added to
49 - = select_date Date.current, :prefix => 'date_added'
52 + Change "Date added" to
53 + .input-group.date
54 + = text_field_tag :date_added, class: 'form-control'
55 + %span.input-group-addon
56 + %span.glyphicon.glyphicon-calendar
57 + -# = select_date Date.current, :prefix => 'date_added'
50 58 &nbsp;&nbsp;&nbsp;
51 - = submit_tag 'Change', :name => 'change_date_added'
59 + = submit_tag 'Change', :name => 'change_date_added', class: 'btn btn-primary btn-sm'
52 60 %li
53 - Set available to
54 - = submit_tag 'True', :name => 'enable_problem'
55 - = submit_tag 'False', :name => 'disable_problem'
61 + Set "Available" to
62 + = submit_tag 'True', :name => 'enable_problem', class: 'btn btn-primary btn-sm'
63 + = submit_tag 'False', :name => 'disable_problem', class: 'btn btn-primary btn-sm'
56 64
57 65 - if GraderConfiguration.multicontests?
58 66 %li
59 - Add to
67 + Add selected problems to contest
60 68 = select("contest","id",Contest.all.collect {|c| [c.title, c.id]})
61 - = submit_tag 'Add', :name => 'add_to_contest'
69 + = submit_tag 'Add', :name => 'add_to_contest', class: 'btn btn-primary btn-sm'
70 + %li
71 + Add selected problems to user group
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-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'
62 78
63 - %table
79 + %table.table.table-hover.datatable
80 + %thead
64 81 %tr{style: "text-align: left;"}
65 82 %th= check_box_tag 'select_all'
66 83 %th Name
67 84 %th Full name
85 + %th Tags
68 86 %th Available
69 87 %th Date added
70 88 - if GraderConfiguration.multicontests?
71 89 %th Contests
72 90
91 + %tbody
73 92 - num = 0
74 93 - for problem in @problems
75 94 - num += 1
76 95 %tr{:id => "row-prob-#{problem.id}", :name=> "prob-#{problem.id}"}
77 96 %td= check_box_tag "prob-#{problem.id}-#{num}"
78 97 %td= problem.name
79 98 %td= problem.full_name
99 + %td
100 + - problem.tags.each do |t|
101 + %span.label.label-default= t.name
80 102 %td= problem.available
81 103 %td= problem.date_added
82 104 - if GraderConfiguration.multicontests?
83 105 %td
84 106 - problem.contests.each do |contest|
85 107 = "(#{contest.name} [#{link_to 'x', :action => 'remove_contest', :id => problem.id, :contest_id => contest.id }])"
108 +
109 + :javascript
110 + $('.input-group.date').datetimepicker({
111 + format: 'DD/MMM/YYYY',
112 + showTodayButton: true,
113 + widgetPositioning: {horizontal: 'auto', vertical: 'bottom'},
114 +
115 + });
116 + $('.datatable').DataTable({
117 + paging: false
118 + });
@@ -4,50 +4,56
4 4 }
5 5
6 6 %h1 Problem stat: #{@problem.name}
7 7 %h2 Overview
8 8
9 9
10 10 %table.info
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 - %table.info#main_table
28 + %table#main_table.table.table-condensed.table-striped
29 29 %thead
30 - %tr.info-head
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 - %tr{class: row_odd ? "info-odd" : "info-even"}
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 - %td= time_ago_in_words(sub.submitted_at) + " ago"
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
56 + :javascript
57 + $("#main_table").DataTable({
58 + paging: false
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', true
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"
@@ -1,24 +1,36
1 1 %h1 Editing site
2 2 = error_messages_for :site
3 3 = form_for(@site) do |f|
4 - %p
5 - %b Name
6 - %br/
7 - = f.text_field :name
8 - %p
9 - %b Password
10 - %br/
11 - = f.text_field :password
12 - %p
13 - %b Started
14 - %br/
15 - = f.check_box :started
16 - %p
17 - %b Start time
18 - %br/
19 - = f.datetime_select :start_time, :include_blank => true
20 - %p
21 - = f.submit "Update"
4 + .row
5 + .col-md-4
6 + .form-group.field
7 + = f.label :name, "Name"
8 + = f.text_field :name, class: 'form-control'
9 + .form-group.field
10 + = f.label :password, "Password"
11 + = f.text_field :password, class: 'form-control'
12 + .form-group.field
13 + = f.label :started, "Started"
14 + = f.check_box :started, class: 'form-control'
15 + .form-group.field
16 + = f.label :start_time, "Start time"
17 + -# = f.datetime_select :start_time, :include_blank => true
18 + .input-group.date
19 + = f.text_field :start_time, class:'form-control' , value: (@site.start_time ? @site.start_time.strftime('%d/%b/%Y %H:%M') : '')
20 + %span.input-group-addon
21 + %span.glyphicon.glyphicon-calendar
22 + .actions
23 + = f.submit "Update", class: 'btn btn-primary'
24 + .col-md-8
25 +
22 26 = link_to 'Show', @site
23 27 |
24 28 = link_to 'Back', sites_path
29 +
30 +
31 + :javascript
32 + $('.input-group.date').datetimepicker({
33 + format: 'DD/MMM/YYYY HH:mm',
34 + showTodayButton: true,
35 + });
36 +
@@ -1,56 +1,59
1 1 %h2 Live submit
2 2 %br
3 3
4 4 %textarea#text_sourcecode{style: "display:none"}~ @source
5 5 .container
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 + - # submission form
14 15 = form_tag({controller: :main, :action => 'submit'}, :multipart => true, class: 'form') do
15 16
16 17 = hidden_field_tag 'editor_text', @source
17 18 = hidden_field_tag 'submission[problem_id]', @problem.id
18 19 .form-group
19 20 = label_tag "Task:"
20 21 = text_field_tag 'asdf', "#{@problem.long_name}", class: 'form-control', disabled: true
21 22
22 23 .form-group
23 24 = label_tag 'Language'
24 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"
25 26 .form-group
26 27 = submit_tag 'Submit', class: 'btn btn-success', id: 'live_submit',
27 28 data: {confirm: "Submitting this source code for task #{@problem.long_name}?"}
28 - .panel.panel-info
29 + - # latest submission status
30 + .panel{class: (@submission && @submission.graded_at) ? "panel-info" : "panel-warning"}
29 31 .panel-heading
30 32 Latest Submission Status
31 33 = link_to "Refresh",get_latest_submission_status_submissions_path(@submission.user,@problem), class: "btn btn-default btn-sm", remote: true if @submission
32 34 .panel-body
35 + %div#latest_status
33 36 - if @submission
34 37 = render :partial => 'submission_short',
35 38 :locals => {submission: @submission, problem_name: @problem.name, problem_id: @problem.id }
36 39 .row
37 40 .col-md-12
38 41 %h2 Console
39 42 %textarea#console{style: 'height: 100%; width: 100%;background-color:#000;color:#fff;font-family: consolas, monaco, "Droid Sans Mono";',rows: 20}
40 43
41 44 :javascript
42 45 $(document).ready(function() {
43 46 e = ace.edit("editor")
44 47 e.setValue($("#text_sourcecode").val());
45 48 e.gotoLine(1);
46 49 $("#language_id").trigger('change');
47 50 brython();
48 51 });
49 52
50 53
51 54 %script#__main__{type:'text/python3'}
52 55 :plain
53 56 import sys
54 57 import traceback
55 58
56 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}})}")
@@ -25,48 +25,57
25 25 matches every user whose login ends with "21"
26 26 .col-md-6
27 27 .panel.panel-primary
28 28 .panel-title.panel-heading
29 29 Action
30 30 .panel-body
31 31 .row.form-group
32 32 .col-md-6
33 33 %label.checkbox-inline
34 34 = check_box_tag "enabled", true, params[:enabled]
35 35 Change "Enabled" to
36 36 .col-md-3
37 37 %label.radio-inline
38 38 = radio_button_tag "enable", 1, params[:enable] == '1', id: 'enable-yes'
39 39 Yes
40 40 .col-md-3
41 41 %label.radio-inline
42 42 = radio_button_tag "enable", 0, params[:enable] == '0', id: 'enable-no'
43 43 No
44 44 .row.form-group
45 45 .col-md-6
46 46 %label.checkbox-inline
47 47 = check_box_tag "gen_password", true, params[:gen_password]
48 48 Generate new random password
49 + .row.form-group
50 + .col-md-4
51 + %label.checkbox-inline
52 + = check_box_tag "add_group", true, params[:add_group]
53 + Add users to group
54 + %label.col-md-3.control-label.text-right Group name
55 + .col-md-5
56 + = select_tag "group_name", options_from_collection_for_select( Group.all, 'id','name',params[:group_name]), id: 'group_name',class: 'form-control select2'
57 +
49 58
50 59 .row
51 60 .col-md-12
52 61 = submit_tag "Preview Result", class: 'btn btn-default'
53 62 - if @users
54 63 .row
55 64 .col-md-4
56 65 - if @action
57 66 %h2 Confirmation
58 67 - if @action[:set_enable]
59 68 .alert.alert-info The following users will be set #{(@action[:enabled] ? 'enable' : 'disable')}.
60 69 - if @action[:gen_password]
61 70 .alert.alert-info The password of the following users will be randomly generated.
62 71 .row
63 72 .col-md-4
64 73 = submit_tag "Perform", class: 'btn btn-primary'
65 74 .row
66 75 .col-md-12
67 76 The pattern matches #{@users.count} following users.
68 77 %br
69 78 - @users.each do |user|
70 79 = user.login
71 80 = ' '
72 81 = user.full_name
@@ -1,11 +1,13
1 1 %h1 Editing user
2 2
3 - = form_tag :action => 'update', :id => @user do
3 + = form_tag( {:action => 'update', :id => @user}, {class: 'form-horizontal'}) do
4 4 = error_messages_for 'user'
5 5 = render partial: "form"
6 - = submit_tag "Edit"
6 + .form-group
7 + .col-md-offset-2.col-md-4
8 + = submit_tag "Edit", class: 'btn btn-primary'
7 9
8 10
9 11 = link_to 'Show', :action => 'show', :id => @user
10 12 |
11 13 = link_to 'Back', :action => 'list'
@@ -1,101 +1,106
1 - %h1 Listing users
1 + %h1 Users
2 2
3 3 .panel.panel-primary
4 4 .panel-title.panel-heading
5 5 Quick Add
6 6 .panel-body
7 7 = form_tag( {method: 'post'}, {class: 'form-inline'}) do
8 8 .form-group
9 9 = label_tag 'user_login', 'Login'
10 10 = text_field 'user', 'login', :size => 10,class: 'form-control'
11 11 .form-group
12 12 = label_tag 'user_full_name', 'Full Name'
13 13 = text_field 'user', 'full_name', :size => 10,class: 'form-control'
14 14 .form-group
15 15 = label_tag 'user_password', 'Password'
16 16 = text_field 'user', 'password', :size => 10,class: 'form-control'
17 17 .form-group
18 18 = label_tag 'user_password_confirmation', 'Confirm'
19 19 = text_field 'user', 'password_confirmation', :size => 10,class: 'form-control'
20 20 .form-group
21 21 = label_tag 'user_email', 'email'
22 22 = text_field 'user', 'email', :size => 10,class: 'form-control'
23 23 =submit_tag "Create", class: 'btn btn-primary'
24 24
25 25 .panel.panel-primary
26 26 .panel-title.panel-heading
27 27 Import from site management
28 28 .panel-body
29 29 = form_tag({:action => 'import'}, :multipart => true,class: 'form form-inline') do
30 30 .form-group
31 31 = label_tag :file, 'File:'
32 32 .input-group
33 33 %span.input-group-btn
34 34 %span.btn.btn-default.btn-file
35 35 Browse
36 36 = file_field_tag 'file'
37 37 = text_field_tag '' , nil, {readonly: true, class: 'form-control'}
38 38 = submit_tag 'Submit', class: 'btn btn-default'
39 39
40 40
41 41 %p
42 42 = link_to '+ New user', { :action => 'new' }, { class: 'btn btn-success '}
43 43 = link_to '+ New list of users', { :action => 'new_list' }, { class: 'btn btn-success '}
44 + = link_to 'Bulk Manage', bulk_manage_user_admin_path , { class: 'btn btn-default btn-info'}
44 45 = link_to 'View administrators',{ :action => 'admin'}, { class: 'btn btn-default '}
45 - = link_to 'Bulk Manage', bulk_manage_user_admin_path , { class: 'btn btn-default '}
46 46 = link_to 'Random passwords',{ :action => 'random_all_passwords'}, { class: 'btn btn-default '}
47 47 = link_to 'View active users',{ :action => 'active'}, { class: 'btn btn-default '}
48 48 = link_to 'Mass mailing',{ :action => 'mass_mailing'}, { class: 'btn btn-default '}
49 49
50 50 - if GraderConfiguration.multicontests?
51 51 %br/
52 52 %b Multi-contest:
53 53 = link_to '[Manage bulk users in contests]', :action => 'contest_management'
54 54 View users in:
55 55 - @contests.each do |contest|
56 56 = link_to "[#{contest.name}]", :action => 'contests', :id => contest.id
57 57 = link_to "[no contest]", :action => 'contests', :id => 'none'
58 58
59 - Total #{@user_count} users |
60 - - if !@paginated
61 - Display all users.
62 - \#{link_to '[show in pages]', :action => 'index', :page => '1'}
63 - - else
64 - Display in pages.
65 - \#{link_to '[display all]', :action => 'index', :page => 'all'} |
66 - \#{will_paginate @users, :container => false}
59 + -# Total #{@user_count} users |
60 + -# - if !@paginated
61 + -# Display all users.
62 + -# \#{link_to '[show in pages]', :action => 'index', :page => '1'}
63 + -# - else
64 + -# Display in pages.
65 + -# \#{link_to '[display all]', :action => 'index', :page => 'all'} |
66 + -# \#{will_paginate @users, :container => false}
67 67
68 68
69 - %table.table.table-hover.table-condense
69 + %table.table.table-hover.table-condense.datatable
70 70 %thead
71 71 %th Login
72 72 %th Full name
73 73 %th email
74 74 %th Remark
75 75 %th
76 76 Activated
77 77 %sup{class: 'text-primary',data: {toggle: 'tooltip', placement: 'top'}, title: 'User has already confirmed the email?' } [?]
78 78 %th
79 79 Enabled
80 80 %sup{class: 'text-primary',data: {toggle: 'tooltip', placement: 'top'}, title: 'Allow the user to login?' } [?]
81 81 %th Last IP
82 82 %th
83 83 %th
84 84 %th
85 85 %th
86 86 - for user in @users
87 87 %tr
88 88 %td= link_to user.login, stat_user_path(user)
89 89 %td= user.full_name
90 90 %td= user.email
91 91 %td= user.remark
92 92 %td= toggle_button(user.activated?, toggle_activate_user_path(user),"toggle_activate_user_#{user.id}")
93 93 %td= toggle_button(user.enabled?, toggle_enable_user_path(user),"toggle_enable_user_#{user.id}")
94 94 %td= user.last_ip
95 95 %td= link_to 'Clear IP', {:action => 'clear_last_ip', :id => user, :page=>params[:page]}, :confirm => 'This will reset last logging in ip of the user, are you sure?', class: 'btn btn-default btn-xs btn-block'
96 96 %td= link_to 'Show', {:action => 'show', :id => user}, class: 'btn btn-default btn-xs btn-block'
97 97 %td= link_to 'Edit', {:action => 'edit', :id => user}, class: 'btn btn-default btn-xs btn-block'
98 - %td= link_to 'Destroy', { :action => 'destroy', :id => user }, :confirm => 'Are you sure?', :method => :delete, class: 'btn btn-danger btn-xs btn-block'
98 + %td= link_to 'Destroy', user_admin_destroy_path(user), data: {confirm: 'Are you sure?'}, method: :delete, class: 'btn btn-danger btn-xs btn-block'
99 99 %br/
100 100 = link_to '+ New user', { :action => 'new' }, { class: 'btn btn-success '}
101 101 = link_to '+ New list of users', { :action => 'new_list' }, { class: 'btn btn-success '}
102 +
103 + :javascript
104 + $('.datatable').DataTable({
105 + 'pageLength': 50
106 + });
@@ -15,52 +15,56
15 15
16 16 <b>Login:</b> #{@user.login} <br/>
17 17 <b>Full name:</b> #{@user.full_name} <br />
18 18
19 19
20 20 %h2 Problem Stat
21 21 %table.info
22 22 %thead
23 23 %tr.info-head
24 24 %th Stat
25 25 %th Value
26 26 %tbody
27 27 %tr{class: cycle('info-even','info-odd')}
28 28 %td.info_param Submissions
29 29 %td= @summary[:count]
30 30 %tr{class: cycle('info-even','info-odd')}
31 31 %td.info_param Solved/Attempted Problem
32 32 %td #{@summary[:solve]}/#{@summary[:attempt]} (#{(@summary[:solve]*100.0/@summary[:attempt]).round(1)}%)
33 33
34 34 %h2 Submission History
35 35
36 36 =render partial: 'application/bar_graph', locals: {histogram: @histogram, param: {bar_width: 7}}
37 37
38 38
39 - %table.tablesorter-cafe#submission_table
39 + %table#submission_table.table.table-striped
40 40 %thead
41 41 %tr
42 42 %th ID
43 43 %th Problem code
44 44 %th Problem full name
45 45 %th Language
46 46 %th Submitted at
47 47 %th Result
48 48 %th Score
49 49 - if session[:admin]
50 50 %th IP
51 51 %tbody
52 52 - @submission.each do |s|
53 53 - next unless s.problem
54 54 %tr
55 55 %td= link_to s.id, submission_path(s)
56 56 %td= link_to s.problem.name, stat_problem_path(s.problem)
57 57 %td= s.problem.full_name
58 58 %td= s.language.pretty_name
59 59 %td #{s.submitted_at.strftime('%Y-%m-%d %H:%M')} (#{time_ago_in_words(s.submitted_at)} ago)
60 60 %td.fix-width= s.grader_comment
61 61 %td= ( s.try(:points) ? (s.points*100/s.problem.full_score) : '' )
62 62 - if session[:admin]
63 63 %td= s.ip_address
64 64
65 65
66 66
67 + :javascript
68 + $("#submission_table").DataTable({
69 + paging: false
70 + });
@@ -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,98 +1,114
1 1 CafeGrader::Application.routes.draw do
2 + resources :tags
2 3 get "sources/direct_edit"
3 4
4 5 root :to => 'main#login'
5 6
6 7 #logins
7 8 get 'login/login', to: 'login#login'
8 9
9 10 resources :contests
10 11
11 12 resources :sites
12 13
13 14 resources :announcements do
14 15 member do
15 16 get 'toggle','toggle_front'
16 17 end
17 18 end
18 19
19 20 resources :problems do
20 21 member do
21 22 get 'toggle'
22 23 get 'toggle_test'
23 24 get 'toggle_view_testcase'
24 25 get 'stat'
25 26 end
26 27 collection do
27 28 get 'turn_all_off'
28 29 get 'turn_all_on'
29 30 get 'import'
30 31 get 'manage'
31 32 end
33 + end
32 34
35 + resources :groups do
36 + member do
37 + post 'add_user', to: 'groups#add_user', as: 'add_user'
38 + delete 'remove_user/:user_id', to: 'groups#remove_user', as: 'remove_user'
39 + delete 'remove_all_user', to: 'groups#remove_all_user', as: 'remove_all_user'
40 + post 'add_problem', to: 'groups#add_problem', as: 'add_problem'
41 + delete 'remove_problem/:problem_id', to: 'groups#remove_problem', as: 'remove_problem'
42 + delete 'remove_all_problem', to: 'groups#remove_all_problem', as: 'remove_all_problem'
43 + end
44 + collection do
45 +
46 + end
33 47 end
34 48
35 49 resources :testcases, only: [] do
36 50 member do
37 51 get 'download_input'
38 52 get 'download_sol'
39 53 end
40 54 collection do
41 55 get 'show_problem/:problem_id(/:test_num)' => 'testcases#show_problem', as: 'show_problem'
42 56 end
43 57 end
44 58
45 59 resources :grader_configuration, controller: 'configurations'
46 60
47 61 resources :users do
48 62 member do
49 63 get 'toggle_activate', 'toggle_enable'
50 64 get 'stat'
51 65 end
52 66 end
53 67
54 68 resources :submissions do
55 69 member do
56 70 get 'download'
57 71 get 'compiler_msg'
58 72 get 'rejudge'
59 73 end
60 74 collection do
61 75 get 'prob/:problem_id', to: 'submissions#index', as: 'problem'
62 - get 'direct_edit_problem/:problem_id', to: 'submissions#direct_edit_problem', as: 'direct_edit_problem'
76 + get 'direct_edit_problem/:problem_id(/:user_id)', to: 'submissions#direct_edit_problem', as: 'direct_edit_problem'
63 77 get 'get_latest_submission_status/:uid/:pid', to: 'submissions#get_latest_submission_status', as: 'get_latest_submission_status'
64 78 end
65 79 end
66 80
67 81
68 82
69 83 #main
70 84 get "main/list"
71 85 get 'main/submission(/:id)', to: 'main#submission', as: 'main_submission'
72 86
73 87 #user admin
74 88 get 'user_admin/bulk_manage', to: 'user_admin#bulk_manage', as: 'bulk_manage_user_admin'
89 + post 'user_admin', to: 'user_admin#create'
90 + delete 'user_admin/:id', to: 'user_admin#destroy', as: 'user_admin_destroy'
75 91
76 92 #report
77 93 get 'report/current_score', to: 'report#current_score', as: 'report_current_score'
78 94 get 'report/problem_hof(/:id)', to: 'report#problem_hof', as: 'report_problem_hof'
79 95 get "report/login"
80 96 get 'report/max_score', to: 'report#max_score', as: 'report_max_score'
81 97 post 'report/show_max_score', to: 'report#show_max_score', as: 'report_show_max_score'
82 98
83 99
84 100 #
85 101 get 'tasks/view/:file.:ext' => 'tasks#view'
86 102 get 'tasks/download/:id/:file.:ext' => 'tasks#download'
87 103 get 'heartbeat/:id/edit' => 'heartbeat#edit'
88 104
89 105 #grader
90 106 get 'graders/list', to: 'graders#list', as: 'grader_list'
91 107
92 108
93 109 # See how all your routes lay out with "rake routes"
94 110
95 111 # This is a legacy wild controller route that's not recommended for RESTful applications.
96 112 # Note: This route will make all actions in every controller accessible via GET requests.
97 113 match ':controller(/:action(/:id))(.:format)', via: [:get, :post]
98 114 end
@@ -1,38 +1,38
1 1 # encoding: UTF-8
2 2 # This file is auto-generated from the current state of the database. Instead
3 3 # of editing this file, please use the migrations feature of Active Record to
4 4 # incrementally modify your database, and then regenerate this schema definition.
5 5 #
6 6 # Note that this schema.rb definition is the authoritative source for your
7 7 # database schema. If you need to create the application database on another
8 8 # system, you should be using db:schema:load, not running all the migrations
9 9 # from scratch. The latter is a flawed and unsustainable approach (the more migrations
10 10 # you'll amass, the slower it'll run and the greater likelihood for issues).
11 11 #
12 12 # It's strongly recommended that you check this file into your version control system.
13 13
14 - ActiveRecord::Schema.define(version: 20170427070345) do
14 + ActiveRecord::Schema.define(version: 20170914150742) do
15 15
16 16 create_table "announcements", force: :cascade do |t|
17 17 t.string "author", limit: 255
18 18 t.text "body", limit: 65535
19 19 t.boolean "published"
20 20 t.datetime "created_at", null: false
21 21 t.datetime "updated_at", null: false
22 22 t.boolean "frontpage", default: false
23 23 t.boolean "contest_only", default: false
24 24 t.string "title", limit: 255
25 25 t.string "notes", limit: 255
26 26 end
27 27
28 28 create_table "contests", force: :cascade do |t|
29 29 t.string "title", limit: 255
30 30 t.boolean "enabled"
31 31 t.datetime "created_at", null: false
32 32 t.datetime "updated_at", null: false
33 33 t.string "name", limit: 255
34 34 end
35 35
36 36 create_table "contests_problems", id: false, force: :cascade do |t|
37 37 t.integer "contest_id", limit: 4
38 38 t.integer "problem_id", limit: 4
@@ -58,96 +58,124
58 58
59 59 create_table "grader_configurations", force: :cascade do |t|
60 60 t.string "key", limit: 255
61 61 t.string "value_type", limit: 255
62 62 t.string "value", limit: 255
63 63 t.datetime "created_at", null: false
64 64 t.datetime "updated_at", null: false
65 65 t.text "description", limit: 65535
66 66 end
67 67
68 68 create_table "grader_processes", force: :cascade do |t|
69 69 t.string "host", limit: 255
70 70 t.integer "pid", limit: 4
71 71 t.string "mode", limit: 255
72 72 t.boolean "active"
73 73 t.datetime "created_at", null: false
74 74 t.datetime "updated_at", null: false
75 75 t.integer "task_id", limit: 4
76 76 t.string "task_type", limit: 255
77 77 t.boolean "terminated"
78 78 end
79 79
80 80 add_index "grader_processes", ["host", "pid"], name: "index_grader_processes_on_ip_and_pid", using: :btree
81 81
82 + create_table "groups", force: :cascade do |t|
83 + t.string "name", limit: 255
84 + t.string "description", limit: 255
85 + end
86 +
87 + create_table "groups_problems", id: false, force: :cascade do |t|
88 + t.integer "problem_id", limit: 4, null: false
89 + t.integer "group_id", limit: 4, null: false
90 + end
91 +
92 + add_index "groups_problems", ["group_id", "problem_id"], name: "index_groups_problems_on_group_id_and_problem_id", using: :btree
93 +
94 + create_table "groups_users", id: false, force: :cascade do |t|
95 + t.integer "group_id", limit: 4, null: false
96 + t.integer "user_id", limit: 4, null: false
97 + end
98 +
99 + add_index "groups_users", ["user_id", "group_id"], name: "index_groups_users_on_user_id_and_group_id", using: :btree
100 +
82 101 create_table "heart_beats", force: :cascade do |t|
83 102 t.integer "user_id", limit: 4
84 103 t.string "ip_address", limit: 255
85 104 t.datetime "created_at", null: false
86 105 t.datetime "updated_at", null: false
87 106 t.string "status", limit: 255
88 107 end
89 108
90 109 add_index "heart_beats", ["updated_at"], name: "index_heart_beats_on_updated_at", using: :btree
91 110
92 111 create_table "languages", force: :cascade do |t|
93 112 t.string "name", limit: 10
94 113 t.string "pretty_name", limit: 255
95 114 t.string "ext", limit: 10
96 115 t.string "common_ext", limit: 255
97 116 end
98 117
99 118 create_table "logins", force: :cascade do |t|
100 119 t.integer "user_id", limit: 4
101 120 t.string "ip_address", limit: 255
102 121 t.datetime "created_at", null: false
103 122 t.datetime "updated_at", null: false
104 123 end
105 124
106 125 create_table "messages", force: :cascade do |t|
107 126 t.integer "sender_id", limit: 4
108 127 t.integer "receiver_id", limit: 4
109 128 t.integer "replying_message_id", limit: 4
110 129 t.text "body", limit: 65535
111 130 t.boolean "replied"
112 131 t.datetime "created_at", null: false
113 132 t.datetime "updated_at", null: false
114 133 end
115 134
116 135 create_table "problems", force: :cascade do |t|
117 136 t.string "name", limit: 30
118 137 t.string "full_name", limit: 255
119 138 t.integer "full_score", limit: 4
120 139 t.date "date_added"
121 140 t.boolean "available"
122 141 t.string "url", limit: 255
123 142 t.integer "description_id", limit: 4
124 143 t.boolean "test_allowed"
125 144 t.boolean "output_only"
126 145 t.string "description_filename", limit: 255
127 146 t.boolean "view_testcase"
128 147 end
129 148
149 + create_table "problems_tags", force: :cascade do |t|
150 + t.integer "problem_id", limit: 4
151 + t.integer "tag_id", limit: 4
152 + end
153 +
154 + add_index "problems_tags", ["problem_id", "tag_id"], name: "index_problems_tags_on_problem_id_and_tag_id", unique: true, using: :btree
155 + add_index "problems_tags", ["problem_id"], name: "index_problems_tags_on_problem_id", using: :btree
156 + add_index "problems_tags", ["tag_id"], name: "index_problems_tags_on_tag_id", using: :btree
157 +
130 158 create_table "rights", force: :cascade do |t|
131 159 t.string "name", limit: 255
132 160 t.string "controller", limit: 255
133 161 t.string "action", limit: 255
134 162 end
135 163
136 164 create_table "rights_roles", id: false, force: :cascade do |t|
137 165 t.integer "right_id", limit: 4
138 166 t.integer "role_id", limit: 4
139 167 end
140 168
141 169 add_index "rights_roles", ["role_id"], name: "index_rights_roles_on_role_id", using: :btree
142 170
143 171 create_table "roles", force: :cascade do |t|
144 172 t.string "name", limit: 255
145 173 end
146 174
147 175 create_table "roles_users", id: false, force: :cascade do |t|
148 176 t.integer "role_id", limit: 4
149 177 t.integer "user_id", limit: 4
150 178 end
151 179
152 180 add_index "roles_users", ["user_id"], name: "index_roles_users_on_user_id", using: :btree
153 181
@@ -179,48 +207,56
179 207
180 208 create_table "submissions", force: :cascade do |t|
181 209 t.integer "user_id", limit: 4
182 210 t.integer "problem_id", limit: 4
183 211 t.integer "language_id", limit: 4
184 212 t.text "source", limit: 65535
185 213 t.binary "binary", limit: 65535
186 214 t.datetime "submitted_at"
187 215 t.datetime "compiled_at"
188 216 t.text "compiler_message", limit: 65535
189 217 t.datetime "graded_at"
190 218 t.integer "points", limit: 4
191 219 t.text "grader_comment", limit: 65535
192 220 t.integer "number", limit: 4
193 221 t.string "source_filename", limit: 255
194 222 t.float "max_runtime", limit: 24
195 223 t.integer "peak_memory", limit: 4
196 224 t.integer "effective_code_length", limit: 4
197 225 t.string "ip_address", limit: 255
198 226 end
199 227
200 228 add_index "submissions", ["user_id", "problem_id", "number"], name: "index_submissions_on_user_id_and_problem_id_and_number", unique: true, using: :btree
201 229 add_index "submissions", ["user_id", "problem_id"], name: "index_submissions_on_user_id_and_problem_id", using: :btree
202 230
231 + create_table "tags", force: :cascade do |t|
232 + t.string "name", limit: 255, null: false
233 + t.text "description", limit: 65535
234 + t.boolean "public"
235 + t.datetime "created_at", null: false
236 + t.datetime "updated_at", null: false
237 + end
238 +
203 239 create_table "tasks", force: :cascade do |t|
204 240 t.integer "submission_id", limit: 4
205 241 t.datetime "created_at"
206 242 t.integer "status", limit: 4
207 243 t.datetime "updated_at"
208 244 end
209 245
210 246 add_index "tasks", ["submission_id"], name: "index_tasks_on_submission_id", using: :btree
211 247
212 248 create_table "test_pairs", force: :cascade do |t|
213 249 t.integer "problem_id", limit: 4
214 250 t.text "input", limit: 16777215
215 251 t.text "solution", limit: 16777215
216 252 t.datetime "created_at", null: false
217 253 t.datetime "updated_at", null: false
218 254 end
219 255
220 256 create_table "test_requests", force: :cascade do |t|
221 257 t.integer "user_id", limit: 4
222 258 t.integer "problem_id", limit: 4
223 259 t.integer "submission_id", limit: 4
224 260 t.string "input_file_name", limit: 255
225 261 t.string "output_file_name", limit: 255
226 262 t.string "running_stat", limit: 255
@@ -259,25 +295,27
259 295 t.datetime "updated_at", null: false
260 296 t.boolean "forced_logout"
261 297 end
262 298
263 299 create_table "users", force: :cascade do |t|
264 300 t.string "login", limit: 50
265 301 t.string "full_name", limit: 255
266 302 t.string "hashed_password", limit: 255
267 303 t.string "salt", limit: 5
268 304 t.string "alias", limit: 255
269 305 t.string "email", limit: 255
270 306 t.integer "site_id", limit: 4
271 307 t.integer "country_id", limit: 4
272 308 t.boolean "activated", default: false
273 309 t.datetime "created_at"
274 310 t.datetime "updated_at"
275 311 t.boolean "enabled", default: true
276 312 t.string "remark", limit: 255
277 313 t.string "last_ip", limit: 255
278 314 t.string "section", limit: 255
279 315 end
280 316
281 317 add_index "users", ["login"], name: "index_users_on_login", unique: true, using: :btree
282 318
319 + add_foreign_key "problems_tags", "problems"
320 + add_foreign_key "problems_tags", "tags"
283 321 end
@@ -142,49 +142,58
142 142 # before the contest ends.
143 143 {
144 144 :key => 'contest.test_request.early_timeout',
145 145 :value_type => 'boolean',
146 146 :default_value => 'false'
147 147 },
148 148
149 149 {
150 150 :key => 'system.multicontests',
151 151 :value_type => 'boolean',
152 152 :default_value => 'false'
153 153 },
154 154
155 155 {
156 156 :key => 'contest.confirm_indv_contest_start',
157 157 :value_type => 'boolean',
158 158 :default_value => 'false'
159 159 },
160 160
161 161 {
162 162 :key => 'contest.default_contest_name',
163 163 :value_type => 'string',
164 164 :default_value => 'none',
165 165 :description => "New user will be assigned to this contest automatically, if it exists. Set to 'none' if there is no default contest."
166 - }
166 + },
167 +
168 + {
169 + :key => 'system.use_problem_group',
170 + :value_type => 'boolean',
171 + :default_value => 'false',
172 + :description => "If true, available problem to the user will be only ones associated with the group of the user."
173 + },
174 +
175 +
167 176
168 177 ]
169 178
170 179
171 180 def create_configuration_key(key,
172 181 value_type,
173 182 default_value,
174 183 description='')
175 184 conf = (GraderConfiguration.find_by_key(key) ||
176 185 GraderConfiguration.new(:key => key,
177 186 :value_type => value_type,
178 187 :value => default_value))
179 188 conf.description = description
180 189 conf.save
181 190 end
182 191
183 192 def seed_config
184 193 CONFIGURATIONS.each do |conf|
185 194 if conf.has_key? :description
186 195 desc = conf[:description]
187 196 else
188 197 desc = ''
189 198 end
190 199 create_configuration_key(conf[:key],
@@ -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
deleted file
deleted file
deleted file
deleted file
deleted file
deleted file
deleted file
deleted file
You need to be logged in to leave comments. Login now