sinatra-hexacta 0.0.1 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/lib/sinatra/extensions/antiquity.rb +34 -0
  3. data/lib/sinatra/extensions/date.rb +49 -0
  4. data/lib/sinatra/extensions/generalmail.rb +26 -0
  5. data/lib/sinatra/extensions/init.rb +8 -0
  6. data/lib/sinatra/extensions/mail.rb +35 -0
  7. data/lib/sinatra/extensions/mailbuilder.rb +38 -0
  8. data/lib/sinatra/extensions/mailsender.rb +88 -0
  9. data/lib/sinatra/extensions/notification.rb +44 -0
  10. data/lib/sinatra/handlers/errors.rb +10 -3
  11. data/lib/sinatra/handlers/init.rb +3 -1
  12. data/lib/sinatra/handlers/notifications.rb +17 -28
  13. data/lib/sinatra/handlers/params.rb +26 -0
  14. data/lib/sinatra/handlers/reports.rb +28 -1
  15. data/lib/sinatra/helpers/alerts.rb +27 -0
  16. data/lib/sinatra/helpers/cas.rb +92 -0
  17. data/lib/sinatra/helpers/charts.rb +20 -4
  18. data/lib/sinatra/helpers/init.rb +4 -0
  19. data/lib/sinatra/helpers/inputs.rb +2 -2
  20. data/lib/sinatra/helpers/libraries.rb +2 -2
  21. data/lib/sinatra/helpers/reports.rb +27 -0
  22. data/lib/sinatra/helpers/schedule.rb +68 -0
  23. data/lib/sinatra/helpers/subscriptions.rb +1 -1
  24. data/lib/sinatra/hexacta.rb +24 -5
  25. data/lib/sinatra/public/css/app.min.1.css +132 -6
  26. data/lib/sinatra/public/js/app.js +49 -42
  27. data/lib/sinatra/public/vendors/chartist/chartist-plugin-legend.js +237 -0
  28. data/lib/sinatra/public/vendors/typeahead.js/typeahead.bundle.min.js +1 -2
  29. data/lib/sinatra/views/alerts/empty.slim +7 -0
  30. data/lib/sinatra/views/alerts/error.slim +7 -0
  31. data/lib/sinatra/views/alerts/info.slim +7 -0
  32. data/lib/sinatra/views/alerts/warning.slim +7 -0
  33. data/lib/sinatra/views/charts/bar.slim +56 -0
  34. data/lib/sinatra/views/charts/gauge.slim +30 -0
  35. data/lib/sinatra/views/charts/{simple_line.slim → line.slim} +7 -8
  36. data/lib/sinatra/views/charts/pie.slim +34 -0
  37. data/lib/sinatra/views/charts/stacked_bar.slim +36 -0
  38. data/lib/sinatra/views/inputs/multiple_select.slim +4 -0
  39. data/lib/sinatra/views/inputs/select.slim +3 -0
  40. data/lib/sinatra/views/libraries/include_css.slim +1 -1
  41. data/lib/sinatra/views/libraries/include_js_after.slim +0 -24
  42. data/lib/sinatra/views/libraries/include_js_before.slim +1 -0
  43. data/lib/sinatra/views/notifications.slim +37 -31
  44. data/lib/sinatra/views/notifications/form.slim +4 -4
  45. data/lib/sinatra/views/notifications/new.slim +4 -4
  46. data/lib/sinatra/views/notifications/widget.slim +9 -5
  47. data/lib/sinatra/views/reports/pick_date.slim +14 -0
  48. data/lib/sinatra/views/reports/pick_dates.slim +16 -0
  49. data/lib/sinatra/views/reports/pick_month.slim +14 -0
  50. data/lib/sinatra/views/reports/pick_year.slim +14 -0
  51. metadata +30 -4
