sinaliza 0.2.1 → 0.3.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.
@@ -0,0 +1,62 @@
1
+ module Sinaliza
2
+ class InterceptorsController < ApplicationController
3
+ before_action :set_interceptor, only: [ :edit, :update, :destroy, :toggle ]
4
+
5
+ def index
6
+ @interceptors = Interceptor.order(created_at: :desc)
7
+ end
8
+
9
+ def new
10
+ @interceptor = Interceptor.new
11
+ end
12
+
13
+ def create
14
+ @interceptor = Interceptor.new(interceptor_params)
15
+
16
+ if @interceptor.save
17
+ redirect_to interceptors_path, notice: "Interceptor created."
18
+ else
19
+ render :new, status: :unprocessable_entity
20
+ end
21
+ end
22
+
23
+ def edit
24
+ end
25
+
26
+ def update
27
+ if @interceptor.update(interceptor_params)
28
+ redirect_to interceptors_path, notice: "Interceptor updated."
29
+ else
30
+ render :edit, status: :unprocessable_entity
31
+ end
32
+ end
33
+
34
+ def destroy
35
+ @interceptor.destroy
36
+ redirect_to interceptors_path, notice: "Interceptor removed."
37
+ end
38
+
39
+ def toggle
40
+ if @interceptor.active?
41
+ @interceptor.deactivate!
42
+ else
43
+ @interceptor.activate!
44
+ end
45
+
46
+ redirect_to interceptors_path
47
+ end
48
+
49
+ private
50
+
51
+ def set_interceptor
52
+ @interceptor = Interceptor.find(params[:id])
53
+ end
54
+
55
+ def interceptor_params
56
+ params.require(:interceptor).permit(
57
+ :target_class, :method_name, :method_type, :event_name,
58
+ :capture_args, :capture_return, :capture_execution_time
59
+ )
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,61 @@
1
+ module Sinaliza
2
+ class Interceptor < ApplicationRecord
3
+ METHOD_TYPES = %w[instance class].freeze
4
+
5
+ validates :target_class, presence: true
6
+ validates :method_name, presence: true
7
+ validates :event_name, presence: true
8
+ validates :method_type, presence: true, inclusion: { in: METHOD_TYPES }
9
+ validates :target_class, uniqueness: { scope: [ :method_name, :method_type ] }
10
+ validate :target_class_exists
11
+ validate :method_exists_on_target
12
+
13
+ scope :active, -> { where(active: true) }
14
+ scope :inactive, -> { where(active: false) }
15
+ scope :by_target_class, ->(name) { where(target_class: name) }
16
+
17
+ after_create :apply_interceptor
18
+ after_destroy :deactivate!
19
+
20
+ def activate!
21
+ update!(active: true)
22
+ apply_interceptor unless InterceptorRegistry.applied?(self)
23
+ end
24
+
25
+ def deactivate!
26
+ update!(active: false) unless destroyed?
27
+ end
28
+
29
+ def key
30
+ separator = method_type == "instance" ? "#" : "."
31
+ "#{target_class}#{separator}#{method_name}"
32
+ end
33
+
34
+ private
35
+
36
+ def apply_interceptor
37
+ InterceptorRegistry.apply!(self)
38
+ end
39
+
40
+ def target_class_exists
41
+ return if target_class.blank?
42
+
43
+ target_class.constantize
44
+ rescue NameError
45
+ errors.add(:target_class, "\"#{target_class}\" does not exist")
46
+ end
47
+
48
+ def method_exists_on_target
49
+ return if target_class.blank? || method_name.blank? || method_type.blank?
50
+
51
+ klass = target_class.constantize
52
+ available = method_type == "class" ? klass.methods : klass.instance_methods + klass.private_instance_methods
53
+
54
+ return if available.include?(method_name.to_sym)
55
+
56
+ errors.add(:method_name, "\"#{method_name}\" does not exist on #{target_class}")
57
+ rescue NameError
58
+ # target_class_exists already handles this
59
+ end
60
+ end
61
+ end
@@ -2,14 +2,25 @@
2
2
  <html>
3
3
  <head>
4
4
  <title>Sinaliza</title>
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
5
6
  <%= csrf_meta_tags %>
6
7
  <%= csp_meta_tag %>
7
8
 
8
9
  <%= yield :head %>
9
10
 
10
- <%= stylesheet_link_tag "sinaliza/application", media: "all" %>
11
+ <%= stylesheet_link_tag "sinaliza/application", media: "all" %>
11
12
  </head>
