rails_db_admin 3.0.0 → 3.2.0

Sign up to get free protection for your applications and to get access to all the features.
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>