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.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.md +2 -3
  3. data/README.md +166 -60
  4. data/app/assets/stylesheets/upright/dashboard.css +65 -193
  5. data/app/assets/stylesheets/upright/tables.css +60 -0
  6. data/app/assets/stylesheets/upright/uptime-bars.css +13 -50
  7. data/app/controllers/upright/alertmanager_proxy_controller.rb +1 -1
  8. data/app/controllers/upright/dashboards/probe_statuses_controller.rb +7 -0
  9. data/app/controllers/upright/probe_results_controller.rb +1 -0
  10. data/app/controllers/upright/sessions_controller.rb +1 -0
  11. data/app/helpers/upright/application_helper.rb +10 -0
  12. data/app/helpers/upright/dashboards_helper.rb +12 -0
  13. data/app/helpers/upright/probe_results_helper.rb +3 -10
  14. data/app/javascript/upright/controllers/auto_refresh_controller.js +16 -0
  15. data/app/models/concerns/upright/playwright/form_authentication.rb +0 -3
  16. data/app/models/concerns/upright/playwright/lifecycle.rb +3 -2
  17. data/app/models/concerns/upright/probe_result/stale_cleanup.rb +23 -0
  18. data/app/models/concerns/upright/probeable.rb +7 -1
  19. data/app/models/upright/http/request.rb +1 -1
  20. data/app/models/upright/playwright/storage_state.rb +12 -3
  21. data/app/models/upright/probe_result.rb +6 -1
  22. data/app/models/upright/probes/http_probe.rb +6 -2
  23. data/app/models/upright/probes/status/probe.rb +24 -0
  24. data/app/models/upright/probes/status/site_status.rb +71 -0
  25. data/app/models/upright/probes/status.rb +54 -0
  26. data/app/models/upright/probes/uptime.rb +4 -4
  27. data/app/models/upright/traceroute/ip_metadata_lookup.rb +1 -2
  28. data/app/views/layouts/upright/_header.html.erb +2 -1
  29. data/app/views/upright/dashboards/_uptime_bars.html.erb +2 -2
  30. data/app/views/upright/dashboards/_uptime_probe_row.html.erb +7 -5
  31. data/app/views/upright/dashboards/probe_statuses/_matrix.html.erb +48 -0
  32. data/app/views/upright/dashboards/probe_statuses/show.html.erb +17 -0
  33. data/app/views/upright/dashboards/uptimes/show.html.erb +1 -1
  34. data/app/views/upright/probe_results/index.html.erb +7 -4
  35. data/app/views/upright/sites/index.html.erb +1 -1
  36. data/config/ci.rb +2 -0
  37. data/config/credentials/development.key +1 -0
  38. data/config/credentials/test.key +1 -0
  39. data/config/routes.rb +1 -0
  40. data/lib/generators/upright/install/install_generator.rb +52 -2
  41. data/lib/generators/upright/install/templates/http_probes.yml +1 -0
  42. data/lib/generators/upright/install/templates/recurring.yml +25 -0
  43. data/lib/generators/upright/install/templates/smtp_probes.yml +3 -0
  44. data/lib/generators/upright/install/templates/traceroute_probes.yml +12 -0
  45. data/lib/generators/upright/install/templates/upright.rb +4 -1
  46. data/lib/generators/upright/install/templates/upright.rules.yml +5 -5
  47. data/lib/upright/configuration.rb +14 -0
  48. data/lib/upright/engine.rb +7 -0
  49. data/lib/upright/geohash.rb +46 -0
  50. data/lib/upright/metrics.rb +2 -2
  51. data/lib/upright/probe_type_registry.rb +33 -0
  52. data/lib/upright/site.rb +1 -3
  53. data/lib/upright/version.rb +1 -1
  54. data/lib/upright.rb +6 -1
  55. metadata +17 -17
@@ -60,4 +60,64 @@
60
60
  padding: calc(var(--block-space) * 0.4) calc(var(--block-space) * 0.4);
61
61
  }
