upright 0.1.2 → 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.
- checksums.yaml +4 -4
- data/LICENSE.md +2 -3
- data/README.md +166 -60
- data/app/assets/stylesheets/upright/dashboard.css +65 -193
- data/app/assets/stylesheets/upright/tables.css +60 -0
- data/app/assets/stylesheets/upright/uptime-bars.css +13 -50
- data/app/controllers/upright/alertmanager_proxy_controller.rb +1 -1
- data/app/controllers/upright/dashboards/probe_statuses_controller.rb +7 -0
- data/app/controllers/upright/probe_results_controller.rb +1 -0
- data/app/controllers/upright/sessions_controller.rb +1 -0
- data/app/helpers/upright/application_helper.rb +10 -0
- data/app/helpers/upright/dashboards_helper.rb +12 -0
- data/app/helpers/upright/probe_results_helper.rb +3 -10
- data/app/javascript/upright/controllers/auto_refresh_controller.js +16 -0
- data/app/models/concerns/upright/playwright/form_authentication.rb +0 -3
- data/app/models/concerns/upright/playwright/lifecycle.rb +3 -2
- data/app/models/concerns/upright/probe_result/stale_cleanup.rb +23 -0
- data/app/models/concerns/upright/probeable.rb +7 -1
- data/app/models/upright/http/request.rb +1 -1
- data/app/models/upright/playwright/storage_state.rb +12 -3
- data/app/models/upright/probe_result.rb +6 -1
- data/app/models/upright/probes/http_probe.rb +6 -2
- data/app/models/upright/probes/status/probe.rb +24 -0
- data/app/models/upright/probes/status/site_status.rb +71 -0
- data/app/models/upright/probes/status.rb +54 -0
- data/app/models/upright/probes/uptime.rb +4 -4
- data/app/models/upright/traceroute/ip_metadata_lookup.rb +1 -2
- data/app/views/layouts/upright/_header.html.erb +2 -1
- data/app/views/upright/dashboards/_uptime_bars.html.erb +2 -2
- data/app/views/upright/dashboards/_uptime_probe_row.html.erb +7 -5
- data/app/views/upright/dashboards/probe_statuses/_matrix.html.erb +48 -0
- data/app/views/upright/dashboards/probe_statuses/show.html.erb +17 -0
- data/app/views/upright/dashboards/uptimes/show.html.erb +1 -1
- data/app/views/upright/probe_results/index.html.erb +7 -4
- data/app/views/upright/sites/index.html.erb +1 -1
- data/config/ci.rb +2 -0
- data/config/credentials/development.key +1 -0
- data/config/credentials/test.key +1 -0
- data/config/routes.rb +1 -0
- data/lib/generators/upright/install/install_generator.rb +52 -2
- data/lib/generators/upright/install/templates/http_probes.yml +1 -0
- data/lib/generators/upright/install/templates/recurring.yml +25 -0
- data/lib/generators/upright/install/templates/smtp_probes.yml +3 -0
- data/lib/generators/upright/install/templates/traceroute_probes.yml +12 -0
- data/lib/generators/upright/install/templates/upright.rb +4 -1
- data/lib/generators/upright/install/templates/upright.rules.yml +5 -5
- data/lib/upright/configuration.rb +14 -0
- data/lib/upright/engine.rb +7 -0
- data/lib/upright/geohash.rb +46 -0
- data/lib/upright/metrics.rb +2 -2
- data/lib/upright/probe_type_registry.rb +33 -0
- data/lib/upright/site.rb +1 -3
- data/lib/upright/version.rb +1 -1
- data/lib/upright.rb +6 -1
- metadata +17 -17
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
<div class="uptime-bars__row">
|
|
2
|
-
<%= link_to upright.site_root_url(subdomain:
|
|
1
|
+
<div class="data-table__row uptime-bars__row">
|
|
2
|
+
<%= link_to upright.site_root_url(subdomain: current_or_default_site.code, probe_type: probe.type, probe_name: probe.name), class: "data-table__probe" do %>
|
|
3
3
|
<%= probe_type_icon(probe.type) %>
|
|
4
|
-
<span class="
|
|
4
|
+
<span class="data-table__probe-name"><%= probe.name %></span>
|
|
5
5
|
<% end %>
|
|
6
6
|
<div class="uptime-bars__days">
|
|
7
7
|
<% dates.each do |date| %>
|
|
@@ -11,8 +11,10 @@
|
|
|
11
11
|
<% else %>
|
|
12
12
|
<% uptime_percent = uptime * 100 %>
|
|
13
13
|
<% downtime_minutes = ((1 - uptime) * 24 * 60).round %>
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
<%= link_to "", upright.site_root_url(subdomain: current_or_default_site.code, probe_type: probe.type, probe_name: probe.name, date: date.iso8601),
|
|
15
|
+
class: "uptime-bar uptime-bar--#{uptime_label(uptime_percent)}",
|
|
16
|
+
title: uptime_bar_tooltip(date, uptime_percent, downtime_minutes),
|
|
17
|
+
aria: { label: uptime_bar_tooltip(date, uptime_percent, downtime_minutes) } %>
|
|
16
18
|
<% end %>
|
|
17
19
|
<% end %>
|
|
18
20
|
</div>
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
<% if probes.empty? %>
|
|
2
|
+
<div class="no-data-message">
|
|
3
|
+
No probe data available for the selected type.
|
|
4
|
+
</div>
|
|
5
|
+
<% else %>
|
|
6
|
+
<div class="scrollable-x">
|
|
7
|
+
<div class="data-table" style="--cols: <%= sites.size %>">
|
|
8
|
+
<div class="data-table__header probe-status__header">
|
|
9
|
+
<div class="sticky-left">Probe</div>
|
|
10
|
+
<% sites.each do |site| %>
|
|
11
|
+
<div class="text-center"><%= site_name(site) %></div>
|
|
12
|
+
<% end %>
|
|
13
|
+
</div>
|
|
14
|
+
<% probes.each do |probe| %>
|
|
15
|
+
<div class="data-table__row probe-status__row <%= "probe-status-row--down" if probe.any_down? %>">
|
|
16
|
+
<div class="sticky-left">
|
|
17
|
+
<%= link_to upright.site_root_url(subdomain: Upright.sites.first.code, probe_type: probe.type, probe_name: probe.name), class: "data-table__probe" do %>
|
|
18
|
+
<%= probe_type_icon(probe.type) %>
|
|
19
|
+
<span><%= probe.name %></span>
|
|
20
|
+
<% end %>
|
|
21
|
+
</div>
|
|
22
|
+
<% sites.each do |site| %>
|
|
23
|
+
<% status = probe.status_for_site(site.code) %>
|
|
24
|
+
<div class="text-center <%= probe_status_css_class(status) %>" data-label="<%= site_name(site) %>">
|
|
25
|
+
<%= link_to upright.site_root_url(subdomain: site.code, probe_type: probe.type, probe_name: probe.name), class: "probe-status-cell-link" do %>
|
|
26
|
+
<% if status.nil? %>
|
|
27
|
+
<span class="probe-status-indicator probe-status-indicator--unknown">—</span>
|
|
28
|
+
<% elsif status.stale? %>
|
|
29
|
+
<span class="probe-status-indicator probe-status-indicator--stale">STALE</span>
|
|
30
|
+
<% elsif status.up? %>
|
|
31
|
+
<span class="probe-status-indicator probe-status-indicator--up">UP</span>
|
|
32
|
+
<% else %>
|
|
33
|
+
<span class="probe-status-indicator probe-status-indicator--down">
|
|
34
|
+
<% if status.down_since_known? %>
|
|
35
|
+
DOWN for <%= time_ago_in_words(status.down_since) %>
|
|
36
|
+
<% else %>
|
|
37
|
+
DOWN
|
|
38
|
+
<% end %>
|
|
39
|
+
</span>
|
|
40
|
+
<% end %>
|
|
41
|
+
<% end %>
|
|
42
|
+
</div>
|
|
43
|
+
<% end %>
|
|
44
|
+
</div>
|
|
45
|
+
<% end %>
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
<% end %>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<div class="dashboard probe-status-dashboard">
|
|
2
|
+
<header class="dashboard-header">
|
|
3
|
+
<h1>Probe Status</h1>
|
|
4
|
+
|
|
5
|
+
<div class="dashboard-filters">
|
|
6
|
+
<%= form_with url: dashboards_probe_status_path, method: :get, data: { controller: "form", turbo_frame: :probe_status_matrix } do %>
|
|
7
|
+
<%= collection_select nil, :probe_type, Upright.probe_types, :type, :name,
|
|
8
|
+
{ selected: @probe_type },
|
|
9
|
+
{ class: "input--select", data: { action: "change->form#submit" } } %>
|
|
10
|
+
<% end %>
|
|
11
|
+
</div>
|
|
12
|
+
</header>
|
|
13
|
+
|
|
14
|
+
<turbo-frame id="probe_status_matrix" data-controller="auto-refresh" data-auto-refresh-interval-value="60000">
|
|
15
|
+
<%= render "matrix", probes: @probes, sites: @sites %>
|
|
16
|
+
</turbo-frame>
|
|
17
|
+
</div>
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
<div class="dashboard-filters">
|
|
6
6
|
<%= form_with url: dashboards_uptime_path, method: :get, data: { controller: "form", turbo_frame: :uptime_bars } do %>
|
|
7
|
-
<%= collection_select nil, :probe_type, Upright
|
|
7
|
+
<%= collection_select nil, :probe_type, Upright.probe_types, :type, :name,
|
|
8
8
|
{ selected: @probe_type },
|
|
9
9
|
{ class: "input--select", data: { action: "change->form#submit" } } %>
|
|
10
10
|
<% end %>
|
|
@@ -2,10 +2,9 @@
|
|
|
2
2
|
<aside>
|
|
3
3
|
<ul>
|
|
4
4
|
<li><%= type_filter_link "All" %></li>
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
<li><%= type_filter_link "Traceroute", "traceroute" %></li>
|
|
5
|
+
<% Upright.probe_types.each do |probe_type| %>
|
|
6
|
+
<li><%= type_filter_link probe_type.name, probe_type.type %></li>
|
|
7
|
+
<% end %>
|
|
9
8
|
</ul>
|
|
10
9
|
</aside>
|
|
11
10
|
|
|
@@ -16,6 +15,7 @@
|
|
|
16
15
|
<div class="filters">
|
|
17
16
|
<%= form_with url: site_root_path, method: :get, data: { controller: "form" } do |f| %>
|
|
18
17
|
<%= hidden_field_tag :probe_type, params[:probe_type] if params[:probe_type].present? %>
|
|
18
|
+
<%= hidden_field_tag :date, params[:date] if params[:date].present? %>
|
|
19
19
|
<label for="probe_name">Name</label>
|
|
20
20
|
<%= select_tag :probe_name,
|
|
21
21
|
options_for_select([["All", ""]] + @probe_names, params[:probe_name]),
|
|
@@ -26,6 +26,9 @@
|
|
|
26
26
|
options_for_select([["All", ""]] + Upright::ProbeResult.statuses.keys.map { |s| [s.titleize, s] }, params[:status]),
|
|
27
27
|
class: "input--select",
|
|
28
28
|
data: { action: "change->form#submit" } %>
|
|
29
|
+
<% if params[:date].present? %>
|
|
30
|
+
<%= link_to "Clear date filter", site_root_path(probe_type: params[:probe_type], probe_name: params[:probe_name], status: params[:status]), class: "filter-clear" %>
|
|
31
|
+
<% end %>
|
|
29
32
|
<% end %>
|
|
30
33
|
</div>
|
|
31
34
|
|
data/config/ci.rb
CHANGED
|
@@ -3,5 +3,7 @@
|
|
|
3
3
|
CI.run do
|
|
4
4
|
step "Style: Ruby", "bin/rubocop"
|
|
5
5
|
step "Security: Brakeman code analysis", "bin/brakeman --quiet --no-pager --exit-on-warn --exit-on-error"
|
|
6
|
+
step "Lint: GitHub Actions (actionlint)", "actionlint"
|
|
7
|
+
step "Lint: GitHub Actions (zizmor)", "zizmor ."
|
|
6
8
|
step "Tests: Rails", "bin/rails test"
|
|
7
9
|
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
40451408e038d0fe36a8f4aed0cef429
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
40451408e038d0fe36a8f4aed0cef429
|
data/config/routes.rb
CHANGED
|
@@ -5,6 +5,15 @@ module Upright
|
|
|
5
5
|
|
|
6
6
|
desc "Install Upright engine into your application"
|
|
7
7
|
|
|
8
|
+
def set_ruby_version
|
|
9
|
+
ruby_version = RUBY_VERSION
|
|
10
|
+
create_file ".ruby-version", "#{ruby_version}\n"
|
|
11
|
+
create_file "mise.toml", <<~TOML
|
|
12
|
+
[tools]
|
|
13
|
+
ruby = "#{ruby_version}"
|
|
14
|
+
TOML
|
|
15
|
+
end
|
|
16
|
+
|
|
8
17
|
def copy_initializers
|
|
9
18
|
template "upright.rb", "config/initializers/upright.rb"
|
|
10
19
|
template "omniauth.rb", "config/initializers/omniauth.rb"
|
|
@@ -19,6 +28,7 @@ module Upright
|
|
|
19
28
|
empty_directory "probes/authenticators"
|
|
20
29
|
template "http_probes.yml", "probes/http_probes.yml"
|
|
21
30
|
template "smtp_probes.yml", "probes/smtp_probes.yml"
|
|
31
|
+
template "traceroute_probes.yml", "probes/traceroute_probes.yml"
|
|
22
32
|
end
|
|
23
33
|
|
|
24
34
|
def copy_observability_configs
|
|
@@ -42,23 +52,63 @@ module Upright
|
|
|
42
52
|
template "puma.rb", "config/puma.rb"
|
|
43
53
|
end
|
|
44
54
|
|
|
55
|
+
def install_solid_queue
|
|
56
|
+
rails_command "solid_queue:install"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def add_queue_database
|
|
60
|
+
gsub_file "config/database.yml",
|
|
61
|
+
"development:\n <<: *default\n database: storage/development.sqlite3",
|
|
62
|
+
"development:\n primary:\n <<: *default\n database: storage/development.sqlite3\n queue:\n <<: *default\n database: storage/development_queue.sqlite3\n migrations_paths: db/queue_migrate"
|
|
63
|
+
|
|
64
|
+
gsub_file "config/database.yml",
|
|
65
|
+
"test:\n <<: *default\n database: storage/test.sqlite3",
|
|
66
|
+
"test:\n primary:\n <<: *default\n database: storage/test.sqlite3\n queue:\n <<: *default\n database: storage/test_queue.sqlite3\n migrations_paths: db/queue_migrate"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def copy_recurring_config
|
|
70
|
+
template "recurring.yml", "config/recurring.yml", force: true
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def add_jobs_to_procfile
|
|
74
|
+
procfile = File.join(destination_root, "Procfile.dev")
|
|
75
|
+
if File.exist?(procfile)
|
|
76
|
+
unless File.read(procfile).include?("jobs:")
|
|
77
|
+
append_to_file "Procfile.dev", "jobs: OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES bin/rails solid_queue:start\n"
|
|
78
|
+
end
|
|
79
|
+
else
|
|
80
|
+
create_file "Procfile.dev", <<~PROCFILE
|
|
81
|
+
web: bin/rails server -b '0.0.0.0' -p ${PORT:-3000}
|
|
82
|
+
jobs: OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES bin/rails solid_queue:start
|
|
83
|
+
PROCFILE
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
45
87
|
def add_routes
|
|
46
88
|
route 'mount Upright::Engine => "/", as: :upright'
|
|
47
89
|
end
|
|
48
90
|
|
|
91
|
+
def install_active_storage
|
|
92
|
+
rails_command "active_storage:install"
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def configure_javascript
|
|
96
|
+
append_to_file "app/javascript/application.js", 'import "upright/application"'
|
|
97
|
+
end
|
|
98
|
+
|
|
49
99
|
def show_post_install_message
|
|
50
100
|
say ""
|
|
51
101
|
say "Upright has been installed!", :green
|
|
52
102
|
say ""
|
|
53
103
|
say "Next steps:"
|
|
54
|
-
say " 1.
|
|
104
|
+
say " 1. Prepare the database: bin/rails db:prepare"
|
|
55
105
|
say " 2. Configure your servers in config/deploy.yml"
|
|
56
106
|
say " 3. Configure sites in config/sites.yml"
|
|
57
107
|
say " 4. Add probes in probes/*.yml"
|
|
58
108
|
say " 5. Set ADMIN_PASSWORD env var (default: upright)"
|
|
59
109
|
say ""
|
|
60
110
|
say "For production, review config/initializers/upright.rb and update:"
|
|
61
|
-
say " config.hostname = \"
|
|
111
|
+
say " config.hostname = \"example.com\""
|
|
62
112
|
say ""
|
|
63
113
|
say "Start dev services (Prometheus, Alertmanager, Playwright):"
|
|
64
114
|
say " docker compose up -d"
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
#
|
|
8
8
|
# Optional fields:
|
|
9
9
|
# - expected_status: HTTP status code to expect (default: 200-399)
|
|
10
|
+
# - alert_severity: Alert severity when probe fails: medium, high (default), critical
|
|
10
11
|
# - basic_auth_credentials: Key in Rails credentials for basic auth
|
|
11
12
|
# - proxy: Key in Rails credentials for proxy settings
|
|
12
13
|
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
development:
|
|
2
|
+
http_probes:
|
|
3
|
+
schedule: every minute
|
|
4
|
+
command: "Upright::Probes::HTTPProbe.check_and_record_all_later"
|
|
5
|
+
|
|
6
|
+
smtp_probes:
|
|
7
|
+
schedule: every minute
|
|
8
|
+
command: "Upright::Probes::SMTPProbe.check_and_record_all_later"
|
|
9
|
+
|
|
10
|
+
production:
|
|
11
|
+
http_probes:
|
|
12
|
+
schedule: "*/30 * * * * *"
|
|
13
|
+
command: "Upright::Probes::HTTPProbe.check_and_record_all_later"
|
|
14
|
+
|
|
15
|
+
smtp_probes:
|
|
16
|
+
schedule: "*/30 * * * * *"
|
|
17
|
+
command: "Upright::Probes::SMTPProbe.check_and_record_all_later"
|
|
18
|
+
|
|
19
|
+
clear_solid_queue_finished_jobs:
|
|
20
|
+
command: "SolidQueue::Job.clear_finished_in_batches(sleep_between_batches: 0.3)"
|
|
21
|
+
schedule: every hour at minute 12
|
|
22
|
+
|
|
23
|
+
cleanup_stale_probe_results:
|
|
24
|
+
command: "Upright::ProbeResult.cleanup_stale"
|
|
25
|
+
schedule: every hour at minute 30
|
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
# Required fields:
|
|
5
5
|
# - name: Unique identifier for the probe
|
|
6
6
|
# - host: SMTP server hostname
|
|
7
|
+
#
|
|
8
|
+
# Optional fields:
|
|
9
|
+
# - alert_severity: Alert severity when probe fails: medium, high (default), critical
|
|
7
10
|
|
|
8
11
|
# - name: Example Mail Server
|
|
9
12
|
# host: mail.example.com
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Traceroute Probe Definitions
|
|
2
|
+
# Add your traceroute probes here
|
|
3
|
+
#
|
|
4
|
+
# Required fields:
|
|
5
|
+
# - name: Unique identifier for the probe
|
|
6
|
+
# - host: Hostname or IP address to trace
|
|
7
|
+
#
|
|
8
|
+
# Optional fields:
|
|
9
|
+
# - alert_severity: Alert severity when probe fails: medium, high (default), critical
|
|
10
|
+
|
|
11
|
+
# - name: Example Service
|
|
12
|
+
# host: example.com
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Upright.configure do |config|
|
|
4
4
|
config.service_name = "<%= Rails.application.class.module_parent_name.underscore %>"
|
|
5
5
|
config.user_agent = "<%= Rails.application.class.module_parent_name.underscore %>/1.0"
|
|
6
|
-
config.hostname = Rails.env.local? ? "<%= Rails.application.class.module_parent_name.
|
|
6
|
+
config.hostname = Rails.env.local? ? "<%= Rails.application.class.module_parent_name.underscore.dasherize %>.localhost" : "<%= Rails.application.class.module_parent_name.underscore.dasherize %>.com"
|
|
7
7
|
|
|
8
8
|
# Playwright browser server URL
|
|
9
9
|
# config.playwright_server_url = ENV["PLAYWRIGHT_SERVER_URL"]
|
|
@@ -18,4 +18,7 @@ Upright.configure do |config|
|
|
|
18
18
|
# client_id: ENV["OIDC_CLIENT_ID"],
|
|
19
19
|
# client_secret: ENV["OIDC_CLIENT_SECRET"]
|
|
20
20
|
# }
|
|
21
|
+
|
|
22
|
+
# Register custom probe types (built-in types: http, playwright, smtp, traceroute)
|
|
23
|
+
# config.probe_types.register :ftp_file, name: "FTP File", icon: "📂"
|
|
21
24
|
end
|
|
@@ -8,9 +8,9 @@ groups:
|
|
|
8
8
|
# Fraction of regions reporting DOWN (0.0 to 1.0)
|
|
9
9
|
- record: upright:probe_down_fraction
|
|
10
10
|
expr: |
|
|
11
|
-
count by (name, type, probe_target) (upright_probe_up == 0)
|
|
11
|
+
count by (name, type, probe_target, alert_severity) (upright_probe_up == 0)
|
|
12
12
|
/
|
|
13
|
-
count by (name, type, probe_target) (upright_probe_up)
|
|
13
|
+
count by (name, type, probe_target, alert_severity) (upright_probe_up)
|
|
14
14
|
|
|
15
15
|
# Daily uptime percentage (0.0 to 1.0)
|
|
16
16
|
# Uptime = percentage of time in past day when majority of sites reported UP
|
|
@@ -148,7 +148,7 @@ groups:
|
|
|
148
148
|
upright: "https://app.<%= app_domain %>/?probe_type={{ $labels.type }}&status=fail&probe_name={{ $labels.name | urlquery }}"
|
|
149
149
|
expr: upright:probe_down_fraction{type="http"} > 0.5
|
|
150
150
|
labels:
|
|
151
|
-
severity:
|
|
151
|
+
severity: "{{ $labels.alert_severity }}"
|
|
152
152
|
group: upright
|
|
153
153
|
|
|
154
154
|
- alert: UprightHTTPProbeDegraded
|
|
@@ -187,7 +187,7 @@ groups:
|
|
|
187
187
|
upright: "https://app.<%= app_domain %>/?probe_type={{ $labels.type }}&status=fail&probe_name={{ $labels.name | urlquery }}"
|
|
188
188
|
expr: upright:probe_down_fraction{type="smtp"} > 0.5
|
|
189
189
|
labels:
|
|
190
|
-
severity:
|
|
190
|
+
severity: "{{ $labels.alert_severity }}"
|
|
191
191
|
group: upright
|
|
192
192
|
|
|
193
193
|
- alert: UprightSMTPProbeDegraded
|
|
@@ -226,7 +226,7 @@ groups:
|
|
|
226
226
|
upright: "https://app.<%= app_domain %>/?probe_type={{ $labels.type }}&status=fail&probe_name={{ $labels.name | urlquery }}"
|
|
227
227
|
expr: upright:probe_down_fraction{type="playwright"} > 0.5
|
|
228
228
|
labels:
|
|
229
|
-
severity:
|
|
229
|
+
severity: "{{ $labels.alert_severity }}"
|
|
230
230
|
group: upright
|
|
231
231
|
|
|
232
232
|
- alert: UprightPlaywrightProbeDegraded
|
|
@@ -29,6 +29,14 @@ class Upright::Configuration
|
|
|
29
29
|
attr_accessor :prometheus_url
|
|
30
30
|
attr_accessor :alert_webhook_url
|
|
31
31
|
|
|
32
|
+
# Probe types
|
|
33
|
+
attr_reader :probe_types
|
|
34
|
+
|
|
35
|
+
# Probe result cleanup
|
|
36
|
+
attr_accessor :stale_success_threshold
|
|
37
|
+
attr_accessor :stale_failure_threshold
|
|
38
|
+
attr_accessor :failure_retention_limit
|
|
39
|
+
|
|
32
40
|
def initialize
|
|
33
41
|
@service_name = "upright"
|
|
34
42
|
@user_agent = "Upright/1.0"
|
|
@@ -41,11 +49,17 @@ class Upright::Configuration
|
|
|
41
49
|
@probes_path = nil
|
|
42
50
|
@authenticators_path = nil
|
|
43
51
|
|
|
52
|
+
@probe_types = Upright::ProbeTypeRegistry.new
|
|
53
|
+
|
|
44
54
|
@playwright_server_url = ENV["PLAYWRIGHT_SERVER_URL"]
|
|
45
55
|
@otel_endpoint = ENV["OTEL_EXPORTER_OTLP_ENDPOINT"]
|
|
46
56
|
|
|
47
57
|
@auth_provider = :static_credentials
|
|
48
58
|
@auth_options = {}
|
|
59
|
+
|
|
60
|
+
@stale_success_threshold = 24.hours
|
|
61
|
+
@stale_failure_threshold = 30.days
|
|
62
|
+
@failure_retention_limit = 20_000
|
|
49
63
|
end
|
|
50
64
|
|
|
51
65
|
def global_subdomain
|
data/lib/upright/engine.rb
CHANGED
|
@@ -56,6 +56,13 @@ class Upright::Engine < ::Rails::Engine
|
|
|
56
56
|
end
|
|
57
57
|
end
|
|
58
58
|
|
|
59
|
+
initializer "upright.probe_types", before: :load_config_initializers do
|
|
60
|
+
Upright.config.probe_types.register :http, name: "HTTP", icon: "🌐"
|
|
61
|
+
Upright.config.probe_types.register :playwright, name: "Playwright", icon: "🎭"
|
|
62
|
+
Upright.config.probe_types.register :smtp, name: "SMTP", icon: "✉️"
|
|
63
|
+
Upright.config.probe_types.register :traceroute, name: "Traceroute", icon: "🛤️"
|
|
64
|
+
end
|
|
65
|
+
|
|
59
66
|
initializer "upright.frozen_record" do
|
|
60
67
|
FrozenRecord::Base.base_path = Upright.configuration.frozen_record_path
|
|
61
68
|
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
module Upright
|
|
2
|
+
module Geohash
|
|
3
|
+
BASE32 = "0123456789bcdefghjkmnpqrstuvwxyz"
|
|
4
|
+
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
def decode(geohash)
|
|
8
|
+
bounds = [ [ -90.0, +90.0 ], [ -180.0, +180.0 ] ]
|
|
9
|
+
|
|
10
|
+
geohash.downcase.each_char.with_index do |c, i|
|
|
11
|
+
d = BASE32.index c
|
|
12
|
+
|
|
13
|
+
5.times do |j|
|
|
14
|
+
bit = (d & (1 << (4 - j))) >> (4 - j)
|
|
15
|
+
k = (~i & 1) ^ (j & 1)
|
|
16
|
+
bounds[k][bit ^ 1] = (bounds[k][0] + bounds[k][1]) / 2
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
bounds.transpose
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def encode(latitude, longitude, precision = 12)
|
|
24
|
+
mids = [ latitude, longitude ]
|
|
25
|
+
bounds = [ [ -90.0, +90.0 ], [ -180.0, +180.0 ] ]
|
|
26
|
+
|
|
27
|
+
geohash = +""
|
|
28
|
+
|
|
29
|
+
precision.times do |i|
|
|
30
|
+
d = 0
|
|
31
|
+
|
|
32
|
+
5.times do |j|
|
|
33
|
+
k = (~i & 1) ^ (j & 1)
|
|
34
|
+
mid = (bounds[k][0] + bounds[k][1]) / 2
|
|
35
|
+
bit = mids[k] > mid ? 1 : 0
|
|
36
|
+
bounds[k][bit ^ 1] = mid
|
|
37
|
+
d |= bit << (4 - j)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
geohash << BASE32[d]
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
geohash
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
data/lib/upright/metrics.rb
CHANGED
|
@@ -44,12 +44,12 @@ module Upright::Metrics
|
|
|
44
44
|
gauge :probe_duration_seconds,
|
|
45
45
|
comment: "Duration of each probe",
|
|
46
46
|
aggregation: :max,
|
|
47
|
-
tags: %i[type name probe_target probe_service status]
|
|
47
|
+
tags: %i[type name probe_target probe_service alert_severity status]
|
|
48
48
|
|
|
49
49
|
gauge :probe_up,
|
|
50
50
|
comment: "Probe status (1 = up, 0 = down)",
|
|
51
51
|
aggregation: :most_recent,
|
|
52
|
-
tags: %i[type name probe_target probe_service]
|
|
52
|
+
tags: %i[type name probe_target probe_service alert_severity]
|
|
53
53
|
|
|
54
54
|
gauge :http_response_status,
|
|
55
55
|
comment: "HTTP response status code",
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
module Upright
|
|
2
|
+
class ProbeTypeRegistry
|
|
3
|
+
ProbeType = Data.define(:type, :name, :icon)
|
|
4
|
+
|
|
5
|
+
include Enumerable
|
|
6
|
+
|
|
7
|
+
def initialize
|
|
8
|
+
@probe_types = []
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def register(type, name:, icon:)
|
|
12
|
+
type = type.to_s
|
|
13
|
+
@probe_types.reject! { |pt| pt.type == type }
|
|
14
|
+
@probe_types << ProbeType.new(type:, name:, icon:)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def types
|
|
18
|
+
@probe_types.map(&:type)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def find(type)
|
|
22
|
+
@probe_types.find { |pt| pt.type == type.to_s }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def unregister(type)
|
|
26
|
+
@probe_types.reject! { |pt| pt.type == type.to_s }
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def each(&block)
|
|
30
|
+
@probe_types.each(&block)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
data/lib/upright/site.rb
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
require "geohash_ruby"
|
|
2
|
-
|
|
3
1
|
module Upright
|
|
4
2
|
class Site
|
|
5
3
|
attr_reader :code, :city, :country, :geohash, :stagger_index
|
|
@@ -43,7 +41,7 @@ module Upright
|
|
|
43
41
|
|
|
44
42
|
private
|
|
45
43
|
def coordinates
|
|
46
|
-
@coordinates ||= Geohash.decode(geohash).first
|
|
44
|
+
@coordinates ||= Upright::Geohash.decode(geohash).first
|
|
47
45
|
end
|
|
48
46
|
end
|
|
49
47
|
end
|
data/lib/upright/version.rb
CHANGED
data/lib/upright.rb
CHANGED
|
@@ -14,12 +14,13 @@ require "importmap-rails"
|
|
|
14
14
|
require "turbo-rails"
|
|
15
15
|
require "stimulus-rails"
|
|
16
16
|
require "geared_pagination"
|
|
17
|
-
require "geohash_ruby"
|
|
18
17
|
require "yabeda/prometheus"
|
|
19
18
|
require "yabeda/puma/plugin"
|
|
20
19
|
|
|
21
20
|
require "upright/version"
|
|
22
21
|
require "upright/configuration"
|
|
22
|
+
require "upright/probe_type_registry"
|
|
23
|
+
require "upright/geohash"
|
|
23
24
|
require "upright/site"
|
|
24
25
|
require "upright/metrics"
|
|
25
26
|
require "upright/tracing"
|
|
@@ -38,6 +39,10 @@ module Upright
|
|
|
38
39
|
yield(configuration)
|
|
39
40
|
end
|
|
40
41
|
|
|
42
|
+
def probe_types
|
|
43
|
+
configuration.probe_types
|
|
44
|
+
end
|
|
45
|
+
|
|
41
46
|
def sites
|
|
42
47
|
@sites ||= load_sites
|
|
43
48
|
end
|