@@ -0,0 +1,26 @@
1
+ # encoding: utf-8
2
+ module Sinatra
3
+ module ParamsHandler
4
+
5
+ def enable_params
6
+ p "Enabling params handler..."
7
+
8
+ before "*" do
9
+ unless params.nil?
10
+ tmp = {}
11
+ for key in params.keys
12
+ value = params[key]
13
+ value = value.join(",").split(",") if value.respond_to? :join
14
+ tmp[key.to_sym] = value
15
+ end
16
+ @params.clear
17
+ @params = tmp
18
+ end
19
+ end
20
+
21
+ end
22
+
23
+ end
24
+
25
+ register ParamsHandler
26
+ end
@@ -1 +1,28 @@
1
- #TOBEDONE
1
+ # encoding: utf-8
2
+ module Sinatra
3
+ module ReportHandler
4
+ extend Hexacta
5
+
6
+ def enable_reports
7
+ p "Enabling reports..."
8
+ before '/reports/*' do
9
+ error(403) if authenticated(User).permissions.empty?
10
+ end
11
+
12
+ get '/reports' do
13
+ slim :reports
14
+ end
15
+
16
+ get '/reports/:report_name' do |report_name|
17
+ date = Date.parse(params["date"]) unless params["date"].nil?
18
+ date ||= Date.today
19
+ headers "Content-Disposition" => "attachment;filename=#{report_name}.#{date}.xls",
20
+ "Content-Type" => "application/octet-stream"
21
+ slim "reports/#{report_name}".to_sym, locals: { :date => date }, :layout => false
22
+ end
23
+ end
24
+
25
+ end
26
+
27
+ register ReportHandler
28
+ end
@@ -0,0 +1,27 @@
1
+ # encoding: utf-8
2
+ module Sinatra
3
+ module AlertHelper
4
+ extend Hexacta
5
+
6
+ def info_alert(option_hash)
7
+ slim "#{Hexacta::GEM_FILE_DIR}/alerts/info".to_sym, locals: option_hash
8
+ end
9
+
10
+ def empty_alert(option_hash)
11
+ slim "#{Hexacta::GEM_FILE_DIR}/alerts/empty".to_sym, locals: option_hash
12
+ end
13
+
14
+ def warning_alert(option_hash)
15
+ slim "#{Hexacta::GEM_FILE_DIR}/alerts/warning".to_sym, locals: option_hash
16
+ end
17
+
18
+ def error_alert(option_hash)
19
+ slim "#{Hexacta::GEM_FILE_DIR}/alerts/error".to_sym, locals: option_hash
20
+ end
21
+
22
+ setup_dir("/app/views/#{Hexacta::GEM_FILE_DIR}/alerts")
23
+ symlink_all("/lib/sinatra/views/alerts","/app/views/#{Hexacta::GEM_FILE_DIR}/alerts")
24
+ end
25
+
26
+ helpers AlertHelper
27
+ end
@@ -0,0 +1,92 @@
1
+ require 'active_support/all' #bug in rubycas client requires this
2
+ require 'rubycas-client'
3
+
4
+ module Sinatra
5
+ module CasHelper
6
+
7
+ CAS_CLIENT = CASClient::Client.new(:cas_base_url => ENV['CAS_BASE_URL'], :log => Logger.new(STDOUT), :ticket_store_config => {:storage_dir => ENV['TICKET_STORE_DIR']})
8
+
9
+ def need_authentication(request, session)
10
+ if session[:cas_ticket]
11
+ if request[:ticket] && session[:cas_ticket] != request[:ticket]
12
+ true
13
+ else
14
+ false
15
+ end
16
+ else
17
+ true
18
+ end
19
+ end
20
+
21
+ def process_cas_login(request, session)
22
+ if request[:ticket] && request[:ticket] != session[:ticket]
23
+
24
+ service_url = read_service_url(request)
25
+ service_ticket = read_ticket(request[:ticket], service_url)
26
+
27
+ CAS_CLIENT.validate_service_ticket(service_ticket)
28
+
29
+ if service_ticket.success
30
+ session[:cas_ticket] = service_ticket.ticket
31
+ session[:cas_user] = service_ticket.user
32
+ else
33
+ redirect request.path_info
34
+ #raise "Service Ticket validation failed! #{st.failure_code} - #{st.failure_message}"
35
+ end
36
+ end
37
+
38
+ end
39
+
40
+ def logged_in?(request, session)
41
+ session[:cas_ticket] && !session[:cas_ticket].empty?
42
+ end
43
+
44
+ def require_authorization(request, session)
45
+ if !logged_in?(request, session)
46
+ service_url = read_service_url(request)
47
+ url = CAS_CLIENT.add_service_to_login_url(service_url)
48
+ redirect url
49
+ end
50
+ end
51
+
52
+ def authenticated(model)
53
+ raise 500 if session[:cas_user].nil? || session[:cas_user].empty?
54
+ model.find(:hxt_id => "#{session[:cas_user]}")
55
+ end
56
+
57
+ private
58
+ def read_ticket(ticket_str, service_url)
59
+ return nil unless ticket_str and !ticket_str.empty?
60
+
61
+ if ticket_str =~ /^PT-/
62
+ CASClient::ProxyTicket.new(ticket_str, service_url)
63
+ else
64
+ CASClient::ServiceTicket.new(ticket_str, service_url)
65
+ end
66
+ end
67
+
68
+ def read_service_url(request)
69
+ service_url = url(request.path_info)
70
+ if request.GET
71
+ params = request.GET.dup
72
+ params.delete(:ticket)
73
+ if params
74
+ [service_url, Rack::Utils.build_nested_query(params)].join('?')
75
+ end
76
+ end
77
+ return service_url
78
+ end
79
+
80
+ def logout_cas(request, session)
81
+ if logged_in?(request, session)
82
+ url = CAS_CLIENT.logout_url()
83
+ session.clear
84
+ str = request.referer
85
+ comeback = "=" + (str.include?("?")? str.slice(0..(str.index('?')-1)) : str) #Hack mal!
86
+ redirect url + (url.include?("?service")? "" : "?service") + comeback
87
+ end
88
+ end
89
+ end
90
+
91
+ helpers CasHelper
92
+ end
@@ -1,15 +1,31 @@
1
1
  # encoding: utf-8