12
- <body>
13
+ <body class="sinaliza-root">
14
+
15
+ <nav class="sinaliza-nav">
16
+ <div class="sinaliza-nav__container">
17
+ <span class="sinaliza-nav__brand">sinaliza</span>
18
+ <div class="sinaliza-nav__links">
19
+ <%= link_to "Events", sinaliza.events_path, class: "sinaliza-nav__link" %>
20
+ <%= link_to "Interceptors", sinaliza.interceptors_path, class: "sinaliza-nav__link" %>
21
+ </div>
22
+ </div>
23
+ </nav>
13
24
 
14
25
  <%= yield %>
15
26
 
@@ -1,5 +1,5 @@
1
1
  <div class="sinaliza-dashboard">
2
- <h1>Events Monitor</h1>
2
+ <h1>Events</h1>
3
3
 
4
4
  <%= render "filters" %>
5
5
 
@@ -22,10 +22,31 @@
22
22
  <td class="sinaliza-time"><%= event.created_at.strftime("%Y-%m-%d %H:%M:%S") %></td>
23
23
  <td><span class="sinaliza-badge"><%= event.name %></span></td>
24
24
  <td><span class="sinaliza-badge sinaliza-badge--source"><%= event.source %></span></td>
25
- <td><%= event.actor ? "#{event.actor_type}##{event.actor_id}" : "-" %></td>
26
- <td><%= event.target ? "#{event.target_type}##{event.target_id}" : "-" %></td>
27
- <td><%= event.context ? "#{event.context_type}##{event.context_id}" : "-" %></td>
28
- <td><%= event.children.size %></td>
25
+ <td>
26
+ <% if event.actor %>
27
+ <span class="sinaliza-ref"><%= event.actor_type %>#<%= event.actor_id %></span>
28
+ <% else %>
29
+ <span class="sinaliza-null">&mdash;</span>
30
+ <% end %>
31
+ </td>
32
+ <td>
33
+ <% if event.target %>
34
+ <span class="sinaliza-ref"><%= event.target_type %>#<%= event.target_id %></span>
35
+ <% else %>
36
+ <span class="sinaliza-null">&mdash;</span>
37
+ <% end %>
38
+ </td>
39
+ <td>
40
+ <% if event.context %>
41
+ <span class="sinaliza-ref"><%= event.context_type %>#<%= event.context_id %></span>
42
+ <% else %>
43
+ <span class="sinaliza-null">&mdash;</span>
44
+ <% end %>
45
+ </td>
46
+ <td>
47
+ <% count = event.children.size %>
48
+ <span class="sinaliza-children-count <%= 'sinaliza-children-count--has' if count > 0 %>"><%= count %></span>
49
+ </td>
29
50
  <td><%= link_to "Detail", event_path(event), class: "sinaliza-link" %></td>
30
51
  </tr>
31
52
  <% end %>
@@ -1,5 +1,5 @@
1
1
  <div class="sinaliza-dashboard">
2
- <p><%= link_to "Back to events", events_path, class: "sinaliza-link" %></p>
2
+ <p><%= link_to "Back to events", events_path, class: "sinaliza-link sinaliza-link--back" %></p>
3
3
 
4
4
  <h1>Event #<%= @event.id %></h1>
5
5
 
@@ -20,15 +20,33 @@
20
20
  </tr>
21
21
  <tr>
22
22
  <th>Actor</th>
23
- <td><%= @event.actor ? "#{@event.actor_type}##{@event.actor_id}" : "-" %></td>
23
+ <td>
24
+ <% if @event.actor %>
25
+ <span class="sinaliza-ref"><%= @event.actor_type %>#<%= @event.actor_id %></span>
26
+ <% else %>
27
+ <span class="sinaliza-null">&mdash;</span>
28
+ <% end %>
29
+ </td>
24
30
  </tr>
25
31
  <tr>
26
32
  <th>Target</th>
27
- <td><%= @event.target ? "#{@event.target_type}##{@event.target_id}" : "-" %></td>
33
+ <td>
34
+ <% if @event.target %>
35
+ <span class="sinaliza-ref"><%= @event.target_type %>#<%= @event.target_id %></span>
36
+ <% else %>
37
+ <span class="sinaliza-null">&mdash;</span>
38
+ <% end %>
39
+ </td>
28
40
  </tr>
29
41
  <tr>
30
42
  <th>Context</th>
