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: 1275 inserted, 462 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
@@ -50,33 +50,34
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'
@@ -52,28 +52,32
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)
@@ -88,24 +92,29
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)
@@ -192,44 +201,47
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
@@ -3,39 +3,45
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
@@ -24,24 +24,27
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'),
@@ -537,12 +540,18
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 + }
@@ -30,67 +30,65
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]
@@ -1,31 +1,15
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
@@ -68,43 +52,24
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 +
@@ -1,47 +1,53
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])
14 - session[:user_id] = user.id
15 - session[:admin] = user.admin?
20 + return
21 + end
22 +
23 + #process logging in
24 + session[:user_id] = user.id
25 + session[:admin] = user.admin?
16 26
17 - # clear forced logout flag for multicontests contest change
18 - if GraderConfiguration.multicontests?
19 - contest_stat = user.contest_stat
20 - if contest_stat.respond_to? :forced_logout
21 - if contest_stat.forced_logout
22 - contest_stat.forced_logout = false
23 - contest_stat.save
24 - end
27 + # clear forced logout flag for multicontests contest change
28 + if GraderConfiguration.multicontests?
29 + contest_stat = user.contest_stat
30 + if contest_stat.respond_to? :forced_logout
31 + if contest_stat.forced_logout
32 + contest_stat.forced_logout = false
33 + contest_stat.save
25 34 end
26 35 end
27 -
28 - #save login information
29 - Login.create(user_id: user.id, ip_address: request.remote_ip)
36 + end
30 37
31 - redirect_to :controller => 'main', :action => 'list'
32 - else
33 - flash[:notice] = 'Wrong password'
34 - redirect_to :controller => 'main', :action => 'login'
35 - end
38 + #save login information
39 + Login.create(user_id: user.id, ip_address: request.remote_ip)
40 +
41 + redirect_to :controller => 'main', :action => 'list'
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
@@ -77,36 +77,36
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'
@@ -156,25 +156,25
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
@@ -186,25 +186,44
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
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
198 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
@@ -234,28 +253,25
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
@@ -279,16 +295,16
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
@@ -43,24 +43,26
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
@@ -1,15 +1,15
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
@@ -42,25 +42,33
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
@@ -85,24 +93,23
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
@@ -17,25 +17,25
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
@@ -15,24 +15,25
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
@@ -219,24 +220,25
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
@@ -402,50 +404,67
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
@@ -76,39 +76,42
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",
@@ -191,25 +194,25
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
@@ -3,24 +3,25
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
@@ -110,24 +111,28
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
123 +
124 + def self.use_problem_group?
125 + return get(SYSTEM_USE_PROBLEM_GROUP)
126 + end
122 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
@@ -1,16 +1,24
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
@@ -25,26 +25,26
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
@@ -128,35 +128,39
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,22 +1,26
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
@@ -231,49 +235,74
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?
245 - return Problem.available_problems
250 + if GraderConfiguration.use_problem_group?
251 + return available_problems_in_group
252 + else
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
@@ -6,25 +6,25
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
@@ -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 - %strong View:
21 - - if GraderConfiguration.show_grading_result
22 - = 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'}
22 + %strong View:
23 + - if GraderConfiguration.show_grading_result
24 + = link_to '[detailed result]', :action => 'result', :id => submission.id
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/
@@ -37,25 +37,27
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
@@ -3,37 +3,41
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
25 - - unless GraderConfiguration['right.bypass_agreement']
26 - %tr
27 - %td{:align => "right"}= check_box_tag 'accept_agree'
28 - %td ยอมรับข้อตกลงการใช้งาน
29 -
30 - = submit_tag t('login.login_submit')
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'
24 + - unless GraderConfiguration['right.bypass_agreement']
25 + .form-group
26 + .col-sm-offset-3.col-sm-9
27 + .checkbox
28 + %label
29 + = check_box_tag 'accept_agree'
30 + ยอมรับข้อตกลงการใช้งาน
31 +
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
@@ -2,21 +2,21
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'
@@ -4,25 +4,25
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'
@@ -1,12 +1,11
1 1 %h1= GraderConfiguration['ui.front.title']
2 2
3 - %table
4 - %tr
5 - %td
6 - - if @announcements.length!=0
7 - .announcementbox{:style => 'margin-top: 0px'}
8 - %span{:class => 'title'}
9 - Announcements
10 - = render :partial => 'announcement', :collection => @announcements
11 - %td{:style => 'vertical-align: top; width: 40%; padding-left: 20px;'}
12 - = render :partial => 'login_box'
3 + .row
4 + .col-md-6
5 + - if @announcements.length!=0
6 + .announcementbox{:style => 'margin-top: 0px'}
7 + %span{:class => 'title'}
8 + Announcements
9 + = render :partial => 'announcement', :collection => @announcements
10 + .col-md-4{style: "padding-left: 20px;"}
11 + = render :partial => 'login_box'
@@ -3,24 +3,27
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?
@@ -1,53 +1,64
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/
@@ -27,59 +27,92
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
43 - What do you want to do to the selected problem?
44 - %br/
45 - (You can shift-click to select a range of problems)
46 - %ul
47 - %li
48 - Change date added to
49 - = select_date Date.current, :prefix => 'date_added'
50 - &nbsp;&nbsp;&nbsp;
51 - = submit_tag 'Change', :name => 'change_date_added'
52 - %li
53 - Set available to
54 - = submit_tag 'True', :name => 'enable_problem'
55 - = submit_tag 'False', :name => 'disable_problem'
42 + .panel.panel-primary
43 + .panel-heading
44 + Action
45 + .panel-body
46 + .submit-box
47 + What do you want to do to the selected problem?
48 + %br/
49 + (You can shift-click to select a range of problems)
50 + %ul.form-inline
51 + %li
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'
58 + &nbsp;&nbsp;&nbsp;
59 + = submit_tag 'Change', :name => 'change_date_added', class: 'btn btn-primary btn-sm'
60 + %li
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 - - if GraderConfiguration.multicontests?
58 - %li
59 - Add to
60 - = select("contest","id",Contest.all.collect {|c| [c.title, c.id]})
61 - = submit_tag 'Add', :name => 'add_to_contest'
65 + - if GraderConfiguration.multicontests?
66 + %li
67 + Add selected problems to contest
68 + = select("contest","id",Contest.all.collect {|c| [c.title, c.id]})
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
64 - %tr{style: "text-align: left;"}
65 - %th= check_box_tag 'select_all'
66 - %th Name
67 - %th Full name
68 - %th Available
69 - %th Date added
70 - - if GraderConfiguration.multicontests?
71 - %th Contests
79 + %table.table.table-hover.datatable
80 + %thead
81 + %tr{style: "text-align: left;"}
82 + %th= check_box_tag 'select_all'
83 + %th Name
84 + %th Full name
85 + %th Tags
86 + %th Available
87 + %th Date added
88 + - if GraderConfiguration.multicontests?
89 + %th Contests
72 90
73 - - num = 0
74 - - for problem in @problems
75 - - num += 1
76 - %tr{:id => "row-prob-#{problem.id}", :name=> "prob-#{problem.id}"}
77 - %td= check_box_tag "prob-#{problem.id}-#{num}"
78 - %td= problem.name
79 - %td= problem.full_name
80 - %td= problem.available
81 - %td= problem.date_added
82 - - if GraderConfiguration.multicontests?
91 + %tbody
92 + - num = 0
93 + - for problem in @problems
94 + - num += 1
95 + %tr{:id => "row-prob-#{problem.id}", :name=> "prob-#{problem.id}"}
96 + %td= check_box_tag "prob-#{problem.id}-#{num}"
97 + %td= problem.name
98 + %td= problem.full_name
83 99 %td
84 - - problem.contests.each do |contest|
85 - = "(#{contest.name} [#{link_to 'x', :action => 'remove_contest', :id => problem.id, :contest_id => contest.id }])"
100 + - problem.tags.each do |t|
101 + %span.label.label-default= t.name
102 + %td= problem.available
103 + %td= problem.date_added
104 + - if GraderConfiguration.multicontests?
105 + %td
106 + - problem.contests.each do |contest|
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 + });
@@ -16,38 +16,44
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 + });
@@ -3,32 +3,67
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')
@@ -24,26 +24,26
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 +
@@ -2,46 +2,49
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
33 - - if @submission
34 - = render :partial => 'submission_short',
35 - :locals => {submission: @submission, problem_name: @problem.name, problem_id: @problem.id }
35 + %div#latest_status
36 + - if @submission
37 + = render :partial => 'submission_short',
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();
@@ -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}})}")
@@ -37,24 +37,33
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]
@@ -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,13 +1,13
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'
@@ -32,50 +32,50
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
@@ -86,16 +86,21
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 + });
@@ -27,25 +27,25
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
@@ -55,12 +55,16
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 + });
@@ -56,17 +56,17
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,13 +1,14
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
@@ -20,25 +21,38
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
@@ -50,37 +64,39
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'
@@ -2,25 +2,25
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
@@ -70,24 +70,43
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
@@ -118,24 +137,33
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
@@ -191,24 +219,32
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
@@ -271,13 +307,15
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
@@ -154,25 +154,34
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))
@@ -24,24 +24,25
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}"
@@ -1,24 +1,25
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 - def import_from_file(tempfile,
12 - time_limit,
11 + #Create or update problem according to the parameter
12 + def import_from_file(tempfile,
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)
@@ -43,24 +44,25
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)
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