Description:
modernize problem
Commit status:
[Not Reviewed]
References:
Diff options:
Comments:
0 Commit comments
0 Inline Comments
Unresolved TODOs:
There are no unresolved TODOs
r876:8cd1c60e1ef0 - - The requested commit is too big and content was truncated. 22 files changed. Show full diff
@@ -0,0 +1,6 | |||
|
1 | + %h1 New Problem | |
|
2 | + | |
|
3 | + = render 'form', problem: @problem | |
|
4 | + .row.my-3 | |
|
5 | + .col-md-4 | |
|
6 | + = link_to 'Back', problems_path, class: 'btn btn-secondary' |
@@ -0,0 +1,57 | |||
|
1 | + # This migration comes from active_storage (originally 20170806125915) | |
|
2 | + class CreateActiveStorageTables < ActiveRecord::Migration[5.2] | |
|
3 | + def change | |
|
4 | + # Use Active Record's configured type for primary and foreign keys | |
|
5 | + primary_key_type, foreign_key_type = primary_and_foreign_key_types | |
|
6 | + | |
|
7 | + create_table :active_storage_blobs, id: primary_key_type do |t| | |
|
8 | + t.string :key, null: false | |
|
9 | + t.string :filename, null: false | |
|
10 | + t.string :content_type | |
|
11 | + t.text :metadata | |
|
12 | + t.string :service_name, null: false | |
|
13 | + t.bigint :byte_size, null: false | |
|
14 | + t.string :checksum | |
|
15 | + | |
|
16 | + if connection.supports_datetime_with_precision? | |
|
17 | + t.datetime :created_at, precision: 6, null: false | |
|
18 | + else | |
|
19 | + t.datetime :created_at, null: false | |
|
20 | + end | |
|
21 | + | |
|
22 | + t.index [ :key ], unique: true | |
|
23 | + end | |
|
24 | + | |
|
25 | + create_table :active_storage_attachments, id: primary_key_type do |t| | |
|
26 | + t.string :name, null: false | |
|
27 | + t.references :record, null: false, polymorphic: true, index: false, type: foreign_key_type | |
|
28 | + t.references :blob, null: false, type: foreign_key_type | |
|
29 | + | |
|
30 | + if connection.supports_datetime_with_precision? | |
|
31 | + t.datetime :created_at, precision: 6, null: false | |
|
32 | + else | |
|
33 | + t.datetime :created_at, null: false | |
|
34 | + end | |
|
35 | + | |
|
36 | + t.index [ :record_type, :record_id, :name, :blob_id ], name: :index_active_storage_attachments_uniqueness, unique: true | |
|
37 | + t.foreign_key :active_storage_blobs, column: :blob_id | |
|
38 | + end | |
|
39 | + | |
|
40 | + create_table :active_storage_variant_records, id: primary_key_type do |t| | |
|
41 | + t.belongs_to :blob, null: false, index: false, type: foreign_key_type | |
|
42 | + t.string :variation_digest, null: false | |
|
43 | + | |
|
44 | + t.index [ :blob_id, :variation_digest ], name: :index_active_storage_variant_records_uniqueness, unique: true | |
|
45 | + t.foreign_key :active_storage_blobs, column: :blob_id | |
|
46 | + end | |
|
47 | + end | |
|
48 | + | |
|
49 | + private | |
|
50 | + def primary_and_foreign_key_types | |
|
51 | + config = Rails.configuration.generators | |
|
52 | + setting = config.options[config.orm][:primary_key_type] | |
|
53 | + primary_key_type = setting || :primary_key | |
|
54 | + foreign_key_type = setting || :bigint | |
|
55 | + [primary_key_type, foreign_key_type] | |
|
56 | + end | |
|
57 | + end |
@@ -0,0 +1,6 | |||
|
1 | + class AddDescriptionToProblems < ActiveRecord::Migration[7.0] | |
|
2 | + def change | |
|
3 | + add_column :problems, :description, :text | |
|
4 | + add_column :problems, :markdown, :boolean | |
|
5 | + end | |
|
6 | + end |
@@ -0,0 +1,21 | |||
|
1 | + Problem.all.each do |p| | |
|
2 | + next unless p.description_filename | |
|
3 | + basename, ext = p.description_filename.split('.') | |
|
4 | + filename = "#{Problem.download_file_basedir}/#{p.id}/#{basename}.#{ext}" | |
|
5 | + | |
|
6 | + if File.exists? filename | |
|
7 | + p.statement.attach io: File.open(filename), filename: "#{basename}.#{ext}" | |
|
8 | + puts "#{p.id}: OK" | |
|
9 | + else | |
|
10 | + puts "#{p.id}: #{p.name} #{filename} ERROR" | |
|
11 | + end | |
|
12 | + | |
|
13 | + d = Description.where(id: p.description_id).first | |
|
14 | + if d | |
|
15 | + p.description = d.body | |
|
16 | + p.markdown = d.markdowned | |
|
17 | + end | |
|
18 | + p.save | |
|
19 | + | |
|
20 | + | |
|
21 | + end |
@@ -1,37 +1,38 | |||
|
1 | 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. |
|
2 | 2 | # |
|
3 | 3 | # If you find yourself ignoring temporary files generated by your text editor |
|
4 | 4 | # or operating system, you probably want to add a global ignore instead: |
|
5 | 5 | # git config --global core.excludesfile ~/.gitignore_global |
|
6 | 6 | |
|
7 | 7 | # Ignore bundler config |
|
8 | 8 | /.bundle |
|
9 | 9 | |
|
10 | 10 | # Ignore the default SQLite database. |
|
11 | 11 | /db/*.sqlite3 |
|
12 | 12 | |
|
13 | 13 | # Ignore all logfiles and tempfiles. |
|
14 | 14 | /log/*.log |
|
15 | 15 | /tmp |
|
16 | 16 | |
|
17 | 17 | *~ |
|
18 | 18 | |
|
19 | 19 | /vendor/plugins/rails_upgrade |
|
20 | 20 | |
|
21 | 21 | #ignore public assets??? |
|
22 | 22 | /public/assets |
|
23 | 23 | /public |
|
24 | 24 | |
|
25 | 25 | /data |
|
26 | 26 | |
|
27 | 27 | #ignore .orig and .swp |
|
28 | 28 | *.orig |
|
29 | 29 | *.swp |
|
30 | 30 | |
|
31 | 31 | #ignore rvm setting file |
|
32 | 32 | #.ruby-gemset |
|
33 | 33 | #.ruby-version |
|
34 | 34 | |
|
35 | 35 | /config/secrets.yml |
|
36 | 36 | |
|
37 | 37 | /.byebug_history |
|
38 | + /storage/* |
@@ -1,123 +1,124 | |||
|
1 | 1 | source 'https://rubygems.org' |
|
2 | 2 | git_source(:github) { |repo| "https://github.com/#{repo}.git" } |
|
3 | 3 | |
|
4 | 4 | ruby '3.1.2' |
|
5 | 5 | |
|
6 | 6 | #rails |
|
7 | 7 | gem 'rails', '~>7.0' |
|
8 | 8 | |
|
9 | 9 | # The original asset pipeline for Rails [https://github.com/rails/sprockets-rails] |
|
10 | 10 | gem "sprockets-rails" |
|
11 | 11 | |
|
12 | 12 | gem 'puma' |
|
13 | 13 | |
|
14 | 14 | # Reduces boot times through caching; required in config/boot.rb |
|
15 | 15 | gem 'bootsnap', require: false |
|
16 | 16 | |
|
17 | 17 | # Bundle edge Rails instead: |
|
18 | 18 | # gem 'rails', :git => 'git://github.com/rails/rails.git' |
|
19 | 19 | |
|
20 | 20 | #---------------- database --------------------- |
|
21 | 21 | #the database |
|
22 | 22 | gem 'mysql2' |
|
23 | 23 | #for testing |
|
24 | 24 | gem 'sqlite3' |
|
25 | 25 | #gem 'rails-controller-testing' |
|
26 | 26 | #for dumping database into yaml |
|
27 | 27 | #gem 'yaml_db' |
|
28 | 28 | |
|
29 | 29 | |
|
30 | 30 | #------------- assset pipeline ----------------- |
|
31 | 31 | # Gems used only for assets and not required |
|
32 | 32 | # in production environments by default. |
|
33 | 33 | #sass-rails is depricated |
|
34 | 34 | #gem 'sass-rails' |
|
35 | 35 | gem 'sassc-rails' |
|
36 | 36 | gem 'coffee-rails' |
|
37 | 37 | gem 'material_icons' |
|
38 | 38 | |
|
39 | 39 | # See https://github.com/sstephenson/execjs#readme for more supported runtimes |
|
40 | 40 | # gem 'therubyracer', :platforms => :ruby |
|
41 | 41 | |
|
42 | 42 | gem "importmap-rails", "~> 1.1" |
|
43 | 43 | # gem 'uglifier' |
|
44 | 44 | |
|
45 | 45 | gem 'haml' |
|
46 | 46 | gem 'haml-rails' |
|
47 | 47 | |
|
48 | 48 | # Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks |
|
49 | 49 | #gem 'turbolinks', '~> 5' |
|
50 | 50 | # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder |
|
51 | 51 | gem 'jbuilder' |
|
52 | 52 | |
|
53 | 53 | |
|
54 | 54 | #in-place editor |
|
55 | 55 | gem 'best_in_place', git: "https://github.com/mmotherwell/best_in_place" |
|
56 | 56 | |
|
57 | 57 | # jquery addition |
|
58 | 58 | gem 'jquery-rails' |
|
59 | 59 | #gem 'jquery-ui-rails' |
|
60 | 60 | #gem 'jquery-timepicker-addon-rails' |
|
61 | 61 | #gem 'jquery-tablesorter' |
|
62 | 62 | #gem 'jquery-countdown-rails' |
|
63 | 63 | |
|
64 | 64 | #syntax highlighter |
|
65 | 65 | gem 'rouge' |
|
66 | 66 | |
|
67 | 67 | #bootstrap add-ons |
|
68 | 68 | #gem 'bootstrap-sass', '~> 3.4.1' |
|
69 | 69 | gem 'bootstrap', '~> 5.2' |
|
70 | 70 | #gem 'bootstrap-switch-rails' |
|
71 | 71 | #gem 'bootstrap-toggle-rails' |
|
72 | 72 | #gem 'autoprefixer-rails' |
|
73 | 73 | gem 'momentjs-rails' |
|
74 | 74 | #gem 'rails_bootstrap_sortable' |
|
75 | 75 | #gem 'bootstrap-datepicker-rails' |
|
76 | 76 | #gem 'bootstrap3-datetimepicker-rails', '~> 4.17.47' |
|
77 | 77 | #gem 'jquery-datatables-rails' |
|
78 | 78 | |
|
79 | 79 | #----------- user interface ----------------- |
|
80 | - gem 'simple_form' | |
|
80 | + gem 'simple_form', git: 'https://github.com/heartcombo/simple_form', ref: '31fe255' | |
|
81 | + | |
|
81 | 82 | #select 2 |
|
82 | 83 | #gem 'select2-rails' |
|
83 | 84 | #ace editor |
|
84 | 85 | gem 'ace-rails-ap' |
|
85 | 86 | #paginator |
|
86 | 87 | #gem 'will_paginate', '~> 3.0.7' |
|
87 | 88 | |
|
88 | 89 | gem 'mail' |
|
89 | 90 | gem 'rdiscount' #markdown |
|
90 | 91 | |
|
91 | 92 | |
|
92 | 93 | #---------------- testiing ----------------------- |
|
93 | 94 | gem 'minitest-reporters' |
|
94 | 95 | |
|
95 | 96 | #---------------- for console -------------------- |
|
96 | 97 | gem 'fuzzy-string-match' |
|
97 | 98 | |
|
98 | 99 | |
|
99 | 100 | group :development, :test do |
|
100 | 101 | # Call 'byebug' anywhere in the code to stop execution and get a debugger console |
|
101 | 102 | gem 'byebug' |
|
102 | 103 | end |
|
103 | 104 | |
|
104 | 105 | group :development do |
|
105 | 106 | # Access an interactive console on exception pages or by calling 'console' anywhere in the code. |
|
106 | 107 | gem 'web-console', '>= 3.3.0' |
|
107 | 108 | gem 'listen', '>= 3.0.5', '< 3.2' |
|
108 | 109 | # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring |
|
109 | 110 | gem 'spring' |
|
110 | 111 | gem 'spring-watcher-listen', '~> 2.0.0' |
|
111 | 112 | |
|
112 | 113 | # Add speed badges [https://github.com/MiniProfiler/rack-mini-profiler] |
|
113 | 114 | # gem "rack-mini-profiler" |
|
114 | 115 | end |
|
115 | 116 | |
|
116 | 117 | group :test do |
|
117 | 118 | # Adds support for Capybara system testing and selenium driver |
|
118 | 119 | gem 'capybara' |
|
119 | 120 | gem 'selenium-webdriver' |
|
120 | 121 | gem 'webdrivers' |
|
121 | 122 | end |
|
122 | 123 | |
|
123 | 124 |
@@ -1,330 +1,336 | |||
|
1 | + GIT | |
|
2 | + remote: https://github.com/heartcombo/simple_form | |
|
3 | + revision: 31fe25504771bd6cd425b585a4e0ed652fba4521 | |
|
4 | + ref: 31fe255 | |
|
5 | + specs: | |
|
6 | + simple_form (5.1.0) | |
|
7 | + actionpack (>= 5.2) | |
|
8 | + activemodel (>= 5.2) | |
|
9 | + | |
|
1 | 10 | GIT |
|
2 | 11 | remote: https://github.com/mmotherwell/best_in_place |
|
3 | 12 | revision: 88eb3052623a9a6cd346864d2aca05021c2f80d0 |
|
4 | 13 | specs: |
|
5 | 14 | best_in_place (3.1.1) |
|
6 | 15 | actionpack (>= 3.2) |
|
7 | 16 | railties (>= 3.2) |
|
8 | 17 | |
|
9 | 18 | GEM |
|
10 | 19 | remote: https://rubygems.org/ |
|
11 | 20 | specs: |
|
12 | 21 | RubyInline (3.12.6) |
|
13 | 22 | ZenTest (~> 4.3) |
|
14 | 23 | ZenTest (4.12.1) |
|
15 | 24 | ace-rails-ap (4.4) |
|
16 | 25 | actioncable (7.0.4) |
|
17 | 26 | actionpack (= 7.0.4) |
|
18 | 27 | activesupport (= 7.0.4) |
|
19 | 28 | nio4r (~> 2.0) |
|
20 | 29 | websocket-driver (>= 0.6.1) |
|
21 | 30 | actionmailbox (7.0.4) |
|
22 | 31 | actionpack (= 7.0.4) |
|
23 | 32 | activejob (= 7.0.4) |
|
24 | 33 | activerecord (= 7.0.4) |
|
25 | 34 | activestorage (= 7.0.4) |
|
26 | 35 | activesupport (= 7.0.4) |
|
27 | 36 | mail (>= 2.7.1) |
|
28 | 37 | net-imap |
|
29 | 38 | net-pop |
|
30 | 39 | net-smtp |
|
31 | 40 | actionmailer (7.0.4) |
|
32 | 41 | actionpack (= 7.0.4) |
|
33 | 42 | actionview (= 7.0.4) |
|
34 | 43 | activejob (= 7.0.4) |
|
35 | 44 | activesupport (= 7.0.4) |
|
36 | 45 | mail (~> 2.5, >= 2.5.4) |
|
37 | 46 | net-imap |
|
38 | 47 | net-pop |
|
39 | 48 | net-smtp |
|
40 | 49 | rails-dom-testing (~> 2.0) |
|
41 | 50 | actionpack (7.0.4) |
|
42 | 51 | actionview (= 7.0.4) |
|
43 | 52 | activesupport (= 7.0.4) |
|
44 | 53 | rack (~> 2.0, >= 2.2.0) |
|
45 | 54 | rack-test (>= 0.6.3) |
|
46 | 55 | rails-dom-testing (~> 2.0) |
|
47 | 56 | rails-html-sanitizer (~> 1.0, >= 1.2.0) |
|
48 | 57 | actiontext (7.0.4) |
|
49 | 58 | actionpack (= 7.0.4) |
|
50 | 59 | activerecord (= 7.0.4) |
|
51 | 60 | activestorage (= 7.0.4) |
|
52 | 61 | activesupport (= 7.0.4) |
|
53 | 62 | globalid (>= 0.6.0) |
|
54 | 63 | nokogiri (>= 1.8.5) |
|
55 | 64 | actionview (7.0.4) |
|
56 | 65 | activesupport (= 7.0.4) |
|
57 | 66 | builder (~> 3.1) |
|
58 | 67 | erubi (~> 1.4) |
|
59 | 68 | rails-dom-testing (~> 2.0) |
|
60 | 69 | rails-html-sanitizer (~> 1.1, >= 1.2.0) |
|
61 | 70 | activejob (7.0.4) |
|
62 | 71 | activesupport (= 7.0.4) |
|
63 | 72 | globalid (>= 0.3.6) |
|
64 | 73 | activemodel (7.0.4) |
|
65 | 74 | activesupport (= 7.0.4) |
|
66 | 75 | activerecord (7.0.4) |
|
67 | 76 | activemodel (= 7.0.4) |
|
68 | 77 | activesupport (= 7.0.4) |
|
69 | 78 | activestorage (7.0.4) |
|
70 | 79 | actionpack (= 7.0.4) |
|
71 | 80 | activejob (= 7.0.4) |
|
72 | 81 | activerecord (= 7.0.4) |
|
73 | 82 | activesupport (= 7.0.4) |
|
74 | 83 | marcel (~> 1.0) |
|
75 | 84 | mini_mime (>= 1.1.0) |
|
76 | 85 | activesupport (7.0.4) |
|
77 | 86 | concurrent-ruby (~> 1.0, >= 1.0.2) |
|
78 | 87 | i18n (>= 1.6, < 2) |
|
79 | 88 | minitest (>= 5.1) |
|
80 | 89 | tzinfo (~> 2.0) |
|
81 | 90 | addressable (2.8.1) |
|
82 | 91 | public_suffix (>= 2.0.2, < 6.0) |
|
83 | 92 | ansi (1.5.0) |
|
84 | 93 | autoprefixer-rails (10.4.7.0) |
|
85 | 94 | execjs (~> 2) |
|
86 | 95 | bindex (0.8.1) |
|
87 | 96 | bootsnap (1.13.0) |
|
88 | 97 | msgpack (~> 1.2) |
|
89 | 98 | bootstrap (5.2.1) |
|
90 | 99 | autoprefixer-rails (>= 9.1.0) |
|
91 | 100 | popper_js (>= 2.11.6, < 3) |
|
92 | 101 | sassc-rails (>= 2.0.0) |
|
93 | 102 | builder (3.2.4) |
|
94 | 103 | byebug (11.1.3) |
|
95 | 104 | capybara (3.37.1) |
|
96 | 105 | addressable |
|
97 | 106 | matrix |
|
98 | 107 | mini_mime (>= 0.1.3) |
|
99 | 108 | nokogiri (~> 1.8) |
|
100 | 109 | rack (>= 1.6.0) |
|
101 | 110 | rack-test (>= 0.6.3) |
|
102 | 111 | regexp_parser (>= 1.5, < 3.0) |
|
103 | 112 | xpath (~> 3.2) |
|
104 | 113 | childprocess (4.1.0) |
|
105 | 114 | coffee-rails (5.0.0) |
|
106 | 115 | coffee-script (>= 2.2.0) |
|
107 | 116 | railties (>= 5.2.0) |
|
108 | 117 | coffee-script (2.4.1) |
|
109 | 118 | coffee-script-source |
|
110 | 119 | execjs |
|
111 | 120 | coffee-script-source (1.12.2) |
|
112 | 121 | concurrent-ruby (1.1.10) |
|
113 | 122 | crass (1.0.6) |
|
114 | 123 | digest (3.1.0) |
|
115 | 124 | erubi (1.11.0) |
|
116 | 125 | erubis (2.7.0) |
|
117 | 126 | execjs (2.8.1) |
|
118 | 127 | ffi (1.15.5) |
|
119 | 128 | fuzzy-string-match (1.0.1) |
|
120 | 129 | RubyInline (>= 3.8.6) |
|
121 | 130 | globalid (1.0.0) |
|
122 | 131 | activesupport (>= 5.0) |
|
123 | 132 | haml (5.2.2) |
|
124 | 133 | temple (>= 0.8.0) |
|
125 | 134 | tilt |
|
126 | 135 | haml-rails (2.0.1) |
|
127 | 136 | actionpack (>= 5.1) |
|
128 | 137 | activesupport (>= 5.1) |
|
129 | 138 | haml (>= 4.0.6, < 6.0) |
|
130 | 139 | html2haml (>= 1.0.1) |
|
131 | 140 | railties (>= 5.1) |
|
132 | 141 | html2haml (2.2.0) |
|
133 | 142 | erubis (~> 2.7.0) |
|
134 | 143 | haml (>= 4.0, < 6) |
|
135 | 144 | nokogiri (>= 1.6.0) |
|
136 | 145 | ruby_parser (~> 3.5) |
|
137 | 146 | i18n (1.12.0) |
|
138 | 147 | concurrent-ruby (~> 1.0) |
|
139 | 148 | importmap-rails (1.1.5) |
|
140 | 149 | actionpack (>= 6.0.0) |
|
141 | 150 | railties (>= 6.0.0) |
|
142 | 151 | jbuilder (2.11.5) |
|
143 | 152 | actionview (>= 5.0.0) |
|
144 | 153 | activesupport (>= 5.0.0) |
|
145 | 154 | jquery-rails (4.5.0) |
|
146 | 155 | rails-dom-testing (>= 1, < 3) |
|
147 | 156 | railties (>= 4.2.0) |
|
148 | 157 | thor (>= 0.14, < 2.0) |
|
149 | 158 | listen (3.0.8) |
|
150 | 159 | rb-fsevent (~> 0.9, >= 0.9.4) |
|
151 | 160 | rb-inotify (~> 0.9, >= 0.9.7) |
|
152 | 161 | loofah (2.19.0) |
|
153 | 162 | crass (~> 1.0.2) |
|
154 | 163 | nokogiri (>= 1.5.9) |
|
155 | 164 | mail (2.7.1) |
|
156 | 165 | mini_mime (>= 0.1.1) |
|
157 | 166 | marcel (1.0.2) |
|
158 | 167 | material_icons (2.2.1) |
|
159 | 168 | railties (>= 3.2) |
|
160 | 169 | matrix (0.4.2) |
|
161 | 170 | method_source (1.0.0) |
|
162 | 171 | mini_mime (1.1.2) |
|
163 | 172 | minitest (5.16.3) |
|
164 | 173 | minitest-reporters (1.5.0) |
|
165 | 174 | ansi |
|
166 | 175 | builder |
|
167 | 176 | minitest (>= 5.0) |
|
168 | 177 | ruby-progressbar |
|
169 | 178 | momentjs-rails (2.29.4.1) |
|
170 | 179 | railties (>= 3.1) |
|
171 | 180 | msgpack (1.5.6) |
|
172 | 181 | mysql2 (0.5.4) |
|
173 | 182 | net-imap (0.2.3) |
|
174 | 183 | digest |
|
175 | 184 | net-protocol |
|
176 | 185 | strscan |
|
177 | 186 | net-pop (0.1.1) |
|
178 | 187 | digest |
|
179 | 188 | net-protocol |
|
180 | 189 | timeout |
|
181 | 190 | net-protocol (0.1.3) |
|
182 | 191 | timeout |
|
183 | 192 | net-smtp (0.3.1) |
|
184 | 193 | digest |
|
185 | 194 | net-protocol |
|
186 | 195 | timeout |
|
187 | 196 | nio4r (2.5.8) |
|
188 | 197 | nokogiri (1.13.8-x86_64-linux) |
|
189 | 198 | racc (~> 1.4) |
|
190 | 199 | popper_js (2.11.6) |
|
191 | 200 | public_suffix (5.0.0) |
|
192 | 201 | puma (5.6.5) |
|
193 | 202 | nio4r (~> 2.0) |
|
194 | 203 | racc (1.6.0) |
|
195 | 204 | rack (2.2.4) |
|
196 | 205 | rack-test (2.0.2) |
|
197 | 206 | rack (>= 1.3) |
|
198 | 207 | rails (7.0.4) |
|
199 | 208 | actioncable (= 7.0.4) |
|
200 | 209 | actionmailbox (= 7.0.4) |
|
201 | 210 | actionmailer (= 7.0.4) |
|
202 | 211 | actionpack (= 7.0.4) |
|
203 | 212 | actiontext (= 7.0.4) |
|
204 | 213 | actionview (= 7.0.4) |
|
205 | 214 | activejob (= 7.0.4) |
|
206 | 215 | activemodel (= 7.0.4) |
|
207 | 216 | activerecord (= 7.0.4) |
|
208 | 217 | activestorage (= 7.0.4) |
|
209 | 218 | activesupport (= 7.0.4) |
|
210 | 219 | bundler (>= 1.15.0) |
|
211 | 220 | railties (= 7.0.4) |
|
212 | 221 | rails-dom-testing (2.0.3) |
|
213 | 222 | activesupport (>= 4.2.0) |
|
214 | 223 | nokogiri (>= 1.6) |
|
215 | 224 | rails-html-sanitizer (1.4.3) |
|
216 | 225 | loofah (~> 2.3) |
|
217 | 226 | railties (7.0.4) |
|
218 | 227 | actionpack (= 7.0.4) |
|
219 | 228 | activesupport (= 7.0.4) |
|
220 | 229 | method_source |
|
221 | 230 | rake (>= 12.2) |
|
222 | 231 | thor (~> 1.0) |
|
223 | 232 | zeitwerk (~> 2.5) |
|
224 | 233 | rake (13.0.6) |
|
225 | 234 | rb-fsevent (0.11.2) |
|
226 | 235 | rb-inotify (0.10.1) |
|
227 | 236 | ffi (~> 1.0) |
|
228 | 237 | rdiscount (2.2.0.2) |
|
229 | 238 | regexp_parser (2.5.0) |
|
230 | 239 | rexml (3.2.5) |
|
231 | 240 | rouge (4.0.0) |
|
232 | 241 | ruby-progressbar (1.11.0) |
|
233 | 242 | ruby_parser (3.19.1) |
|
234 | 243 | sexp_processor (~> 4.16) |
|
235 | 244 | rubyzip (2.3.2) |
|
236 | 245 | sassc (2.4.0) |
|
237 | 246 | ffi (~> 1.9) |
|
238 | 247 | sassc-rails (2.1.2) |
|
239 | 248 | railties (>= 4.0.0) |
|
240 | 249 | sassc (>= 2.0) |
|
241 | 250 | sprockets (> 3.0) |
|
242 | 251 | sprockets-rails |
|
243 | 252 | tilt |
|
244 | 253 | selenium-webdriver (4.4.0) |
|
245 | 254 | childprocess (>= 0.5, < 5.0) |
|
246 | 255 | rexml (~> 3.2, >= 3.2.5) |
|
247 | 256 | rubyzip (>= 1.2.2, < 3.0) |
|
248 | 257 | websocket (~> 1.0) |
|
249 | 258 | sexp_processor (4.16.1) |
|
250 | - simple_form (5.1.0) | |
|
251 | - actionpack (>= 5.2) | |
|
252 | - activemodel (>= 5.2) | |
|
253 | 259 | spring (2.1.1) |
|
254 | 260 | spring-watcher-listen (2.0.1) |
|
255 | 261 | listen (>= 2.7, < 4.0) |
|
256 | 262 | spring (>= 1.2, < 3.0) |
|
257 | 263 | sprockets (4.1.1) |
|
258 | 264 | concurrent-ruby (~> 1.0) |
|
259 | 265 | rack (> 1, < 3) |
|
260 | 266 | sprockets-rails (3.4.2) |
|
261 | 267 | actionpack (>= 5.2) |
|
262 | 268 | activesupport (>= 5.2) |
|
263 | 269 | sprockets (>= 3.0.0) |
|
264 | 270 | sqlite3 (1.5.0-x86_64-linux) |
|
265 | 271 | strscan (3.0.4) |
|
266 | 272 | temple (0.8.2) |
|
267 | 273 | thor (1.2.1) |
|
268 | 274 | tilt (2.0.11) |
|
269 | 275 | timeout (0.3.0) |
|
270 | 276 | tzinfo (2.0.5) |
|
271 | 277 | concurrent-ruby (~> 1.0) |
|
272 | 278 | web-console (4.2.0) |
|
273 | 279 | actionview (>= 6.0.0) |
|
274 | 280 | activemodel (>= 6.0.0) |
|
275 | 281 | bindex (>= 0.4.0) |
|
276 | 282 | railties (>= 6.0.0) |
|
277 | 283 | webdrivers (5.1.0) |
|
278 | 284 | nokogiri (~> 1.6) |
|
279 | 285 | rubyzip (>= 1.3.0) |
|
280 | 286 | selenium-webdriver (~> 4.0) |
|
281 | 287 | websocket (1.2.9) |
|
282 | 288 | websocket-driver (0.7.5) |
|
283 | 289 | websocket-extensions (>= 0.1.0) |
|
284 | 290 | websocket-extensions (0.1.5) |
|
285 | 291 | xpath (3.2.0) |
|
286 | 292 | nokogiri (~> 1.8) |
|
287 | 293 | zeitwerk (2.6.0) |
|
288 | 294 | |
|
289 | 295 | PLATFORMS |
|
290 | 296 | x86_64-linux |
|
291 | 297 | |
|
292 | 298 | DEPENDENCIES |
|
293 | 299 | ace-rails-ap |
|
294 | 300 | best_in_place! |
|
295 | 301 | bootsnap |
|
296 | 302 | bootstrap (~> 5.2) |
|
297 | 303 | byebug |
|
298 | 304 | capybara |
|
299 | 305 | coffee-rails |
|
300 | 306 | fuzzy-string-match |
|
301 | 307 | haml |
|
302 | 308 | haml-rails |
|
303 | 309 | importmap-rails (~> 1.1) |
|
304 | 310 | jbuilder |
|
305 | 311 | jquery-rails |
|
306 | 312 | listen (>= 3.0.5, < 3.2) |
|
307 | 313 | |
|
308 | 314 | material_icons |
|
309 | 315 | minitest-reporters |
|
310 | 316 | momentjs-rails |
|
311 | 317 | mysql2 |
|
312 | 318 | puma |
|
313 | 319 | rails (~> 7.0) |
|
314 | 320 | rdiscount |
|
315 | 321 | rouge |
|
316 | 322 | sassc-rails |
|
317 | 323 | selenium-webdriver |
|
318 | - simple_form | |
|
324 | + simple_form! | |
|
319 | 325 | spring |
|
320 | 326 | spring-watcher-listen (~> 2.0.0) |
|
321 | 327 | sprockets-rails |
|
322 | 328 | sqlite3 |
|
323 | 329 | web-console (>= 3.3.0) |
|
324 | 330 | webdrivers |
|
325 | 331 | |
|
326 | 332 | RUBY VERSION |
|
327 | 333 | ruby 3.1.2p20 |
|
328 | 334 | |
|
329 | 335 | BUNDLED WITH |
|
330 | 336 | 2.3.22 |
@@ -1,424 +1,420 | |||
|
1 | 1 | |
|
2 | 2 | .secondnavbar { |
|
3 | 3 | top: 50px; |
|
4 | 4 | } |
|
5 | 5 | |
|
6 | 6 | |
|
7 | 7 | //for google material design |
|
8 | 8 | .mi-bs { |
|
9 | 9 | vertical-align: middle; |
|
10 | 10 | position: relative; |
|
11 | 11 | top: -3px; |
|
12 | 12 | } |
|
13 | 13 | |
|
14 | 14 | // --------------- bootstrap file upload ---------------------- |
|
15 | 15 | .btn-file { |
|
16 | 16 | position: relative; |
|
17 | 17 | overflow: hidden; |
|
18 | 18 | } |
|
19 | 19 | |
|
20 | 20 | .btn-file input[type=file] { |
|
21 | 21 | position: absolute; |
|
22 | 22 | top: 0; |
|
23 | 23 | right: 0; |
|
24 | 24 | min-width: 100%; |
|
25 | 25 | min-height: 100%; |
|
26 | 26 | font-size: 100px; |
|
27 | 27 | text-align: right; |
|
28 | 28 | filter: alpha(opacity = 0); |
|
29 | 29 | opacity: 0; |
|
30 | 30 | outline: none; |
|
31 | 31 | background: white; |
|
32 | 32 | cursor: inherit; |
|
33 | 33 | display: block; |
|
34 | 34 | } |
|
35 | 35 | |
|
36 | 36 | body { |
|
37 | 37 | //font-size: 13px |
|
38 | 38 | font-family: 'Noto Sans Thai', Tahoma, "sans-serif"; |
|
39 | 39 | margin: 10px; |
|
40 | 40 | padding: 10px; |
|
41 | 41 | padding-top: 60px; |
|
42 | 42 | } |
|
43 | 43 | |
|
44 | 44 | // ------------------ bootstrap sortable -------------------- |
|
45 | 45 | table.sortable th { |
|
46 | 46 | padding-right: 20px !important; |
|
47 | 47 | |
|
48 | 48 | span.sign { |
|
49 | 49 | right: (-15px) !important; |
|
50 | 50 | } |
|
51 | 51 | |
|
52 | 52 | &.text-right { |
|
53 | 53 | padding-left: 20px !important; |
|
54 | 54 | padding-right: 8px !important; |
|
55 | 55 | |
|
56 | 56 | &:after, span.sign { |
|
57 | 57 | left: (-15px) !important; |
|
58 | 58 | } |
|
59 | 59 | } |
|
60 | 60 | } |
|
61 | 61 | |
|
62 | - input { | |
|
63 | - font-family: Tahoma, "sans-serif"; | |
|
64 | - } | |
|
65 | - | |
|
66 | 62 | h1 { |
|
67 | 63 | color: #334488; |
|
68 | 64 | } |
|
69 | 65 | |
|
70 | 66 | h2 { |
|
71 | 67 | color: #5566bb; |
|
72 | 68 | } |
|
73 | 69 | |
|
74 | 70 | hr { |
|
75 | 71 | border-top: 1px solid #dddddd; |
|
76 | 72 | border-bottom: 1px solid #eeeeee; |
|
77 | 73 | } |
|
78 | 74 | |
|
79 | 75 | //#a |
|
80 | 76 | // color: #6666cc |
|
81 | 77 | // text-decoration: none |
|
82 | 78 | // |
|
83 | 79 | // &:link, &:visited |
|
84 | 80 | // color: #6666cc |
|
85 | 81 | // text-decoration: none |
|
86 | 82 | // |
|
87 | 83 | // &:hover, &:focus |
|
88 | 84 | // color: #111166 |
|
89 | 85 | // text-decoration: none |
|
90 | 86 | |
|
91 | 87 | div { |
|
92 | 88 | &.userbar { |
|
93 | 89 | line-height: 1.5em; |
|
94 | 90 | text-align: right; |
|
95 | 91 | font-size: 12px; |
|
96 | 92 | } |
|
97 | 93 | |
|
98 | 94 | &.title { |
|
99 | 95 | padding: 10px 0px; |
|
100 | 96 | line-height: 1.5em; |
|
101 | 97 | font-size: 13px; |
|
102 | 98 | |
|
103 | 99 | span.contest-over-msg { |
|
104 | 100 | font-size: 15px; |
|
105 | 101 | color: red; |
|
106 | 102 | } |
|
107 | 103 | |
|
108 | 104 | table { |
|
109 | 105 | width: 100%; |
|
110 | 106 | font-weight: bold; |
|
111 | 107 | } |
|
112 | 108 | |
|
113 | 109 | td { |
|
114 | 110 | &.left-col { |
|
115 | 111 | text-align: left; |
|
116 | 112 | vertical-align: top; |
|
117 | 113 | color: #444444; |
|
118 | 114 | } |
|
119 | 115 | |
|
120 | 116 | &.right-col { |
|
121 | 117 | text-align: right; |
|
122 | 118 | vertical-align: top; |
|
123 | 119 | font-size: 18px; |
|
124 | 120 | color: #116699; |
|
125 | 121 | } |
|
126 | 122 | } |
|
127 | 123 | } |
|
128 | 124 | } |
|
129 | 125 | |
|
130 | 126 | table.info { |
|
131 | 127 | margin: 10px 0; |
|
132 | 128 | border: 1px solid #666666; |
|
133 | 129 | border-collapse: collapse; |
|
134 | 130 | font-size: 12px; |
|
135 | 131 | |
|
136 | 132 | th { |
|
137 | 133 | border: 1px solid #666666; |
|
138 | 134 | line-height: 1.5em; |
|
139 | 135 | padding: 0 0.5em; |
|
140 | 136 | } |
|
141 | 137 | |
|
142 | 138 | td { |
|
143 | 139 | border-left: 1px solid #666666; |
|
144 | 140 | border-right: 1px solid #666666; |
|
145 | 141 | line-height: 1.5em; |
|
146 | 142 | padding: 0 0.5em; |
|
147 | 143 | } |
|
148 | 144 | } |
|
149 | 145 | |
|
150 | 146 | tr { |
|
151 | 147 | &.info-head { |
|
152 | 148 | background: #777777; |
|
153 | 149 | color: white; |
|
154 | 150 | } |
|
155 | 151 | |
|
156 | 152 | &.info-odd { |
|
157 | 153 | background: #eeeeee; |
|
158 | 154 | } |
|
159 | 155 | |
|
160 | 156 | &.info-even { |
|
161 | 157 | background: #fcfcfc; |
|
162 | 158 | } |
|
163 | 159 | } |
|
164 | 160 | |
|
165 | 161 | @mixin basicbox { |
|
166 | 162 | background: #eeeeff; |
|
167 | 163 | border: 1px dotted #99aaee; |
|
168 | 164 | padding: 5px; |
|
169 | 165 | margin: 10px 0px; |
|
170 | 166 | color: black; |
|
171 | 167 | font-size: 13px; |
|
172 | 168 | } |
|
173 | 169 | |
|
174 | 170 | .infobox { |
|
175 | 171 | @include basicbox; |
|
176 | 172 | } |
|
177 | 173 | |
|
178 | 174 | .submitbox { |
|
179 | 175 | @include basicbox; |
|
180 | 176 | } |
|
181 | 177 | |
|
182 | 178 | .errorExplanation { |
|
183 | 179 | border: 1px dotted gray; |
|
184 | 180 | color: #bb2222; |
|
185 | 181 | padding: 5px 15px 5px 15px; |
|
186 | 182 | margin-bottom: 5px; |
|
187 | 183 | background-color: white; |
|
188 | 184 | font-weight: normal; |
|
189 | 185 | |
|
190 | 186 | h2 { |
|
191 | 187 | color: #cc1111; |
|
192 | 188 | font-weight: bold; |
|
193 | 189 | } |
|
194 | 190 | } |
|
195 | 191 | |
|
196 | 192 | table.uinfo { |
|
197 | 193 | border-collapse: collapse; |
|
198 | 194 | border: 1px solid black; |
|
199 | 195 | font-size: 13px; |
|
200 | 196 | } |
|
201 | 197 | |
|
202 | 198 | td.uinfo { |
|
203 | 199 | vertical-align: top; |
|
204 | 200 | border: 1px solid black; |
|
205 | 201 | padding: 5px; |
|
206 | 202 | } |
|
207 | 203 | |
|
208 | 204 | th.uinfo { |
|
209 | 205 | background: lightgreen; |
|
210 | 206 | vertical-align: top; |
|
211 | 207 | text-align: right; |
|
212 | 208 | border: 1px solid black; |
|
213 | 209 | padding: 5px; |
|
214 | 210 | } |
|
215 | 211 | |
|
216 | 212 | div { |
|
217 | 213 | &.compilermsgbody { |
|
218 | 214 | font-family: monospace; |
|
219 | 215 | } |
|
220 | 216 | |
|
221 | 217 | &.task-menu { |
|
222 | 218 | text-align: center; |
|
223 | 219 | font-size: 13px; |
|
224 | 220 | line-height: 1.75em; |
|
225 | 221 | font-weight: bold; |
|
226 | 222 | border-top: 1px dashed gray; |
|
227 | 223 | border-bottom: 1px dashed gray; |
|
228 | 224 | margin-top: 2px; |
|
229 | 225 | margin-bottom: 4px; |
|
230 | 226 | } |
|
231 | 227 | } |
|
232 | 228 | |
|
233 | 229 | table.taskdesc { |
|
234 | 230 | border: 2px solid #dddddd; |
|
235 | 231 | border-collapse: collapse; |
|
236 | 232 | margin: 10px auto; |
|
237 | 233 | width: 90%; |
|
238 | 234 | font-size: 13px; |
|
239 | 235 | |
|
240 | 236 | p { |
|
241 | 237 | font-size: 13px; |
|
242 | 238 | } |
|
243 | 239 | |
|
244 | 240 | tr.name { |
|
245 | 241 | border: 2px solid #dddddd; |
|
246 | 242 | background: #dddddd; |
|
247 | 243 | color: #333333; |
|
248 | 244 | font-weight: bold; |
|
249 | 245 | font-size: 14px; |
|
250 | 246 | line-height: 1.5em; |
|
251 | 247 | text-align: center; |
|
252 | 248 | } |
|
253 | 249 | |
|
254 | 250 | td { |
|
255 | 251 | &.desc-odd { |
|
256 | 252 | padding: 5px; |
|
257 | 253 | padding-left: 20px; |
|
258 | 254 | background: #fefeee; |
|
259 | 255 | } |
|
260 | 256 | |
|
261 | 257 | &.desc-even { |
|
262 | 258 | padding: 5px; |
|
263 | 259 | padding-left: 20px; |
|
264 | 260 | background: #feeefe; |
|
265 | 261 | } |
|
266 | 262 | } |
|
267 | 263 | } |
|
268 | 264 | |
|
269 | 265 | .announcementbox { |
|
270 | 266 | margin: 10px 0px; |
|
271 | 267 | background: #bbddee; |
|
272 | 268 | padding: 1px; |
|
273 | 269 | |
|
274 | 270 | span.title { |
|
275 | 271 | font-weight: bold; |
|
276 | 272 | color: #224455; |
|
277 | 273 | padding-left: 10px; |
|
278 | 274 | line-height: 1.6em; |
|
279 | 275 | } |
|
280 | 276 | } |
|
281 | 277 | |
|
282 | 278 | .announcement { |
|
283 | 279 | margin: 2px; |
|
284 | 280 | background: white; |
|
285 | 281 | padding: 1px; |
|
286 | 282 | padding-left: 10px; |
|
287 | 283 | padding-right: 10px; |
|
288 | 284 | padding-top: 5px; |
|
289 | 285 | padding-bottom: 5px; |
|
290 | 286 | } |
|
291 | 287 | |
|
292 | 288 | .announcement p { |
|
293 | 289 | font-size: 12px; |
|
294 | 290 | margin: 2px; |
|
295 | 291 | } |
|
296 | 292 | |
|
297 | 293 | .pub-info { |
|
298 | 294 | text-align: right; |
|
299 | 295 | font-style: italic; |
|
300 | 296 | font-size: 9px; |
|
301 | 297 | |
|
302 | 298 | p { |
|
303 | 299 | text-align: right; |
|
304 | 300 | font-style: italic; |
|
305 | 301 | font-size: 9px; |
|
306 | 302 | } |
|
307 | 303 | } |
|
308 | 304 | |
|
309 | 305 | .announcement { |
|
310 | 306 | .toggles { |
|
311 | 307 | font-weight: normal; |
|
312 | 308 | float: right; |
|
313 | 309 | font-size: 80%; |
|
314 | 310 | } |
|
315 | 311 | |
|
316 | 312 | .announcement-title { |
|
317 | 313 | font-weight: bold; |
|
318 | 314 | } |
|
319 | 315 | } |
|
320 | 316 | |
|
321 | 317 | div { |
|
322 | 318 | &.message { |
|
323 | 319 | margin: 10px 0 0; |
|
324 | 320 | |
|
325 | 321 | div { |
|
326 | 322 | &.message { |
|
327 | 323 | margin: 0 0 0 30px; |
|
328 | 324 | } |
|
329 | 325 | |
|
330 | 326 | &.body { |
|
331 | 327 | border: 2px solid #dddddd; |
|
332 | 328 | background: #fff8f8; |
|
333 | 329 | padding-left: 5px; |
|
334 | 330 | } |
|
335 | 331 | |
|
336 | 332 | &.reply-body { |
|
337 | 333 | border: 2px solid #bbbbbb; |
|
338 | 334 | background: #fffff8; |
|
339 | 335 | padding-left: 5px; |
|
340 | 336 | } |
|
341 | 337 | |
|
342 | 338 | &.stat { |
|
343 | 339 | font-size: 10px; |
|
344 | 340 | line-height: 1.75em; |
|
345 | 341 | padding: 0 5px; |
|
346 | 342 | color: #333333; |
|
347 | 343 | background: #dddddd; |
|
348 | 344 | font-weight: bold; |
|
349 | 345 | } |
|
350 | 346 | |
|
351 | 347 | &.message div.stat { |
|
352 | 348 | font-size: 10px; |
|
353 | 349 | line-height: 1.75em; |
|
354 | 350 | padding: 0 5px; |
|
355 | 351 | color: #444444; |
|
356 | 352 | background: #bbbbbb; |
|
357 | 353 | font-weight: bold; |
|
358 | 354 | } |
|
359 | 355 | } |
|
360 | 356 | } |
|
361 | 357 | |
|
362 | 358 | &.contest-title { |
|
363 | 359 | color: white; |
|
364 | 360 | text-align: center; |
|
365 | 361 | line-height: 2em; |
|
366 | 362 | } |
|
367 | 363 | |
|
368 | 364 | &.registration-desc, &.test-desc { |
|
369 | 365 | border: 1px dotted gray; |
|
370 | 366 | background: #f5f5f5; |
|
371 | 367 | padding: 5px; |
|
372 | 368 | margin: 10px 0; |
|
373 | 369 | font-size: 12px; |
|
374 | 370 | line-height: 1.5em; |
|
375 | 371 | } |
|
376 | 372 | } |
|
377 | 373 | |
|
378 | 374 | h2.contest-title { |
|
379 | 375 | margin-top: 5px; |
|
380 | 376 | margin-bottom: 5px; |
|
381 | 377 | } |
|
382 | 378 | |
|
383 | 379 | |
|
384 | 380 | |
|
385 | 381 | .grader-comment { |
|
386 | 382 | word-wrap: break-word; |
|
387 | 383 | font-family: consolas; |
|
388 | 384 | } |
|
389 | 385 | |
|
390 | 386 | |
|
391 | 387 | golden-btn + .golden-btn { margin-top: 1em; } |
|
392 | 388 | |
|
393 | 389 | .golden-btn { |
|
394 | 390 | display: inline-block; |
|
395 | 391 | outline: none; |
|
396 | 392 | font-family: inherit; |
|
397 | 393 | box-sizing: border-box; |
|
398 | 394 | border: none; |
|
399 | 395 | box-shadow: 0 3px 6px rgba(0,0,0,.16), 0 3px 6px rgba(110,80,20,.4), |
|
400 | 396 | inset 0 -2px 5px 1px rgba(139,66,8,1), |
|
401 | 397 | inset 0 -1px 1px 3px rgba(250,227,133,1); |
|
402 | 398 | background-image: linear-gradient(160deg, #a54e07, #b47e11, #fef1a2, #bc881b, #a54e07) !important; |
|
403 | 399 | border: 1px solid #a55d07; |
|
404 | 400 | color: rgb(120,50,5); |
|
405 | 401 | text-shadow: 0 2px 2px rgba(250, 227, 133, 1); |
|
406 | 402 | cursor: pointer; |
|
407 | 403 | transition: all .2s ease-in-out; |
|
408 | 404 | background-size: 100% 100%; |
|
409 | 405 | background-position:center; |
|
410 | 406 | } |
|
411 | 407 | .golden-btn:focus, |
|
412 | 408 | .golden-btn:hover { |
|
413 | 409 | background-size: 150% 150%; |
|
414 | 410 | box-shadow: 0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23), |
|
415 | 411 | inset 0 -2px 5px 1px #b17d10, |
|
416 | 412 | inset 0 -1px 1px 3px rgba(250,227,133,1); |
|
417 | 413 | border: 1px solid rgba(165,93,7,.6); |
|
418 | 414 | color: rgba(120,50,5,.8); |
|
419 | 415 | } |
|
420 | 416 | .golden-btn:active { |
|
421 | 417 | box-shadow: 0 3px 6px rgba(0,0,0,.16), 0 3px 6px rgba(110,80,20,.4), |
|
422 | 418 | inset 0 -2px 5px 1px #b17d10, |
|
423 | 419 | inset 0 -1px 1px 3px rgba(250,227,133,1); |
|
424 | 420 | } |
@@ -1,262 +1,269 | |||
|
1 | 1 | require 'ipaddr' |
|
2 | 2 | require "securerandom" |
|
3 | 3 | |
|
4 | 4 | class ApplicationController < ActionController::Base |
|
5 | 5 | protect_from_forgery |
|
6 | 6 | |
|
7 | 7 | before_action :current_user |
|
8 | 8 | before_action :nav_announcement |
|
9 | 9 | before_action :unique_visitor_id |
|
10 | + before_action :active_controller_action | |
|
10 | 11 | |
|
11 | 12 | SINGLE_USER_MODE_CONF_KEY = 'system.single_user_mode' |
|
12 | 13 | MULTIPLE_IP_LOGIN_CONF_KEY = 'right.multiple_ip_login' |
|
13 | 14 | WHITELIST_IGNORE_CONF_KEY = 'right.whitelist_ignore' |
|
14 | 15 | WHITELIST_IP_CONF_KEY = 'right.whitelist_ip' |
|
15 | 16 | |
|
16 | 17 | #report and redirect for unauthorized activities |
|
17 | 18 | def unauthorized_redirect(notice = 'You are not authorized to view the page you requested') |
|
18 | 19 | flash[:notice] = notice |
|
19 | 20 | redirect_to login_main_path |
|
20 | 21 | end |
|
21 | 22 | |
|
22 | 23 | # Returns the current logged-in user (if any). |
|
23 | 24 | def current_user |
|
24 | 25 | return nil unless session[:user_id] |
|
25 | 26 | @current_user ||= User.find(session[:user_id]) |
|
26 | 27 | end |
|
27 | 28 | |
|
28 | 29 | def nav_announcement |
|
29 | 30 | @nav_announcement = Announcement.where(on_nav_bar: true) |
|
30 | 31 | end |
|
31 | 32 | |
|
33 | + def active_controller_action | |
|
34 | + #so that we can override this value inside each action | |
|
35 | + @active_controller = controller_name | |
|
36 | + @active_action = action_name | |
|
37 | + end | |
|
38 | + | |
|
32 | 39 | def admin_authorization |
|
33 | 40 | return false unless check_valid_login |
|
34 | 41 | user = User.includes(:roles).find(session[:user_id]) |
|
35 | 42 | unless user.admin? |
|
36 | 43 | unauthorized_redirect |
|
37 | 44 | return false |
|
38 | 45 | end |
|
39 | 46 | return true |
|
40 | 47 | end |
|
41 | 48 | |
|
42 | 49 | #admin always count as every roles |
|
43 | 50 | def role_authorization(roles) |
|
44 | 51 | return false unless check_valid_login |
|
45 | 52 | user = User.find(session[:user_id]) |
|
46 | 53 | return true if user.admin? |
|
47 | 54 | roles.each do |r| |
|
48 | 55 | return true if user.has_role?(r) |
|
49 | 56 | end |
|
50 | 57 | unauthorized_redirect |
|
51 | 58 | end |
|
52 | 59 | |
|
53 | 60 | def authorization_by_roles(allowed_roles) |
|
54 | 61 | return false unless check_valid_login |
|
55 | 62 | unless @current_user.roles.detect { |role| allowed_roles.member?(role.name) } |
|
56 | 63 | unauthorized_redirect |
|
57 | 64 | return false |
|
58 | 65 | end |
|
59 | 66 | end |
|
60 | 67 | |
|
61 | 68 | def testcase_authorization |
|
62 | 69 | #admin always has privileged |
|
63 | 70 | if @current_user.admin? |
|
64 | 71 | return true |
|
65 | 72 | end |
|
66 | 73 | |
|
67 | 74 | unauthorized_redirect unless GraderConfiguration["right.view_testcase"] |
|
68 | 75 | end |
|
69 | 76 | |
|
70 | 77 | def unique_visitor_id |
|
71 | 78 | unless cookies.encrypted[:uuid] |
|
72 | 79 | value = SecureRandom.uuid |
|
73 | 80 | cookies.encrypted[:uuid] = { value: value, expires: 20.year } |
|
74 | 81 | end |
|
75 | 82 | end |
|
76 | 83 | |
|
77 | 84 | protected |
|
78 | 85 | |
|
79 | 86 | #redirect to root (and also force logout) |
|
80 | 87 | #if the user is not logged_in or the system is in "ADMIN ONLY" mode |
|
81 | 88 | def check_valid_login |
|
82 | 89 | #check if logged in |
|
83 | 90 | unless session[:user_id] |
|
84 | 91 | if GraderConfiguration[SINGLE_USER_MODE_CONF_KEY] |
|
85 | 92 | unauthorized_redirect('You need to login but you cannot log in at this time') |
|
86 | 93 | else |
|
87 | 94 | unauthorized_redirect('You need to login') |
|
88 | 95 | end |
|
89 | 96 | return false |
|
90 | 97 | end |
|
91 | 98 | |
|
92 | 99 | # check if run in single user mode |
|
93 | 100 | if GraderConfiguration[SINGLE_USER_MODE_CONF_KEY] |
|
94 | 101 | if @current_user==nil || (!@current_user.admin?) |
|
95 | 102 | unauthorized_redirect('You cannot log in at this time') |
|
96 | 103 | return false |
|
97 | 104 | end |
|
98 | 105 | end |
|
99 | 106 | |
|
100 | 107 | # check if the user is enabled |
|
101 | 108 | unless @current_user.enabled? || @current_user.admin? |
|
102 | 109 | unauthorized_redirect 'Your account is disabled' |
|
103 | 110 | return false |
|
104 | 111 | end |
|
105 | 112 | |
|
106 | 113 | # check if user ip is allowed |
|
107 | 114 | unless @current_user.admin? || GraderConfiguration[WHITELIST_IGNORE_CONF_KEY] |
|
108 | 115 | unless is_request_ip_allowed? |
|
109 | 116 | unauthorized_redirect 'Your IP is not allowed to login at this time.' |
|
110 | 117 | return false |
|
111 | 118 | end |
|
112 | 119 | end |
|
113 | 120 | |
|
114 | 121 | if GraderConfiguration.multicontests? |
|
115 | 122 | return true if @current_user.admin? |
|
116 | 123 | begin |
|
117 | 124 | if @current_user.contest_stat(true).forced_logout |
|
118 | 125 | flash[:notice] = 'You have been automatically logged out.' |
|
119 | 126 | redirect_to :controller => 'main', :action => 'index' |
|
120 | 127 | end |
|
121 | 128 | rescue |
|
122 | 129 | end |
|
123 | 130 | end |
|
124 | 131 | return true |
|
125 | 132 | end |
|
126 | 133 | |
|
127 | 134 | #redirect to root (and also force logout) |
|
128 | 135 | #if the user use different ip from the previous connection |
|
129 | 136 | # only applicable when MULTIPLE_IP_LOGIN options is false only |
|
130 | 137 | def authenticate_by_ip_address |
|
131 | 138 | #this assume that we have already authenticate normally |
|
132 | 139 | unless GraderConfiguration[MULTIPLE_IP_LOGIN_CONF_KEY] |
|
133 | 140 | user = User.find(session[:user_id]) |
|
134 | 141 | if (!user.admin? && user.last_ip && user.last_ip != request.remote_ip) |
|
135 | 142 | flash[:notice] = "You cannot use the system from #{request.remote_ip}. Your last ip is #{user.last_ip}" |
|
136 | 143 | redirect_to :controller => 'main', :action => 'login' |
|
137 | 144 | return false |
|
138 | 145 | end |
|
139 | 146 | unless user.last_ip |
|
140 | 147 | user.last_ip = request.remote_ip |
|
141 | 148 | user.save |
|
142 | 149 | end |
|
143 | 150 | end |
|
144 | 151 | return true |
|
145 | 152 | end |
|
146 | 153 | |
|
147 | 154 | def authorization |
|
148 | 155 | return false unless check_valid_login |
|
149 | 156 | user = User.find(session[:user_id]) |
|
150 | 157 | unless user.roles.detect { |role| |
|
151 | 158 | role.rights.detect{ |right| |
|
152 | 159 | right.controller == self.class.controller_name and |
|
153 | 160 | (right.action == 'all' || right.action == action_name) |
|
154 | 161 | } |
|
155 | 162 | } |
|
156 | 163 | flash[:notice] = 'You are not authorized to view the page you requested' |
|
157 | 164 | #request.env['HTTP_REFERER'] ? (redirect_to :back) : (redirect_to :controller => 'login') |
|
158 | 165 | redirect_to :controller => 'main', :action => 'login' |
|
159 | 166 | return false |
|
160 | 167 | end |
|
161 | 168 | end |
|
162 | 169 | |
|
163 | 170 | def verify_time_limit |
|
164 | 171 | return true if session[:user_id]==nil |
|
165 | 172 | user = User.find(session[:user_id], :include => :site) |
|
166 | 173 | return true if user==nil || user.site == nil |
|
167 | 174 | if user.contest_finished? |
|
168 | 175 | flash[:notice] = 'Error: the contest you are participating is over.' |
|
169 | 176 | redirect_to :back |
|
170 | 177 | return false |
|
171 | 178 | end |
|
172 | 179 | return true |
|
173 | 180 | end |
|
174 | 181 | |
|
175 | 182 | def is_request_ip_allowed? |
|
176 | 183 | unless GraderConfiguration[WHITELIST_IGNORE_CONF_KEY] |
|
177 | 184 | user_ip = IPAddr.new(request.remote_ip) |
|
178 | 185 | allowed = GraderConfiguration[WHITELIST_IP_CONF_KEY] || '' |
|
179 | 186 | |
|
180 | 187 | allowed.delete(' ').split(',').each do |ips| |
|
181 | 188 | allow_ips = IPAddr.new(ips) |
|
182 | 189 | if allow_ips.include?(user_ip) |
|
183 | 190 | return true |
|
184 | 191 | end |
|
185 | 192 | end |
|
186 | 193 | return false |
|
187 | 194 | end |
|
188 | 195 | return true |
|
189 | 196 | end |
|
190 | 197 | |
|
191 | 198 | #function for datatable ajax query |
|
192 | 199 | #return record,total_count,filter_count |
|
193 | 200 | def process_query_record(record, |
|
194 | 201 | total_count: nil, |
|
195 | 202 | select: '', |
|
196 | 203 | global_search: [], |
|
197 | 204 | no_search: false, |
|
198 | 205 | force_order: '', |
|
199 | 206 | date_filter: '', date_param_since: 'date_since',date_param_until: 'date_until', |
|
200 | 207 | hard_limit: nil) |
|
201 | 208 | arel_table = record.model.arel_table |
|
202 | 209 | |
|
203 | 210 | if !no_search && params['search'] |
|
204 | 211 | global_value = record.model.sanitize_sql(params['search']['value'].strip.downcase) |
|
205 | 212 | if !global_value.blank? |
|
206 | 213 | global_value.split.each do |value| |
|
207 | 214 | global_where = global_search.map{|f| "LOWER(#{f}) like '%#{value}%'"}.join(' OR ') |
|
208 | 215 | record = record.where(global_where) |
|
209 | 216 | end |
|
210 | 217 | end |
|
211 | 218 | |
|
212 | 219 | params['columns'].each do |i, col| |
|
213 | 220 | if !col['search']['value'].blank? |
|
214 | 221 | record = record.where(arel_table[col['name']].lower.matches("%#{col['search']['value'].strip.downcase}%")) |
|
215 | 222 | end |
|
216 | 223 | end |
|
217 | 224 | end |
|
218 | 225 | |
|
219 | 226 | if !date_filter.blank? |
|
220 | 227 | param_since = params[date_param_since] |
|
221 | 228 | param_until = params[date_param_until] |
|
222 | 229 | date_since = Time.zone.parse( param_since ) || Time.new(1,1,1) rescue Time.new(1,1,1) |
|
223 | 230 | date_until = Time.zone.parse( param_until ) || Time.zone.now() rescue Time.zone.now() |
|
224 | 231 | date_range = date_since..(date_until.end_of_day) |
|
225 | 232 | record = record.where(date_filter.to_sym => date_range) |
|
226 | 233 | end |
|
227 | 234 | |
|
228 | 235 | if force_order.blank? |
|
229 | 236 | if params['order'] |
|
230 | 237 | params['order'].each do |i, o| |
|
231 | 238 | colName = params['columns'][o['column']]['name'] |
|
232 | 239 | colName = "#{record.model.table_name}.#{colName}" if colName.upcase == 'ID' |
|
233 | 240 | record = record.order("#{colName} #{o['dir'].casecmp('desc') != 0 ? 'ASC' : 'DESC'}") unless colName.blank? |
|
234 | 241 | end |
|
235 | 242 | end |
|
236 | 243 | else |
|
237 | 244 | record = record.order(force_order) |
|
238 | 245 | end |
|
239 | 246 | |
|
240 | 247 | filterCount = record.count(record.model.primary_key) |
|
241 | 248 | # if .group() is used, filterCount might be like {id_1: count_1, id_2: count_2, ...} |
|
242 | 249 | # so we should count the result again.. |
|
243 | 250 | if filterCount.is_a? Hash |
|
244 | 251 | filterCount = filterCount.count |
|
245 | 252 | end |
|
246 | 253 | |
|
247 | 254 | |
|
248 | 255 | record = record.offset(params['start'] || 0) |
|
249 | 256 | record = record.limit(hard_limit) |
|
250 | 257 | if (params['length']) |
|
251 | 258 | limit = params['length'].to_i |
|
252 | 259 | limit == hard_limit if (hard_limit && hard_limit < limit) |
|
253 | 260 | record = record.limit(limit) |
|
254 | 261 | end |
|
255 | 262 | if (!select.blank?) |
|
256 | 263 | record = record.select(select) |
|
257 | 264 | end |
|
258 | 265 | |
|
259 | 266 | return record, total_count || record.model.count, filterCount |
|
260 | 267 | end |
|
261 | 268 | |
|
262 | 269 | end |
@@ -1,306 +1,287 | |||
|
1 | 1 | class ProblemsController < ApplicationController |
|
2 | 2 | |
|
3 | + include ActiveStorage::SetCurrent | |
|
4 | + | |
|
3 | 5 | before_action :admin_authorization, except: [:stat] |
|
6 | + before_action :set_problem, only: [:show, :edit, :update, :destroy, :get_statement, :toggle, :toggle_test, :toggle_view_testcase, :stat] | |
|
4 | 7 | before_action only: [:stat] do |
|
5 | 8 | authorization_by_roles(['admin','ta']) |
|
6 | 9 | end |
|
7 | 10 | |
|
11 | + | |
|
8 | 12 | def index |
|
9 | 13 | @problems = Problem.order(date_added: :desc) |
|
10 | 14 | end |
|
11 | 15 | |
|
12 | 16 | |
|
13 | 17 | def show |
|
14 | - @problem = Problem.find(params[:id]) | |
|
18 | + end | |
|
19 | + | |
|
20 | + #get statement download link | |
|
21 | + def get_statement | |
|
22 | + unless @current_user.can_view_problem? @problem | |
|
23 | + redirect_to list_main_path, error: 'You are not authorized to access this file' | |
|
24 | + return | |
|
25 | + end | |
|
26 | + | |
|
27 | + if params[:ext]=='pdf' | |
|
28 | + content_type = 'application/pdf' | |
|
29 | + else | |
|
30 | + content_type = 'application/octet-stream' | |
|
31 | + end | |
|
32 | + | |
|
33 | + filename = @problem.statement.filename.to_s | |
|
34 | + data =@problem.statement.download | |
|
35 | + | |
|
36 | + send_data data, stream: false, disposition: 'inline', filename: filename, type: content_type | |
|
15 | 37 | end |
|
16 | 38 | |
|
17 | 39 | def new |
|
18 | 40 | @problem = Problem.new |
|
19 | - @description = nil | |
|
20 | 41 | end |
|
21 | 42 | |
|
22 | 43 | def create |
|
23 | 44 | @problem = Problem.new(problem_params) |
|
24 | - @description = Description.new(description_params) | |
|
25 | - if @description.body!='' | |
|
26 | - if !@description.save | |
|
27 | - render :action => new and return | |
|
28 | - end | |
|
29 | - else | |
|
30 | - @description = nil | |
|
31 | - end | |
|
32 | - @problem.description = @description | |
|
33 | 45 | if @problem.save |
|
34 |
- |
|
|
35 | - redirect_to action: :index | |
|
46 | + redirect_to action: :index, notice: 'Problem was successfully created.' | |
|
36 | 47 | else |
|
37 | 48 | render :action => 'new' |
|
38 | 49 | end |
|
39 | 50 | end |
|
40 | 51 | |
|
41 | 52 | def quick_create |
|
42 | 53 | @problem = Problem.new(problem_params) |
|
43 | 54 | @problem.full_name = @problem.name if @problem.full_name == '' |
|
44 | 55 | @problem.full_score = 100 |
|
45 | 56 | @problem.available = false |
|
46 | 57 | @problem.test_allowed = true |
|
47 | 58 | @problem.output_only = false |
|
48 | 59 | @problem.date_added = Time.new |
|
49 | 60 | if @problem.save |
|
50 | 61 | flash[:notice] = 'Problem was successfully created.' |
|
51 | 62 | redirect_to action: :index |
|
52 | 63 | else |
|
53 | 64 | flash[:notice] = 'Error saving problem' |
|
54 | 65 | redirect_to action: :index |
|
55 | 66 | end |
|
56 | 67 | end |
|
57 | 68 | |
|
58 | 69 | def edit |
|
59 | - @problem = Problem.find(params[:id]) | |
|
60 | 70 | @description = @problem.description |
|
61 | 71 | end |
|
62 | 72 | |
|
63 | 73 | def update |
|
64 | - @problem = Problem.find(params[:id]) | |
|
65 | - @description = @problem.description | |
|
66 | - if @description.nil? and params[:description][:body]!='' | |
|
67 | - @description = Description.new(description_params) | |
|
68 | - if !@description.save | |
|
69 | - flash[:notice] = 'Error saving description' | |
|
70 | - render :action => 'edit' and return | |
|
71 | - end | |
|
72 | - @problem.description = @description | |
|
73 | - elsif @description | |
|
74 | - if !@description.update(description_params) | |
|
75 | - flash[:notice] = 'Error saving description' | |
|
76 | - render :action => 'edit' and return | |
|
77 | - end | |
|
78 | - end | |
|
79 | - if params[:file] and params[:file].content_type != 'application/pdf' | |
|
80 | - flash[:notice] = 'Error: Uploaded file is not PDF' | |
|
81 | - render :action => 'edit' and return | |
|
74 | + if problem_params[:statement] && problem_params[:statement].content_type != 'application/pdf' | |
|
75 | + flash[:error] = 'Error: Uploaded file is not PDF' | |
|
76 | + render :action => 'edit' | |
|
77 | + return | |
|
82 | 78 | end |
|
83 | 79 | if @problem.update(problem_params) |
|
84 | 80 | flash[:notice] = 'Problem was successfully updated.' |
|
85 | - unless params[:file] == nil or params[:file] == '' | |
|
86 | - flash[:notice] = 'Problem was successfully updated and a new PDF file is uploaded.' | |
|
87 | - out_dirname = "#{Problem.download_file_basedir}/#{@problem.id}" | |
|
88 | - if not FileTest.exists? out_dirname | |
|
89 | - Dir.mkdir out_dirname | |
|
90 | - end | |
|
91 | - | |
|
92 | - out_filename = "#{out_dirname}/#{@problem.name}.pdf" | |
|
93 | - if FileTest.exists? out_filename | |
|
94 | - File.delete out_filename | |
|
95 | - end | |
|
96 | - | |
|
97 | - File.open(out_filename,"wb") do |file| | |
|
98 | - file.write(params[:file].read) | |
|
99 | - end | |
|
100 | - @problem.description_filename = "#{@problem.name}.pdf" | |
|
81 | + flash[:notice] += 'A new statement PDF is uploaded' if problem_params[:statement] | |
|
101 | 82 |
|
|
102 | - end | |
|
103 | - redirect_to :action => 'show', :id => @problem | |
|
83 | + redirect_to edit_problem_path(@problem) | |
|
104 | 84 | else |
|
105 | 85 | render :action => 'edit' |
|
106 | 86 | end |
|
107 | 87 | end |
|
108 | 88 | |
|
109 | 89 | def destroy |
|
110 |
- p |
|
|
90 | + @problem.destroy | |
|
111 | 91 | redirect_to action: :index |
|
112 | 92 | end |
|
113 | 93 | |
|
114 | 94 | def toggle |
|
115 | - @problem = Problem.find(params[:id]) | |
|
116 | 95 | @problem.update(available: !(@problem.available) ) |
|
117 | 96 | respond_to do |format| |
|
118 | 97 | format.js { } |
|
119 | 98 | end |
|
120 | 99 | end |
|
121 | 100 | |
|
122 | 101 | def toggle_test |
|
123 | - @problem = Problem.find(params[:id]) | |
|
124 | 102 | @problem.update(test_allowed: !(@problem.test_allowed?) ) |
|
125 | 103 | respond_to do |format| |
|
126 | 104 | format.js { } |
|
127 | 105 | end |
|
128 | 106 | end |
|
129 | 107 | |
|
130 | 108 | def toggle_view_testcase |
|
131 | - @problem = Problem.find(params[:id]) | |
|
132 | 109 | @problem.update(view_testcase: !(@problem.view_testcase?) ) |
|
133 | 110 | respond_to do |format| |
|
134 | 111 | format.js { } |
|
135 | 112 | end |
|
136 | 113 | end |
|
137 | 114 | |
|
138 | 115 | def turn_all_off |
|
139 | 116 | Problem.available.all.each do |problem| |
|
140 | 117 | problem.available = false |
|
141 | 118 | problem.save |
|
142 | 119 | end |
|
143 | 120 | redirect_to action: :index |
|
144 | 121 | end |
|
145 | 122 | |
|
146 | 123 | def turn_all_on |
|
147 | 124 | Problem.where.not(available: true).each do |problem| |
|
148 | 125 | problem.available = true |
|
149 | 126 | problem.save |
|
150 | 127 | end |
|
151 | 128 | redirect_to action: :index |
|
152 | 129 | end |
|
153 | 130 | |
|
154 | 131 | def stat |
|
155 | - @problem = Problem.find(params[:id]) | |
|
156 | 132 | unless @problem.available or session[:admin] |
|
157 | 133 | redirect_to :controller => 'main', :action => 'list' |
|
158 | 134 | return |
|
159 | 135 | end |
|
160 | 136 | @submissions = Submission.includes(:user).includes(:language).where(problem_id: params[:id]).order(:user_id,:id) |
|
161 | 137 | |
|
162 | 138 | #stat summary |
|
163 | 139 | range =65 |
|
164 | 140 | @histogram = { data: Array.new(range,0), summary: {} } |
|
165 | 141 | user = Hash.new(0) |
|
166 | 142 | @submissions.find_each do |sub| |
|
167 | 143 | d = (DateTime.now.in_time_zone - sub.submitted_at) / 24 / 60 / 60 |
|
168 | 144 | @histogram[:data][d.to_i] += 1 if d < range |
|
169 | 145 | user[sub.user_id] = [user[sub.user_id], ((sub.try(:points) || 0) >= @problem.full_score) ? 1 : 0].max |
|
170 | 146 | end |
|
171 | 147 | @histogram[:summary][:max] = [@histogram[:data].max,1].max |
|
172 | 148 | |
|
173 | 149 | @summary = { attempt: user.count, solve: 0 } |
|
174 | 150 | user.each_value { |v| @summary[:solve] += 1 if v == 1 } |
|
175 | 151 | end |
|
176 | 152 | |
|
177 | 153 | def manage |
|
178 | 154 | @problems = Problem.order(date_added: :desc) |
|
179 | 155 | end |
|
180 | 156 | |
|
181 | 157 | def do_manage |
|
182 | 158 | if params.has_key? 'change_date_added' and params[:date_added].strip.empty? == false |
|
183 | 159 | change_date_added |
|
184 | 160 | elsif params.has_key? 'add_to_contest' |
|
185 | 161 | add_to_contest |
|
186 | 162 | elsif params.has_key? 'enable_problem' |
|
187 | 163 | set_available(true) |
|
188 | 164 | elsif params.has_key? 'disable_problem' |
|
189 | 165 | set_available(false) |
|
190 | 166 | elsif params.has_key? 'add_group' |
|
191 | 167 | group = Group.find(params[:group_id]) |
|
192 | 168 | ok = [] |
|
193 | 169 | failed = [] |
|
194 | 170 | get_problems_from_params.each do |p| |
|
195 | 171 | begin |
|
196 | 172 | group.problems << p |
|
197 | 173 | ok << p.full_name |
|
198 | 174 | rescue => e |
|
199 | 175 | failed << p.full_name |
|
200 | 176 | end |
|
201 | 177 | end |
|
202 | 178 | flash[:success] = "The following problems are added to the group #{group.name}: " + ok.join(', ') if ok.count > 0 |
|
203 | 179 | flash[:alert] = "The following problems are already in the group #{group.name}: " + failed.join(', ') if failed.count > 0 |
|
204 | 180 | elsif params.has_key? 'add_tags' |
|
205 | 181 | get_problems_from_params.each do |p| |
|
206 | 182 | p.tag_ids += params[:tag_ids] |
|
207 | 183 | end |
|
208 | 184 | end |
|
209 | 185 | |
|
210 | 186 | redirect_to :action => 'manage' |
|
211 | 187 | end |
|
212 | 188 | |
|
213 | 189 | def import |
|
214 | 190 | @allow_test_pair_import = allow_test_pair_import? |
|
215 | 191 | end |
|
216 | 192 | |
|
217 | 193 | def do_import |
|
218 | 194 | old_problem = Problem.find_by_name(params[:name]) |
|
219 | 195 | if !allow_test_pair_import? and params.has_key? :import_to_db |
|
220 | 196 | params.delete :import_to_db |
|
221 | 197 | end |
|
222 | 198 | @problem, import_log = Problem.create_from_import_form_params(params, |
|
223 | 199 | old_problem) |
|
224 | 200 | |
|
225 | 201 | if !@problem.errors.empty? |
|
226 | 202 | render :action => 'import' and return |
|
227 | 203 | end |
|
228 | 204 | |
|
229 | 205 | if old_problem!=nil |
|
230 | 206 | flash[:notice] = "The test data has been replaced for problem #{@problem.name}" |
|
231 | 207 | end |
|
232 | 208 | @log = import_log |
|
233 | 209 | end |
|
234 | 210 | |
|
235 | 211 | def remove_contest |
|
236 | 212 | problem = Problem.find(params[:id]) |
|
237 | 213 | contest = Contest.find(params[:contest_id]) |
|
238 | 214 | if problem!=nil and contest!=nil |
|
239 | 215 | problem.contests.delete(contest) |
|
240 | 216 | end |
|
241 | 217 | redirect_to :action => 'manage' |
|
242 | 218 | end |
|
243 | 219 | |
|
244 | 220 | ################################## |
|
245 | 221 | protected |
|
246 | 222 | |
|
247 | 223 | def allow_test_pair_import? |
|
248 | 224 | if defined? ALLOW_TEST_PAIR_IMPORT |
|
249 | 225 | return ALLOW_TEST_PAIR_IMPORT |
|
250 | 226 | else |
|
251 | 227 | return false |
|
252 | 228 | end |
|
253 | 229 | end |
|
254 | 230 | |
|
255 | 231 | def change_date_added |
|
256 | 232 | problems = get_problems_from_params |
|
257 | 233 | date = Date.parse(params[:date_added]) |
|
258 | 234 | problems.each do |p| |
|
259 | 235 | p.date_added = date |
|
260 | 236 | p.save |
|
261 | 237 | end |
|
262 | 238 | end |
|
263 | 239 | |
|
264 | 240 | def add_to_contest |
|
265 | 241 | problems = get_problems_from_params |
|
266 | 242 | contest = Contest.find(params[:contest][:id]) |
|
267 | 243 | if contest!=nil and contest.enabled |
|
268 | 244 | problems.each do |p| |
|
269 | 245 | p.contests << contest |
|
270 | 246 | end |
|
271 | 247 | end |
|
272 | 248 | end |
|
273 | 249 | |
|
274 | 250 | def set_available(avail) |
|
275 | 251 | problems = get_problems_from_params |
|
276 | 252 | problems.each do |p| |
|
277 | 253 | p.available = avail |
|
278 | 254 | p.save |
|
279 | 255 | end |
|
280 | 256 | end |
|
281 | 257 | |
|
282 | 258 | def get_problems_from_params |
|
283 | 259 | problems = [] |
|
284 | 260 | params.keys.each do |k| |
|
285 | 261 | if k.index('prob-')==0 |
|
286 | 262 | name, id, order = k.split('-') |
|
287 | 263 | problems << Problem.find(id) |
|
288 | 264 | end |
|
289 | 265 | end |
|
290 | 266 | problems |
|
291 | 267 | end |
|
292 | 268 | |
|
293 | 269 | def get_problems_stat |
|
294 | 270 | end |
|
295 | 271 | |
|
296 | 272 | private |
|
297 | 273 | |
|
274 | + def set_problem | |
|
275 | + @problem = Problem.find(params[:id]) | |
|
276 | + end | |
|
277 | + | |
|
298 | 278 | def problem_params |
|
299 |
- params.require(:problem).permit(:name, :full_name, :full_score, :change_date_added, :date_added, :available, |
|
|
279 | + params.require(:problem).permit(:name, :full_name, :full_score, :change_date_added, :date_added, :available, | |
|
280 | + :test_allowed, :output_only, :url, :description, :statement, :description, tag_ids:[]) | |
|
300 | 281 | end |
|
301 | 282 | |
|
302 | 283 | def description_params |
|
303 | 284 | params.require(:description).permit(:body, :markdowned) |
|
304 | 285 | end |
|
305 | 286 | |
|
306 | 287 | end |
@@ -1,212 +1,223 | |||
|
1 | 1 | # Methods added to this helper will be available to all templates in the application. |
|
2 | 2 | module ApplicationHelper |
|
3 | 3 | |
|
4 | 4 | #new bootstrap header |
|
5 | 5 | def navbar_user_header |
|
6 | 6 | left_menu = '' |
|
7 | 7 | right_menu = '' |
|
8 | 8 | user = User.find(session[:user_id]) |
|
9 | 9 | |
|
10 | 10 | if (user!=nil) and (GraderConfiguration.show_tasks_to?(user)) |
|
11 | 11 | left_menu << add_menu("#{I18n.t 'menu.tasks'}", 'tasks', 'list') |
|
12 | 12 | left_menu << add_menu("#{I18n.t 'menu.submissions'}", 'main', 'submission') |
|
13 | 13 | left_menu << add_menu("#{I18n.t 'menu.test'}", 'test', 'index') |
|
14 | 14 | end |
|
15 | 15 | |
|
16 | 16 | if GraderConfiguration['right.user_hall_of_fame'] |
|
17 | 17 | left_menu << add_menu("#{I18n.t 'menu.hall_of_fame'}", 'report', 'problem_hof') |
|
18 | 18 | end |
|
19 | 19 | |
|
20 | 20 | right_menu << add_menu("#{content_tag(:span,'',class: 'glyphicon glyphicon-question-sign')}".html_safe, 'main', 'help') |
|
21 | 21 | right_menu << add_menu("#{content_tag(:span,'',class: 'glyphicon glyphicon-comment')}".html_safe, 'messages', 'list', {title: I18n.t('menu.messages'), data: {toggle: 'tooltip'}}) |
|
22 | 22 | if GraderConfiguration['system.user_setting_enabled'] |
|
23 | 23 | right_menu << add_menu("#{content_tag(:span,'',class: 'glyphicon glyphicon-cog')}".html_safe, 'users', 'index', {title: I18n.t('menu.settings'), data: {toggle: 'tooltip'}}) |
|
24 | 24 | end |
|
25 | 25 | right_menu << add_menu("#{content_tag(:span,'',class: 'glyphicon glyphicon-log-out')} #{user.full_name}".html_safe, 'main', 'login', {title: I18n.t('menu.log_out'), data: {toggle: 'tooltip'}}) |
|
26 | 26 | |
|
27 | 27 | |
|
28 | 28 | result = content_tag(:ul,left_menu.html_safe,class: 'nav navbar-nav') + content_tag(:ul,right_menu.html_safe,class: 'nav navbar-nav navbar-right') |
|
29 | 29 | end |
|
30 | 30 | |
|
31 | 31 | def add_menu(title, controller, action, html_option = {}) |
|
32 | 32 | link_option = {controller: controller, action: action} |
|
33 | 33 | html_option[:class] = (html_option[:class] || '') + " active" if current_page?(link_option) |
|
34 | 34 | content_tag(:li, link_to(title,link_option),html_option) |
|
35 | 35 | end |
|
36 | 36 | |
|
37 | 37 | def user_header |
|
38 | 38 | menu_items = '' |
|
39 | 39 | user = User.find(session[:user_id]) |
|
40 | 40 | |
|
41 | 41 | if (user!=nil) and (session[:admin]) |
|
42 | 42 | # admin menu |
|
43 | 43 | menu_items << "<b>Administrative task:</b> " |
|
44 | 44 | append_to menu_items, '[Announcements]', 'announcements', 'index' |
|
45 | 45 | append_to menu_items, '[Msg console]', 'messages', 'console' |
|
46 | 46 | append_to menu_items, '[Problems]', 'problems', 'index' |
|
47 | 47 | append_to menu_items, '[Users]', 'user_admin', 'index' |
|
48 | 48 | append_to menu_items, '[Results]', 'user_admin', 'user_stat' |
|
49 | 49 | append_to menu_items, '[Report]', 'report', 'multiple_login' |
|
50 | 50 | append_to menu_items, '[Graders]', 'graders', 'list' |
|
51 | 51 | append_to menu_items, '[Contests]', 'contest_management', 'index' |
|
52 | 52 | append_to menu_items, '[Sites]', 'sites', 'index' |
|
53 | 53 | append_to menu_items, '[System config]', 'configurations', 'index' |
|
54 | 54 | menu_items << "<br/>" |
|
55 | 55 | end |
|
56 | 56 | |
|
57 | 57 | # main page |
|
58 | 58 | append_to menu_items, "[#{I18n.t 'menu.main'}]", 'main', 'list' |
|
59 | 59 | append_to menu_items, "[#{I18n.t 'menu.messages'}]", 'messages', 'list' |
|
60 | 60 | |
|
61 | 61 | if (user!=nil) and (GraderConfiguration.show_tasks_to?(user)) |
|
62 | 62 | append_to menu_items, "[#{I18n.t 'menu.tasks'}]", 'tasks', 'list' |
|
63 | 63 | append_to menu_items, "[#{I18n.t 'menu.submissions'}]", 'main', 'submission' |
|
64 | 64 | append_to menu_items, "[#{I18n.t 'menu.test'}]", 'test', 'index' |
|
65 | 65 | end |
|
66 | 66 | |
|
67 | 67 | if GraderConfiguration['right.user_hall_of_fame'] |
|
68 | 68 | append_to menu_items, "[#{I18n.t 'menu.hall_of_fame'}]", 'report', 'problem_hof' |
|
69 | 69 | end |
|
70 | 70 | append_to menu_items, "[#{I18n.t 'menu.help'}]", 'main', 'help' |
|
71 | 71 | |
|
72 | 72 | if GraderConfiguration['system.user_setting_enabled'] |
|
73 | 73 | append_to menu_items, "[#{I18n.t 'menu.settings'}]", 'users', 'index' |
|
74 | 74 | end |
|
75 | 75 | append_to menu_items, "[#{I18n.t 'menu.log_out'}]", 'main', 'login' |
|
76 | 76 | |
|
77 | 77 | menu_items.html_safe |
|
78 | 78 | end |
|
79 | 79 | |
|
80 | 80 | def append_to(option,label, controller, action) |
|
81 | 81 | option << ' ' if option!='' |
|
82 | 82 | option << link_to_unless_current(label, |
|
83 | 83 | :controller => controller, |
|
84 | 84 | :action => action) |
|
85 | 85 | end |
|
86 | 86 | |
|
87 | 87 | def format_short_time(time) |
|
88 | 88 | now = Time.zone.now |
|
89 | 89 | st = '' |
|
90 | 90 | if (time.yday != now.yday) or (time.year != now.year) |
|
91 | 91 | st = time.strftime("%d/%m/%y ") |
|
92 | 92 | end |
|
93 | 93 | st + time.strftime("%X") |
|
94 | 94 | end |
|
95 | 95 | |
|
96 | 96 | def format_short_duration(duration) |
|
97 | 97 | return '' if duration==nil |
|
98 | 98 | d = duration.to_f |
|
99 | 99 | return Time.at(d).gmtime.strftime("%X") |
|
100 | 100 | end |
|
101 | 101 | |
|
102 | 102 | def format_full_time_ago(time) |
|
103 | 103 | st = time_ago_in_words(time) + ' ago (' + format_short_time(time) + ')' |
|
104 | 104 | end |
|
105 | 105 | |
|
106 | 106 | def read_textfile(fname,max_size=2048) |
|
107 | 107 | begin |
|
108 | 108 | File.open(fname).read(max_size) |
|
109 | 109 | rescue |
|
110 | 110 | nil |
|
111 | 111 | end |
|
112 | 112 | end |
|
113 | 113 | |
|
114 | 114 | def toggle_button(on,toggle_url,id, option={}) |
|
115 | 115 | btn_size = option[:size] || 'btn-sm' |
|
116 | 116 | btn_block = option[:block] || 'btn-block' |
|
117 | 117 | link_to (on ? "Yes" : "No"), toggle_url, |
|
118 | 118 | {class: "btn #{btn_block} #{btn_size} btn-#{on ? 'success' : 'outline-secondary'} ajax-toggle", |
|
119 | 119 | id: id, |
|
120 | 120 | data: {remote: true, method: 'get'}} |
|
121 | 121 | end |
|
122 | 122 | |
|
123 | 123 | def get_ace_mode(language) |
|
124 | 124 | # return ace mode string from Language |
|
125 | 125 | |
|
126 | 126 | case language.pretty_name |
|
127 | 127 | when 'Pascal' |
|
128 | 128 | 'ace/mode/pascal' |
|
129 | 129 | when 'C++','C' |
|
130 | 130 | 'ace/mode/c_cpp' |
|
131 | 131 | when 'Ruby' |
|
132 | 132 | 'ace/mode/ruby' |
|
133 | 133 | when 'Python' |
|
134 | 134 | 'ace/mode/python' |
|
135 | 135 | when 'Java' |
|
136 | 136 | 'ace/mode/java' |
|
137 | 137 | else |
|
138 | 138 | 'ace/mode/c_cpp' |
|
139 | 139 | end |
|
140 | 140 | end |
|
141 | 141 | |
|
142 | 142 | |
|
143 | 143 | def user_title_bar(user) |
|
144 | 144 | header = '' |
|
145 | 145 | time_left = '' |
|
146 | 146 | |
|
147 | 147 | # |
|
148 | 148 | # if the contest is over |
|
149 | 149 | if GraderConfiguration.time_limit_mode? |
|
150 | 150 | if user.contest_finished? |
|
151 | 151 | header = <<CONTEST_OVER |
|
152 | 152 | <tr><td colspan="2" align="center"> |
|
153 | 153 | <span class="contest-over-msg">THE CONTEST IS OVER</span> |
|
154 | 154 | </td></tr> |
|
155 | 155 | CONTEST_OVER |
|
156 | 156 | end |
|
157 | 157 | if !user.contest_started? |
|
158 | 158 | time_left = " " + (t 'title_bar.contest_not_started') |
|
159 | 159 | else |
|
160 | 160 | time_left = " " + (t 'title_bar.remaining_time') + |
|
161 | 161 | " #{format_short_duration(user.contest_time_left)}" |
|
162 | 162 | end |
|
163 | 163 | end |
|
164 | 164 | |
|
165 | 165 | # |
|
166 | 166 | # if the contest is in the anaysis mode |
|
167 | 167 | if GraderConfiguration.analysis_mode? |
|
168 | 168 | header = <<ANALYSISMODE |
|
169 | 169 | <tr><td colspan="2" align="center"> |
|
170 | 170 | <span class="contest-over-msg">ANALYSIS MODE</span> |
|
171 | 171 | </td></tr> |
|
172 | 172 | ANALYSISMODE |
|
173 | 173 | end |
|
174 | 174 | |
|
175 | 175 | contest_name = GraderConfiguration['contest.name'] |
|
176 | 176 | |
|
177 | 177 | # |
|
178 | 178 | # build real title bar |
|
179 | 179 | result = <<TITLEBAR |
|
180 | 180 | <div class="title"> |
|
181 | 181 | <table> |
|
182 | 182 | #{header} |
|
183 | 183 | <tr> |
|
184 | 184 | <td class="left-col"> |
|
185 | 185 | <br/> |
|
186 | 186 | </td> |
|
187 | 187 | <td class="right-col">#{contest_name}</td> |
|
188 | 188 | </tr> |
|
189 | 189 | </table> |
|
190 | 190 | </div> |
|
191 | 191 | TITLEBAR |
|
192 | 192 | result.html_safe |
|
193 | 193 | end |
|
194 | 194 | |
|
195 | 195 | def markdown(text) |
|
196 | 196 | markdown = RDiscount.new(text) |
|
197 | 197 | markdown.to_html.html_safe |
|
198 | 198 | end |
|
199 | 199 | |
|
200 | 200 | |
|
201 | 201 | BOOTSTRAP_FLASH_MSG = { |
|
202 | 202 | success: 'alert-success', |
|
203 | 203 | error: 'alert-danger', |
|
204 | 204 | alert: 'alert-danger', |
|
205 | 205 | notice: 'alert-info' |
|
206 | 206 | } |
|
207 | 207 | |
|
208 | 208 | def bootstrap_class_for(flash_type) |
|
209 | 209 | BOOTSTRAP_FLASH_MSG.fetch(flash_type.to_sym, flash_type.to_s) |
|
210 | 210 | end |
|
211 | 211 | |
|
212 | + def active_class_when(options = {},cname = @active_controller, aname = @active_action) | |
|
213 | + class_name = ' active ' | |
|
214 | + ok = true | |
|
215 | + options.each do |k,v| | |
|
216 | + ok = false if k == :controller && v.to_s != cname | |
|
217 | + ok = false if k == :action && v.to_s != aname | |
|
212 | 218 | end |
|
219 | + return class_name if ok && options.size > 0 | |
|
220 | + return '' | |
|
221 | + end | |
|
222 | + | |
|
223 | + end |
@@ -1,168 +1,172 | |||
|
1 | 1 | class Problem < ApplicationRecord |
|
2 | 2 | |
|
3 | - belongs_to :description | |
|
3 | + #belongs_to :description | |
|
4 | + | |
|
4 | 5 | has_and_belongs_to_many :contests, :uniq => true |
|
5 | 6 | |
|
6 | 7 | #has_and_belongs_to_many :groups |
|
7 | 8 | has_many :groups_problems, class_name: 'GroupProblem' |
|
8 | 9 | has_many :groups, :through => :groups_problems |
|
9 | 10 | |
|
10 | 11 | has_many :problems_tags, class_name: 'ProblemTag' |
|
11 | 12 | has_many :tags, through: :problems_tags |
|
12 | 13 | |
|
13 | 14 | has_many :test_pairs, :dependent => :delete_all |
|
14 | 15 | has_many :testcases, :dependent => :destroy |
|
15 | 16 | |
|
16 | 17 | has_many :submissions |
|
17 | 18 | |
|
18 | 19 | validates_presence_of :name |
|
19 | 20 | validates_format_of :name, :with => /\A\w+\z/ |
|
20 | 21 | validates_presence_of :full_name |
|
21 | 22 | |
|
22 | 23 | scope :available, -> { where(available: true) } |
|
23 | 24 | |
|
24 | 25 | DEFAULT_TIME_LIMIT = 1 |
|
25 | 26 | DEFAULT_MEMORY_LIMIT = 32 |
|
26 | 27 | |
|
28 | + has_one_attached :statement | |
|
29 | + has_many_attached :attachments | |
|
30 | + | |
|
27 | 31 | def get_jschart_history |
|
28 | 32 | start = 4.month.ago.beginning_of_day |
|
29 | 33 | start_date = start.to_date |
|
30 | 34 | count = Submission.where(problem: self).where('submitted_at >= ?', start).group('DATE(submitted_at)').count |
|
31 | 35 | i = 0 |
|
32 | 36 | label = [] |
|
33 | 37 | value = [] |
|
34 | 38 | while (start_date + i < Time.zone.now.to_date) |
|
35 | 39 | if (start_date+i).day == 1 |
|
36 | 40 | #label << (start_date+i).strftime("%d %b %Y") |
|
37 | 41 | #label << (start_date+i).strftime("%d") |
|
38 | 42 | else |
|
39 | 43 | #label << ' ' |
|
40 | 44 | #label << (start_date+i).strftime("%d") |
|
41 | 45 | end |
|
42 | 46 | label << (start_date+i).strftime("%d-%b") |
|
43 | 47 | value << (count[start_date+i] || 0) |
|
44 | 48 | i+=1 |
|
45 | 49 | end |
|
46 | 50 | return {labels: label,datasets: [label:'sub',data: value, backgroundColor: 'rgba(54, 162, 235, 0.2)', borderColor: 'rgb(75, 192, 192)']} |
|
47 | 51 | end |
|
48 | 52 | |
|
49 | 53 | def self.available_problems |
|
50 | 54 | available.order(date_added: :desc).order(:name) |
|
51 | 55 | #Problem.available.all(:order => "date_added DESC, name ASC") |
|
52 | 56 | end |
|
53 | 57 | |
|
54 | 58 | def self.create_from_import_form_params(params, old_problem=nil) |
|
55 | 59 | org_problem = old_problem || Problem.new |
|
56 | 60 | import_params, problem = Problem.extract_params_and_check(params, |
|
57 | 61 | org_problem) |
|
58 | 62 | |
|
59 | 63 | if !problem.errors.empty? |
|
60 | 64 | return problem, 'Error importing' |
|
61 | 65 | end |
|
62 | 66 | |
|
63 | 67 | problem.full_score = 100 |
|
64 | 68 | problem.date_added = Time.new |
|
65 | 69 | problem.test_allowed = true |
|
66 | 70 | problem.output_only = false |
|
67 | 71 | problem.available = false |
|
68 | 72 | |
|
69 | 73 | if not problem.save |
|
70 | 74 | return problem, 'Error importing' |
|
71 | 75 | end |
|
72 | 76 | |
|
73 | 77 | import_to_db = params.has_key? :import_to_db |
|
74 | 78 | |
|
75 | 79 | importer = TestdataImporter.new(problem) |
|
76 | 80 | |
|
77 | 81 | if not importer.import_from_file(import_params[:file], |
|
78 | 82 | import_params[:time_limit], |
|
79 | 83 | import_params[:memory_limit], |
|
80 | 84 | import_params[:checker_name], |
|
81 | 85 | import_to_db) |
|
82 | 86 | problem.errors.add(:base,'Import error.') |
|
83 | 87 | end |
|
84 | 88 | |
|
85 | 89 | return problem, importer.log_msg |
|
86 | 90 | end |
|
87 | 91 | |
|
88 | 92 | def self.download_file_basedir |
|
89 | 93 | return "#{Rails.root}/data/tasks" |
|
90 | 94 | end |
|
91 | 95 | |
|
92 | 96 | def get_submission_stat |
|
93 | 97 | result = Hash.new |
|
94 | 98 | #total number of submission |
|
95 | 99 | result[:total_sub] = Submission.where(problem_id: self.id).count |
|
96 | 100 | result[:attempted_user] = Submission.where(problem_id: self.id).group(:user_id) |
|
97 | 101 | result[:pass] = Submission.where(problem_id: self.id).where("points >= ?",self.full_score).count |
|
98 | 102 | return result |
|
99 | 103 | end |
|
100 | 104 | |
|
101 | 105 | def long_name |
|
102 | 106 | "[#{name}] #{full_name}" |
|
103 | 107 | end |
|
104 | 108 | |
|
105 | 109 | protected |
|
106 | 110 | |
|
107 | 111 | def self.to_i_or_default(st, default) |
|
108 | 112 | if st!='' |
|
109 | 113 | result = st.to_i |
|
110 | 114 | end |
|
111 | 115 | result ||= default |
|
112 | 116 | end |
|
113 | 117 | |
|
114 | 118 | def self.to_f_or_default(st, default) |
|
115 | 119 | if st!='' |
|
116 | 120 | result = st.to_f |
|
117 | 121 | end |
|
118 | 122 | result ||= default |
|
119 | 123 | end |
|
120 | 124 | |
|
121 | 125 | def self.extract_params_and_check(params, problem) |
|
122 | 126 | time_limit = Problem.to_f_or_default(params[:time_limit], |
|
123 | 127 | DEFAULT_TIME_LIMIT) |
|
124 | 128 | memory_limit = Problem.to_i_or_default(params[:memory_limit], |
|
125 | 129 | DEFAULT_MEMORY_LIMIT) |
|
126 | 130 | |
|
127 | 131 | if time_limit<=0 or time_limit >60 |
|
128 | 132 | problem.errors.add(:base,'Time limit out of range.') |
|
129 | 133 | end |
|
130 | 134 | |
|
131 | 135 | if memory_limit==0 and params[:memory_limit]!='0' |
|
132 | 136 | problem.errors.add(:base,'Memory limit format errors.') |
|
133 | 137 | elsif memory_limit<=0 or memory_limit >512 |
|
134 | 138 | problem.errors.add(:base,'Memory limit out of range.') |
|
135 | 139 | end |
|
136 | 140 | |
|
137 | 141 | if params[:file]==nil or params[:file]=='' |
|
138 | 142 | problem.errors.add(:base,'No testdata file.') |
|
139 | 143 | end |
|
140 | 144 | |
|
141 | 145 | checker_name = 'text' |
|
142 | 146 | if ['text','float'].include? params[:checker] |
|
143 | 147 | checker_name = params[:checker] |
|
144 | 148 | end |
|
145 | 149 | |
|
146 | 150 | file = params[:file] |
|
147 | 151 | |
|
148 | 152 | if !problem.errors.empty? |
|
149 | 153 | return nil, problem |
|
150 | 154 | end |
|
151 | 155 | |
|
152 | 156 | problem.name = params[:name] |
|
153 | 157 | if params[:full_name]!='' |
|
154 | 158 | problem.full_name = params[:full_name] |
|
155 | 159 | else |
|
156 | 160 | problem.full_name = params[:name] |
|
157 | 161 | end |
|
158 | 162 | |
|
159 | 163 | return [{ |
|
160 | 164 | :time_limit => time_limit, |
|
161 | 165 | :memory_limit => memory_limit, |
|
162 | 166 | :file => file, |
|
163 | 167 | :checker_name => checker_name |
|
164 | 168 | }, |
|
165 | 169 | problem] |
|
166 | 170 | end |
|
167 | 171 | |
|
168 | 172 | end |
@@ -1,87 +1,93 | |||
|
1 | 1 | %header |
|
2 | 2 | %nav.navbar.fixed-top.navbar-dark.bg-primary.navbar-expand-lg |
|
3 | 3 | .container-fluid |
|
4 | 4 | %a.navbar-brand{href: list_main_path} |
|
5 | 5 | %span.mi.mi-bs home |
|
6 | 6 | MAIN |
|
7 | 7 | %button.navbar-toggler.collapsed{ type: :button, 'data-bs': {toggle: 'collapse', target: '#navbar-collapse'} } |
|
8 | 8 | %span.navbar-toggler-icon |
|
9 | 9 | .collapse.navbar-collapse#navbar-collapse |
|
10 | 10 | %ul.navbar-nav.me-auto.mb-2.mb-lg-0 |
|
11 | 11 | / submission |
|
12 | 12 | - if (@current_user!=nil) and (GraderConfiguration.show_tasks_to?(@current_user)) |
|
13 | 13 | %li.nav-item.dropdown.mx-2 |
|
14 | - %a.nav-link.dropdown-toggle{href: '#', 'data-bs': {toggle:'dropdown'}, aria: {expanded:"false"}, role: "button"} | |
|
14 | + %a.nav-link.dropdown-toggle.active-with-children{href: '#', 'data-bs': {toggle:'dropdown'}, aria: {expanded:"false"}, role: "button"} | |
|
15 | 15 | = "#{I18n.t 'menu.submissions'}" |
|
16 | 16 | %ul.dropdown-menu |
|
17 | - %li= link_to 'View', submissions_path, class:'dropdown-item' | |
|
17 | + %li= link_to 'View', submissions_path, class: 'dropdown-item '+active_class_when(controller: :submissions) | |
|
18 | 18 | %li= link_to 'Self Test', test_index_path, class:'dropdown-item' |
|
19 | 19 | / hall of fame |
|
20 | 20 | - if GraderConfiguration['right.user_hall_of_fame'] |
|
21 | - %li= link_to "#{I18n.t 'menu.hall_of_fame'}", problem_hof_report_path, class: 'nav-link mx-2' | |
|
21 | + %li= link_to "#{I18n.t 'menu.hall_of_fame'}", problem_hof_report_path, class: 'nav-link mx-2'+active_class_when(controller: :report, action: :problem_hof) | |
|
22 | 22 | / display MODE button (with countdown in contest mode) |
|
23 | 23 | - if GraderConfiguration.analysis_mode? |
|
24 | 24 | %div.btn.btn-success#countdown= "ANALYSIS MODE" |
|
25 | 25 | - elsif GraderConfiguration.time_limit_mode? |
|
26 | 26 | - if @current_user.contest_finished? |
|
27 | 27 | %div.btn.btn-danger#countdown= "Contest is over" |
|
28 | 28 | - elsif !@current_user.contest_started? |
|
29 | 29 | %div.btn.btn-primary#countdown= (t 'title_bar.contest_not_started') |
|
30 | 30 | - else |
|
31 | 31 | %div.btn.btn-primary#countdown asdf |
|
32 | 32 | :javascript |
|
33 | 33 | $("#countdown").countdown({until: "+#{@current_user.contest_time_left.to_i}s", layout: 'Time left: {hnn}:{mnn}:{snn}'}); |
|
34 | 34 | / admin section |
|
35 | 35 | - if (@current_user!=nil) and (session[:admin]) |
|
36 | 36 | / management |
|
37 | 37 | %li.nav-item.dropdown.mx-2 |
|
38 |
- %a.nav-link.dropdown-toggle{href: '#', 'data-bs': {toggle:'dropdown'}, |
|
|
38 | + %a.nav-link.dropdown-toggle.active-with-children{href: '#', 'data-bs': {toggle:'dropdown'}, aria: {haspopup:"true", expanded:"false"}, role: "button"} | |
|
39 | 39 | Manage |
|
40 | 40 | %ul.dropdown-menu |
|
41 | - %li= link_to 'Announcements', announcements_path, class: 'dropdown-item' | |
|
42 | - %li= link_to 'Problems', problems_path, class: 'dropdown-item' | |
|
43 | - %li= link_to 'Tags', tags_path, class: 'dropdown-item' | |
|
44 | - %li= link_to 'Users', user_admin_index_path, class: 'dropdown-item' | |
|
45 | - %li= link_to 'User Groups', groups_path, class: 'dropdown-item' | |
|
46 | - %li= link_to 'Graders', graders_list_path, class: 'dropdown-item' | |
|
47 | - %li= link_to 'Message ', console_messages_path, class: 'dropdown-item' | |
|
41 | + %li= link_to 'Announcements', announcements_path, class: 'dropdown-item'+active_class_when(controller: :announcements) | |
|
42 | + %li= link_to 'Problems', problems_path, class: 'dropdown-item'+active_class_when(controller: :problems) | |
|
43 | + %li= link_to 'Tags', tags_path, class: 'dropdown-item'+active_class_when(controller: :tags) | |
|
44 | + %li= link_to 'Users', user_admin_index_path, class: 'dropdown-item'+active_class_when(controller: :user_admin) | |
|
45 | + %li= link_to 'User Groups', groups_path, class: 'dropdown-item'+active_class_when(controller: :groups) | |
|
46 | + %li= link_to 'Graders', graders_list_path, class: 'dropdown-item'+active_class_when(controller: :graders) | |
|
47 | + %li= link_to 'Message ', console_messages_path, class: 'dropdown-item'+active_class_when(controller: :messages) | |
|
48 | 48 | %li |
|
49 | 49 | %hr.dropdown-divider |
|
50 | - %li= link_to 'System config', grader_configuration_index_path, class: 'dropdown-item' | |
|
50 | + %li= link_to 'System config', grader_configuration_index_path, class: 'dropdown-item'+active_class_when(controller: :grader_configuration) | |
|
51 | 51 | %li |
|
52 | 52 | %hr.dropdown-divider |
|
53 | - %li= link_to 'Sites', sites_path, class: 'dropdown-item' | |
|
54 | - %li= link_to 'Contests', contest_management_index_path, class: 'dropdown-item' | |
|
53 | + %li= link_to 'Sites', sites_path, class: 'dropdown-item'+active_class_when(controller: :sites) | |
|
54 | + %li= link_to 'Contests', contest_management_index_path, class: 'dropdown-item'+active_class_when(controller: :contest_management) | |
|
55 | 55 | -# |
|
56 | 56 | / report |
|
57 | 57 | %li.nav-item.dropdown.mx-2 |
|
58 | - %a.nav-link.dropdown-toggle{href: '#', 'data-bs': {toggle:'dropdown'}, aria: {haspopup:"true", expanded:"false"}, role: "button"} | |
|
58 | + %a.nav-link.dropdown-toggle.active-with-children{href: '#', 'data-bs': {toggle:'dropdown'}, aria: {haspopup:"true", expanded:"false"}, role: "button"} | |
|
59 | 59 | Report |
|
60 | 60 | %ul.dropdown-menu |
|
61 | - %li= link_to 'Current Score', current_score_report_path, class: 'dropdown-item' | |
|
62 | - %li= link_to 'Score Report', max_score_report_path, class: 'dropdown-item' | |
|
63 | - %li= link_to 'Submission Report', submission_report_path, class: 'dropdown-item' | |
|
64 | - %li= link_to 'Login Report', login_report_path, class: 'dropdown-item' | |
|
61 | + %li= link_to 'Current Score', current_score_report_path, class: 'dropdown-item'+active_class_when(controller: :report, action: :current_score) | |
|
62 | + %li= link_to 'Score Report', max_score_report_path, class: 'dropdown-item'+active_class_when(controller: :report, action: :max_score) | |
|
63 | + %li= link_to 'Submission Report', submission_report_path, class: 'dropdown-item'+active_class_when(controller: :report, action: :submission) | |
|
64 | + %li= link_to 'Login Report', login_report_path, class: 'dropdown-item'+active_class_when(controller: :report, action: :login) | |
|
65 | 65 | - if (ungraded = Submission.where('graded_at is null').where('submitted_at < ?', 1.minutes.ago).count) > 0 |
|
66 | 66 | =link_to "#{ungraded} backlogs!", |
|
67 | 67 | graders_list_path, |
|
68 | 68 | class: 'navbar-btn btn btn-default btn-warning', data: {toggle: 'tooltip'},title: 'Number of ungraded submission' |
|
69 | 69 | / announcement |
|
70 | 70 | - @nav_announcement.each do |ann| |
|
71 | 71 | %p.navbar-text |
|
72 | 72 | = ann.body.html_safe |
|
73 | 73 | %ul.navbar-nav |
|
74 | 74 | %li.nav-item |
|
75 | 75 | %a.nav-link{href: help_main_path} |
|
76 | 76 | %span.mi.mi-bs.md-18 help |
|
77 | 77 | %li.nav-item |
|
78 | 78 | %a.nav-link{href: messages_path} |
|
79 | 79 | %span.mi.mi-bs.md-18 chat |
|
80 | 80 | - if GraderConfiguration['system.user_setting_enabled'] |
|
81 | 81 | %li.nav-item |
|
82 | 82 | %a.nav-link{href: profile_users_path} |
|
83 | 83 | %span.mi.mi-bs.md-18 settings |
|
84 | 84 | %li.nav-item |
|
85 | 85 | %a.nav-link{href: login_main_path} |
|
86 | 86 | %span.mi.mi-bs.md-18 exit_to_app |
|
87 | 87 | = @current_user.full_name |
|
88 | + :javascript | |
|
89 | + $('.active-with-children').each( (index,obj) => { | |
|
90 | + if ($(obj).siblings('.dropdown-menu').has('.active').length > 0) { | |
|
91 | + $(obj).addClass('active') | |
|
92 | + } | |
|
93 | + } ) |
@@ -1,55 +1,78 | |||
|
1 | + = simple_form_for problem do |form| | |
|
2 | + .row | |
|
3 | + .col-md-6 | |
|
4 | + = form.input :name | |
|
5 | + = form.input :full_name | |
|
6 | + = form.input :full_score | |
|
7 | + = form.input :tag_ids, collection: Tag.all, class: 'select2' | |
|
8 | + = form.input :date_added | |
|
9 | + = form.input :available | |
|
10 | + = form.input :test_allowed | |
|
11 | + = form.input :output_only | |
|
12 | + = form.input :description, as: :text | |
|
13 | + = form.input :markdown | |
|
14 | + = form.input :url | |
|
15 | + = form.input :statement | |
|
16 | + %p | |
|
17 | + - if @problem.statement.attached? | |
|
18 | + %a{href: get_statement_problem_path(@problem)} [Download current Statement] | |
|
19 | + - else | |
|
20 | + no statement attached to this problem | |
|
21 | + = form.submit :submit, class: 'btn btn-primary' | |
|
22 | + -# | |
|
1 | 23 | = error_messages_for 'problem' |
|
24 | + | |
|
2 | 25 |
|
|
3 | 26 | .form-group |
|
4 | 27 | %label{:for => "problem_name"} Name |
|
5 | 28 | = text_field 'problem', 'name', class: 'form-control' |
|
6 | 29 | %small |
|
7 | 30 | 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 | 31 | .form-group |
|
9 | 32 | %label{:for => "problem_full_name"} Full name |
|
10 | 33 | = text_field 'problem', 'full_name', class: 'form-control' |
|
11 | 34 | .form-group |
|
12 | 35 | %label{:for => "problem_full_score"} Full score |
|
13 | 36 | = text_field 'problem', 'full_score', class: 'form-control' |
|
14 | 37 | .form-group |
|
15 | 38 | %label{:for => "problem_full_score"} Tags |
|
16 | 39 | = collection_select(:problem, :tag_ids, Tag.all, :id, :name, {}, {multiple: true, class: 'form-control select2'}) |
|
17 | 40 | .form-group |
|
18 | 41 | %label{:for => "problem_date_added"} Date added |
|
19 | 42 | = date_select 'problem', 'date_added', class: 'form-control' |
|
20 | 43 |
|
|
21 | 44 |
|
|
22 | 45 |
|
|
23 | 46 |
|
|
24 | 47 |
|
|
25 | 48 |
|
|
26 | 49 | .checkbox |
|
27 | 50 | %label{:for => "problem_available"} |
|
28 | 51 | = check_box :problem, :available |
|
29 | 52 | Available? |
|
30 | 53 | .checkbox |
|
31 | 54 | %label{:for => "problem_test_allowed"} |
|
32 | 55 | = check_box :problem, :test_allowed |
|
33 | 56 | Test allowed? |
|
34 | 57 | .checkbox |
|
35 | 58 | %label{:for => "problem_output_only"} |
|
36 | 59 | = check_box :problem, :output_only |
|
37 | 60 | Output only? |
|
38 | 61 |
|
|
39 | 62 | .form-group |
|
40 | 63 | %label{:for => "description_body"} Description |
|
41 | 64 | %br/ |
|
42 | 65 | = text_area :description, :body, :rows => 10, :cols => 80,class: 'form-control' |
|
43 | 66 | .form-group |
|
44 | 67 | %label{:for => "description_markdowned"} Markdowned? |
|
45 | 68 | = select "description", | |
|
46 | 69 | "markdowned", | |
|
47 | 70 | [['True',true],['False',false]], | |
|
48 | 71 | {:selected => (@description) ? @description.markdowned : false } | |
|
49 | 72 | .form-group |
|
50 | 73 | %label{:for => "problem_url"} URL |
|
51 | 74 | %br/ |
|
52 | 75 | = text_field 'problem', 'url',class: 'form-control' |
|
53 | 76 | %p |
|
54 | 77 | Task PDF #{file_field_tag 'file'} |
|
55 | 78 |
|
@@ -1,14 +1,6 | |||
|
1 | - .container-fluid | |
|
2 | - = form_for @problem,url:{action: 'update'},html: {multipart: true} do | |
|
3 | - .row | |
|
4 | - .col-md-6 | |
|
5 | - %h1 Editing problem | |
|
6 | - = render :partial => 'form' | |
|
7 | - .row | |
|
1 | + %h1 Editing Problem | |
|
2 | + | |
|
3 | + = render 'form', problem: @problem | |
|
4 | + .row.my-3 | |
|
8 | 5 |
|
|
9 | - = submit_tag 'Edit', class: 'btn btn-primary btn-block' | |
|
10 | - .col-md-4 | |
|
11 | - = link_to 'Show', {:action => 'show', :id => @problem}, class: 'btn btn-default btn-block' | |
|
12 | - .col-md-4 | |
|
13 | - = link_to 'Back', problems_path, class: 'btn btn-default btn-block' | |
|
14 | - .div{style: 'height: 5em'} | |
|
6 | + = link_to 'Back', problems_path, class: 'btn btn-secondary' |
@@ -1,65 +1,67 | |||
|
1 | 1 | - content_for :head do |
|
2 | 2 | = stylesheet_link_tag 'problems' |
|
3 | 3 | %h1 Problems |
|
4 | 4 | %p |
|
5 | 5 | = link_to 'Import problems', {:action => 'import'}, class: 'btn btn-success btn-sm' |
|
6 | 6 | = link_to 'New problem', new_problem_path, class: 'btn btn-success btn-sm' |
|
7 | 7 | = link_to 'Bulk Manage', { action: 'manage'}, class: 'btn btn-info btn-sm' |
|
8 | 8 | = link_to 'Turn off all problems', {:action => 'turn_all_off'}, class: 'btn btn-secondary btn-sm' |
|
9 | 9 | = link_to 'Turn on all problems', {:action => 'turn_all_on'}, class: 'btn btn-secondary btn-sm' |
|
10 | 10 | .submitbox |
|
11 | 11 | = form_tag action: 'quick_create', controller: 'problems' 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 | 24 | %th Tags |
|
25 | 25 | %th |
|
26 | 26 | Submit |
|
27 | 27 | %sup{class: 'text-primary',data: {toggle: 'tooltip'}, title: 'Admin can always submit to any problem' } [?] |
|
28 | 28 | %th Date added |
|
29 | 29 | %th.text-center |
|
30 | 30 | Avail? |
|
31 | 31 | %sup{class: 'text-primary',data: {toggle: 'tooltip'}, title: 'Let user submits to this problem?' } [?] |
|
32 | 32 | %th.text-center |
|
33 | 33 | View Data? |
|
34 | 34 | %sup{class: 'text-primary',data: {toggle: 'tooltip'}, title: 'Let user view the testcase of this problem?' } [?] |
|
35 | 35 | %th.text-center |
|
36 | 36 | Test? |
|
37 | 37 | %sup{class: 'text-primary',data: {toggle: 'tooltip'}, title: 'Let user uses test interface on this problem?' } [?] |
|
38 | 38 | - if GraderConfiguration.multicontests? |
|
39 | 39 | %th Contests |
|
40 | + %th.text-center | |
|
41 | + %th.text-center | |
|
42 | + %th.text-center | |
|
40 | 43 | - for problem in @problems |
|
41 | 44 | %tr{:class => "#{(problem.available) ? "bg-success bg-opacity-25" : "bg-opacity-25"}", :id => "prob-#{problem.id}", :name => "prob-#{problem.id}"} |
|
42 | 45 | - @problem=problem |
|
43 | 46 | %td= problem.name #in_place_editor_field :problem, :name, {}, :rows=>1 |
|
44 | 47 | %td |
|
45 | 48 | = problem.full_name #in_place_editor_field :problem, :full_name, {}, :rows=>1 |
|
46 | 49 | = link_to_description_if_any "[#{t 'main.problem_desc'}] <span class='glyphicon glyphicon-file'></span>".html_safe, problem |
|
47 | 50 | %td.text-right= problem.full_score #in_place_editor_field :problem, :full_score, {}, :rows=>1 |
|
48 | 51 | %td |
|
49 | 52 | - problem.tags.each do |t| |
|
50 | 53 | - #%button.btn.btn-default.btn-sm= t.name |
|
51 | 54 | %span.label.label-default= t.name |
|
52 | 55 | %td= link_to "Submit", direct_edit_problem_submissions_path(problem,@current_user.id), class: 'btn btn-sm btn-primary' |
|
53 | 56 | %td= problem.date_added |
|
54 | 57 | %td= toggle_button(@problem.available?, toggle_problem_path(@problem), "problem-avail-#{@problem.id}") |
|
55 | 58 | %td= toggle_button(@problem.view_testcase?, toggle_view_testcase_problem_path(@problem), "problem-view-testcase-#{@problem.id}") |
|
56 | 59 | %td= toggle_button(@problem.test_allowed?, toggle_test_problem_path(@problem), "problem-test-#{@problem.id}") |
|
57 | 60 | - if GraderConfiguration.multicontests? |
|
58 | 61 | %td |
|
59 | 62 | = problem.contests.collect { |c| c.name }.join(', ') |
|
60 | 63 | %td= link_to 'Stat', {:action => 'stat', :id => problem.id}, class: 'btn btn-info btn-sm btn-block' |
|
61 | - %td= link_to 'Show', {:action => 'show', :id => problem}, class: 'btn btn-info btn-sm btn-block' | |
|
62 | 64 | %td= link_to 'Edit', {:action => 'edit', :id => problem}, class: 'btn btn-info btn-sm btn-block' |
|
63 | 65 | %td= link_to 'Destroy', { :action => 'destroy', :id => problem }, :confirm => 'Are you sure?', :method => :delete, class: 'btn btn-danger btn-sm btn-block' |
|
64 | 66 | %br/ |
|
65 | 67 | = link_to '[New problem]', :action => 'new' |
@@ -1,176 +1,176 | |||
|
1 | 1 | # frozen_string_literal: true |
|
2 | 2 | # |
|
3 | 3 | # Uncomment this and change the path if necessary to include your own |
|
4 | 4 | # components. |
|
5 | 5 | # See https://github.com/heartcombo/simple_form#custom-components to know |
|
6 | 6 | # more about custom components. |
|
7 | 7 | # Dir[Rails.root.join('lib/components/**/*.rb')].each { |f| require f } |
|
8 | 8 | # |
|
9 | 9 | # Use this setup block to configure all options available in SimpleForm. |
|
10 | 10 | SimpleForm.setup do |config| |
|
11 | 11 | # Wrappers are used by the form builder to generate a |
|
12 | 12 | # complete input. You can remove any component from the |
|
13 | 13 | # wrapper, change the order or even add your own to the |
|
14 | 14 | # stack. The options given below are used to wrap the |
|
15 | 15 | # whole input. |
|
16 | 16 | config.wrappers :default, class: :input, |
|
17 | 17 | hint_class: :field_with_hint, error_class: :field_with_errors, valid_class: :field_without_errors do |b| |
|
18 | 18 | ## Extensions enabled by default |
|
19 | 19 | # Any of these extensions can be disabled for a |
|
20 | 20 | # given input by passing: `f.input EXTENSION_NAME => false`. |
|
21 | 21 | # You can make any of these extensions optional by |
|
22 | 22 | # renaming `b.use` to `b.optional`. |
|
23 | 23 | |
|
24 | 24 | # Determines whether to use HTML5 (:email, :url, ...) |
|
25 | 25 | # and required attributes |
|
26 | 26 | b.use :html5 |
|
27 | 27 | |
|
28 | 28 | # Calculates placeholders automatically from I18n |
|
29 | 29 | # You can also pass a string as f.input placeholder: "Placeholder" |
|
30 | 30 | b.use :placeholder |
|
31 | 31 | |
|
32 | 32 | ## Optional extensions |
|
33 | 33 | # They are disabled unless you pass `f.input EXTENSION_NAME => true` |
|
34 | 34 | # to the input. If so, they will retrieve the values from the model |
|
35 | 35 | # if any exists. If you want to enable any of those |
|
36 | 36 | # extensions by default, you can change `b.optional` to `b.use`. |
|
37 | 37 | |
|
38 | 38 | # Calculates maxlength from length validations for string inputs |
|
39 | 39 | # and/or database column lengths |
|
40 | 40 | b.optional :maxlength |
|
41 | 41 | |
|
42 | 42 | # Calculate minlength from length validations for string inputs |
|
43 | 43 | b.optional :minlength |
|
44 | 44 | |
|
45 | 45 | # Calculates pattern from format validations for string inputs |
|
46 | 46 | b.optional :pattern |
|
47 | 47 | |
|
48 | 48 | # Calculates min and max from length validations for numeric inputs |
|
49 | 49 | b.optional :min_max |
|
50 | 50 | |
|
51 | 51 | # Calculates readonly automatically from readonly attributes |
|
52 | 52 | b.optional :readonly |
|
53 | 53 | |
|
54 | 54 | ## Inputs |
|
55 | 55 | # b.use :input, class: 'input', error_class: 'is-invalid', valid_class: 'is-valid' |
|
56 | 56 | b.use :label_input |
|
57 | 57 | b.use :hint, wrap_with: { tag: :span, class: :hint } |
|
58 | 58 | b.use :error, wrap_with: { tag: :span, class: :error } |
|
59 | 59 | |
|
60 | 60 | ## full_messages_for |
|
61 | 61 | # If you want to display the full error message for the attribute, you can |
|
62 | 62 | # use the component :full_error, like: |
|
63 | 63 | # |
|
64 | 64 | # b.use :full_error, wrap_with: { tag: :span, class: :error } |
|
65 | 65 | end |
|
66 | 66 | |
|
67 | 67 | # The default wrapper to be used by the FormBuilder. |
|
68 | 68 | config.default_wrapper = :default |
|
69 | 69 | |
|
70 | 70 | # Define the way to render check boxes / radio buttons with labels. |
|
71 | 71 | # Defaults to :nested for bootstrap config. |
|
72 | 72 | # inline: input + label |
|
73 | 73 | # nested: label > input |
|
74 | 74 | config.boolean_style = :nested |
|
75 | 75 | |
|
76 | 76 | # Default class for buttons |
|
77 | 77 | config.button_class = 'btn' |
|
78 | 78 | |
|
79 | 79 | # Method used to tidy up errors. Specify any Rails Array method. |
|
80 | 80 | # :first lists the first message for each field. |
|
81 | 81 | # Use :to_sentence to list all errors for each field. |
|
82 | 82 | # config.error_method = :first |
|
83 | 83 | |
|
84 | 84 | # Default tag used for error notification helper. |
|
85 | 85 | config.error_notification_tag = :div |
|
86 | 86 | |
|
87 | 87 | # CSS class to add for error notification helper. |
|
88 | 88 | config.error_notification_class = 'error_notification' |
|
89 | 89 | |
|
90 | 90 | # Series of attempts to detect a default label method for collection. |
|
91 | 91 | # config.collection_label_methods = [ :to_label, :name, :title, :to_s ] |
|
92 | 92 | |
|
93 | 93 | # Series of attempts to detect a default value method for collection. |
|
94 | 94 | # config.collection_value_methods = [ :id, :to_s ] |
|
95 | 95 | |
|
96 | 96 | # You can wrap a collection of radio/check boxes in a pre-defined tag, defaulting to none. |
|
97 | 97 | # config.collection_wrapper_tag = nil |
|
98 | 98 | |
|
99 | 99 | # You can define the class to use on all collection wrappers. Defaulting to none. |
|
100 | 100 | # config.collection_wrapper_class = nil |
|
101 | 101 | |
|
102 | 102 | # You can wrap each item in a collection of radio/check boxes with a tag, |
|
103 | 103 | # defaulting to :span. |
|
104 | 104 | # config.item_wrapper_tag = :span |
|
105 | 105 | |
|
106 | 106 | # You can define a class to use in all item wrappers. Defaulting to none. |
|
107 | 107 | # config.item_wrapper_class = nil |
|
108 | 108 | |
|
109 | 109 | # How the label text should be generated altogether with the required text. |
|
110 | 110 | # config.label_text = lambda { |label, required, explicit_label| "#{required} #{label}" } |
|
111 | 111 | |
|
112 | 112 | # You can define the class to use on all labels. Default is nil. |
|
113 | 113 | # config.label_class = nil |
|
114 | 114 | |
|
115 | - # You can define the default class to be used on forms. Can be overriden | |
|
115 | + # You can define the default class to be used on forms. Can be overridden | |
|
116 | 116 | # with `html: { :class }`. Defaulting to none. |
|
117 | 117 | # config.default_form_class = nil |
|
118 | 118 | |
|
119 | 119 | # You can define which elements should obtain additional classes |
|
120 | 120 | # config.generate_additional_classes_for = [:wrapper, :label, :input] |
|
121 | 121 | |
|
122 | 122 | # Whether attributes are required by default (or not). Default is true. |
|
123 | 123 | # config.required_by_default = true |
|
124 | 124 | |
|
125 | 125 | # Tell browsers whether to use the native HTML5 validations (novalidate form option). |
|
126 | 126 | # These validations are enabled in SimpleForm's internal config but disabled by default |
|
127 | 127 | # in this configuration, which is recommended due to some quirks from different browsers. |
|
128 | 128 | # To stop SimpleForm from generating the novalidate option, enabling the HTML5 validations, |
|
129 | 129 | # change this configuration to true. |
|
130 | 130 | config.browser_validations = false |
|
131 | 131 | |
|
132 | 132 | # Custom mappings for input types. This should be a hash containing a regexp |
|
133 | 133 | # to match as key, and the input type that will be used when the field name |
|
134 | 134 | # matches the regexp as value. |
|
135 | 135 | # config.input_mappings = { /count/ => :integer } |
|
136 | 136 | |
|
137 | 137 | # Custom wrappers for input types. This should be a hash containing an input |
|
138 | 138 | # type as key and the wrapper that will be used for all inputs with specified type. |
|
139 | 139 | # config.wrapper_mappings = { string: :prepend } |
|
140 | 140 | |
|
141 | 141 | # Namespaces where SimpleForm should look for custom input classes that |
|
142 | 142 | # override default inputs. |
|
143 | 143 | # config.custom_inputs_namespaces << "CustomInputs" |
|
144 | 144 | |
|
145 | 145 | # Default priority for time_zone inputs. |
|
146 | 146 | # config.time_zone_priority = nil |
|
147 | 147 | |
|
148 | 148 | # Default priority for country inputs. |
|
149 | 149 | # config.country_priority = nil |
|
150 | 150 | |
|
151 | 151 | # When false, do not use translations for labels. |
|
152 | 152 | # config.translate_labels = true |
|
153 | 153 | |
|
154 | 154 | # Automatically discover new inputs in Rails' autoload path. |
|
155 | 155 | # config.inputs_discovery = true |
|
156 | 156 | |
|
157 | 157 | # Cache SimpleForm inputs discovery |
|
158 | 158 | # config.cache_discovery = !Rails.env.development? |
|
159 | 159 | |
|
160 | 160 | # Default class for inputs |
|
161 | 161 | # config.input_class = nil |
|
162 | 162 | |
|
163 | 163 | # Define the default class of the input wrapper of the boolean input. |
|
164 | 164 | config.boolean_label_class = 'checkbox' |
|
165 | 165 | |
|
166 | 166 | # Defines if the default input wrapper class should be included in radio |
|
167 | 167 | # collection wrappers. |
|
168 | 168 | # config.include_default_input_wrapper_class = true |
|
169 | 169 | |
|
170 | 170 | # Defines which i18n scope will be used in Simple Form. |
|
171 | 171 | # config.i18n_scope = 'simple_form' |
|
172 | 172 | |
|
173 | 173 | # Defines validation classes to the input_field. By default it's nil. |
|
174 | 174 | # config.input_field_valid_class = 'is-valid' |
|
175 | 175 | # config.input_field_error_class = 'is-invalid' |
|
176 | 176 | end |
@@ -1,440 +1,372 | |||
|
1 | 1 | # frozen_string_literal: true |
|
2 | 2 | |
|
3 | - # Please do not make direct changes to this file! | |
|
4 | - # This generator is maintained by the community around simple_form-bootstrap: | |
|
5 | - # https://github.com/rafaelfranca/simple_form-bootstrap | |
|
6 | - # All future development, tests, and organization should happen there. | |
|
7 | - # Background history: https://github.com/heartcombo/simple_form/issues/1561 | |
|
3 | + # These defaults are defined and maintained by the community at | |
|
4 | + # https://github.com/heartcombo/simple_form-bootstrap | |
|
5 | + # Please submit feedback, changes and tests only there. | |
|
8 | 6 | |
|
9 | 7 | # Uncomment this and change the path if necessary to include your own |
|
10 | 8 | # components. |
|
11 | 9 | # See https://github.com/heartcombo/simple_form#custom-components |
|
12 | 10 | # to know more about custom components. |
|
13 | 11 | # Dir[Rails.root.join('lib/components/**/*.rb')].each { |f| require f } |
|
14 | 12 | |
|
15 | 13 | # Use this setup block to configure all options available in SimpleForm. |
|
16 | 14 | SimpleForm.setup do |config| |
|
17 | 15 | # Default class for buttons |
|
18 | 16 | config.button_class = 'btn' |
|
19 | 17 | |
|
20 | 18 | # Define the default class of the input wrapper of the boolean input. |
|
21 | 19 | config.boolean_label_class = 'form-check-label' |
|
22 | 20 | |
|
23 | 21 | # How the label text should be generated altogether with the required text. |
|
24 | 22 | config.label_text = lambda { |label, required, explicit_label| "#{label} #{required}" } |
|
25 | 23 | |
|
26 | 24 | # Define the way to render check boxes / radio buttons with labels. |
|
27 | 25 | config.boolean_style = :inline |
|
28 | 26 | |
|
29 | 27 | # You can wrap each item in a collection of radio/check boxes with a tag |
|
30 | 28 | config.item_wrapper_tag = :div |
|
31 | 29 | |
|
32 | 30 | # Defines if the default input wrapper class should be included in radio |
|
33 | 31 | # collection wrappers. |
|
34 | 32 | config.include_default_input_wrapper_class = false |
|
35 | 33 | |
|
36 | 34 | # CSS class to add for error notification helper. |
|
37 | 35 | config.error_notification_class = 'alert alert-danger' |
|
38 | 36 | |
|
39 | 37 | # Method used to tidy up errors. Specify any Rails Array method. |
|
40 | 38 | # :first lists the first message for each field. |
|
41 | 39 | # :to_sentence to list all errors for each field. |
|
42 | 40 | config.error_method = :to_sentence |
|
43 | 41 | |
|
44 | 42 | # add validation classes to `input_field` |
|
45 | 43 | config.input_field_error_class = 'is-invalid' |
|
46 | - config.input_field_valid_class = 'is-valid' | |
|
44 | + config.input_field_valid_class = 'is-valid-xxx' | |
|
47 | 45 | |
|
48 | 46 | |
|
49 | 47 | # vertical forms |
|
50 | 48 | # |
|
51 | 49 | # vertical default_wrapper |
|
52 | - config.wrappers :vertical_form, tag: 'div', class: 'form-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b| | |
|
50 | + config.wrappers :vertical_form, class: 'mb-3' do |b| | |
|
53 | 51 | b.use :html5 |
|
54 | 52 | b.use :placeholder |
|
55 | 53 | b.optional :maxlength |
|
56 | 54 | b.optional :minlength |
|
57 | 55 | b.optional :pattern |
|
58 | 56 | b.optional :min_max |
|
59 | 57 | b.optional :readonly |
|
60 | - b.use :label | |
|
61 |
- b.use :input, class: 'form-control', error_class: 'is-invalid' |
|
|
62 |
- b.use :full_error, wrap_with: { |
|
|
63 |
- b.use :hint, wrap_with: { |
|
|
58 | + b.use :label, class: 'form-label' | |
|
59 | + b.use :input, class: 'form-control', error_class: 'is-invalid' | |
|
60 | + b.use :full_error, wrap_with: { class: 'invalid-feedback' } | |
|
61 | + b.use :hint, wrap_with: { class: 'form-text' } | |
|
64 | 62 | end |
|
65 | 63 | |
|
66 | 64 | # vertical input for boolean |
|
67 |
- config.wrappers :vertical_boolean, tag: 'fieldset', class: ' |
|
|
65 | + config.wrappers :vertical_boolean, tag: 'fieldset', class: 'mb-3' do |b| | |
|
68 | 66 | b.use :html5 |
|
69 | 67 | b.optional :readonly |
|
70 |
- b.wrapper :form_check_wrapper |
|
|
71 |
- bb.use :input, class: 'form-check-input', error_class: 'is-invalid' |
|
|
68 | + b.wrapper :form_check_wrapper, class: 'form-check' do |bb| | |
|
69 | + bb.use :input, class: 'form-check-input', error_class: 'is-invalid' | |
|
72 | 70 | bb.use :label, class: 'form-check-label' |
|
73 |
- bb.use :full_error, wrap_with: { |
|
|
74 |
- bb.use :hint, wrap_with: { |
|
|
71 | + bb.use :full_error, wrap_with: { class: 'invalid-feedback' } | |
|
72 | + bb.use :hint, wrap_with: { class: 'form-text' } | |
|
75 | 73 | end |
|
76 | 74 | end |
|
77 | 75 | |
|
78 | 76 | # vertical input for radio buttons and check boxes |
|
79 |
- config.wrappers :vertical_collection, item_wrapper_class: 'form-check', item_label_class: 'form-check-label', tag: 'fieldset', class: ' |
|
|
77 | + config.wrappers :vertical_collection, item_wrapper_class: 'form-check', item_label_class: 'form-check-label', tag: 'fieldset', class: 'mb-3' do |b| | |
|
80 | 78 | b.use :html5 |
|
81 | 79 | b.optional :readonly |
|
82 | 80 | b.wrapper :legend_tag, tag: 'legend', class: 'col-form-label pt-0' do |ba| |
|
83 | 81 | ba.use :label_text |
|
84 | 82 | end |
|
85 |
- b.use :input, class: 'form-check-input', error_class: 'is-invalid' |
|
|
86 |
- b.use :full_error, wrap_with: { |
|
|
87 |
- b.use :hint, wrap_with: { |
|
|
83 | + b.use :input, class: 'form-check-input', error_class: 'is-invalid' | |
|
84 | + b.use :full_error, wrap_with: { class: 'invalid-feedback d-block' } | |
|
85 | + b.use :hint, wrap_with: { class: 'form-text' } | |
|
88 | 86 | end |
|
89 | 87 | |
|
90 | 88 | # vertical input for inline radio buttons and check boxes |
|
91 |
- config.wrappers :vertical_collection_inline, item_wrapper_class: 'form-check form-check-inline', item_label_class: 'form-check-label', tag: 'fieldset', class: ' |
|
|
89 | + config.wrappers :vertical_collection_inline, item_wrapper_class: 'form-check form-check-inline', item_label_class: 'form-check-label', tag: 'fieldset', class: 'mb-3' do |b| | |
|
92 | 90 | b.use :html5 |
|
93 | 91 | b.optional :readonly |
|
94 | 92 | b.wrapper :legend_tag, tag: 'legend', class: 'col-form-label pt-0' do |ba| |
|
95 | 93 | ba.use :label_text |
|
96 | 94 | end |
|
97 |
- b.use :input, class: 'form-check-input', error_class: 'is-invalid' |
|
|
98 |
- b.use :full_error, wrap_with: { |
|
|
99 |
- b.use :hint, wrap_with: { |
|
|
95 | + b.use :input, class: 'form-check-input', error_class: 'is-invalid' | |
|
96 | + b.use :full_error, wrap_with: { class: 'invalid-feedback d-block' } | |
|
97 | + b.use :hint, wrap_with: { class: 'form-text' } | |
|
100 | 98 | end |
|
101 | 99 | |
|
102 | 100 | # vertical file input |
|
103 | - config.wrappers :vertical_file, tag: 'div', class: 'form-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b| | |
|
101 | + config.wrappers :vertical_file, class: 'mb-3' do |b| | |
|
104 | 102 | b.use :html5 |
|
105 | 103 | b.use :placeholder |
|
106 | 104 | b.optional :maxlength |
|
107 | 105 | b.optional :minlength |
|
108 | 106 | b.optional :readonly |
|
109 | - b.use :label | |
|
110 |
- b.use :input, class: 'form-control |
|
|
111 |
- b.use :full_error, wrap_with: { |
|
|
112 |
- b.use :hint, wrap_with: { |
|
|
107 | + b.use :label, class: 'form-label' | |
|
108 | + b.use :input, class: 'form-control', error_class: 'is-invalid' | |
|
109 | + b.use :full_error, wrap_with: { class: 'invalid-feedback' } | |
|
110 | + b.use :hint, wrap_with: { class: 'form-text' } | |
|
111 | + end | |
|
112 | + | |
|
113 | + # vertical select input | |
|
114 | + config.wrappers :vertical_select, class: 'mb-3' do |b| | |
|
115 | + b.use :html5 | |
|
116 | + b.optional :readonly | |
|
117 | + b.use :label, class: 'form-label' | |
|
118 | + b.use :input, class: 'form-select', error_class: 'is-invalid' | |
|
119 | + b.use :full_error, wrap_with: { class: 'invalid-feedback' } | |
|
120 | + b.use :hint, wrap_with: { class: 'form-text' } | |
|
113 | 121 | end |
|
114 | 122 | |
|
115 | 123 | # vertical multi select |
|
116 | - config.wrappers :vertical_multi_select, tag: 'div', class: 'form-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b| | |
|
124 | + config.wrappers :vertical_multi_select, class: 'mb-3' do |b| | |
|
117 | 125 | b.use :html5 |
|
118 | 126 | b.optional :readonly |
|
119 | - b.use :label | |
|
120 |
- b.wrapper |
|
|
121 |
- ba.use :input, class: 'form-c |
|
|
127 | + b.use :label, class: 'form-label' | |
|
128 | + b.wrapper class: 'd-flex flex-row justify-content-between align-items-center' do |ba| | |
|
129 | + ba.use :input, class: 'form-select mx-1', error_class: 'is-invalid' | |
|
122 | 130 | end |
|
123 |
- b.use :full_error, wrap_with: { |
|
|
124 |
- b.use :hint, wrap_with: { |
|
|
131 | + b.use :full_error, wrap_with: { class: 'invalid-feedback d-block' } | |
|
132 | + b.use :hint, wrap_with: { class: 'form-text' } | |
|
125 | 133 | end |
|
126 | 134 | |
|
127 | 135 | # vertical range input |
|
128 | - config.wrappers :vertical_range, tag: 'div', class: 'form-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b| | |
|
136 | + config.wrappers :vertical_range, class: 'mb-3' do |b| | |
|
129 | 137 | b.use :html5 |
|
130 | 138 | b.use :placeholder |
|
131 | 139 | b.optional :readonly |
|
132 | 140 | b.optional :step |
|
133 | - b.use :label | |
|
134 |
- b.use :input, class: 'form- |
|
|
135 |
- b.use :full_error, wrap_with: { |
|
|
136 |
- b.use :hint, wrap_with: { |
|
|
141 | + b.use :label, class: 'form-label' | |
|
142 | + b.use :input, class: 'form-range', error_class: 'is-invalid' | |
|
143 | + b.use :full_error, wrap_with: { class: 'invalid-feedback' } | |
|
144 | + b.use :hint, wrap_with: { class: 'form-text' } | |
|
137 | 145 | end |
|
138 | 146 | |
|
139 | 147 | |
|
140 | 148 | # horizontal forms |
|
141 | 149 | # |
|
142 | 150 | # horizontal default_wrapper |
|
143 | - config.wrappers :horizontal_form, tag: 'div', class: 'form-group row', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b| | |
|
151 | + config.wrappers :horizontal_form, class: 'row mb-3' do |b| | |
|
144 | 152 | b.use :html5 |
|
145 | 153 | b.use :placeholder |
|
146 | 154 | b.optional :maxlength |
|
147 | 155 | b.optional :minlength |
|
148 | 156 | b.optional :pattern |
|
149 | 157 | b.optional :min_max |
|
150 | 158 | b.optional :readonly |
|
151 | 159 | b.use :label, class: 'col-sm-3 col-form-label' |
|
152 |
- b.wrapper :grid_wrapper |
|
|
153 |
- ba.use :input, class: 'form-control', error_class: 'is-invalid' |
|
|
154 |
- ba.use :full_error, wrap_with: { |
|
|
155 |
- ba.use :hint, wrap_with: { |
|
|
160 | + b.wrapper :grid_wrapper, class: 'col-sm-9' do |ba| | |
|
161 | + ba.use :input, class: 'form-control', error_class: 'is-invalid' | |
|
162 | + ba.use :full_error, wrap_with: { class: 'invalid-feedback' } | |
|
163 | + ba.use :hint, wrap_with: { class: 'form-text' } | |
|
156 | 164 | end |
|
157 | 165 | end |
|
158 | 166 | |
|
159 | 167 | # horizontal input for boolean |
|
160 | - config.wrappers :horizontal_boolean, tag: 'div', class: 'form-group row', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b| | |
|
168 | + config.wrappers :horizontal_boolean, class: 'row mb-3' do |b| | |
|
161 | 169 | b.use :html5 |
|
162 | 170 | b.optional :readonly |
|
163 |
- b.wrapper |
|
|
164 | - ba.use :label_text | |
|
165 | - end | |
|
166 | - b.wrapper :grid_wrapper, tag: 'div', class: 'col-sm-9' do |wr| | |
|
167 | - wr.wrapper :form_check_wrapper, tag: 'div', class: 'form-check' do |bb| | |
|
168 | - bb.use :input, class: 'form-check-input', error_class: 'is-invalid', valid_class: 'is-valid' | |
|
171 | + b.wrapper :grid_wrapper, class: 'col-sm-9 offset-sm-3' do |wr| | |
|
172 | + wr.wrapper :form_check_wrapper, class: 'form-check' do |bb| | |
|
173 | + bb.use :input, class: 'form-check-input', error_class: 'is-invalid' | |
|
169 | 174 | bb.use :label, class: 'form-check-label' |
|
170 |
- bb.use :full_error, wrap_with: { |
|
|
171 |
- bb.use :hint, wrap_with: { |
|
|
175 | + bb.use :full_error, wrap_with: { class: 'invalid-feedback' } | |
|
176 | + bb.use :hint, wrap_with: { class: 'form-text' } | |
|
172 | 177 | end |
|
173 | 178 | end |
|
174 | 179 | end |
|
175 | 180 | |
|
176 | 181 | # horizontal input for radio buttons and check boxes |
|
177 |
- config.wrappers :horizontal_collection, item_wrapper_class: 'form-check', item_label_class: 'form-check-label', |
|
|
182 | + config.wrappers :horizontal_collection, item_wrapper_class: 'form-check', item_label_class: 'form-check-label', class: 'row mb-3' do |b| | |
|
178 | 183 | b.use :html5 |
|
179 | 184 | b.optional :readonly |
|
180 | 185 | b.use :label, class: 'col-sm-3 col-form-label pt-0' |
|
181 |
- b.wrapper :grid_wrapper |
|
|
182 |
- ba.use :input, class: 'form-check-input', error_class: 'is-invalid' |
|
|
183 |
- ba.use :full_error, wrap_with: { |
|
|
184 |
- ba.use :hint, wrap_with: { |
|
|
186 | + b.wrapper :grid_wrapper, class: 'col-sm-9' do |ba| | |
|
187 | + ba.use :input, class: 'form-check-input', error_class: 'is-invalid' | |
|
188 | + ba.use :full_error, wrap_with: { class: 'invalid-feedback d-block' } | |
|
189 | + ba.use :hint, wrap_with: { class: 'form-text' } | |
|
185 | 190 | end |
|
186 | 191 | end |
|
187 | 192 | |
|
188 | 193 | # horizontal input for inline radio buttons and check boxes |
|
189 |
- config.wrappers :horizontal_collection_inline, item_wrapper_class: 'form-check form-check-inline', item_label_class: 'form-check-label', |
|
|
194 | + config.wrappers :horizontal_collection_inline, item_wrapper_class: 'form-check form-check-inline', item_label_class: 'form-check-label', class: 'row mb-3' do |b| | |
|
190 | 195 | b.use :html5 |
|
191 | 196 | b.optional :readonly |
|
192 | 197 | b.use :label, class: 'col-sm-3 col-form-label pt-0' |
|
193 |
- b.wrapper :grid_wrapper |
|
|
194 |
- ba.use :input, class: 'form-check-input', error_class: 'is-invalid' |
|
|
195 |
- ba.use :full_error, wrap_with: { |
|
|
196 |
- ba.use :hint, wrap_with: { |
|
|
198 | + b.wrapper :grid_wrapper, class: 'col-sm-9' do |ba| | |
|
199 | + ba.use :input, class: 'form-check-input', error_class: 'is-invalid' | |
|
200 | + ba.use :full_error, wrap_with: { class: 'invalid-feedback d-block' } | |
|
201 | + ba.use :hint, wrap_with: { class: 'form-text' } | |
|
197 | 202 | end |
|
198 | 203 | end |
|
199 | 204 | |
|
200 | 205 | # horizontal file input |
|
201 | - config.wrappers :horizontal_file, tag: 'div', class: 'form-group row', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b| | |
|
206 | + config.wrappers :horizontal_file, class: 'row mb-3' do |b| | |
|
202 | 207 | b.use :html5 |
|
203 | 208 | b.use :placeholder |
|
204 | 209 | b.optional :maxlength |
|
205 | 210 | b.optional :minlength |
|
206 | 211 | b.optional :readonly |
|
207 | 212 | b.use :label, class: 'col-sm-3 col-form-label' |
|
208 |
- b.wrapper :grid_wrapper |
|
|
209 |
- ba.use :input, error_class: 'is-invalid' |
|
|
210 |
- ba.use :full_error, wrap_with: { |
|
|
211 |
- ba.use :hint, wrap_with: { |
|
|
213 | + b.wrapper :grid_wrapper, class: 'col-sm-9' do |ba| | |
|
214 | + ba.use :input, class: 'form-control', error_class: 'is-invalid' | |
|
215 | + ba.use :full_error, wrap_with: { class: 'invalid-feedback' } | |
|
216 | + ba.use :hint, wrap_with: { class: 'form-text' } | |
|
217 | + end | |
|
218 | + end | |
|
219 | + | |
|
220 | + # horizontal select input | |
|
221 | + config.wrappers :horizontal_select, class: 'row mb-3' do |b| | |
|
222 | + b.use :html5 | |
|
223 | + b.optional :readonly | |
|
224 | + b.use :label, class: 'col-sm-3 col-form-label' | |
|
225 | + b.wrapper :grid_wrapper, class: 'col-sm-9' do |ba| | |
|
226 | + ba.use :input, class: 'form-select', error_class: 'is-invalid' | |
|
227 | + ba.use :full_error, wrap_with: { class: 'invalid-feedback' } | |
|
228 | + ba.use :hint, wrap_with: { class: 'form-text' } | |
|
212 | 229 | end |
|
213 | 230 | end |
|
214 | 231 | |
|
215 | 232 | # horizontal multi select |
|
216 | - config.wrappers :horizontal_multi_select, tag: 'div', class: 'form-group row', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b| | |
|
233 | + config.wrappers :horizontal_multi_select, class: 'row mb-3' do |b| | |
|
217 | 234 | b.use :html5 |
|
218 | 235 | b.optional :readonly |
|
219 | 236 | b.use :label, class: 'col-sm-3 col-form-label' |
|
220 |
- b.wrapper :grid_wrapper |
|
|
221 |
- ba.wrapper |
|
|
222 |
- bb.use :input, class: 'form-c |
|
|
237 | + b.wrapper :grid_wrapper, class: 'col-sm-9' do |ba| | |
|
238 | + ba.wrapper class: 'd-flex flex-row justify-content-between align-items-center' do |bb| | |
|
239 | + bb.use :input, class: 'form-select mx-1', error_class: 'is-invalid' | |
|
223 | 240 | end |
|
224 |
- ba.use :full_error, wrap_with: { |
|
|
225 |
- ba.use :hint, wrap_with: { |
|
|
241 | + ba.use :full_error, wrap_with: { class: 'invalid-feedback d-block' } | |
|
242 | + ba.use :hint, wrap_with: { class: 'form-text' } | |
|
226 | 243 | end |
|
227 | 244 | end |
|
228 | 245 | |
|
229 | 246 | # horizontal range input |
|
230 | - config.wrappers :horizontal_range, tag: 'div', class: 'form-group row', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b| | |
|
247 | + config.wrappers :horizontal_range, class: 'row mb-3' do |b| | |
|
231 | 248 | b.use :html5 |
|
232 | 249 | b.use :placeholder |
|
233 | 250 | b.optional :readonly |
|
234 | 251 | b.optional :step |
|
235 | - b.use :label, class: 'col-sm-3 col-form-label' | |
|
236 |
- b.wrapper :grid_wrapper |
|
|
237 |
- ba.use :input, class: 'form- |
|
|
238 |
- ba.use :full_error, wrap_with: { |
|
|
239 |
- ba.use :hint, wrap_with: { |
|
|
252 | + b.use :label, class: 'col-sm-3 col-form-label pt-0' | |
|
253 | + b.wrapper :grid_wrapper, class: 'col-sm-9' do |ba| | |
|
254 | + ba.use :input, class: 'form-range', error_class: 'is-invalid' | |
|
255 | + ba.use :full_error, wrap_with: { class: 'invalid-feedback' } | |
|
256 | + ba.use :hint, wrap_with: { class: 'form-text' } | |
|
240 | 257 | end |
|
241 | 258 | end |
|
242 | 259 | |
|
243 | 260 | |
|
244 | 261 | # inline forms |
|
245 | 262 | # |
|
246 | 263 | # inline default_wrapper |
|
247 | - config.wrappers :inline_form, tag: 'span', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b| | |
|
264 | + config.wrappers :inline_form, class: 'col-12' do |b| | |
|
248 | 265 | b.use :html5 |
|
249 | 266 | b.use :placeholder |
|
250 | 267 | b.optional :maxlength |
|
251 | 268 | b.optional :minlength |
|
252 | 269 | b.optional :pattern |
|
253 | 270 | b.optional :min_max |
|
254 | 271 | b.optional :readonly |
|
255 |
- b.use :label, class: ' |
|
|
272 | + b.use :label, class: 'visually-hidden' | |
|
256 | 273 | |
|
257 |
- b.use :input, class: 'form-control', error_class: 'is-invalid' |
|
|
258 |
- b.use :error, wrap_with: { |
|
|
259 |
- b.optional :hint, wrap_with: { |
|
|
274 | + b.use :input, class: 'form-control', error_class: 'is-invalid' | |
|
275 | + b.use :error, wrap_with: { class: 'invalid-feedback' } | |
|
276 | + b.optional :hint, wrap_with: { class: 'form-text' } | |
|
260 | 277 | end |
|
261 | 278 | |
|
262 | 279 | # inline input for boolean |
|
263 | - config.wrappers :inline_boolean, tag: 'span', class: 'form-check mb-2 mr-sm-2', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b| | |
|
280 | + config.wrappers :inline_boolean, class: 'col-12' do |b| | |
|
264 | 281 | b.use :html5 |
|
265 | 282 | b.optional :readonly |
|
266 | - b.use :input, class: 'form-check-input', error_class: 'is-invalid', valid_class: 'is-valid' | |
|
267 |
- b.use : |
|
|
268 | - b.use :error, wrap_with: { tag: 'div', class: 'invalid-feedback' } | |
|
269 | - b.optional :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' } | |
|
283 | + b.wrapper :form_check_wrapper, class: 'form-check' do |bb| | |
|
284 | + bb.use :input, class: 'form-check-input', error_class: 'is-invalid' | |
|
285 | + bb.use :label, class: 'form-check-label' | |
|
286 | + bb.use :error, wrap_with: { class: 'invalid-feedback' } | |
|
287 | + bb.optional :hint, wrap_with: { class: 'form-text' } | |
|
288 | + end | |
|
270 | 289 | end |
|
271 | 290 | |
|
272 | 291 | |
|
273 | 292 | # bootstrap custom forms |
|
274 | 293 | # |
|
275 | - # custom input for boolean | |
|
276 | - config.wrappers :custom_boolean, tag: 'fieldset', class: 'form-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b| | |
|
277 | - b.use :html5 | |
|
278 | - b.optional :readonly | |
|
279 | - b.wrapper :form_check_wrapper, tag: 'div', class: 'custom-control custom-checkbox' do |bb| | |
|
280 | - bb.use :input, class: 'custom-control-input', error_class: 'is-invalid', valid_class: 'is-valid' | |
|
281 | - bb.use :label, class: 'custom-control-label' | |
|
282 | - bb.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback' } | |
|
283 | - bb.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' } | |
|
284 | - end | |
|
285 | - end | |
|
286 | - | |
|
287 | 294 | # custom input switch for boolean |
|
288 | - config.wrappers :custom_boolean_switch, tag: 'fieldset', class: 'form-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b| | |
|
289 | - b.use :html5 | |
|
290 | - b.optional :readonly | |
|
291 | - b.wrapper :form_check_wrapper, tag: 'div', class: 'custom-control custom-switch' do |bb| | |
|
292 | - bb.use :input, class: 'custom-control-input', error_class: 'is-invalid', valid_class: 'is-valid' | |
|
293 | - bb.use :label, class: 'custom-control-label' | |
|
294 | - bb.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback' } | |
|
295 | - bb.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' } | |
|
296 | - end | |
|
297 | - end | |
|
298 | - | |
|
299 | - # custom input for radio buttons and check boxes | |
|
300 | - config.wrappers :custom_collection, item_wrapper_class: 'custom-control', item_label_class: 'custom-control-label', tag: 'fieldset', class: 'form-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b| | |
|
301 | - b.use :html5 | |
|
302 | - b.optional :readonly | |
|
303 | - b.wrapper :legend_tag, tag: 'legend', class: 'col-form-label pt-0' do |ba| | |
|
304 | - ba.use :label_text | |
|
305 | - end | |
|
306 | - b.use :input, class: 'custom-control-input', error_class: 'is-invalid', valid_class: 'is-valid' | |
|
307 | - b.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback d-block' } | |
|
308 | - b.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' } | |
|
309 | - end | |
|
310 | - | |
|
311 | - # custom input for inline radio buttons and check boxes | |
|
312 | - config.wrappers :custom_collection_inline, item_wrapper_class: 'custom-control custom-control-inline', item_label_class: 'custom-control-label', tag: 'fieldset', class: 'form-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b| | |
|
295 | + config.wrappers :custom_boolean_switch, class: 'mb-3' do |b| | |
|
313 | 296 | b.use :html5 |
|
314 | 297 | b.optional :readonly |
|
315 |
- b.wrapper : |
|
|
316 | - ba.use :label_text | |
|
317 | - end | |
|
318 | - b.use :input, class: 'custom-control-input', error_class: 'is-invalid', valid_class: 'is-valid' | |
|
319 | - b.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback d-block' } | |
|
320 | - b.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' } | |
|
321 | - end | |
|
322 | - | |
|
323 | - # custom file input | |
|
324 | - config.wrappers :custom_file, tag: 'div', class: 'form-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b| | |
|
325 | - b.use :html5 | |
|
326 | - b.use :placeholder | |
|
327 | - b.optional :maxlength | |
|
328 | - b.optional :minlength | |
|
329 | - b.optional :readonly | |
|
330 | - b.use :label | |
|
331 | - b.wrapper :custom_file_wrapper, tag: 'div', class: 'custom-file' do |ba| | |
|
332 | - ba.use :input, class: 'custom-file-input', error_class: 'is-invalid', valid_class: 'is-valid' | |
|
333 | - ba.use :label, class: 'custom-file-label' | |
|
334 | - ba.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback' } | |
|
335 | - end | |
|
336 | - b.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' } | |
|
298 | + b.wrapper :form_check_wrapper, tag: 'div', class: 'form-check form-switch' do |bb| | |
|
299 | + bb.use :input, class: 'form-check-input', error_class: 'is-invalid' | |
|
300 | + bb.use :label, class: 'form-check-label' | |
|
301 | + bb.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback' } | |
|
302 | + bb.use :hint, wrap_with: { class: 'form-text' } | |
|
337 | 303 | end |
|
338 | - | |
|
339 | - # custom multi select | |
|
340 | - config.wrappers :custom_multi_select, tag: 'div', class: 'form-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b| | |
|
341 | - b.use :html5 | |
|
342 | - b.optional :readonly | |
|
343 | - b.use :label | |
|
344 | - b.wrapper tag: 'div', class: 'd-flex flex-row justify-content-between align-items-center' do |ba| | |
|
345 | - ba.use :input, class: 'custom-select mx-1', error_class: 'is-invalid', valid_class: 'is-valid' | |
|
346 | - end | |
|
347 | - b.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback d-block' } | |
|
348 | - b.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' } | |
|
349 | - end | |
|
350 | - | |
|
351 | - # custom range input | |
|
352 | - config.wrappers :custom_range, tag: 'div', class: 'form-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b| | |
|
353 | - b.use :html5 | |
|
354 | - b.use :placeholder | |
|
355 | - b.optional :readonly | |
|
356 | - b.optional :step | |
|
357 | - b.use :label | |
|
358 | - b.use :input, class: 'custom-range', error_class: 'is-invalid', valid_class: 'is-valid' | |
|
359 | - b.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback d-block' } | |
|
360 | - b.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' } | |
|
361 | 304 | end |
|
362 | 305 | |
|
363 | 306 | |
|
364 | 307 | # Input Group - custom component |
|
365 |
- # see example app and config at https://github.com/ |
|
|
366 | - # config.wrappers :input_group, tag: 'div', class: 'form-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b| | |
|
367 | - # b.use :html5 | |
|
368 | - # b.use :placeholder | |
|
369 | - # b.optional :maxlength | |
|
370 | - # b.optional :minlength | |
|
371 | - # b.optional :pattern | |
|
372 | - # b.optional :min_max | |
|
373 | - # b.optional :readonly | |
|
374 | - # b.use :label | |
|
375 | - # b.wrapper :input_group_tag, tag: 'div', class: 'input-group' do |ba| | |
|
376 | - # ba.optional :prepend | |
|
377 | - # ba.use :input, class: 'form-control', error_class: 'is-invalid', valid_class: 'is-valid' | |
|
378 | - # ba.optional :append | |
|
379 | - # end | |
|
380 | - # b.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback d-block' } | |
|
381 | - # b.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' } | |
|
382 | - # end | |
|
383 | - | |
|
384 | - | |
|
385 | - # Floating Labels form | |
|
386 | - # | |
|
387 | - # floating labels default_wrapper | |
|
388 | - config.wrappers :floating_labels_form, tag: 'div', class: 'form-label-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b| | |
|
308 | + # see example app and config at https://github.com/heartcombo/simple_form-bootstrap | |
|
309 | + config.wrappers :input_group, class: 'mb-3' do |b| | |
|
389 | 310 | b.use :html5 |
|
390 | 311 | b.use :placeholder |
|
391 | 312 | b.optional :maxlength |
|
392 | 313 | b.optional :minlength |
|
393 | 314 | b.optional :pattern |
|
394 | 315 | b.optional :min_max |
|
395 | 316 | b.optional :readonly |
|
396 | - b.use :input, class: 'form-control', error_class: 'is-invalid', valid_class: 'is-valid' | |
|
317 | + b.use :label, class: 'form-label' | |
|
318 | + b.wrapper :input_group_tag, class: 'input-group' do |ba| | |
|
319 | + ba.optional :prepend | |
|
320 | + ba.use :input, class: 'form-control', error_class: 'is-invalid' | |
|
321 | + ba.optional :append | |
|
322 | + ba.use :full_error, wrap_with: { class: 'invalid-feedback' } | |
|
323 | + end | |
|
324 | + b.use :hint, wrap_with: { class: 'form-text' } | |
|
325 | + end | |
|
326 | + | |
|
327 | + | |
|
328 | + # Floating Labels form | |
|
329 | + # | |
|
330 | + # floating labels default_wrapper | |
|
331 | + config.wrappers :floating_labels_form, class: 'form-floating mb-3' do |b| | |
|
332 | + b.use :html5 | |
|
333 | + b.use :placeholder | |
|
334 | + b.optional :maxlength | |
|
335 | + b.optional :minlength | |
|
336 | + b.optional :pattern | |
|
337 | + b.optional :min_max | |
|
338 | + b.optional :readonly | |
|
339 | + b.use :input, class: 'form-control', error_class: 'is-invalid' | |
|
397 | 340 | b.use :label |
|
398 |
- b.use :full_error, wrap_with: { |
|
|
399 |
- b.use :hint, wrap_with: { |
|
|
341 | + b.use :full_error, wrap_with: { class: 'invalid-feedback' } | |
|
342 | + b.use :hint, wrap_with: { class: 'form-text' } | |
|
400 | 343 | end |
|
401 | 344 | |
|
402 | 345 | # custom multi select |
|
403 |
- config.wrappers :floating_labels_select, |
|
|
346 | + config.wrappers :floating_labels_select, class: 'form-floating mb-3' do |b| | |
|
404 | 347 | b.use :html5 |
|
405 | 348 | b.optional :readonly |
|
406 |
- b.use :input, class: ' |
|
|
349 | + b.use :input, class: 'form-select', error_class: 'is-invalid' | |
|
407 | 350 | b.use :label |
|
408 |
- b.use :full_error, wrap_with: { |
|
|
409 |
- b.use :hint, wrap_with: { |
|
|
351 | + b.use :full_error, wrap_with: { class: 'invalid-feedback' } | |
|
352 | + b.use :hint, wrap_with: { class: 'form-text' } | |
|
410 | 353 | end |
|
411 | 354 | |
|
412 | 355 | |
|
413 | 356 | # The default wrapper to be used by the FormBuilder. |
|
414 | 357 | config.default_wrapper = :vertical_form |
|
415 | 358 | |
|
416 | 359 | # Custom wrappers for input types. This should be a hash containing an input |
|
417 | 360 | # type as key and the wrapper that will be used for all inputs with specified type. |
|
418 | 361 | config.wrapper_mappings = { |
|
419 | 362 | boolean: :vertical_boolean, |
|
420 | 363 | check_boxes: :vertical_collection, |
|
421 | 364 | date: :vertical_multi_select, |
|
422 | 365 | datetime: :vertical_multi_select, |
|
423 | 366 | file: :vertical_file, |
|
424 | 367 | radio_buttons: :vertical_collection, |
|
425 | 368 | range: :vertical_range, |
|
426 | - time: :vertical_multi_select | |
|
369 | + time: :vertical_multi_select, | |
|
370 | + select: :vertical_select | |
|
427 | 371 | } |
|
428 | - | |
|
429 | - # enable custom form wrappers | |
|
430 | - # config.wrapper_mappings = { | |
|
431 | - # boolean: :custom_boolean, | |
|
432 | - # check_boxes: :custom_collection, | |
|
433 | - # date: :custom_multi_select, | |
|
434 | - # datetime: :custom_multi_select, | |
|
435 | - # file: :custom_file, | |
|
436 | - # radio_buttons: :custom_collection, | |
|
437 | - # range: :custom_range, | |
|
438 | - # time: :custom_multi_select | |
|
439 | - # } | |
|
440 | 372 | end |
@@ -1,213 +1,214 | |||
|
1 | 1 | Rails.application.routes.draw do |
|
2 | 2 | resources :tags |
|
3 | 3 | get "sources/direct_edit" |
|
4 | 4 | |
|
5 | 5 | root :to => 'main#login' |
|
6 | 6 | |
|
7 | 7 | #logins |
|
8 | 8 | match 'login/login', to: 'login#login', via: [:get,:post] |
|
9 | 9 | |
|
10 | 10 | resources :contests |
|
11 | 11 | resources :sites |
|
12 | 12 | resources :test |
|
13 | 13 | |
|
14 | 14 | resources :messages do |
|
15 | 15 | member do |
|
16 | 16 | get 'hide' |
|
17 | 17 | post 'reply' |
|
18 | 18 | end |
|
19 | 19 | collection do |
|
20 | 20 | get 'console' |
|
21 | 21 | get 'list_all' |
|
22 | 22 | end |
|
23 | 23 | end |
|
24 | 24 | |
|
25 | 25 | resources :announcements do |
|
26 | 26 | member do |
|
27 | 27 | get 'toggle','toggle_front' |
|
28 | 28 | end |
|
29 | 29 | end |
|
30 | 30 | |
|
31 | 31 | resources :problems do |
|
32 | 32 | member do |
|
33 | 33 | get 'toggle' |
|
34 | 34 | get 'toggle_test' |
|
35 | 35 | get 'toggle_view_testcase' |
|
36 | 36 | get 'stat' |
|
37 | + get 'get_statement' | |
|
37 | 38 | end |
|
38 | 39 | collection do |
|
39 | 40 | get 'turn_all_off' |
|
40 | 41 | get 'turn_all_on' |
|
41 | 42 | get 'import' |
|
42 | 43 | get 'manage' |
|
43 | 44 | get 'quick_create' |
|
44 | 45 | post 'do_manage' |
|
45 | 46 | post 'do_import' |
|
46 | 47 | end |
|
47 | 48 | end |
|
48 | 49 | |
|
49 | 50 | resources :groups do |
|
50 | 51 | member do |
|
51 | 52 | post 'add_user', to: 'groups#add_user', as: 'add_user' |
|
52 | 53 | delete 'remove_user/:user_id', to: 'groups#remove_user', as: 'remove_user' |
|
53 | 54 | delete 'remove_all_user', to: 'groups#remove_all_user', as: 'remove_all_user' |
|
54 | 55 | post 'add_problem', to: 'groups#add_problem', as: 'add_problem' |
|
55 | 56 | delete 'remove_problem/:problem_id', to: 'groups#remove_problem', as: 'remove_problem' |
|
56 | 57 | delete 'remove_all_problem', to: 'groups#remove_all_problem', as: 'remove_all_problem' |
|
57 | 58 | get 'toggle' |
|
58 | 59 | end |
|
59 | 60 | collection do |
|
60 | 61 | |
|
61 | 62 | end |
|
62 | 63 | end |
|
63 | 64 | |
|
64 | 65 | resources :testcases, only: [] do |
|
65 | 66 | member do |
|
66 | 67 | get 'download_input' |
|
67 | 68 | get 'download_sol' |
|
68 | 69 | end |
|
69 | 70 | collection do |
|
70 | 71 | get 'show_problem/:problem_id(/:test_num)' => 'testcases#show_problem', as: 'show_problem' |
|
71 | 72 | end |
|
72 | 73 | end |
|
73 | 74 | |
|
74 | 75 | resources :grader_configuration, controller: 'configurations' do |
|
75 | 76 | collection do |
|
76 | 77 | get 'set_exam_right(/:value)', action: 'set_exam_right', as: 'set_exam_right' |
|
77 | 78 | end |
|
78 | 79 | end |
|
79 | 80 | |
|
80 | 81 | resources :users do |
|
81 | 82 | member do |
|
82 | 83 | get 'toggle_activate', 'toggle_enable' |
|
83 | 84 | get 'stat' |
|
84 | 85 | end |
|
85 | 86 | collection do |
|
86 | 87 | get 'profile' |
|
87 | 88 | post 'chg_passwd' |
|
88 | 89 | post 'chg_default_language' |
|
89 | 90 | end |
|
90 | 91 | end |
|
91 | 92 | |
|
92 | 93 | resources :submissions do |
|
93 | 94 | member do |
|
94 | 95 | get 'download' |
|
95 | 96 | get 'compiler_msg' |
|
96 | 97 | get 'rejudge' |
|
97 | 98 | get 'set_tag' |
|
98 | 99 | end |
|
99 | 100 | collection do |
|
100 | 101 | get 'prob/:problem_id', to: 'submissions#index', as: 'problem' |
|
101 | 102 | get 'direct_edit_problem/:problem_id(/:user_id)', to: 'submissions#direct_edit_problem', as: 'direct_edit_problem' |
|
102 | 103 | get 'get_latest_submission_status/:uid/:pid', to: 'submissions#get_latest_submission_status', as: 'get_latest_submission_status' |
|
103 | 104 | end |
|
104 | 105 | end |
|
105 | 106 | |
|
106 | 107 | |
|
107 | 108 | #user admin |
|
108 | 109 | resources :user_admin do |
|
109 | 110 | collection do |
|
110 | 111 | match 'bulk_manage', via: [:get, :post] |
|
111 | 112 | get 'bulk_mail' |
|
112 | 113 | get 'user_stat' |
|
113 | 114 | get 'import' |
|
114 | 115 | get 'new_list' |
|
115 | 116 | get 'admin' |
|
116 | 117 | get 'active' |
|
117 | 118 | get 'mass_mailing' |
|
118 | 119 | match 'modify_role', via: [:get, :post] |
|
119 | 120 | match 'create_from_list', via: [:get, :post] |
|
120 | 121 | match 'random_all_passwords', via: [:get, :post] |
|
121 | 122 | end |
|
122 | 123 | member do |
|
123 | 124 | get 'clear_last_ip' |
|
124 | 125 | end |
|
125 | 126 | end |
|
126 | 127 | |
|
127 | 128 | resources :contest_management, only: [:index] do |
|
128 | 129 | collection do |
|
129 | 130 | get 'user_stat' |
|
130 | 131 | get 'clear_stat' |
|
131 | 132 | get 'clear_all_stat' |
|
132 | 133 | get 'change_contest_mode' |
|
133 | 134 | end |
|
134 | 135 | end |
|
135 | 136 | |
|
136 | 137 | #get 'user_admin', to: 'user_admin#index' |
|
137 | 138 | #get 'user_admin/bulk_manage', to: 'user_admin#bulk_manage', as: 'bulk_manage_user_admin' |
|
138 | 139 | #post 'user_admin', to: 'user_admin#create' |
|
139 | 140 | #delete 'user_admin/:id', to: 'user_admin#destroy', as: 'user_admin_destroy' |
|
140 | 141 | |
|
141 | 142 | #singular resource |
|
142 | 143 | #---- BEWARE ---- singular resource maps to plural controller by default, we can override by provide controller name directly |
|
143 | 144 | #report |
|
144 | 145 | resource :report, only: [], controller: 'report' do |
|
145 | 146 | get 'login' |
|
146 | 147 | get 'multiple_login' |
|
147 | 148 | get 'problem_hof(/:id)', action: 'problem_hof', as: 'problem_hof' |
|
148 | 149 | get 'current_score(/:group_id)', action: 'current_score', as: 'current_score' |
|
149 | 150 | get 'max_score' |
|
150 | 151 | post 'show_max_score' |
|
151 | 152 | get 'stuck' |
|
152 | 153 | get 'cheat_report' |
|
153 | 154 | post 'cheat_report' |
|
154 | 155 | get 'cheat_scrutinize' |
|
155 | 156 | post 'cheat_scrutinize' |
|
156 | 157 | get 'submission' |
|
157 | 158 | post 'submission_query' |
|
158 | 159 | get 'login_stat' |
|
159 | 160 | post 'login_stat' |
|
160 | 161 | get 'login' |
|
161 | 162 | post 'login_summary_query' |
|
162 | 163 | post 'login_detail_query' |
|
163 | 164 | end |
|
164 | 165 | #get 'report/current_score', to: 'report#current_score', as: 'report_current_score' |
|
165 | 166 | #get 'report/problem_hof(/:id)', to: 'report#problem_hof', as: 'report_problem_hof' |
|
166 | 167 | #get "report/login" |
|
167 | 168 | #get 'report/max_score', to: 'report#max_score', as: 'report_max_score' |
|
168 | 169 | #post 'report/show_max_score', to: 'report#show_max_score', as: 'report_show_max_score' |
|
169 | 170 | |
|
170 | 171 | resource :main, only: [], controller: 'main' do |
|
171 | 172 | get 'login' |
|
172 | 173 | get 'logout' |
|
173 | 174 | get 'list' |
|
174 | 175 | get 'submission(/:id)', action: 'submission', as: 'main_submission' |
|
175 | 176 | get 'announcements' |
|
176 | 177 | get 'help' |
|
177 | 178 | post 'submit' |
|
178 | 179 | end |
|
179 | 180 | #main |
|
180 | 181 | #get "main/list" |
|
181 | 182 | #get 'main/submission(/:id)', to: 'main#submission', as: 'main_submission' |
|
182 | 183 | #post 'main/submit', to: 'main#submit' |
|
183 | 184 | #get 'main/announcements', to: 'main#announcements' |
|
184 | 185 | |
|
185 | 186 | |
|
186 | 187 | # |
|
187 | 188 | get 'tasks/view/:file.:ext' => 'tasks#view' |
|
188 | 189 | get 'tasks/download/:id/:file.:ext' => 'tasks#download', as: 'download_task' |
|
189 | 190 | get 'heartbeat/:id/edit' => 'heartbeat#edit' |
|
190 | 191 | |
|
191 | 192 | #grader |
|
192 | 193 | #get 'graders/list', to: 'graders#list', as: 'grader_list' |
|
193 | 194 | namespace :graders do |
|
194 | 195 | get 'task/:id/:type', action: 'task', as: 'task' |
|
195 | 196 | get 'view/:id/:type', action: 'view', as: 'view' |
|
196 | 197 | get 'clear/:id', action: 'clear', as: 'clear' |
|
197 | 198 | get 'start_grading' |
|
198 | 199 | get 'start_exam' |
|
199 | 200 | get 'clear_all' |
|
200 | 201 | get 'stop_all' |
|
201 | 202 | |
|
202 | 203 | get 'stop' |
|
203 | 204 | get 'clear_terminated' |
|
204 | 205 | get 'list' |
|
205 | 206 | end |
|
206 | 207 | |
|
207 | 208 | |
|
208 | 209 | # See how all your routes lay out with "rake routes" |
|
209 | 210 | |
|
210 | 211 | # This is a legacy wild controller route that's not recommended for RESTful applications. |
|
211 | 212 | # Note: This route will make all actions in every controller accessible via GET requests. |
|
212 | 213 | # match ':controller(/:action(/:id))(.:format)', via: [:get, :post] |
|
213 | 214 | end |
@@ -1,325 +1,346 | |||
|
1 | 1 | # This file is auto-generated from the current state of the database. Instead |
|
2 | 2 | # of editing this file, please use the migrations feature of Active Record to |
|
3 | 3 | # incrementally modify your database, and then regenerate this schema definition. |
|
4 | 4 | # |
|
5 | 5 | # This file is the source Rails uses to define your schema when running `bin/rails |
|
6 | 6 | # db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to |
|
7 | 7 | # be faster and is potentially less error prone than running all of your |
|
8 | 8 | # migrations from scratch. Old migrations may fail to apply correctly if those |
|
9 | 9 | # migrations use external dependencies or application code. |
|
10 | 10 | # |
|
11 | 11 | # It's strongly recommended that you check this file into your version control system. |
|
12 | 12 | |
|
13 |
- ActiveRecord::Schema[7.0].define(version: 2022_02_04 |
|
|
14 |
- create_table "a |
|
|
13 | + ActiveRecord::Schema[7.0].define(version: 2022_09_27_074644) do | |
|
14 | + create_table "active_storage_attachments", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| | |
|
15 | + t.string "name", null: false | |
|
16 | + t.string "record_type", null: false | |
|
17 | + t.bigint "record_id", null: false | |
|
18 | + t.bigint "blob_id", null: false | |
|
19 | + t.datetime "created_at", null: false | |
|
20 | + t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id" | |
|
21 | + t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true | |
|
22 | + end | |
|
23 | + | |
|
24 | + create_table "active_storage_blobs", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| | |
|
25 | + t.string "key", null: false | |
|
26 | + t.string "filename", null: false | |
|
27 | + t.string "content_type" | |
|
28 | + t.text "metadata" | |
|
29 | + t.string "service_name", null: false | |
|
30 | + t.bigint "byte_size", null: false | |
|
31 | + t.string "checksum" | |
|
32 | + t.datetime "created_at", null: false | |
|
33 | + t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true | |
|
34 | + end | |
|
35 | + | |
|
36 | + create_table "active_storage_variant_records", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| | |
|
37 | + t.bigint "blob_id", null: false | |
|
38 | + t.string "variation_digest", null: false | |
|
39 | + t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true | |
|
40 | + end | |
|
41 | + | |
|
42 | + create_table "announcements", id: :integer, charset: "utf8mb3", collation: "utf8mb3_unicode_ci", force: :cascade do |t| | |
|
15 | 43 | t.string "author" |
|
16 |
- t.text "body" |
|
|
44 | + t.text "body" | |
|
17 | 45 | t.boolean "published" |
|
18 | 46 | t.datetime "created_at", precision: nil, null: false |
|
19 | 47 | t.datetime "updated_at", precision: nil, null: false |
|
20 | 48 | t.boolean "frontpage", default: false |
|
21 | 49 | t.boolean "contest_only", default: false |
|
22 | 50 | t.string "title" |
|
23 | 51 | t.string "notes" |
|
24 | 52 | t.boolean "on_nav_bar", default: false |
|
25 | 53 | end |
|
26 | 54 | |
|
27 | - create_table "contests", id: :integer, charset: "utf8", force: :cascade do |t| | |
|
55 | + create_table "contests", id: :integer, charset: "utf8mb3", collation: "utf8mb3_unicode_ci", force: :cascade do |t| | |
|
28 | 56 | t.string "title" |
|
29 | 57 | t.boolean "enabled" |
|
30 | 58 | t.datetime "created_at", precision: nil, null: false |
|
31 | 59 | t.datetime "updated_at", precision: nil, null: false |
|
32 | 60 | t.string "name" |
|
33 | 61 | end |
|
34 | 62 | |
|
35 | - create_table "contests_problems", id: false, charset: "utf8", force: :cascade do |t| | |
|
63 | + create_table "contests_problems", id: false, charset: "utf8mb3", collation: "utf8mb3_unicode_ci", force: :cascade do |t| | |
|
36 | 64 | t.integer "contest_id" |
|
37 | 65 | t.integer "problem_id" |
|
38 | 66 | end |
|
39 | 67 | |
|
40 | - create_table "contests_users", id: false, charset: "utf8", force: :cascade do |t| | |
|
68 | + create_table "contests_users", id: false, charset: "utf8mb3", collation: "utf8mb3_unicode_ci", force: :cascade do |t| | |
|
41 | 69 | t.integer "contest_id" |
|
42 | 70 | t.integer "user_id" |
|
43 | 71 | end |
|
44 | 72 | |
|
45 | - create_table "countries", id: :integer, charset: "utf8", force: :cascade do |t| | |
|
73 | + create_table "countries", id: :integer, charset: "utf8mb3", collation: "utf8mb3_unicode_ci", force: :cascade do |t| | |
|
46 | 74 | t.string "name" |
|
47 | 75 | t.datetime "created_at", precision: nil, null: false |
|
48 | 76 | t.datetime "updated_at", precision: nil, null: false |
|
49 | 77 | end |
|
50 | 78 | |
|
51 | - create_table "descriptions", id: :integer, charset: "utf8", force: :cascade do |t| | |
|
52 |
- t.text "body" |
|
|
79 | + create_table "descriptions", id: :integer, charset: "utf8mb3", collation: "utf8mb3_unicode_ci", force: :cascade do |t| | |
|
80 | + t.text "body" | |
|
53 | 81 | t.boolean "markdowned" |
|
54 | 82 | t.datetime "created_at", precision: nil, null: false |
|
55 | 83 | t.datetime "updated_at", precision: nil, null: false |
|
56 | 84 | end |
|
57 | 85 | |
|
58 | - create_table "grader_configurations", id: :integer, charset: "utf8", force: :cascade do |t| | |
|
86 | + create_table "grader_configurations", id: :integer, charset: "utf8mb3", collation: "utf8mb3_unicode_ci", force: :cascade do |t| | |
|
59 | 87 | t.string "key" |
|
60 | 88 | t.string "value_type" |
|
61 | 89 | t.string "value" |
|
62 | 90 | t.datetime "created_at", precision: nil, null: false |
|
63 | 91 | t.datetime "updated_at", precision: nil, null: false |
|
64 |
- t.text "description" |
|
|
92 | + t.text "description" | |
|
65 | 93 | end |
|
66 | 94 | |
|
67 | - create_table "grader_processes", id: :integer, charset: "utf8", force: :cascade do |t| | |
|
95 | + create_table "grader_processes", id: :integer, charset: "utf8mb3", collation: "utf8mb3_unicode_ci", force: :cascade do |t| | |
|
68 | 96 | t.string "host" |
|
69 | 97 | t.integer "pid" |
|
70 | 98 | t.string "mode" |
|
71 | 99 | t.boolean "active" |
|
72 | 100 | t.datetime "created_at", precision: nil, null: false |
|
73 | 101 | t.datetime "updated_at", precision: nil, null: false |
|
74 | 102 | t.integer "task_id" |
|
75 | 103 | t.string "task_type" |
|
76 | 104 | t.boolean "terminated" |
|
77 | 105 | t.index ["host", "pid"], name: "index_grader_processes_on_ip_and_pid" |
|
78 | 106 | end |
|
79 | 107 | |
|
80 | 108 | create_table "groups", id: :integer, charset: "latin1", force: :cascade do |t| |
|
81 | 109 | t.string "name" |
|
82 | 110 | t.string "description" |
|
83 | 111 | t.boolean "enabled", default: true |
|
84 | 112 | end |
|
85 | 113 | |
|
86 | 114 | create_table "groups_problems", id: false, charset: "latin1", force: :cascade do |t| |
|
87 | 115 | t.integer "problem_id", null: false |
|
88 | 116 | t.integer "group_id", null: false |
|
89 | 117 | t.index ["group_id", "problem_id"], name: "index_groups_problems_on_group_id_and_problem_id" |
|
90 | 118 | end |
|
91 | 119 | |
|
92 | 120 | create_table "groups_users", charset: "latin1", force: :cascade do |t| |
|
93 | 121 | t.integer "group_id", null: false |
|
94 | 122 | t.integer "user_id", null: false |
|
95 | 123 | t.index ["user_id", "group_id"], name: "index_groups_users_on_user_id_and_group_id" |
|
96 | 124 | end |
|
97 | 125 | |
|
98 |
- create_table "heart_beats", id: :integer, charset: " |
|
|
126 | + create_table "heart_beats", id: :integer, charset: "utf8mb3", collation: "utf8mb3_unicode_ci", force: :cascade do |t| | |
|
99 | 127 | t.integer "user_id" |
|
100 | 128 | t.string "ip_address" |
|
101 | 129 | t.datetime "created_at", precision: nil, null: false |
|
102 | 130 | t.datetime "updated_at", precision: nil, null: false |
|
103 | 131 | t.string "status" |
|
104 | 132 | t.index ["updated_at"], name: "index_heart_beats_on_updated_at" |
|
105 | 133 | end |
|
106 | 134 | |
|
107 | - create_table "languages", id: :integer, charset: "utf8", force: :cascade do |t| | |
|
135 | + create_table "languages", id: :integer, charset: "utf8mb3", collation: "utf8mb3_unicode_ci", force: :cascade do |t| | |
|
108 | 136 | t.string "name", limit: 10 |
|
109 | 137 | t.string "pretty_name" |
|
110 | 138 | t.string "ext", limit: 10 |
|
111 | 139 | t.string "common_ext" |
|
112 | 140 | end |
|
113 | 141 | |
|
114 |
- create_table "logins", id: :integer, charset: " |
|
|
142 | + create_table "logins", id: :integer, charset: "utf8mb3", collation: "utf8mb3_unicode_ci", force: :cascade do |t| | |
|
115 | 143 | t.integer "user_id" |
|
116 | 144 | t.string "ip_address" |
|
117 | 145 | t.datetime "created_at", precision: nil, null: false |
|
118 | 146 | t.datetime "updated_at", precision: nil, null: false |
|
119 | 147 | t.index ["user_id"], name: "index_logins_on_user_id" |
|
120 | 148 | end |
|
121 | 149 | |
|
122 | - create_table "messages", id: :integer, charset: "utf8", force: :cascade do |t| | |
|
150 | + create_table "messages", id: :integer, charset: "utf8mb3", collation: "utf8mb3_unicode_ci", force: :cascade do |t| | |
|
123 | 151 | t.integer "sender_id" |
|
124 | 152 | t.integer "receiver_id" |
|
125 | 153 | t.integer "replying_message_id" |
|
126 |
- t.text "body" |
|
|
154 | + t.text "body" | |
|
127 | 155 | t.boolean "replied" |
|
128 | 156 | t.datetime "created_at", precision: nil, null: false |
|
129 | 157 | t.datetime "updated_at", precision: nil, null: false |
|
130 | 158 | end |
|
131 | 159 | |
|
132 |
- create_table "problems", id: : |
|
|
133 |
- t.string "name", limit: |
|
|
160 | + create_table "problems", id: :integer, charset: "utf8mb3", collation: "utf8mb3_unicode_ci", force: :cascade do |t| | |
|
161 | + t.string "name", limit: 30 | |
|
134 | 162 | t.string "full_name" |
|
135 | 163 | t.integer "full_score" |
|
136 | 164 | t.date "date_added" |
|
137 | 165 | t.boolean "available" |
|
138 | 166 | t.string "url" |
|
139 | 167 | t.integer "description_id" |
|
140 | 168 | t.boolean "test_allowed" |
|
141 | 169 | t.boolean "output_only" |
|
142 | 170 | t.string "description_filename" |
|
143 | 171 | t.boolean "view_testcase" |
|
144 | 172 | t.integer "difficulty" |
|
173 | + t.text "description" | |
|
174 | + t.boolean "markdown" | |
|
145 | 175 | end |
|
146 | 176 | |
|
147 |
- create_table "problems_tags", id: : |
|
|
148 |
- t. |
|
|
177 | + create_table "problems_tags", id: :integer, charset: "latin1", force: :cascade do |t| | |
|
178 | + t.integer "problem_id" | |
|
149 | 179 | t.integer "tag_id" |
|
150 | 180 | t.index ["problem_id", "tag_id"], name: "index_problems_tags_on_problem_id_and_tag_id", unique: true |
|
151 | 181 | t.index ["problem_id"], name: "index_problems_tags_on_problem_id" |
|
152 | 182 | t.index ["tag_id"], name: "index_problems_tags_on_tag_id" |
|
153 | 183 | end |
|
154 | 184 | |
|
155 | - create_table "rights", id: :integer, charset: "utf8", force: :cascade do |t| | |
|
185 | + create_table "rights", id: :integer, charset: "utf8mb3", collation: "utf8mb3_unicode_ci", force: :cascade do |t| | |
|
156 | 186 | t.string "name" |
|
157 | 187 | t.string "controller" |
|
158 | 188 | t.string "action" |
|
159 | 189 | end |
|
160 | 190 | |
|
161 | - create_table "rights_roles", id: false, charset: "utf8", force: :cascade do |t| | |
|
191 | + create_table "rights_roles", id: false, charset: "utf8mb3", collation: "utf8mb3_unicode_ci", force: :cascade do |t| | |
|
162 | 192 | t.integer "right_id" |
|
163 | 193 | t.integer "role_id" |
|
164 | 194 | t.index ["role_id"], name: "index_rights_roles_on_role_id" |
|
165 | 195 | end |
|
166 | 196 | |
|
167 | - create_table "roles", id: :integer, charset: "utf8", force: :cascade do |t| | |
|
197 | + create_table "roles", id: :integer, charset: "utf8mb3", collation: "utf8mb3_unicode_ci", force: :cascade do |t| | |
|
168 | 198 | t.string "name" |
|
169 | 199 | end |
|
170 | 200 | |
|
171 | - create_table "roles_users", id: false, charset: "utf8", force: :cascade do |t| | |
|
201 | + create_table "roles_users", id: false, charset: "utf8mb3", collation: "utf8mb3_unicode_ci", force: :cascade do |t| | |
|
172 | 202 | t.integer "role_id" |
|
173 | 203 | t.integer "user_id" |
|
174 | 204 | t.index ["user_id"], name: "index_roles_users_on_user_id" |
|
175 | 205 | end |
|
176 | 206 | |
|
177 | - create_table "sessions", id: :integer, charset: "utf8", force: :cascade do |t| | |
|
207 | + create_table "sessions", id: :integer, charset: "utf8mb3", collation: "utf8mb3_unicode_ci", force: :cascade do |t| | |
|
178 | 208 | t.string "session_id" |
|
179 |
- t.text "data" |
|
|
209 | + t.text "data" | |
|
180 | 210 | t.datetime "updated_at", precision: nil |
|
181 | 211 | t.index ["session_id"], name: "index_sessions_on_session_id" |
|
182 | 212 | t.index ["updated_at"], name: "index_sessions_on_updated_at" |
|
183 | 213 | end |
|
184 | 214 | |
|
185 | - create_table "sites", id: :integer, charset: "utf8", force: :cascade do |t| | |
|
215 | + create_table "sites", id: :integer, charset: "utf8mb3", collation: "utf8mb3_unicode_ci", force: :cascade do |t| | |
|
186 | 216 | t.string "name" |
|
187 | 217 | t.boolean "started" |
|
188 | 218 | t.datetime "start_time", precision: nil |
|
189 | 219 | t.datetime "created_at", precision: nil, null: false |
|
190 | 220 | t.datetime "updated_at", precision: nil, null: false |
|
191 | 221 | t.integer "country_id" |
|
192 | 222 | t.string "password" |
|
193 | 223 | end |
|
194 | 224 | |
|
195 |
- create_table "s |
|
|
196 | - t.string "solution" | |
|
197 | - t.bigint "problem_id" | |
|
198 | - t.bigint "submission_id" | |
|
199 | - t.integer "type" | |
|
200 | - t.index ["problem_id"], name: "index_solutions_on_problem_id" | |
|
201 | - t.index ["submission_id"], name: "index_solutions_on_submission_id" | |
|
202 | - end | |
|
203 | - | |
|
204 | - create_table "submission_view_logs", id: :integer, charset: "latin1", force: :cascade do |t| | |
|
225 | + create_table "submission_view_logs", id: :integer, charset: "utf8mb3", collation: "utf8mb3_unicode_ci", force: :cascade do |t| | |
|
205 | 226 | t.integer "user_id" |
|
206 | 227 | t.integer "submission_id" |
|
207 | 228 | t.datetime "created_at", precision: nil, null: false |
|
208 | 229 | t.datetime "updated_at", precision: nil, null: false |
|
209 | 230 | end |
|
210 | 231 | |
|
211 |
- create_table "submissions", id: : |
|
|
232 | + create_table "submissions", id: :integer, charset: "utf8mb3", collation: "utf8mb3_unicode_ci", force: :cascade do |t| | |
|
212 | 233 | t.integer "user_id" |
|
213 | 234 | t.integer "problem_id" |
|
214 | 235 | t.integer "language_id" |
|
215 | 236 | t.text "source", size: :medium |
|
216 | 237 | t.binary "binary" |
|
217 | 238 | t.datetime "submitted_at", precision: nil |
|
218 | 239 | t.datetime "compiled_at", precision: nil |
|
219 |
- t.text "compiler_message" |
|
|
240 | + t.text "compiler_message" | |
|
220 | 241 | t.datetime "graded_at", precision: nil |
|
221 | 242 | t.integer "points" |
|
222 |
- t.text "grader_comment" |
|
|
243 | + t.text "grader_comment" | |
|
223 | 244 | t.integer "number" |
|
224 | 245 | t.string "source_filename" |
|
225 | 246 | t.float "max_runtime" |
|
226 | 247 | t.integer "peak_memory" |
|
227 | 248 | t.integer "effective_code_length" |
|
228 | 249 | t.string "ip_address" |
|
229 | 250 | t.integer "tag", default: 0 |
|
230 | 251 | t.index ["submitted_at"], name: "index_submissions_on_submitted_at" |
|
231 | 252 | t.index ["user_id", "problem_id", "number"], name: "index_submissions_on_user_id_and_problem_id_and_number", unique: true |
|
232 | 253 | t.index ["user_id", "problem_id"], name: "index_submissions_on_user_id_and_problem_id" |
|
233 | 254 | end |
|
234 | 255 | |
|
235 | 256 | create_table "tags", id: :integer, charset: "latin1", force: :cascade do |t| |
|
236 | 257 | t.string "name", null: false |
|
237 | 258 | t.text "description" |
|
238 | 259 | t.boolean "public" |
|
239 | 260 | t.datetime "created_at", precision: nil, null: false |
|
240 | 261 | t.datetime "updated_at", precision: nil, null: false |
|
241 | 262 | end |
|
242 | 263 | |
|
243 | - create_table "tasks", id: :integer, charset: "utf8", force: :cascade do |t| | |
|
264 | + create_table "tasks", id: :integer, charset: "utf8mb3", collation: "utf8mb3_unicode_ci", force: :cascade do |t| | |
|
244 | 265 | t.integer "submission_id" |
|
245 | 266 | t.datetime "created_at", precision: nil |
|
246 | 267 | t.integer "status" |
|
247 | 268 | t.datetime "updated_at", precision: nil |
|
248 | 269 | t.index ["status"], name: "index_tasks_on_status" |
|
249 | 270 | t.index ["submission_id"], name: "index_tasks_on_submission_id" |
|
250 | 271 | end |
|
251 | 272 | |
|
252 | - create_table "test_pairs", id: :integer, charset: "utf8", force: :cascade do |t| | |
|
273 | + create_table "test_pairs", id: :integer, charset: "utf8mb3", collation: "utf8mb3_unicode_ci", force: :cascade do |t| | |
|
253 | 274 | t.integer "problem_id" |
|
254 |
- t.text "input", size: : |
|
|
255 |
- t.text "solution", size: : |
|
|
275 | + t.text "input", size: :medium | |
|
276 | + t.text "solution", size: :medium | |
|
256 | 277 | t.datetime "created_at", precision: nil, null: false |
|
257 | 278 | t.datetime "updated_at", precision: nil, null: false |
|
258 | 279 | end |
|
259 | 280 | |
|
260 | - create_table "test_requests", id: :integer, charset: "utf8", force: :cascade do |t| | |
|
281 | + create_table "test_requests", id: :integer, charset: "utf8mb3", collation: "utf8mb3_unicode_ci", force: :cascade do |t| | |
|
261 | 282 | t.integer "user_id" |
|
262 | 283 | t.integer "problem_id" |
|
263 | 284 | t.integer "submission_id" |
|
264 | 285 | t.string "input_file_name" |
|
265 | 286 | t.string "output_file_name" |
|
266 | 287 | t.string "running_stat" |
|
267 | 288 | t.integer "status" |
|
268 | 289 | t.datetime "updated_at", precision: nil, null: false |
|
269 | 290 | t.datetime "submitted_at", precision: nil |
|
270 | 291 | t.datetime "compiled_at", precision: nil |
|
271 |
- t.text "compiler_message" |
|
|
292 | + t.text "compiler_message" | |
|
272 | 293 | t.datetime "graded_at", precision: nil |
|
273 | 294 | t.string "grader_comment" |
|
274 | 295 | t.datetime "created_at", precision: nil, null: false |
|
275 | 296 | t.float "running_time" |
|
276 | 297 | t.string "exit_status" |
|
277 | 298 | t.integer "memory_usage" |
|
278 | 299 | t.index ["user_id", "problem_id"], name: "index_test_requests_on_user_id_and_problem_id" |
|
279 | 300 | end |
|
280 | 301 | |
|
281 | 302 | create_table "testcases", id: :integer, charset: "latin1", force: :cascade do |t| |
|
282 | 303 | t.integer "problem_id" |
|
283 | 304 | t.integer "num" |
|
284 | 305 | t.integer "group" |
|
285 | 306 | t.integer "score" |
|
286 | 307 | t.text "input", size: :long |
|
287 | 308 | t.text "sol", size: :long |
|
288 |
- t.datetime "created_at", precision: nil |
|
|
289 |
- t.datetime "updated_at", precision: nil |
|
|
309 | + t.datetime "created_at", precision: nil | |
|
310 | + t.datetime "updated_at", precision: nil | |
|
290 | 311 | t.index ["problem_id"], name: "index_testcases_on_problem_id" |
|
291 | 312 | end |
|
292 | 313 | |
|
293 | - create_table "user_contest_stats", id: :integer, charset: "utf8", force: :cascade do |t| | |
|
314 | + create_table "user_contest_stats", id: :integer, charset: "utf8mb3", collation: "utf8mb3_unicode_ci", force: :cascade do |t| | |
|
294 | 315 | t.integer "user_id" |
|
295 | 316 | t.datetime "started_at", precision: nil |
|
296 | 317 | t.datetime "created_at", precision: nil, null: false |
|
297 | 318 | t.datetime "updated_at", precision: nil, null: false |
|
298 | 319 | t.boolean "forced_logout" |
|
299 | 320 | end |
|
300 | 321 | |
|
301 | - create_table "users", id: :integer, charset: "utf8", force: :cascade do |t| | |
|
322 | + create_table "users", id: :integer, charset: "utf8mb3", collation: "utf8mb3_unicode_ci", force: :cascade do |t| | |
|
302 | 323 | t.string "login", limit: 50 |
|
303 | 324 | t.string "full_name" |
|
304 | 325 | t.string "hashed_password" |
|
305 | 326 | t.string "salt", limit: 5 |
|
306 | 327 | t.string "alias" |
|
307 | 328 | t.string "email" |
|
308 | 329 | t.integer "site_id" |
|
309 | 330 | t.integer "country_id" |
|
310 | 331 | t.boolean "activated", default: false |
|
311 | 332 | t.datetime "created_at", precision: nil |
|
312 | 333 | t.datetime "updated_at", precision: nil |
|
313 | - t.string "section" | |
|
314 | 334 | t.boolean "enabled", default: true |
|
315 | 335 | t.string "remark" |
|
316 | 336 | t.string "last_ip" |
|
337 | + t.string "section" | |
|
317 | 338 | t.integer "default_language" |
|
318 | 339 | t.index ["login"], name: "index_users_on_login", unique: true |
|
319 | 340 | end |
|
320 | 341 | |
|
342 | + add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id" | |
|
343 | + add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id" | |
|
321 | 344 | add_foreign_key "problems_tags", "problems" |
|
322 | 345 | add_foreign_key "problems_tags", "tags" |
|
323 | - add_foreign_key "solutions", "problems" | |
|
324 | - add_foreign_key "solutions", "submissions" | |
|
325 | 346 | end |
modified file |
deleted file |
You need to be logged in to leave comments.
Login now