audit-log 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 3fe77595997c59059c5a91e1bc753aa017692ddea0a268dfa7f636304085d1d1
4
+ data.tar.gz: ddc300e06f79289ae8f5078479e272468df07d95992d82d6e20571515cdd6d2d
5
+ SHA512:
6
+ metadata.gz: a493e052a5429903138bfbde7476ad6268c2fc3eae34cce1b1d9e1c3d38ae958f17808e65a5dc8063834216dd893cb8e1c40e130a326dafa193656470c2338c0
7
+ data.tar.gz: 69df72ffd3c5e8912c8006f5fe0f8159d123f2075e3590c13b7853ad0258620e206f114c77644f32a9c1050087f032388d9344ef4520f3f5fe3d3b8312e1f5ac
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2019 Jason Lee
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,54 @@
1
+ # AuditLog
2
+
3
+ Trail audit logs (Operation logs) into the database for user behaviors, including a web UI to query logs.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'audit-log'
11
+ ```
12
+
13
+ And then execute:
14
+ ```bash
15
+ $ bundle
16
+ ```
17
+
18
+ ## Usage
19
+
20
+ Use in controllers:
21
+
22
+ ```rb
23
+ class TicktsController < ApplicationController
24
+ def index
25
+ audit! :tickets, nil
26
+ end
27
+
28
+ def update
29
+ audit! :update_ticket, @ticket, payload: ticket_params
30
+ end
31
+
32
+ def destroy
33
+ audit! :delete_ticket
34
+ end
35
+
36
+ private
37
+
38
+ def ticket_params
39
+ params.required(:ticket).permit!(:title, :description, :status)
40
+ end
41
+ end
42
+ ```
43
+
44
+ In models or other places:
45
+
46
+ ```rb
47
+ AuditLog.audit!(:update_password, @user, payload: { ip: request.ip })
48
+ AuditLog.audit!(:sign_in, @user, payload: { ip: request.ip })
49
+ AuditLog.audit!(:create_address, nil, payload: params)
50
+ ```
51
+
52
+ ## License
53
+
54
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ require "bundler/setup"
5
+ rescue LoadError
6
+ puts "You must `gem install bundler` and `bundle install` to run rake tasks"
7
+ end
8
+
9
+ require "rdoc/task"
10
+
11
+ RDoc::Task.new(:rdoc) do |rdoc|
12
+ rdoc.rdoc_dir = "rdoc"
13
+ rdoc.title = "AuditLog"
14
+ rdoc.options << "--line-numbers"
15
+ rdoc.rdoc_files.include("README.md")
16
+ rdoc.rdoc_files.include("lib/**/*.rb")
17
+ end
18
+
19
+ APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
20
+ load "rails/tasks/engine.rake"
21
+
22
+ load "rails/tasks/statistics.rake"
23
+
24
+ require "bundler/gem_tasks"
25
+
26
+ require "rake/testtask"
27
+
28
+ Rake::TestTask.new(:test) do |t|
29
+ t.libs << "lib"
30
+ t.libs << "test"
31
+ t.pattern = "test/**/*_test.rb"
32
+ t.verbose = false
33
+ end
34
+
35
+ task default: :test
@@ -0,0 +1,2 @@
1
+ //= link_directory ../javascripts/audit-log.js
2
+ //= link_directory ../stylesheets/audit-log.css
@@ -0,0 +1,205 @@
1
+
2
+ body {
3
+ margin: 0;
4
+ padding: 20px 0;
5
+ background-color: #eee;
6
+ }
7
+
8
+ body, textarea {
9
+ font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", Arial, Helvetica, sans-serif;
10
+ font-size: 14px;
11
+ line-height: 1.4;
12
+ color: #333;
13
+ }
14
+
15
+ .footer {
16
+ padding: 15px;
17
+ text-align: center;
18
+ color: #999;
19
+ }
20
+ .footer a:link,
21
+ .footer a:visited { color: #666; text-decoration: underline;}
22
+
23
+ a, a:visited, a:active {
24
+ color: #364cc9;
25
+ text-decoration: none;
26
+ }
27
+
28
+ a:hover {
29
+ text-decoration: underline;
30
+ }
31
+
32
+ table {
33
+ width: 100%;
34
+ border-collapse: collapse;
35
+ border-spacing: 0;
36
+ margin-bottom: 20px;
37
+ }
38
+
39
+ th {
40
+ text-align: left;
41
+ border-bottom: solid 1px #e0e0e0;
42
+ }
43
+
44
+ td.date { width: 150px; font-size: 12px; }
45
+
46
+ h1 {
47
+ margin-top: 0;
48
+ font-size: 20px;
49
+ font-weight: bold;
50
+ }
51
+
52
+ h1, p {
53
+ margin-bottom: 20px;
54
+ }
55
+
56
+ h3 {
57
+ text-align: center;
58
+ }
59
+
60
+ ul {
61
+ list-style-type: none;
62
+ padding: 0;
63
+ margin: 0;
64
+ }
65
+
66
+ table td, table th {
67
+ padding: 10px 15px;
68
+ }
69
+ th { background: #f5f5f5; border-bottom: 1px solid #e0e0e0; }
70
+ td {
71
+ border-top: solid 1px #e0e0e0;
72
+ }
73
+
74
+ pre {
75
+ background-color: #eee;
76
+ padding: 10px;
77
+ white-space: pre-wrap;
78
+ word-break: break-word;
79
+ }
80
+
81
+ textarea {
82
+ width: 100%;
83
+ height: 100px;
84
+ border: solid 1px #ddd;
85
+ padding: 10px;
86
+ }
87
+
88
+ hr {
89
+ border: none;
90
+ height: 0;
91
+ border-top: solid 1px #ddd;
92
+ margin-bottom: 15px;
93
+ }
94
+
95
+ .btn {
96
+ display: inline-block;
97
+ margin-bottom: 0;
98
+ font-size: 14px;
99
+ font-weight: 400;
100
+ line-height: 1.42857143;
101
+ text-align: center;
102
+ white-space: nowrap;
103
+ vertical-align: middle;
104
+ cursor: pointer;
105
+ -webkit-user-select: none;
106
+ -moz-user-select: none;
107
+ -ms-user-select: none;
108
+ user-select: none;
109
+ border-radius: 3px;
110
+ border: 1px solid #ccc;
111
+ padding: 6px 16px;
112
+ color: #555 !important;
113
+ outline: 0 !important;
114
+ background: #FFF;
115
+ }
116
+ .btn:hover { text-decoration: none !important; background: #f7f7f7; }
117
+ .btn-danger { background: #fff; color: #E33F00 !important; border-color: #E33F00;}
118
+ .btn-danger:hover {
119
+ background: #FCEDEC;
120
+ }
121
+
122
+ .form-control {
123
+ font-size: 14px;
124
+ line-height: 1.42857143;
125
+ border: 1px solid #ccc;
126
+ padding: 6px 16px;
127
+ outline: 0 !important;
128
+ border-radius: 3px;
129
+ }
130
+ .container {
131
+ max-width: 1000px;
132
+ margin-left: auto;
133
+ margin-right: auto;
134
+ padding: 20px;
135
+ background-color: #fff;
136
+ box-shadow: 0 1px 8px rgba(200, 200, 200, 0.26);
137
+ border-radius: 3px;
138
+ }
139
+
140
+ .no-record {
141
+ padding: 50px;
142
+ text-align: center;
143
+ font-size: 16px;
144
+ }
145
+
146
+ .toolbar {
147
+ margin-bottom: 15px;
148
+ height: 34px;
149
+ line-height: 34px;
150
+ }
151
+ .toolbar form { display: inline; }
152
+ .toolbar .pull-right { float: right; }
153
+
154
+ #notice {
155
+ padding: 8px 15px;
156
+ background: #3CBD46;
157
+ color: #fff;
158
+ margin-bottom: 15px;
159
+ border-radius: 3px;
160
+ }
161
+
162
+ pre {
163
+ background: #f7f7f7;
164
+ padding: 8px;
165
+ border-radius: 3px;
166
+ font-size: 12px;
167
+ font-family: Menlo, Monaco, Consolas, monospace;
168
+ }
169
+
170
+ h1 { font-size: 16px; }
171
+
172
+ .pagination {
173
+ padding-bottom: 15px;
174
+ font-size: 14px;
175
+ }
176
+
177
+ .pagination li { display: inline; }
178
+
179
+ .pagination a {
180
+ display: inline-block;
181
+ padding: 5px 10px;
182
+ border: 1px solid #eee;
183
+ color: #555;
184
+ text-decoration: none;
185
+ }
186
+ .pagination a:hover {
187
+ background: #f7f7f7;
188
+ }
189
+ .pagination em,
190
+ .pagination .current {
191
+ display: inline-block;
192
+ padding: 5px 10px;
193
+ border: 1px solid #f0f0f0;
194
+ background: #f0f0f0;
195
+ font-style: normal;
196
+ }
197
+
198
+ .detail-group {
199
+ margin-bottom: 8px;
200
+ }
201
+ .detail-group .control-label {
202
+ width: 120px;
203
+ color: #777;
204
+ display: inline-block;
205
+ }
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AuditLog
4
+ class LogsController < ActionController::Base
5
+ layout "audit-log/application"
6
+ before_action :set_log, only: %i[show destroy]
7
+
8
+ def index
9
+ @logs = Log.order("id desc").includes(:user)
10
+
11
+ if params[:q]
12
+ @logs = @logs.where("action like ?", "%#{params[:q]}%")
13
+ end
14
+
15
+ @logs = @logs.page(params[:page]).per(15)
16
+ end
17
+
18
+ def show; end
19
+
20
+ private
21
+
22
+ # Use callbacks to share common setup or constraints between actions.
23
+ def set_log
24
+ @log = Log.find(params[:id])
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AuditLog
4
+ class Log < ActiveRecord::Base
5
+ include AuditLog::Model
6
+ end
7
+ end
@@ -0,0 +1,41 @@
1
+ <div class="toolbar">
2
+ <form class="form-inline" action="<%= audit_log.logs_path %>" method="GET">
3
+ <div class="form-group">
4
+ <input name="q" type="text" class="form-control" placeholder="Search action" value="<%= params[:q] %>" />
5
+ <button type="submit" class="btn btn-primary">Search</button>
6
+ </div>
7
+ </form>
8
+ </div>
9
+ <% if @logs.blank? %>
10
+ <div class="no-record">No audit logs.</div>
11
+ <% else %>
12
+ <%= paginate @logs %>
13
+
14
+ <table class="table table-borded">
15
+ <thead>
16
+ <tr>
17
+ <th>#</th>
18
+ <th>User</th>
19
+ <th>Action</th>
20
+ <th class="date"></th>
21
+ <th></th>
22
+ </tr>
23
+ </thead>
24
+
25
+ <tbody>
26
+ <% @logs.each do |log| %>
27
+ <tr>
28
+ <td><%= log.id %></td>
29
+ <td><%= log.user_name %></td>
30
+ <td><%= log.action_name %></td>
31
+ <td class="date"><%= log.created_at.to_s %></td>
32
+ <td>
33
+ <%= link_to "View", audit_log.log_path(log.id) %>
34
+ </td>
35
+ </tr>
36
+ <% end %>
37
+ </tbody>
38
+ </table>
39
+
40
+ <%= paginate @logs %>
41
+ <% end %>
@@ -0,0 +1,41 @@
1
+ <div class="toolbar">
2
+ <%= link_to 'Back', logs_path, class: 'btn' %>
3
+ </div>
4
+
5
+ <div class="audit-log-detail">
6
+ <h1><%= @log.action_name %></h1>
7
+
8
+ <div class="detail-group">
9
+ <label class="control-label">User:</label>
10
+ <%= @log.user_name %>
11
+ </div>
12
+
13
+ <div class="detail-group">
14
+ <label class="control-label">Time:</label>
15
+ <%= @log.created_at %>
16
+ </div>
17
+
18
+ <div class="detail-group">
19
+ <label class="control-label">URL:</label>
20
+ <%= @log.request["url"] %>
21
+ </div>
22
+
23
+ <div class="detail-group">
24
+ <label class="control-label">IP Address:</label>
25
+ <%= @log.request["ip"] %>
26
+ </div>
27
+
28
+ <div class="detail-group">
29
+ <label class="control-label">User Agent:</label>
30
+ <%= @log.request["user_agent"] %>
31
+ </div>
32
+
33
+ <h4>Payload<h4>
34
+ <pre class="payload"><%= JSON.pretty_generate(@log.payload) %></pre>
35
+
36
+ <% if @log.record %>
37
+ <h4>Record<h4>
38
+ <pre class="payload"><%= JSON.pretty_generate(@log.record.as_json) %></pre>
39
+ <% end %>
40
+
41
+
@@ -0,0 +1,23 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Audit Log</title>
5
+ <%= stylesheet_link_tag "audit-log/application", media: "all" %>
6
+ <%= csrf_meta_tags %>
7
+ </head>
8
+ <body>
9
+
10
+ <div class="container">
11
+ <% if notice %>
12
+ <div id="notice"><%= notice %></div>
13
+ <% end %>
14
+
15
+ <%= yield %>
16
+ </div>
17
+
18
+ <div class="footer">
19
+ <a href="https://github.com/rails-engine/audit-log">AuditLog</a> powered.
20
+ </div>
21
+
22
+ </body>
23
+ </html>
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ AuditLog.configure do
4
+ # class name of you User model, default: 'User'
5
+ # self.user_class = "User"
6
+ # current_user method name in your Controller, default: 'current_user'
7
+ # self.current_user_method = "current_user"
8
+ end
data/config/routes.rb ADDED
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ AuditLog::Engine.routes.draw do
4
+ resources :logs, path: ""
5
+ end
@@ -0,0 +1,17 @@
1
+ class CreateAuditLogs < ActiveRecord::Migration[5.0]
2
+ def change
3
+ create_table "audit_logs", force: :cascade do |t|
4
+ t.string "action", null: false
5
+ t.integer "user_id"
6
+ t.integer "record_id"
7
+ t.string "record_type"
8
+ t.text "payload"
9
+ t.text "request"
10
+ t.datetime "created_at"
11
+ t.datetime "updated_at"
12
+ t.index ["record_type", "record_id"], using: :btree
13
+ t.index ["user_id", "action"], using: :btree
14
+ t.index ["action"], using: :btree
15
+ end
16
+ end
17
+ end
data/lib/audit-log.rb ADDED
@@ -0,0 +1,44 @@
1
+ require_relative "./audit-log/version"
2
+ require_relative "./audit-log/configuration"
3
+ require_relative "./audit-log/model"
4
+ require_relative "./audit-log/engine"
5
+ require "kaminari"
6
+
7
+ module AuditLog
8
+ class << self
9
+ def config
10
+ return @config if defined?(@config)
11
+ @config = Configuration.new
12
+ @config.user_class = "User"
13
+ @config.current_user_method = "current_user"
14
+ @config.user_name_method = "name"
15
+ @config
16
+ end
17
+
18
+ def configure(&block)
19
+ config.instance_exec(&block)
20
+ end
21
+
22
+ # Create an audit log
23
+ #
24
+ # AuditLog.audit!(:edit_account, @account, payload: account_params, user: current_user)
25
+ def audit!(action, record = nil, payload: nil, user: nil, request: nil)
26
+ request_info = {}
27
+ if request
28
+ request_info = {
29
+ ip: request.ip,
30
+ url: request.url,
31
+ user_agent: request.user_agent,
32
+ }
33
+ end
34
+
35
+ AuditLog::Log.create!(
36
+ action: action,
37
+ record: record,
38
+ payload: (payload || {}).to_h.deep_stringify_keys,
39
+ user: user,
40
+ request: request_info.deep_stringify_keys
41
+ )
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AuditLog
4
+ class Configuration
5
+ # class name of you User model, default: 'User'
6
+ attr_accessor :user_class
7
+
8
+ # current_user method name in your Controller, default: 'current_user'
9
+ attr_accessor :current_user_method
10
+
11
+ # user name method, default: "name"
12
+ attr_accessor :user_name_method
13
+ end
14
+ end
@@ -0,0 +1,11 @@
1
+ module AuditLog
2
+ module ControllerHelper
3
+ # Create an audit log
4
+ #
5
+ # audit!(:edit_account, @account, payload: account_params)
6
+ def audit!(action, record = nil, payload: nil, user: nil)
7
+ user ||= self.send(AuditLog.config.current_user_method.to_sym)
8
+ AuditLog.audit!(action, record, payload: payload, request: request, user: user)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ require_relative "./controller_helper"
2
+
3
+ module AuditLog
4
+ class Engine < Rails::Engine
5
+ isolate_namespace AuditLog
6
+
7
+ ActiveSupport.on_load(:action_controller_base) do
8
+ prepend AuditLog::ControllerHelper
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,34 @@
1
+ module AuditLog
2
+ module Model
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ self.table_name = "audit_logs"
7
+
8
+ serialize :payload, JSON
9
+ serialize :request, JSON
10
+
11
+ belongs_to :user, class_name: AuditLog.config.user_class, required: false
12
+ belongs_to :record, polymorphic: true, required: false
13
+
14
+ validates :action, presence: true
15
+
16
+ after_initialize :initialize_payload_request
17
+ end
18
+
19
+ def initialize_payload_request
20
+ self.payload = {} if payload.nil?
21
+ self.request = {} if request.nil?
22
+ end
23
+
24
+ def user_name
25
+ return "none" if self.user.blank?
26
+
27
+ self.user.send(AuditLog.config.user_name_method)
28
+ end
29
+
30
+ def action_name
31
+ I18n.t("audit_log.action.#{self.action}", default: self.action)
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,3 @@
1
+ module AuditLog
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+ module AuditLog
5
+ module Generators
6
+ class InstallGenerator < Rails::Generators::Base
7
+ desc "Create AuditLog's base files"
8
+ source_root File.expand_path("../../..", __dir__)
9
+
10
+ def add_initializer
11
+ template "config/initializers/audit-log.rb", "config/initializers/audit-log.rb"
12
+ end
13
+
14
+ def add_migrations
15
+ exec("rake audit_log:install:migrations")
16
+ end
17
+ end
18
+ end
19
+ end
metadata ADDED
@@ -0,0 +1,107 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: audit-log
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Jason Lee
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-05-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '4.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '4.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: kaminari
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0.15'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0.15'
41
+ - !ruby/object:Gem::Dependency
42
+ name: mysql2
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: Trail audit logs (Operation logs) into the database for user behaviors,
56
+ including a web UI to query logs.
57
+ email:
58
+ - huacnlee@gmail.com
59
+ executables: []
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
63
+ - MIT-LICENSE
64
+ - README.md
65
+ - Rakefile
66
+ - app/assets/config/exception_track_manifest.js
67
+ - app/assets/stylesheets/audit-log/application.css
68
+ - app/controllers/audit_log/logs_controller.rb
69
+ - app/models/audit_log/log.rb
70
+ - app/views/audit_log/logs/index.html.erb
71
+ - app/views/audit_log/logs/show.html.erb
72
+ - app/views/layouts/audit-log/application.html.erb
73
+ - config/initializers/audit-log.rb
74
+ - config/routes.rb
75
+ - db/migrate/20190527035005_create_audit_logs.rb
76
+ - lib/audit-log.rb
77
+ - lib/audit-log/configuration.rb
78
+ - lib/audit-log/controller_helper.rb
79
+ - lib/audit-log/engine.rb
80
+ - lib/audit-log/model.rb
81
+ - lib/audit-log/version.rb
82
+ - lib/generators/audit_log/install_generator.rb
83
+ homepage: https://github.com/rails-engine/audit-log
84
+ licenses:
85
+ - MIT
86
+ metadata: {}
87
+ post_install_message:
88
+ rdoc_options: []
89
+ require_paths:
90
+ - lib
91
+ required_ruby_version: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ required_rubygems_version: !ruby/object:Gem::Requirement
97
+ requirements:
98
+ - - ">="
99
+ - !ruby/object:Gem::Version
100
+ version: '0'
101
+ requirements: []
102
+ rubygems_version: 3.0.3
103
+ signing_key:
104
+ specification_version: 4
105
+ summary: Trail audit logs (Operation logs) into the database for user behaviors, including
106
+ a web UI to query logs
107
+ test_files: []