signalman 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 +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +28 -0
- data/Rakefile +8 -0
- data/app/assets/config/signalman_manifest.js +0 -0
- data/app/controllers/signalman/application_controller.rb +4 -0
- data/app/controllers/signalman/generators/models_controller.rb +15 -0
- data/app/controllers/signalman/generators/scaffolds_controller.rb +15 -0
- data/app/controllers/signalman/jobs_controller.rb +11 -0
- data/app/controllers/signalman/mails_controller.rb +11 -0
- data/app/controllers/signalman/queries_controller.rb +11 -0
- data/app/controllers/signalman/requests_controller.rb +11 -0
- data/app/controllers/signalman/views_controller.rb +11 -0
- data/app/helpers/signalman/events_helper.rb +18 -0
- data/app/models/signalman/event.rb +25 -0
- data/app/models/signalman.rb +5 -0
- data/app/views/layouts/signalman/application.html.erb +22 -0
- data/app/views/signalman/generators/models/create.html.erb +5 -0
- data/app/views/signalman/generators/models/show.html.erb +36 -0
- data/app/views/signalman/generators/scaffolds/create.html.erb +9 -0
- data/app/views/signalman/generators/scaffolds/show.html.erb +36 -0
- data/app/views/signalman/jobs/index.html.erb +28 -0
- data/app/views/signalman/jobs/show.html.erb +17 -0
- data/app/views/signalman/mails/index.html.erb +28 -0
- data/app/views/signalman/mails/show.html.erb +17 -0
- data/app/views/signalman/queries/index.html.erb +24 -0
- data/app/views/signalman/queries/show.html.erb +17 -0
- data/app/views/signalman/requests/index.html.erb +31 -0
- data/app/views/signalman/requests/show.html.erb +19 -0
- data/app/views/signalman/shared/_flash.html.erb +7 -0
- data/app/views/signalman/shared/_nav.html.erb +30 -0
- data/app/views/signalman/views/index.html.erb +27 -0
- data/app/views/signalman/views/show.html.erb +20 -0
- data/config/routes.rb +16 -0
- data/db/migrate/20230729122215_create_signalman_events.rb +15 -0
- data/lib/signalman/engine.rb +16 -0
- data/lib/signalman/version.rb +3 -0
- data/lib/signalman.rb +183 -0
- data/lib/tasks/signalman_tasks.rake +4 -0
- metadata +98 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: b339dda80c8dea8c1ce6c651d543c808fdcacc634b013d27e3791fe9be6c1a13
|
4
|
+
data.tar.gz: 482987c4c114ee1e1f3c630c45e37879dc058b70747712e82b5f818da28fe129
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 9c998b4967fd840aa5c503fc4aa9242ea6e479300b96bab94d6720ab8fadb2c7630d967deed07f6a6360a3237371a6d58df52ab612366b3ac32c90bd57a1ae4d
|
7
|
+
data.tar.gz: 8c17c1381bbf37112493f7cf9afe13322951c2089f9d71b9e5bdd000eab4c843cc3dc19a085d6ce8aac103ef93127e8357c2834dbb5a8932065c612d01fcf29c
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2023 Chris Oliver
|
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,28 @@
|
|
1
|
+
# Signalman
|
2
|
+
Short description and motivation.
|
3
|
+
|
4
|
+
## Usage
|
5
|
+
How to use my plugin.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
Add this line to your application's Gemfile:
|
9
|
+
|
10
|
+
```ruby
|
11
|
+
gem "signalman"
|
12
|
+
```
|
13
|
+
|
14
|
+
And then execute:
|
15
|
+
```bash
|
16
|
+
$ bundle
|
17
|
+
```
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
```bash
|
21
|
+
$ gem install signalman
|
22
|
+
```
|
23
|
+
|
24
|
+
## Contributing
|
25
|
+
Contribution directions go here.
|
26
|
+
|
27
|
+
## License
|
28
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
File without changes
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require "open3"
|
2
|
+
|
3
|
+
module Signalman
|
4
|
+
class Generators::ModelsController < ApplicationController
|
5
|
+
def show
|
6
|
+
end
|
7
|
+
|
8
|
+
def create
|
9
|
+
@fields = params[:fields].map{ |field| [field[:name], field[:type]].join(":") }
|
10
|
+
Bundler.with_original_env do
|
11
|
+
@stdout, @stderr, @status = Open3.capture3("rails", "generate", "model", params[:model_name], *@fields)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require "open3"
|
2
|
+
|
3
|
+
module Signalman
|
4
|
+
class Generators::ScaffoldsController < ApplicationController
|
5
|
+
def show
|
6
|
+
end
|
7
|
+
|
8
|
+
def create
|
9
|
+
@fields = params[:fields].map{ |field| [field[:name], field[:type]].join(":") }
|
10
|
+
Bundler.with_original_env do
|
11
|
+
@stdout, @stderr, @status = Open3.capture3("rails", "generate", "scaffold", params[:model_name], *@fields)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Signalman
|
2
|
+
module EventsHelper
|
3
|
+
def signalman_path_for(event)
|
4
|
+
block = Signalman.events.find{ |key, _| key.match? event.name }.last[:path]
|
5
|
+
instance_exec event, &block
|
6
|
+
end
|
7
|
+
|
8
|
+
def badge_for_request_duration(duration, &block)
|
9
|
+
if duration <= 200
|
10
|
+
tag.span "#{duration.round}ms", class: "bg-gray-100 text-gray-800 px-2 py-1 rounded text-xs"
|
11
|
+
elsif duration <= 500
|
12
|
+
tag.span "#{duration.round}ms", class: "bg-yellow-100 text-yellow-800 px-2 py-1 rounded text-xs"
|
13
|
+
else
|
14
|
+
tag.span "#{duration.round}ms", class: "bg-red-100 text-red-800 rounded px-2 py-1 text-xs"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
class Signalman::Event < ApplicationRecord
|
2
|
+
scope :requests, ->{ where(name: "process_action.action_controller") }
|
3
|
+
scope :mails, ->{ where(name: "deliver.action_mailer") }
|
4
|
+
scope :queries, ->{ where(name: "sql.active_record") }
|
5
|
+
scope :views, ->{
|
6
|
+
where(name: [
|
7
|
+
"render_template.action_view",
|
8
|
+
"render_partial.action_view",
|
9
|
+
"render_collection.action_view",
|
10
|
+
"render_layout.action_view",
|
11
|
+
])
|
12
|
+
}
|
13
|
+
scope :jobs, -> {
|
14
|
+
where(name: [
|
15
|
+
"enqueue_at.active_job",
|
16
|
+
"enqueue.active_job",
|
17
|
+
"perform.active_job",
|
18
|
+
"perform_start.active_job",
|
19
|
+
"discard.active_job",
|
20
|
+
])
|
21
|
+
}
|
22
|
+
|
23
|
+
|
24
|
+
scope :recent_first, ->{ order(created_at: :desc) }
|
25
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>Signalman</title>
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
6
|
+
<%= csrf_meta_tags %>
|
7
|
+
<%= csp_meta_tag %>
|
8
|
+
|
9
|
+
<script src="https://cdn.tailwindcss.com?plugins=forms,typography,aspect-ratio"></script>
|
10
|
+
</head>
|
11
|
+
|
12
|
+
<body>
|
13
|
+
<div class="flex gap-4">
|
14
|
+
<%= render "signalman/shared/nav" %>
|
15
|
+
<div class="ml-72 w-full p-4 sm:p-8 prose max-w-none prose-a:no-underline">
|
16
|
+
<%= render "signalman/shared/flash" %>
|
17
|
+
<%= yield %>
|
18
|
+
</div>
|
19
|
+
</div>
|
20
|
+
</body>
|
21
|
+
</html>
|
22
|
+
|
@@ -0,0 +1,36 @@
|
|
1
|
+
<h1>Generate Model </h1>
|
2
|
+
|
3
|
+
<%= form_with url: generators_model_path do |form| %>
|
4
|
+
<div>
|
5
|
+
<%= form.label :model_name, class: "block" %>
|
6
|
+
<%= form.text_field :model_name, placeholder: "User", required: true %>
|
7
|
+
</div>
|
8
|
+
|
9
|
+
<template id="field">
|
10
|
+
<div class="mt-4">
|
11
|
+
<%= form.text_field "fields[][name]", placeholder: "email", required: true %>
|
12
|
+
<%= form.select "fields[][type]", options_for_select(ActiveRecord::Base.connection.class::NATIVE_DATABASE_TYPES.keys.excluding(:primary_key)) %>
|
13
|
+
<%= button_tag "Remove", onclick: "event.preventDefault(); this.parentElement.remove()" %>
|
14
|
+
</div>
|
15
|
+
</template>
|
16
|
+
|
17
|
+
<label>Fields</label>
|
18
|
+
<div id="fields">
|
19
|
+
</div>
|
20
|
+
<%= button_tag "Add field", onclick: "addField(event)" %>
|
21
|
+
|
22
|
+
<div class="mt-4">
|
23
|
+
<%= form.button "Generate" %>
|
24
|
+
</div>
|
25
|
+
<% end %>
|
26
|
+
|
27
|
+
<script>
|
28
|
+
const template = document.querySelector("#field")
|
29
|
+
const fields = document.querySelector("#fields")
|
30
|
+
|
31
|
+
function addField(event) {
|
32
|
+
event.preventDefault()
|
33
|
+
const clone = template.content.cloneNode(true)
|
34
|
+
fields.appendChild(clone)
|
35
|
+
}
|
36
|
+
</script>
|
@@ -0,0 +1,9 @@
|
|
1
|
+
<% if @status.success? %>
|
2
|
+
<p>Visit <%= link_to "/#{params[:model_name].underscore.pluralize}", "/#{params[:model_name].underscore.pluralize}", target: :_blank %></p>
|
3
|
+
<% end %>
|
4
|
+
|
5
|
+
<pre>
|
6
|
+
$ rails generate scaffold <%= params[:model_name] %> <%= @fields.join(" ") %>
|
7
|
+
<%= simple_format @stdout %>
|
8
|
+
<%= simple_format @stderr %>
|
9
|
+
</pre>
|
@@ -0,0 +1,36 @@
|
|
1
|
+
<h1>Generate Scaffold</h1>
|
2
|
+
|
3
|
+
<%= form_with url: generators_scaffold_path do |form| %>
|
4
|
+
<div>
|
5
|
+
<%= form.label :model_name, class: "block" %>
|
6
|
+
<%= form.text_field :model_name, placeholder: "User", required: true %>
|
7
|
+
</div>
|
8
|
+
|
9
|
+
<template id="field">
|
10
|
+
<div class="mt-4">
|
11
|
+
<%= form.text_field "fields[][name]", placeholder: "email", required: true %>
|
12
|
+
<%= form.select "fields[][type]", options_for_select(ActiveRecord::Base.connection.class::NATIVE_DATABASE_TYPES.keys.excluding(:primary_key)) %>
|
13
|
+
<%= button_tag "Remove", onclick: "event.preventDefault(); this.parentElement.remove()" %>
|
14
|
+
</div>
|
15
|
+
</template>
|
16
|
+
|
17
|
+
<label>Fields</label>
|
18
|
+
<div id="fields">
|
19
|
+
</div>
|
20
|
+
<%= button_tag "Add field", onclick: "addField(event)" %>
|
21
|
+
|
22
|
+
<div class="mt-4">
|
23
|
+
<%= form.button "Generate" %>
|
24
|
+
</div>
|
25
|
+
<% end %>
|
26
|
+
|
27
|
+
<script>
|
28
|
+
const template = document.querySelector("#field")
|
29
|
+
const fields = document.querySelector("#fields")
|
30
|
+
|
31
|
+
function addField(event) {
|
32
|
+
event.preventDefault()
|
33
|
+
const clone = template.content.cloneNode(true)
|
34
|
+
fields.appendChild(clone)
|
35
|
+
}
|
36
|
+
</script>
|
@@ -0,0 +1,28 @@
|
|
1
|
+
<h1>Jobs</h1>
|
2
|
+
|
3
|
+
<div class="table w-full">
|
4
|
+
<div class="table-header-group">
|
5
|
+
<div class="table-row font-bold">
|
6
|
+
<div class="table-cell border-b border-neutral-100 p-2">Job</div>
|
7
|
+
<div class="table-cell border-b border-neutral-100 p-2">Id</div>
|
8
|
+
<div class="table-cell border-b border-neutral-100 p-2">Arguments</div>
|
9
|
+
<div class="table-cell border-b border-neutral-100 p-2">Duration</div>
|
10
|
+
<div class="table-cell border-b border-neutral-100 p-2">Happened</div>
|
11
|
+
</div>
|
12
|
+
</div>
|
13
|
+
<div class="table-row-group">
|
14
|
+
<% @events.each do |event| %>
|
15
|
+
<%= link_to signalman_path_for(event), class: "table-row", id: dom_id(event) do %>
|
16
|
+
<%= tag.div event.payload["class"], class: "table-cell border-b border-neutral-100 p-2" %>
|
17
|
+
<%= tag.div event.payload["id"], class: "table-cell border-b border-neutral-100 p-2" %>
|
18
|
+
<%= tag.div event.payload["args"], class: "table-cell border-b border-neutral-100 p-2" %>
|
19
|
+
<%= tag.div class: "table-cell border-b border-neutral-100 p-2" do %>
|
20
|
+
<%= event.duration.round %>ms
|
21
|
+
<% end %>
|
22
|
+
<%= tag.div class: "table-cell border-b border-neutral-100 p-2" do %>
|
23
|
+
<%= time_ago_in_words event.started_at %>
|
24
|
+
<% end %>
|
25
|
+
<% end %>
|
26
|
+
<% end %>
|
27
|
+
</div>
|
28
|
+
</div>
|
@@ -0,0 +1,17 @@
|
|
1
|
+
<h1 class="mb-2"><%= @event.payload["class"] %></h1>
|
2
|
+
<div class="text-xs">Started at <%= @event.started_at %></div>
|
3
|
+
|
4
|
+
<h4>ID</h4>
|
5
|
+
<pre><%= @event.payload["id"] %></pre>
|
6
|
+
|
7
|
+
<h4>Queue Name</h4>
|
8
|
+
<pre><%= @event.payload["queue_name"] %></pre>
|
9
|
+
|
10
|
+
<h4>Enqueued At</h4>
|
11
|
+
<pre><%= @event.payload["enqueued_at"] %></pre>
|
12
|
+
|
13
|
+
<h4>Scheduled At</h4>
|
14
|
+
<pre><%= @event.payload["scheduled_at"] || "nil" %></pre>
|
15
|
+
|
16
|
+
<h4>Arguments</h4>
|
17
|
+
<pre><%= @event.payload["args"] %></pre>
|
@@ -0,0 +1,28 @@
|
|
1
|
+
<h1>Mail</h1>
|
2
|
+
|
3
|
+
<div class="table w-full">
|
4
|
+
<div class="table-header-group">
|
5
|
+
<div class="table-row font-bold">
|
6
|
+
<div class="table-cell border-b border-neutral-100 p-2">Mailer</div>
|
7
|
+
<div class="table-cell border-b border-neutral-100 p-2">Subject</div>
|
8
|
+
<div class="table-cell border-b border-neutral-100 p-2">To</div>
|
9
|
+
<div class="table-cell border-b border-neutral-100 p-2">Duration</div>
|
10
|
+
<div class="table-cell border-b border-neutral-100 p-2">Happened</div>
|
11
|
+
</div>
|
12
|
+
</div>
|
13
|
+
<div class="table-row-group">
|
14
|
+
<% @events.each do |event| %>
|
15
|
+
<%= link_to signalman_path_for(event), class: "table-row", id: dom_id(event) do %>
|
16
|
+
<%= tag.div event.payload["mailer"], class: "table-cell border-b border-neutral-100 p-2" %>
|
17
|
+
<%= tag.div event.payload["subject"], class: "table-cell border-b border-neutral-100 p-2" %>
|
18
|
+
<%= tag.div event.payload["to"], class: "table-cell border-b border-neutral-100 p-2" %>
|
19
|
+
<%= tag.div class: "table-cell border-b border-neutral-100 p-2" do %>
|
20
|
+
<%= event.duration.round %>ms
|
21
|
+
<% end %>
|
22
|
+
<%= tag.div class: "table-cell border-b border-neutral-100 p-2" do %>
|
23
|
+
<%= time_ago_in_words event.started_at %>
|
24
|
+
<% end %>
|
25
|
+
<% end %>
|
26
|
+
<% end %>
|
27
|
+
</div>
|
28
|
+
</div>
|
@@ -0,0 +1,17 @@
|
|
1
|
+
<h1 class="mb-2"><%= @event.payload["mailer"] %></h1>
|
2
|
+
<div class="text-xs">Started at <%= @event.started_at %></div>
|
3
|
+
|
4
|
+
<h4>Message ID</h4>
|
5
|
+
<pre><%= @event.payload["message_id"] %></pre>
|
6
|
+
|
7
|
+
<h4>Subject</h4>
|
8
|
+
<pre><%= @event.payload["subject"] %></pre>
|
9
|
+
|
10
|
+
<h4>To</h4>
|
11
|
+
<pre><%= @event.payload["to"] %></pre>
|
12
|
+
|
13
|
+
<h4>From </h4>
|
14
|
+
<pre><%= @event.payload["from"] %></pre>
|
15
|
+
|
16
|
+
<h4>Mail</h4>
|
17
|
+
<pre><%= @event.payload["mail"] %></pre>
|
@@ -0,0 +1,24 @@
|
|
1
|
+
<h1>Queries</h1>
|
2
|
+
|
3
|
+
<div class="table w-full">
|
4
|
+
<div class="table-header-group">
|
5
|
+
<div class="table-row font-bold">
|
6
|
+
<div class="table-cell border-b border-neutral-100 p-2">SQL</div>
|
7
|
+
<div class="table-cell border-b border-neutral-100 p-2">Duration</div>
|
8
|
+
<div class="table-cell border-b border-neutral-100 p-2">Happened</div>
|
9
|
+
</div>
|
10
|
+
</div>
|
11
|
+
<div class="table-row-group">
|
12
|
+
<% @events.each do |event| %>
|
13
|
+
<%= link_to signalman_path_for(event), class: "table-row", id: dom_id(event) do %>
|
14
|
+
<%= tag.div event.payload["sql"], class: "table-cell border-b border-neutral-100 p-2" %>
|
15
|
+
<%= tag.div class: "table-cell border-b border-neutral-100 p-2" do %>
|
16
|
+
<%= event.duration.round %>ms
|
17
|
+
<% end %>
|
18
|
+
<%= tag.div class: "table-cell border-b border-neutral-100 p-2" do %>
|
19
|
+
<%= time_ago_in_words event.started_at %>
|
20
|
+
<% end %>
|
21
|
+
<% end %>
|
22
|
+
<% end %>
|
23
|
+
</div>
|
24
|
+
</div>
|
@@ -0,0 +1,17 @@
|
|
1
|
+
<h1 class="mb-2"><%= @event.payload["name"] %></h1>
|
2
|
+
<div class="text-xs">Started at <%= @event.started_at %></div>
|
3
|
+
|
4
|
+
<h4>SQL</h4>
|
5
|
+
<pre><%= @event.payload["sql"] %></pre>
|
6
|
+
|
7
|
+
<h4>Binds</h4>
|
8
|
+
<pre><%= @event.payload["binds"] %></pre>
|
9
|
+
|
10
|
+
<h4>Type Casted Binds</h4>
|
11
|
+
<pre><%= @event.payload["type_casted_binds"] %></pre>
|
12
|
+
|
13
|
+
<h4>Statement Name</h4>
|
14
|
+
<pre><%= @event.payload["statement_name"] %></pre>
|
15
|
+
|
16
|
+
<h4>Async</h4>
|
17
|
+
<pre><%= @event.payload["async"] %></pre>
|
@@ -0,0 +1,31 @@
|
|
1
|
+
<h1>Requests</h1>
|
2
|
+
|
3
|
+
<div class="table w-full">
|
4
|
+
<div class="table-header-group">
|
5
|
+
<div class="table-row font-bold">
|
6
|
+
<div class="table-cell border-b border-neutral-100 p-2">Method</div>
|
7
|
+
<div class="table-cell border-b border-neutral-100 p-2">Path</div>
|
8
|
+
<div class="table-cell border-b border-neutral-100 p-2">Status</div>
|
9
|
+
<div class="table-cell border-b border-neutral-100 p-2">Duration</div>
|
10
|
+
<div class="table-cell border-b border-neutral-100 p-2">Happened</div>
|
11
|
+
</div>
|
12
|
+
</div>
|
13
|
+
<div class="table-row-group">
|
14
|
+
<% @events.each do |event| %>
|
15
|
+
<%= link_to signalman_path_for(event), class: "table-row", id: dom_id(event) do %>
|
16
|
+
<%= tag.div event.payload["method"], class: "table-cell border-b border-neutral-100 p-2" %>
|
17
|
+
<%= tag.div event.payload["path"], class: "table-cell border-b border-neutral-100 p-2" %>
|
18
|
+
<%= tag.div class: "table-cell border-b border-neutral-100 p-2" do %>
|
19
|
+
<%= event.payload["status"] %>
|
20
|
+
<%= Rack::Utils::HTTP_STATUS_CODES[event.payload["status"]] %>
|
21
|
+
<% end %>
|
22
|
+
<%= tag.div class: "table-cell border-b border-neutral-100 p-2" do %>
|
23
|
+
<%= badge_for_request_duration(event.duration) %>
|
24
|
+
<% end %>
|
25
|
+
<%= tag.div class: "table-cell border-b border-neutral-100 p-2" do %>
|
26
|
+
<%= time_ago_in_words event.started_at %>
|
27
|
+
<% end %>
|
28
|
+
<% end %>
|
29
|
+
<% end %>
|
30
|
+
</div>
|
31
|
+
</div>
|
@@ -0,0 +1,19 @@
|
|
1
|
+
<h1 class="mb-2"><%= @event.payload["method"] %> <%= @event.payload["path"] %> as <%= @event.payload["format"].upcase %></h1>
|
2
|
+
<div class="text-xs">Started at <%= @event.started_at %></div>
|
3
|
+
|
4
|
+
<h4>Processed by</h4>
|
5
|
+
<pre><%= @event.payload["controller"] %>#<%= @event.payload["action"] %></pre>
|
6
|
+
<p class="text-sm">Completed in <%= @event.duration.round %>ms (Views: <%= @event.payload["view_runtime"].round %>ms | ActiveRecord: <%= @event.payload["db_runtime"].round %>ms)</p>
|
7
|
+
|
8
|
+
<h4>Response</h4>
|
9
|
+
<pre><%= @event.payload["status"] %> <%= Rack::Utils::HTTP_STATUS_CODES[@event.payload["status"]] %></pre>
|
10
|
+
|
11
|
+
<h4>Params</h4>
|
12
|
+
<pre><%= @event.payload["params"] %></pre>
|
13
|
+
|
14
|
+
<h4>Headers</h4>
|
15
|
+
<pre>
|
16
|
+
<% @event.payload["headers"].sort.each do |key, value| %>
|
17
|
+
<%= key %>: <%= value %>
|
18
|
+
<% end %>
|
19
|
+
</pre>
|
@@ -0,0 +1,30 @@
|
|
1
|
+
<nav class="p-4 sm:p-8 fixed inset-y-0 w-72 bg-neutral-900 text-white">
|
2
|
+
<h1 class="flex items-center gap-1 font-bold">
|
3
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-6 h-6">
|
4
|
+
<path fill-rule="evenodd" d="M10.5 3.798v5.02a3 3 0 01-.879 2.121l-2.377 2.377a9.845 9.845 0 015.091 1.013 8.315 8.315 0 005.713.636l.285-.071-3.954-3.955a3 3 0 01-.879-2.121v-5.02a23.614 23.614 0 00-3 0zm4.5.138a.75.75 0 00.093-1.495A24.837 24.837 0 0012 2.25a25.048 25.048 0 00-3.093.191A.75.75 0 009 3.936v4.882a1.5 1.5 0 01-.44 1.06l-6.293 6.294c-1.62 1.621-.903 4.475 1.471 4.88 2.686.46 5.447.698 8.262.698 2.816 0 5.576-.239 8.262-.697 2.373-.406 3.092-3.26 1.47-4.881L15.44 9.879A1.5 1.5 0 0115 8.818V3.936z" clip-rule="evenodd" />
|
5
|
+
</svg>
|
6
|
+
<%= link_to "Signalman", root_path %>
|
7
|
+
</h1>
|
8
|
+
|
9
|
+
<br />
|
10
|
+
<%= link_to "Back to app", main_app.root_path, class: "block text-sm" %>
|
11
|
+
|
12
|
+
<br />
|
13
|
+
<%= link_to "Requests", requests_path, class: "block" %>
|
14
|
+
<%= link_to "Jobs", jobs_path, class: "block" %>
|
15
|
+
<%= link_to "Mail", mails_path, class: "block" %>
|
16
|
+
<%= link_to "Queries", queries_path, class: "block" %>
|
17
|
+
<%= link_to "Views", views_path, class: "block" %>
|
18
|
+
<%#= link_to "Cache", requests_path, class: "block" %>
|
19
|
+
<%#= link_to "Exceptions", requests_path, class: "block" %>
|
20
|
+
|
21
|
+
<br/>
|
22
|
+
<h2>Generators</h2>
|
23
|
+
<%= link_to "Model", generators_model_path, class: "block" %>
|
24
|
+
<%= link_to "Scaffold", generators_scaffold_path, class: "block" %>
|
25
|
+
|
26
|
+
<br/>
|
27
|
+
<%= link_to "Routes", "/rails/info/routes", class: "block" %>
|
28
|
+
<%= link_to "Mailer Previews", "/rails/mailers", class: "block" %>
|
29
|
+
<%= link_to "Inbound Emails", "/rails/conductor/action_mailbox/inbound_emails", class: "block" %>
|
30
|
+
</nav>
|
@@ -0,0 +1,27 @@
|
|
1
|
+
<h1>Views</h1>
|
2
|
+
|
3
|
+
<div class="table w-full">
|
4
|
+
<div class="table-header-group">
|
5
|
+
<div class="table-row font-bold">
|
6
|
+
<div class="table-cell border-b border-neutral-100 p-2">Type</div>
|
7
|
+
<div class="table-cell border-b border-neutral-100 p-2">Identifier</div>
|
8
|
+
<div class="table-cell border-b border-neutral-100 p-2">Duration</div>
|
9
|
+
<div class="table-cell border-b border-neutral-100 p-2">Happened</div>
|
10
|
+
</div>
|
11
|
+
</div>
|
12
|
+
<div class="table-row-group">
|
13
|
+
<% @events.each do |event| %>
|
14
|
+
<%= link_to signalman_path_for(event), class: "table-row", id: dom_id(event) do %>
|
15
|
+
<%= tag.div event.name.split(".").first.humanize, class: "table-cell border-b border-neutral-100 p-2" %>
|
16
|
+
<%= tag.div event.payload["identifier"], class: "table-cell border-b border-neutral-100 p-2" %>
|
17
|
+
<%= tag.div class: "table-cell border-b border-neutral-100 p-2" do %>
|
18
|
+
<%= badge_for_request_duration(event.duration) %>
|
19
|
+
<% end %>
|
20
|
+
<%= tag.div class: "table-cell border-b border-neutral-100 p-2" do %>
|
21
|
+
<%= time_ago_in_words event.started_at %>
|
22
|
+
<% end %>
|
23
|
+
<% end %>
|
24
|
+
<% end %>
|
25
|
+
</div>
|
26
|
+
</div>
|
27
|
+
|
@@ -0,0 +1,20 @@
|
|
1
|
+
<h1 class="mb-2"><%= @event.name.split(".").first.humanize %></h1>
|
2
|
+
<div class="text-xs">Started at <%= @event.started_at %></div>
|
3
|
+
|
4
|
+
<h4>Identifier</h4>
|
5
|
+
<pre><%= @event.payload["identifier"] %></pre>
|
6
|
+
|
7
|
+
<% if @event.payload.has_key?("layout") %>
|
8
|
+
<h4>Layout</h4>
|
9
|
+
<pre><%= @event.payload["layout"] %></pre>
|
10
|
+
<% end %>
|
11
|
+
|
12
|
+
<% if @event.payload.has_key?("count") %>
|
13
|
+
<h4>Count</h4>
|
14
|
+
<pre><%= @event.payload["count"] %></pre>
|
15
|
+
<% end %>
|
16
|
+
|
17
|
+
<% if @event.payload.has_key?("cache_hits") %>
|
18
|
+
<h4>Cache Hits</h4>
|
19
|
+
<pre><%= @event.payload["cache_hits"] %></pre>
|
20
|
+
<% end %>
|
data/config/routes.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
Signalman::Engine.routes.draw do
|
2
|
+
resources :requests
|
3
|
+
resources :jobs
|
4
|
+
resources :cache
|
5
|
+
resources :exceptions
|
6
|
+
resources :mails
|
7
|
+
resources :queries
|
8
|
+
resources :views
|
9
|
+
|
10
|
+
namespace :generators do
|
11
|
+
resource :scaffold
|
12
|
+
resource :model
|
13
|
+
end
|
14
|
+
|
15
|
+
root "requests#index"
|
16
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class CreateSignalmanEvents < ActiveRecord::Migration[7.0]
|
2
|
+
def change
|
3
|
+
create_table :signalman_events do |t|
|
4
|
+
t.string :name
|
5
|
+
t.datetime :started_at
|
6
|
+
t.datetime :finished_at
|
7
|
+
t.float :duration
|
8
|
+
t.json :payload
|
9
|
+
|
10
|
+
t.timestamps
|
11
|
+
end
|
12
|
+
|
13
|
+
add_index :signalman_events, [:name, :duration]
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Signalman
|
2
|
+
class Engine < ::Rails::Engine
|
3
|
+
isolate_namespace Signalman
|
4
|
+
|
5
|
+
initializer "signalman.register_watchers" do
|
6
|
+
Signalman.register_watchers
|
7
|
+
end
|
8
|
+
|
9
|
+
# helpers must be accessible anywhere for Turbo broadcasts
|
10
|
+
initializer 'signalman.helpers' do
|
11
|
+
ActiveSupport.on_load :action_controller do
|
12
|
+
helper Signalman::EventsHelper
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/lib/signalman.rb
ADDED
@@ -0,0 +1,183 @@
|
|
1
|
+
require "signalman/version"
|
2
|
+
require "signalman/engine"
|
3
|
+
|
4
|
+
module Signalman
|
5
|
+
class BaseHandler
|
6
|
+
attr_reader :current_time, :event
|
7
|
+
|
8
|
+
def self.call(event)
|
9
|
+
current_time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond)
|
10
|
+
new(event, current_time).start
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(event, current_time)
|
14
|
+
@event, @current_time = event, current_time
|
15
|
+
end
|
16
|
+
|
17
|
+
def start
|
18
|
+
process unless skip?
|
19
|
+
end
|
20
|
+
|
21
|
+
def process
|
22
|
+
create_event
|
23
|
+
end
|
24
|
+
|
25
|
+
def skip?
|
26
|
+
false
|
27
|
+
end
|
28
|
+
|
29
|
+
def create_event(payload = nil)
|
30
|
+
payload ||= event.payload
|
31
|
+
|
32
|
+
Event.create(
|
33
|
+
name: event.name,
|
34
|
+
started_at: started_at,
|
35
|
+
finished_at: finished_at,
|
36
|
+
duration: event.duration,
|
37
|
+
payload: payload
|
38
|
+
)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Time measure since system boot with
|
42
|
+
# Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond)
|
43
|
+
def started_at
|
44
|
+
Time.current - ((current_time - event.time) / 1_000)
|
45
|
+
end
|
46
|
+
|
47
|
+
def finished_at
|
48
|
+
Time.current - ((current_time - event.end) / 1_000)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class ActionHandler < BaseHandler
|
53
|
+
def process
|
54
|
+
headers = {}
|
55
|
+
event.payload.fetch(:headers, {}).each do |name, value|
|
56
|
+
headers[name] = value if name.start_with?("HTTP")
|
57
|
+
headers[name] = value if ActionDispatch::Http::Headers::CGI_VARIABLES.include?(name)
|
58
|
+
|
59
|
+
[
|
60
|
+
"action_dispatch.request_id"
|
61
|
+
].each do |header_name|
|
62
|
+
headers[name] = value if name == header_name
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
create_event event.payload.slice(
|
67
|
+
:method,
|
68
|
+
:path,
|
69
|
+
:controller,
|
70
|
+
:action,
|
71
|
+
:params,
|
72
|
+
:format,
|
73
|
+
:status,
|
74
|
+
:db_runtime,
|
75
|
+
:view_runtime
|
76
|
+
).merge(headers: headers)
|
77
|
+
end
|
78
|
+
|
79
|
+
def skip?
|
80
|
+
event.payload[:controller].start_with?("Signalman::")
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
class ViewHandler < BaseHandler
|
85
|
+
def skip?
|
86
|
+
event.payload[:identifier].include?("app/views/signalman/")
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
class QueryHandler < BaseHandler
|
91
|
+
def skip?
|
92
|
+
["SCHEMA", "TRANSACTION"].include?(event.payload[:name]) ||
|
93
|
+
event.payload[:name]&.include?("Signalman::")
|
94
|
+
end
|
95
|
+
|
96
|
+
def process
|
97
|
+
create_event event.payload.except(:connection)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
class MailHandler < BaseHandler
|
102
|
+
end
|
103
|
+
|
104
|
+
class JobHandler < BaseHandler
|
105
|
+
def process
|
106
|
+
job = event.payload[:job]
|
107
|
+
create_event(
|
108
|
+
class: job.class.name,
|
109
|
+
id: job.job_id,
|
110
|
+
enqueued_at: job.enqueued_at,
|
111
|
+
scheduled_at: scheduled_at(event),
|
112
|
+
queue_name: queue_name(event),
|
113
|
+
args: args_info(job)
|
114
|
+
)
|
115
|
+
end
|
116
|
+
|
117
|
+
# From ActiveJob::LogSubscriber
|
118
|
+
def queue_name(event)
|
119
|
+
# Rails 7.1 -> ActiveJob.adapter_name(event.payload[:adapter]) + "(#{event.payload[:job].queue_name})"
|
120
|
+
event.payload[:adapter].class.name.demodulize.remove("Adapter") + "(#{event.payload[:job].queue_name})"
|
121
|
+
end
|
122
|
+
|
123
|
+
def args_info(job)
|
124
|
+
if job.class.log_arguments? && job.arguments.any?
|
125
|
+
" with arguments: " +
|
126
|
+
job.arguments.map { |arg| format(arg).inspect }.join(", ")
|
127
|
+
else
|
128
|
+
""
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def format(arg)
|
133
|
+
case arg
|
134
|
+
when Hash
|
135
|
+
arg.transform_values { |value| format(value) }
|
136
|
+
when Array
|
137
|
+
arg.map { |value| format(value) }
|
138
|
+
when GlobalID::Identification
|
139
|
+
arg.to_global_id rescue arg
|
140
|
+
else
|
141
|
+
arg
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def scheduled_at(event)
|
146
|
+
return unless event.payload[:job].scheduled_at
|
147
|
+
Time.at(event.payload[:job].scheduled_at).utc
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
cattr_accessor :events, default: {
|
152
|
+
"process_action.action_controller" => {
|
153
|
+
handler: ActionHandler,
|
154
|
+
path: ->(event) { request_path(event) }
|
155
|
+
},
|
156
|
+
/^\w+\.action_view/ => {
|
157
|
+
handler: ViewHandler,
|
158
|
+
path: ->(event) { view_path(event) }
|
159
|
+
},
|
160
|
+
"sql.active_record" => {
|
161
|
+
handler: QueryHandler,
|
162
|
+
path: ->(event) { query_path(event) }
|
163
|
+
},
|
164
|
+
"deliver.action_mailer" => {
|
165
|
+
handler: MailHandler,
|
166
|
+
path: ->(event) { mail_path(event) }
|
167
|
+
},
|
168
|
+
/^\w+\.active_job/ => {
|
169
|
+
handler: JobHandler,
|
170
|
+
path: ->(event) { job_path(event) }
|
171
|
+
}
|
172
|
+
}
|
173
|
+
|
174
|
+
def self.register_watchers
|
175
|
+
events.each do |event_name, options|
|
176
|
+
options[:subscriber] = add_watcher event_name, options[:handler]
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def self.add_watcher(event_name, handler)
|
181
|
+
ActiveSupport::Notifications.subscribe event_name, handler
|
182
|
+
end
|
183
|
+
end
|
metadata
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: signalman
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Chris Oliver
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2023-08-01 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: 7.0.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 7.0.0
|
27
|
+
description: Development tools for Ruby on Rails
|
28
|
+
email:
|
29
|
+
- excid3@gmail.com
|
30
|
+
executables: []
|
31
|
+
extensions: []
|
32
|
+
extra_rdoc_files: []
|
33
|
+
files:
|
34
|
+
- MIT-LICENSE
|
35
|
+
- README.md
|
36
|
+
- Rakefile
|
37
|
+
- app/assets/config/signalman_manifest.js
|
38
|
+
- app/controllers/signalman/application_controller.rb
|
39
|
+
- app/controllers/signalman/generators/models_controller.rb
|
40
|
+
- app/controllers/signalman/generators/scaffolds_controller.rb
|
41
|
+
- app/controllers/signalman/jobs_controller.rb
|
42
|
+
- app/controllers/signalman/mails_controller.rb
|
43
|
+
- app/controllers/signalman/queries_controller.rb
|
44
|
+
- app/controllers/signalman/requests_controller.rb
|
45
|
+
- app/controllers/signalman/views_controller.rb
|
46
|
+
- app/helpers/signalman/events_helper.rb
|
47
|
+
- app/models/signalman.rb
|
48
|
+
- app/models/signalman/event.rb
|
49
|
+
- app/views/layouts/signalman/application.html.erb
|
50
|
+
- app/views/signalman/generators/models/create.html.erb
|
51
|
+
- app/views/signalman/generators/models/show.html.erb
|
52
|
+
- app/views/signalman/generators/scaffolds/create.html.erb
|
53
|
+
- app/views/signalman/generators/scaffolds/show.html.erb
|
54
|
+
- app/views/signalman/jobs/index.html.erb
|
55
|
+
- app/views/signalman/jobs/show.html.erb
|
56
|
+
- app/views/signalman/mails/index.html.erb
|
57
|
+
- app/views/signalman/mails/show.html.erb
|
58
|
+
- app/views/signalman/queries/index.html.erb
|
59
|
+
- app/views/signalman/queries/show.html.erb
|
60
|
+
- app/views/signalman/requests/index.html.erb
|
61
|
+
- app/views/signalman/requests/show.html.erb
|
62
|
+
- app/views/signalman/shared/_flash.html.erb
|
63
|
+
- app/views/signalman/shared/_nav.html.erb
|
64
|
+
- app/views/signalman/views/index.html.erb
|
65
|
+
- app/views/signalman/views/show.html.erb
|
66
|
+
- config/routes.rb
|
67
|
+
- db/migrate/20230729122215_create_signalman_events.rb
|
68
|
+
- lib/signalman.rb
|
69
|
+
- lib/signalman/engine.rb
|
70
|
+
- lib/signalman/version.rb
|
71
|
+
- lib/tasks/signalman_tasks.rake
|
72
|
+
homepage: https://github.com/excid3/signalman
|
73
|
+
licenses:
|
74
|
+
- MIT
|
75
|
+
metadata:
|
76
|
+
homepage_uri: https://github.com/excid3/signalman
|
77
|
+
source_code_uri: https://github.com/excid3/signalman
|
78
|
+
changelog_uri: https://github.com/excid3/signalman/blob/main/CHANGELOG.md
|
79
|
+
post_install_message:
|
80
|
+
rdoc_options: []
|
81
|
+
require_paths:
|
82
|
+
- lib
|
83
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
84
|
+
requirements:
|
85
|
+
- - ">="
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '0'
|
88
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
89
|
+
requirements:
|
90
|
+
- - ">="
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: '0'
|
93
|
+
requirements: []
|
94
|
+
rubygems_version: 3.4.17
|
95
|
+
signing_key:
|
96
|
+
specification_version: 4
|
97
|
+
summary: Development tools for Ruby on Rails
|
98
|
+
test_files: []
|