31
- <td><%= @event.context ? "#{@event.context_type}##{@event.context_id}" : "-" %></td>
43
+ <td>
44
+ <% if @event.context %>
45
+ <span class="sinaliza-ref"><%= @event.context_type %>#<%= @event.context_id %></span>
46
+ <% else %>
47
+ <span class="sinaliza-null">&mdash;</span>
48
+ <% end %>
49
+ </td>
32
50
  </tr>
33
51
  <tr>
34
52
  <th>Metadata</th>
@@ -36,19 +54,25 @@
36
54
  </tr>
37
55
  <tr>
38
56
  <th>IP address</th>
39
- <td><%= @event.ip_address || "-" %></td>
57
+ <td><%= @event.ip_address || content_tag(:span, "&mdash;".html_safe, class: "sinaliza-null") %></td>
40
58
  </tr>
41
59
  <tr>
42
60
  <th>User agent</th>
43
- <td><%= @event.user_agent || "-" %></td>
61
+ <td><%= @event.user_agent || content_tag(:span, "&mdash;".html_safe, class: "sinaliza-null") %></td>
44
62
  </tr>
45
63
  <tr>
46
64
  <th>Request ID</th>
47
- <td><%= @event.request_id || "-" %></td>
65
+ <td>
66
+ <% if @event.request_id %>
67
+ <span class="sinaliza-ref"><%= @event.request_id %></span>
68
+ <% else %>
69
+ <span class="sinaliza-null">&mdash;</span>
70
+ <% end %>
71
+ </td>
48
72
  </tr>
49
73
  <tr>
50
74
  <th>Created at</th>
51
- <td><%= @event.created_at.strftime("%Y-%m-%d %H:%M:%S %Z") %></td>
75
+ <td class="sinaliza-time"><%= @event.created_at.strftime("%Y-%m-%d %H:%M:%S %Z") %></td>
52
76
  </tr>
53
77
  </table>
54
78
 
@@ -72,8 +96,20 @@
72
96
  <td class="sinaliza-time"><%= child.created_at.strftime("%Y-%m-%d %H:%M:%S") %></td>
73
97
  <td><span class="sinaliza-badge"><%= child.name %></span></td>
74
98
  <td><span class="sinaliza-badge sinaliza-badge--source"><%= child.source %></span></td>
75
- <td><%= child.actor ? "#{child.actor_type}##{child.actor_id}" : "-" %></td>
76
- <td><%= child.target ? "#{child.target_type}##{child.target_id}" : "-" %></td>
99
+ <td>
100
+ <% if child.actor %>
101
+ <span class="sinaliza-ref"><%= child.actor_type %>#<%= child.actor_id %></span>
102
+ <% else %>
103
+ <span class="sinaliza-null">&mdash;</span>
104
+ <% end %>
105
+ </td>
106
+ <td>
107
+ <% if child.target %>
108
+ <span class="sinaliza-ref"><%= child.target_type %>#<%= child.target_id %></span>
109
+ <% else %>
110
+ <span class="sinaliza-null">&mdash;</span>
111
+ <% end %>
112
+ </td>
77
113
  <td><%= link_to "Detail", event_path(child), class: "sinaliza-link" %></td>
78
114
  </tr>
79
115
  <% end %>