2
2
  module Sinatra
3
- module ChartsHandler
3
+ module ChartsHelper
4
4
  extend Hexacta
5
5
 
6
- def simple_line(option_hash)
7
- slim "#{Hexacta::GEM_FILE_DIR}/charts/simple_line".to_sym, locals: option_hash
6
+ def line_chart(option_hash)
7
+ slim "#{Hexacta::GEM_FILE_DIR}/charts/line".to_sym, locals: option_hash
8
+ end
9
+
10
+ def bar_chart(option_hash)
11
+ slim "#{Hexacta::GEM_FILE_DIR}/charts/bar".to_sym, locals: option_hash
12
+ end
13
+
14
+ def pie_chart(option_hash)
15
+ slim "#{Hexacta::GEM_FILE_DIR}/charts/pie".to_sym, locals: option_hash
16
+ end
17
+
18
+ def gauge_chart(option_hash)
19
+ slim "#{Hexacta::GEM_FILE_DIR}/charts/gauge".to_sym, locals: option_hash
20
+ end
21
+
22
+ def stacked_bar_chart(option_hash)
23
+ slim "#{Hexacta::GEM_FILE_DIR}/charts/stacked_bar".to_sym, locals: option_hash
8
24
  end
9
25
 
10
26
  setup_dir("/app/views/#{Hexacta::GEM_FILE_DIR}/charts")
11
27
  symlink_all("/lib/sinatra/views/charts","/app/views/#{Hexacta::GEM_FILE_DIR}/charts")
12
28
  end
13
29
 
14
- helpers ChartsHandler
30
+ helpers ChartsHelper
15
31
  end
@@ -3,3 +3,7 @@ require_relative 'libraries'
3
3
  require_relative 'inputs'
4
4
  require_relative 'charts'
5
5
  require_relative 'subscriptions'
6
+ require_relative 'cas'
7
+ require_relative 'schedule'
8
+ require_relative 'alerts'
9
+ require_relative 'reports'
@@ -1,6 +1,6 @@
1
1
  # encoding: utf-8
2
2
  module Sinatra
3
- module InputHandler
3
+ module InputHelper
4
4
  extend Hexacta
5
5
 
6
6
  def date_input(option_hash)
@@ -43,5 +43,5 @@ module Sinatra
43
43
  symlink_all("/lib/sinatra/views/inputs","/app/views/#{Hexacta::GEM_FILE_DIR}/inputs")
44
44
  end
45
45
 
46
- helpers InputHandler
46
+ helpers InputHelper
47
47
  end
@@ -1,6 +1,6 @@
1
1
  # encoding: utf-8
2
2
  module Sinatra
3
- module LibrariesHandler
3
+ module LibrariesHelper
4
4
  extend Hexacta
5
5
 
6
6
  def include_css
@@ -21,5 +21,5 @@ module Sinatra
21
21
  symlink_all("/lib/sinatra/views/libraries","/app/views/#{Hexacta::GEM_FILE_DIR}/libraries")
