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 "omniauth"
|
|
2
|
+
|
|
3
|
+
module OmniAuth
|
|
4
|
+
module Strategies
|
|
5
|
+
class StaticCredentials
|
|
6
|
+
include OmniAuth::Strategy
|
|
7
|
+
|
|
8
|
+
option :name, "static_credentials"
|
|
9
|
+
option :title, "Sign In"
|
|
10
|
+
option :credentials, {}
|
|
11
|
+
|
|
12
|
+
def request_phase
|
|
13
|
+
OmniAuth::Form.build(title: options.title, url: callback_path) do
|
|
14
|
+
text_field "Username", "username"
|
|
15
|
+
password_field "Password", "password"
|
|
16
|
+
end.to_response
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def callback_phase
|
|
20
|
+
if valid_credentials?
|
|
21
|
+
super
|
|
22
|
+
else
|
|
23
|
+
fail!(:invalid_credentials)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
uid { username }
|
|
28
|
+
|
|
29
|
+
info do
|
|
30
|
+
{ name: username, email: "#{username}@localhost" }
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
protected
|
|
34
|
+
|
|
35
|
+
def valid_credentials?
|
|
36
|
+
return false if username.blank? || password.blank?
|
|
37
|
+
|
|
38
|
+
configured_credentials.any? do |user, pass|
|
|
39
|
+
ActiveSupport::SecurityUtils.secure_compare(username, user.to_s) &&
|
|
40
|
+
ActiveSupport::SecurityUtils.secure_compare(password, pass.to_s)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def configured_credentials
|
|
45
|
+
options.credentials || {}
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def username
|
|
49
|
+
request.params["username"]
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def password
|
|
53
|
+
request.params["password"]
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
class Upright::Configuration
|
|
2
|
+
# Global subdomain is always "app" - this is documented behavior
|
|
3
|
+
GLOBAL_SUBDOMAIN = "app"
|
|
4
|
+
|
|
5
|
+
# Core settings
|
|
6
|
+
attr_accessor :service_name
|
|
7
|
+
attr_accessor :user_agent
|
|
8
|
+
attr_accessor :default_timeout
|
|
9
|
+
|
|
10
|
+
# Storage paths
|
|
11
|
+
attr_accessor :prometheus_dir
|
|
12
|
+
attr_accessor :video_storage_dir
|
|
13
|
+
attr_accessor :storage_state_dir
|
|
14
|
+
attr_accessor :frozen_record_path
|
|
15
|
+
|
|
16
|
+
# Probe and authenticator paths (for auto-loading app-specific code)
|
|
17
|
+
attr_writer :probes_path
|
|
18
|
+
attr_writer :authenticators_path
|
|
19
|
+
|
|
20
|
+
# Playwright
|
|
21
|
+
attr_accessor :playwright_server_url
|
|
22
|
+
|
|
23
|
+
# Authentication
|
|
24
|
+
attr_accessor :auth_provider
|
|
25
|
+
attr_accessor :auth_options
|
|
26
|
+
|
|
27
|
+
# Observability
|
|
28
|
+
attr_accessor :otel_endpoint
|
|
29
|
+
attr_accessor :prometheus_url
|
|
30
|
+
attr_accessor :alert_webhook_url
|
|
31
|
+
|
|
32
|
+
def initialize
|
|
33
|
+
@service_name = "upright"
|
|
34
|
+
@user_agent = "Upright/1.0"
|
|
35
|
+
@default_timeout = 10.seconds
|
|
36
|
+
|
|
37
|
+
@prometheus_dir = nil
|
|
38
|
+
@video_storage_dir = nil
|
|
39
|
+
@storage_state_dir = nil
|
|
40
|
+
@frozen_record_path = nil
|
|
41
|
+
@probes_path = nil
|
|
42
|
+
@authenticators_path = nil
|
|
43
|
+
|
|
44
|
+
@playwright_server_url = ENV["PLAYWRIGHT_SERVER_URL"]
|
|
45
|
+
@otel_endpoint = ENV["OTEL_EXPORTER_OTLP_ENDPOINT"]
|
|
46
|
+
|
|
47
|
+
@auth_provider = :static_credentials
|
|
48
|
+
@auth_options = {}
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def global_subdomain
|
|
52
|
+
GLOBAL_SUBDOMAIN
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def site_subdomains
|
|
56
|
+
Upright.sites.map { |site| site.code.to_s }
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def prometheus_dir
|
|
60
|
+
@prometheus_dir || Rails.root.join("tmp", "prometheus")
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def video_storage_dir
|
|
64
|
+
@video_storage_dir || Rails.root.join("storage", "playwright_videos")
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def storage_state_dir
|
|
68
|
+
@storage_state_dir || Rails.root.join("storage", "playwright_storage_states")
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def frozen_record_path
|
|
72
|
+
@frozen_record_path || Rails.root.join("config", "probes")
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def probes_path
|
|
76
|
+
@probes_path || Rails.root.join("probes")
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def authenticators_path
|
|
80
|
+
@authenticators_path || Rails.root.join("probes", "authenticators")
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def hostname=(value)
|
|
84
|
+
@hostname = value
|
|
85
|
+
configure_allowed_hosts
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def hostname
|
|
89
|
+
@hostname
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def default_url_options
|
|
93
|
+
if Rails.env.production?
|
|
94
|
+
{ protocol: "https", host: "#{global_subdomain}.#{hostname}", domain: hostname }
|
|
95
|
+
else
|
|
96
|
+
{ protocol: "http", host: "#{global_subdomain}.#{hostname}", port: ENV.fetch("PORT", 3000).to_i, domain: hostname }
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
private
|
|
101
|
+
def configure_allowed_hosts
|
|
102
|
+
port_suffix = Rails.env.local? ? "(:\\d+)?" : ""
|
|
103
|
+
Rails.application.config.hosts = [ /.*\.#{Regexp.escape(hostname)}#{port_suffix}/, /#{Regexp.escape(hostname)}#{port_suffix}/ ]
|
|
104
|
+
Rails.application.config.action_dispatch.tld_length = 1
|
|
105
|
+
end
|
|
106
|
+
end
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
class Upright::Engine < ::Rails::Engine
|
|
2
|
+
isolate_namespace Upright
|
|
3
|
+
|
|
4
|
+
# Add concerns to autoload paths
|
|
5
|
+
config.autoload_paths << root.join("app/models/concerns")
|
|
6
|
+
|
|
7
|
+
# Session store configuration
|
|
8
|
+
initializer "upright.session_store", before: :load_config_initializers do |app|
|
|
9
|
+
app.config.session_store :cookie_store,
|
|
10
|
+
key: "_upright_session",
|
|
11
|
+
domain: :all,
|
|
12
|
+
same_site: :lax,
|
|
13
|
+
secure: Rails.env.production?,
|
|
14
|
+
expire_after: 24.hours
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
config.after_initialize do
|
|
18
|
+
url_options = Upright.configuration.default_url_options
|
|
19
|
+
Rails.application.routes.default_url_options = url_options
|
|
20
|
+
Upright::Engine.routes.default_url_options = url_options
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
initializer "upright.solid_queue", before: :set_configs_for_current_railties do |app|
|
|
24
|
+
unless Rails.env.test?
|
|
25
|
+
app.config.active_job.queue_adapter = :solid_queue
|
|
26
|
+
app.config.solid_queue.connects_to = { database: { writing: :queue, reading: :queue } }
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Configure Mission Control to use engine's authenticated controller
|
|
31
|
+
initializer "upright.mission_control" do
|
|
32
|
+
MissionControl::Jobs.base_controller_class = "Upright::ApplicationController"
|
|
33
|
+
MissionControl::Jobs.http_basic_auth_enabled = false
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Configure acronym inflections for autoloading
|
|
37
|
+
initializer "upright.inflections", before: :bootstrap_hook do
|
|
38
|
+
ActiveSupport::Inflector.inflections(:en) do |inflect|
|
|
39
|
+
inflect.acronym "HTTP"
|
|
40
|
+
inflect.acronym "SMTP"
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
config.generators do |g|
|
|
45
|
+
g.test_framework :minitest
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
initializer "upright.assets" do |app|
|
|
49
|
+
app.config.assets.paths << root.join("app/javascript")
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
initializer "upright.importmap", before: "importmap" do |app|
|
|
53
|
+
if defined?(Importmap::Engine)
|
|
54
|
+
app.config.importmap.paths << root.join("config/importmap.rb")
|
|
55
|
+
app.config.importmap.cache_sweepers << root.join("app/javascript")
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
initializer "upright.frozen_record" do
|
|
60
|
+
FrozenRecord::Base.base_path = Upright.configuration.frozen_record_path
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
initializer "upright.yabeda" do
|
|
64
|
+
Upright::Metrics.configure
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
initializer "upright.opentelemetry" do
|
|
68
|
+
Upright::Tracing.configure
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Start metrics server for Solid Queue worker process
|
|
72
|
+
initializer "upright.solid_queue_metrics" do
|
|
73
|
+
SolidQueue.on_start do
|
|
74
|
+
ENV["PROMETHEUS_EXPORTER_PORT"] ||= Rails.env.local? ? "9395" : "9394"
|
|
75
|
+
ENV["PROMETHEUS_EXPORTER_LOG_REQUESTS"] = "false"
|
|
76
|
+
Yabeda::Prometheus::Exporter.start_metrics_server!
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
initializer "upright.duration_extension" do
|
|
81
|
+
ActiveSupport::Duration.class_eval do
|
|
82
|
+
def in_ms
|
|
83
|
+
(to_f * 1000).to_i
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Silence Ethon's verbose debug output to stdout
|
|
89
|
+
# By default, Ethon's debug callback prints curl verbose messages which pollutes logs
|
|
90
|
+
initializer "upright.ethon" do
|
|
91
|
+
Ethon::Easy::Callbacks.module_eval do
|
|
92
|
+
def debug_callback
|
|
93
|
+
@debug_callback ||= proc { |handle, type, data, size, udata|
|
|
94
|
+
message = data.read_string(size)
|
|
95
|
+
@debug_info.add(type, message)
|
|
96
|
+
0
|
|
97
|
+
}
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Allow host app to override views
|
|
103
|
+
config.to_prepare do
|
|
104
|
+
Upright::ApplicationController.helper Rails.application.helpers
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Print available URLs in development
|
|
108
|
+
config.after_initialize do
|
|
109
|
+
if Rails.env.development? && defined?(Rails::Server)
|
|
110
|
+
url_options = Upright.configuration.default_url_options
|
|
111
|
+
hostname = url_options[:domain]
|
|
112
|
+
port = url_options[:port]
|
|
113
|
+
protocol = url_options[:protocol]
|
|
114
|
+
|
|
115
|
+
puts ""
|
|
116
|
+
puts "Upright is running at:"
|
|
117
|
+
puts " Global: #{protocol}://#{Upright.configuration.global_subdomain}.#{hostname}:#{port}"
|
|
118
|
+
Upright.sites.each do |site|
|
|
119
|
+
puts " #{site.city || site.code}: #{protocol}://#{site.code}.#{hostname}:#{port}"
|
|
120
|
+
end
|
|
121
|
+
puts ""
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Auto-load Playwright probes and authenticators from configured paths
|
|
126
|
+
config.after_initialize do
|
|
127
|
+
# Define namespaces for app-specific probes and authenticators
|
|
128
|
+
module ::Probes
|
|
129
|
+
module Playwright
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
module ::Playwright
|
|
134
|
+
module Authenticator
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
probes_path = Upright.configuration.probes_path
|
|
139
|
+
if probes_path && Dir.exist?(probes_path)
|
|
140
|
+
Dir[probes_path.join("*_probe.rb")].sort.each { |file| require file }
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
authenticators_path = Upright.configuration.authenticators_path
|
|
144
|
+
if authenticators_path && Dir.exist?(authenticators_path)
|
|
145
|
+
Dir[authenticators_path.join("*.rb")].sort.each { |file| require file }
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Add engine migrations to host app
|
|
150
|
+
initializer "upright.migrations" do |app|
|
|
151
|
+
unless app.root.to_s == root.join("test/dummy").to_s
|
|
152
|
+
config.paths["db/migrate"].expanded.each do |expanded_path|
|
|
153
|
+
app.config.paths["db/migrate"] << expanded_path
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
require "prometheus/client"
|
|
2
|
+
require "prometheus/client/data_stores/direct_file_store"
|
|
3
|
+
require "yabeda"
|
|
4
|
+
|
|
5
|
+
module Upright::Metrics
|
|
6
|
+
class << self
|
|
7
|
+
def configure
|
|
8
|
+
setup_prometheus_store unless Rails.env.test?
|
|
9
|
+
define_metrics
|
|
10
|
+
Yabeda.configure!
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def probe_duration_seconds
|
|
14
|
+
Yabeda.upright_probe_duration_seconds
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def probe_up
|
|
18
|
+
Yabeda.upright_probe_up
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def http_response_status
|
|
22
|
+
Yabeda.upright_http_response_status
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
def setup_prometheus_store
|
|
27
|
+
prometheus_dir = Upright.configuration.prometheus_dir
|
|
28
|
+
FileUtils.mkdir_p(prometheus_dir)
|
|
29
|
+
|
|
30
|
+
Prometheus::Client.config.data_store = Prometheus::Client::DataStores::DirectFileStore.new(dir: prometheus_dir.to_s)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def define_metrics
|
|
34
|
+
current_site = Upright.current_site
|
|
35
|
+
|
|
36
|
+
Yabeda.configure do
|
|
37
|
+
default_tag :site_code, current_site&.code
|
|
38
|
+
default_tag :site_city, current_site&.city
|
|
39
|
+
default_tag :site_country, current_site&.country
|
|
40
|
+
default_tag :site_geohash, current_site&.geohash
|
|
41
|
+
default_tag :site_provider, current_site&.provider
|
|
42
|
+
|
|
43
|
+
group :upright do
|
|
44
|
+
gauge :probe_duration_seconds,
|
|
45
|
+
comment: "Duration of each probe",
|
|
46
|
+
aggregation: :max,
|
|
47
|
+
tags: %i[type name probe_target probe_service status]
|
|
48
|
+
|
|
49
|
+
gauge :probe_up,
|
|
50
|
+
comment: "Probe status (1 = up, 0 = down)",
|
|
51
|
+
aggregation: :most_recent,
|
|
52
|
+
tags: %i[type name probe_target probe_service]
|
|
53
|
+
|
|
54
|
+
gauge :http_response_status,
|
|
55
|
+
comment: "HTTP response status code",
|
|
56
|
+
aggregation: :max,
|
|
57
|
+
tags: %i[name probe_target probe_service]
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
(() => {
|
|
2
|
+
const metrics = {};
|
|
3
|
+
|
|
4
|
+
const [navigation] = performance.getEntriesByType("navigation");
|
|
5
|
+
if (navigation) {
|
|
6
|
+
metrics.ttfb = Math.round(navigation.responseStart);
|
|
7
|
+
metrics.dns = Math.round(navigation.domainLookupEnd - navigation.domainLookupStart);
|
|
8
|
+
metrics.tcp = Math.round(navigation.connectEnd - navigation.connectStart);
|
|
9
|
+
metrics.request = Math.round(navigation.responseStart - navigation.requestStart);
|
|
10
|
+
metrics.response = Math.round(navigation.responseEnd - navigation.responseStart);
|
|
11
|
+
metrics.domInteractive = Math.round(navigation.domInteractive);
|
|
12
|
+
metrics.domContentLoaded = Math.round(navigation.domContentLoadedEventEnd);
|
|
13
|
+
metrics.domComplete = Math.round(navigation.domComplete);
|
|
14
|
+
metrics.loadComplete = Math.round(navigation.loadEventEnd);
|
|
15
|
+
metrics.transferSize = navigation.transferSize;
|
|
16
|
+
metrics.encodedBodySize = navigation.encodedBodySize;
|
|
17
|
+
metrics.decodedBodySize = navigation.decodedBodySize;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const [fcp] = performance.getEntriesByType("paint").filter(e => e.name === "first-contentful-paint");
|
|
21
|
+
if (fcp) {
|
|
22
|
+
metrics.fcp = Math.round(fcp.startTime);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const lcpEntries = performance.getEntriesByType("largest-contentful-paint");
|
|
26
|
+
if (lcpEntries.length > 0) {
|
|
27
|
+
metrics.lcp = Math.round(lcpEntries[lcpEntries.length - 1].startTime);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const [fid] = performance.getEntriesByType("first-input");
|
|
31
|
+
if (fid) {
|
|
32
|
+
metrics.fid = Math.round(fid.processingStart - fid.startTime);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return metrics;
|
|
36
|
+
})();
|
data/lib/upright/site.rb
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
require "geohash_ruby"
|
|
2
|
+
|
|
3
|
+
module Upright
|
|
4
|
+
class Site
|
|
5
|
+
attr_reader :code, :city, :country, :geohash, :stagger_index
|
|
6
|
+
|
|
7
|
+
def initialize(code:, city: nil, country: nil, geohash: nil, provider: nil, stagger_index: 0)
|
|
8
|
+
@code = code.to_sym
|
|
9
|
+
@city = city
|
|
10
|
+
@country = country
|
|
11
|
+
@geohash = geohash
|
|
12
|
+
@provider = provider
|
|
13
|
+
@stagger_index = stagger_index
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def host
|
|
17
|
+
URI.parse(url).host
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def provider
|
|
21
|
+
@provider.to_s.inquiry
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def default_timeout
|
|
25
|
+
Upright.configuration.default_timeout
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def latitude
|
|
29
|
+
coordinates.first
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def longitude
|
|
33
|
+
coordinates.last
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def url
|
|
37
|
+
Upright::Engine.routes.url_helpers.root_url(subdomain: code)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def to_leaflet
|
|
41
|
+
{ hostname: host, city: city, lat: latitude, lon: longitude, url: url }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
def coordinates
|
|
46
|
+
@coordinates ||= Geohash.decode(geohash).first
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
module Upright::Tracing
|
|
2
|
+
class << self
|
|
3
|
+
def configure
|
|
4
|
+
current_site = Upright.current_site
|
|
5
|
+
|
|
6
|
+
OpenTelemetry::SDK.configure do |c|
|
|
7
|
+
c.service_name = Upright.configuration.service_name
|
|
8
|
+
c.service_version = Upright::VERSION
|
|
9
|
+
|
|
10
|
+
c.resource = OpenTelemetry::SDK::Resources::Resource.create(
|
|
11
|
+
resource_attributes(current_site)
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
# Use OTLP exporter if endpoint is configured
|
|
15
|
+
if Upright.configuration.otel_endpoint
|
|
16
|
+
c.add_span_processor(
|
|
17
|
+
OpenTelemetry::SDK::Trace::Export::BatchSpanProcessor.new(
|
|
18
|
+
OpenTelemetry::Exporter::OTLP::Exporter.new(
|
|
19
|
+
endpoint: Upright.configuration.otel_endpoint
|
|
20
|
+
)
|
|
21
|
+
)
|
|
22
|
+
)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
c.use_all
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def tracer
|
|
30
|
+
OpenTelemetry.tracer_provider.tracer(Upright.configuration.service_name, Upright::VERSION)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def with_span(name, attributes: {}, &block)
|
|
34
|
+
tracer.in_span(name, attributes: attributes, &block)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
def resource_attributes(site)
|
|
39
|
+
{
|
|
40
|
+
"deployment.environment" => Rails.env.to_s,
|
|
41
|
+
"site.code" => site.code.to_s,
|
|
42
|
+
"site.city" => site.city.to_s,
|
|
43
|
+
"site.country" => site.country.to_s,
|
|
44
|
+
"site.geohash" => site.geohash.to_s,
|
|
45
|
+
"site.provider" => site.provider.to_s
|
|
46
|
+
}.compact_blank
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
data/lib/upright.rb
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
require "frozen_record"
|
|
2
|
+
require "prometheus/api_client"
|
|
3
|
+
require "opentelemetry-sdk"
|
|
4
|
+
require "opentelemetry-exporter-otlp"
|
|
5
|
+
require "typhoeus"
|
|
6
|
+
require "solid_queue"
|
|
7
|
+
require "mission_control/jobs"
|
|
8
|
+
require "omniauth"
|
|
9
|
+
require "omniauth_openid_connect"
|
|
10
|
+
require "omniauth/rails_csrf_protection"
|
|
11
|
+
require "omniauth/strategies/static_credentials"
|
|
12
|
+
require "propshaft"
|
|
13
|
+
require "importmap-rails"
|
|
14
|
+
require "turbo-rails"
|
|
15
|
+
require "stimulus-rails"
|
|
16
|
+
require "geared_pagination"
|
|
17
|
+
require "geohash_ruby"
|
|
18
|
+
require "yabeda/prometheus"
|
|
19
|
+
require "yabeda/puma/plugin"
|
|
20
|
+
|
|
21
|
+
require "upright/version"
|
|
22
|
+
require "upright/configuration"
|
|
23
|
+
require "upright/site"
|
|
24
|
+
require "upright/metrics"
|
|
25
|
+
require "upright/tracing"
|
|
26
|
+
require "upright/engine"
|
|
27
|
+
|
|
28
|
+
module Upright
|
|
29
|
+
class ConfigurationError < StandardError; end
|
|
30
|
+
|
|
31
|
+
class << self
|
|
32
|
+
def configuration
|
|
33
|
+
@configuration ||= Configuration.new
|
|
34
|
+
end
|
|
35
|
+
alias_method :config, :configuration
|
|
36
|
+
|
|
37
|
+
def configure
|
|
38
|
+
yield(configuration)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def sites
|
|
42
|
+
@sites ||= load_sites
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def find_site(code)
|
|
46
|
+
sites.find { |site| site.code.to_s == code.to_s }
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def current_site
|
|
50
|
+
find_site(ENV["SITE_SUBDOMAIN"]) || sites.first
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
def load_sites
|
|
55
|
+
sites_config_path = Rails.root.join("config/sites.yml")
|
|
56
|
+
|
|
57
|
+
if sites_config_path.exist?
|
|
58
|
+
config = Rails.application.config_for(:sites)
|
|
59
|
+
|
|
60
|
+
config[:sites].map.with_index do |site_config, index|
|
|
61
|
+
Site.new(stagger_index: index, **site_config)
|
|
62
|
+
end
|
|
63
|
+
else
|
|
64
|
+
[]
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|