effective_logging 1.7.1 → 1.8.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/README.md +0 -32
- data/app/helpers/effective_logging_helper.rb +0 -2
- data/app/models/effective/log.rb +2 -15
- data/config/effective_logging.rb +0 -5
- data/config/routes.rb +1 -11
- data/lib/effective_logging.rb +0 -5
- data/lib/effective_logging/active_record_logger.rb +2 -19
- data/lib/effective_logging/engine.rb +1 -2
- data/lib/effective_logging/{log_changes_user.rb → set_current_user.rb} +1 -1
- data/lib/effective_logging/version.rb +1 -1
- metadata +2 -11
- data/app/controllers/admin/trash_controller.rb +0 -28
- data/app/controllers/effective/trash_controller.rb +0 -44
- data/app/models/concerns/acts_as_trashable.rb +0 -40
- data/app/models/effective/datatables/trash.rb +0 -53
- data/app/views/admin/trash/_actions.html.haml +0 -15
- data/app/views/admin/trash/index.html.haml +0 -3
- data/app/views/admin/trash/show.html.haml +0 -4
- data/app/views/effective/trash/index.html.haml +0 -6
- data/app/views/effective/trash/show.html.haml +0 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a450d9893a3bdc765a9d509a3108afb28f367b8e
|
4
|
+
data.tar.gz: 3c283840520c3676974d2f874d1b727fff8dd117
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0efb78ee44a94a1f32f44d687707904aa5dfc41770e98777b1acf5156ce25aa18c3358b1f37312370c394e5d6560d940f1aad639fc9e09d18ef314aa06953699
|
7
|
+
data.tar.gz: f50fcc75ca0eb4e8138ddae57f740773e0414789883196a01b75721b4f490091da7a7f45aecf705f5d762fdb0bdfd7e913e1ebf82ef352fd085f300f7168dec5
|
data/README.md
CHANGED
@@ -16,8 +16,6 @@ Automatically logs any email sent by the application, any successful user login
|
|
16
16
|
|
17
17
|
Has an effective_datatables driven admin interface to display and search/sort/filter all logs.
|
18
18
|
|
19
|
-
Audits changes and performs Trash restore functionality for Active Record objects.
|
20
|
-
|
21
19
|
## Getting Started
|
22
20
|
|
23
21
|
Add to your Gemfile:
|
@@ -247,36 +245,6 @@ def log_changes_formatted_value(attribute, value)
|
|
247
245
|
end
|
248
246
|
```
|
249
247
|
|
250
|
-
### Trash & Restore
|
251
|
-
|
252
|
-
This gem provides a fully functional Trash implementation that can be
|
253
|
-
used to delete and restore active record objects in any rails application.
|
254
|
-
|
255
|
-
Add to your model:
|
256
|
-
|
257
|
-
```ruby
|
258
|
-
class Post < ActiveRecord::Base
|
259
|
-
acts_as_trashable
|
260
|
-
end
|
261
|
-
```
|
262
|
-
|
263
|
-
The `acts_as_trashable` mixin sets up `before_destroy` hook and copies the now-deleted attributes to an `Effective::Log` object.
|
264
|
-
|
265
|
-
Visit `/trash`, or `/admin/trash` to restore them.
|
266
|
-
|
267
|
-
The above routes require the following, user specific, permissions:
|
268
|
-
|
269
|
-
```ruby
|
270
|
-
can :restore, Effective::Log, user_id: user.id
|
271
|
-
```
|
272
|
-
|
273
|
-
and for admin:
|
274
|
-
|
275
|
-
```ruby
|
276
|
-
can :restore, Effective::Log
|
277
|
-
can :admin, :effective_logging
|
278
|
-
```
|
279
|
-
|
280
248
|
### Logging From JavaScript
|
281
249
|
|
282
250
|
First, require the javascript in your application.js:
|
@@ -8,7 +8,6 @@ module EffectiveLoggingHelper
|
|
8
8
|
when 'info' ; 'info'
|
9
9
|
when 'warning' ; 'warning'
|
10
10
|
when 'error' ; 'danger'
|
11
|
-
when 'trashed' ; 'default'
|
12
11
|
else 'primary'
|
13
12
|
end
|
14
13
|
end
|
@@ -16,7 +15,6 @@ module EffectiveLoggingHelper
|
|
16
15
|
def render_log(log)
|
17
16
|
render(partial: 'effective/logs/log', locals: {:log => log})
|
18
17
|
end
|
19
|
-
alias_method :render_trash, :render_log
|
20
18
|
|
21
19
|
def parents_of_log(log)
|
22
20
|
parents = [log.parent]
|
data/app/models/effective/log.rb
CHANGED
@@ -27,21 +27,15 @@ module Effective
|
|
27
27
|
# end
|
28
28
|
|
29
29
|
validates :message, presence: true
|
30
|
-
validates :status, presence: true, inclusion: { in: (EffectiveLogging.statuses + [EffectiveLogging.log_changes_status
|
30
|
+
validates :status, presence: true, inclusion: { in: (EffectiveLogging.statuses + [EffectiveLogging.log_changes_status]) }
|
31
31
|
|
32
32
|
default_scope -> { order(updated_at: :desc) }
|
33
33
|
|
34
34
|
scope :logged_changes, -> { where(status: EffectiveLogging.log_changes_status)}
|
35
35
|
scope :changes, -> { where(status: EffectiveLogging.log_changes_status)}
|
36
|
-
scope :trash, -> { where(status: EffectiveLogging.trashable_status)}
|
37
36
|
|
38
37
|
def to_s
|
39
|
-
|
40
|
-
when EffectiveLogging.trashable_status
|
41
|
-
[associated_type, associated_id].join(' ').presence || 'New Trash item'
|
42
|
-
else
|
43
|
-
"Log #{id}"
|
44
|
-
end
|
38
|
+
"Log #{id}"
|
45
39
|
end
|
46
40
|
|
47
41
|
def log(message, status = EffectiveLogging.statuses.first, options = {})
|
@@ -52,13 +46,6 @@ module Effective
|
|
52
46
|
self[:details] || {}
|
53
47
|
end
|
54
48
|
|
55
|
-
# So this is a Trash item
|
56
|
-
# When we delete ourselves, we restore this trash item first
|
57
|
-
def restore_trashable!
|
58
|
-
raise 'no attributes to restore from' unless details.kind_of?(Hash) && details[:attributes].present?
|
59
|
-
associated_type.constantize.new(details[:attributes]).save!
|
60
|
-
end
|
61
|
-
|
62
49
|
# def next_log
|
63
50
|
# @next_log ||= Log.unscoped.order(:id).where(:parent_id => self.parent_id).where('id > ?', self.id).first
|
64
51
|
# end
|
data/config/effective_logging.rb
CHANGED
@@ -31,9 +31,7 @@ EffectiveLogging.setup do |config|
|
|
31
31
|
|
32
32
|
# config.layout = {
|
33
33
|
# logs: 'application',
|
34
|
-
# trash: 'application',
|
35
34
|
# admin_logs: 'admin',
|
36
|
-
# admin_trash: 'admin'
|
37
35
|
# }
|
38
36
|
|
39
37
|
# All statuses defined here, as well as 'info', 'success', and 'error' (hardcoded) will be created as
|
@@ -50,7 +48,4 @@ EffectiveLogging.setup do |config|
|
|
50
48
|
# Log all successful user login attempts
|
51
49
|
config.user_logins_enabled = true
|
52
50
|
config.user_logouts_enabled = false
|
53
|
-
|
54
|
-
# Enable the /trash, /admin/trash and /trash/:id/restore routes. Doesn't affect acts_as_trashable itself.
|
55
|
-
config.trash_enabled = true
|
56
51
|
end
|
data/config/routes.rb
CHANGED
@@ -5,21 +5,11 @@ EffectiveLogging::Engine.routes.draw do
|
|
5
5
|
resources :logs, only: [:create, :index, :show] do
|
6
6
|
member { get :html_part }
|
7
7
|
end
|
8
|
-
|
9
|
-
if EffectiveLogging.trash_enabled
|
10
|
-
resources :trash, only: [:index, :show] do
|
11
|
-
member { get :restore }
|
12
|
-
end
|
13
|
-
end
|
14
8
|
end
|
15
9
|
|
16
10
|
if defined?(EffectiveDatatables)
|
17
11
|
namespace :admin do
|
18
|
-
resources :logs, :
|
19
|
-
|
20
|
-
if EffectiveLogging.trash_enabled
|
21
|
-
resources :trash, only: [:index, :show]
|
22
|
-
end
|
12
|
+
resources :logs, only: [:index, :show]
|
23
13
|
end
|
24
14
|
end
|
25
15
|
|
data/lib/effective_logging.rb
CHANGED
@@ -15,7 +15,6 @@ module EffectiveLogging
|
|
15
15
|
mattr_accessor :emails_enabled
|
16
16
|
mattr_accessor :user_logins_enabled
|
17
17
|
mattr_accessor :user_logouts_enabled
|
18
|
-
mattr_accessor :trash_enabled
|
19
18
|
|
20
19
|
def self.setup
|
21
20
|
yield self
|
@@ -44,10 +43,6 @@ module EffectiveLogging
|
|
44
43
|
'logged change'.freeze
|
45
44
|
end
|
46
45
|
|
47
|
-
def self.trashable_status
|
48
|
-
'trashed'.freeze
|
49
|
-
end
|
50
|
-
|
51
46
|
# This is set by the "set_effective_logging_current_user" before_filter.
|
52
47
|
def self.current_user=(user)
|
53
48
|
@effective_logging_current_user = user
|
@@ -12,9 +12,7 @@ module EffectiveLogging
|
|
12
12
|
@depth = options.delete(:depth) || 0
|
13
13
|
@options = options
|
14
14
|
|
15
|
-
|
16
|
-
raise ArgumentError.new('logger must respond to logged_changes or trash')
|
17
|
-
end
|
15
|
+
raise ArgumentError.new('logger must respond to logged_changes') unless @logger.respond_to?(:logged_changes)
|
18
16
|
end
|
19
17
|
|
20
18
|
# execute! is called when we recurse, otherwise the following methods are best called individually
|
@@ -28,11 +26,6 @@ module EffectiveLogging
|
|
28
26
|
end
|
29
27
|
end
|
30
28
|
|
31
|
-
# before_destroy
|
32
|
-
def trashed!
|
33
|
-
log_trash((resource.to_s rescue ''), details: applicable(attributes))
|
34
|
-
end
|
35
|
-
|
36
29
|
# before_destroy
|
37
30
|
def destroyed!
|
38
31
|
log('Deleted', details: applicable(attributes))
|
@@ -87,8 +80,7 @@ module EffectiveLogging
|
|
87
80
|
|
88
81
|
# Collect to_s representations for all has_one associations
|
89
82
|
(resource.class.try(:reflect_on_all_associations, :has_one) || []).each do |association|
|
90
|
-
|
91
|
-
attributes[association.name] = resource.send(association.name).to_s.presence || 'nil'
|
83
|
+
attributes[association.name] = resource.send(association.name).to_s.presence
|
92
84
|
end
|
93
85
|
|
94
86
|
# Collects attributes for all accepts_as_nested_parameters has_many associations
|
@@ -127,15 +119,6 @@ module EffectiveLogging
|
|
127
119
|
).tap { |log| log.save }
|
128
120
|
end
|
129
121
|
|
130
|
-
def log_trash(message, details: {})
|
131
|
-
logger.build_trash(
|
132
|
-
user: EffectiveLogging.current_user,
|
133
|
-
status: EffectiveLogging.trashable_status,
|
134
|
-
message: "#{"\t" * depth}#{options[:prefix]}#{message}",
|
135
|
-
details: details
|
136
|
-
).tap { |log| log.save }
|
137
|
-
end
|
138
|
-
|
139
122
|
# TODO: Make this work better with nested objects
|
140
123
|
def applicable(attributes)
|
141
124
|
atts = if options[:only].present?
|
@@ -29,14 +29,13 @@ module EffectiveLogging
|
|
29
29
|
initializer 'effective_logging.active_record' do |app|
|
30
30
|
ActiveSupport.on_load :active_record do
|
31
31
|
ActiveRecord::Base.extend(ActsAsLoggable::ActiveRecord)
|
32
|
-
ActiveRecord::Base.extend(ActsAsTrashable::ActiveRecord)
|
33
32
|
end
|
34
33
|
end
|
35
34
|
|
36
35
|
# Register the log_page_views concern so that it can be called in ActionController or elsewhere
|
37
36
|
initializer 'effective_logging.log_changes_action_controller' do |app|
|
38
37
|
ActiveSupport.on_load :action_controller do
|
39
|
-
ActionController::Base.include(EffectiveLogging::
|
38
|
+
ActionController::Base.include(EffectiveLogging::SetCurrentUser)
|
40
39
|
ActionController::Base.send(:before_action, :set_effective_logging_current_user)
|
41
40
|
end
|
42
41
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: effective_logging
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.8.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Code and Effect
|
@@ -93,15 +93,11 @@ files:
|
|
93
93
|
- app/assets/javascripts/effective_logging.js
|
94
94
|
- app/assets/javascripts/effective_logging/effective_logger.js.coffee.erb
|
95
95
|
- app/controllers/admin/logs_controller.rb
|
96
|
-
- app/controllers/admin/trash_controller.rb
|
97
96
|
- app/controllers/effective/logs_controller.rb
|
98
|
-
- app/controllers/effective/trash_controller.rb
|
99
97
|
- app/helpers/effective_logging_helper.rb
|
100
98
|
- app/models/concerns/acts_as_loggable.rb
|
101
|
-
- app/models/concerns/acts_as_trashable.rb
|
102
99
|
- app/models/effective/access_denied.rb
|
103
100
|
- app/models/effective/datatables/logs.rb
|
104
|
-
- app/models/effective/datatables/trash.rb
|
105
101
|
- app/models/effective/log.rb
|
106
102
|
- app/models/effective_logger.rb
|
107
103
|
- app/views/active_admin/effective_logging/logs/index.html.haml
|
@@ -109,14 +105,9 @@ files:
|
|
109
105
|
- app/views/admin/logs/_actions.html.haml
|
110
106
|
- app/views/admin/logs/index.html.haml
|
111
107
|
- app/views/admin/logs/show.html.haml
|
112
|
-
- app/views/admin/trash/_actions.html.haml
|
113
|
-
- app/views/admin/trash/index.html.haml
|
114
|
-
- app/views/admin/trash/show.html.haml
|
115
108
|
- app/views/effective/logs/_log.html.haml
|
116
109
|
- app/views/effective/logs/index.html.haml
|
117
110
|
- app/views/effective/logs/show.html.haml
|
118
|
-
- app/views/effective/trash/index.html.haml
|
119
|
-
- app/views/effective/trash/show.html.haml
|
120
111
|
- config/effective_logging.rb
|
121
112
|
- config/routes.rb
|
122
113
|
- db/migrate/01_create_effective_logging.rb.erb
|
@@ -124,8 +115,8 @@ files:
|
|
124
115
|
- lib/effective_logging/active_record_logger.rb
|
125
116
|
- lib/effective_logging/email_logger.rb
|
126
117
|
- lib/effective_logging/engine.rb
|
127
|
-
- lib/effective_logging/log_changes_user.rb
|
128
118
|
- lib/effective_logging/log_page_views.rb
|
119
|
+
- lib/effective_logging/set_current_user.rb
|
129
120
|
- lib/effective_logging/user_logger.rb
|
130
121
|
- lib/effective_logging/version.rb
|
131
122
|
- lib/generators/effective_logging/install_generator.rb
|
@@ -1,28 +0,0 @@
|
|
1
|
-
# This copies the permissions of The Logs controller
|
2
|
-
|
3
|
-
module Admin
|
4
|
-
class TrashController < ApplicationController
|
5
|
-
respond_to?(:before_action) ? before_action(:authenticate_user!) : before_filter(:authenticate_user!) # Devise
|
6
|
-
|
7
|
-
layout (EffectiveLogging.layout.kind_of?(Hash) ? EffectiveLogging.layout[:admin_trash] : EffectiveLogging.layout)
|
8
|
-
|
9
|
-
skip_log_page_views quiet: true
|
10
|
-
helper EffectiveLoggingHelper
|
11
|
-
|
12
|
-
def index
|
13
|
-
@datatable = Effective::Datatables::Trash.new()
|
14
|
-
@page_title = 'Trash'
|
15
|
-
|
16
|
-
EffectiveLogging.authorized?(self, :restore, Effective::Log)
|
17
|
-
EffectiveLogging.authorized?(self, :admin, :effective_logging)
|
18
|
-
end
|
19
|
-
|
20
|
-
def show
|
21
|
-
@trash = Effective::Log.trash.find(params[:id])
|
22
|
-
@page_title = "Trash item - #{@trash.to_s}"
|
23
|
-
|
24
|
-
EffectiveLogging.authorized?(self, :restore, @trash)
|
25
|
-
EffectiveLogging.authorized?(self, :admin, :effective_logging)
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
@@ -1,44 +0,0 @@
|
|
1
|
-
module Effective
|
2
|
-
class TrashController < ApplicationController
|
3
|
-
if respond_to?(:before_action) # Devise
|
4
|
-
before_action :authenticate_user!
|
5
|
-
else
|
6
|
-
before_filter :authenticate_user!
|
7
|
-
end
|
8
|
-
|
9
|
-
# This is the User index event
|
10
|
-
def index
|
11
|
-
@datatable = Effective::Datatables::Trash.new(user_id: current_user.id)
|
12
|
-
@page_title = 'Trash'
|
13
|
-
|
14
|
-
EffectiveLogging.authorized?(self, :restore, Effective::Log.new(user_id: current_user.id))
|
15
|
-
end
|
16
|
-
|
17
|
-
# This is the User show event
|
18
|
-
def show
|
19
|
-
@trash = Effective::Log.trash.find(params[:id])
|
20
|
-
@page_title = "Trash item - #{@trash.to_s}"
|
21
|
-
|
22
|
-
EffectiveLogging.authorized?(self, :restore, @trash)
|
23
|
-
end
|
24
|
-
|
25
|
-
def restore
|
26
|
-
@trash = Effective::Log.trash.find(params[:id])
|
27
|
-
EffectiveLogging.authorized?(self, :restore, @trash)
|
28
|
-
|
29
|
-
Effective::Log.transaction do
|
30
|
-
begin
|
31
|
-
@trash.restore_trashable!
|
32
|
-
@trash.destroy!
|
33
|
-
flash[:success] = "Successfully restored #{@trash}"
|
34
|
-
rescue => e
|
35
|
-
flash[:danger] = "Unable to restore: #{e.message}"
|
36
|
-
raise ActiveRecord::Rollback
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
redirect_back(fallback_location: effective_logging.trash_path)
|
41
|
-
end
|
42
|
-
|
43
|
-
end
|
44
|
-
end
|
@@ -1,40 +0,0 @@
|
|
1
|
-
module ActsAsTrashable
|
2
|
-
extend ActiveSupport::Concern
|
3
|
-
|
4
|
-
module ActiveRecord
|
5
|
-
def acts_as_trashable(*options)
|
6
|
-
@acts_as_trashable_options = options.try(:first) || {}
|
7
|
-
|
8
|
-
unless @acts_as_trashable_options.kind_of?(Hash)
|
9
|
-
raise ArgumentError.new("invalid arguments passed to (effective_trash) acts_as_trashable. Expecting no options.")
|
10
|
-
end
|
11
|
-
|
12
|
-
include ::ActsAsTrashable
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
included do
|
17
|
-
has_one :trash, -> { where(status: EffectiveLogging.trashable_status) }, as: :associated, class_name: Effective::Log
|
18
|
-
|
19
|
-
before_destroy do
|
20
|
-
EffectiveLogging::ActiveRecordLogger.new(self, acts_as_trashable_options).trashed!
|
21
|
-
true
|
22
|
-
end
|
23
|
-
|
24
|
-
# Parse Options
|
25
|
-
acts_as_trashable_options = {
|
26
|
-
only: Array(@acts_as_trashable_options[:only]).map { |attribute| attribute.to_s },
|
27
|
-
except: Array(@acts_as_trashable_options[:except]).map { |attribute| attribute.to_s },
|
28
|
-
additionally: Array(@acts_as_trashable_options[:additionally]).map { |attribute| attribute.to_s }
|
29
|
-
}
|
30
|
-
|
31
|
-
self.send(:define_method, :acts_as_trashable_options) { acts_as_trashable_options }
|
32
|
-
end
|
33
|
-
|
34
|
-
module ClassMethods
|
35
|
-
end
|
36
|
-
|
37
|
-
# Regular instance methods
|
38
|
-
|
39
|
-
end
|
40
|
-
|
@@ -1,53 +0,0 @@
|
|
1
|
-
if defined?(EffectiveDatatables)
|
2
|
-
module Effective
|
3
|
-
module Datatables
|
4
|
-
class Trash < Effective::Datatable
|
5
|
-
include EffectiveLoggingHelper
|
6
|
-
|
7
|
-
datatable do
|
8
|
-
default_order :created_at, :desc
|
9
|
-
|
10
|
-
table_column :created_at, label: 'Destroyed at'
|
11
|
-
table_column :id, visible: false
|
12
|
-
|
13
|
-
unless attributes[:user_id] || attributes[:user] || (attributes[:user] == false)
|
14
|
-
table_column :user, label: 'Destroyed by', visible: false
|
15
|
-
end
|
16
|
-
|
17
|
-
table_column :associated_type, label: 'Type'
|
18
|
-
table_column :associated_id, label: 'Original Id', visible: false
|
19
|
-
table_column :message, label: 'Item'
|
20
|
-
|
21
|
-
table_column :details, visible: true, sortable: false do |trash|
|
22
|
-
tableize_hash(trash.details.except(:trash), th: true, sub_th: false, width: '100%')
|
23
|
-
end
|
24
|
-
|
25
|
-
unless attributes[:actions] == false
|
26
|
-
actions_column partial: 'admin/trash/actions', partial_local: :trash
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
# A nil attributes[:log_id] means give me all the top level log entries
|
31
|
-
# If we set a log_id then it's for sub logs
|
32
|
-
def collection
|
33
|
-
collection = Effective::Log.trash.includes(:user)
|
34
|
-
|
35
|
-
if attributes[:user_id].present?
|
36
|
-
collection = collection.where(user_id: attributes[:user_id])
|
37
|
-
end
|
38
|
-
|
39
|
-
if attributes[:user].present?
|
40
|
-
collection = collection.where(user: attributes[:user])
|
41
|
-
end
|
42
|
-
|
43
|
-
if attributes[:associated_id] && attributes[:associated_type]
|
44
|
-
collection = collection.where(associated_id: attributes[:associated_id], associated_type: attributes[:associated_type])
|
45
|
-
end
|
46
|
-
|
47
|
-
collection
|
48
|
-
end
|
49
|
-
|
50
|
-
end
|
51
|
-
end
|
52
|
-
end
|
53
|
-
end
|
@@ -1,15 +0,0 @@
|
|
1
|
-
:ruby
|
2
|
-
show_path =
|
3
|
-
if datatables_active_admin_path?
|
4
|
-
admin_effective_trash_path(trash)
|
5
|
-
elsif datatables_admin_path?
|
6
|
-
effective_logging.admin_trash_path(trash)
|
7
|
-
else
|
8
|
-
effective_logging.trash_path(trash)
|
9
|
-
end
|
10
|
-
|
11
|
-
= link_to show_path, title: 'view' do
|
12
|
-
%span.glyphicon.glyphicon-eye-open
|
13
|
-
|
14
|
-
= link_to effective_logging.restore_trash_path(trash), title: 'Restore', data: {confirm: 'Restore this item?'} do
|
15
|
-
%span.glyphicon.glyphicon-retweet
|