22
22
  end
23
23
 
24
- helpers LibrariesHandler
24
+ helpers LibrariesHelper
25
25
  end
@@ -0,0 +1,27 @@
1
+ # encoding: utf-8
2
+ module Sinatra
3
+ module ReportHelper
4
+ extend Hexacta
5
+
6
+ def pick_date_input(option_hash)
7
+ slim "#{Hexacta::GEM_FILE_DIR}/reports/pick_date".to_sym, locals: option_hash
8
+ end
9
+
10
+ def pick_dates_input(option_hash)
11
+ slim "#{Hexacta::GEM_FILE_DIR}/reports/pick_dates".to_sym, locals: option_hash
12
+ end
13
+
14
+ def pick_month_input(option_hash)
15
+ slim "#{Hexacta::GEM_FILE_DIR}/reports/pick_month".to_sym, locals: option_hash
16
+ end
17
+
18
+ def pick_year_input(option_hash)
19
+ slim "#{Hexacta::GEM_FILE_DIR}/reports/pick_year".to_sym, locals: option_hash
20
+ end
21
+
22
+ setup_dir("/app/views/#{Hexacta::GEM_FILE_DIR}/reports")
23
+ symlink_all("/lib/sinatra/views/reports","/app/views/#{Hexacta::GEM_FILE_DIR}/reports")
24
+ end
25
+
26
+ helpers ReportHelper
27
+ end
@@ -0,0 +1,68 @@
1
+ require 'timeout'
2
+ # encoding: utf-8
3
+ module Sinatra
4
+ module ScheduleHelper
5
+
6
+ @@scheduler = Rufus::Scheduler.new
7
+
8
+ def schedule_every(time)
9
+ @@scheduler.every time do
10
+ begin
11
+ file_path = "/tmp/schedule.lock";
12
+ f = File.open(file_path, "w+")
13
+ # if file was previosly locked then flock throw a exception
14
+ f.flock(File::LOCK_EX)
15
+ ten_minutes = 600
16
+ # if the process overcome ten minutes, the timeout api throw a exception
17
+ Timeout::timeout(ten_minutes) do
18
+
19
+ begin
20
+ yield
21
+ rescue StandardError => error
22
+ title = error.message.split(':')[0].gsub('#<','');
23
+ message = error.backtrace.join(',');
24
+ NotificationSender.instance.send_error(nil,title,message)
25
+ end
26
+
27
+ end
28
+ ensure
29
+ unless f.nil?
30
+ f.flock(File::LOCK_UN)
31
+ f.close
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+ def schedule_at(cron_expression)
38
+ @@scheduler.cron cron_expression do
39
+ begin
40
+ file_path = "/tmp/schedule.lock";
41
+ f = File.open(file_path, "w+")
42
+ # if file was previosly locked then flock throw a exception
43
+ f.flock(File::LOCK_EX)
44
+ ten_minutes = 600
45
+ # if the process overcome ten minutes, the timeout api throw a exception
46
+ Timeout::timeout(ten_minutes) do
47
+
48
+ begin
49
+ yield
50
+ rescue error
51
+ title = error.message.split(':')[0].gsub('#<','');
52
+ message = error.backtrace.join(',');
53
+ NotificationSender.instance.send_error(nil,title,message)
54
+ end
55
+
56
+ end
57
+ ensure
58
+ unless f.nil?
59
+ f.flock(File::LOCK_UN)
60
+ f.close
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+
67
+ register ScheduleHelper
68
+ end
@@ -3,7 +3,6 @@ module Sinatra
3
3
  module SubscriptionHelper
4
4
 
5
5
  def notify_to(user,creator,title,message,label,link=nil)