62
62
  }
63
+
64
+ /* Div-based data tables (shared by dashboard views) */
65
+ .data-table {
66
+ background: oklch(var(--lch-ink-lightest) / 85%);
67
+ border-radius: 0.25rem;
68
+ box-shadow: var(--shadow);
69
+ }
70
+
71
+ .data-table__header {
72
+ background: var(--color-ink-lighter);
73
+ font-size: var(--text-x-small);
74
+ font-weight: 500;
75
+ letter-spacing: 0.08em;
76
+ text-transform: uppercase;
77
+ }
78
+
79
+ .data-table__row {
80
+ border-top: 1px solid var(--color-ink-lighter);
81
+ transition: background-color 100ms;
82
+
83
+ &:hover {
84
+ background-color: var(--color-canvas);
85
+ }
86
+ }
87
+
88
+ .data-table__probe {
89
+ align-items: center;
90
+ color: inherit;
91
+ display: flex;
92
+ gap: 0.75em;
93
+ min-width: 0;
94
+ text-decoration: none;
95
+
96
+ &:hover {
97
+ color: var(--color-link);
98
+ text-decoration: none;
99
+ }
100
+ }
101
+
102
+ .data-table__probe-name {
103
+ overflow: hidden;
104
+ text-overflow: ellipsis;
105
+ white-space: nowrap;
106
+ }
107
+
108
+ /* Utilities */
109
+ .text-center {
110
+ text-align: center;
111
+ }
112
+
113
+ .sticky-left {
114
+ background: inherit;
115
+ left: 0;
116
+ position: sticky;
117
+ z-index: 1;
118
+ }
119
+
120
+ .scrollable-x {
121
+ overflow-x: auto;
122
+ }
63
123
  }
