maxpage 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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 7ee92dec537b5ae2ee89664e27910d5733907481a42a96cd2bdf5e6173ac4ee1
4
+ data.tar.gz: b90b61f8366d572789c5c3cc875308e8e66145cd412bc2e47a4c44184f208fc2
5
+ SHA512:
6
+ metadata.gz: 5f56e14e148f434df90bbb27e5cbb68cef5dbcbe9a5188ff0bd7e7b4b033bc2ef7181bf02f1b69ed67a2e3ed21d47a8775bf239514facd6f7fe7373adad3ce29
7
+ data.tar.gz: 527356a2d0b834136fea9b60d0d03476428792debd02c9e5231ac0eb01b974ae6cd40368529242d38fc7f7a6c034f646075ea744054c0a8d2c7cdda499b551a7
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2022 Lucas Castro
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,254 @@
1
+ # 🐶 Max, your app's best friend!
2
+
3
+ __Max Page__ sniffs metrics in your app to check if is everything right.
4
+
5
+ ![ "MaxPage Example (code and generated page)"](./docs/print.png?raw=true)
6
+
7
+ In other words, it's a [Ruby on Rails](https://github.com/rails/rails) engine to create "status pages" for your project.
8
+
9
+ __Max__ is similiar to what [GitHub](https://www.githubstatus.com/), [Heroku](https://status.heroku.com/), [Atlassian](https://status.atlassian.com/) and others do to keep their users noticed about the operational status. However, __Max__ it's much more about the apps's data than its infrastructure. If you would like something to keep a history of incidents, subscriptions, and integrations with external, try specific tools like [StatusPage](https://www.atlassian.com/software/statuspage).
10
+
11
+ __Max get together your app's metrics, in a single page.__
12
+
13
+ For metrics, I mean some numbers and verifications that help us to understand if the app is running fine as a whole. They are not necessarily infrastructure checks.
14
+
15
+ Examples of metrics:
16
+
17
+ 1. Check health of internal and external services;
18
+ 2. Number of users registered last 24h;
19
+ 3. Number of clients that are using the key features;
20
+ 4. Size of internal queue processing services;
21
+ 5. Nodes we are consuming in the Kubernetes cluster.
22
+
23
+ ## Installation
24
+
25
+ Install the `maxpage` gem in your Rails app by using `bundle`:
26
+
27
+ ```bash
28
+ $ bundle add maxpage
29
+ ```
30
+
31
+ After that, add the configuration file `config/initializers/maxpage.rb` to start creating your metrics, like the following code:
32
+
33
+ ```ruby
34
+ MaxPage.setup do
35
+ metric 'Users registered last 24h' do
36
+ User.where("created_at > ?", 24.hours.ago).count
37
+ end
38
+ end
39
+ ```
40
+
41
+ Mount the route for the status page in `config/routes.rb`:
42
+
43
+ ```ruby
44
+ mount MaxPage::Engine => "/status"
45
+ ```
46
+
47
+ In that case, we are going to accces the page using the `/status` path: [http://localhost:3000/status](http://localhost:3000/status).
48
+
49
+ You can use __Max__ exclusively to monitor other apps and services. In that case consider to use only `/` instead `/status` to mount the `MaxPage` engine, as your Rails application would not need other routes.
50
+
51
+ ## Usage
52
+
53
+ To set up `MaxPage` all we need is to create an initializer file to define metrics.
54
+
55
+ Before do it, let's establish some concepts:
56
+ * A `metric` is anything you want to monitor;
57
+ * A `metric` has a `name` and a `block` of code;
58
+ * The `block`'s result will be presented in the page;
59
+ * The `block`'s result can be verified to produce an __warning__ or __success__ message;
60
+
61
+ Let's to an example!
62
+
63
+ ```ruby
64
+ MaxPage.setup do
65
+ metric 'Users registered last 24h' do
66
+ User.where("created_at > ?", 24.hours.ago).count
67
+ end
68
+ end
69
+ ```
70
+
71
+ In the above code we have a metric named "User registered last 24h", with a block code that count in the database the users registed last 24h, using `ActiveRecord`.
72
+
73
+ __⚠️ Important:__ As our configuration is under `config/initializers` folder we __MUST__ restart the Rails server to see the changes. Soon we're going to move the setup code to the `app` directory and eliminate the need of restarting. Work in progress.
74
+
75
+ ### The option `verify`
76
+
77
+ Let's say that usually I have 30 new users per day in my app.
78
+
79
+ I would like to know when this number is less than 20, what means something wrong is happening.
80
+
81
+ So, we are going to add the `verify` option to check this metric status:
82
+
83
+ ```ruby
84
+ MaxPage.setup do
85
+ metric 'Users registered >= 20 ', verify: { min: 20 } do
86
+ User.where("created_at > ?", 24.hours.ago).count
87
+ end
88
+ end
89
+ ```
90
+
91
+ Alternatively, we could use `verify: true`:
92
+
93
+ ```ruby
94
+ MaxPage.setup do
95
+ metric 'Users registered last 24h is more than 20', verify: true do
96
+ User.where("created_at > ?", 24.hours.ago).count > 20
97
+ end
98
+ end
99
+ ```
100
+
101
+ Observe that when we use `verify: true`, it's expected the metric returns `true`. The page shows
102
+ a check icon (✅) if this condition is satisfied, otherwise, it shows a warning icon (⚠️).
103
+
104
+ At mostly, when I am checking numbers, I prefer to use verify with `min` or `max`.
105
+
106
+ ### Warning and success messages
107
+
108
+ On top of the page, Max print a message accordling the verifications.
109
+
110
+ If all the metrics have their verify satisfied, the `success message` is printed.
111
+ Otherwise, we see the `warning message`.
112
+
113
+ Remember that the `verify` is not a required option, therefore if it is not defined we consider the metric is always __Okay__ to the overall verification.
114
+
115
+ We can change these messages. In the following code I translated them to Portuguese:
116
+
117
+ ```ruby
118
+ StatusPage.setup do
119
+ success_message 'Tudo certo!'
120
+ warning_message 'Ops, tem algo de errado.'
121
+
122
+ metric 'Usuários cadastrados nas últimas 24 horas', verify: { min: 20 } do
123
+ User.where("created_at > ?", 24.hours.ago).count
124
+ end
125
+ end
126
+ ```
127
+
128
+ ### Groups of metrics
129
+
130
+ It's possible to create group of metrics just to organize them.
131
+
132
+ We can do it as the following code:
133
+
134
+ ```ruby
135
+ MaxPage.setup do
136
+ group 'Application data' do
137
+ metric 'User registered last 24h' do
138
+ # ...
139
+ end
140
+
141
+ # ...
142
+ end
143
+
144
+ group 'Internal services status' do
145
+ metric 'ElasticSearch is up' do
146
+ # ...
147
+ end
148
+
149
+ metric 'Redis is up' do
150
+ # ...
151
+ end
152
+ end
153
+ end
154
+ ```
155
+
156
+ ### Authentications and Authorizations
157
+
158
+ Sometimes our page presents sensible data that could't be published to everyone.
159
+
160
+ Thinking of this, we can use the `before_action` option to define authentication and authorization rules.
161
+
162
+ Example:
163
+
164
+ ```ruby
165
+ MaxPage.setup do
166
+ before_action do
167
+ authenticate_user!
168
+ end
169
+
170
+ # ...
171
+ end
172
+ ```
173
+
174
+ The `before_action` block will be evaluated in `before_action` callback, using the controller scope.
175
+ This is why you can use methods like `authenticate_<resource_name>!` from [Devise](https://github.com/heartcombo/devise) and `authorize` from [Pundit](https://github.com/varvet/pundit).
176
+
177
+ ## Examples
178
+
179
+ ### Health status
180
+
181
+ Here we are checking the standard library `Net::HTTP` the request a URI:
182
+
183
+ ```ruby
184
+ metric 'Health check using Net::HTTP', verify: true do
185
+ # Rescue errors returning false
186
+ result = Net::HTTP.get(URI('https://example.com/health/check')) rescue false
187
+
188
+ # Double bang to return true because result is String if the request succeeded.
189
+ !!result
190
+ end
191
+ ```
192
+
193
+ Now, some examples using [HTTParty](https://github.com/jnunemaker/httparty) to check the HTTP status.
194
+
195
+ ```ruby
196
+ require 'httparty'
197
+
198
+ metric 'Health check', verify: true do
199
+ HTTParty.get('https://example.com').success?
200
+ end
201
+
202
+ # Will warn once https://example.com/health/check will return the 404 status.
203
+ metric 'Health check status code', verify: 200 do
204
+ HTTParty.get('https://example.com/health/check').code
205
+ end
206
+ ```
207
+
208
+ ### Database records
209
+
210
+ ```ruby
211
+ metric 'PostgreSQL records count', description: "Heroku's Hobby Dev plan limits to 10,000", verify: { max: 10_000 } do
212
+ ActiveRecord::Base.connection.execute(%{
213
+ select sum(c.reltuples) as rows
214
+ from pg_class c
215
+ join pg_namespace n on n.oid = c.relnamespace
216
+ where c.relkind = 'r'
217
+ and n.nspname not in ('information_schema','pg_catalog');
218
+ }).first['rows']
219
+ end
220
+ ```
221
+
222
+ ### Delayed::Job
223
+
224
+ ```ruby
225
+ group "Delayed Job" do
226
+ metric 'Failures', verify: { max: 0 } do
227
+ Delayed::Job.where.not(failed_at: nil).count
228
+ end
229
+
230
+ metric 'Size of the queue "mailer"' do
231
+ Delayed::Job.where(queue: 'mailer').count
232
+ end
233
+ end
234
+ ```
235
+
236
+ ### Bugsnag errors
237
+
238
+ ```ruby
239
+ require 'httparty'
240
+
241
+ metric 'Open errors on Bugsnag', verify: { max: 0 } do
242
+ project_id = '<PROJECT-ID>'
243
+ auth_token = '<AUTH-TOKEN>'
244
+ response = HTTParty.get "https://api.bugsnag.com/projects/#{project_id}?auth_token=#{auth_token}"
245
+ response['open_error_count']
246
+ end
247
+ ```
248
+
249
+ ## Contributing
250
+
251
+ Contributions are welcome! Feel free to open an issue and pull request on [GitHub](https://github.com/lucasmncastro/maxpage).
252
+
253
+ ## License
254
+ 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,8 @@
1
+ require "bundler/setup"
2
+
3
+ APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
4
+ load "rails/tasks/engine.rake"
5
+
6
+ load "rails/tasks/statistics.rake"
7
+
8
+ require "bundler/gem_tasks"
@@ -0,0 +1 @@
1
+ //= link_directory ../stylesheets/maxpage .css
@@ -0,0 +1,4 @@
1
+ module MaxPage
2
+ class ApplicationController < ActionController::Base
3
+ end
4
+ end
@@ -0,0 +1,30 @@
1
+ module MaxPage
2
+ class MetricsController < ApplicationController
3
+ layout 'max_page/application'
4
+
5
+ before_action :before_index
6
+
7
+ def index
8
+ @title = MaxPage.config.title
9
+ @metrics = MaxPage.config.metrics
10
+ @metrics.each &:run
11
+ @alright = @metrics.map(&:ok?).all? true
12
+ if @alright
13
+ @message = MaxPage.config.success_message
14
+ else
15
+ @message = MaxPage.config.warning_message
16
+ end
17
+ @metrics_without_group = @metrics.reject(&:group)
18
+ @groups = MaxPage.config.groups
19
+ end
20
+
21
+ protected
22
+
23
+ def before_index
24
+ block = MaxPage.config.before_action
25
+ if block
26
+ instance_eval(&block)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,4 @@
1
+ module MaxPage
2
+ module ApplicationHelper
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module MaxPage
2
+ module MetricsHelper
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module MaxPage
2
+ class ApplicationJob < ActiveJob::Base
3
+ end
4
+ end
@@ -0,0 +1,6 @@
1
+ module MaxPage
2
+ class ApplicationMailer < ActionMailer::Base
3
+ default from: "from@example.com"
4
+ layout "mailer"
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ module MaxPage
2
+ class ApplicationRecord < ActiveRecord::Base
3
+ self.abstract_class = true
4
+ end
5
+ end
@@ -0,0 +1,14 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title><%= @title %></title>
5
+
6
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.8.1/font/bootstrap-icons.css">
7
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
8
+ </head>
9
+ <body>
10
+
11
+ <%= yield %>
12
+
13
+ </body>
14
+ </html>
@@ -0,0 +1,23 @@
1
+ <li class="list-group-item">
2
+ <div class="d-flex justify-content-between align-items-center">
3
+ <span class="w-100"><%= metric.name %></span>
4
+
5
+ <% if metric.verify == true %>
6
+ <% if metric.verify? %>
7
+ <% if metric.ok? %>
8
+ <i class="bi bi-check-circle-fill text-success"></i>
9
+ <% else %>
10
+ <i class="bi bi-exclamation-triangle-fill text-warning"></i>
11
+ <% end %>
12
+ <% end %>
13
+ <% else %>
14
+ <span class="badge text-black <%= (metric.ok? ? 'text-white bg-success' : 'text-white bg-warning') if metric.verify? %>">
15
+ <%= metric.value %>
16
+ </span>
17
+ <% end %>
18
+ </div>
19
+
20
+ <% if metric.description %>
21
+ <small class="text-muted"><%= metric.description %></small>
22
+ <% end %>
23
+ </li>
@@ -0,0 +1,48 @@
1
+ <% if @metrics.blank? %>
2
+ <div class="bg-dark shadow-lg">
3
+ <div class="container pt-4 pb-3 text-white">
4
+ <h1>
5
+ Almost there!
6
+ </h1>
7
+ </div>
8
+ </div>
9
+ <div class="container mt-4">
10
+ You need to set up the metrics. Go to the <a href="https://github.com/lucasmncastro/maxpage" target="blank">README</a> to more informations.
11
+ </div>
12
+ <% else %>
13
+ <div class="<%= @alright ? 'bg-success' : 'bg-warning' %> shadow-lg">
14
+ <div class="container pt-4 pb-3 text-white">
15
+ <h1>
16
+ <%= @message %>
17
+ </h1>
18
+ </div>
19
+ </div>
20
+
21
+ <div class="container mt-4">
22
+ <% if @metrics_without_group.any? %>
23
+ <div class="card card-default mb-3">
24
+ <ul class="list-group list-group-flush">
25
+ <% @metrics_without_group.each do |metric| %>
26
+ <%= render 'metric', metric: metric %>
27
+ <% end %>
28
+ </ul>
29
+ </div>
30
+ <% end %>
31
+ <% @groups.each do |group| %>
32
+ <div class="card card-default mb-3">
33
+ <div class="card-header">
34
+ <%= group.name %>
35
+ </div>
36
+ <ul class="list-group list-group-flush">
37
+ <% group.metrics.each do |metric| %>
38
+ <%= render 'metric', metric: metric %>
39
+ <% end %>
40
+ </ul>
41
+ </div>
42
+ <% end %>
43
+ </div>
44
+ <% end %>
45
+
46
+ <style>
47
+ body { background: #f6f8fa; }
48
+ </style>
data/config/routes.rb ADDED
@@ -0,0 +1,4 @@
1
+ MaxPage::Engine.routes.draw do
2
+ get 'metrics/index', as: :metrics_index
3
+ root 'metrics#index'
4
+ end
@@ -0,0 +1,68 @@
1
+ module MaxPage
2
+ class Configuration
3
+ attr_reader :metrics, :groups
4
+
5
+ def initialize
6
+ @groups = []
7
+ @metrics = []
8
+ @success_message = "It's all right!"
9
+ @warning_message = "Something is wrong!"
10
+ end
11
+
12
+ # Same method to set and get the title.
13
+ def title(title=nil)
14
+ return @title if title.nil?
15
+
16
+ @title = title
17
+ end
18
+
19
+ # Same method to set and get the before_action block.
20
+ def before_action(&block)
21
+ return @before_action if block.nil?
22
+
23
+ @before_action ||= block
24
+ end
25
+
26
+ # Same method to set and get the title.
27
+ def success_message(message=nil)
28
+ return @success_message if message.nil?
29
+
30
+ @success_message = message
31
+ end
32
+
33
+ # Same method to set and get the title.
34
+ def warning_message(message=nil)
35
+ return @warning_message if message.nil?
36
+
37
+ @warning_message = message
38
+ end
39
+
40
+ # Add a metric.
41
+ def metric(name, description: nil, verify: nil, &block)
42
+ metric = Metric.new
43
+ metric.name = name
44
+ metric.description = description
45
+ metric.verify = verify
46
+ metric.block = block
47
+
48
+ if @current_group
49
+ metric.group = @current_group
50
+ @current_group.metrics << metric
51
+ end
52
+
53
+ @metrics << metric
54
+ end
55
+
56
+ def group(name=nil, &block)
57
+ group = Group.new
58
+ group.name = name
59
+ group.metrics = []
60
+
61
+ @current_group = group
62
+ instance_eval(&block)
63
+ @current_group = nil
64
+
65
+ @groups << group
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,5 @@
1
+ module MaxPage
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace MaxPage
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module MaxPage
2
+ class Group
3
+ attr_accessor :name, :metrics
4
+ end
5
+ end
@@ -0,0 +1,43 @@
1
+ module MaxPage
2
+ class Metric
3
+ attr_accessor :name, :description, :verify, :group, :block
4
+ attr_reader :value
5
+
6
+ def run
7
+ @value = block.call
8
+ end
9
+
10
+ def verify?
11
+ !!verify
12
+ end
13
+
14
+ def verify=(rules)
15
+ if rules.is_a? Hash
16
+ invalid_options = rules.keys - [:min, :max]
17
+ raise "Invalid rule: #{invalid_options.join(', ')}" if invalid_options.any?
18
+ end
19
+
20
+ @verify = rules
21
+ end
22
+
23
+ def ok?
24
+ return true if not verify?
25
+
26
+ run if not value
27
+
28
+ if verify.is_a? Hash
29
+ validations = verify.map do |rule_name, rule_value|
30
+ case rule_name
31
+ when :min then value.to_i >= rule_value
32
+ when :max then value.to_i <= rule_value
33
+ else
34
+ raise "Invalid rule: #{rule_name}"
35
+ end
36
+ end
37
+ validations.all? true
38
+ else
39
+ value == verify
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,3 @@
1
+ module MaxPage
2
+ VERSION = "0.1.0"
3
+ end
data/lib/maxpage.rb ADDED
@@ -0,0 +1,18 @@
1
+ require "max_page/version"
2
+ require "max_page/engine"
3
+ require "max_page/configuration"
4
+ require "max_page/metric"
5
+ require "max_page/group"
6
+
7
+ module MaxPage
8
+ class << self
9
+ def setup(&block)
10
+ @config = Configuration.new
11
+ config.instance_eval &block
12
+ end
13
+
14
+ def config
15
+ @config ||= Configuration.new
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :maxpage do
3
+ # # Task goes here
4
+ # end
metadata ADDED
@@ -0,0 +1,82 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: maxpage
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Lucas Castro
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2022-04-21 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: '5.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '5.0'
27
+ description: Max is a Ruby on Rails engine that provides a DLS to sniff and tell you
28
+ if everything is running well.
29
+ email:
30
+ - castro.lucas@gmail.com
31
+ executables: []
32
+ extensions: []
33
+ extra_rdoc_files: []
34
+ files:
35
+ - MIT-LICENSE
36
+ - README.md
37
+ - Rakefile
38
+ - app/assets/config/maxpage_manifest.js
39
+ - app/controllers/max_page/application_controller.rb
40
+ - app/controllers/max_page/metrics_controller.rb
41
+ - app/helpers/max_page/application_helper.rb
42
+ - app/helpers/max_page/metrics_helper.rb
43
+ - app/jobs/max_page/application_job.rb
44
+ - app/mailers/max_page/application_mailer.rb
45
+ - app/models/max_page/application_record.rb
46
+ - app/views/layouts/max_page/application.html.erb
47
+ - app/views/max_page/metrics/_metric.html.erb
48
+ - app/views/max_page/metrics/index.html.erb
49
+ - config/routes.rb
50
+ - lib/max_page/configuration.rb
51
+ - lib/max_page/engine.rb
52
+ - lib/max_page/group.rb
53
+ - lib/max_page/metric.rb
54
+ - lib/max_page/version.rb
55
+ - lib/maxpage.rb
56
+ - lib/tasks/maxpage_tasks.rake
57
+ homepage: https://github.com/lucasmncastro/maxpage
58
+ licenses:
59
+ - MIT
60
+ metadata:
61
+ homepage_uri: https://github.com/lucasmncastro/maxpage
62
+ source_code_uri: https://github.com/lucasmncastro/maxpage
63
+ post_install_message:
64
+ rdoc_options: []
65
+ require_paths:
66
+ - lib
67
+ required_ruby_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: '0'
72
+ required_rubygems_version: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ requirements: []
78
+ rubygems_version: 3.2.32
79
+ signing_key:
80
+ specification_version: 4
81
+ summary: Simplest way to create a usage and status page for your Rails app.
82
+ test_files: []