upright 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/LICENSE.md +10 -0
- data/README.md +455 -0
- data/Rakefile +6 -0
- data/app/assets/stylesheets/upright/_global.css +104 -0
- data/app/assets/stylesheets/upright/artifact.css +148 -0
- data/app/assets/stylesheets/upright/base.css +68 -0
- data/app/assets/stylesheets/upright/buttons.css +21 -0
- data/app/assets/stylesheets/upright/dashboard.css +287 -0
- data/app/assets/stylesheets/upright/forms.css +104 -0
- data/app/assets/stylesheets/upright/header.css +124 -0
- data/app/assets/stylesheets/upright/layout.css +100 -0
- data/app/assets/stylesheets/upright/map.css +25 -0
- data/app/assets/stylesheets/upright/pagination.css +45 -0
- data/app/assets/stylesheets/upright/probes.css +72 -0
- data/app/assets/stylesheets/upright/reset.css +26 -0
- data/app/assets/stylesheets/upright/tables.css +63 -0
- data/app/assets/stylesheets/upright/typography.css +27 -0
- data/app/assets/stylesheets/upright/uptime-bars.css +154 -0
- data/app/controllers/concerns/upright/authentication.rb +21 -0
- data/app/controllers/concerns/upright/subdomain_scoping.rb +18 -0
- data/app/controllers/upright/alertmanager_proxy_controller.rb +21 -0
- data/app/controllers/upright/application_controller.rb +12 -0
- data/app/controllers/upright/artifacts_controller.rb +5 -0
- data/app/controllers/upright/dashboards/uptimes_controller.rb +6 -0
- data/app/controllers/upright/jobs_controller.rb +4 -0
- data/app/controllers/upright/probe_results_controller.rb +17 -0
- data/app/controllers/upright/prometheus_proxy_controller.rb +62 -0
- data/app/controllers/upright/sessions_controller.rb +29 -0
- data/app/controllers/upright/sites_controller.rb +5 -0
- data/app/helpers/upright/application_helper.rb +11 -0
- data/app/helpers/upright/dashboards_helper.rb +31 -0
- data/app/helpers/upright/probe_results_helper.rb +49 -0
- data/app/javascript/upright/application.js +2 -0
- data/app/javascript/upright/controllers/application.js +5 -0
- data/app/javascript/upright/controllers/form_controller.js +7 -0
- data/app/javascript/upright/controllers/index.js +4 -0
- data/app/javascript/upright/controllers/popover_controller.js +15 -0
- data/app/javascript/upright/controllers/probe_results_chart_controller.js +79 -0
- data/app/javascript/upright/controllers/results_table_controller.js +16 -0
- data/app/javascript/upright/controllers/sites_map_controller.js +33 -0
- data/app/jobs/upright/application_job.rb +2 -0
- data/app/jobs/upright/probe_check_job.rb +42 -0
- data/app/models/concerns/upright/exception_recording.rb +38 -0
- data/app/models/concerns/upright/playwright/form_authentication.rb +27 -0
- data/app/models/concerns/upright/playwright/helpers.rb +7 -0
- data/app/models/concerns/upright/playwright/lifecycle.rb +44 -0
- data/app/models/concerns/upright/playwright/logging.rb +87 -0
- data/app/models/concerns/upright/playwright/otel_tracing.rb +137 -0
- data/app/models/concerns/upright/playwright/video_recording.rb +60 -0
- data/app/models/concerns/upright/probe_yaml_source.rb +10 -0
- data/app/models/concerns/upright/probeable.rb +125 -0
- data/app/models/concerns/upright/staggerable.rb +22 -0
- data/app/models/concerns/upright/traceroute/otel_tracing.rb +108 -0
- data/app/models/upright/application_record.rb +3 -0
- data/app/models/upright/artifact.rb +61 -0
- data/app/models/upright/current.rb +9 -0
- data/app/models/upright/http/request.rb +59 -0
- data/app/models/upright/http/response.rb +55 -0
- data/app/models/upright/playwright/authenticator/base.rb +128 -0
- data/app/models/upright/playwright/storage_state.rb +31 -0
- data/app/models/upright/probe_result.rb +31 -0
- data/app/models/upright/probes/http_probe.rb +102 -0
- data/app/models/upright/probes/playwright/base.rb +48 -0
- data/app/models/upright/probes/smtp_probe.rb +48 -0
- data/app/models/upright/probes/traceroute_probe.rb +32 -0
- data/app/models/upright/probes/uptime/summary.rb +36 -0
- data/app/models/upright/probes/uptime.rb +36 -0
- data/app/models/upright/traceroute/hop.rb +49 -0
- data/app/models/upright/traceroute/ip_metadata_lookup.rb +107 -0
- data/app/models/upright/traceroute/mtr_parser.rb +47 -0
- data/app/models/upright/traceroute/result.rb +57 -0
- data/app/models/upright/user.rb +14 -0
- data/app/views/layouts/upright/_header.html.erb +23 -0
- data/app/views/layouts/upright/application.html.erb +25 -0
- data/app/views/upright/active_storage/attachments/_attachment.html.erb +21 -0
- data/app/views/upright/alertmanager_proxy/show.html.erb +1 -0
- data/app/views/upright/artifacts/show.html.erb +9 -0
- data/app/views/upright/dashboards/_uptime_bars.html.erb +17 -0
- data/app/views/upright/dashboards/_uptime_probe_row.html.erb +22 -0
- data/app/views/upright/dashboards/uptimes/show.html.erb +17 -0
- data/app/views/upright/jobs/show.html.erb +1 -0
- data/app/views/upright/probe_results/_pagination.html.erb +19 -0
- data/app/views/upright/probe_results/index.html.erb +72 -0
- data/app/views/upright/prometheus_proxy/show.html.erb +1 -0
- data/app/views/upright/sessions/new.html.erb +6 -0
- data/app/views/upright/sites/index.html.erb +22 -0
- data/config/brakeman.ignore +39 -0
- data/config/ci.rb +7 -0
- data/config/importmap.rb +18 -0
- data/config/routes.rb +41 -0
- data/db/migrate/20250114000001_create_upright_probe_results.rb +19 -0
- data/lib/generators/upright/install/install_generator.rb +83 -0
- data/lib/generators/upright/install/templates/alertmanager.yml +14 -0
- data/lib/generators/upright/install/templates/deploy.yml +118 -0
- data/lib/generators/upright/install/templates/development_alertmanager.yml +11 -0
- data/lib/generators/upright/install/templates/development_prometheus.yml +12 -0
- data/lib/generators/upright/install/templates/docker-compose.yml +38 -0
- data/lib/generators/upright/install/templates/http_probes.yml +14 -0
- data/lib/generators/upright/install/templates/omniauth.rb +8 -0
- data/lib/generators/upright/install/templates/otel_collector.yml +24 -0
- data/lib/generators/upright/install/templates/prometheus.yml +10 -0
- data/lib/generators/upright/install/templates/puma.rb +40 -0
- data/lib/generators/upright/install/templates/sites.yml +26 -0
- data/lib/generators/upright/install/templates/smtp_probes.yml +9 -0
- data/lib/generators/upright/install/templates/upright.rb +21 -0
- data/lib/generators/upright/install/templates/upright.rules.yml +256 -0
- data/lib/generators/upright/playwright_probe/playwright_probe_generator.rb +30 -0
- data/lib/generators/upright/playwright_probe/templates/authenticator.rb.tt +14 -0
- data/lib/generators/upright/playwright_probe/templates/probe.rb.tt +14 -0
- data/lib/omniauth/strategies/static_credentials.rb +57 -0
- data/lib/tasks/upright_tasks.rake +4 -0
- data/lib/upright/configuration.rb +106 -0
- data/lib/upright/engine.rb +157 -0
- data/lib/upright/metrics.rb +62 -0
- data/lib/upright/playwright/collect_performance_metrics.js +36 -0
- data/lib/upright/site.rb +49 -0
- data/lib/upright/tracing.rb +49 -0
- data/lib/upright/version.rb +3 -0
- data/lib/upright.rb +68 -0
- metadata +513 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
require "open3"
|
|
2
|
+
|
|
3
|
+
class Upright::Traceroute::Result
|
|
4
|
+
PROBE_COUNT = 10
|
|
5
|
+
MAX_HOPS = 30
|
|
6
|
+
|
|
7
|
+
attr_reader :host, :hops, :raw_json
|
|
8
|
+
|
|
9
|
+
def self.for(host)
|
|
10
|
+
new(host).tap(&:run)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def initialize(host)
|
|
14
|
+
@host = host
|
|
15
|
+
@hops = []
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def run
|
|
19
|
+
@raw_json = run_mtr
|
|
20
|
+
@hops = build_hops(Upright::Traceroute::MtrParser.new(@raw_json).parse)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def reached_destination?
|
|
24
|
+
hops.any? && hops.last.responded?
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
def run_mtr
|
|
29
|
+
stdout, _status = Open3.capture2(
|
|
30
|
+
"mtr",
|
|
31
|
+
"--json",
|
|
32
|
+
"--aslookup",
|
|
33
|
+
"--report-cycles", PROBE_COUNT.to_s,
|
|
34
|
+
"--max-ttl", MAX_HOPS.to_s,
|
|
35
|
+
host.to_s
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
Rails.logger.info stdout
|
|
39
|
+
|
|
40
|
+
stdout
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def build_hops(parsed_hops)
|
|
44
|
+
ips = parsed_hops.map { |h| h[:ip] }.compact
|
|
45
|
+
metadata_by_ip = Upright::Traceroute::IpMetadataLookup.for_many(ips)
|
|
46
|
+
|
|
47
|
+
parsed_hops.map.with_index do |hop_data, index|
|
|
48
|
+
metadata = metadata_by_ip[hop_data[:ip]] || {}
|
|
49
|
+
|
|
50
|
+
Upright::Traceroute::Hop.new(
|
|
51
|
+
**hop_data,
|
|
52
|
+
**metadata,
|
|
53
|
+
geohash: index == 0 ? Upright.current_site.geohash : metadata[:geohash]
|
|
54
|
+
)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<header class="header">
|
|
2
|
+
<div class="header__left">
|
|
3
|
+
<%= link_to "Upright", root_url(subdomain: Upright.configuration.global_subdomain), class: "header__logo" %>
|
|
4
|
+
<% if Upright::Current.site.present? %>
|
|
5
|
+
<span class="header__site"><%= Upright::Current.site.city %></span>
|
|
6
|
+
<% end %>
|
|
7
|
+
</div>
|
|
8
|
+
|
|
9
|
+
<nav class="header__nav">
|
|
10
|
+
<%= link_to "Probes", upright.site_root_url(subdomain: current_or_default_site.code), class: "header__link" %>
|
|
11
|
+
<%= link_to "Uptime", upright.dashboards_uptime_url(subdomain: Upright.configuration.global_subdomain), class: "header__link" %>
|
|
12
|
+
<span class="header__divider"></span>
|
|
13
|
+
<%= link_to "Jobs", upright.jobs_url(subdomain: current_or_default_site.code), class: "header__link" %>
|
|
14
|
+
<span class="header__divider"></span>
|
|
15
|
+
<%= link_to "Prometheus", upright.prometheus_url(subdomain: Upright.configuration.global_subdomain), class: "header__link" %>
|
|
16
|
+
<%= link_to "Alertmanager", upright.alertmanager_url(subdomain: Upright.configuration.global_subdomain), class: "header__link" %>
|
|
17
|
+
</nav>
|
|
18
|
+
|
|
19
|
+
<div class="header__user">
|
|
20
|
+
<span><%= Upright::Current.user.name %></span>
|
|
21
|
+
<%= button_to "Sign out", upright.session_path, method: :delete, data: { turbo: false }, class: "btn" %>
|
|
22
|
+
</div>
|
|
23
|
+
</header>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<title>Upright</title>
|
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
6
|
+
<meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)">
|
|
7
|
+
<meta name="theme-color" content="#1f2937" media="(prefers-color-scheme: dark)">
|
|
8
|
+
<%= csrf_meta_tags %>
|
|
9
|
+
<%= csp_meta_tag %>
|
|
10
|
+
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin="">
|
|
11
|
+
<%= upright_stylesheet_link_tag "data-turbo-track": "reload" %>
|
|
12
|
+
<%= javascript_importmap_tags %>
|
|
13
|
+
</head>
|
|
14
|
+
|
|
15
|
+
<body>
|
|
16
|
+
<% if signed_in? %>
|
|
17
|
+
<%= render "layouts/upright/header" %>
|
|
18
|
+
<main class="main">
|
|
19
|
+
<%= yield %>
|
|
20
|
+
</main>
|
|
21
|
+
<% else %>
|
|
22
|
+
<%= yield %>
|
|
23
|
+
<% end %>
|
|
24
|
+
</body>
|
|
25
|
+
</html>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<details class="artifact" data-controller="popover" data-action="popover#loadFrame" data-results-table-target="popover">
|
|
2
|
+
<summary title="<%= attachment.filename %>"><%= artifact_icon(attachment) %></summary>
|
|
3
|
+
<div class="artifact__content">
|
|
4
|
+
<header class="artifact__header">
|
|
5
|
+
<div class="artifact__header-text">
|
|
6
|
+
<% if local_assigns[:probe_result] %>
|
|
7
|
+
<div class="artifact__probe"><%= probe_type_icon(probe_result.probe_type) %> <%= probe_result.probe_name %></div>
|
|
8
|
+
<% if probe_result.probe_target.present? %>
|
|
9
|
+
<div class="artifact__probe-target"><%= probe_result.probe_target %></div>
|
|
10
|
+
<% end %>
|
|
11
|
+
<div class="artifact__probe-time" title="<%= probe_result.created_at.to_fs(:db) %>"><%= time_ago_in_words(probe_result.created_at) %> ago</div>
|
|
12
|
+
<% end %>
|
|
13
|
+
<h3 class="artifact__title"><%= artifact_icon(attachment) %> <%= attachment.filename %></h3>
|
|
14
|
+
</div>
|
|
15
|
+
<button type="button" class="artifact__close" data-action="popover#close" aria-label="Close">×</button>
|
|
16
|
+
</header>
|
|
17
|
+
<div class="artifact__body">
|
|
18
|
+
<%= turbo_frame_tag dom_id(attachment), src: upright.site_artifact_path(attachment), loading: :lazy, data: { popover_target: "frame" } %>
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
</details>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<iframe src="<%= alertmanager_proxy_path %>" class="service-frame"></iframe>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<%= turbo_frame_tag dom_id(@artifact) do %>
|
|
2
|
+
<% if @artifact.video? %>
|
|
3
|
+
<video controls width="960" preload="none" autoplay muted>
|
|
4
|
+
<source src="<%= main_app.rails_blob_path(@artifact, disposition: :inline) %>" type="video/webm">
|
|
5
|
+
</video>
|
|
6
|
+
<% else %>
|
|
7
|
+
<pre><%= @artifact.download %></pre>
|
|
8
|
+
<% end %>
|
|
9
|
+
<% end %>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<% if probes.empty? %>
|
|
2
|
+
<div class="no-data-message">
|
|
3
|
+
No probe data available for the selected filters.
|
|
4
|
+
</div>
|
|
5
|
+
<% else %>
|
|
6
|
+
<div class="uptime-bars">
|
|
7
|
+
<div class="uptime-bars__header">
|
|
8
|
+
<div>Probe</div>
|
|
9
|
+
<div>Past 30 days</div>
|
|
10
|
+
<div>Uptime</div>
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
<% probes.each do |probe| %>
|
|
14
|
+
<%= render "upright/dashboards/uptime_probe_row", probe: probe, dates: date_range %>
|
|
15
|
+
<% end %>
|
|
16
|
+
</div>
|
|
17
|
+
<% end %>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<div class="uptime-bars__row">
|
|
2
|
+
<%= link_to upright.site_root_url(subdomain: Upright.sites.first.code, probe_type: probe.type, probe_name: probe.name), class: "uptime-bars__probe" do %>
|
|
3
|
+
<%= probe_type_icon(probe.type) %>
|
|
4
|
+
<span class="probe__name"><%= probe.name %></span>
|
|
5
|
+
<% end %>
|
|
6
|
+
<div class="uptime-bars__days">
|
|
7
|
+
<% dates.each do |date| %>
|
|
8
|
+
<% uptime = probe.uptime_for_date(date) %>
|
|
9
|
+
<% if uptime.nil? %>
|
|
10
|
+
<div class="uptime-bar uptime-bar--no-data" title="<%= date.to_fs(:short) %>: No data"></div>
|
|
11
|
+
<% else %>
|
|
12
|
+
<% uptime_percent = uptime * 100 %>
|
|
13
|
+
<% downtime_minutes = ((1 - uptime) * 24 * 60).round %>
|
|
14
|
+
<div class="uptime-bar uptime-bar--<%= uptime_label(uptime_percent) %>"
|
|
15
|
+
title="<%= uptime_bar_tooltip(date, uptime_percent, downtime_minutes) %>"></div>
|
|
16
|
+
<% end %>
|
|
17
|
+
<% end %>
|
|
18
|
+
</div>
|
|
19
|
+
<div class="uptime-bars__uptime uptime-bars__uptime--<%= uptime_label(probe.overall_uptime) %>">
|
|
20
|
+
<%= number_with_precision(probe.overall_uptime, precision: 2) %>%
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<div class="dashboard uptime-dashboard">
|
|
2
|
+
<header class="dashboard-header">
|
|
3
|
+
<h1>Uptime Dashboard</h1>
|
|
4
|
+
|
|
5
|
+
<div class="dashboard-filters">
|
|
6
|
+
<%= form_with url: dashboards_uptime_path, method: :get, data: { controller: "form", turbo_frame: :uptime_bars } do %>
|
|
7
|
+
<%= collection_select nil, :probe_type, Upright::Probeable::TYPES, :itself, :titleize,
|
|
8
|
+
{ selected: @probe_type },
|
|
9
|
+
{ class: "input--select", data: { action: "change->form#submit" } } %>
|
|
10
|
+
<% end %>
|
|
11
|
+
</div>
|
|
12
|
+
</header>
|
|
13
|
+
|
|
14
|
+
<%= turbo_frame_tag :uptime_bars do %>
|
|
15
|
+
<%= render "upright/dashboards/uptime_bars", probes: @probes %>
|
|
16
|
+
<% end %>
|
|
17
|
+
</div>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<iframe src="<%= mission_control_jobs.root_path %>" class="service-frame"></iframe>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<% if page.recordset.page_count > 1 %>
|
|
2
|
+
<nav class="pagination">
|
|
3
|
+
<% if page.first? %>
|
|
4
|
+
<span class="pagination__prev pagination__prev--disabled">Previous</span>
|
|
5
|
+
<% else %>
|
|
6
|
+
<%= link_to "Previous", url_for(request.query_parameters.merge(page: page.number - 1)), class: "pagination__prev" %>
|
|
7
|
+
<% end %>
|
|
8
|
+
|
|
9
|
+
<span class="pagination__info">
|
|
10
|
+
Page <%= page.number %> of <%= page.recordset.page_count %>
|
|
11
|
+
</span>
|
|
12
|
+
|
|
13
|
+
<% if page.last? %>
|
|
14
|
+
<span class="pagination__next pagination__next--disabled">Next</span>
|
|
15
|
+
<% else %>
|
|
16
|
+
<%= link_to "Next", url_for(request.query_parameters.merge(page: page.next_param)), class: "pagination__next" %>
|
|
17
|
+
<% end %>
|
|
18
|
+
</nav>
|
|
19
|
+
<% end %>
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
<div class="container">
|
|
2
|
+
<aside>
|
|
3
|
+
<ul>
|
|
4
|
+
<li><%= type_filter_link "All" %></li>
|
|
5
|
+
<li><%= type_filter_link "HTTP", "http" %></li>
|
|
6
|
+
<li><%= type_filter_link "Playwright", "playwright" %></li>
|
|
7
|
+
<li><%= type_filter_link "SMTP", "smtp" %></li>
|
|
8
|
+
<li><%= type_filter_link "Traceroute", "traceroute" %></li>
|
|
9
|
+
</ul>
|
|
10
|
+
</aside>
|
|
11
|
+
|
|
12
|
+
<main>
|
|
13
|
+
<h1>Probe Results</h1>
|
|
14
|
+
<p><%= results_summary(@page) %></p>
|
|
15
|
+
|
|
16
|
+
<div class="filters">
|
|
17
|
+
<%= form_with url: site_root_path, method: :get, data: { controller: "form" } do |f| %>
|
|
18
|
+
<%= hidden_field_tag :probe_type, params[:probe_type] if params[:probe_type].present? %>
|
|
19
|
+
<label for="probe_name">Name</label>
|
|
20
|
+
<%= select_tag :probe_name,
|
|
21
|
+
options_for_select([["All", ""]] + @probe_names, params[:probe_name]),
|
|
22
|
+
class: "input--select",
|
|
23
|
+
data: { action: "change->form#submit" } %>
|
|
24
|
+
<label for="status">Status</label>
|
|
25
|
+
<%= select_tag :status,
|
|
26
|
+
options_for_select([["All", ""]] + Upright::ProbeResult.statuses.keys.map { |s| [s.titleize, s] }, params[:status]),
|
|
27
|
+
class: "input--select",
|
|
28
|
+
data: { action: "change->form#submit" } %>
|
|
29
|
+
<% end %>
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
<div class="probe-results__chart"
|
|
33
|
+
data-controller="probe-results-chart"
|
|
34
|
+
data-probe-results-chart-results-value="<%= @chart_data.to_json %>">
|
|
35
|
+
</div>
|
|
36
|
+
|
|
37
|
+
<table>
|
|
38
|
+
<thead>
|
|
39
|
+
<tr>
|
|
40
|
+
<th>Probe Name</th>
|
|
41
|
+
<th>Status</th>
|
|
42
|
+
<th>Duration</th>
|
|
43
|
+
<th>Time</th>
|
|
44
|
+
<th>Artifacts</th>
|
|
45
|
+
</tr>
|
|
46
|
+
</thead>
|
|
47
|
+
<tbody data-controller="results-table" data-action="click->results-table#openArtifact">
|
|
48
|
+
<% @page.records.each do |result| %>
|
|
49
|
+
<tr>
|
|
50
|
+
<td class="probe__cell">
|
|
51
|
+
<span class="probe__icon"><%= probe_type_icon(result.probe_type) %></span>
|
|
52
|
+
<div class="probe__details">
|
|
53
|
+
<div class="probe__name"><%= result.probe_name %></div>
|
|
54
|
+
<% if result.probe_target.present? %>
|
|
55
|
+
<div class="probe__target"><%= result.probe_target %></div>
|
|
56
|
+
<% end %>
|
|
57
|
+
</div>
|
|
58
|
+
</td>
|
|
59
|
+
<td class="status--<%= result.status %>"><%= result.status.titleize %></td>
|
|
60
|
+
<td><%= result.duration ? "#{sprintf('%.3f', result.duration)}s" : "N/A" %></td>
|
|
61
|
+
<td title="<%= result.created_at.to_fs(:db) %>"><%= time_ago_in_words(result.created_at) %> ago</td>
|
|
62
|
+
<td>
|
|
63
|
+
<%= render partial: "upright/active_storage/attachments/attachment", collection: result.artifacts, as: :attachment, locals: { probe_result: result } %>
|
|
64
|
+
</td>
|
|
65
|
+
</tr>
|
|
66
|
+
<% end %>
|
|
67
|
+
</tbody>
|
|
68
|
+
</table>
|
|
69
|
+
|
|
70
|
+
<%= render "pagination", page: @page %>
|
|
71
|
+
</main>
|
|
72
|
+
</div>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<iframe src="<%= prometheus_proxy_path %>" class="service-frame"></iframe>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<main>
|
|
2
|
+
<h1>Sites</h1>
|
|
3
|
+
|
|
4
|
+
<div id="sites-map" data-controller="sites-map" data-sites-map-sites-value="<%= @sites.map(&:to_leaflet).to_json %>"></div>
|
|
5
|
+
|
|
6
|
+
<table>
|
|
7
|
+
<thead>
|
|
8
|
+
<tr>
|
|
9
|
+
<th>Host</th>
|
|
10
|
+
<th>City</th>
|
|
11
|
+
</tr>
|
|
12
|
+
</thead>
|
|
13
|
+
<tbody>
|
|
14
|
+
<% @sites.each do |site| %>
|
|
15
|
+
<tr>
|
|
16
|
+
<td><%= link_to site.host, site.url %></td>
|
|
17
|
+
<td><%= site.city %></td>
|
|
18
|
+
</tr>
|
|
19
|
+
<% end %>
|
|
20
|
+
</tbody>
|
|
21
|
+
</table>
|
|
22
|
+
</main>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"ignored_warnings": [
|
|
3
|
+
{
|
|
4
|
+
"warning_type": "Cross-Site Scripting",
|
|
5
|
+
"warning_code": 4,
|
|
6
|
+
"fingerprint": "842fc181b52f46f9a26f463b023f2567ff74c2b5b2b79c6004bab29f2e62203c",
|
|
7
|
+
"check_name": "LinkToHref",
|
|
8
|
+
"message": "Potentially unsafe model attribute in `link_to` href",
|
|
9
|
+
"file": "app/views/upright/sites/index.html.erb",
|
|
10
|
+
"line": 16,
|
|
11
|
+
"link": "https://brakemanscanner.org/docs/warning_types/link_to_href",
|
|
12
|
+
"code": "link_to(Upright::Site.new.host, Upright::Site.new.url)",
|
|
13
|
+
"render_path": [
|
|
14
|
+
{
|
|
15
|
+
"type": "controller",
|
|
16
|
+
"class": "Upright::SitesController",
|
|
17
|
+
"method": "index",
|
|
18
|
+
"line": 5,
|
|
19
|
+
"file": "app/controllers/upright/sites_controller.rb",
|
|
20
|
+
"rendered": {
|
|
21
|
+
"name": "upright/sites/index",
|
|
22
|
+
"file": "app/views/upright/sites/index.html.erb"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
],
|
|
26
|
+
"location": {
|
|
27
|
+
"type": "template",
|
|
28
|
+
"template": "upright/sites/index"
|
|
29
|
+
},
|
|
30
|
+
"user_input": "Upright::Site.new.url",
|
|
31
|
+
"confidence": "Weak",
|
|
32
|
+
"cwe_id": [
|
|
33
|
+
79
|
|
34
|
+
],
|
|
35
|
+
"note": "URL is generated from Rails route helpers, not user input"
|
|
36
|
+
}
|
|
37
|
+
],
|
|
38
|
+
"brakeman_version": "7.1.2"
|
|
39
|
+
}
|
data/config/ci.rb
ADDED
data/config/importmap.rb
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Pin engine JavaScript
|
|
2
|
+
pin "application", to: "upright/application.js"
|
|
3
|
+
pin "upright/application", to: "upright/application.js"
|
|
4
|
+
pin "upright/controllers/application", to: "upright/controllers/application.js"
|
|
5
|
+
pin "upright/controllers", to: "upright/controllers/index.js"
|
|
6
|
+
|
|
7
|
+
pin_all_from Upright::Engine.root.join("app/javascript/upright/controllers"),
|
|
8
|
+
under: "upright/controllers",
|
|
9
|
+
to: "upright/controllers"
|
|
10
|
+
|
|
11
|
+
# Dependencies
|
|
12
|
+
pin "@hotwired/turbo-rails", to: "https://cdn.jsdelivr.net/npm/@hotwired/turbo-rails@8.0.20/+esm"
|
|
13
|
+
pin "@hotwired/turbo", to: "https://cdn.jsdelivr.net/npm/@hotwired/turbo@8.0.20/+esm"
|
|
14
|
+
pin "@hotwired/stimulus", to: "https://cdn.jsdelivr.net/npm/@hotwired/stimulus@3.2.2/dist/stimulus.js"
|
|
15
|
+
pin "@hotwired/stimulus-loading", to: "stimulus-loading.js"
|
|
16
|
+
pin "@rails/actioncable/src", to: "https://cdn.jsdelivr.net/npm/@rails/actioncable@8.1.100/src/index.js"
|
|
17
|
+
pin "leaflet", to: "https://cdn.jsdelivr.net/npm/leaflet@1.9.4/dist/leaflet-src.esm.js"
|
|
18
|
+
pin "frappe-charts", to: "https://cdn.jsdelivr.net/npm/frappe-charts@1.6.2/dist/frappe-charts.min.esm.js"
|
data/config/routes.rb
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
Upright::Engine.routes.draw do
|
|
2
|
+
global_subdomain = ->(req) { Upright.configuration.global_subdomain == req.subdomain }
|
|
3
|
+
site_subdomain = ->(req) { Upright.configuration.site_subdomains.include?(req.subdomain) }
|
|
4
|
+
|
|
5
|
+
constraints global_subdomain do
|
|
6
|
+
root "sites#index", as: :admin_root
|
|
7
|
+
|
|
8
|
+
resource :session, only: [ :new, :create ], as: :admin_session
|
|
9
|
+
match "auth/:provider/callback", to: "sessions#create", as: :auth_callback, via: [ :get, :post ]
|
|
10
|
+
|
|
11
|
+
namespace :dashboards do
|
|
12
|
+
resource :uptime, only: :show
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
scope :framed do
|
|
16
|
+
resource :prometheus, only: :show, controller: :prometheus_proxy
|
|
17
|
+
resource :alertmanager, only: :show, controller: :alertmanager_proxy
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
post "prometheus/api/v1/otlp/v1/metrics", to: "prometheus_proxy#otlp"
|
|
21
|
+
|
|
22
|
+
match "prometheus(/*path)", to: "prometheus_proxy#proxy", via: :all, as: :prometheus_proxy
|
|
23
|
+
match "alertmanager(/*path)", to: "alertmanager_proxy#proxy", via: :all, as: :alertmanager_proxy
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
constraints site_subdomain do
|
|
27
|
+
root "probe_results#index", as: :site_root
|
|
28
|
+
|
|
29
|
+
resources :artifacts, only: :show, as: :site_artifacts
|
|
30
|
+
|
|
31
|
+
scope :framed do
|
|
32
|
+
resource :jobs, only: :show
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
mount MissionControl::Jobs::Engine, at: "/jobs"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Base routes (no subdomain constraint)
|
|
39
|
+
resource :session, only: :destroy
|
|
40
|
+
root "sites#index"
|
|
41
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
class CreateUprightProbeResults < ActiveRecord::Migration[8.0]
|
|
2
|
+
def change
|
|
3
|
+
create_table :upright_probe_results do |t|
|
|
4
|
+
t.string :probe_name
|
|
5
|
+
t.string :probe_type
|
|
6
|
+
t.string :probe_target
|
|
7
|
+
t.string :probe_service
|
|
8
|
+
t.decimal :duration
|
|
9
|
+
t.integer :status
|
|
10
|
+
|
|
11
|
+
t.timestamps
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
add_index :upright_probe_results, :probe_type
|
|
15
|
+
add_index :upright_probe_results, :probe_name
|
|
16
|
+
add_index :upright_probe_results, :status
|
|
17
|
+
add_index :upright_probe_results, :created_at
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
module Upright
|
|
2
|
+
module Generators
|
|
3
|
+
class InstallGenerator < Rails::Generators::Base
|
|
4
|
+
source_root File.expand_path("templates", __dir__)
|
|
5
|
+
|
|
6
|
+
desc "Install Upright engine into your application"
|
|
7
|
+
|
|
8
|
+
def copy_initializers
|
|
9
|
+
template "upright.rb", "config/initializers/upright.rb"
|
|
10
|
+
template "omniauth.rb", "config/initializers/omniauth.rb"
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def copy_sites_config
|
|
14
|
+
template "sites.yml", "config/sites.yml"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def create_probe_directories
|
|
18
|
+
empty_directory "probes"
|
|
19
|
+
empty_directory "probes/authenticators"
|
|
20
|
+
template "http_probes.yml", "probes/http_probes.yml"
|
|
21
|
+
template "smtp_probes.yml", "probes/smtp_probes.yml"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def copy_observability_configs
|
|
25
|
+
template "prometheus.yml", "config/prometheus/prometheus.yml"
|
|
26
|
+
template "upright.rules.yml", "config/prometheus/rules/upright.rules.yml"
|
|
27
|
+
template "alertmanager.yml", "config/alertmanager/alertmanager.yml"
|
|
28
|
+
template "otel_collector.yml", "config/otel_collector.yml"
|
|
29
|
+
template "development_prometheus.yml", "config/prometheus/development/prometheus.yml"
|
|
30
|
+
template "development_alertmanager.yml", "config/alertmanager/development/alertmanager.yml"
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def copy_dev_services
|
|
34
|
+
template "docker-compose.yml", "docker-compose.yml"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def copy_deploy_config
|
|
38
|
+
template "deploy.yml", "config/deploy.yml"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def copy_puma_config
|
|
42
|
+
template "puma.rb", "config/puma.rb"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def add_routes
|
|
46
|
+
route 'mount Upright::Engine => "/", as: :upright'
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def show_post_install_message
|
|
50
|
+
say ""
|
|
51
|
+
say "Upright has been installed!", :green
|
|
52
|
+
say ""
|
|
53
|
+
say "Next steps:"
|
|
54
|
+
say " 1. Run migrations: bin/rails db:migrate"
|
|
55
|
+
say " 2. Configure your servers in config/deploy.yml"
|
|
56
|
+
say " 3. Configure sites in config/sites.yml"
|
|
57
|
+
say " 4. Add probes in probes/*.yml"
|
|
58
|
+
say " 5. Set ADMIN_PASSWORD env var (default: upright)"
|
|
59
|
+
say ""
|
|
60
|
+
say "For production, review config/initializers/upright.rb and update:"
|
|
61
|
+
say " config.hostname = \"honcho-upright.com\""
|
|
62
|
+
say ""
|
|
63
|
+
say "Start dev services (Prometheus, Alertmanager, Playwright):"
|
|
64
|
+
say " docker compose up -d"
|
|
65
|
+
say ""
|
|
66
|
+
say "Start the development server with: bin/dev"
|
|
67
|
+
say ""
|
|
68
|
+
say "Then access your app at:"
|
|
69
|
+
say " http://app.#{app_name}.localhost:3000"
|
|
70
|
+
say ""
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
private
|
|
74
|
+
def app_name
|
|
75
|
+
Rails.application.class.module_parent_name.underscore.dasherize
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def app_domain
|
|
79
|
+
"#{app_name}.example.com"
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
global:
|
|
2
|
+
resolve_timeout: 5m
|
|
3
|
+
|
|
4
|
+
route:
|
|
5
|
+
receiver: default
|
|
6
|
+
group_by: ['alertname', 'probe_name']
|
|
7
|
+
group_wait: 30s
|
|
8
|
+
group_interval: 5m
|
|
9
|
+
repeat_interval: 4h
|
|
10
|
+
|
|
11
|
+
receivers:
|
|
12
|
+
- name: default
|
|
13
|
+
# webhook_configs:
|
|
14
|
+
# - url: 'http://your-webhook-url'
|