6
- #return if creator.id == user.id
7
6
  notification = Notification.find_or_create(:user_id => user.id,
8
7
  :creator_id => creator.id,
9
8
  :title => title,
@@ -26,6 +25,7 @@ module Sinatra
26
25
 
27
26
  def notify_error(creator,title,message)
28
27
  Subscription.where(:label => 'error').all.each do |subscription|
28
+ creator = subscription.user if creator.nil?
29
29
  notification = Notification.find(:user_id => subscription.user_id,
30
30
  :creator_id => creator.id,
31
31
  :title => title,
@@ -1,32 +1,51 @@
1
1
  # encoding: utf-8
2
2
  require 'sinatra/base'
3
3
  require 'fileutils'
4
+ require 'sucker_punch'
5
+ require 'rufus-scheduler'
4
6
 
5
7
  module Sinatra
8
+
6
9
  module Hexacta
7
10
  GEM_FILE_DIR = "sinatra-hexacta"
8
11
 
9
12
  def symlink(original_path, link_path)
10
- #p "Symlinking #{original_path} #{link_path}"
11
13
  File.symlink "#{Gem.loaded_specs["sinatra-hexacta"].gem_dir}#{original_path}", "#{link_path}" unless Gem.loaded_specs["sinatra-hexacta"].nil? || File.exist?("#{link_path}")
12
14
  end
13
15
 
14
16
  def symlink_all(original_path, link_path)
15
17
  Dir.foreach("#{Gem.loaded_specs["sinatra-hexacta"].gem_dir}#{original_path}") do |child|
16
18
  symlink("#{original_path}/#{child}","#{link_path}/#{child}") unless child == '.' || child == '..'
17
- end
19
+ end unless Gem.loaded_specs["sinatra-hexacta"].nil?
18
20
  end
19
21
 
20
22
  def setup_dir(path)
21
- FileUtils.mkdir_p path unless Dir.exist? path
23
+ unless Gem.loaded_specs["sinatra-hexacta"].nil?
24
+ FileUtils.remove_dir path if Dir.exist? path
25
+ FileUtils.mkdir_p path
26
+ end
22
27
  end
23
28
 
24
29
  def copy_dir_structure(original_path, destination_path)
25
- FileUtils.remove_dir destination_path if Dir.exist? destination_path
26
- FileUtils.copy_entry("#{Gem.loaded_specs["sinatra-hexacta"].gem_dir}#{original_path}", destination_path)
30
+ unless Gem.loaded_specs["sinatra-hexacta"].nil?
31
+ FileUtils.remove_dir destination_path if Dir.exist? destination_path
32
+ FileUtils.copy_entry("#{Gem.loaded_specs["sinatra-hexacta"].gem_dir}#{original_path}", destination_path)
33
+ end
27
34
  end
28
35
  end
29
36
  end
30
37
 
38
+ if Gem.loaded_specs["sinatra-hexacta"].nil?
39
+ p "ERROR: Sinatra hexacta gem was not found"
40
+ else
41
+ p "Sinatr hexacta gem was found in #{Gem.loaded_specs["sinatra-hexacta"].gem_dir}"
42
+ end
43
+
44
+ unless Gem.loaded_specs["sinatra-hexacta"].nil?
45
+ FileUtils.remove_dir "/app/views/sinatra-hexacta" if Dir.exist? "/app/views/sinatra-hexacta"
46
+ FileUtils.remove_dir "/app/public/sinatra-hexacta" if Dir.exist? "/app/public/sinatra-hexacta"
47
+ end
48
+
31
49
  require_relative 'helpers/init'
32
50
  require_relative 'handlers/init'
51
+ require_relative 'extensions/init'
@@ -10550,10 +10550,6 @@ h6 small,
10550
10550
  left: 0;
10551
10551
  top: 0;
10552
10552
  padding: 0 11px;
10553
- transition: all .5s ease-out;
10554
- -moz-transition: all .5s ease-out;
10555
- -o-transition: all .5s ease-out;
10556
- -webkit-transition: all .5s ease-out;
10557
10553
  }
10558
10554
 
10559
10555
  #header:not(.sidebar-toggled).header-up {
@@ -10594,8 +10590,8 @@ h6 small,
10594
10590
  color: white;
10595
10591
  }
10596
10592
 
10597
- #header input[type="text"]::placeholder { /* Chrome, Firefox, Opera, Safari 10.1+ */
10598
- color: rgba(255,255,255,0.6);;
10593
+ #header input[type="text"]::placeholder {
10594
+ color: rgba(255,255,255,0.6);;
10599
10595
  }
10600
10596
 
10601
10597
  #top-search-wrap input[type="text"] {
@@ -11925,6 +11921,10 @@ a.lv-footer:hover {
11925
11921
  width: 100%;
11926
11922
  }
11927
11923
 
11924
+ .chart-table table {
11925
+ font-size: 11px;
11926
+ }
11927
+
11928
11928
  /*
11929
11929
  * Sparkline Tooltip
11930
11930
  */
