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

r877:93c4562b9761 - - The requested commit is too big and content was truncated. 14 files changed. Show full diff

new file 100644
@@ -1,1 +1,5
1 $font-size-base: 0.875rem;
1 $font-size-base: 0.875rem;
2 +
3 + $primary: #0074d9;
4 + //$danger: #ff4136;
5 +
@@ -1,229 +1,230
1 require 'csv'
1 require 'csv'
2
2
3 class UserAdminController < ApplicationController
3 class UserAdminController < ApplicationController
4
4
5 include MailHelperMethods
5 include MailHelperMethods
6
6
7 before_action :admin_authorization
7 before_action :admin_authorization
8
8
9 def index
9 def index
10 @user_count = User.count
10 @user_count = User.count
11 @users = User.all
11 @users = User.all
12 @hidden_columns = ['hashed_password', 'salt', 'created_at', 'updated_at']
12 @hidden_columns = ['hashed_password', 'salt', 'created_at', 'updated_at']
13 @contests = Contest.enabled
13 @contests = Contest.enabled
14 @user = User.new
14 @user = User.new
15 end
15 end
16
16
17 def active
17 def active
18 sessions = ActiveRecord::SessionStore::Session.where("updated_at >= ?", 60.minutes.ago)
18 sessions = ActiveRecord::SessionStore::Session.where("updated_at >= ?", 60.minutes.ago)
19 @users = []
19 @users = []
20 sessions.each do |session|
20 sessions.each do |session|
21 if session.data[:user_id]
21 if session.data[:user_id]
22 @users << User.find(session.data[:user_id])
22 @users << User.find(session.data[:user_id])
23 end
23 end
24 end
24 end
25 end
25 end
26
26
27 def show
27 def show
28 @user = User.find(params[:id])
28 @user = User.find(params[:id])
29 end
29 end
30
30
31 def new
31 def new
32 @user = User.new
32 @user = User.new
33 end
33 end
34
34
35 def create
35 def create
36 @user = User.new(user_params)
36 @user = User.new(user_params)
37 @user.activated = true
37 @user.activated = true
38 + byebug
38 if @user.save
39 if @user.save
39 flash[:notice] = 'User was successfully created.'
40 flash[:notice] = 'User was successfully created.'
40 redirect_to :action => 'index'
41 redirect_to :action => 'index'
41 else
42 else
42 render :action => 'new'
43 render :action => 'new'
43 end
44 end
44 end
45 end
45
46
46 def clear_last_ip
47 def clear_last_ip
47 @user = User.find(params[:id])
48 @user = User.find(params[:id])
48 @user.last_ip = nil
49 @user.last_ip = nil
49 @user.save
50 @user.save
50 redirect_to action: 'index', page: params[:page]
51 redirect_to action: 'index', page: params[:page]
51 end
52 end
52
53
53 def create_from_list
54 def create_from_list
54 lines = params[:user_list]
55 lines = params[:user_list]
55
56
56
57
57 res = User.create_from_list(lines)
58 res = User.create_from_list(lines)
58 error_logins = res[:error_logins]
59 error_logins = res[:error_logins]
59 error_msg = res[:first_error]
60 error_msg = res[:first_error]
60 ok_user = res[:created_users]
61 ok_user = res[:created_users]
61
62
62
63
63 #add to group
64 #add to group
64 if params[:add_to_group]
65 if params[:add_to_group]
65 group = Group.find_by(id: params[:group_id])&.add_users_skip_existing(ok_user)
66 group = Group.find_by(id: params[:group_id])&.add_users_skip_existing(ok_user)
66 end
67 end
67
68
68 # show flash
69 # show flash
69 if ok_user.count > 0
70 if ok_user.count > 0
70 flash[:success] = "#{ok_user.count} user(s) was created or updated successfully"
71 flash[:success] = "#{ok_user.count} user(s) was created or updated successfully"
71 end
72 end
72 if error_logins.size > 0
73 if error_logins.size > 0
73 flash[:error] = "Following user(s) failed to be created: " + error_logins.join(', ') + ". The error of the first failed one are: " + error_msg;
74 flash[:error] = "Following user(s) failed to be created: " + error_logins.join(', ') + ". The error of the first failed one are: " + error_msg;
74 end
75 end
75 redirect_to :action => 'index'
76 redirect_to :action => 'index'
76 end
77 end
77
78
78 def edit
79 def edit
79 @user = User.find(params[:id])
80 @user = User.find(params[:id])
80 end
81 end
81
82
82 def update
83 def update
83 @user = User.find(params[:id])
84 @user = User.find(params[:id])
84 if @user.update_attributes(user_params)
85 if @user.update_attributes(user_params)
85 flash[:notice] = 'User was successfully updated.'
86 flash[:notice] = 'User was successfully updated.'
86 redirect_to :action => 'show', :id => @user
87 redirect_to :action => 'show', :id => @user
87 else
88 else
88 render :action => 'edit'
89 render :action => 'edit'
89 end
90 end
90 end
91 end
91
92
92 def destroy
93 def destroy
93 User.find(params[:id]).destroy
94 User.find(params[:id]).destroy
94 redirect_to :action => 'index'
95 redirect_to :action => 'index'
95 end
96 end
96
97
97 def user_stat
98 def user_stat
98 if params[:commit] == 'download csv'
99 if params[:commit] == 'download csv'
99 @problems = Problem.all
100 @problems = Problem.all
100 else
101 else
101 @problems = Problem.available_problems
102 @problems = Problem.available_problems
102 end
103 end
103 @users = User.includes(:contests, :contest_stat).where(enabled: true)
104 @users = User.includes(:contests, :contest_stat).where(enabled: true)
104 @scorearray = Array.new
105 @scorearray = Array.new
105 @users.each do |u|
106 @users.each do |u|
106 ustat = Array.new
107 ustat = Array.new
107 ustat[0] = u
108 ustat[0] = u
108 @problems.each do |p|
109 @problems.each do |p|
109 sub = Submission.find_last_by_user_and_problem(u.id,p.id)
110 sub = Submission.find_last_by_user_and_problem(u.id,p.id)
110 if (sub!=nil) and (sub.points!=nil) and p and p.full_score
111 if (sub!=nil) and (sub.points!=nil) and p and p.full_score
111 ustat << [(sub.points.to_f*100/p.full_score).round, (sub.points>=p.full_score)]
112 ustat << [(sub.points.to_f*100/p.full_score).round, (sub.points>=p.full_score)]
112 else
113 else
113 ustat << [0,false]
114 ustat << [0,false]
114 end
115 end
115 end
116 end
116 @scorearray << ustat
117 @scorearray << ustat
117 end
118 end
118 if params[:commit] == 'download csv' then
119 if params[:commit] == 'download csv' then
119 csv = gen_csv_from_scorearray(@scorearray,@problems)
120 csv = gen_csv_from_scorearray(@scorearray,@problems)
120 send_data csv, filename: 'last_score.csv'
121 send_data csv, filename: 'last_score.csv'
121 else
122 else
122 render template: 'user_admin/user_stat'
123 render template: 'user_admin/user_stat'
123 end
124 end
124 end
125 end
125
126
126 def user_stat_max
127 def user_stat_max
127 if params[:commit] == 'download csv'
128 if params[:commit] == 'download csv'
128 @problems = Problem.all
129 @problems = Problem.all
129 else
130 else
130 @problems = Problem.available_problems
131 @problems = Problem.available_problems
131 end
132 end
132 @users = User.includes(:contests).includes(:contest_stat).all
133 @users = User.includes(:contests).includes(:contest_stat).all
133 @scorearray = Array.new
134 @scorearray = Array.new
134 #set up range from param
135 #set up range from param
135 since_id = params.fetch(:since_id, 0).to_i
136 since_id = params.fetch(:since_id, 0).to_i
136 until_id = params.fetch(:until_id, 0).to_i
137 until_id = params.fetch(:until_id, 0).to_i
137 @users.each do |u|
138 @users.each do |u|
138 ustat = Array.new
139 ustat = Array.new
139 ustat[0] = u
140 ustat[0] = u
140 @problems.each do |p|
141 @problems.each do |p|
141 max_points = 0
142 max_points = 0
142 Submission.find_in_range_by_user_and_problem(u.id,p.id,since_id,until_id).each do |sub|
143 Submission.find_in_range_by_user_and_problem(u.id,p.id,since_id,until_id).each do |sub|
143 max_points = sub.points if sub and sub.points and (sub.points > max_points)
144 max_points = sub.points if sub and sub.points and (sub.points > max_points)
144 end
145 end
145 ustat << [(max_points.to_f*100/p.full_score).round, (max_points>=p.full_score)]
146 ustat << [(max_points.to_f*100/p.full_score).round, (max_points>=p.full_score)]
146 end
147 end
147 @scorearray << ustat
148 @scorearray << ustat
148 end
149 end
149
150
150 if params[:commit] == 'download csv' then
151 if params[:commit] == 'download csv' then
151 csv = gen_csv_from_scorearray(@scorearray,@problems)
152 csv = gen_csv_from_scorearray(@scorearray,@problems)
152 send_data csv, filename: 'max_score.csv'
153 send_data csv, filename: 'max_score.csv'
153 else
154 else
154 render template: 'user_admin/user_stat'
155 render template: 'user_admin/user_stat'
155 end
156 end
156 end
157 end
157
158
158 def import
159 def import
159 if params[:file]==''
160 if params[:file]==''
160 flash[:notice] = 'Error importing no file'
161 flash[:notice] = 'Error importing no file'
161 redirect_to :action => 'index' and return
162 redirect_to :action => 'index' and return
162 end
163 end
163 import_from_file(params[:file])
164 import_from_file(params[:file])
164 end
165 end
165
166
166 def random_all_passwords
167 def random_all_passwords
167 users = User.all
168 users = User.all
168 @prefix = params[:prefix] || ''
169 @prefix = params[:prefix] || ''
169 @non_admin_users = User.find_non_admin_with_prefix(@prefix)
170 @non_admin_users = User.find_non_admin_with_prefix(@prefix)
170 @changed = false
171 @changed = false
171 if params[:commit] == 'Go ahead'
172 if params[:commit] == 'Go ahead'
172 @non_admin_users.each do |user|
173 @non_admin_users.each do |user|
173 password = random_password
174 password = random_password
174 user.password = password
175 user.password = password
175 user.password_confirmation = password
176 user.password_confirmation = password
176 user.save
177 user.save
177 end
178 end
178 @changed = true
179 @changed = true
179 end
180 end
180 end
181 end
181
182
182 # contest management
183 # contest management
183
184
184 def contests
185 def contests
185 @contest, @users = find_contest_and_user_from_contest_id(params[:id])
186 @contest, @users = find_contest_and_user_from_contest_id(params[:id])
186 @contests = Contest.enabled
187 @contests = Contest.enabled
187 end
188 end
188
189
189 def assign_from_list
190 def assign_from_list
190 contest_id = params[:users_contest_id]
191 contest_id = params[:users_contest_id]
191 org_contest, users = find_contest_and_user_from_contest_id(contest_id)
192 org_contest, users = find_contest_and_user_from_contest_id(contest_id)
192 contest = Contest.find(params[:new_contest][:id])
193 contest = Contest.find(params[:new_contest][:id])
193 if !contest
194 if !contest
194 flash[:notice] = 'Error: no contest'
195 flash[:notice] = 'Error: no contest'
195 redirect_to :action => 'contests', :id =>contest_id
196 redirect_to :action => 'contests', :id =>contest_id
196 end
197 end
197
198
198 note = []
199 note = []
199 users.each do |u|
200 users.each do |u|
200 u.contests = [contest]
201 u.contests = [contest]
201 note << u.login
202 note << u.login
202 end
203 end
203 flash[:notice] = 'User(s) ' + note.join(', ') +
204 flash[:notice] = 'User(s) ' + note.join(', ') +
204 " were successfully reassigned to #{contest.title}."
205 " were successfully reassigned to #{contest.title}."
205 redirect_to :action => 'contests', :id =>contest.id
206 redirect_to :action => 'contests', :id =>contest.id
206 end
207 end
207
208
208 def add_to_contest
209 def add_to_contest
209 user = User.find(params[:id])
210 user = User.find(params[:id])
210 contest = Contest.find(params[:contest_id])
211 contest = Contest.find(params[:contest_id])
211 if user and contest
212 if user and contest
212 user.contests << contest
213 user.contests << contest
213 end
214 end
214 redirect_to :action => 'index'
215 redirect_to :action => 'index'
215 end
216 end
216
217
217 def remove_from_contest
218 def remove_from_contest
218 user = User.find(params[:id])
219 user = User.find(params[:id])
219 contest = Contest.find(params[:contest_id])
220 contest = Contest.find(params[:contest_id])
220 if user and contest
221 if user and contest
221 user.contests.delete(contest)
222 user.contests.delete(contest)
222 end
223 end
223 redirect_to :action => 'index'
224 redirect_to :action => 'index'
224 end
225 end
225
226
226 def contest_management
227 def contest_management
227 end
228 end
228
229
229 def manage_contest
230 def manage_contest
@@ -1,56 +1,57
1 // Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails
1 // Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails
2 //import "@hotwired/turbo-rails"
2 //import "@hotwired/turbo-rails"
3 //import "controllers"
3 //import "controllers"
4 //
4 //
5
5
6
6
7 //bootstrap
7 //bootstrap
8 //import "bootstrap"
8 //import "bootstrap"
9 //window.bootstrap = bootstrap
9 //window.bootstrap = bootstrap
10
10
11 //datatable
11 //datatable
12 //import 'datatables-bundle'
12 //import 'datatables-bundle'
13 import "pdfmake"
13 import "pdfmake"
14 import "pdfmake-vfs"
14 import "pdfmake-vfs"
15 import "jszip"
15 import "jszip"
16 import "datatables"
16 import "datatables"
17 import "datatables-bs5"
17 import "datatables-bs5"
18 import "datatables-editor"
18 import "datatables-editor"
19 import "datatables-editor-bs5"
19 import "datatables-editor-bs5"
20 import "datatables-autofill"
20 import "datatables-autofill"
21 import "datatables-autofill-bs5"
21 import "datatables-autofill-bs5"
22 import "datatables-button"
22 import "datatables-button"
23 import "datatables-button-bs5"
23 import "datatables-button-bs5"
24 import "datatables-button-colvis"
24 import "datatables-button-colvis"
25 import "datatables-button-html5"
25 import "datatables-button-html5"
26 import "datatables-button-print"
26 import "datatables-button-print"
27 import "datatables-colrecorder"
27 import "datatables-colrecorder"
28 import "datatables-datetime"
28 import "datatables-datetime"
29 import "datatables-fixedcolumns"
29 import "datatables-fixedcolumns"
30 import "datatables-fixedheader"
30 import "datatables-fixedheader"
31 import "datatables-keytable"
31 import "datatables-keytable"
32 import "datatables-responsive"
32 import "datatables-responsive"
33 import "datatables-responsive-bs5"
33 import "datatables-responsive-bs5"
34 import "datatables-rowgroup"
34 import "datatables-rowgroup"
35 import "datatables-rowreorder"
35 import "datatables-rowreorder"
36 import "datatables-scroller"
36 import "datatables-scroller"
37 import "datatables-searchbuilder"
37 import "datatables-searchbuilder"
38 import "datatables-searchbuilder-bs5"
38 import "datatables-searchbuilder-bs5"
39 import "datatables-searchpanes"
39 import "datatables-searchpanes"
40 import "datatables-searchpanes-bs5"
40 import "datatables-searchpanes-bs5"
41 import "datatables-select"
41 import "datatables-select"
42 import "datatables-staterestore"
42 import "datatables-staterestore"
43 import "datatables-staterestore-bs5"
43 import "datatables-staterestore-bs5"
44 /* */
44 /* */
45
45
46 import "select2"
46 import "select2"
47 + import "chart"
47
48
48 //my own customization
49 //my own customization
49 import 'custom'
50 import 'custom'
50
51
51
52
52 //trigger import map ready
53 //trigger import map ready
53 console.log('application.js ready')
54 console.log('application.js ready')
54 window.importmapScriptsLoaded = true
55 window.importmapScriptsLoaded = true
55 const import_map_loaded = new CustomEvent('import-map-loaded', { });
56 const import_map_loaded = new CustomEvent('import-map-loaded', { });
56 document.dispatchEvent(import_map_loaded);
57 document.dispatchEvent(import_map_loaded);
@@ -1,48 +1,45
1 $(document).on('change', '.btn-file :file', function() {
1 $(document).on('change', '.btn-file :file', function() {
2 var input, label, numFiles;
2 var input, label, numFiles;
3 input = $(this);
3 input = $(this);
4 numFiles = input.get(0).files ? input.get(0).files.length : 1;
4 numFiles = input.get(0).files ? input.get(0).files.length : 1;
5 label = input.val().replace(/\\/g, '/').replace(/.*\//, '');
5 label = input.val().replace(/\\/g, '/').replace(/.*\//, '');
6 input.trigger('fileselect', [numFiles, label]);
6 input.trigger('fileselect', [numFiles, label]);
7 });
7 });
8
8
9 $(function() {
9 $(function() {
10 var e;
10 var e;
11 $(".select2").select2({
11 $(".select2").select2({
12 theme: "bootstrap-5",
12 theme: "bootstrap-5",
13 //selectionCssClass: "select2--small",
13 //selectionCssClass: "select2--small",
14 //dropdownCssClass: "select2--small",
14 //dropdownCssClass: "select2--small",
15 });
15 });
16
16
17 $('.btn-file :file').on('fileselect', function(event, numFiles, label) {
17 $('.btn-file :file').on('fileselect', function(event, numFiles, label) {
18 var input, log;
18 var input, log;
19 input = $(this).parents('.input-group').find(':text');
19 input = $(this).parents('.input-group').find(':text');
20 log = numFiles > 1 ? numFiles + ' files selected' : label;
20 log = numFiles > 1 ? numFiles + ' files selected' : label;
21 if (input.length) {
21 if (input.length) {
22 input.val(log);
22 input.val(log);
23 } else {
23 } else {
24 if (log) {
24 if (log) {
25 alert(log);
25 alert(log);
26 }
26 }
27 }
27 }
28 });
28 });
29 $('.ajax-toggle').on('click', function(event) {
29 $('.ajax-toggle').on('click', function(event) {
30 var target;
30 var target;
31 target = $(event.target);
31 target = $(event.target);
32 target.removeClass('btn-default');
32 target.removeClass('btn-default');
33 target.removeClass('btn-success');
33 target.removeClass('btn-success');
34 target.addClass('btn-warning');
34 target.addClass('btn-warning');
35 target.text('...');
35 target.text('...');
36 });
36 });
37 if ($("#editor").length > 0) {
37 if ($("#editor").length > 0) {
38 e = ace.edit("editor");
38 e = ace.edit("editor");
39 e.setTheme('ace/theme/merbivore');
39 e.setTheme('ace/theme/merbivore');
40 e.getSession().setTabSize(2);
40 e.getSession().setTabSize(2);
41 e.getSession().setUseSoftTabs(true);
41 e.getSession().setUseSoftTabs(true);
42 }
42 }
43
43
44 //jQuery(".best_in_place").best_in_place();
44 //jQuery(".best_in_place").best_in_place();
45 });
45 });
46 -
47 - // ---
48 - // generated by coffee-script 1.9.2
@@ -1,245 +1,245
1 require 'digest/sha1'
1 require 'digest/sha1'
2 require 'net/pop'
2 require 'net/pop'
3 require 'net/https'
3 require 'net/https'
4 require 'net/http'
4 require 'net/http'
5 require 'json'
5 require 'json'
6
6
7 class User < ActiveRecord::Base
7 class User < ActiveRecord::Base
8
8
9 has_and_belongs_to_many :roles
9 has_and_belongs_to_many :roles
10
10
11 #has_and_belongs_to_many :groups
11 #has_and_belongs_to_many :groups
12 has_many :groups_users, class_name: 'GroupUser'
12 has_many :groups_users, class_name: 'GroupUser'
13 has_many :groups, :through => :groups_users
13 has_many :groups, :through => :groups_users
14
14
15 has_many :test_requests, -> {order(submitted_at: :desc)}
15 has_many :test_requests, -> {order(submitted_at: :desc)}
16
16
17 has_many :messages, -> { order(created_at: :desc) },
17 has_many :messages, -> { order(created_at: :desc) },
18 :class_name => "Message",
18 :class_name => "Message",
19 :foreign_key => "sender_id"
19 :foreign_key => "sender_id"
20
20
21 has_many :replied_messages, -> { order(created_at: :desc) },
21 has_many :replied_messages, -> { order(created_at: :desc) },
22 :class_name => "Message",
22 :class_name => "Message",
23 :foreign_key => "receiver_id"
23 :foreign_key => "receiver_id"
24
24
25 has_many :logins
25 has_many :logins
26
26
27 has_one :contest_stat, :class_name => "UserContestStat", :dependent => :destroy
27 has_one :contest_stat, :class_name => "UserContestStat", :dependent => :destroy
28
28
29 - belongs_to :site
29 + belongs_to :site, optional: true
30 - belongs_to :country
30 + belongs_to :country, optional: true
31
31
32 has_and_belongs_to_many :contests, -> { order(:name)}
32 has_and_belongs_to_many :contests, -> { order(:name)}
33
33
34 scope :activated_users, -> {where activated: true}
34 scope :activated_users, -> {where activated: true}
35
35
36 validates_presence_of :login
36 validates_presence_of :login
37 validates_uniqueness_of :login
37 validates_uniqueness_of :login
38 validates_format_of :login, :with => /\A[\_A-Za-z0-9]+\z/
38 validates_format_of :login, :with => /\A[\_A-Za-z0-9]+\z/
39 validates_length_of :login, :within => 3..30
39 validates_length_of :login, :within => 3..30
40
40
41 validates_presence_of :full_name
41 validates_presence_of :full_name
42 validates_length_of :full_name, :minimum => 1
42 validates_length_of :full_name, :minimum => 1
43
43
44 validates_presence_of :password, :if => :password_required?
44 validates_presence_of :password, :if => :password_required?
45 validates_length_of :password, :within => 4..50, :if => :password_required?
45 validates_length_of :password, :within => 4..50, :if => :password_required?
46 validates_confirmation_of :password, :if => :password_required?
46 validates_confirmation_of :password, :if => :password_required?
47
47
48 - validates_format_of :email,
48 + validates_format_of :email,
49 - :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i,
49 + :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i,
50 :if => :email_validation?
50 :if => :email_validation?
51 - validate :uniqueness_of_email_from_activated_users,
51 + validate :uniqueness_of_email_from_activated_users,
52 :if => :email_validation?
52 :if => :email_validation?
53 - validate :enough_time_interval_between_same_email_registrations,
53 + validate :enough_time_interval_between_same_email_registrations,
54 :if => :email_validation?
54 :if => :email_validation?
55
55
56 # these are for ytopc
56 # these are for ytopc
57 # disable for now
57 # disable for now
58 #validates_presence_of :province
58 #validates_presence_of :province
59
59
60 attr_accessor :password
60 attr_accessor :password
61
61
62 before_save :encrypt_new_password
62 before_save :encrypt_new_password
63 before_save :assign_default_site
63 before_save :assign_default_site
64 before_save :assign_default_contest
64 before_save :assign_default_contest
65
65
66 # this is for will_paginate
66 # this is for will_paginate
67 cattr_reader :per_page
67 cattr_reader :per_page
68 @@per_page = 50
68 @@per_page = 50
69
69
70 def self.authenticate(login, password)
70 def self.authenticate(login, password)
71 user = find_by_login(login)
71 user = find_by_login(login)
72 if user
72 if user
73 return user if user.authenticated?(password)
73 return user if user.authenticated?(password)
74 end
74 end
75 end
75 end
76
76
77 def authenticated?(password)
77 def authenticated?(password)
78 if self.activated
78 if self.activated
79 hashed_password == User.encrypt(password,self.salt)
79 hashed_password == User.encrypt(password,self.salt)
80 else
80 else
81 false
81 false
82 end
82 end
83 end
83 end
84
84
85 def login_with_name
85 def login_with_name
86 "[#{login}] #{full_name}"
86 "[#{login}] #{full_name}"
87 end
87 end
88
88
89 def admin?
89 def admin?
90 has_role?('admin')
90 has_role?('admin')
91 end
91 end
92
92
93 def has_role?(role)
93 def has_role?(role)
94 self.roles.where(name: [role,'admin']).count > 0
94 self.roles.where(name: [role,'admin']).count > 0
95 end
95 end
96
96
97 def email_for_editing
97 def email_for_editing
98 if self.email==nil
98 if self.email==nil
99 "(unknown)"
99 "(unknown)"
100 elsif self.email==''
100 elsif self.email==''
101 "(blank)"
101 "(blank)"
102 else
102 else
103 self.email
103 self.email
104 end
104 end
105 end
105 end
106
106
107 def email_for_editing=(e)
107 def email_for_editing=(e)
108 self.email=e
108 self.email=e
109 end
109 end
110
110
111 def alias_for_editing
111 def alias_for_editing
112 if self.alias==nil
112 if self.alias==nil
113 "(unknown)"
113 "(unknown)"
114 elsif self.alias==''
114 elsif self.alias==''
115 "(blank)"
115 "(blank)"
116 else
116 else
117 self.alias
117 self.alias
118 end
118 end
119 end
119 end
120
120
121 def alias_for_editing=(e)
121 def alias_for_editing=(e)
122 self.alias=e
122 self.alias=e
123 end
123 end
124
124
125 def activation_key
125 def activation_key
126 if self.hashed_password==nil
126 if self.hashed_password==nil
127 encrypt_new_password
127 encrypt_new_password
128 end
128 end
129 Digest::SHA1.hexdigest(self.hashed_password)[0..7]
129 Digest::SHA1.hexdigest(self.hashed_password)[0..7]
130 end
130 end
131
131
132 def verify_activation_key(key)
132 def verify_activation_key(key)
133 key == activation_key
133 key == activation_key
134 end
134 end
135
135
136 def self.random_password(length=5)
136 def self.random_password(length=5)
137 chars = 'abcdefghjkmnopqrstuvwxyz'
137 chars = 'abcdefghjkmnopqrstuvwxyz'
138 password = ''
138 password = ''
139 length.times { password << chars[rand(chars.length - 1)] }
139 length.times { password << chars[rand(chars.length - 1)] }
140 password
140 password
141 end
141 end
142
142
143
143
144 # Contest information
144 # Contest information
145
145
146 def self.find_users_with_no_contest()
146 def self.find_users_with_no_contest()
147 users = User.all
147 users = User.all
148 return users.find_all { |u| u.contests.length == 0 }
148 return users.find_all { |u| u.contests.length == 0 }
149 end
149 end
150
150
151
151
152 def contest_time_left
152 def contest_time_left
153 if GraderConfiguration.contest_mode?
153 if GraderConfiguration.contest_mode?
154 return nil if site==nil
154 return nil if site==nil
155 return site.time_left
155 return site.time_left
156 elsif GraderConfiguration.indv_contest_mode?
156 elsif GraderConfiguration.indv_contest_mode?
157 time_limit = GraderConfiguration.contest_time_limit
157 time_limit = GraderConfiguration.contest_time_limit
158 if time_limit == nil
158 if time_limit == nil
159 return nil
159 return nil
160 end
160 end
161 if contest_stat==nil or contest_stat.started_at==nil
161 if contest_stat==nil or contest_stat.started_at==nil
162 return (Time.now.gmtime + time_limit) - Time.now.gmtime
162 return (Time.now.gmtime + time_limit) - Time.now.gmtime
163 else
163 else
164 finish_time = contest_stat.started_at + time_limit
164 finish_time = contest_stat.started_at + time_limit
165 current_time = Time.now.gmtime
165 current_time = Time.now.gmtime
166 if current_time > finish_time
166 if current_time > finish_time
167 return 0
167 return 0
168 else
168 else
169 return finish_time - current_time
169 return finish_time - current_time
170 end
170 end
171 end
171 end
172 else
172 else
173 return nil
173 return nil
174 end
174 end
175 end
175 end
176
176
177 def contest_finished?
177 def contest_finished?
178 if GraderConfiguration.contest_mode?
178 if GraderConfiguration.contest_mode?
179 return false if site==nil
179 return false if site==nil
180 return site.finished?
180 return site.finished?
181 elsif GraderConfiguration.indv_contest_mode?
181 elsif GraderConfiguration.indv_contest_mode?
182 return false if self.contest_stat==nil
182 return false if self.contest_stat==nil
183 return contest_time_left == 0
183 return contest_time_left == 0
184 else
184 else
185 return false
185 return false
186 end
186 end
187 end
187 end
188
188
189 def contest_started?
189 def contest_started?
190 if GraderConfiguration.indv_contest_mode?
190 if GraderConfiguration.indv_contest_mode?
191 stat = self.contest_stat
191 stat = self.contest_stat
192 return ((stat != nil) and (stat.started_at != nil))
192 return ((stat != nil) and (stat.started_at != nil))
193 elsif GraderConfiguration.contest_mode?
193 elsif GraderConfiguration.contest_mode?
194 return true if site==nil
194 return true if site==nil
195 return site.started
195 return site.started
196 else
196 else
197 return true
197 return true
198 end
198 end
199 end
199 end
200
200
201 def update_start_time
201 def update_start_time
202 stat = self.contest_stat
202 stat = self.contest_stat
203 if stat.nil? or stat.started_at.nil?
203 if stat.nil? or stat.started_at.nil?
204 stat ||= UserContestStat.new(:user => self)
204 stat ||= UserContestStat.new(:user => self)
205 stat.started_at = Time.now.gmtime
205 stat.started_at = Time.now.gmtime
206 stat.save
206 stat.save
207 end
207 end
208 end
208 end
209
209
210 def problem_in_user_contests?(problem)
210 def problem_in_user_contests?(problem)
211 problem_contests = problem.contests.all
211 problem_contests = problem.contests.all
212
212
213 if problem_contests.length == 0 # this is public contest
213 if problem_contests.length == 0 # this is public contest
214 return true
214 return true
215 end
215 end
216
216
217 contests.each do |contest|
217 contests.each do |contest|
218 if problem_contests.find {|c| c.id == contest.id }
218 if problem_contests.find {|c| c.id == contest.id }
219 return true
219 return true
220 end
220 end
221 end
221 end
222 return false
222 return false
223 end
223 end
224
224
225 def available_problems_group_by_contests
225 def available_problems_group_by_contests
226 contest_problems = []
226 contest_problems = []
227 pin = {}
227 pin = {}
228 contests.enabled.each do |contest|
228 contests.enabled.each do |contest|
229 available_problems = contest.problems.available
229 available_problems = contest.problems.available
230 contest_problems << {
230 contest_problems << {
231 :contest => contest,
231 :contest => contest,
232 :problems => available_problems
232 :problems => available_problems
233 }
233 }
234 available_problems.each {|p| pin[p.id] = true}
234 available_problems.each {|p| pin[p.id] = true}
235 end
235 end
236 other_avaiable_problems = Problem.available.find_all {|p| pin[p.id]==nil and p.contests.length==0}
236 other_avaiable_problems = Problem.available.find_all {|p| pin[p.id]==nil and p.contests.length==0}
237 contest_problems << {
237 contest_problems << {
238 :contest => nil,
238 :contest => nil,
239 :problems => other_avaiable_problems
239 :problems => other_avaiable_problems
240 }
240 }
241 return contest_problems
241 return contest_problems
242 end
242 end
243
243
244 def solve_all_available_problems?
244 def solve_all_available_problems?
245 available_problems.each do |p|
245 available_problems.each do |p|
@@ -1,82 +1,82
1 - content_for :head do
1 - content_for :head do
2 <meta http-equiv ="refresh" content="60"/>
2 <meta http-equiv ="refresh" content="60"/>
3
3
4 %h1 Grader information
4 %h1 Grader information
5
5
6 %p
6 %p
7 = link_to 'Refresh', { :action => 'list' }, class: 'btn btn-info'
7 = link_to 'Refresh', { :action => 'list' }, class: 'btn btn-info'
8
8
9 .panel.panel-primary
9 .panel.panel-primary
10 .panel-heading
10 .panel-heading
11 Grader control:
11 Grader control:
12 .panel-body
12 .panel-body
13 - =link_to 'Start Graders in grading env', { action: 'start_grading'}, class: 'btn btn-default'
13 + =link_to 'Start Graders in grading env', { action: 'start_grading'}, class: 'btn btn-secondary'
14 - =link_to 'Start Graders in exam env', { action: 'start_exam'}, class: 'btn btn-default'
14 + =link_to 'Start Graders in exam env', { action: 'start_exam'}, class: 'btn btn-secondary'
15 - =link_to 'Stop all running Graders', { action: 'stop_all'}, class: 'btn btn-default'
15 + =link_to 'Stop all running Graders', { action: 'stop_all'}, class: 'btn btn-secondary'
16 - =link_to 'Clear all data', { action: 'clear_all'}, class: 'btn btn-default'
16 + =link_to 'Clear all data', { action: 'clear_all'}, class: 'btn btn-secondary'
17
17
18 .row
18 .row
19 .col-md-6
19 .col-md-6
20 - if @last_task
20 - if @last_task
21 Last task:
21 Last task:
22 = link_to "#{@last_task.id}", :action => 'view', :id => @last_task.id, :type => 'Task'
22 = link_to "#{@last_task.id}", :action => 'view', :id => @last_task.id, :type => 'Task'
23
23
24 %br/
24 %br/
25
25
26 - if @last_test_request
26 - if @last_test_request
27 Last test_request:
27 Last test_request:
28 = link_to "#{@last_test_request.id}", :action => 'view', :id => @last_test_request.id, :type => 'TestRequest'
28 = link_to "#{@last_test_request.id}", :action => 'view', :id => @last_test_request.id, :type => 'TestRequest'
29
29
30 %h2 Current graders
30 %h2 Current graders
31
31
32 = render :partial => 'grader_list', :locals => {:grader_list => @grader_processes}
32 = render :partial => 'grader_list', :locals => {:grader_list => @grader_processes}
33
33
34 %h2 Stalled graders
34 %h2 Stalled graders
35
35
36 = render :partial => 'grader_list', :locals => {:grader_list => @stalled_processes}
36 = render :partial => 'grader_list', :locals => {:grader_list => @stalled_processes}
37
37
38 %h2 Terminated graders
38 %h2 Terminated graders
39
39
40 %p= link_to 'Clear data for terminated graders', { action: 'clear_terminated'}, class: 'btn btn-default', method: 'post'
40 %p= link_to 'Clear data for terminated graders', { action: 'clear_terminated'}, class: 'btn btn-default', method: 'post'
41
41
42 = render :partial => 'grader_list', :locals => {:grader_list => @terminated_processes}
42 = render :partial => 'grader_list', :locals => {:grader_list => @terminated_processes}
43 .col-md-6
43 .col-md-6
44 %h2 Last 20 submissions
44 %h2 Last 20 submissions
45 %table.table.table-striped.table-condensed
45 %table.table.table-striped.table-condensed
46 %thead
46 %thead
47 %th ID
47 %th ID
48 %th User
48 %th User
49 %th Problem
49 %th Problem
50 %th Submitted
50 %th Submitted
51 %th Graded
51 %th Graded
52 %th Result
52 %th Result
53 %tbody
53 %tbody
54 - @submission.each do |sub|
54 - @submission.each do |sub|
55 %tr.inactive
55 %tr.inactive
56 %td= link_to sub.id, submission_path(sub.id)
56 %td= link_to sub.id, submission_path(sub.id)
57 %td= ("" unless sub.user) || link_to(sub.try(:user).try(:full_name), stat_user_path(sub.user.id))
57 %td= ("" unless sub.user) || link_to(sub.try(:user).try(:full_name), stat_user_path(sub.user.id))
58 %td= ("" unless sub.problem) || link_to(sub.try(:problem).try(:full_name), stat_problem_path(sub.problem.id))
58 %td= ("" unless sub.problem) || link_to(sub.try(:problem).try(:full_name), stat_problem_path(sub.problem.id))
59 %td= "#{time_ago_in_words(sub.submitted_at)} ago"
59 %td= "#{time_ago_in_words(sub.submitted_at)} ago"
60 %td= sub.graded_at ? "#{time_ago_in_words(sub.graded_at)} ago" : " "
60 %td= sub.graded_at ? "#{time_ago_in_words(sub.graded_at)} ago" : " "
61 %td= sub.grader_comment
61 %td= sub.grader_comment
62 %h2 Ungraded submission
62 %h2 Ungraded submission
63 %table.table.table-striped.table-condensed
63 %table.table.table-striped.table-condensed
64 %thead
64 %thead
65 %th ID
65 %th ID
66 %th User
66 %th User
67 %th Problem
67 %th Problem
68 %th Submitted
68 %th Submitted
69 %th Graded
69 %th Graded
70 %th Result
70 %th Result
71 %tbody
71 %tbody
72 - @backlog_submission.each do |sub|
72 - @backlog_submission.each do |sub|
73 %tr.inactive
73 %tr.inactive
74 %td= link_to sub.id, submission_path(sub.id)
74 %td= link_to sub.id, submission_path(sub.id)
75 %td= ("" unless sub.user) || link_to( sub.try(:user).try(:full_name), stat_user_path(sub.user.id))
75 %td= ("" unless sub.user) || link_to( sub.try(:user).try(:full_name), stat_user_path(sub.user.id))
76 %td= ("" unless sub.problem) || link_to( sub.try(:problem).try(:full_name), stat_problem_path(sub.problem.id))
76 %td= ("" unless sub.problem) || link_to( sub.try(:problem).try(:full_name), stat_problem_path(sub.problem.id))
77 %td= "#{time_ago_in_words(sub.submitted_at)} ago"
77 %td= "#{time_ago_in_words(sub.submitted_at)} ago"
78 %td= sub.graded_at ? "#{time_ago_in_words(sub.graded_at)} ago" : " "
78 %td= sub.graded_at ? "#{time_ago_in_words(sub.graded_at)} ago" : " "
79 %td= sub.grader_comment
79 %td= sub.grader_comment
80
80
81
81
82
82
@@ -1,27 +1,7
1 - = form_for @group do |f|
1 + .row
2 - - if @group.errors.any?
2 + .col-md-6
3 - #error_explanation
3 + = simple_form_for @group do |f|
4 - %h2= "#{pluralize(@group.errors.count, "error")} prohibited this group from being saved:"
4 + = f.input :name
5 - %ul
5 + = f.input :description
6 - - @group.errors.full_messages.each do |msg|
6 + = f.input :enabled
7 - %li= msg
7 + = f.button :submit, class: 'btn btn-primary'
8 - .row
9 - .col-md-6
10 - .form-group.field
11 - = f.label :name
12 - = f.text_field :name, class: 'form-control'
13 - .row
14 - .col-md-6
15 - .form-group.field
16 - = f.label :description
17 - = f.text_field :description, class: 'form-control'
18 - .row
19 - .col-md-6
20 - .checkbox
21 - = f.label :enabled do
22 - = f.check_box :enabled
23 - Enabled
24 - .row
25 - .col-md-6
26 - .form-group.actions
27 - = f.submit 'Save', class: 'btn btn-primary'
@@ -1,7 +1,9
1 %h1 Editing group
1 %h1 Editing group
2
2
3 = render 'form'
3 = render 'form'
4
4
5 - = link_to 'Show', @group
5 + .row.my-3
6 - \|
6 + .col-auto
7 - = link_to 'Back', groups_path
7 + = link_to 'Edit members and problems', @group, class: 'btn btn-info'
8 + .col-auto
9 + = link_to 'Back', groups_path, class: 'btn btn-secondary'
@@ -1,24 +1,23
1 %h1 Groups
1 %h1 Groups
2
2
3 %p
3 %p
4 - = link_to 'New Group', new_group_path, class: 'btn btn-primary'
4 + = link_to 'New Group', new_group_path, class: 'btn btn-success'
5 %table.table.table-hover
5 %table.table.table-hover
6 %thead
6 %thead
7 %tr
7 %tr
8 %th Name
8 %th Name
9 %th Description
9 %th Description
10 %th Enabled?
10 %th Enabled?
11 %th
11 %th
12 - %th
13 -
14 %tbody
12 %tbody
15 - @groups.each do |group|
13 - @groups.each do |group|
16 %tr{:class => "#{(group.enabled?) ? "success" : "danger"}", id: "group-#{group.id}"}
14 %tr{:class => "#{(group.enabled?) ? "success" : "danger"}", id: "group-#{group.id}"}
17 %td= group.name
15 %td= group.name
18 %td= group.description
16 %td= group.description
19 - %td= toggle_button(group.enabled?, toggle_group_path(group), "group-enabled-#{group.id}", size: ' ', block: ' ')
17 + %td= toggle_button(group.enabled?, toggle_group_path(group), "group-enabled-#{group.id}", block: ' ')
20 - %td= link_to 'View', group, class: 'btn btn-default'
18 + %td
21 - %td= link_to 'Destroy', group, :method => :delete, :data => { :confirm => 'Are you sure?' }, class: 'btn btn-danger'
19 + = link_to 'Edit members and problems', group, class: 'btn btn-secondary btn-sm'
20 + = link_to 'Destroy', group, :method => :delete, :data => { :confirm => 'Are you sure?' }, class: 'btn btn-danger btn-sm'
22
21
23 %br
22 %br
24
23
@@ -1,82 +1,92
1 - .container-fluid
1 + .row.mb-3
2 - .row
2 + .col-md-6
3 - .col-md-6
3 + %h1 Editing Group members and problems
4 - %h1 Group #{@group.name}
4 + .row.mb-3
5 - .row
5 + .col-md-6
6 - .col-md-6
6 + %b Name:
7 - %b Description:
7 + = @group.name
8 - = @group.description
8 + .row.mb-3
9 - %br
9 + .col-md-6
10 - = link_to 'Edit', edit_group_path(@group), class: 'btn btn-primary'
10 + %b Description:
11 - .row
11 + = @group.description
12 - .col-md-12
12 + .row.mb-3
13 - %h1 Group details
13 + .col-md-6
14 - .row
14 + = link_to 'Edit', edit_group_path(@group), class: 'btn btn-primary'
15 - .col-md-6
15 + .row.mb-3
16 - .panel.panel-default
16 + .col-md-12
17 - .panel-heading
17 + %h1 Group details
18 - .panel-title Users in this group
18 + .row
19 - .panel-body
19 + .col-md-6
20 - %ul
20 + .card
21 - %li
21 + .card-header
22 - If you want to add several users to a group, it may be easier to just re-import those users in
22 + Users in this group
23 - = link_to 'New list of users', new_list_user_admin_index_path
23 + .card-body
24 - page. You can also use
24 + %ul
25 - = link_to 'Bulk Manage User', bulk_manage_user_admin_index_path
25 + %li
26 - page.
26 + If you want to add several users to a group, it may be easier to just re-import those users in
27 - =form_tag add_user_group_path(@group), class: 'form-inline' do
27 + = link_to 'New list of users', new_list_user_admin_index_path
28 - .form-group
28 + page. You can also use
29 - =label_tag :user_id, "User"
29 + = link_to 'Bulk Manage User', bulk_manage_user_admin_index_path
30 + page.
31 + =form_tag add_user_group_path(@group), class: 'form-inline' do
32 + .row
33 + .col-auto
34 + =label_tag :user_id, "User", class: 'col-form-label'
35 + .col-auto
30 =select_tag :user_id, options_from_collection_for_select(User.all,'id','login_with_name'), class: 'select2', style: 'width: 25em';
36 =select_tag :user_id, options_from_collection_for_select(User.all,'id','login_with_name'), class: 'select2', style: 'width: 25em';
31 - =submit_tag "Add",class: 'btn btn-primary'
37 + .col-auto
38 + =submit_tag "Add",class: 'btn btn-primary'
32
39
33
40
34 - %table.table.table-hover
41 + %table.table.table-hover
35 - %thead
42 + %thead
36 - %tr
43 + %tr
37 - %th Login
44 + %th Login
38 - %th Full name
45 + %th Full name
39 - %th Remark
46 + %th Remark
40 - %th= link_to 'Remove All', remove_all_user_group_path(@group), method: :delete, :data => { :confirm => "Remove ALL USERS from group?" }, class: 'btn btn-danger btn-sm'
47 + %th= link_to 'Remove All', remove_all_user_group_path(@group), method: :delete, :data => { :confirm => "Remove ALL USERS from group?" }, class: 'btn btn-danger btn-sm'
41
48
42 - %tbody
49 + %tbody
43 - - @group.users.each do |user|
50 + - @group.users.each do |user|
44 - %tr
51 + %tr
45 - %td= user.login
52 + %td= user.login
46 - %td= user.full_name
53 + %td= user.full_name
47 - %td= user.remark
54 + %td= user.remark
48 - %td= link_to 'Remove', remove_user_group_path(@group,user), :method => :delete, :data => { :confirm => "Remove #{user.full_name}?" }, class: 'btn btn-danger btn-sm'
55 + %td= link_to 'Remove', remove_user_group_path(@group,user), :method => :delete, :data => { :confirm => "Remove #{user.full_name}?" }, class: 'btn btn-danger btn-sm'
49 - .col-md-6
56 + .col-md-6
50 - .panel.panel-default
57 + .card
51 - .panel-heading
58 + .card-header
52 - .panel-title Problems
59 + Problems
53 - .panel-body
60 + .card-body
54 - %ul
61 + %ul
55 - %li
62 + %li
56 - If you want to add several problem to a group, it may be easier to bulk manage them in the
63 + If you want to add several problem to a group, it may be easier to bulk manage them in the
57 - = link_to 'Bulk Manage Problems', manage_problems_path
64 + = link_to 'Bulk Manage Problems', manage_problems_path
58 - page
65 + page
59 - =form_tag add_problem_group_path(@group), class: 'form-inline' do
66 + =form_tag add_problem_group_path(@group) do
60 - .form-group
67 + .row
61 - =label_tag :problem_id, "Problem"
68 + .col-auto
69 + =label_tag :problem_id, "Problem",class: 'col-form-label'
70 + .col-auto
62 =select_tag :problem_id, options_from_collection_for_select(Problem.all,'id','long_name'), class: 'select2', style: 'width: 25em';
71 =select_tag :problem_id, options_from_collection_for_select(Problem.all,'id','long_name'), class: 'select2', style: 'width: 25em';
63 - =submit_tag "Add",class: 'btn btn-primary'
72 + .col-auto
73 + =submit_tag "Add",class: 'btn btn-primary'
64
74
65
75
66 - %table.table.table-hover
76 + %table.table.table-hover
67 - %thead
77 + %thead
68 - %tr
78 + %tr
69 - %th name
79 + %th name
70 - %th Full name
80 + %th Full name
71 - %th Full score
81 + %th Full score
72 - %th= link_to 'Remove All', remove_all_problem_group_path(@group), method: :delete, :data => { :confirm => "Remove ALL PROBLEMS from group?" }, class: 'btn btn-danger btn-sm'
82 + %th= link_to 'Remove All', remove_all_problem_group_path(@group), method: :delete, :data => { :confirm => "Remove ALL PROBLEMS from group?" }, class: 'btn btn-danger btn-sm'
73
83
74 - %tbody
84 + %tbody
75 - - @group.problems.each do |problem|
85 + - @group.problems.each do |problem|
76 - %tr
86 + %tr
77 - %td= problem.name
87 + %td= problem.name
78 - %td= problem.full_name
88 + %td= problem.full_name
79 - %td= problem.full_score
89 + %td= problem.full_score
80 - %td= link_to 'Remove', remove_problem_group_path(@group,problem), :method => :delete, :data => { :confirm => "Remove #{problem.full_name}?" }, class: 'btn btn-danger btn-sm'
90 + %td= link_to 'Remove', remove_problem_group_path(@group,problem), :method => :delete, :data => { :confirm => "Remove #{problem.full_name}?" }, class: 'btn btn-danger btn-sm'
81
91
82
92
@@ -1,155 +1,156
1 :css
1 :css
2 .hof_user { color: orangered; font-style: italic; }
2 .hof_user { color: orangered; font-style: italic; }
3 .hof_language { color: green; font-style: italic; }
3 .hof_language { color: green; font-style: italic; }
4 .hof_value { color: deeppink;font-style: italic; }
4 .hof_value { color: deeppink;font-style: italic; }
5 .info_param { font-weight: bold;text-align: right; }
5 .info_param { font-weight: bold;text-align: right; }
6 .tooltip {
6 .tooltip {
7 font-family: Verdana,sans-serif;
7 font-family: Verdana,sans-serif;
8 font-weight: normal;
8 font-weight: normal;
9 text-align: left;
9 text-align: left;
10 font-size: 1.0em;
10 font-size: 1.0em;
11 color: black;
11 color: black;
12 line-height: 1.1;
12 line-height: 1.1;
13 display: none;
13 display: none;
14 min-width: 20em;
14 min-width: 20em;
15 position: absolute;
15 position: absolute;
16 left: 25px;
16 left: 25px;
17 bottom: 5px;
17 bottom: 5px;
18 border: 1px solid;
18 border: 1px solid;
19 padding: 5px;
19 padding: 5px;
20 background-color: #FFF;
20 background-color: #FFF;
21 word-wrap: break-word;
21 word-wrap: break-word;
22 z-index: 9999;
22 z-index: 9999;
23 overflow: auto;
23 overflow: auto;
24 }
24 }
25
25
26
26
27 .row.mb-3
27 .row.mb-3
28 .col-md-8
28 .col-md-8
29 .card
29 .card
30 .card-body
30 .card-body
31 %h2.card-title Submission History
31 %h2.card-title Submission History
32 %canvas#chart{height: '50px'}
32 %canvas#chart{height: '50px'}
33
33
34 .col-md-4
34 .col-md-4
35 .card
35 .card
36 .card-body
36 .card-body
37 %h2.card-title General Info
37 %h2.card-title General Info
38 .row
38 .row
39 .col-sm-6
39 .col-sm-6
40 Subs
40 Subs
41 .col-sm-6
41 .col-sm-6
42 = @summary[:count]
42 = @summary[:count]
43 .row
43 .row
44 .col-sm-6
44 .col-sm-6
45 Solved/Attempted User
45 Solved/Attempted User
46 .col-sm-6
46 .col-sm-6
47 #{@summary[:solve]}/#{@summary[:attempt]} (#{(@summary[:solve]*100.0/@summary[:attempt]).round(1)}%)
47 #{@summary[:solve]}/#{@summary[:attempt]} (#{(@summary[:solve]*100.0/@summary[:attempt]).round(1)}%)
48 .row.mb-3
48 .row.mb-3
49 .col-md-4
49 .col-md-4
50 .card
50 .card
51 .card-body
51 .card-body
52 %h2.card-title Model submission
52 %h2.card-title Model submission
53 %table.table.table-hover
53 %table.table.table-hover
54 %thead
54 %thead
55 %tr
55 %tr
56 %th #Sub (lang)
56 %th #Sub (lang)
57 %th Author
57 %th Author
58 %tbody
58 %tbody
59 - @model_subs.each do |sub|
59 - @model_subs.each do |sub|
60 %tr
60 %tr
61 %td
61 %td
62 = link_to "##{sub.id}", submission_path(sub)
62 = link_to "##{sub.id}", submission_path(sub)
63 = "(#{sub.language.pretty_name})"
63 = "(#{sub.language.pretty_name})"
64 %td= sub.user.full_name
64 %td= sub.user.full_name
65 .col-md-8
65 .col-md-8
66 - if @best
66 - if @best
67 .card
67 .card
68 .card-body
68 .card-body
69 %h2.card-title Top Submissions
69 %h2.card-title Top Submissions
70 %table.table.table-hover
70 %table.table.table-hover
71 %thead
71 %thead
72 %tr
72 %tr
73 %th Language
73 %th Language
74 %th Best runtime (ms)
74 %th Best runtime (ms)
75 %th Best memory (kbytes)
75 %th Best memory (kbytes)
76 %th Shortest Code (bytes)
76 %th Shortest Code (bytes)
77 %th First solver
77 %th First solver
78 %tbody
78 %tbody
79 %tr.bg-warning
79 %tr.bg-warning
80 %td
80 %td
81 Overall
81 Overall
82 %td
82 %td
83 by #{link_to @best[:runtime][:user], stat_user_path(@best[:runtime][:user_id])}
83 by #{link_to @best[:runtime][:user], stat_user_path(@best[:runtime][:user_id])}
84 %br
84 %br
85 using <span class="text-success">#{@best[:runtime][:lang]}</span>
85 using <span class="text-success">#{@best[:runtime][:lang]}</span>
86 %br
86 %br
87 with <span class="text-success">#{@best[:runtime][:value] * 1000} milliseconds</span>
87 with <span class="text-success">#{@best[:runtime][:value] * 1000} milliseconds</span>
88 %br= link_to "#" + @best[:runtime][:sub_id].to_s, submission_path(@best[:runtime][:sub_id])
88 %br= link_to "#" + @best[:runtime][:sub_id].to_s, submission_path(@best[:runtime][:sub_id])
89 %td
89 %td
90 by #{link_to @best[:memory][:user], stat_user_path(@best[:memory][:user_id])}
90 by #{link_to @best[:memory][:user], stat_user_path(@best[:memory][:user_id])}
91 %br
91 %br
92 using <span class="text-success">#{@best[:memory][:lang]}</span>
92 using <span class="text-success">#{@best[:memory][:lang]}</span>
93 %br
93 %br
94 with <span class="text-success">#{number_with_delimiter(@best[:memory][:value])} kbytes </span>
94 with <span class="text-success">#{number_with_delimiter(@best[:memory][:value])} kbytes </span>
95 %br= link_to "#" + @best[:memory][:sub_id].to_s, submission_path(@best[:memory][:sub_id])
95 %br= link_to "#" + @best[:memory][:sub_id].to_s, submission_path(@best[:memory][:sub_id])
96 %td
96 %td
97 by #{link_to @best[:length][:user], stat_user_path(@best[:length][:user_id])}
97 by #{link_to @best[:length][:user], stat_user_path(@best[:length][:user_id])}
98 %br
98 %br
99 using <span class="text-success">#{@best[:length][:lang]}</span>
99 using <span class="text-success">#{@best[:length][:lang]}</span>
100 %br
100 %br
101 with <span class="text-success">#{@best[:length][:value]} bytes</span>
101 with <span class="text-success">#{@best[:length][:value]} bytes</span>
102 %br= link_to "#" + @best[:length][:sub_id].to_s, submission_path(@best[:length][:sub_id])
102 %br= link_to "#" + @best[:length][:sub_id].to_s, submission_path(@best[:length][:sub_id])
103 %td
103 %td
104 - if @best[:first][:user] != '(NULL)'
104 - if @best[:first][:user] != '(NULL)'
105 #{link_to @best[:first][:user], stat_user_path(@best[:first][:user_id])} is the first solver
105 #{link_to @best[:first][:user], stat_user_path(@best[:first][:user_id])} is the first solver
106 %br
106 %br
107 using <span class="text-success">#{@best[:first][:lang]}</span>
107 using <span class="text-success">#{@best[:first][:lang]}</span>
108 %br
108 %br
109 on <span class="text-success">#{@best[:first][:value]}</span>
109 on <span class="text-success">#{@best[:first][:value]}</span>
110 %br= link_to "#" + @best[:first][:sub_id].to_s, submission_path( @best[:first][:sub_id])
110 %br= link_to "#" + @best[:first][:sub_id].to_s, submission_path( @best[:first][:sub_id])
111 - else
111 - else
112 no first solver
112 no first solver
113 - @by_lang.each do |lang,value|
113 - @by_lang.each do |lang,value|
114 %tr
114 %tr
115 %td= lang
115 %td= lang
116 %td
116 %td
117 = link_to value[:runtime][:user], stat_user_path(value[:runtime][:user_id])
117 = link_to value[:runtime][:user], stat_user_path(value[:runtime][:user_id])
118 %br
118 %br
119 = "#{(value[:runtime][:value] * 1000).to_i} @"
119 = "#{(value[:runtime][:value] * 1000).to_i} @"
120 = link_to "#" + value[:runtime][:sub_id].to_s, submission_path( value[:runtime][:sub_id])
120 = link_to "#" + value[:runtime][:sub_id].to_s, submission_path( value[:runtime][:sub_id])
121 %td
121 %td
122 = link_to value[:memory][:user], stat_user_path( value[:memory][:user_id])
122 = link_to value[:memory][:user], stat_user_path( value[:memory][:user_id])
123 %br
123 %br
124 = "#{number_with_delimiter(value[:memory][:value])} @"
124 = "#{number_with_delimiter(value[:memory][:value])} @"
125 = link_to "#" + value[:memory][:sub_id].to_s, submission_path(value[:memory][:sub_id])
125 = link_to "#" + value[:memory][:sub_id].to_s, submission_path(value[:memory][:sub_id])
126 %td
126 %td
127 = link_to value[:length][:user], stat_user_path(value[:length][:user_id])
127 = link_to value[:length][:user], stat_user_path(value[:length][:user_id])
128 %br
128 %br
129 = "#{value[:length][:value]} @"
129 = "#{value[:length][:value]} @"
130 = link_to "#" + value[:length][:sub_id].to_s, submission_path(value[:length][:sub_id])
130 = link_to "#" + value[:length][:sub_id].to_s, submission_path(value[:length][:sub_id])
131 %td
131 %td
132 - if value[:first][:user] != '(NULL)' #TODO: i know... this is wrong...
132 - if value[:first][:user] != '(NULL)' #TODO: i know... this is wrong...
133 = link_to value[:first][:user], stat_user_path(value[:first][:user_id])
133 = link_to value[:first][:user], stat_user_path(value[:first][:user_id])
134 %br
134 %br
135 = "#{value[:first][:value]} @"
135 = "#{value[:first][:value]} @"
136 = link_to "#" + value[:first][:sub_id].to_s, submission_path( value[:first][:sub_id])
136 = link_to "#" + value[:first][:sub_id].to_s, submission_path( value[:first][:sub_id])
137
137
138 - %script{src:"https://cdn.jsdelivr.net/npm/chart.js"}
139 :javascript
138 :javascript
140 - data = #{@chart_dataset}
139 + $(document).on('import-map-loaded',(e) => {
141 - config = {
140 + data = #{@chart_dataset}
142 - type: 'bar',
141 + config = {
143 - data: data,
142 + type: 'bar',
144 - options: {
143 + data: data,
145 - plugins: {
144 + options: {
146 - legend: {
145 + plugins: {
147 - display: false
146 + legend: {
147 + display: false
148 + },
148 },
149 },
149 - },
150 + }
150 }
151 }
151 - }
152 + Chart.defaults.font.size = 15
152 - Chart.defaults.font.size = 15
153 + //Chart.defaults.font.family = 'Sarabun Light'
153 - //Chart.defaults.font.family = 'Sarabun Light'
154 + chart = new Chart($('#chart'),config)
154 - chart = new Chart($('#chart'),config)
155 + });
155
156
@@ -1,101 +1,91
1 %h1 Users
1 %h1 Users
2
2
3 .card.border-success.mb-3
3 .card.border-success.mb-3
4 .card-header.text-bg-success.border-success
4 .card-header.text-bg-success.border-success
5 Quick Add
5 Quick Add
6 .card-body
6 .card-body
7 = form_with url: user_admin_index_path, scope: :user, class: 'row row-cols-lg-auto g-3 align-items-center' do |f|
7 = form_with url: user_admin_index_path, scope: :user, class: 'row row-cols-lg-auto g-3 align-items-center' do |f|
8 .col-12
8 .col-12
9 = f.text_field 'login', :size => 10,class: 'form-control', placeholder: 'login'
9 = f.text_field 'login', :size => 10,class: 'form-control', placeholder: 'login'
10 .form-group
10 .form-group
11 = f.text_field 'full_name', :size => 10,class: 'form-control', placeholder: 'full name'
11 = f.text_field 'full_name', :size => 10,class: 'form-control', placeholder: 'full name'
12 .form-group
12 .form-group
13 = f.password_field 'password', :size => 10,class: 'form-control', placeholder: 'password'
13 = f.password_field 'password', :size => 10,class: 'form-control', placeholder: 'password'
14 .form-group
14 .form-group
15 = f.password_field 'password_confirmation', :size => 10,class: 'form-control', placeholder: 'password confirmation'
15 = f.password_field 'password_confirmation', :size => 10,class: 'form-control', placeholder: 'password confirmation'
16 .form-group
16 .form-group
17 = f.text_field 'email', :size => 10,class: 'form-control', placeholder: 'email'
17 = f.text_field 'email', :size => 10,class: 'form-control', placeholder: 'email'
18 =submit_tag "Create", class: 'btn btn-success align-items-bottom'
18 =submit_tag "Create", class: 'btn btn-success align-items-bottom'
19
19
20 .card.border-success.mb-3
20 .card.border-success.mb-3
21 .card-header.text-bg-success.border-success
21 .card-header.text-bg-success.border-success
22 Import from site management
22 Import from site management
23 .card-body
23 .card-body
24 = form_with url: import_user_admin_index_path, :multipart => true do |f|
24 = form_with url: import_user_admin_index_path, :multipart => true do |f|
25 .row
25 .row
26 .col-auto
26 .col-auto
27 = f.label :file, 'File:', class: 'col-form-label'
27 = f.label :file, 'File:', class: 'col-form-label'
28 .col-auto
28 .col-auto
29 = f.file_field :file, class: 'form-control'
29 = f.file_field :file, class: 'form-control'
30 .col-auto
30 .col-auto
31 = f.submit 'Submit', class: 'btn btn-secondary'
31 = f.submit 'Submit', class: 'btn btn-secondary'
32
32
33
33
34 %p
34 %p
35 = link_to '+ New user', { :action => 'new' }, { class: 'btn btn-success '}
35 = link_to '+ New user', { :action => 'new' }, { class: 'btn btn-success '}
36 = link_to '+ New list of users', { :action => 'new_list' }, { class: 'btn btn-success '}
36 = link_to '+ New list of users', { :action => 'new_list' }, { class: 'btn btn-success '}
37 = link_to 'Bulk Manage', { action: :bulk_manage} , { class: 'btn btn-secondary btn-info'}
37 = link_to 'Bulk Manage', { action: :bulk_manage} , { class: 'btn btn-secondary btn-info'}
38 = link_to 'View administrators',{ :action => 'admin'}, { class: 'btn btn-secondary '}
38 = link_to 'View administrators',{ :action => 'admin'}, { class: 'btn btn-secondary '}
39 = link_to 'Random passwords',{ :action => 'random_all_passwords'}, { class: 'btn btn-secondary '}
39 = link_to 'Random passwords',{ :action => 'random_all_passwords'}, { class: 'btn btn-secondary '}
40 = link_to 'View active users',{ :action => 'active'}, { class: 'btn btn-secondary '}
40 = link_to 'View active users',{ :action => 'active'}, { class: 'btn btn-secondary '}
41 = link_to 'Mass mailing',{ :action => 'mass_mailing'}, { class: 'btn btn-secondary '}
41 = link_to 'Mass mailing',{ :action => 'mass_mailing'}, { class: 'btn btn-secondary '}
42
42
43 - if GraderConfiguration.multicontests?
43 - if GraderConfiguration.multicontests?
44 %br/
44 %br/
45 %b Multi-contest:
45 %b Multi-contest:
46 = link_to '[Manage bulk users in contests]', :action => 'contest_management'
46 = link_to '[Manage bulk users in contests]', :action => 'contest_management'
47 View users in:
47 View users in:
48 - @contests.each do |contest|
48 - @contests.each do |contest|
49 = link_to "[#{contest.name}]", :action => 'contests', :id => contest.id
49 = link_to "[#{contest.name}]", :action => 'contests', :id => contest.id
50 = link_to "[no contest]", :action => 'contests', :id => 'none'
50 = link_to "[no contest]", :action => 'contests', :id => 'none'
51
51
52 - -# Total #{@user_count} users |
53 - -# - if !@paginated
54 - -# Display all users.
55 - -# \#{link_to '[show in pages]', :action => 'index', :page => '1'}
56 - -# - else
57 - -# Display in pages.
58 - -# \#{link_to '[display all]', :action => 'index', :page => 'all'} |
59 - -# \#{will_paginate @users, :container => false}
60 -
61 -
62 %table.table.table-hover.table-condense.datatable
52 %table.table.table-hover.table-condense.datatable
63 %thead
53 %thead
64 %th Login
54 %th Login
65 %th Full name
55 %th Full name
66 %th email
56 %th email
67 %th Remark
57 %th Remark
68 %th
58 %th
69 Activated
59 Activated
70 %sup{class: 'text-primary',data: {toggle: 'tooltip', placement: 'top'}, title: 'User has already confirmed the email?' } [?]
60 %sup{class: 'text-primary',data: {toggle: 'tooltip', placement: 'top'}, title: 'User has already confirmed the email?' } [?]
71 %th
61 %th
72 Enabled
62 Enabled
73 %sup{class: 'text-primary',data: {toggle: 'tooltip', placement: 'top'}, title: 'Allow the user to login?' } [?]
63 %sup{class: 'text-primary',data: {toggle: 'tooltip', placement: 'top'}, title: 'Allow the user to login?' } [?]
74 %th Last IP
64 %th Last IP
75 %th
65 %th
76 %th
66 %th
77 %th
67 %th
78 %th
68 %th
79 - for user in @users
69 - for user in @users
80 %tr
70 %tr
81 %td= link_to user.login, stat_user_path(user)
71 %td= link_to user.login, stat_user_path(user)
82 %td= user.full_name
72 %td= user.full_name
83 %td= user.email
73 %td= user.email
84 %td= user.remark
74 %td= user.remark
85 %td= toggle_button(user.activated?, toggle_activate_user_path(user),"toggle_activate_user_#{user.id}")
75 %td= toggle_button(user.activated?, toggle_activate_user_path(user),"toggle_activate_user_#{user.id}")
86 %td= toggle_button(user.enabled?, toggle_enable_user_path(user),"toggle_enable_user_#{user.id}")
76 %td= toggle_button(user.enabled?, toggle_enable_user_path(user),"toggle_enable_user_#{user.id}")
87 %td= user.last_ip
77 %td= user.last_ip
88 - %td= link_to 'Clear IP', {:action => 'clear_last_ip', :id => user, :page=>params[:page]}, :confirm => 'This will reset last logging in ip of the user, are you sure?', class: 'btn btn-default btn-xs btn-block'
78 + %td= link_to 'Clear IP', {:action => 'clear_last_ip', :id => user, :page=>params[:page]}, :confirm => 'This will reset last logging in ip of the user, are you sure?', class: 'btn btn-secondary btn-sm btn-block'
89 - %td= link_to 'Show', {:action => 'show', :id => user}, class: 'btn btn-default btn-xs btn-block'
79 + %td= link_to 'Show', {:action => 'show', :id => user}, class: 'btn btn-secondary btn-sm btn-block'
90 - %td= link_to 'Edit', {:action => 'edit', :id => user}, class: 'btn btn-default btn-xs btn-block'
80 + %td= link_to 'Edit', {:action => 'edit', :id => user}, class: 'btn btn-secondary btn-sm btn-block'
91 - %td= link_to 'Destroy', {action: :destroy, id: user}, data: {confirm: 'Are you sure?'}, method: :delete, class: 'btn btn-danger btn-xs btn-block'
81 + %td= link_to 'Destroy', {action: :destroy, id: user}, data: {confirm: 'Are you sure?'}, method: :delete, class: 'btn btn-danger btn-sm btn-block'
92 %br/
82 %br/
93 = link_to '+ New user', { :action => 'new' }, { class: 'btn btn-success '}
83 = link_to '+ New user', { :action => 'new' }, { class: 'btn btn-success '}
94 = link_to '+ New list of users', { :action => 'new_list' }, { class: 'btn btn-success '}
84 = link_to '+ New list of users', { :action => 'new_list' }, { class: 'btn btn-success '}
95
85
96 :javascript
86 :javascript
97 $(document).on('import-map-loaded',(e) => {
87 $(document).on('import-map-loaded',(e) => {
98 $('.datatable').DataTable({
88 $('.datatable').DataTable({
99 'pageLength': 50
89 'pageLength': 50
100 });
90 });
101 })
91 })
@@ -1,57 +1,58
1 # Pin npm packages by running ./bin/importmap
1 # Pin npm packages by running ./bin/importmap
2
2
3 #entry point
3 #entry point
4 pin "application"
4 pin "application"
5 pin "prepend_jquery"
5 pin "prepend_jquery"
6 #pin "my_sprocket"
6 #pin "my_sprocket"
7 pin_all_from "app/javascript/controllers", under: "controllers"
7 pin_all_from "app/javascript/controllers", under: "controllers"
8
8
9 #we don't need jquery in importmap because we use sprocket version
9 #we don't need jquery in importmap because we use sprocket version
10 #pin "jquery", to: 'my_jquery.js', preload: true
10 #pin "jquery", to: 'my_jquery.js', preload: true
11 #pin "bootstrap", to: "bootstrap.bundle.min.js", preload: true
11 #pin "bootstrap", to: "bootstrap.bundle.min.js", preload: true
12 #no need popper, because bundled already in bootstrap
12 #no need popper, because bundled already in bootstrap
13 #pin "@popperjs/core", to: "https://ga.jspm.io/npm:@popperjs/core@2.11.6/lib/index.js"
13 #pin "@popperjs/core", to: "https://ga.jspm.io/npm:@popperjs/core@2.11.6/lib/index.js"
14
14
15 # datatable
15 # datatable
16 # I have to fix vfs_font.js for this to work
16 # I have to fix vfs_font.js for this to work
17 pin "jszip", to: "jszip.min.js"
17 pin "jszip", to: "jszip.min.js"
18 pin "pdfmake"
18 pin "pdfmake"
19 pin "pdfmake-vfs", to: 'datatables/pdfmake-0.1.36/vfs_fonts.js'
19 pin "pdfmake-vfs", to: 'datatables/pdfmake-0.1.36/vfs_fonts.js'
20
20
21 pin "datatables-bundle", to: 'datatables/datatables.js'
21 pin "datatables-bundle", to: 'datatables/datatables.js'
22 pin "datatables", to: "datatables/DataTables-1.12.1/js/jquery.dataTables.js"
22 pin "datatables", to: "datatables/DataTables-1.12.1/js/jquery.dataTables.js"
23 pin "datatables-bs5", to: "datatables/DataTables-1.12.1/js/dataTables.bootstrap5.js"
23 pin "datatables-bs5", to: "datatables/DataTables-1.12.1/js/dataTables.bootstrap5.js"
24 pin "datatables-editor", to: "datatables/Editor-2.0.9/js/dataTables.editor.js"
24 pin "datatables-editor", to: "datatables/Editor-2.0.9/js/dataTables.editor.js"
25 pin "datatables-editor-bs5", to: "datatables/Editor-2.0.9/js/editor.bootstrap5.min.js"
25 pin "datatables-editor-bs5", to: "datatables/Editor-2.0.9/js/editor.bootstrap5.min.js"
26 pin "datatables-autofill", to: "datatables/AutoFill-2.4.0/js/dataTables.autoFill.js"
26 pin "datatables-autofill", to: "datatables/AutoFill-2.4.0/js/dataTables.autoFill.js"
27 pin "datatables-autofill-bs5", to: "datatables/AutoFill-2.4.0/js/autoFill.bootstrap5.js"
27 pin "datatables-autofill-bs5", to: "datatables/AutoFill-2.4.0/js/autoFill.bootstrap5.js"
28 pin "datatables-button", to: "datatables/Buttons-2.2.3/js/dataTables.buttons.js"
28 pin "datatables-button", to: "datatables/Buttons-2.2.3/js/dataTables.buttons.js"
29 pin "datatables-button-bs5", to: "datatables/Buttons-2.2.3/js/buttons.bootstrap5.js"
29 pin "datatables-button-bs5", to: "datatables/Buttons-2.2.3/js/buttons.bootstrap5.js"
30 pin "datatables-button-colvis", to: "datatables/Buttons-2.2.3/js/buttons.colVis.js"
30 pin "datatables-button-colvis", to: "datatables/Buttons-2.2.3/js/buttons.colVis.js"
31 pin "datatables-button-html5", to: "datatables/Buttons-2.2.3/js/buttons.html5.js"
31 pin "datatables-button-html5", to: "datatables/Buttons-2.2.3/js/buttons.html5.js"
32 pin "datatables-button-print", to: "datatables/Buttons-2.2.3/js/buttons.print.js"
32 pin "datatables-button-print", to: "datatables/Buttons-2.2.3/js/buttons.print.js"
33 pin "datatables-colrecorder", to: "datatables/ColReorder-1.5.6/js/dataTables.colReorder.js"
33 pin "datatables-colrecorder", to: "datatables/ColReorder-1.5.6/js/dataTables.colReorder.js"
34 pin "datatables-datetime", to: "datatables/DateTime-1.1.2/js/dataTables.dateTime.js"
34 pin "datatables-datetime", to: "datatables/DateTime-1.1.2/js/dataTables.dateTime.js"
35 pin "datatables-fixedcolumns", to: "datatables/FixedColumns-4.1.0/js/dataTables.fixedColumns.js"
35 pin "datatables-fixedcolumns", to: "datatables/FixedColumns-4.1.0/js/dataTables.fixedColumns.js"
36 pin "datatables-fixedheader", to: "datatables/FixedHeader-3.2.4/js/dataTables.fixedHeader.js"
36 pin "datatables-fixedheader", to: "datatables/FixedHeader-3.2.4/js/dataTables.fixedHeader.js"
37 pin "datatables-keytable", to: "datatables/KeyTable-2.7.0/js/dataTables.keyTable.js"
37 pin "datatables-keytable", to: "datatables/KeyTable-2.7.0/js/dataTables.keyTable.js"
38 pin "datatables-responsive", to: "datatables/Responsive-2.3.0/js/dataTables.responsive.js"
38 pin "datatables-responsive", to: "datatables/Responsive-2.3.0/js/dataTables.responsive.js"
39 pin "datatables-responsive-bs5", to: "datatables/Responsive-2.3.0/js/responsive.bootstrap5.js"
39 pin "datatables-responsive-bs5", to: "datatables/Responsive-2.3.0/js/responsive.bootstrap5.js"
40 pin "datatables-rowgroup", to: "datatables/RowGroup-1.2.0/js/dataTables.rowGroup.js"
40 pin "datatables-rowgroup", to: "datatables/RowGroup-1.2.0/js/dataTables.rowGroup.js"
41 pin "datatables-rowreorder", to: "datatables/RowReorder-1.2.8/js/dataTables.rowReorder.js"
41 pin "datatables-rowreorder", to: "datatables/RowReorder-1.2.8/js/dataTables.rowReorder.js"
42 pin "datatables-scroller", to: "datatables/Scroller-2.0.7/js/dataTables.scroller.js"
42 pin "datatables-scroller", to: "datatables/Scroller-2.0.7/js/dataTables.scroller.js"
43 pin "datatables-searchbuilder", to: "datatables/SearchBuilder-1.3.4/js/dataTables.searchBuilder.js"
43 pin "datatables-searchbuilder", to: "datatables/SearchBuilder-1.3.4/js/dataTables.searchBuilder.js"
44 pin "datatables-searchbuilder-bs5", to: "datatables/SearchBuilder-1.3.4/js/searchBuilder.bootstrap5.js"
44 pin "datatables-searchbuilder-bs5", to: "datatables/SearchBuilder-1.3.4/js/searchBuilder.bootstrap5.js"
45 pin "datatables-searchpanes", to: "datatables/SearchPanes-2.0.2/js/dataTables.searchPanes.js"
45 pin "datatables-searchpanes", to: "datatables/SearchPanes-2.0.2/js/dataTables.searchPanes.js"
46 pin "datatables-searchpanes-bs5", to: "datatables/SearchPanes-2.0.2/js/searchPanes.bootstrap5.js"
46 pin "datatables-searchpanes-bs5", to: "datatables/SearchPanes-2.0.2/js/searchPanes.bootstrap5.js"
47 pin "datatables-select", to: "datatables/Select-1.4.0/js/dataTables.select.js"
47 pin "datatables-select", to: "datatables/Select-1.4.0/js/dataTables.select.js"
48 pin "datatables-staterestore", to: "datatables/StateRestore-1.1.1/js/dataTables.stateRestore.js"
48 pin "datatables-staterestore", to: "datatables/StateRestore-1.1.1/js/dataTables.stateRestore.js"
49 pin "datatables-staterestore-bs5", to: "datatables/StateRestore-1.1.1/js/stateRestore.bootstrap5.js"
49 pin "datatables-staterestore-bs5", to: "datatables/StateRestore-1.1.1/js/stateRestore.bootstrap5.js"
50
50
51 #select2
51 #select2
52 pin "select2", to: "select2.min.js"
52 pin "select2", to: "select2.min.js"
53
53
54 #my local js
54 #my local js
55 pin "custom", to: "custom.js"
55 pin "custom", to: "custom.js"
56
56
57 #pin "ace-rails-ap"
57 #pin "ace-rails-ap"
58 + pin "chart", to: 'chart.js' # @3.9.1
You need to be logged in to leave comments. Login now