rails_db_admin 3.0.0 → 3.2.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.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/{public → app/assets}/images/icons/rails_db_admin/rails_db_admin_16x16.png +0 -0
  3. data/{public → app/assets}/images/icons/rails_db_admin/rails_db_admin_24x24.png +0 -0
  4. data/{public → app/assets}/images/icons/rails_db_admin/rails_db_admin_32x32.png +0 -0
  5. data/{public → app/assets}/images/icons/rails_db_admin/rails_db_admin_64x64.png +0 -0
  6. data/{public → app/assets}/images/icons/rails_db_admin/rails_db_admin_light_16x16.png +0 -0
  7. data/app/assets/images/splash/db_admin_splash.png +0 -0
  8. data/{public → app/assets}/images/splash/images/console-icon.png +0 -0
  9. data/{public → app/assets}/images/splash/images/data-model-icon.png +0 -0
  10. data/app/assets/javascripts/erp_app/desktop/applications/rails_db_admin/app.js +20 -0
  11. data/{public → app/assets}/javascripts/erp_app/desktop/applications/rails_db_admin/database_combo.js +1 -1
  12. data/{public → app/assets}/javascripts/erp_app/desktop/applications/rails_db_admin/module.js +227 -135
  13. data/{public → app/assets}/javascripts/erp_app/desktop/applications/rails_db_admin/queries_tree_menu.js +6 -6
  14. data/app/assets/javascripts/erp_app/desktop/applications/rails_db_admin/query_panel.js +383 -0
  15. data/{public → app/assets}/javascripts/erp_app/desktop/applications/rails_db_admin/readonly_table_data_grid.js +0 -0
  16. data/app/assets/javascripts/erp_app/desktop/applications/rails_db_admin/reports/ParamWindow.js +426 -0
  17. data/app/assets/javascripts/erp_app/desktop/applications/rails_db_admin/reports/ParamsManager.js +177 -0
  18. data/app/assets/javascripts/erp_app/desktop/applications/rails_db_admin/reports/RolesPanel.js +95 -0
  19. data/app/assets/javascripts/erp_app/desktop/applications/rails_db_admin/reports/SetDefaultWindow.js +244 -0
  20. data/app/assets/javascripts/erp_app/desktop/applications/rails_db_admin/reports/Settings.js +155 -0
  21. data/app/assets/javascripts/erp_app/desktop/applications/rails_db_admin/reports/TreePanel.js +391 -0
  22. data/app/assets/javascripts/erp_app/desktop/applications/rails_db_admin/splash_screen.js +163 -0
  23. data/{public → app/assets}/javascripts/erp_app/desktop/applications/rails_db_admin/tables_tree_menu.js +5 -3
  24. data/app/assets/javascripts/erp_app/shared/MultiOption.js +151 -0
  25. data/app/assets/javascripts/erp_app/shared/ReportsParams.js +289 -0
  26. data/app/assets/javascripts/erp_app/shared/rails_db_admin_shared.js +13 -0
  27. data/{public/stylesheets/erp_app/desktop/applications/rails_db_admin/rails_db_admin.css → app/assets/stylesheets/erp_app/shared/rails_db_admin_shared.css} +5 -10
  28. data/app/controllers/rails_db_admin/erp_app/desktop/base_controller.rb +7 -6
  29. data/app/controllers/rails_db_admin/erp_app/desktop/queries_controller.rb +88 -74
  30. data/app/controllers/rails_db_admin/erp_app/desktop/reports_controller.rb +361 -30
  31. data/app/controllers/rails_db_admin/reports/base_controller.rb +105 -22
  32. data/app/mailers/report_mailer.rb +15 -0
  33. data/app/models/report.rb +227 -14
  34. data/app/views/rails_db_admin/reports/base/error_report.erb +27 -0
  35. data/app/views/report_mailer/email_report.html.erb +255 -0
  36. data/config/routes.rb +2 -1
  37. data/db/data_migrations/20110816005525_rails_db_admin_application.rb +6 -14
  38. data/db/data_migrations/20151015093724_add_report_role_type.rb +15 -0
  39. data/db/migrate/20121210160131_add_reports.rb +2 -0
  40. data/db/migrate/20150926061259_add_meta_data_to_reports.rb +9 -0
  41. data/lib/rails_db_admin.rb +4 -2
  42. data/lib/rails_db_admin/engine.rb +12 -4
  43. data/lib/rails_db_admin/erp_string_parser.rb +26 -0
  44. data/lib/rails_db_admin/extensions/railties/action_view/helpers/report_helper.rb +167 -10
  45. data/lib/rails_db_admin/extjs/json_data_builder.rb +36 -20
  46. data/lib/rails_db_admin/query_params.rb +14 -0
  47. data/lib/rails_db_admin/query_support.rb +56 -46
  48. data/lib/rails_db_admin/services/report_helper.rb +241 -0
  49. data/lib/rails_db_admin/version.rb +1 -1
  50. metadata +41 -30
  51. data/app/assets/javascripts/rails_db_admin/application.js +0 -9
  52. data/app/assets/stylesheets/rails_db_admin/application.css +0 -7
  53. data/app/views/rails_db_admin/reports/base/show_report.erb +0 -11
  54. data/db/migrate/20131112013053_add_rails_db_admin_missing_indexes.rb +0 -17
  55. data/lib/rails_db_admin/report_support.rb +0 -52
  56. data/public/images/splash/splash.png +0 -0
  57. data/public/javascripts/erp_app/desktop/applications/rails_db_admin/query_panel.js +0 -223
  58. data/public/javascripts/erp_app/desktop/applications/rails_db_admin/report_panel.js +0 -78
  59. data/public/javascripts/erp_app/desktop/applications/rails_db_admin/reports_tree_panel.js +0 -237
  60. data/public/javascripts/erp_app/desktop/applications/rails_db_admin/splash_screen.js +0 -136
