Description:
modernize problem
Commit status:
[Not Reviewed]
References:
Comments:
0 Commit comments 0 Inline Comments
Unresolved TODOs:
There are no unresolved TODOs
Add another comment

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 mail
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 - flash[:notice] = 'Problem was successfully created.'
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 - 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"
101 - @problem.save
102 - end
103 - redirect_to :action => 'show', :id => @problem
80 + flash[:notice] = 'Problem was successfully updated. '
81 + flash[:notice] += 'A new statement PDF is uploaded' if problem_params[:statement]
82 + @problem.save
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 = Problem.find(params[:id]).destroy
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, :test_allowed,:output_only, :url, :description, tag_ids:[])
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 = "&nbsp;&nbsp;" + (t 'title_bar.contest_not_started')
159 159 else
160 160 time_left = "&nbsp;&nbsp;" + (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
218 + end
219 + return class_name if ok && options.size > 0
220 + return ''
221 + end
222 +
212 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'}, aria: {haspopup:"true", expanded:"false"}, role: "button"}
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 - = error_messages_for 'problem'
2 - / [form:problem]
3 - .form-group
4 - %label{:for => "problem_name"} Name
5 - = text_field 'problem', 'name', class: 'form-control'
6 - %small
7 - Do not directly edit the problem name, unless you know what you are doing. If you want to change the name, use the name change button in the problem management menu instead.
8 - .form-group
9 - %label{:for => "problem_full_name"} Full name
10 - = text_field 'problem', 'full_name', class: 'form-control'
11 - .form-group
12 - %label{:for => "problem_full_score"} Full score
13 - = text_field 'problem', 'full_score', class: 'form-control'
14 - .form-group
15 - %label{:for => "problem_full_score"} Tags
16 - = collection_select(:problem, :tag_ids, Tag.all, :id, :name, {}, {multiple: true, class: 'form-control select2'})
17 - .form-group
18 - %label{:for => "problem_date_added"} Date added
19 - = date_select 'problem', 'date_added', class: 'form-control'
20 - - # TODO: these should be put in model Problem, but I can't think of
21 - - # nice default values for them. These values look fine only
22 - - # in this case (of lazily adding new problems).
23 - - @problem.available = true if @problem!=nil and @problem.available==nil
24 - - @problem.test_allowed = true if @problem!=nil and @problem.test_allowed==nil
25 - - @problem.output_only = false if @problem!=nil and @problem.output_only==nil
26 - .checkbox
27 - %label{:for => "problem_available"}
28 - = check_box :problem, :available
29 - Available?
30 - .checkbox
31 - %label{:for => "problem_test_allowed"}
32 - = check_box :problem, :test_allowed
33 - Test allowed?
34 - .checkbox
35 - %label{:for => "problem_output_only"}
36 - = check_box :problem, :output_only
37 - Output only?
38 - = error_messages_for 'description'
39 - .form-group
40 - %label{:for => "description_body"} Description
41 - %br/
42 - = text_area :description, :body, :rows => 10, :cols => 80,class: 'form-control'
43 - .form-group
44 - %label{:for => "description_markdowned"} Markdowned?
45 - = select "description", |
46 - "markdowned", |
47 - [['True',true],['False',false]], |
48 - {:selected => (@description) ? @description.markdowned : false } |
49 - .form-group
50 - %label{:for => "problem_url"} URL
51 - %br/
52 - = text_field 'problem', 'url',class: 'form-control'
53 - %p
54 - Task PDF #{file_field_tag 'file'}
55 - / [eoform:problem]
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 + -#
23 + = error_messages_for 'problem'
24 +
25 + / [form:problem]
26 + .form-group
27 + %label{:for => "problem_name"} Name
28 + = text_field 'problem', 'name', class: 'form-control'
29 + %small
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.
31 + .form-group
32 + %label{:for => "problem_full_name"} Full name
33 + = text_field 'problem', 'full_name', class: 'form-control'
34 + .form-group
35 + %label{:for => "problem_full_score"} Full score
36 + = text_field 'problem', 'full_score', class: 'form-control'
37 + .form-group
38 + %label{:for => "problem_full_score"} Tags
39 + = collection_select(:problem, :tag_ids, Tag.all, :id, :name, {}, {multiple: true, class: 'form-control select2'})
40 + .form-group
41 + %label{:for => "problem_date_added"} Date added
42 + = date_select 'problem', 'date_added', class: 'form-control'
43 + - # TODO: these should be put in model Problem, but I can't think of
44 + - # nice default values for them. These values look fine only
45 + - # in this case (of lazily adding new problems).
46 + - @problem.available = true if @problem!=nil and @problem.available==nil
47 + - @problem.test_allowed = true if @problem!=nil and @problem.test_allowed==nil
48 + - @problem.output_only = false if @problem!=nil and @problem.output_only==nil
49 + .checkbox
50 + %label{:for => "problem_available"}
51 + = check_box :problem, :available
52 + Available?
53 + .checkbox
54 + %label{:for => "problem_test_allowed"}
55 + = check_box :problem, :test_allowed
56 + Test allowed?
57 + .checkbox
58 + %label{:for => "problem_output_only"}
59 + = check_box :problem, :output_only
60 + Output only?
61 + = error_messages_for 'description'
62 + .form-group
63 + %label{:for => "description_body"} Description
64 + %br/
65 + = text_area :description, :body, :rows => 10, :cols => 80,class: 'form-control'
66 + .form-group
67 + %label{:for => "description_markdowned"} Markdowned?
68 + = select "description", |
69 + "markdowned", |
70 + [['True',true],['False',false]], |
71 + {:selected => (@description) ? @description.markdowned : false } |
72 + .form-group
73 + %label{:for => "problem_url"} URL
74 + %br/
75 + = text_field 'problem', 'url',class: 'form-control'
76 + %p
77 + Task PDF #{file_field_tag 'file'}
78 + / [eoform:problem]
@@ -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
8 - .col-md-4
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'}
1 + %h1 Editing 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'
@@ -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', valid_class: 'is-valid'
62 - b.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback' }
63 - b.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' }
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: 'form-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b|
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, tag: 'div', class: 'form-check' do |bb|
71 - bb.use :input, class: 'form-check-input', error_class: 'is-invalid', valid_class: 'is-valid'
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: { tag: 'div', class: 'invalid-feedback' }
74 - bb.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' }
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: 'form-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b|
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', valid_class: 'is-valid'
86 - b.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback d-block' }
87 - b.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' }
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: 'form-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b|
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', valid_class: 'is-valid'
98 - b.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback d-block' }
99 - b.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' }
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-file', error_class: 'is-invalid', valid_class: 'is-valid'
111 - b.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback' }
112 - b.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' }
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 tag: 'div', class: 'd-flex flex-row justify-content-between align-items-center' do |ba|
121 - ba.use :input, class: 'form-control mx-1', error_class: 'is-invalid', valid_class: 'is-valid'
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: { tag: 'div', class: 'invalid-feedback d-block' }
124 - b.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' }
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-control-range', error_class: 'is-invalid', valid_class: 'is-valid'
135 - b.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback d-block' }
136 - b.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' }
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, tag: 'div', class: 'col-sm-9' do |ba|
153 - ba.use :input, class: 'form-control', error_class: 'is-invalid', valid_class: 'is-valid'
154 - ba.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback' }
155 - ba.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' }
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 tag: 'label', class: 'col-sm-3' do |ba|
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: { tag: 'div', class: 'invalid-feedback d-block' }
171 - bb.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' }
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', tag: 'div', class: 'form-group row', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b|
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, tag: 'div', class: 'col-sm-9' do |ba|
182 - ba.use :input, class: 'form-check-input', error_class: 'is-invalid', valid_class: 'is-valid'
183 - ba.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback d-block' }
184 - ba.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' }
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', tag: 'div', class: 'form-group row', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b|
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, tag: 'div', class: 'col-sm-9' do |ba|
194 - ba.use :input, class: 'form-check-input', error_class: 'is-invalid', valid_class: 'is-valid'
195 - ba.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback d-block' }
196 - ba.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' }
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, tag: 'div', class: 'col-sm-9' do |ba|
209 - ba.use :input, error_class: 'is-invalid', valid_class: 'is-valid'
210 - ba.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback d-block' }
211 - ba.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' }
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, tag: 'div', class: 'col-sm-9' do |ba|
221 - ba.wrapper tag: 'div', class: 'd-flex flex-row justify-content-between align-items-center' do |bb|
222 - bb.use :input, class: 'form-control mx-1', error_class: 'is-invalid', valid_class: 'is-valid'
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: { tag: 'div', class: 'invalid-feedback d-block' }
225 - ba.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' }
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, tag: 'div', class: 'col-sm-9' do |ba|
237 - ba.use :input, class: 'form-control-range', error_class: 'is-invalid', valid_class: 'is-valid'
238 - ba.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback d-block' }
239 - ba.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' }
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: 'sr-only'
272 + b.use :label, class: 'visually-hidden'
256 273
257 - b.use :input, class: 'form-control', error_class: 'is-invalid', valid_class: 'is-valid'
258 - b.use :error, wrap_with: { tag: 'div', class: 'invalid-feedback' }
259 - b.optional :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' }
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 :label, class: 'form-check-label'
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 :legend_tag, tag: 'legend', class: 'col-form-label pt-0' do |ba|
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' }
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' }
335 303 end
336 - b.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' }
337 - 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/rafaelfranca/simple_form-bootstrap
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: { tag: 'div', class: 'invalid-feedback' }
399 - b.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' }
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, tag: 'div', class: 'form-label-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b|
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: 'custom-select', error_class: 'is-invalid', valid_class: 'is-valid'
349 + b.use :input, class: 'form-select', error_class: 'is-invalid'
407 350 b.use :label
408 - b.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback' }
409 - b.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' }
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_080936) do
14 - create_table "announcements", id: :integer, charset: "utf8", force: :cascade do |t|
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", size: :medium
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", size: :medium
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", size: :medium
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: "latin1", force: :cascade do |t|
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: "latin1", force: :cascade do |t|
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", size: :medium
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: :bigint, default: nil, charset: "utf8", force: :cascade do |t|
133 - t.string "name", limit: 100
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: :bigint, default: nil, charset: "latin1", force: :cascade do |t|
148 - t.bigint "problem_id"
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", size: :medium
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 "solutions", charset: "latin1", force: :cascade do |t|
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: :bigint, default: nil, charset: "utf8", force: :cascade do |t|
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", size: :medium
240 + t.text "compiler_message"
220 241 t.datetime "graded_at", precision: nil
221 242 t.integer "points"
222 - t.text "grader_comment", size: :medium
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: :long
255 - t.text "solution", size: :long
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", size: :medium
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, null: false
289 - t.datetime "updated_at", precision: nil, null: false
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
deleted file
You need to be logged in to leave comments. Login now