@@ -1,56 +1,10 @@
1
1
  @layer components {
2
- .uptime-bars {
3
- display: flex;
4
- flex-direction: column;
5
- }
6
-
7
- .uptime-bars__header {
8
- background: var(--color-ink-lighter);
9
- display: grid;
10
- font-size: var(--text-x-small);
11
- font-weight: 500;
12
- gap: var(--block-space);
13
- grid-template-columns: minmax(200px, 1fr) 1fr auto;
14
- letter-spacing: 0.04em;
15
- padding: calc(var(--block-space) * 0.75) var(--block-space);
16
- text-transform: uppercase;
17
- }
18
-
19
-
2
+ .uptime-bars__header,
20
3
  .uptime-bars__row {
21
- border-top: 1px solid var(--color-ink-lighter);
22
4
  display: grid;
23
5
  gap: var(--block-space);
24
6
  grid-template-columns: minmax(200px, 1fr) 1fr auto;
25
7
  padding: calc(var(--block-space) * 0.75) var(--block-space);
26
- transition: background-color 100ms;
27
- }
28
-
29
- .uptime-bars__row:hover {
30
- background: var(--color-canvas);
31
- }
32
-
33
- .uptime-bars__probe {
34
- align-items: center;
35
- color: inherit;
36
- display: flex;
37
- gap: 0.75em;
38
- min-width: 0;
39
-
40
- &:hover {
41
- color: var(--color-link);
42
- text-decoration: none;
43
- }
44
- }
45
-
46
- .uptime-bars__probe .probe__badge {
47
- flex-shrink: 0;
48
- }
49
-
50
- .uptime-bars__probe .probe__name {
51
- overflow: hidden;
52
- text-overflow: ellipsis;
53
- white-space: nowrap;
54
8
  }
55
9
 
56
10
  .uptime-bars__days {
@@ -83,10 +37,9 @@
83
37
  color: var(--color-negative);
84
38
  }
85
39
 
86
- /* Individual uptime bar (separate block) */
40
+ /* Individual uptime bar */
87
41
  .uptime-bar {
88
42
  border-radius: 2px;
89
- cursor: default;
90
43
  flex: 1;
91
44
  height: 24px;
92
45
  max-width: 12px;
@@ -94,6 +47,16 @@
94
47
  transition: transform 100ms, filter 100ms;
95
48
  }
96
49
 
50
+ a.uptime-bar {
51
+ cursor: pointer;
52
+ display: block;
53
+ text-decoration: none;
54
+ }
55
+
56
+ div.uptime-bar {
57
+ cursor: default;
58
+ }
59
+
97
60
  .uptime-bar:hover {
98
61
  filter: brightness(1.1);
99
62
  transform: scaleY(1.15);
@@ -131,7 +94,7 @@
131
94
  gap: calc(var(--block-space) * 0.5);
132
95
  }
133
96
 
134
- .uptime-bars__probe .probe__badge {
97
+ .data-table__probe .probe__badge {
135
98
  display: none;
136
99
  }
137
100
  }
@@ -5,7 +5,7 @@ class Upright::AlertmanagerProxyController < Upright::ApplicationController
5
5
  end
6
6
 
7
7
  def proxy
8
- proxy_to_alertmanager request.fullpath.delete_prefix("/alertmanager")
8
+ proxy_to_alertmanager request.fullpath.delete_prefix("/alertmanager"), body: request.body&.read
9
9
  end
10
10
 
11
11
  private
@@ -0,0 +1,7 @@
1
+ class Upright::Dashboards::ProbeStatusesController < Upright::ApplicationController
2
+ def show
3
+ @probe_type = params.fetch(:probe_type, :http)
4
+ @probes = Upright::Probes::Status.for_type(@probe_type)
5
+ @sites = Upright.sites
6
+ end
7
+ end
@@ -12,6 +12,7 @@ class Upright::ProbeResultsController < Upright::ApplicationController
12
12
  .by_type(params[:probe_type])
13
13
  .by_status(params[:status])
14
14
  .by_name(params[:probe_name])
15
+ .by_date(params[:date])
15
16
  .with_attached_artifacts
16
17
  end
17
18
  end
@@ -8,6 +8,7 @@ class Upright::SessionsController < Upright::ApplicationController
8
8
  end
9
9
 
10
10
  def create
11
+ reset_session
11
12
  user = Upright::User.from_omniauth(request.env["omniauth.auth"])
12
13
  session[:user_info] = { email: user.email, name: user.name }
13
14
  redirect_to upright.root_path
@@ -3,9 +3,19 @@ module Upright::ApplicationHelper
3
3
  Upright::Current.site || Upright.sites.first
4
4
  end
5
5
 
6
+ def site_name(site)
7
+ "#{country_flag(site.country)} #{site.city}"
8
+ end
9
+
6
10
  def upright_stylesheet_link_tag(**options)
7
11
  Upright::Engine.root.join("app/assets/stylesheets/upright").glob("*.css")
8
12
  .map { |f| "upright/#{f.basename('.css')}" }.sort
9
13
  .then { |stylesheets| stylesheet_link_tag(*stylesheets, **options) }
10
14
  end
15
+
16
+ private
17
+
18
+ def country_flag(country_code)
19
+ country_code&.upcase&.gsub(/[A-Z]/) { |c| (c.ord + 0x1F1A5).chr(Encoding::UTF_8) }
20
+ end
11
21
  end
@@ -28,4 +28,16 @@ module Upright::DashboardsHelper
28
28
  mins.zero? ? "#{hours}h" : "#{hours}h #{mins}m"
29
29
  end
30
30
  end
31
+
32
+ def probe_status_css_class(status)
33
+ if status.nil?
34
+ "probe-status-cell--unknown"
35
+ elsif status.stale?
36
+ "probe-status-cell--stale"
37
+ elsif status.up?
38
+ "probe-status-cell--up"
39
+ else
40
+ "probe-status-cell--down"
41
+ end
42
+ end
31
43
  end
@@ -1,15 +1,7 @@
1
1
  module Upright::ProbeResultsHelper
2
- PROBE_TYPE_ICONS = {
3
- http: "🌐",
4
- playwright: "🎭",
5
- ping: "📶",
6
- smtp: "✉️",
7
- traceroute: "🛤️"
8
- }
9
-
10
2
  def probe_type_icon(probe_type)
11
- icon = PROBE_TYPE_ICONS.fetch(probe_type.to_s.downcase.to_sym)
12
- content_tag(:span, icon, title: probe_type.titleize)
3
+ registered = Upright.probe_types.find(probe_type)
4
+ content_tag(:span, registered.icon, title: registered.name)
13
5
  end
14
6
 
15
7
  def type_filter_link(label, probe_type = nil)
@@ -44,6 +36,7 @@ module Upright::ProbeResultsHelper
44
36
  parts << "for #{params[:probe_type].titleize} probes" if params[:probe_type].present?
45
37
  parts << "named #{params[:probe_name]}" if params[:probe_name].present?
46
38
  parts << "with status #{params[:status]}" if params[:status].present?
39
+ parts << "on #{Date.parse(params[:date]).to_fs(:long)}" if params[:date].present?
47
40
  parts.join(" ")
48
41
  end
49
42
  end
@@ -0,0 +1,16 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static values = { interval: { type: Number, default: 60000 } }
5
+
6
+ connect() {
7
+ this.timer = setInterval(() => {
8
+ this.element.src = window.location.href
9
+ this.element.reload()
10
+ }, this.intervalValue)
11
+ }
12
+
13
+ disconnect() {
14
+ clearInterval(this.timer)
15
+ }
16
+ }
@@ -19,9 +19,6 @@ module Upright::Playwright::FormAuthentication
19
19
  end