@@ -1,38 +1,121 @@
1
1
  module RailsDbAdmin
2
2
  module Reports
3
3
 
4
- class BaseController < ::ErpApp::Desktop::BaseController
4
+ class BaseController < ::ErpApp::ApplicationController
5
+ layout nil
6
+ before_filter :require_login
5
7
 
6
- def index
7
- report_iid = params[:iid]
8
+ def show
9
+ report_helper = RailsDbAdmin::Services::ReportHelper.new
8
10
 
9
- @report = Report.find_by_internal_identifier(report_iid)
11
+ respond_to do |format|
10
12
 
11
- if @report.nil?
12
- render :no_report, :layout => false
13
+ format.html {
14
+
15
+ result = report_helper.build_report(params[:iid],
16
+ [:html],
17
+ build_report_params)
18
+ if result.is_a? String
19
+ @error = result
20
+ render :error_report
21
+ else
22
+ render inline: result.first[:data]
23
+ end
24
+ }
25
+
26
+ format.pdf {
27
+ result = report_helper.build_report(params[:iid],
28
+ [:pdf],
29
+ build_report_params)
30
+
31
+ if result.is_a? String
32
+ @error = result
33
+ render :error_report
34
+ else
35
+ send_data result.first[:data],
36
+ filename: result.first[:name],
37
+ type: 'application/pdf',
38
+ disposition: :inline
39
+ end
40
+ }
41
+
42
+ format.csv {
43
+ result = report_helper.build_report(params[:iid],
44
+ [:csv],
45
+ build_report_params)
46
+ if result.is_a? String
47
+ @error = result
48
+ render :error_report
49
+ else
50
+ send_data result.first[:data],
51
+ filename: result.first[:name],
52
+ type: 'application/csv'
53
+ end
54
+ }
55
+
56
+ end
57
+
58
+ end
59
+
60
+ def email
61
+ report_helper = RailsDbAdmin::Services::ReportHelper.new
62
+ file_attachments = report_helper.build_report(params[:iid],
63
+ params[:report_format].map { |format| format.to_sym },
64
+ build_report_params)
65
+
66
+ if file_attachments.is_a? String
67
+ render :json => {success: false, message: file_attachments}
13
68
  else
14
- respond_to do |format|
15
- format.html {
16
- render :show_report, :layout => false
17
- }
69
+ default_name = Report.find_by_internal_identifier(params[:iid]).name
18
70
 