@@ -16229,3 +16229,129 @@ body.login-content:before {
16229
16229
  .error .chosen-container-active .chosen-single:after {
16230
16230
  background: #f6675d !Important;
16231
16231
  }
16232
+
16233
+ svg.ct-chart-bar, svg.ct-chart-line{
16234
+ overflow: visible;
16235
+ }
16236
+
16237
+ .ct-label.ct-label.ct-horizontal.ct-end {
16238
+ position: relative;
16239
+ justify-content: flex-end;
16240
+ text-align: right;
16241
+ transform-origin: 100% 0;
16242
+ transform: translate(-100%) rotate(-45deg);
16243
+ white-space:nowrap;
16244
+ }
16245
+
16246
+
16247
+ .ct-legend {
16248
+ position: relative;
16249
+ z-index: 10;
16250
+ list-style: none;
16251
+ text-align: left;
16252
+ cursor: pointer;
16253
+ }
16254
+
16255
+ .bar-chart .ct-legend, .line-chart .ct-legend {
16256
+ text-align: center;
16257
+ }
16258
+
16259
+ .ct-legend li {
16260
+ position: relative;
16261
+ padding-left: 23px;
16262
+ margin-bottom: 3px;
16263
+ }
16264
+
16265
+ .bar-chart .ct-legend li, .line-chart .ct-legend li{
16266
+ display: inline-block;
16267
+ margin: 10px;
16268
+ }
16269
+
16270
+ .ct-legend li:before {
16271
+ width: 12px;
16272
+ height: 12px;
16273
+ position: absolute;
16274
+ left: 0;
16275
+ content: '';
16276
+ border: 3px solid transparent;
16277
+ border-radius: 2px;
16278
+ }
16279
+
16280
+ .ct-legend li.inactive:before {
16281
+ background: transparent;
16282
+ }
16283
+
16284
+ .ct-legend .ct-series-0:before {
16285
+ background-color: #d70206;
16286
+ border-color: #d70206;
16287
+ }
16288
+
16289
+ .ct-legend .ct-series-1:before {
16290
+ background-color: #f05b4f;
16291
+ border-color: #f05b4f;
16292
+ }
16293
+
16294
+ .ct-legend .ct-series-2:before {
16295
+ background-color: #f4c63d;
16296
+ border-color: #f4c63d;
16297
+ }
16298
+
16299
+ .ct-legend .ct-series-3:before {
16300
+ background-color: #d17905;
16301
+ border-color: #d17905;
16302
+ }
16303
+
16304
+ .ct-legend .ct-series-4:before {
16305
+ background-color: #453d3f;
16306
+ border-color: #453d3f;
16307
+ }
16308
+
16309
+ .ct-legend .ct-series-5:before {
16310
+ background-color: #59922b;
16311
+ border-color: #59922b;
16312
+ }
16313
+
16314
+ .ct-legend .ct-series-6:before {
16315
+ background-color: #0544d3;
16316
+ border-color: #0544d3;
16317
+ }
16318
+
16319
+ .ct-legend .ct-series-7:before {
16320
+ background-color: #6b0392;
16321
+ border-color: #6b0392;
16322
+ }
16323
+
16324
+ .ct-legend .ct-series-8:before {
16325
+ background-color: #f05b4f;
16326
+ border-color: #f05b4f;
16327
+ }
16328
+
16329
+ .ct-legend .ct-series-9:before {
16330
+ background-color: #dda458;
16331
+ border-color: #dda458;
16332
+ }
16333
+
16334
+ .ct-legend .ct-series-10:before {
16335
+ background-color: #eacf7d;
16336
+ border-color: #eacf7d;
16337
+ }
16338
+
16339
+ .ct-legend .ct-series-11:before {
16340
+ background-color: #86797d;
16341
+ border-color: #86797d;
16342
+ }
16343
+
16344
+ .ct-legend .ct-series-12:before {
16345
+ background-color: #b2c326;
16346
+ border-color: #b2c326;
16347
+ }
16348
+
16349
+ .ct-legend .ct-series-13:before {
16350
+ background-color: #6188e2;
16351
+ border-color: #6188e2;
16352
+ }
16353
+
16354
+ .ct-legend .ct-series-14:before {
16355
+ background-color: #a748ca;
16356
+ border-color: #a748ca;
16357
+ }