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

r515:25e5802e70aa - - 7 files changed: 69 inserted, 3 deleted

@@ -0,0 +1,17
1 + %table.info
2 + %thead
3 + %tr.info-head
4 + %th Problem
5 + %th User
6 + %th tries
7 + %tbody
8 + - @struggle.each do |s|
9 + %tr
10 + %td
11 + = link_to "(#{s[:problem].name})", controller: :problems, action: :stat, id: s[:problem]
12 + = s[:problem].full_name
13 + %td
14 + = link_to "(#{s[:user].login})", controller: :users, action: :profile, id: s[:user]
15 + = s[:user].full_name
16 + %td
17 + = s[:tries]
@@ -1,118 +1,119
1 1 class GradersController < ApplicationController
2 2
3 3 before_filter :admin_authorization, except: [ :submission ]
4 4 before_filter(only: [:submission]) {
5 5 return false unless authenticate
6 6
7 7 if GraderConfiguration["right.user_view_submission"]
8 8 return true;
9 9 end
10 10
11 11 admin_authorization
12 12 }
13 13
14 14 verify :method => :post, :only => ['clear_all',
15 15 'start_exam',
16 16 'start_grading',
17 17 'stop_all',
18 18 'clear_terminated'],
19 19 :redirect_to => {:action => 'index'}
20 20
21 21 def index
22 22 redirect_to :action => 'list'
23 23 end
24 24
25 25 def list
26 26 @grader_processes = GraderProcess.find_running_graders
27 27 @stalled_processes = GraderProcess.find_stalled_process
28 28
29 29 @terminated_processes = GraderProcess.find_terminated_graders
30 30
31 31 @last_task = Task.find(:first,
32 32 :order => 'created_at DESC')
33 33 @last_test_request = TestRequest.find(:first,
34 34 :order => 'created_at DESC')
35 + @submission = Submission.order("id desc").limit(20)
35 36 end
36 37
37 38 def clear
38 39 grader_proc = GraderProcess.find(params[:id])
39 40 grader_proc.destroy if grader_proc!=nil
40 41 redirect_to :action => 'list'
41 42 end
42 43
43 44 def clear_terminated
44 45 GraderProcess.find_terminated_graders.each do |p|
45 46 p.destroy
46 47 end
47 48 redirect_to :action => 'list'
48 49 end
49 50
50 51 def clear_all
51 52 GraderProcess.find(:all).each do |p|
52 53 p.destroy
53 54 end
54 55 redirect_to :action => 'list'
55 56 end
56 57
57 58 def view
58 59 if params[:type]=='Task'
59 60 redirect_to :action => 'task', :id => params[:id]
60 61 else
61 62 redirect_to :action => 'test_request', :id => params[:id]
62 63 end
63 64 end
64 65
65 66 def test_request
66 67 @test_request = TestRequest.find(params[:id])
67 68 end
68 69
69 70 def task
70 71 @task = Task.find(params[:id])
71 72 end
72 73
73 74 def submission
74 75 @submission = Submission.find(params[:id])
75 76 formatter = Rouge::Formatters::HTML.new(css_class: 'highlight', line_numbers: true )
76 77 lexer = case @submission.language.name
77 78 when "c" then Rouge::Lexers::C.new
78 79 when "cpp" then Rouge::Lexers::Cpp.new
79 80 when "pas" then Rouge::Lexers::Pas.new
80 81 when "ruby" then Rouge::Lexers::Ruby.new
81 82 when "python" then Rouge::Lexers::Python.new
82 83 when "java" then Rouge::Lexers::Java.new
83 84 when "php" then Rouge::Lexers::PHP.new
84 85 end
85 86 @formatted_code = formatter.format(lexer.lex(@submission.source))
86 87 @css_style = Rouge::Themes::ThankfulEyes.render(scope: '.highlight')
87 88
88 89 end
89 90
90 91 # various grader controls
91 92
92 93 def stop
93 94 grader_proc = GraderProcess.find(params[:id])
94 95 GraderScript.stop_grader(grader_proc.pid)
95 96 flash[:notice] = 'Grader stopped. It may not disappear now, but it should disappear shortly.'
96 97 redirect_to :action => 'list'
97 98 end
98 99
99 100 def stop_all
100 101 GraderScript.stop_graders(GraderProcess.find_running_graders +
101 102 GraderProcess.find_stalled_process)
102 103 flash[:notice] = 'Graders stopped. They may not disappear now, but they should disappear shortly.'
103 104 redirect_to :action => 'list'
104 105 end
105 106
106 107 def start_grading
107 108 GraderScript.start_grader('grading')
108 109 flash[:notice] = '2 graders in grading env started, one for grading queue tasks, another for grading test request'
109 110 redirect_to :action => 'list'
110 111 end
111 112
112 113 def start_exam
113 114 GraderScript.start_grader('exam')
114 115 flash[:notice] = '2 graders in grading env started, one for grading queue tasks, another for grading test request'
115 116 redirect_to :action => 'list'
116 117 end
117 118
118 119 end
@@ -1,162 +1,163
1 1 class MainController < ApplicationController
2 2
3 3 before_filter :authenticate, :except => [:index, :login]
4 4 before_filter :check_viewability, :except => [:index, :login]
5 5
6 6 append_before_filter :confirm_and_update_start_time,
7 7 :except => [:index,
8 8 :login,
9 9 :confirm_contest_start]
10 10
11 11 # to prevent log in box to be shown when user logged out of the
12 12 # system only in some tab
13 13 prepend_before_filter :reject_announcement_refresh_when_logged_out,
14 14 :only => [:announcements]
15 15
16 16 # COMMENTED OUT: filter in each action instead
17 17 # before_filter :verify_time_limit, :only => [:submit]
18 18
19 19 verify :method => :post, :only => [:submit],
20 20 :redirect_to => { :action => :index }
21 21
22 22 # COMMENT OUT: only need when having high load
23 23 # caches_action :index, :login
24 24
25 25 # NOTE: This method is not actually needed, 'config/routes.rb' has
26 26 # assigned action login as a default action.
27 27 def index
28 28 redirect_to :action => 'login'
29 29 end
30 30
31 31 def login
32 32 saved_notice = flash[:notice]
33 33 reset_session
34 34 flash.now[:notice] = saved_notice
35 35
36 36 # EXPERIMENT:
37 37 # Hide login if in single user mode and the url does not
38 38 # explicitly specify /login
39 39 #
40 40 # logger.info "PATH: #{request.path}"
41 41 # if GraderConfiguration['system.single_user_mode'] and
42 42 # request.path!='/main/login'
43 43 # @hidelogin = true
44 44 # end
45 45
46 46 @announcements = Announcement.find_for_frontpage
47 47 render :action => 'login', :layout => 'empty'
48 48 end
49 49
50 50 def list
51 51 prepare_list_information
52 52 end
53 53
54 54 def help
55 55 @user = User.find(session[:user_id])
56 56 end
57 57
58 58 def submit
59 59 user = User.find(session[:user_id])
60 60
61 61 @submission = Submission.new
62 62 @submission.problem_id = params[:submission][:problem_id]
63 63 @submission.user = user
64 64 @submission.language_id = 0
65 65 if (params['file']) and (params['file']!='')
66 - @submission.source = params['file'].read
66 + @submission.source = File.open(params['file'].path,'r:UTF-8',&:read)
67 + @submission.source.encode!('UTF-8','UTF-8',invalid: :replace, replace: '')
67 68 @submission.source_filename = params['file'].original_filename
68 69 end
69 70 @submission.submitted_at = Time.new.gmtime
70 71 @submission.ip_address = request.remote_ip
71 72
72 73 if GraderConfiguration.time_limit_mode? and user.contest_finished?
73 74 @submission.errors.add_to_base "The contest is over."
74 75 prepare_list_information
75 76 render :action => 'list' and return
76 77 end
77 78
78 79 if @submission.valid?
79 80 if @submission.save == false
80 81 flash[:notice] = 'Error saving your submission'
81 82 elsif Task.create(:submission_id => @submission.id,
82 83 :status => Task::STATUS_INQUEUE) == false
83 84 flash[:notice] = 'Error adding your submission to task queue'
84 85 end
85 86 else
86 87 prepare_list_information
87 88 render :action => 'list' and return
88 89 end
89 90 redirect_to :action => 'list'
90 91 end
91 92
92 93 def source
93 94 submission = Submission.find(params[:id])
94 95 if ((submission.user_id == session[:user_id]) and
95 96 (submission.problem != nil) and
96 97 (submission.problem.available))
97 98 send_data(submission.source,
98 99 {:filename => submission.download_filename,
99 100 :type => 'text/plain'})
100 101 else
101 102 flash[:notice] = 'Error viewing source'
102 103 redirect_to :action => 'list'
103 104 end
104 105 end
105 106
106 107 def compiler_msg
107 108 @submission = Submission.find(params[:id])
108 109 if @submission.user_id == session[:user_id]
109 110 render :action => 'compiler_msg', :layout => 'empty'
110 111 else
111 112 flash[:notice] = 'Error viewing source'
112 113 redirect_to :action => 'list'
113 114 end
114 115 end
115 116
116 117 def submission
117 118 @user = User.find(session[:user_id])
118 119 @problems = @user.available_problems
119 120 if params[:id]==nil
120 121 @problem = nil
121 122 @submissions = nil
122 123 else
123 124 @problem = Problem.find_by_name(params[:id])
124 125 if not @problem.available
125 126 redirect_to :action => 'list'
126 127 flash[:notice] = 'Error: submissions for that problem are not viewable.'
127 128 return
128 129 end
129 130 @submissions = Submission.find_all_by_user_problem(@user.id, @problem.id)
130 131 end
131 132 end
132 133
133 134 def result
134 135 if !GraderConfiguration.show_grading_result
135 136 redirect_to :action => 'list' and return
136 137 end
137 138 @user = User.find(session[:user_id])
138 139 @submission = Submission.find(params[:id])
139 140 if @submission.user!=@user
140 141 flash[:notice] = 'You are not allowed to view result of other users.'
141 142 redirect_to :action => 'list' and return
142 143 end
143 144 prepare_grading_result(@submission)
144 145 end
145 146
146 147 def load_output
147 148 if !GraderConfiguration.show_grading_result or params[:num]==nil
148 149 redirect_to :action => 'list' and return
149 150 end
150 151 @user = User.find(session[:user_id])
151 152 @submission = Submission.find(params[:id])
152 153 if @submission.user!=@user
153 154 flash[:notice] = 'You are not allowed to view result of other users.'
154 155 redirect_to :action => 'list' and return
155 156 end
156 157 case_num = params[:num].to_i
157 158 out_filename = output_filename(@user.login,
158 159 @submission.problem.name,
159 160 @submission.id,
160 161 case_num)
161 162 if !FileTest.exists?(out_filename)
162 163 flash[:notice] = 'Output not found.'
@@ -95,97 +95,122
95 95 @problems = @user.available_problems
96 96
97 97 # get selected problems or the default
98 98 if params[:id]
99 99 begin
100 100 @problem = Problem.available.find(params[:id])
101 101 rescue
102 102 redirect_to action: :problem_hof
103 103 flash[:notice] = 'Error: submissions for that problem are not viewable.'
104 104 return
105 105 end
106 106 end
107 107
108 108 return unless @problem
109 109
110 110 @by_lang = {} #aggregrate by language
111 111
112 112 range =65
113 113 @histogram = { data: Array.new(range,0), summary: {} }
114 114 @summary = {count: 0, solve: 0, attempt: 0}
115 115 user = Hash.new(0)
116 116 Submission.where(problem_id: @problem.id).find_each do |sub|
117 117 #histogram
118 118 d = (DateTime.now.in_time_zone - sub.submitted_at) / 24 / 60 / 60
119 119 @histogram[:data][d.to_i] += 1 if d < range
120 120
121 121 @summary[:count] += 1
122 122 user[sub.user_id] = [user[sub.user_id], (sub.points >= @problem.full_score) ? 1 : 0].max
123 123
124 124 lang = Language.find_by_id(sub.language_id)
125 125 next unless lang
126 126 next unless sub.points >= @problem.full_score
127 127
128 128 #initialize
129 129 unless @by_lang.has_key?(lang.pretty_name)
130 130 @by_lang[lang.pretty_name] = {
131 131 runtime: { avail: false, value: 2**30-1 },
132 132 memory: { avail: false, value: 2**30-1 },
133 133 length: { avail: false, value: 2**30-1 },
134 134 first: { avail: false, value: DateTime.new(3000,1,1) }
135 135 }
136 136 end
137 137
138 138 if sub.max_runtime and sub.max_runtime < @by_lang[lang.pretty_name][:runtime][:value]
139 139 @by_lang[lang.pretty_name][:runtime] = { avail: true, user_id: sub.user_id, value: sub.max_runtime, sub_id: sub.id }
140 140 end
141 141
142 142 if sub.peak_memory and sub.peak_memory < @by_lang[lang.pretty_name][:memory][:value]
143 143 @by_lang[lang.pretty_name][:memory] = { avail: true, user_id: sub.user_id, value: sub.peak_memory, sub_id: sub.id }
144 144 end
145 145
146 146 if sub.submitted_at and sub.submitted_at < @by_lang[lang.pretty_name][:first][:value] and
147 147 !sub.user.admin?
148 148 @by_lang[lang.pretty_name][:first] = { avail: true, user_id: sub.user_id, value: sub.submitted_at, sub_id: sub.id }
149 149 end
150 150
151 151 if @by_lang[lang.pretty_name][:length][:value] > sub.effective_code_length
152 152 @by_lang[lang.pretty_name][:length] = { avail: true, user_id: sub.user_id, value: sub.effective_code_length, sub_id: sub.id }
153 153 end
154 154 end
155 155
156 156 #process user_id
157 157 @by_lang.each do |lang,prop|
158 158 prop.each do |k,v|
159 159 v[:user] = User.exists?(v[:user_id]) ? User.find(v[:user_id]).full_name : "(NULL)"
160 160 end
161 161 end
162 162
163 163 #sum into best
164 164 if @by_lang and @by_lang.first
165 165 @best = @by_lang.first[1].clone
166 166 @by_lang.each do |lang,prop|
167 167 if @best[:runtime][:value] >= prop[:runtime][:value]
168 168 @best[:runtime] = prop[:runtime]
169 169 @best[:runtime][:lang] = lang
170 170 end
171 171 if @best[:memory][:value] >= prop[:memory][:value]
172 172 @best[:memory] = prop[:memory]
173 173 @best[:memory][:lang] = lang
174 174 end
175 175 if @best[:length][:value] >= prop[:length][:value]
176 176 @best[:length] = prop[:length]
177 177 @best[:length][:lang] = lang
178 178 end
179 179 if @best[:first][:value] >= prop[:first][:value]
180 180 @best[:first] = prop[:first]
181 181 @best[:first][:lang] = lang
182 182 end
183 183 end
184 184 end
185 185
186 186 @histogram[:summary][:max] = [@histogram[:data].max,1].max
187 187 @summary[:attempt] = user.count
188 188 user.each_value { |v| @summary[:solve] += 1 if v == 1 }
189 189 end
190 190
191 + def stuck #report struggling user,problem
192 + # init
193 + user,problem = nil
194 + solve = true
195 + tries = 0
196 + @struggle = Array.new
197 + record = {}
198 + Submission.includes(:problem,:user).order(:problem_id,:user_id).find_each do |sub|
199 + if user != sub.user_id or problem != sub.problem_id
200 + @struggle << { user: record[:user], problem: record[:problem], tries: tries } unless solve
201 + record = {user: sub.user, problem: sub.problem}
202 + user,problem = sub.user_id, sub.problem_id
203 + solve = false
204 + tries = 0
191 205 end
206 + if sub.points >= sub.problem.full_score
207 + solve = true
208 + else
209 + tries += 1
210 + end
211 + end
212 + @struggle.sort!{|a,b| b[:tries] <=> a[:tries] }
213 + @struggle = @struggle[0..50]
214 + end
215 +
216 + end
@@ -31,164 +31,165
31 31 end
32 32
33 33 def chg_passwd
34 34 user = User.find(session[:user_id])
35 35 user.password = params[:passwd]
36 36 user.password_confirmation = params[:passwd_verify]
37 37 if user.save
38 38 flash[:notice] = 'password changed'
39 39 else
40 40 flash[:notice] = 'Error: password changing failed'
41 41 end
42 42 redirect_to :action => 'index'
43 43 end
44 44
45 45 def new
46 46 @user = User.new
47 47 render :action => 'new', :layout => 'empty'
48 48 end
49 49
50 50 def register
51 51 if(params[:cancel])
52 52 redirect_to :controller => 'main', :action => 'login'
53 53 return
54 54 end
55 55 @user = User.new(params[:user])
56 56 @user.password_confirmation = @user.password = User.random_password
57 57 @user.activated = false
58 58 if (@user.valid?) and (@user.save)
59 59 if send_confirmation_email(@user)
60 60 render :action => 'new_splash', :layout => 'empty'
61 61 else
62 62 @admin_email = GraderConfiguration['system.admin_email']
63 63 render :action => 'email_error', :layout => 'empty'
64 64 end
65 65 else
66 66 @user.errors.add_to_base("Email cannot be blank") if @user.email==''
67 67 render :action => 'new', :layout => 'empty'
68 68 end
69 69 end
70 70
71 71 def confirm
72 72 login = params[:login]
73 73 key = params[:activation]
74 74 @user = User.find_by_login(login)
75 75 if (@user) and (@user.verify_activation_key(key))
76 76 if @user.valid? # check uniquenss of email
77 77 @user.activated = true
78 78 @user.save
79 79 @result = :successful
80 80 else
81 81 @result = :email_used
82 82 end
83 83 else
84 84 @result = :failed
85 85 end
86 86 render :action => 'confirm', :layout => 'empty'
87 87 end
88 88
89 89 def forget
90 90 render :action => 'forget', :layout => 'empty'
91 91 end
92 92
93 93 def retrieve_password
94 94 email = params[:email]
95 95 user = User.find_by_email(email)
96 96 if user
97 97 last_updated_time = user.updated_at || user.created_at || (Time.now.gmtime - 1.hour)
98 98 if last_updated_time > Time.now.gmtime - 5.minutes
99 99 flash[:notice] = 'The account has recently created or new password has recently been requested. Please wait for 5 minutes'
100 100 else
101 101 user.password = user.password_confirmation = User.random_password
102 102 user.save
103 103 send_new_password_email(user)
104 104 flash[:notice] = 'New password has been mailed to you.'
105 105 end
106 106 else
107 107 flash[:notice] = I18n.t 'registration.password_retrieval.no_email'
108 108 end
109 109 redirect_to :action => 'forget'
110 110 end
111 111
112 112 def profile
113 113 @user = User.find(params[:id])
114 114 @submission = Submission.includes(:problem).where(user_id: params[:id])
115 115
116 116 range = 120
117 117 @histogram = { data: Array.new(range,0), summary: {} }
118 118 @summary = {count: 0, solve: 0, attempt: 0}
119 119 problem = Hash.new(0)
120 120
121 121 @submission.find_each do |sub|
122 122 #histogram
123 123 d = (DateTime.now.in_time_zone - sub.submitted_at) / 24 / 60 / 60
124 124 @histogram[:data][d.to_i] += 1 if d < range
125 125
126 126 @summary[:count] += 1
127 + next unless sub.problem
127 128 problem[sub.problem] = [problem[sub.problem], (sub.points >= sub.problem.full_score) ? 1 : 0].max
128 129 end
129 130
130 131 @histogram[:summary][:max] = [@histogram[:data].max,1].max
131 132 @summary[:attempt] = problem.count
132 133 problem.each_value { |v| @summary[:solve] += 1 if v == 1 }
133 134 end
134 135
135 136 protected
136 137
137 138 def verify_online_registration
138 139 if !GraderConfiguration['system.online_registration']
139 140 redirect_to :controller => 'main', :action => 'login'
140 141 end
141 142 end
142 143
143 144 def send_confirmation_email(user)
144 145 contest_name = GraderConfiguration['contest.name']
145 146 activation_url = url_for(:action => 'confirm',
146 147 :login => user.login,
147 148 :activation => user.activation_key)
148 149 home_url = url_for(:controller => 'main', :action => 'index')
149 150 mail_subject = "[#{contest_name}] Confirmation"
150 151 mail_body = t('registration.email_body', {
151 152 :full_name => user.full_name,
152 153 :contest_name => contest_name,
153 154 :login => user.login,
154 155 :password => user.password,
155 156 :activation_url => activation_url,
156 157 :admin_email => admin_email
157 158 })
158 159
159 160 logger.info mail_body
160 161
161 162 send_mail(user.email, mail_subject, mail_body)
162 163 end
163 164
164 165 def send_new_password_email(user)
165 166 contest_name = GraderConfiguration['contest.name']
166 167 mail_subject = "[#{contest_name}] Password recovery"
167 168 mail_body = t('registration.password_retrieval.email_body', {
168 169 :full_name => user.full_name,
169 170 :contest_name => contest_name,
170 171 :login => user.login,
171 172 :password => user.password,
172 173 :admin_email => admin_email
173 174 })
174 175
175 176 logger.info mail_body
176 177
177 178 send_mail(user.email, mail_subject, mail_body)
178 179 end
179 180
180 181 # allow viewing of regular user profile only when options allow so
181 182 # only admins can view admins profile
182 183 def profile_authorization
183 184 #if view admins' profile, allow only admin
184 185 return false unless(params[:id])
185 186 user = User.find(params[:id])
186 187 return false unless user
187 188 return admin_authorization if user.admin?
188 189 return true if GraderConfiguration["right.user_view_submission"]
189 190
190 191 #finally, we allow only admin
191 192 admin_authorization
192 193 end
193 194
194 195 end
@@ -1,51 +1,72
1 1 - content_for :head do
2 2 = stylesheet_link_tag 'graders'
3 3 <meta http-equiv ="refresh" content="60"/>
4 4
5 5 %h1 Grader information
6 6
7 7 = link_to '[Refresh]', :action => 'list'
8 8 %br/
9 9
10 10 .submitbox
11 11 .item
12 12 Grader control:
13 13 .item
14 14 = form_for :clear, :url => {:action => 'start_grading'} do |f|
15 15 = submit_tag 'Start graders in grading env'
16 16 .item
17 17 = form_for :clear, :url => {:action => 'start_exam'} do |f|
18 18 = submit_tag 'Start graders in exam env'
19 19 .item
20 20 = form_for :clear, :url => {:action => 'stop_all'} do |f|
21 21 = submit_tag 'Stop all running graders'
22 22 .item
23 23 = form_for :clear, :url => {:action => 'clear_all'} do |f|
24 24 = submit_tag 'Clear all data'
25 25 %br{:style => 'clear:both'}/
26 26
27 + %div{style: 'width:500px; float: left;'}
27 28 - if @last_task
28 29 Last task:
29 30 = link_to "#{@last_task.id}", :action => 'view', :id => @last_task.id, :type => 'Task'
30 31
31 32 %br/
32 33
33 34 - if @last_test_request
34 35 Last test_request:
35 36 = link_to "#{@last_test_request.id}", :action => 'view', :id => @last_test_request.id, :type => 'TestRequest'
36 37
37 -
38 38 %h2 Current graders
39 39
40 40 = render :partial => 'grader_list', :locals => {:grader_list => @grader_processes}
41 41
42 42 %h2 Stalled graders
43 43
44 44 = render :partial => 'grader_list', :locals => {:grader_list => @stalled_processes}
45 45
46 46 %h2 Terminated graders
47 47
48 48 = form_for :clear, :url => {:action => 'clear_terminated'} do |f|
49 49 = submit_tag 'Clear data for terminated graders'
50 50
51 51 = render :partial => 'grader_list', :locals => {:grader_list => @terminated_processes}
52 + %div{}
53 + %h2 Last 20 submissions
54 + %table.graders
55 + %thead
56 + %th ID
57 + %th User
58 + %th Problem
59 + %th Submitted
60 + %th Graded
61 + %th Result
62 + %tbody
63 + - @submission.each do |sub|
64 + %tr.inactive
65 + %td= link_to sub.id, controller: 'graders' ,action: 'submission', id: sub.id
66 + %td= sub.try(:user).try(:full_name)
67 + %td= sub.try(:problem).try(:full_name)
68 + %td= "#{time_ago_in_words(sub.submitted_at)} ago"
69 + %td= "#{time_ago_in_words(sub.graded_at)} ago"
70 + %td= sub.grader_comment
71 +
72 +
@@ -1,7 +1,7
1 1
2 2 .task-menu
3 3 Reports
4 4 %br/
5 5 = link_to '[Hall of Fame]', :action => 'problem_hof'
6 - = link_to '[Submission]', :action => 'submission_stat'
6 + = link_to '[Struggle]', :action => 'stuck'
7 7 = link_to '[Login]', :action => 'login_stat'
You need to be logged in to leave comments. Login now