20
20
 
21
21
  def authenticator_for(service)
22
- # First try the host app's authenticator, then fall back to engine's
23
22
  "::Playwright::Authenticator::#{service.to_s.camelize}".constantize
24
- rescue NameError
25
- "Upright::Playwright::Authenticator::#{service.to_s.camelize}".constantize
26
23
  end
27
24
  end
@@ -33,8 +33,9 @@ module Upright::Playwright::Lifecycle
33
33
  run_callbacks :page_ready
34
34
  yield
35
35
  ensure
36
- page&.close
37
- context&.close
36
+ # Rescue each step independently so a failed close doesn't prevent video capture
37
+ page&.close rescue Rails.error.report($!)
38
+ context&.close rescue Rails.error.report($!)
38
39
  run_callbacks :page_close
39
40
  end
40
41
 
@@ -0,0 +1,23 @@
1
+ module Upright::ProbeResult::StaleCleanup
2
+ extend ActiveSupport::Concern
3
+
4
+ class_methods do
5
+ def cleanup_stale
6
+ cleanup_stale_successes
7
+ cleanup_stale_failures
8
+ end
9
+
10
+ def cleanup_stale_successes
11
+ ok.where(created_at: ...Upright.config.stale_success_threshold.ago).in_batches.destroy_all
12
+ end
13
+
14
+ def cleanup_stale_failures
15
+ cutoff = [
16
+ Upright.config.stale_failure_threshold.ago,
17
+ fail.order(created_at: :desc).offset(Upright.config.failure_retention_limit).pick(:created_at)
18
+ ].compact.max
19
+
20
+ fail.where(created_at: ..cutoff).in_batches.destroy_all
21
+ end
22
+ end
23
+ end
@@ -2,7 +2,7 @@ module Upright::Probeable
2
2
  extend ActiveSupport::Concern
3
3
  include Upright::Staggerable
4
4
 
5
- TYPES = %w[ http playwright smtp traceroute ]
5
+ ALERT_SEVERITIES = %i[ medium high critical ]
6
6
 
7
7
  included do
8
8
  attr_writer :logger
@@ -35,6 +35,7 @@ module Upright::Probeable
35
35
  probe_name: probe_name,
36
36
  probe_target: probe_target,
37
37
  probe_service: probe_service,
38
+ probe_alert_severity: probe_alert_severity,
38
39
  status: result[:status],
