sinatra-hexacta 0.0.2 → 0.1.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/lib/sinatra/extensions/antiquity.rb +34 -0
- data/lib/sinatra/extensions/date.rb +49 -0
- data/lib/sinatra/extensions/init.rb +4 -0
- data/lib/sinatra/extensions/notification.rb +41 -0
- data/lib/sinatra/handlers/errors.rb +10 -3
- data/lib/sinatra/handlers/init.rb +3 -1
- data/lib/sinatra/handlers/notifications.rb +17 -28
- data/lib/sinatra/handlers/params.rb +26 -0
- data/lib/sinatra/handlers/reports.rb +28 -1
- data/lib/sinatra/helpers/alerts.rb +27 -0
- data/lib/sinatra/helpers/cas.rb +92 -0
- data/lib/sinatra/helpers/charts.rb +20 -4
- data/lib/sinatra/helpers/init.rb +4 -0
- data/lib/sinatra/helpers/inputs.rb +2 -2
- data/lib/sinatra/helpers/libraries.rb +2 -2
- data/lib/sinatra/helpers/mailer.rb +74 -0
- data/lib/sinatra/helpers/reports.rb +27 -0
- data/lib/sinatra/helpers/schedule.rb +68 -0
- data/lib/sinatra/helpers/subscriptions.rb +1 -1
- data/lib/sinatra/hexacta.rb +13 -5
- data/lib/sinatra/public/css/app.min.1.css +130 -12
- data/lib/sinatra/public/js/app.js +49 -42
- data/lib/sinatra/public/vendors/chartist/chartist-plugin-legend.js +237 -0
- data/lib/sinatra/views/alerts/empty.slim +7 -0
- data/lib/sinatra/views/alerts/error.slim +7 -0
- data/lib/sinatra/views/alerts/info.slim +7 -0
- data/lib/sinatra/views/alerts/warning.slim +7 -0
- data/lib/sinatra/views/charts/bar.slim +56 -0
- data/lib/sinatra/views/charts/gauge.slim +30 -0
- data/lib/sinatra/views/charts/{simple_line.slim → line.slim} +7 -8
- data/lib/sinatra/views/charts/pie.slim +34 -0
- data/lib/sinatra/views/charts/stacked_bar.slim +36 -0
- data/lib/sinatra/views/inputs/multiple_select.slim +4 -0
- data/lib/sinatra/views/inputs/select.slim +3 -0
- data/lib/sinatra/views/libraries/include_js_after.slim +0 -24
- data/lib/sinatra/views/libraries/include_js_before.slim +1 -0
- data/lib/sinatra/views/notifications.slim +37 -31
- data/lib/sinatra/views/notifications/form.slim +4 -4
- data/lib/sinatra/views/notifications/new.slim +4 -4
- data/lib/sinatra/views/notifications/widget.slim +9 -5
- data/lib/sinatra/views/reports/pick_date.slim +14 -0
- data/lib/sinatra/views/reports/pick_dates.slim +16 -0
- data/lib/sinatra/views/reports/pick_month.slim +14 -0
- data/lib/sinatra/views/reports/pick_year.slim +14 -0
- metadata +26 -3
@@ -1,15 +1,31 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
module Sinatra
|
3
|
-
module
|
3
|
+
module ChartsHelper
|
4
4
|
extend Hexacta
|
5
5
|
|
6
|
-
def
|
7
|
-
slim "#{Hexacta::GEM_FILE_DIR}/charts/
|
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
|
30
|
+
helpers ChartsHelper
|
15
31
|
end
|
data/lib/sinatra/helpers/init.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
module Sinatra
|
3
|
-
module
|
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
|
46
|
+
helpers InputHelper
|
47
47
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
module Sinatra
|
3
|
-
module
|
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
|
24
|
+
helpers LibrariesHelper
|
25
25
|
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'sucker_punch'
|
3
|
+
|
4
|
+
module Sinatra
|
5
|
+
module MailHelper
|
6
|
+
|
7
|
+
attr_reader :access_token, :expire, :refresh_token
|
8
|
+
|
9
|
+
def _do_connect(uri, form={},token=nil)
|
10
|
+
url = URI.parse("#{uri}")
|
11
|
+
http = Net::HTTP.new(url.host, url.port)
|
12
|
+
http.read_timeout = 1000
|
13
|
+
http.use_ssl = (url.scheme == 'https')
|
14
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
15
|
+
header = { 'Content-Type' => 'application/x-www-form-urlencoded' }
|
16
|
+
header["Authorization"] = "Bearer #{token}" unless token.nil?
|
17
|
+
request = Net::HTTP::Post.new(url, header)
|
18
|
+
request.form_data = form
|
19
|
+
http.start {|http| http.request(request) }
|
20
|
+
end
|
21
|
+
|
22
|
+
def _authorize_mail
|
23
|
+
params = [ [ "grant_type", "password" ], [ "client_id", "MailApp" ], [ "client_secret", "MailAppPRD2018!" ] ]
|
24
|
+
response = _do_connect("https://comunicacion.hexacta.com:4443/apptoken",params)
|
25
|
+
if( response.is_a?( Net::HTTPSuccess ) )
|
26
|
+
token = JSON.parse(response.body)
|
27
|
+
@access_token = token["access_token"]
|
28
|
+
@refresh_token = token["refresh_token"]
|
29
|
+
@expire = DateTime.parse(token[".expires"])
|
30
|
+
else
|
31
|
+
p "Didn't succeed :("
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def _build_map_values(mail_values)
|
36
|
+
values = []
|
37
|
+
for key in mail_values.keys
|
38
|
+
index = mail_values.keys.index(key)
|
39
|
+
values << ["MailValues[#{index}].Key", key]
|
40
|
+
values << ["MailValues[#{index}].Value", mail_values[key]]
|
41
|
+
end
|
42
|
+
values
|
43
|
+
end
|
44
|
+
|
45
|
+
def _build_to_map(to_map)
|
46
|
+
values = []
|
47
|
+
for key in to_map.keys
|
48
|
+
index = to_map.keys.index(key)
|
49
|
+
values << ["ResourcesRequest[#{index}].TypeGroup", to_map[key]]
|
50
|
+
values << ["ResourcesRequest[#{index}].Name", key]
|
51
|
+
end
|
52
|
+
values
|
53
|
+
end
|
54
|
+
|
55
|
+
def _expired?
|
56
|
+
expire.nil? || DateTime.now < expire
|
57
|
+
end
|
58
|
+
|
59
|
+
def send_mail(template,subject,mail_values={},to_map)
|
60
|
+
if _expired?
|
61
|
+
_authorize_mail
|
62
|
+
end
|
63
|
+
params = [ [ "ApplicationCode", 1 ], [ "Name", template ], [ "Subject", subject ], [ "EmailAddress", "apps@hexacta.com" ] ]
|
64
|
+
params = params + _build_map_values(mail_values) + _build_to_map(to_map)
|
65
|
+
response = do_connect("https://comunicacion.hexacta.com:4444/api/app/TemplateNotification/SendMail",params,@access_token)
|
66
|
+
if( !response.is_a?( Net::HTTPSuccess ) )
|
67
|
+
p "Didn't succeed :("
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
helpers MailHelper
|
74
|
+
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.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.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,
|
data/lib/sinatra/hexacta.rb
CHANGED
@@ -1,32 +1,40 @@
|
|
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
|
-
|
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
|
-
|
26
|
-
|
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
|
|
31
38
|
require_relative 'helpers/init'
|
32
39
|
require_relative 'handlers/init'
|
40
|
+
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 {
|
@@ -10571,14 +10567,6 @@ h6 small,
|
|
10571
10567
|
left: 0;
|
10572
10568
|
width: 100%;
|
10573
10569
|
height: 70px;
|
10574
|
-
background: #fff;
|
10575
|
-
-webkit-transition: all;
|
10576
|
-
-o-transition: all;
|
10577
|
-
transition: all;
|
10578
|
-
-webkit-transition-duration: 300ms;
|
10579
|
-
transition-duration: 300ms;
|
10580
|
-
opacity: 0;
|
10581
|
-
filter: alpha(opacity=0);
|
10582
10570
|
z-index: 10;
|
10583
10571
|
}
|
10584
10572
|
|
@@ -11925,6 +11913,10 @@ a.lv-footer:hover {
|
|
11925
11913
|
width: 100%;
|
11926
11914
|
}
|
11927
11915
|
|
11916
|
+
.chart-table table {
|
11917
|
+
font-size: 11px;
|
11918
|
+
}
|
11919
|
+
|
11928
11920
|
/*
|
11929
11921
|
* Sparkline Tooltip
|
11930
11922
|
*/
|
@@ -16229,3 +16221,129 @@ body.login-content:before {
|
|
16229
16221
|
.error .chosen-container-active .chosen-single:after {
|
16230
16222
|
background: #f6675d !Important;
|
16231
16223
|
}
|
16224
|
+
|
16225
|
+
svg.ct-chart-bar, svg.ct-chart-line{
|
16226
|
+
overflow: visible;
|
16227
|
+
}
|
16228
|
+
|
16229
|
+
.ct-label.ct-label.ct-horizontal.ct-end {
|
16230
|
+
position: relative;
|
16231
|
+
justify-content: flex-end;
|
16232
|
+
text-align: right;
|
16233
|
+
transform-origin: 100% 0;
|
16234
|
+
transform: translate(-100%) rotate(-45deg);
|
16235
|
+
white-space:nowrap;
|
16236
|
+
}
|
16237
|
+
|
16238
|
+
|
16239
|
+
.ct-legend {
|
16240
|
+
position: relative;
|
16241
|
+
z-index: 10;
|
16242
|
+
list-style: none;
|
16243
|
+
text-align: left;
|
16244
|
+
cursor: pointer;
|
16245
|
+
}
|
16246
|
+
|
16247
|
+
.bar-chart .ct-legend, .line-chart .ct-legend {
|
16248
|
+
text-align: center;
|
16249
|
+
}
|
16250
|
+
|
16251
|
+
.ct-legend li {
|
16252
|
+
position: relative;
|
16253
|
+
padding-left: 23px;
|
16254
|
+
margin-bottom: 3px;
|
16255
|
+
}
|
16256
|
+
|
16257
|
+
.bar-chart .ct-legend li, .line-chart .ct-legend li{
|
16258
|
+
display: inline-block;
|
16259
|
+
margin: 10px;
|
16260
|
+
}
|
16261
|
+
|
16262
|
+
.ct-legend li:before {
|
16263
|
+
width: 12px;
|
16264
|
+
height: 12px;
|
16265
|
+
position: absolute;
|
16266
|
+
left: 0;
|
16267
|
+
content: '';
|
16268
|
+
border: 3px solid transparent;
|
16269
|
+
border-radius: 2px;
|
16270
|
+
}
|
16271
|
+
|
16272
|
+
.ct-legend li.inactive:before {
|
16273
|
+
background: transparent;
|
16274
|
+
}
|
16275
|
+
|
16276
|
+
.ct-legend .ct-series-0:before {
|
16277
|
+
background-color: #d70206;
|
16278
|
+
border-color: #d70206;
|
16279
|
+
}
|
16280
|
+
|
16281
|
+
.ct-legend .ct-series-1:before {
|
16282
|
+
background-color: #f05b4f;
|
16283
|
+
border-color: #f05b4f;
|
16284
|
+
}
|
16285
|
+
|
16286
|
+
.ct-legend .ct-series-2:before {
|
16287
|
+
background-color: #f4c63d;
|
16288
|
+
border-color: #f4c63d;
|
16289
|
+
}
|
16290
|
+
|
16291
|
+
.ct-legend .ct-series-3:before {
|
16292
|
+
background-color: #d17905;
|
16293
|
+
border-color: #d17905;
|
16294
|
+
}
|
16295
|
+
|
16296
|
+
.ct-legend .ct-series-4:before {
|
16297
|
+
background-color: #453d3f;
|
16298
|
+
border-color: #453d3f;
|
16299
|
+
}
|
16300
|
+
|
16301
|
+
.ct-legend .ct-series-5:before {
|
16302
|
+
background-color: #59922b;
|
16303
|
+
border-color: #59922b;
|
16304
|
+
}
|
16305
|
+
|
16306
|
+
.ct-legend .ct-series-6:before {
|
16307
|
+
background-color: #0544d3;
|
16308
|
+
border-color: #0544d3;
|
16309
|
+
}
|
16310
|
+
|
16311
|
+
.ct-legend .ct-series-7:before {
|
16312
|
+
background-color: #6b0392;
|
16313
|
+
border-color: #6b0392;
|
16314
|
+
}
|
16315
|
+
|
16316
|
+
.ct-legend .ct-series-8:before {
|
16317
|
+
background-color: #f05b4f;
|
16318
|
+
border-color: #f05b4f;
|
16319
|
+
}
|
16320
|
+
|
16321
|
+
.ct-legend .ct-series-9:before {
|
16322
|
+
background-color: #dda458;
|
16323
|
+
border-color: #dda458;
|
16324
|
+
}
|
16325
|
+
|
16326
|
+
.ct-legend .ct-series-10:before {
|
16327
|
+
background-color: #eacf7d;
|
16328
|
+
border-color: #eacf7d;
|
16329
|
+
}
|
16330
|
+
|
16331
|
+
.ct-legend .ct-series-11:before {
|
16332
|
+
background-color: #86797d;
|
16333
|
+
border-color: #86797d;
|
16334
|
+
}
|
16335
|
+
|
16336
|
+
.ct-legend .ct-series-12:before {
|
16337
|
+
background-color: #b2c326;
|
16338
|
+
border-color: #b2c326;
|
16339
|
+
}
|
16340
|
+
|
16341
|
+
.ct-legend .ct-series-13:before {
|
16342
|
+
background-color: #6188e2;
|
16343
|
+
border-color: #6188e2;
|
16344
|
+
}
|
16345
|
+
|
16346
|
+
.ct-legend .ct-series-14:before {
|
16347
|
+
background-color: #a748ca;
|
16348
|
+
border-color: #a748ca;
|
16349
|
+
}
|