@@ -0,0 +1,42 @@
1
+ <%= form_with model: @interceptor, url: url, method: method, class: "sinaliza-form" do |f| %>
2
+ <% if @interceptor.errors.any? %>
3
+ <div class="sinaliza-errors">
4
+ <ul>
5
+ <% @interceptor.errors.full_messages.each do |message| %>
6
+ <li><%= message %></li>
7
+ <% end %>
8
+ </ul>
9
+ </div>
10
+ <% end %>
11
+
12
+ <div class="sinaliza-form__field">
13
+ <%= f.label :target_class, "Target class" %>
14
+ <%= f.text_field :target_class, placeholder: "User" %>
15
+ </div>
16
+
17
+ <div class="sinaliza-form__field">
18
+ <%= f.label :method_name, "Method name" %>
19
+ <%= f.text_field :method_name, placeholder: "update_profile" %>
20
+ </div>
21
+
22
+ <div class="sinaliza-form__field">
23
+ <%= f.label :method_type, "Method type" %>
24
+ <%= f.select :method_type, Sinaliza::Interceptor::METHOD_TYPES %>
25
+ </div>
26
+
27
+ <div class="sinaliza-form__field">
28
+ <%= f.label :event_name, "Event name" %>
29
+ <%= f.text_field :event_name, placeholder: "user.profile_updated" %>
30
+ </div>
31
+
32
+ <div class="sinaliza-form__checkboxes">
33
+ <label><%= f.check_box :capture_args %> Capture arguments</label>
34
+ <label><%= f.check_box :capture_return %> Capture return value</label>
35
+ <label><%= f.check_box :capture_execution_time %> Capture execution time</label>
36
+ </div>
37
+
38
+ <div class="sinaliza-form__actions">
39
+ <%= f.submit class: "sinaliza-btn" %>
40
+ <%= link_to "Cancel", interceptors_path, class: "sinaliza-link" %>
41
+ </div>
42
+ <% end %>
@@ -0,0 +1,7 @@
1
+ <div class="sinaliza-dashboard">
2
+ <p><%= link_to "Back to interceptors", interceptors_path, class: "sinaliza-link sinaliza-link--back" %></p>
3
+
4
+ <h1>Edit Interceptor</h1>
5
+
6
+ <%= render "form", url: interceptor_path(@interceptor), method: :patch %>
7
+ </div>
@@ -0,0 +1,57 @@
1
+ <div class="sinaliza-dashboard">
2
+ <div class="sinaliza-header">
3
+ <h1>Interceptors</h1>
4
+ <%= link_to "New interceptor", new_interceptor_path, class: "sinaliza-btn" %>
5
+ </div>
6
+
7
+ <table class="sinaliza-table">
8
+ <thead>
9
+ <tr>
10
+ <th>Target</th>
11
+ <th>Event name</th>
12
+ <th>Capture</th>
13
+ <th>Status</th>
14
+ <th></th>
15
+ </tr>
16
+ </thead>
17
+ <tbody>
18
+ <% @interceptors.each do |interceptor| %>
19
+ <tr>
20
+ <td><span class="sinaliza-badge"><%= interceptor.key %></span></td>
21
+ <td><span class="sinaliza-badge sinaliza-badge--source"><%= interceptor.event_name %></span></td>
22
+ <td>
23
+ <% captures = [] %>
24
+ <% captures << "args" if interceptor.capture_args %>
25
+ <% captures << "return" if interceptor.capture_return %>
26
+ <% captures << "time" if interceptor.capture_execution_time %>
27
+ <% if captures.any? %>
28
+ <% captures.each do |c| %>
29
+ <span class="sinaliza-capture-tag"><%= c %></span>
30
+ <% end %>
31
+ <% else %>
32
+ <span class="sinaliza-null">&mdash;</span>
33
+ <% end %>
34
+ </td>
35
+ <td>
36
+ <%= button_to toggle_interceptor_path(interceptor), method: :patch, class: "sinaliza-switch-form" do %>
37
+ <span class="sinaliza-switch <%= 'sinaliza-switch--on' if interceptor.active? %>">
38
+ <span class="sinaliza-switch__track">
39
+ <span class="sinaliza-switch__knob"></span>
40
+ </span>
41
+ </span>
42
+ <% end %>
43
+ </td>
44
+ <td class="sinaliza-actions">
45
+ <%= link_to "Edit", edit_interceptor_path(interceptor), class: "sinaliza-link" %>
46
+ <%= button_to "Delete", interceptor_path(interceptor), method: :delete,
47
+ data: { turbo_confirm: "Are you sure?" }, class: "sinaliza-link sinaliza-link--danger" %>
48
+ </td>
49
+ </tr>
50
+ <% end %>
51
+ </tbody>
52
+ </table>
53
+
54
+ <% if @interceptors.empty? %>
55
+ <p class="sinaliza-empty">No interceptors configured.</p>
56
+ <% end %>
57
+ </div>
@@ -0,0 +1,7 @@
1
+ <div class="sinaliza-dashboard">
2
+ <p><%= link_to "Back to interceptors", interceptors_path, class: "sinaliza-link sinaliza-link--back" %></p>
3
+
4
+ <h1>New Interceptor</h1>
5
+
6
+ <%= render "form", url: interceptors_path, method: :post %>
7
+ </div>
data/config/routes.rb CHANGED
@@ -1,4 +1,9 @@
1
1
  Sinaliza::Engine.routes.draw do
2
2
  resources :events, only: [ :index, :show ]
3
+ resources :interceptors, except: [ :show ] do
4
+ member do
5
+ patch :toggle
6
+ end
7
+ end
3
8
  root to: "events#index"
4
9
  end
