exception_handler 0.6.5 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +33 -33
- data/.rspec +3 -3
- data/.travis.yml +5 -5
- data/Gemfile +13 -13
- data/LICENSE.txt +22 -22
- data/README.md +349 -341
- data/Rakefile +7 -7
- data/app/assets/images/{favicon.ico → exception_handler.ico} +0 -0
- data/app/assets/images/exception_handler/alert.jpg +0 -0
- data/app/assets/images/exception_handler/bg.jpg +0 -0
- data/app/assets/images/exception_handler/favicon.ico +0 -0
- data/app/assets/images/exception_handler/icon.png +0 -0
- data/app/assets/stylesheets/exception_handler.css.erb +8 -8
- data/app/assets/stylesheets/styles/_base.css.erb +35 -34
- data/app/assets/stylesheets/styles/_exception.css.erb +105 -103
- data/app/assets/stylesheets/styles/_footer.css.erb +24 -27
- data/app/assets/stylesheets/styles/_responsive.css +5 -5
- data/app/controllers/exception_handler/exception_controller.rb +45 -38
- data/app/mailers/exception_handler/exception_mailer.rb +16 -16
- data/app/models/exception_handler/exception.rb +191 -191
- data/app/views/exception_handler/exception/show.html.erb +1 -1
- data/app/views/exception_handler/mailers/new_exception.erb +4 -4
- data/app/views/layouts/exception.html.erb +27 -21
- data/config/locales/exception_handler.en.yml +13 -13
- data/exception_handler.gemspec +80 -84
- data/lib/exception_handler.rb +32 -29
- data/lib/exception_handler/config.rb +80 -66
- data/lib/exception_handler/engine.rb +44 -36
- data/lib/generators/exception_handler/migration_generator.rb +55 -55
- data/lib/generators/exception_handler/views_generator.rb +42 -42
- data/lib/generators/templates/migration.rb.erb +16 -16
- data/spec/database.yml +2 -2
- data/spec/dummy/Rakefile +6 -6
- data/spec/dummy/app/assets/config/manifest.js +4 -4
- data/spec/dummy/app/assets/javascripts/application.js +13 -13
- data/spec/dummy/app/assets/javascripts/cable.coffee +11 -11
- data/spec/dummy/app/assets/stylesheets/application.css +15 -15
- data/spec/dummy/app/channels/application_cable/channel.rb +5 -5
- data/spec/dummy/app/channels/application_cable/connection.rb +5 -5
- data/spec/dummy/app/controllers/application_controller.rb +5 -5
- data/spec/dummy/app/helpers/application_helper.rb +2 -2
- data/spec/dummy/app/jobs/application_job.rb +2 -2
- data/spec/dummy/app/mailers/application_mailer.rb +4 -4
- data/spec/dummy/app/models/application_record.rb +3 -3
- data/spec/dummy/app/views/layouts/application.html.erb +15 -15
- data/spec/dummy/app/views/layouts/mailer.html.erb +13 -13
- data/spec/dummy/app/views/layouts/mailer.text.erb +1 -1
- data/spec/dummy/bin/bundle +3 -3
- data/spec/dummy/bin/rails +4 -4
- data/spec/dummy/bin/rake +4 -4
- data/spec/dummy/bin/setup +34 -34
- data/spec/dummy/bin/update +29 -29
- data/spec/dummy/config.ru +8 -8
- data/spec/dummy/config/application.rb +21 -21
- data/spec/dummy/config/boot.rb +5 -5
- data/spec/dummy/config/cable.yml +10 -10
- data/spec/dummy/config/database.yml +25 -25
- data/spec/dummy/config/environment.rb +5 -5
- data/spec/dummy/config/environments/development.rb +62 -62
- data/spec/dummy/config/environments/production.rb +87 -87
- data/spec/dummy/config/environments/test.rb +42 -42
- data/spec/dummy/config/initializers/active_record_belongs_to_required_by_default.rb +6 -6
- data/spec/dummy/config/initializers/application_controller_renderer.rb +6 -6
- data/spec/dummy/config/initializers/assets.rb +11 -11
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -7
- data/spec/dummy/config/initializers/callback_terminator.rb +6 -6
- data/spec/dummy/config/initializers/cookies_serializer.rb +5 -5
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -4
- data/spec/dummy/config/initializers/inflections.rb +16 -16
- data/spec/dummy/config/initializers/mime_types.rb +4 -4
- data/spec/dummy/config/initializers/per_form_csrf_tokens.rb +4 -4
- data/spec/dummy/config/initializers/request_forgery_protection.rb +4 -4
- data/spec/dummy/config/initializers/session_store.rb +3 -3
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -14
- data/spec/dummy/config/locales/en.yml +23 -23
- data/spec/dummy/config/puma.rb +47 -47
- data/spec/dummy/config/routes.rb +6 -6
- data/spec/dummy/config/secrets.yml +22 -22
- data/spec/dummy/public/404.html +67 -67
- data/spec/dummy/public/422.html +67 -67
- data/spec/dummy/public/500.html +66 -66
- data/spec/exception_handler.rb +21 -21
- data/spec/exception_handler/exception_controller_spec.rb +18 -0
- data/spec/spec_helper.rb +54 -54
- metadata +11 -8
@@ -1,17 +1,17 @@
|
|
1
|
-
module ExceptionHandler
|
2
|
-
class ExceptionMailer < ActionMailer::Base
|
3
|
-
|
4
|
-
# Layout
|
5
|
-
layout "mailers/layouts/mailer"
|
6
|
-
|
7
|
-
# Defaults
|
8
|
-
default from: ExceptionHandler.config.email
|
9
|
-
default template_path: "exception_handler/mailers" # => http://stackoverflow.com/a/18579046/1143732
|
10
|
-
|
11
|
-
def new_exception e
|
12
|
-
@exception = e
|
1
|
+
module ExceptionHandler
|
2
|
+
class ExceptionMailer < ActionMailer::Base
|
3
|
+
|
4
|
+
# Layout
|
5
|
+
layout "mailers/layouts/mailer"
|
6
|
+
|
7
|
+
# Defaults
|
8
|
+
default from: ExceptionHandler.config.email
|
9
|
+
default template_path: "exception_handler/mailers" # => http://stackoverflow.com/a/18579046/1143732
|
10
|
+
|
11
|
+
def new_exception e
|
12
|
+
@exception = e
|
13
13
|
mail to: ExceptionHandler.config.email
|
14
|
-
Rails.logger.info "Exception Sent To → #{ExceptionHandler.config.email}"
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
14
|
+
Rails.logger.info "Exception Sent To → #{ExceptionHandler.config.email}"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -1,191 +1,191 @@
|
|
1
|
-
module ExceptionHandler
|
2
|
-
|
3
|
-
############################################################
|
4
|
-
############################################################
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
############################################################
|
15
|
-
############################################################
|
16
|
-
|
17
|
-
# => Class (inheritance dependent on whether db option is available)
|
18
|
-
self::Exception = Class.new (ExceptionHandler.config.try(:db) ? ActiveRecord::Base : Object) do
|
19
|
-
|
20
|
-
# => Include individual elements
|
21
|
-
# => Only required if no db present (no ActiveRecord)
|
22
|
-
if ExceptionHandler.config.try(:db)
|
23
|
-
|
24
|
-
# => Set Attrs
|
25
|
-
def initialize attributes={}
|
26
|
-
super
|
27
|
-
ATTRS.each do |type|
|
28
|
-
self[type] = eval type.to_s
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
else
|
33
|
-
|
34
|
-
# => AciveModel
|
35
|
-
include ActiveModel::Model
|
36
|
-
include ActiveModel::Validations
|
37
|
-
|
38
|
-
# => Callback Extension
|
39
|
-
extend ActiveModel::Callbacks
|
40
|
-
define_model_callbacks :initialize, only: :after
|
41
|
-
|
42
|
-
# => Initialize
|
43
|
-
# => http://api.rubyonrails.org/classes/ActiveModel/Callbacks.html
|
44
|
-
# => http://stackoverflow.com/a/17682228/1143732
|
45
|
-
def initialize attributes={}
|
46
|
-
super
|
47
|
-
run_callbacks :initialize do
|
48
|
-
# => Needed for after_initialize
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
end
|
53
|
-
|
54
|
-
##################################
|
55
|
-
##################################
|
56
|
-
|
57
|
-
####################
|
58
|
-
# Table #
|
59
|
-
####################
|
60
|
-
|
61
|
-
# Schema
|
62
|
-
###################
|
63
|
-
# class_name @exception.class.name
|
64
|
-
# status ActionDispatch::ExceptionWrapper.new(@request.env, @exception).status_code
|
65
|
-
# message @exception.message
|
66
|
-
# trace @exception.backtrace.join("\n")
|
67
|
-
# target @request.url
|
68
|
-
# referer @request.referer
|
69
|
-
# params @request.params.inspect
|
70
|
-
# user_agent @request.user_agent
|
71
|
-
# created_at
|
72
|
-
# updated_at
|
73
|
-
|
74
|
-
# => Table is called "errors"
|
75
|
-
# => Dev needs to use migration to create db
|
76
|
-
if ExceptionHandler.config.try(:db)
|
77
|
-
def self.table_name
|
78
|
-
ExceptionHandler.config.db
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
##################################
|
83
|
-
##################################
|
84
|
-
|
85
|
-
####################
|
86
|
-
# Options #
|
87
|
-
####################
|
88
|
-
|
89
|
-
# => Email
|
90
|
-
# => after_initialize invoked after .new method called
|
91
|
-
# => Should have been after_create but user may not save
|
92
|
-
after_initialize Proc.new { |e| ExceptionHandler::ExceptionMailer.new_exception(e).deliver } if ExceptionHandler.config.try(:email) && ExceptionHandler.config.email.is_a?(String)
|
93
|
-
|
94
|
-
# => Attributes
|
95
|
-
attr_accessor :request, :klass, :exception, :description
|
96
|
-
attr_accessor *ATTRS unless ExceptionHandler.config.try(:db)
|
97
|
-
|
98
|
-
# => Validations
|
99
|
-
validates :klass, exclusion: { in: [ActionController::RoutingError, AbstractController::ActionNotFound, ActiveRecord::RecordNotFound], message: "%{value}" }, if: "referer.blank?"
|
100
|
-
validates :user_agent, format: { without: Regexp.new( BOTS.join("|"), Regexp::IGNORECASE ) }
|
101
|
-
|
102
|
-
##################################
|
103
|
-
##################################
|
104
|
-
|
105
|
-
####################################
|
106
|
-
# Virtual
|
107
|
-
####################################
|
108
|
-
|
109
|
-
# => Klass
|
110
|
-
# => Used for validation (needs to be cleaned up in 0.7.0)
|
111
|
-
def klass
|
112
|
-
exception.class
|
113
|
-
end
|
114
|
-
|
115
|
-
# => Exception (virtual)
|
116
|
-
def exception
|
117
|
-
request.env['action_dispatch.exception']
|
118
|
-
end
|
119
|
-
|
120
|
-
# => Description
|
121
|
-
def description
|
122
|
-
I18n.with_options scope: [:exception], message: message do |i18n|
|
123
|
-
i18n.t response, default: status
|
124
|
-
end
|
125
|
-
end
|
126
|
-
|
127
|
-
####################################
|
128
|
-
# Exception
|
129
|
-
####################################
|
130
|
-
|
131
|
-
# => Class Name
|
132
|
-
def class_name
|
133
|
-
exception.class.name
|
134
|
-
end
|
135
|
-
|
136
|
-
# => Message
|
137
|
-
def message
|
138
|
-
exception.message
|
139
|
-
end
|
140
|
-
|
141
|
-
# => Trace
|
142
|
-
def trace
|
143
|
-
exception.backtrace.join("\n")
|
144
|
-
end
|
145
|
-
|
146
|
-
####################################
|
147
|
-
# Request
|
148
|
-
####################################
|
149
|
-
|
150
|
-
# => Target URL
|
151
|
-
def target
|
152
|
-
request.url
|
153
|
-
end
|
154
|
-
|
155
|
-
# => Referrer URL
|
156
|
-
def referer
|
157
|
-
request.referer
|
158
|
-
end
|
159
|
-
|
160
|
-
# => Params
|
161
|
-
def params
|
162
|
-
request.params.inspect
|
163
|
-
end
|
164
|
-
|
165
|
-
# => User Agent
|
166
|
-
def user_agent
|
167
|
-
request.user_agent
|
168
|
-
end
|
169
|
-
|
170
|
-
####################################
|
171
|
-
# Other
|
172
|
-
####################################
|
173
|
-
|
174
|
-
# => Status code (404, 500 etc)
|
175
|
-
def status
|
176
|
-
ActionDispatch::ExceptionWrapper.new(request.env, exception).status_code.to_s
|
177
|
-
end
|
178
|
-
|
179
|
-
# => Server Response ("Not Found" etc)
|
180
|
-
def response
|
181
|
-
ActionDispatch::ExceptionWrapper.rescue_responses[class_name]
|
182
|
-
end
|
183
|
-
|
184
|
-
##################################
|
185
|
-
##################################
|
186
|
-
|
187
|
-
end
|
188
|
-
end
|
189
|
-
|
190
|
-
############################################################
|
191
|
-
############################################################
|
1
|
+
module ExceptionHandler
|
2
|
+
|
3
|
+
############################################################
|
4
|
+
############################################################
|
5
|
+
|
6
|
+
# => Search Bots
|
7
|
+
# => Used in "Exception" class
|
8
|
+
BOTS = %w(Baidu Gigabot Googlebot libwww-per lwp-trivial msnbot SiteUptime Slurp Wordpress ZIBB ZyBorg Yandex Jyxobot Huaweisymantecspider ApptusBot)
|
9
|
+
|
10
|
+
# => Attributes
|
11
|
+
# => Determine schema etc
|
12
|
+
ATTRS = %i(class_name status message trace target referrer params user_agent)
|
13
|
+
|
14
|
+
############################################################
|
15
|
+
############################################################
|
16
|
+
|
17
|
+
# => Class (inheritance dependent on whether db option is available)
|
18
|
+
self::Exception = Class.new (ExceptionHandler.config.try(:db) ? ActiveRecord::Base : Object) do
|
19
|
+
|
20
|
+
# => Include individual elements
|
21
|
+
# => Only required if no db present (no ActiveRecord)
|
22
|
+
if ExceptionHandler.config.try(:db)
|
23
|
+
|
24
|
+
# => Set Attrs
|
25
|
+
def initialize attributes={}
|
26
|
+
super
|
27
|
+
ATTRS.each do |type|
|
28
|
+
self[type] = eval type.to_s
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
else
|
33
|
+
|
34
|
+
# => AciveModel
|
35
|
+
include ActiveModel::Model
|
36
|
+
include ActiveModel::Validations
|
37
|
+
|
38
|
+
# => Callback Extension
|
39
|
+
extend ActiveModel::Callbacks
|
40
|
+
define_model_callbacks :initialize, only: :after
|
41
|
+
|
42
|
+
# => Initialize
|
43
|
+
# => http://api.rubyonrails.org/classes/ActiveModel/Callbacks.html
|
44
|
+
# => http://stackoverflow.com/a/17682228/1143732
|
45
|
+
def initialize attributes={}
|
46
|
+
super
|
47
|
+
run_callbacks :initialize do
|
48
|
+
# => Needed for after_initialize
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
##################################
|
55
|
+
##################################
|
56
|
+
|
57
|
+
####################
|
58
|
+
# Table #
|
59
|
+
####################
|
60
|
+
|
61
|
+
# Schema
|
62
|
+
###################
|
63
|
+
# class_name @exception.class.name
|
64
|
+
# status ActionDispatch::ExceptionWrapper.new(@request.env, @exception).status_code
|
65
|
+
# message @exception.message
|
66
|
+
# trace @exception.backtrace.join("\n")
|
67
|
+
# target @request.url
|
68
|
+
# referer @request.referer
|
69
|
+
# params @request.params.inspect
|
70
|
+
# user_agent @request.user_agent
|
71
|
+
# created_at
|
72
|
+
# updated_at
|
73
|
+
|
74
|
+
# => Table is called "errors"
|
75
|
+
# => Dev needs to use migration to create db
|
76
|
+
if ExceptionHandler.config.try(:db)
|
77
|
+
def self.table_name
|
78
|
+
ExceptionHandler.config.db
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
##################################
|
83
|
+
##################################
|
84
|
+
|
85
|
+
####################
|
86
|
+
# Options #
|
87
|
+
####################
|
88
|
+
|
89
|
+
# => Email
|
90
|
+
# => after_initialize invoked after .new method called
|
91
|
+
# => Should have been after_create but user may not save
|
92
|
+
after_initialize Proc.new { |e| ExceptionHandler::ExceptionMailer.new_exception(e).deliver } if ExceptionHandler.config.try(:email) && ExceptionHandler.config.email.is_a?(String)
|
93
|
+
|
94
|
+
# => Attributes
|
95
|
+
attr_accessor :request, :klass, :exception, :description
|
96
|
+
attr_accessor *ATTRS unless ExceptionHandler.config.try(:db)
|
97
|
+
|
98
|
+
# => Validations
|
99
|
+
validates :klass, exclusion: { in: [ActionController::RoutingError, AbstractController::ActionNotFound, ActiveRecord::RecordNotFound], message: "%{value}" }, if: "referer.blank?"
|
100
|
+
validates :user_agent, format: { without: Regexp.new( BOTS.join("|"), Regexp::IGNORECASE ) }
|
101
|
+
|
102
|
+
##################################
|
103
|
+
##################################
|
104
|
+
|
105
|
+
####################################
|
106
|
+
# Virtual
|
107
|
+
####################################
|
108
|
+
|
109
|
+
# => Klass
|
110
|
+
# => Used for validation (needs to be cleaned up in 0.7.0)
|
111
|
+
def klass
|
112
|
+
exception.class
|
113
|
+
end
|
114
|
+
|
115
|
+
# => Exception (virtual)
|
116
|
+
def exception
|
117
|
+
request.env['action_dispatch.exception']
|
118
|
+
end
|
119
|
+
|
120
|
+
# => Description
|
121
|
+
def description
|
122
|
+
I18n.with_options scope: [:exception], message: message, status: status do |i18n|
|
123
|
+
i18n.t response, default: status
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
####################################
|
128
|
+
# Exception
|
129
|
+
####################################
|
130
|
+
|
131
|
+
# => Class Name
|
132
|
+
def class_name
|
133
|
+
exception.class.name
|
134
|
+
end
|
135
|
+
|
136
|
+
# => Message
|
137
|
+
def message
|
138
|
+
exception.message
|
139
|
+
end
|
140
|
+
|
141
|
+
# => Trace
|
142
|
+
def trace
|
143
|
+
exception.backtrace.join("\n")
|
144
|
+
end
|
145
|
+
|
146
|
+
####################################
|
147
|
+
# Request
|
148
|
+
####################################
|
149
|
+
|
150
|
+
# => Target URL
|
151
|
+
def target
|
152
|
+
request.url
|
153
|
+
end
|
154
|
+
|
155
|
+
# => Referrer URL
|
156
|
+
def referer
|
157
|
+
request.referer
|
158
|
+
end
|
159
|
+
|
160
|
+
# => Params
|
161
|
+
def params
|
162
|
+
request.params.inspect
|
163
|
+
end
|
164
|
+
|
165
|
+
# => User Agent
|
166
|
+
def user_agent
|
167
|
+
request.user_agent
|
168
|
+
end
|
169
|
+
|
170
|
+
####################################
|
171
|
+
# Other
|
172
|
+
####################################
|
173
|
+
|
174
|
+
# => Status code (404, 500 etc)
|
175
|
+
def status
|
176
|
+
ActionDispatch::ExceptionWrapper.new(request.env, exception).status_code.to_s
|
177
|
+
end
|
178
|
+
|
179
|
+
# => Server Response ("Not Found" etc)
|
180
|
+
def response
|
181
|
+
ActionDispatch::ExceptionWrapper.rescue_responses[class_name]
|
182
|
+
end
|
183
|
+
|
184
|
+
##################################
|
185
|
+
##################################
|
186
|
+
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
############################################################
|
191
|
+
############################################################
|
@@ -1,3 +1,3 @@
|
|
1
|
-
<%= content_tag :div, class: "exception", data: { status: @exception.status, response: @exception.response.to_s.humanize, rails: Rails.version }, onclick: "location.href=\"#{root_url}\";" do %>
|
1
|
+
<%= content_tag :div, class: "exception", data: { status: @exception.status, response: @exception.response.to_s.humanize, rails: Rails.version }, onclick: ("location.href=\"#{root_url}\";" if @exception.status == "500" && Rails.application.routes.recognize_path("/")), title: ("Return Home" if @exception.status == "500" && Rails.application.routes.recognize_path("/")) do %>
|
2
2
|
<%= content_tag :span, @exception.description.html_safe %>
|
3
3
|
<% end %>
|
@@ -1,4 +1,4 @@
|
|
1
|
-
<h1><%= Rails.application.class.parent_name %> (<%= Rails.env.titleize %>)</h1>
|
2
|
-
|
3
|
-
<strong><u><%= @exception.response %> (<%= @exception.status %>)</u></strong>
|
4
|
-
<p><%= @exception.message %></p>
|
1
|
+
<h1><%= Rails.application.class.parent_name %> (<%= Rails.env.titleize %>)</h1>
|
2
|
+
|
3
|
+
<strong><u><%= @exception.response %> (<%= @exception.status %>)</u></strong>
|
4
|
+
<p><%= @exception.message %></p>
|
@@ -1,21 +1,27 @@
|
|
1
|
-
<!DOCTYPE html>
|
2
|
-
<html>
|
3
|
-
<!-- Head -->
|
4
|
-
<head>
|
5
|
-
|
6
|
-
<%=
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
<%
|
21
|
-
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<!-- Head -->
|
4
|
+
<head>
|
5
|
+
<!-- Info -->
|
6
|
+
<%= content_tag :title, "Error - #{@exception.status} (#{@exception.response.to_s.gsub("_", " ").titleize}) | #{Rails.application.class.parent_name.titleize}" %>
|
7
|
+
|
8
|
+
<!-- Styling -->
|
9
|
+
<%= stylesheet_link_tag :exception_handler %>
|
10
|
+
<%= favicon_link_tag "exception_handler/favicon.ico" %>
|
11
|
+
|
12
|
+
<!-- Auth -->
|
13
|
+
<%= csrf_meta_tags %>
|
14
|
+
</head>
|
15
|
+
|
16
|
+
<!-- Body -->
|
17
|
+
<%= content_tag :body, yield %>
|
18
|
+
|
19
|
+
<!-- Footer -->
|
20
|
+
<% if ExceptionHandler.config.try(:social) %>
|
21
|
+
<%= content_tag :footer do %>
|
22
|
+
<% ExceptionHandler.config.social.each do |k,v| %>
|
23
|
+
<%= link_to image_tag("exception_handler/connect/#{k}.png"), ExceptionHandler::Config::SOCIAL[k] + "/" + v, id: k, title: "#{k.to_s.titleize} Feed", target: :blank if v %>
|
24
|
+
<% end %>
|
25
|
+
<% end %>
|
26
|
+
<% end %>
|
27
|
+
</html>
|