Description:
Merge pull request #20 from nattee/master
feature merge
Commit status:
[Not Reviewed]
References:
Diff options:
Comments:
0 Commit comments
0 Inline Comments
Unresolved TODOs:
There are no unresolved TODOs
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,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,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,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 |
- # |
|
|
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 | |
|
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.1 |
|
|
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 |
|
|
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 |
- |
|
|
18 |
- |
|
|
19 |
- |
|
|
20 |
- |
|
|
21 |
- |
|
|
22 |
- |
|
|
23 |
- |
|
|
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 |
- |
|
|
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 |
- |
|
|
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, |
|
|
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 |
- |
|
|
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 |
|
|
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- |
|
|
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 |
|
|
3 | 2 | = "-" |
|
4 | 3 | - else |
|
4 | + %strong= "Submission ID:" | |
|
5 | + = submission.id | |
|
6 | + %br | |
|
5 | 7 | - unless submission.graded_at |
|
6 | - = t 'main.submitted_at' | |
|
7 |
- = format_ |
|
|
8 | + %strong= t 'main.submitted_at:' | |
|
9 | + = format_full_time_ago(submission.submitted_at.localtime) | |
|
8 | 10 | - else |
|
9 | - %strong= t 'main.graded_at' | |
|
10 |
- = |
|
|
11 | + %strong= t 'main.graded_at:' | |
|
12 | + = format_full_time_ago(submission.graded_at.localtime) | |
|
11 | 13 | %br |
|
12 | 14 | - if GraderConfiguration['ui.show_score'] |
|
13 | 15 | %strong=t 'main.score' |
|
14 | 16 | = "#{(submission.points*100/submission.problem.full_score).to_i} " |
|
15 | 17 | = " [" |
|
16 | 18 | %tt |
|
17 | 19 | = submission.grader_comment |
|
18 | 20 | = "]" |
|
19 | 21 | %br |
|
20 |
- |
|
|
21 |
- |
|
|
22 |
- |
|
|
23 |
- |
|
|
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]', |
|
|
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 |
|
|
3 | + %h1 Problems | |
|
4 | 4 | %p |
|
5 |
- = link_to ' |
|
|
6 |
- = link_to ' |
|
|
7 |
- = link_to ' |
|
|
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]', |
|
|
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 | - | |
|
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 | + | |
|
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 |
|
|
68 |
- %th |
|
|
69 |
- %th |
|
|
70 | - - if GraderConfiguration.multicontests? | |
|
71 |
- %th |
|
|
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. |
|
|
80 |
- %td= problem. |
|
|
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. |
|
|
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 |
|
|
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 |
|
|
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', |
|
|
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 |
- |
|
|
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 |
|
|
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 |
- |
|
|
62 |
- |
|
|
63 | - - else | |
|
64 |
- |
|
|
65 |
- |
|
|
66 |
- |
|
|
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', |
|
|
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: 20170 |
|
|
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