@@ -0,0 +1,20 @@
1
+ class CreateSinalizaInterceptors < ActiveRecord::Migration[8.0]
2
+ def change
3
+ create_table :sinaliza_interceptors do |t|
4
+ t.string :target_class, null: false
5
+ t.string :method_name, null: false
6
+ t.string :method_type, null: false, default: "instance"
7
+ t.string :event_name, null: false
8
+ t.boolean :capture_args, default: false
9
+ t.boolean :capture_return, default: false
10
+ t.boolean :capture_execution_time, default: false
11
+ t.boolean :active, default: true
12
+
13
+ t.timestamps
14
+ end
15
+
16
+ add_index :sinaliza_interceptors, [ :target_class, :method_name, :method_type ],
17
+ unique: true, name: "idx_sinaliza_interceptors_uniqueness"
18
+ add_index :sinaliza_interceptors, :active
19
+ end
20
+ end
@@ -1,5 +1,28 @@
1
1
  module Sinaliza
2
2
  class Engine < ::Rails::Engine
3
3
  isolate_namespace Sinaliza
4
+
5
+ initializer "sinaliza.interceptors", after: :load_config_initializers do
6
+ ActiveSupport.on_load(:active_record) do
7
+ ActiveRecord::Base.connection_pool.with_connection do
8
+ if ActiveRecord::Base.connection.table_exists?(:sinaliza_interceptors)
9
+ Sinaliza::InterceptorRegistry.apply_all!
10
+ end
11
+ end
12
+ rescue ActiveRecord::NoDatabaseError
13
+ # Database not created yet — skip
14
+ end
15
+ end
16
+
17
+ config.to_prepare do
18
+ ActiveRecord::Base.connection_pool.with_connection do
19
+ if ActiveRecord::Base.connection.table_exists?(:sinaliza_interceptors)
20
+ Sinaliza::InterceptorRegistry.reset!
21
+ Sinaliza::InterceptorRegistry.apply_all!
22
+ end
23
+ end
24
+ rescue ActiveRecord::NoDatabaseError
25
+ # Database not created yet — skip
26
+ end
4
27
  end
5
28
  end
@@ -0,0 +1,109 @@
1
+ module Sinaliza
2
+ module InterceptorRegistry
3
+ class << self
4
+ def apply_all!
5
+ Sinaliza::Interceptor.find_each do |interceptor|
6
+ apply!(interceptor)
7
+ end
8
+ end
9
+
10
+ def apply!(interceptor)
11
+ key = interceptor.key
12
+
13
+ # Store the authoritative interceptor ID for this key
14
+ authoritative_ids[key] = interceptor.id
15
+
16
+ klass = interceptor.target_class.constantize
17
+ target = interceptor.method_type == "class" ? klass.singleton_class : klass
18
+ class_id = target.object_id
19
+
20
+ # Only prepend if the class object changed (reloaded) or never prepended
21
+ unless prepended_classes[key] == class_id
22
+ mod = build_module(key)
23
+ target.prepend(mod)
24
+ prepended_classes[key] = class_id
25
+ end
26
+ rescue NameError
27
+ # Target class does not exist — skip
28
+ end
29
+
30
+ def applied?(interceptor)
31
+ prepended_classes.key?(interceptor.key)
32
+ end
33
+
34
+ def authoritative_id_for(key)
35
+ authoritative_ids[key]
36
+ end
37
+
38
+ def reset!
39
+ prepended_classes.clear
40
+ authoritative_ids.clear
41
+ end
42
+
43
+ private
44
+
45
+ def authoritative_ids
46
+ @authoritative_ids ||= {}
47
+ end
48
+
49
+ def prepended_classes
50
+ @prepended_classes ||= {}
51
+ end
52
+
53
+ def build_module(key)
54
+ is_instance = key.include?("#")
55
+
56
+ Module.new do
57
+ method_name = is_instance ? key.split("#").last : key.split(".").last
58
+
59
+ define_method(method_name.to_sym) do |*args, **kwargs, &block|
60
+ # Prevent reentrant recording (e.g. ActiveRecord calling save internally)
61
+ thread_key = :"_sinaliza_interceptor_#{key}"
62
+ if Thread.current[thread_key]
63
+ return super(*args, **kwargs, &block)
64
+ end
65
+
66
+ auth_id = Sinaliza::InterceptorRegistry.authoritative_id_for(key)
67
+ record = auth_id ? Sinaliza::Interceptor.find_by(id: auth_id) : nil
68
+
69
+ unless record&.active?
70
+ return super(*args, **kwargs, &block)
71
+ end
72
+
73
+ metadata = {}
74
+
75
+ if record.capture_args
76
+ metadata[:args] = args.map(&:inspect)
77
+ metadata[:kwargs] = kwargs.transform_values(&:inspect) if kwargs.any?
78
+ end
79
+
80
+ begin
81
+ Thread.current[thread_key] = true
82
+ start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) if record.capture_execution_time
83
+ result = super(*args, **kwargs, &block)
84
+ ensure
85
+ Thread.current[thread_key] = nil
86
+ end
87
+
88
+ if record.capture_execution_time && start_time
89
+ elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time
90
+ metadata[:execution_time_ms] = (elapsed * 1000).round(2)
91
+ end
92
+
93
+ metadata[:return] = result.inspect if record.capture_return
94
+
95
+ event_attrs = { name: record.event_name, metadata: metadata, source: "interceptor" }
96
+
97
+ # For instance methods, self is the target object
98
+ if is_instance && self.class < ActiveRecord::Base
99
+ event_attrs[:target] = self
100
+ end
101
+
102
+ Sinaliza.record(**event_attrs)
103
+ result
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
@@ -1,3 +1,3 @@
1
1
  module Sinaliza