39
40
  duration: result[:duration],
40
41
  error: result[:error]
@@ -63,6 +64,11 @@ module Upright::Probeable
63
64
  nil
64
65
  end
65
66
 
67
+ def probe_alert_severity
68
+ severity = try(:alert_severity)&.to_sym
69
+ ALERT_SEVERITIES.include?(severity) ? severity : :high
70
+ end
71
+
66
72
  private
67
73
  def failsafe_check
68
74
  result, error, duration = nil
@@ -23,7 +23,7 @@ class Upright::HTTP::Request
23
23
  proxy: proxy_url,
24
24
  proxyuserpwd: proxy_userpwd,
25
25
  verbose: true,
26
- forbid_reuse: true
26
+ forbid_reuse: proxy_url.nil?
27
27
  }.compact
28
28
  end
29
29
 
@@ -8,12 +8,16 @@ class Upright::Playwright::StorageState
8
8
  end
9
9
 
10
10
  def load
11
- JSON.parse(path.read) if exists?
11
+ if exists?
12
+ decrypted_json = encryptor.decrypt_and_verify(path.read)
13
+ JSON.parse(decrypted_json)
14
+ end
12
15
  end
13
16
 
14
17
  def save(state)
15
18
  FileUtils.mkdir_p(storage_dir)
16
- path.write(JSON.pretty_generate(state))
19
+ encrypted_data = encryptor.encrypt_and_sign(JSON.generate(state))
20
+ path.write(encrypted_data)
17
21
  end
18
22
 
19
23
  def clear
@@ -26,6 +30,11 @@ class Upright::Playwright::StorageState
26
30
  end
27
31
 
28
32
  def path
29
- storage_dir.join("#{@service}.json")
33
+ storage_dir.join("#{@service}.enc")
34
+ end
35
+
36
+ def encryptor
37
+ key = Rails.application.key_generator.generate_key("playwright_storage_state", 32)
38
+ ActiveSupport::MessageEncryptor.new(key)
30
39
  end
31
40
  end
@@ -1,11 +1,16 @@
1
1
  class Upright::ProbeResult < Upright::ApplicationRecord
2
2
  include Upright::ExceptionRecording
3
+ include Upright::ProbeResult::StaleCleanup
4
+
5
+ attr_accessor :probe_alert_severity
3
6
 
4
7
  has_many_attached :artifacts
5
8
 
6
9
  scope :by_type, ->(type) { where(probe_type: type) if type.present? }
7
10
  scope :by_status, ->(status) { where(status: status) if status.present? }
8
11
  scope :by_name, ->(name) { where(probe_name: name) if name.present? }
12
+ scope :by_date, ->(date) { where(created_at: Date.parse(date).all_day) if date.present? }
13
+
9
14
  scope :stale, -> { where(created_at: ...24.hours.ago) }
10
15
 
11
16
  enum :status, [ :ok, :fail ]
@@ -23,7 +28,7 @@ class Upright::ProbeResult < Upright::ApplicationRecord
23
28
 
24
29
  private
25
30
  def increment_metrics
26
- labels = { type: probe_type, name: probe_name, probe_target: probe_target, probe_service: probe_service }
31
+ labels = { type: probe_type, name: probe_name, probe_target: probe_target, probe_service: probe_service, alert_severity: probe_alert_severity || :high }
27
32
 
28
33
  Yabeda.upright_probe_duration_seconds.set(labels.merge(status: status), duration.to_f)
29
34
  Yabeda.upright_probe_up.set(labels, ok? ? 1 : 0)
@@ -74,11 +74,15 @@ class Upright::Probes::HTTPProbe < FrozenRecord::Base
74
74
  end
75
75
 
76
76
  def proxy_credentials
77
- if try(:proxy)
78
- Rails.application.credentials.dig(:proxies, proxy.to_sym)
77
+ if selected_proxy
78
+ Rails.application.credentials.dig(:proxies, selected_proxy.to_sym)
79
79
  end
80
80
  end