19
- format.csv {
20
- data = RailsDbAdmin::ReportSupport.new.render_report(report_iid, :csv)
71
+ to_email = params[:send_to]
72
+ cc_email = params[:cc_email]
73
+ message = params[:message].blank? ? "Attached is report #{default_name}" : params[:message]
74
+ subject = params[:subject].blank? ? "Attached is report #{default_name}" : params[:subject]
21
75
 
22
- send_data(data, :filename => "#{@report.name}.csv", :type => "application/csv")
23
- }
76
+ ReportMailer.email_report(to_email, cc_email, file_attachments, subject, message, current_user.party.dba_organization).deliver
24
77
 
25
- format.pdf {
26
- data = RailsDbAdmin::ReportSupport.new.render_report(report_iid, :pdf)
78
+ render json: {success: true}
79
+ end
80
+
81
+ rescue StandardError => ex
82
+ Rails.logger.error ex.message
83
+ Rails.logger.error ex.backtrace.join("\n")
84
+
85
+ # email notification
86
+ ExceptionNotifier.notify_exception(ex) if defined? ExceptionNotifier
87
+
88
+ render :json => {success: false, message: ex.message}
89
+ end
27
90
 
28
- send_data(data, :filename => "#{@report.name}.pdf", :type => "application/pdf")
29
- }
30
- end
91
+ protected
92
+
93
+ # Get the report data by running executing the sql
94
+ #
95
+ def build_report_params
96
+ report_params = params[:report_params]
97
+
98
+ parsed_report_params = if report_params.blank?
99
+ {}
100
+ else
101
+ JSON.parse(report_params).symbolize_keys
102
+ end
103
+
104
+ # add current_user to locals
105
+ parsed_report_params[:current_user] = current_user
106
+
107
+ # add utc offset if it wasn't in the report params
108
+ if parsed_report_params[:client_utc_offset].blank?
109
+ parsed_report_params[:client_utc_offset] = params[:client_utc_offset]
31
110
  end
32
111
 
112
+ if params[:business_module_id].present?
113
+ parsed_report_params[:business_module_id] = params[:business_module_id]
114
+ end
33
115
 
116
+ parsed_report_params
34
117
  end
35
118
 
36
- end #BaseController
37
- end #Reports
38
- end #RailsDbAdmin
119
+ end # BaseController
120
+ end # Reports
121
+ end # RailsDbAdmin
@@ -0,0 +1,15 @@
1
+ class ReportMailer < ActionMailer::Base
2
+ default :from => ErpTechSvcs::Config.email_notifications_from
3
+
4
+ def email_report(to_email, cc_email, file_attachments, subject, message, dba_organization=nil)
5
+ @message = message
6
+ @subject = subject
7
+
8
+ file_attachments.each do |file_attachment|
9
+ attachments[file_attachment[:name]] = file_attachment[:data]
10
+ end
11
+
12
+ mail(:to => to_email, :subject => subject, :cc => cc_email)
13
+ end
14
+
15
+ end
@@ -1,31 +1,244 @@
1
1
  class Report < ActiveRecord::Base
2
2
  attr_protected :created_at, :updated_at
3
3
  attr_accessible :name, :internal_identifier
4
-
4
+
5
5
  validates :name, :internal_identifier, :uniqueness => true
6
6
 
7
7
  before_create :set_default_template
8
8
 