2
- VERSION = "0.2.1"
2
+ VERSION = "0.3.0"
3
3
  end
data/lib/sinaliza.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require "sinaliza/version"
2
2
  require "sinaliza/engine"
3
3
  require "sinaliza/configuration"
4
+ require "sinaliza/interceptor_registry"
4
5
 
5
6
  module Sinaliza
6
7
  class << self
@@ -29,6 +30,17 @@ module Sinaliza
29
30
  )
30
31
  end
31
32
 
33
+ def intercept(target_class, method_name, event_name:, method_type: "instance", **options)
34
+ interceptor = Sinaliza::Interceptor.create!(
35
+ target_class: target_class.to_s,
36
+ method_name: method_name.to_s,
37
+ method_type: method_type,
38
+ event_name: event_name,
39
+ **options
40
+ )
41
+ interceptor
42
+ end
43
+
32
44
  def record_later(name:, actor: nil, target: nil, context: nil, parent: nil, metadata: {}, source: nil, ip_address: nil, user_agent: nil, request_id: nil)
33
45
  attributes = {
34
46
  name: name,
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sinaliza
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Marcelo Moraes
@@ -45,6 +45,7 @@ files:
45
45
  - app/controllers/concerns/sinaliza/traceable.rb
46
46
  - app/controllers/sinaliza/application_controller.rb
47
47
  - app/controllers/sinaliza/events_controller.rb
48
+ - app/controllers/sinaliza/interceptors_controller.rb
48
49
  - app/helpers/sinaliza/application_helper.rb
49
50
  - app/jobs/sinaliza/application_job.rb
50
51
  - app/jobs/sinaliza/record_event_job.rb
@@ -52,17 +53,24 @@ files:
52
53
  - app/models/concerns/sinaliza/trackable.rb
53
54
  - app/models/sinaliza/application_record.rb
54
55
  - app/models/sinaliza/event.rb
56
+ - app/models/sinaliza/interceptor.rb
55
57
  - app/views/layouts/sinaliza/application.html.erb
56
58
  - app/views/sinaliza/events/_filters.html.erb
57
59
  - app/views/sinaliza/events/index.html.erb
58
60
  - app/views/sinaliza/events/show.html.erb
61
+ - app/views/sinaliza/interceptors/_form.html.erb
62
+ - app/views/sinaliza/interceptors/edit.html.erb
63
+ - app/views/sinaliza/interceptors/index.html.erb
64
+ - app/views/sinaliza/interceptors/new.html.erb
59
65
  - config/routes.rb
60
66
  - db/migrate/20260219000000_create_sinaliza_events.rb
61
67
  - db/migrate/20260219100000_add_parent_id_to_sinaliza_events.rb
62
68
  - db/migrate/20260220000000_add_context_to_sinaliza_events.rb
69
+ - db/migrate/20260314000000_create_sinaliza_interceptors.rb
63
70
  - lib/sinaliza.rb
64
71
  - lib/sinaliza/configuration.rb
65
72
  - lib/sinaliza/engine.rb
73
+ - lib/sinaliza/interceptor_registry.rb
66
74
  - lib/sinaliza/version.rb
67
75
  - lib/tasks/sinaliza_tasks.rake
68
76
  homepage: https://github.com/marcelonmoraes/sinaliza