81
81
 
82
+ def selected_proxy
83
+ Array(try(:proxies) || try(:proxy)).sample
84
+ end
85
+
82
86
  def record_response_status
83
87
  if last_response && !last_response.network_error? && defined?(Yabeda)
84
88
  Yabeda.upright_http_response_status.set(
@@ -0,0 +1,24 @@
1
+ class Upright::Probes::Status::Probe
2
+ include Comparable
3
+
4
+ attr_reader :name, :type, :probe_target, :site_statuses
5
+
6
+ def initialize(name:, type:, probe_target:, site_statuses:)
7
+ @name = name
8
+ @type = type
9
+ @probe_target = probe_target
10
+ @site_statuses = site_statuses
11
+ end
12
+
13
+ def status_for_site(code)
14
+ site_statuses.find { |s| s.site_code == code.to_s }
15
+ end
16
+
17
+ def any_down?
18
+ site_statuses.any?(&:down?)
19
+ end
20
+
21
+ def <=>(other)
22
+ [ any_down? ? 0 : 1, type, name ] <=> [ other.any_down? ? 0 : 1, other.type, other.name ]
23
+ end
24
+ end
@@ -0,0 +1,71 @@
1
+ class Upright::Probes::Status::SiteStatus
2
+ STALE_THRESHOLD = 5.minutes
3
+
4
+ attr_reader :site_code, :site_city
5
+
6
+ def initialize(site_code:, site_city:, values:)
7
+ @site_code = site_code
8
+ @site_city = site_city
9
+ @values = values
10
+ end
11
+
12
+ def up?
13
+ latest_value == 1
14
+ end
15
+
16
+ def down?
17
+ !up?
18
+ end
19
+
20
+ def stale?
21
+ if @values.empty?
22
+ true
23
+ else
24
+ Time.at(latest_timestamp) < STALE_THRESHOLD.ago
25
+ end
26
+ end
27
+
28
+ def down_since
29
+ if down? && @values.any?
30
+ Time.at(down_start_timestamp)
31
+ end
32
+ end
33
+
34
+ def down_since_known?
35
+ if down? && @values.any?
36
+ down_start_timestamp != sorted_values.first.first
37
+ else
38
+ false
39
+ end
40
+ end
41
+
42
+ private
43
+ def sorted_values
44
+ @sorted_values ||= @values.sort_by(&:first)
45
+ end
46
+
47
+ def down_start_timestamp
48
+ @down_start_timestamp ||= begin
49
+ result = sorted_values.last.first
50
+
51
+ sorted_values.reverse_each do |timestamp, value|
52
+ break if value.to_f == 1
53
+ result = timestamp
54
+ end
55
+
56
+ result
57
+ end
58
+ end
59
+
60
+ def latest_value
61
+ if @values.any?
62
+ sorted_values.last.last.to_f
63
+ end
64
+ end
65
+
66
+ def latest_timestamp
67
+ if @values.any?
68
+ sorted_values.last.first
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,54 @@
1
+ class Upright::Probes::Status
2
+ class << self
3
+ def for_type(probe_type)
4
+ results = prometheus_client.query_range(
5
+ query: query(probe_type),
6
+ start: 30.minutes.ago.iso8601,
7
+ end: Time.current.iso8601,
8
+ step: "30s"
9
+ ).deep_symbolize_keys
10
+
11
+ build_probes(results[:result])
12
+ end
13
+
14
+ private
15
+ def query(probe_type)
16
+ "upright_probe_up#{label_selector(probe_type)}"
17
+ end
18
+
19
+ def label_selector(probe_type)
20
+ matchers = [ "alert_severity!=\"\"" ]
21
+ matchers << "type=\"#{probe_type}\"" if probe_type.present?
22
+ "{#{matchers.join(",")}}"
23
+ end
24
+
25
+ def prometheus_client
26
+ Prometheus::ApiClient.client(
27
+ url: ENV.fetch("PROMETHEUS_URL", "http://localhost:9090"),
28
+ options: { timeout: 30.seconds }
29
+ )
30
+ end
31
+
32
+ def build_probes(results)
33
+ # Group results by probe identity (name + type + probe_target)
34
+ grouped = results.group_by { |r| [ r[:metric][:name], r[:metric][:type], r[:metric][:probe_target] ] }
35
+
36
+ grouped.map do |(_name, _type, _target), series|
37
+ site_statuses = series.map do |s|
38
+ SiteStatus.new(
39
+ site_code: s[:metric][:site_code],
40
+ site_city: s[:metric][:site_city],
41
+ values: s[:values]
42
+ )
43
+ end
44
+
45
+ Probe.new(
46
+ name: _name,
47
+ type: _type,
48
+ probe_target: _target,
49
+ site_statuses: site_statuses
50
+ )
51
+ end.sort
52
+ end
53
+ end
54
+ end
@@ -17,13 +17,13 @@ class Upright::Probes::Uptime
17
17
 
18
18
  private
19
19
  def query(probe_type)
20
- "upright:probe_uptime_daily#{label_selector(probe_type)}"
20
+ "min by (name, type, probe_target) (upright:probe_uptime_daily#{label_selector(probe_type)})"
21
21
  end
22
22
 
23
23
  def label_selector(probe_type)
24
- if probe_type.present?
25
- "{type=\"#{probe_type}\"}"
26
- end
24
+ matchers = [ "alert_severity!=\"\"" ]
25
+ matchers << "type=\"#{probe_type}\"" if probe_type.present?
26
+ "{#{matchers.join(",")}}"
27
27
  end
28
28
 
29
29
  def prometheus_client
@@ -1,7 +1,6 @@
1
1
  require "net/http"
2
2
  require "json"
3
3
  require "resolv"
4
- require "geohash_ruby"
5
4
 
6
5
  class Upright::Traceroute::IpMetadataLookup
7
6
  API_URL = "http://ip-api.com/batch"
@@ -84,7 +83,7 @@ class Upright::Traceroute::IpMetadataLookup
84
83
 
85
84
  def encode_geohash(latitude, longitude)
86
85
  if latitude && longitude
87
- Geohash.encode(latitude, longitude, GEOHASH_PRECISION)
86
+ Upright::Geohash.encode(latitude, longitude, GEOHASH_PRECISION)
88
87
  end
89
88
  end
90
89
 
@@ -2,13 +2,14 @@
2
2
  <div class="header__left">
3
3
  <%= link_to "Upright", root_url(subdomain: Upright.configuration.global_subdomain), class: "header__logo" %>
4
4
  <% if Upright::Current.site.present? %>
5
- <span class="header__site"><%= Upright::Current.site.city %></span>
5
+ <span class="header__site"><%= site_name(Upright::Current.site) %></span>
6
6
  <% end %>
7
7
  </div>
8
8
 
9
9
  <nav class="header__nav">
10
10
  <%= link_to "Probes", upright.site_root_url(subdomain: current_or_default_site.code), class: "header__link" %>
11
11
  <%= link_to "Uptime", upright.dashboards_uptime_url(subdomain: Upright.configuration.global_subdomain), class: "header__link" %>
12
+ <%= link_to "Status", upright.dashboards_probe_status_url(subdomain: Upright.configuration.global_subdomain), class: "header__link" %>
12
13
  <span class="header__divider"></span>
13
14
  <%= link_to "Jobs", upright.jobs_url(subdomain: current_or_default_site.code), class: "header__link" %>
14
15
  <span class="header__divider"></span>
@@ -3,8 +3,8 @@
3
3
  No probe data available for the selected filters.
4
4
  </div>
5
5
  <% else %>
6
- <div class="uptime-bars">
7
- <div class="uptime-bars__header">
6
+ <div class="data-table">
7
+ <div class="data-table__header uptime-bars__header">
8
8
  <div>Probe</div>
9
9
  <div>Past 30 days</div>
10
10
  <div>Uptime</div>