9
+ after_create :create_report_files!
10
+
11
+ before_destroy :delete_report_files!
12
+
13
+ has_file_assets
14
+
15
+ has_party_roles
16
+
17
+ is_json :meta_data
18
+
19
+ REPORT_STRUCTURE = ['stylesheets', 'javascripts', 'images', 'templates']
20
+
21
+ class << self
22
+ def iid(internal_identifier)
23
+ find_by_internal_identifier(internal_identifier)
24
+ end
25
+
26
+ def import(file)
27
+ ActiveRecord::Base.transaction do
28
+ name_and_iid = file.original_filename.to_s.gsub(/(^.*(\\|\/))|(\.zip$)/, '')
29
+ report_name = name_and_iid.split('[').first
30
+ report_iid = name_and_iid.split('[').last.gsub(']', '')
31
+ return false unless valid_report?(file)
32
+ Report.skip_callback(:create, :after, :create_report_files!)
33
+ report = Report.create(:name => report_name, :internal_identifier => report_iid)
34
+ report.import(file)
35
+ Report.set_callback(:create, :after, :create_report_files!)
36
+ end
37
+ end
38
+
39
+ def make_tmp_dir
40
+ Pathname.new(Rails.root.to_s + "/tmp/reports/tmp_#{Time.now.to_i.to_s}/").tap do |dir|
41
+ FileUtils.mkdir_p(dir) unless dir.exist?
42
+ end
43
+ end
44
+
45
+ def valid_report?(file)
46
+ valid = false
47
+ Zip::ZipFile.open(file.path) do |zip|
48
+ zip.sort.each do |entry|
49
+ entry.name.split('/').each do |file|
50
+ valid = true if REPORT_STRUCTURE.include?(file)
51
+ end
52
+ end
53
+ end
54
+ valid
55
+ end
56
+ end
57
+
58
+ def base_dir
59
+ "#{Rails.root}/public/compass_ae_reports/#{self.internal_identifier}"
60
+ end
61
+
62
+ def url
63
+ "/public/compass_ae_reports/#{self.internal_identifier}"
64
+ end
65
+
66
+ def import(file)
67
+ file_support = ErpTechSvcs::FileSupport::Base.new(:storage => Rails.application.config.erp_tech_svcs.file_storage)
68
+ file = ActionController::UploadedTempfile.new("uploaded-report").tap do |f|
69
+ f.puts file.read
70
+ f.original_filename = file.original_filename
71
+ f.read # no idea why we need this here, otherwise the zip can't be opened
72
+ end unless file.path
73
+
74
+ Zip::ZipFile.open(file.path) do |zip|
75
+ zip.each do |entry|
76
+ if entry.name.split('/').last == 'base.html.erb'
77
+ name = entry.name.sub(/__MACOSX\//, '')
78
+ data = ''
79
+ entry.get_input_stream { |io| data = io.read }
80
+ self.template = data
81
+ self.save
82
+ self.add_file(self.template, File.join(self.url, 'templates', "base.html.erb"))
83
+ elsif entry.name.split('/').last == 'meta_data.yml'
84
+ data = ''
85
+ entry.get_input_stream { |io| data = io.read }
86
+ data = StringIO.new(data) if data.present?
87
+ self.meta_data = YAML.load(data)
88
+ self.save
89
+ elsif entry.name.split('/').last == 'query.sql'
90
+ data = ''
91
+ entry.get_input_stream { |io| data = io.read }
92
+ self.query = data
93
+ self.save
94
+ else
95
+ if entry.file?
96
+ name = entry.name.sub(/__MACOSX\//, '')
97
+ data = ''
98
+ entry.get_input_stream { |io| data = io.read }
99
+ data = StringIO.new(data) if data.present?
100
+ report_file = self.files.where("name = ? and directory = ?", File.basename(name), File.join(self.url, File.dirname(name))).first
101
+ unless report_file.nil?
102
+ report_file.data = data
103
+ report_file.save
104
+ else
105
+ self.add_file(data, File.join(file_support.root, self.url, name))
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
111
+
112
+ end
113
+
114
+ def export
115
+ file_support = ErpTechSvcs::FileSupport::Base.new(:storage => Rails.application.config.erp_tech_svcs.file_storage)
116
+ tmp_dir = Report.make_tmp_dir
117
+ (tmp_dir + "#{name}[#{internal_identifier}].zip").tap do |file_name|
118
+ file_name.unlink if file_name.exist?
119
+ Zip::ZipFile.open(file_name.to_s, Zip::ZipFile::CREATE) do |zip|
120
+ files.each do |file|
121
+ contents = file_support.get_contents(File.join(file_support.root, file.directory, file.name))
122
+ relative_path = file.directory.sub("#{url}", '')
123
+ path = FileUtils.mkdir_p(File.join(tmp_dir, relative_path))
124
+ full_path = File.join(path, file.name)
125
+ File.open(full_path, 'wb+') { |f| f.puts(contents) }
126
+ zip.add(File.join(relative_path[1..relative_path.length], file.name), full_path) if ::File.exists?(full_path)
127
+ end
128
+ ::File.open(tmp_dir + 'query.sql', 'wb+') { |f| f.puts(query) }
129
+ zip.add('query.sql', tmp_dir + 'query.sql')
130
+ ::File.open(tmp_dir + 'meta_data.yml', 'wb+') { |f| f.puts(meta_data.to_yaml) }
131
+ zip.add('meta_data.yml', tmp_dir + 'meta_data.yml')
132
+
133
+ end
134
+ end
135
+ end
136
+
137
+ def stylesheet_path(source)
138
+ file_support = ErpTechSvcs::FileSupport::Base.new(:storage => ErpTechSvcs::Config.file_storage)
139
+ File.join(file_support.root, self.url, 'stylesheets', source)
140
+ end
141
+
142
+ def image_path(source)
143
+ file_support = ErpTechSvcs::FileSupport::Base.new(:storage => ErpTechSvcs::Config.file_storage)
144
+ File.join(file_support.root, self.url, 'images', source)
145
+ end
146
+
147
+ def template_path(source)
148
+ file_support = ErpTechSvcs::FileSupport::Base.new(:storage => ErpTechSvcs::Config.file_storage)
149
+ File.join(file_support.root, self.url, 'templates', source)
150
+ end
151
+
152
+ def javascript_path(source)
153
+ file_support = ErpTechSvcs::FileSupport::Base.new(:storage => ErpTechSvcs::Config.file_storage)
154
+ File.join(file_support.root, self.url, 'javascripts', source)
155
+ end
156
+
157
+ def create_report_files!
158
+ self.add_file(self.template, File.join(self.url, 'templates', "base.html.erb"))
159
+ self.add_file(self.default_stylesheet,File.join(self.url, 'stylesheets', "#{self.internal_identifier}.css") )
160
+ end
161
+
162
+ def delete_report_files!
163
+ file_support = ErpTechSvcs::FileSupport::Base.new(:storage => ErpTechSvcs::Config.file_storage)
164
+ file_support.delete_file(File.join(file_support.root, self.url), :force => true)
165
+ end
166
+
9
167
  def set_default_template
10
168
  self.template =
11
- "<h3><%= title %></h3>
12
-
169
+ "<%= bootstrap_load %>
170
+ <%= report_stylesheet_link_tag '#{self.internal_identifier}','#{self.internal_identifier}.css' %>
171
+ <h3> <%= title %> </h3>
13
172
  <table>
14
- <tr>
15
- <% columns.each do |column| %>
16
- <th><%= column %></th>
17
- <% end %>
18
- </tr>
19
- <% rows.each do |row| %>
173
+ <thead>
20
174
  <tr>
21
- <% row.values.each do |value| %>
22
- <td><%= value %></td>
175
+ <% columns.each do |column| %>
176
+ <th> <%= column %> </th>
23
177
  <% end %>
24
178
  </tr>
179
+ </thead>
180
+ <% rows.each do |row| %>
181
+ <tr>
182
+ <% row.values.each do |value| %>
183
+ <td> <%= value %> </td>
184
+ <% end %>
185
+ </tr>
25
186
  <% end %>
26
187
  </table>
188
+ <%= jquery_load %>"
189
+ end
190
+
191
+ def default_stylesheet
192
+ "body{
193
+ padding: 5px;
194
+ }
195
+
196
+ table{
197
+ width: 100%;
198
+ }
199
+
200
+ table tr{
201
+ page-break-inside: avoid !important;
202
+ }
27
203
 
28
- <%= report_download_link(unique_name, :csv, 'Download CSV') %> |
29
- <%= report_download_link(unique_name, :pdf, 'Download PDF') %>"
204
+ table th{
205
+ border: 1px solid black;
206
+ color: white;
207
+ border-collapse:collapse;
208
+ font-size: 16px;
209
+ vertical-align:center;
210
+ text-align: center;
211
+ background-color: 537697;
212
+ padding-top: 2px;
213
+ padding-bottom: 2px;
214
+ padding-left: 15px;
215
+ padding-right: 15px;
216
+
217
+ }
218
+
219
+ table td{
220
+ border: 1px solid black;
221
+ border-collapse:collapse;
222
+ font-size: 14px;
223
+ vertical-align:top;
224
+ padding-top: 2px;
225
+ padding-left: 15px;
226
+ padding-right: 15px;
227
+ }
228
+
229
+ td.no-border {
230
+ border: 0px;
231
+ height: 20px;
232
+ }
233
+
234
+ .bold{
235
+ font-weight: bold;
236
+ height: 20px;
237
+ }
238
+
239
+ .left-padded{
240
+ padding-left: 20px;
241
+ }"
30
242
  end
31
- end
243
+
244
+ end
@@ -0,0 +1,27 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Report Error</title>
5
+ <style type="text/css">
6
+ body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
7
+ div.dialog {
8
+ width: 25em;
9
+ padding: 0 4em;
10
+ margin: 4em auto 0 auto;
11
+ border: 1px solid #ccc;
12
+ border-right-color: #999;
13
+ border-bottom-color: #999;
14
+ }
15
+ h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
16
+ </style>
17
+ </head>
18
+
19
+ <body>
20
+ <!-- This file lives in public/500.html -->
21
+ <div class="dialog">
22
+ <h1>We're sorry, but something went wrong.</h1>
23
+ <p><%= @error %></p>
24
+ </div>
25
+ </body>
26
+ </html>
27
+
@@ -0,0 +1,255 @@
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
2
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
3
+ <html xmlns="http://www.w3.org/1999/xhtml">
4
+ <head>
5
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
6
+ <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1"/>
7
+ <title>Reports</title>
8
+ <style type="text/css">
9
+ .ReadMsgBody {
10
+ width: 100%;
11
+ background-color: #ffffff;
12
+ }
13
+
14
+ .ExternalClass {
15
+ width: 100%;
16
+ background-color: #ffffff;
17
+ }
18
+
19
+ html {
20
+ width: 100%;
21
+ }
22
+
23
+ body {
24
+ -webkit-text-size-adjust: none;
25
+ -ms-text-size-adjust: none;
26
+ margin: 0;
27
+ padding: 0;
28
+ }
29
+
30
+ table {
31
+ border-spacing: 0;
32
+ border-collapse: collapse;
33
+ }
34
+
35
+ img {
36
+ display: block !important;
37
+ }
38
+
39
+ table td {
40
+ border-collapse: collapse;
41
+ }
42
+
43
+ .top-margin {
44
+ height: 50px;
45
+ }
46
+
47
+ .main-header {
48
+ padding: 50px 100px;
49
+ background: rgba(0, 0, 0, 0.5);
50
+ font-weight: 300;
51
+ }
52
+
53
+ .footer {
54
+ margin-top: 20px;
55
+ }
56
+
57
+ /* ----------- media queries (responsive) ----------- */
58
+
59
+ @media only screen and (max-width: 640px) {
60
+ body .show {
61
+ display: block !important;
62
+ }
63
+
64
+ body .hide {
65
+ display: none !important;
66
+ }
67
+
68
+ body .container590 {
69
+ width: 440px !important;
70
+ }
71
+ }
72
+
73
+ @media only screen and (max-width: 480px) {
74
+ body .show {
75
+ display: block !important;
76
+ }
77
+
78
+ body .hide {
79
+ display: none !important;
80
+ }
81
+
82
+ body .container590 {
83
+ width: 280px !important;
84
+ }
85
+ }
86
+
87
+ </style>
88
+ </head>
89
+
90
+ <body>
91
+
92
+ <div class="top-margin"></div>
93
+
94
+ <table border="0" width="100%" cellpadding="0" cellspacing="0">
95
+ <tr>
96
+ <td align="center" bgcolor="323232"
97
+ style="background-image: url('https://truenorthtechnology.com/download/life3.jpg?path=&disposition=inline'); background-size: cover; background-position: top center; background-repeat: repeat;">
98
+
99
+ <table border="0" align="center" cellpadding="0" cellspacing="0" class="container590">
100
+
101
+ <tr>
102
+ <td height="25" style="font-size: 25px; line-height: 25px;">&nbsp;</td>
103
+ </tr>
104
+
105
+
106
+ <tr>
107
+ <td height="30" style="font-size: 30px; line-height: 30px;">&nbsp;</td>
108
+ </tr>
109
+ <tr>
110
+ <td height="50" style="font-size: 50px; line-height: 50px;">&nbsp;</td>
111
+ </tr>
112
+
113
+ <tr>
114
+ <td class="main-header" align="center" height="42"
115
+ style="color: #ffffff; font-size: 32px; font-family: 'Open Sans', Calibri, sans-serif; mso-line-height-rule: exactly; line-height: 40px;">
116
+
117
+ <!-- ============ title header ============ -->
118
+
119
+ <div style="line-height: 34px;">
120
+ <span>
121
+ <multiline>
122
+ <strong><%= @subject %></strong>
123
+ </multiline>
124
+ </span>
125
+ </div>
126
+ </td>
127
+ </tr>
128
+
129
+ <tr>
130
+ <td height="10" style="font-size: 10px; line-height: 10px;">&nbsp;</td>
131
+ </tr>
132
+
133
+
134
+ <tr>
135
+ <td height="50" style="font-size: 50px; line-height: 50px;">&nbsp;</td>
136
+ </tr>
137
+
138
+ <tr>
139
+ <td align="center">
140
+
141
+ <table class="cta-button" border="0" align="center" width="163" cellpadding="0" cellspacing="0"
142
+ style="border: none">
143
+ <tr>
144
+ <td height="10" style="font-size: 10px; line-height: 10px;">&nbsp;</td>
145
+ </tr>
146
+
147
+ <tr>
148
+ <td>
149
+ <table border="0" align="center" cellpadding="0" cellspacing="0">
150
+ <tr>
151
+ <td align="center"
152
+ style="color: #ffffff; font-size: 13px; font-family: 'Open Sans', Calibri, sans-serif; font-weight: 600; line-height: 24px;">
153
+ <!-- ============ headline button ============ -->
154
+ </td>
155
+
156
+ <td width="14" align="right" valign="middle">
157
+
158
+ </td>
159
+ </tr>
160
+ </table>
161
+ </td>
162
+ </tr>
163
+
164
+ <tr>
165
+ <td height="10" style="font-size: 10px; line-height: 10px;">&nbsp;</td>
166
+ </tr>
167
+
168
+ </table>
169
+ </td>
170
+ </tr>
171
+
172
+ <tr>
173
+ <td height="115" style="font-size: 115px; line-height: 115px;">&nbsp;</td>
174
+ </tr>
175
+
176
+ </table>
177
+ </td>
178
+ </tr>
179
+
180
+ </table>
181
+
182
+
183
+ <table width="100%" border="0" align="center" cellpadding="0" cellspacing="0" bgcolor="#ffffff">
184
+ <tr>
185
+ <td height="50">&nbsp;</td>
186
+ </tr>
187
+
188
+ <tr>
189
+ <td>
190
+ <table class="container590" border="0" align="center" width="590" cellpadding="0" cellspacing="0">
191
+ <tr>
192
+ <td>
193
+ <p class="instructions" style="color: #555; font-size: 24px; font-family: 'Open Sans', Calibri, sans-serif; mso-line-height-rule: exactly; line-height: 42px;">
194
+ <%= raw @message -%>
195
+ </p>
196
+ </td>
197
+ </tr>
198
+ </table>
199
+
200
+ </td>
201
+ </tr>
202
+
203
+ <tr>
204
+ <td height="100" style="font-size: 100px; line-height: 100px;">&nbsp;</td>
205
+ </tr>
206
+
207
+ </table>
208
+
209
+
210
+ <table class="footer" border="0" width="100%" cellpadding="0" cellspacing="0" bgcolor="2d2d2d">
211
+
212
+ <tr>
213
+ <td height="20" style="font-size: 20px; line-height: 20px;">&nbsp;</td>
214
+ </tr>
215
+
216
+ <tr>
217
+ <td align="center">
218
+
219
+ <table border="0" align="center" width="590" class="container590" cellpadding="0" cellspacing="0">
220
+
221
+ <tr>
222
+ <td align="center">
223
+
224
+ <table border="0" class="container590" align="center" cellpadding="0" cellspacing="0">
225
+ <tr>
226
+ <td align="center"
227
+ style="color: #717171; font-size: 14px; font-family: 'Open Sans', Calibri, sans-serif; line-height: 25px;">
228
+ <div style=" line-height: 25px;">
229
+ <span>
230
+ <multiline>
231
+ © <%= Time.now.year %> Copyright. All Rights Reserved.
232
+ </multiline>
233
+ </span>
234
+ </div>
235
+ </td>
236
+ </tr>
237
+
238
+ </table>
239
+
240
+ </td>
241
+ </tr>
242
+
243
+ </table>
244
+ </td>
245
+ </tr>
246
+
247
+ <tr>
248
+ <td height="20" style="font-size: 20px; line-height: 20px;">&nbsp;</td>
249
+ </tr>
250
+
251
+ </table>
252
+
253
+
254
+ </body>
255
+ </html>