upright 0.2.0 → 0.4.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/README.md +208 -56
- data/Rakefile +22 -0
- data/app/assets/images/upright/.keep +0 -0
- data/app/assets/stylesheets/upright/_global.css +2 -0
- data/app/assets/stylesheets/upright/artifact.css +7 -25
- data/app/assets/stylesheets/upright/flash.css +19 -0
- data/app/assets/stylesheets/upright/header.css +14 -0
- data/app/assets/stylesheets/upright/incidents.css +610 -0
- data/app/assets/stylesheets/upright/map.css +6 -0
- data/app/assets/stylesheets/upright/public_status.css +534 -0
- data/app/assets/stylesheets/upright/uptime-bars.css +10 -1
- data/app/controllers/concerns/.keep +0 -0
- data/app/controllers/concerns/upright/authentication.rb +1 -0
- data/app/controllers/concerns/upright/proxy_authentication.rb +134 -0
- data/app/controllers/upright/alertmanager_proxy_controller.rb +12 -4
- data/app/controllers/upright/artifacts_controller.rb +1 -1
- data/app/controllers/upright/incidents/updates_controller.rb +13 -0
- data/app/controllers/upright/incidents_controller.rb +53 -0
- data/app/controllers/upright/probe_results_controller.rb +1 -0
- data/app/controllers/upright/prometheus_proxy_controller.rb +13 -18
- data/app/controllers/upright/public/base_controller.rb +16 -0
- data/app/controllers/upright/public/incidents_controller.rb +6 -0
- data/app/controllers/upright/public/service_incidents_controller.rb +7 -0
- data/app/controllers/upright/public/services_controller.rb +6 -0
- data/app/controllers/upright/sessions_controller.rb +37 -1
- data/app/controllers/upright/traces_controller.rb +88 -0
- data/app/helpers/upright/application_helper.rb +22 -2
- data/app/helpers/upright/artifacts_helper.rb +20 -0
- data/app/helpers/upright/probe_results_helper.rb +4 -10
- data/app/helpers/upright/public/services_helper.rb +70 -0
- data/app/javascript/upright/application.js +3 -0
- data/app/javascript/upright/controllers/sites_map_controller.js +13 -5
- data/app/javascript/upright/controllers/template_controller.js +11 -0
- data/app/javascript/upright/public.js +5 -0
- data/app/jobs/upright/health_metrics_job.rb +27 -0
- data/app/jobs/upright/incident_reporter_job.rb +7 -0
- data/app/jobs/upright/maintenance_advance_job.rb +8 -0
- data/app/jobs/upright/playwright_video_sweep_job.rb +21 -0
- data/app/jobs/upright/rollups/daily_aggregation_job.rb +14 -0
- data/app/models/concerns/.keep +0 -0
- data/app/models/concerns/upright/incidents/auto_reporting.rb +93 -0
- data/app/models/concerns/upright/incidents/lifecycle.rb +47 -0
- data/app/models/concerns/upright/playwright/form_authentication.rb +3 -5
- data/app/models/concerns/upright/playwright/lifecycle.rb +8 -13
- data/app/models/concerns/upright/playwright/trace_recording.rb +53 -0
- data/app/models/concerns/upright/playwright/video_recording.rb +55 -14
- data/app/models/concerns/upright/probe_result/stale_cleanup.rb +47 -0
- data/app/models/concerns/upright/probeable.rb +16 -2
- data/app/models/concerns/upright/services/live_status.rb +69 -0
- data/app/models/concerns/upright/services/maintenance_status.rb +19 -0
- data/app/models/upright/artifact.rb +2 -1
- data/app/models/upright/http/request.rb +13 -4
- data/app/models/upright/incident/automatic_report.rb +3 -0
- data/app/models/upright/incident.rb +87 -0
- data/app/models/upright/incident_affected_service.rb +9 -0
- data/app/models/upright/incident_update.rb +7 -0
- data/app/models/upright/maintenance.rb +36 -0
- data/app/models/upright/persistent_record.rb +12 -0
- data/app/models/upright/playwright/authenticator/base.rb +50 -70
- data/app/models/upright/probe_result.rb +6 -1
- data/app/models/upright/probes/http_probe.rb +30 -1
- data/app/models/upright/probes/playwright/base.rb +2 -0
- data/app/models/upright/probes/status/probe.rb +8 -0
- data/app/models/upright/probes/status.rb +5 -13
- data/app/models/upright/probes/uptime.rb +5 -12
- data/app/models/upright/rollups/daily_uptime.rb +28 -0
- data/app/models/upright/rollups/probe_rollup.rb +64 -0
- data/app/models/upright/rollups/probe_uptime.rb +9 -0
- data/app/models/upright/rollups/site_uptime.rb +75 -0
- data/app/models/upright/service/daily_status.rb +39 -0
- data/app/models/upright/service/incident_history.rb +24 -0
- data/app/models/upright/service/outage.rb +15 -0
- data/app/models/upright/service/status_page.rb +35 -0
- data/app/models/upright/service.rb +101 -0
- data/app/models/upright/status.rb +21 -0
- data/app/models/upright/traceroute/ip_metadata_lookup.rb +21 -2
- data/app/models/upright/traceroute/result.rb +8 -1
- data/app/views/layouts/upright/_header.html.erb +12 -2
- data/app/views/layouts/upright/application.html.erb +4 -1
- data/app/views/layouts/upright/public.html.erb +18 -0
- data/app/views/layouts/upright/public.rss.builder +1 -0
- data/app/views/upright/active_storage/attachments/_attachment.html.erb +1 -1
- data/app/views/upright/artifacts/show.html.erb +8 -0
- data/app/views/upright/dashboards/_uptime_probe_row.html.erb +5 -3
- data/app/views/upright/dashboards/probe_statuses/show.html.erb +1 -1
- data/app/views/upright/dashboards/uptimes/show.html.erb +1 -1
- data/app/views/upright/incidents/_form.html.erb +93 -0
- data/app/views/upright/incidents/_row.html.erb +15 -0
- data/app/views/upright/incidents/edit.html.erb +64 -0
- data/app/views/upright/incidents/index.html.erb +53 -0
- data/app/views/upright/incidents/new.html.erb +6 -0
- data/app/views/upright/probe_results/index.html.erb +7 -4
- data/app/views/upright/public/_branding.html.erb +3 -0
- data/app/views/upright/public/_footer.html.erb +3 -0
- data/app/views/upright/public/incidents/show.html.erb +29 -0
- data/app/views/upright/public/service_incidents/_incident.html.erb +14 -0
- data/app/views/upright/public/service_incidents/index.html.erb +50 -0
- data/app/views/upright/public/services/_active_events.html.erb +28 -0
- data/app/views/upright/public/services/_degraded_list.html.erb +14 -0
- data/app/views/upright/public/services/_overall_banner.html.erb +4 -0
- data/app/views/upright/public/services/_service.html.erb +34 -0
- data/app/views/upright/public/services/_upcoming_maintenance.html.erb +21 -0
- data/app/views/upright/public/services/_uptime_bar.html.erb +9 -0
- data/app/views/upright/public/services/index.html.erb +13 -0
- data/app/views/upright/public/services/index.rss.builder +29 -0
- data/app/views/upright/sessions/new.html.erb +14 -1
- data/config/ci.rb +2 -0
- data/config/importmap.rb +25 -14
- data/config/initializers/date_formats.rb +5 -0
- data/config/initializers/mime_types.rb +1 -0
- data/config/routes.rb +26 -2
- data/db/migrate/20260629000001_fix_probe_result_duration_precision.rb +14 -0
- data/db/persistent_migrate/20260512000001_create_upright_rollups.rb +16 -0
- data/db/persistent_migrate/20260702000001_create_upright_incidents.rb +34 -0
- data/db/persistent_migrate/20260707000001_add_probe_identity_to_upright_rollups.rb +11 -0
- data/db/persistent_migrate/20260709000001_add_created_by_to_incidents.rb +7 -0
- data/db/persistent_migrate/20260901000001_add_auto_reporting_to_upright_incidents.rb +22 -0
- data/lib/generators/upright/install/database_config.rb +46 -0
- data/lib/generators/upright/install/install_generator.rb +52 -3
- data/lib/generators/upright/install/templates/Dockerfile +80 -0
- data/lib/generators/upright/install/templates/content_security_policy.rb +42 -0
- data/lib/generators/upright/install/templates/deploy.yml +13 -13
- data/lib/generators/upright/install/templates/development_prometheus.yml +1 -1
- data/lib/generators/upright/install/templates/docker-compose.yml +12 -11
- data/lib/generators/upright/install/templates/http_probes.yml +1 -0
- data/lib/generators/upright/install/templates/omniauth.rb +7 -3
- data/lib/generators/upright/install/templates/otel_collector.yml +6 -0
- data/lib/generators/upright/install/templates/recurring.yml +45 -0
- data/lib/generators/upright/install/templates/sites.yml +4 -0
- data/lib/generators/upright/install/templates/smtp_probes.yml +3 -0
- data/lib/generators/upright/install/templates/traceroute_probes.yml +3 -0
- data/lib/generators/upright/install/templates/upright.rb +24 -2
- data/lib/generators/upright/install/templates/upright.rules.yml +14 -7
- data/lib/upright/configuration.rb +127 -6
- data/lib/upright/engine.rb +32 -2
- data/lib/upright/metrics.rb +27 -2
- data/lib/upright/probe_type_registry.rb +33 -0
- data/lib/upright/site.rb +31 -1
- data/lib/upright/version.rb +3 -1
- data/lib/upright.rb +34 -2
- metadata +110 -7
- data/config/credentials/development.key +0 -1
- data/config/credentials/test.key +0 -1
- data/lib/tasks/upright_tasks.rake +0 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c91a962bc77007bffeebba4df5da09c213a42c46a416de5fce7d19e9169258f7
|
|
4
|
+
data.tar.gz: 47b63615e16a15aede26f738a3ffc7256fada1aab09b775ff7ddd90282c91ea0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 25a77e92224a4869f1da746deba9f5a4440c4a071d2e3b0c8fef363c2abd36e270cc9a1147a11081284a85f0deb8e22cc73a09d8a3dcd183ea29dd7a0419e872
|
|
7
|
+
data.tar.gz: a5e047ccb8feb9e864652496e81cdbb17ea4e0ae0972b8e380df9fa1206b011c23d5fd0b1e8bae2f366313dcb48b7af25d3babeba1f96e1211967bf70e954415
|
data/README.md
CHANGED
|
@@ -47,7 +47,8 @@ Upright is a self-hosted synthetic monitoring system. It provides a framework fo
|
|
|
47
47
|
|
|
48
48
|
## Installation
|
|
49
49
|
|
|
50
|
-
|
|
50
|
+
> [!NOTE]
|
|
51
|
+
> Upright is designed to be run in its own Rails app and deployed with Kamal.
|
|
51
52
|
|
|
52
53
|
### Quick Start (New Project)
|
|
53
54
|
|
|
@@ -58,7 +59,7 @@ rails new my-upright --database=sqlite3 --skip-test
|
|
|
58
59
|
cd my-upright
|
|
59
60
|
bundle add upright
|
|
60
61
|
bin/rails generate upright:install
|
|
61
|
-
bin/rails db:
|
|
62
|
+
bin/rails db:prepare
|
|
62
63
|
```
|
|
63
64
|
|
|
64
65
|
Start the server:
|
|
@@ -75,7 +76,10 @@ Visit http://app.my-upright.localhost:3000 to see your Upright instance.
|
|
|
75
76
|
|
|
76
77
|
The `upright:install` generator creates:
|
|
77
78
|
|
|
79
|
+
- `config/database.yml` - Development and test split into `primary` (probe results), `persistent` (rollups and incidents, migrated from the gem) and `queue` (Solid Queue) databases
|
|
80
|
+
- `config/recurring.yml` - Probe schedules and the engine's housekeeping, health, rollup, incident and maintenance jobs
|
|
78
81
|
- `config/initializers/upright.rb` - Engine configuration
|
|
82
|
+
- `config/initializers/content_security_policy.rb` - Ready-to-enable CSP covering the engine's CDN dependencies
|
|
79
83
|
- `config/sites.yml` - Site definitions for each VPS you host Upright on
|
|
80
84
|
- `config/prometheus/prometheus.yml` - Prometheus configuration
|
|
81
85
|
- `config/alertmanager/alertmanager.yml` - AlertManager configuration
|
|
@@ -121,6 +125,8 @@ shared:
|
|
|
121
125
|
country: NL
|
|
122
126
|
geohash: u17982
|
|
123
127
|
provider: digitalocean
|
|
128
|
+
stores_metrics: true
|
|
129
|
+
primary: true
|
|
124
130
|
|
|
125
131
|
- code: sfo
|
|
126
132
|
city: San Francisco
|
|
@@ -131,14 +137,16 @@ shared:
|
|
|
131
137
|
|
|
132
138
|
Each site node identifies itself via the `SITE_SUBDOMAIN` environment variable, configured in your Kamal deploy.yml.
|
|
133
139
|
|
|
140
|
+
Two optional flags give a site a role beyond running probes: `primary` serves the app and status hostnames and runs the jobs writing the shared database, and `stores_metrics` runs a local Prometheus and Alertmanager. See [Sites and their roles](https://github.com/basecamp/upright/blob/main/docs/sites.md) for what each one changes, the health metrics every site exports, and how daily rollups read across them.
|
|
141
|
+
|
|
134
142
|
### Authentication
|
|
135
143
|
|
|
136
144
|
#### Static Credentials
|
|
137
145
|
|
|
138
|
-
Upright uses static credentials by default with username `admin` and password `
|
|
146
|
+
Upright uses static credentials by default with username `admin` and the password taken from the `ADMIN_PASSWORD` environment variable.
|
|
139
147
|
|
|
140
|
-
> [!
|
|
141
|
-
>
|
|
148
|
+
> [!IMPORTANT]
|
|
149
|
+
> There is no default password: `ADMIN_PASSWORD` must be set, and sign-in fails closed without it. Set it as a Kamal secret in production (the generated `config/deploy.yml` already lists it) and export it locally for development.
|
|
142
150
|
|
|
143
151
|
|
|
144
152
|
#### OpenID Connect
|
|
@@ -157,6 +165,97 @@ Upright.configure do |config|
|
|
|
157
165
|
end
|
|
158
166
|
```
|
|
159
167
|
|
|
168
|
+
### Probe Result Cleanup
|
|
169
|
+
|
|
170
|
+
Upright automatically cleans up old probe results on a recurring schedule. You can configure the retention thresholds:
|
|
171
|
+
|
|
172
|
+
```ruby
|
|
173
|
+
Upright.configure do |config|
|
|
174
|
+
config.stale_success_threshold = 24.hours # Delete successful results older than this (default: 24 hours)
|
|
175
|
+
config.stale_failure_threshold = 30.days # Delete failed results older than this (default: 30 days)
|
|
176
|
+
config.failure_retention_limit = 20_000 # Keep at most this many failed results (default: 20,000)
|
|
177
|
+
end
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## Custom Probe Types
|
|
181
|
+
|
|
182
|
+
Upright ships with four built-in probe types: HTTP, Playwright, SMTP, and Traceroute. You can register your own to extend the system.
|
|
183
|
+
|
|
184
|
+
### 1. Register the type
|
|
185
|
+
|
|
186
|
+
Add it to your initializer so Upright knows about its name and icon:
|
|
187
|
+
|
|
188
|
+
```ruby
|
|
189
|
+
# config/initializers/upright.rb
|
|
190
|
+
Upright.configure do |config|
|
|
191
|
+
config.probe_types.register :ping, name: "Ping", icon: "📶"
|
|
192
|
+
end
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### 2. Create the probe class
|
|
196
|
+
|
|
197
|
+
Add a Ruby class in your `probes/` directory that extends `FrozenRecord::Base` and includes `Upright::Probeable` and `Upright::ProbeYamlSource`. Implement `probe_type`, `probe_target`, `check`, and `on_check_recorded`:
|
|
198
|
+
|
|
199
|
+
```ruby
|
|
200
|
+
# probes/ping_probe.rb
|
|
201
|
+
class PingProbe < FrozenRecord::Base
|
|
202
|
+
include Upright::Probeable
|
|
203
|
+
include Upright::ProbeYamlSource
|
|
204
|
+
|
|
205
|
+
stagger_by_site 3.seconds
|
|
206
|
+
|
|
207
|
+
# Hostnames, IPv4, and IPv6 addresses only. The first character may not be
|
|
208
|
+
# a dash, so a configured host can never be mistaken for a ping flag.
|
|
209
|
+
HOST_PATTERN = /\A[a-z0-9:][a-z0-9:._-]*\z/i
|
|
210
|
+
|
|
211
|
+
def check
|
|
212
|
+
raise ArgumentError, "invalid ping host: #{host.inspect}" unless HOST_PATTERN.match?(host.to_s)
|
|
213
|
+
|
|
214
|
+
@ping_output, status = Open3.capture2e("ping", "-c", "1", "-W", "5", "--", host)
|
|
215
|
+
status.success?
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def on_check_recorded(probe_result)
|
|
219
|
+
if @ping_output.present?
|
|
220
|
+
Upright::Artifact.new(name: "ping.log", content: @ping_output).attach_to(probe_result)
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def probe_type = "ping"
|
|
225
|
+
def probe_target = host
|
|
226
|
+
end
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
The `check` method should return a truthy value on success and a falsy value on failure.
|
|
230
|
+
|
|
231
|
+
### 3. Define probes in YAML
|
|
232
|
+
|
|
233
|
+
Create a YAML file matching the class name (e.g., `PingProbe` → `probes/ping_probes.yml`):
|
|
234
|
+
|
|
235
|
+
```yaml
|
|
236
|
+
# probes/ping_probes.yml
|
|
237
|
+
- name: "Cloudflare DNS"
|
|
238
|
+
host: "1.1.1.1"
|
|
239
|
+
|
|
240
|
+
- name: "Google DNS"
|
|
241
|
+
host: "8.8.8.8"
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
Fields defined in the YAML are available as methods on the probe instance (e.g., `host`).
|
|
245
|
+
|
|
246
|
+
### 4. Schedule it
|
|
247
|
+
|
|
248
|
+
Add a recurring job in `config/recurring.yml`:
|
|
249
|
+
|
|
250
|
+
```yaml
|
|
251
|
+
production:
|
|
252
|
+
ping_probes:
|
|
253
|
+
command: "PingProbe.check_and_record_all_later"
|
|
254
|
+
schedule: every 30 seconds
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
The custom type will automatically appear in all dashboard dropdowns and filter links.
|
|
258
|
+
|
|
160
259
|
## Defining Probes
|
|
161
260
|
|
|
162
261
|
### HTTP Probes
|
|
@@ -171,12 +270,15 @@ Add probes to `probes/http_probes.yml`:
|
|
|
171
270
|
- name: API Health
|
|
172
271
|
url: https://api.example.com/health
|
|
173
272
|
expected_status: 200
|
|
273
|
+
alert_severity: critical
|
|
174
274
|
|
|
175
275
|
- name: Admin Panel
|
|
176
276
|
url: https://admin.example.com
|
|
177
277
|
basic_auth_credentials: admin_auth # Key in Rails credentials
|
|
178
278
|
```
|
|
179
279
|
|
|
280
|
+
The optional `alert_severity` field controls the Prometheus alert severity when a probe fails. Values: `medium`, `high` (default), `critical`.
|
|
281
|
+
|
|
180
282
|
### SMTP Probes
|
|
181
283
|
|
|
182
284
|
Add probes to `probes/smtp_probes.yml`:
|
|
@@ -317,70 +419,83 @@ image: your-org/upright
|
|
|
317
419
|
servers:
|
|
318
420
|
web:
|
|
319
421
|
hosts:
|
|
320
|
-
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
422
|
+
- ams.upright.example.com: [amsterdam]
|
|
423
|
+
- nyc.upright.example.com: [new_york]
|
|
424
|
+
- sfo.upright.example.com: [san_francisco]
|
|
425
|
+
jobs:
|
|
325
426
|
hosts:
|
|
326
|
-
- ams.upright.example.com
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
427
|
+
- ams.upright.example.com: [amsterdam]
|
|
428
|
+
- nyc.upright.example.com: [new_york]
|
|
429
|
+
- sfo.upright.example.com: [san_francisco]
|
|
430
|
+
cmd: bin/jobs
|
|
330
431
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
registry:
|
|
338
|
-
server: ghcr.io
|
|
339
|
-
username: your-org
|
|
340
|
-
password:
|
|
341
|
-
- KAMAL_REGISTRY_PASSWORD
|
|
432
|
+
proxy:
|
|
433
|
+
app_port: 3000
|
|
434
|
+
ssl: true
|
|
435
|
+
hosts:
|
|
436
|
+
- "*.upright.example.com"
|
|
342
437
|
|
|
343
438
|
env:
|
|
344
|
-
clear:
|
|
345
|
-
RAILS_ENV: production
|
|
346
|
-
RAILS_LOG_TO_STDOUT: true
|
|
347
439
|
secret:
|
|
348
440
|
- RAILS_MASTER_KEY
|
|
349
|
-
-
|
|
350
|
-
|
|
441
|
+
- ADMIN_PASSWORD
|
|
442
|
+
tags:
|
|
443
|
+
amsterdam:
|
|
444
|
+
SITE_SUBDOMAIN: ams
|
|
445
|
+
new_york:
|
|
446
|
+
SITE_SUBDOMAIN: nyc
|
|
447
|
+
san_francisco:
|
|
448
|
+
SITE_SUBDOMAIN: sfo
|
|
351
449
|
|
|
352
450
|
accessories:
|
|
353
451
|
playwright:
|
|
354
|
-
image:
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
452
|
+
image: jacoblincool/playwright:chromium-server-1.55.0
|
|
453
|
+
port: "127.0.0.1:53333:53333"
|
|
454
|
+
roles:
|
|
455
|
+
- jobs
|
|
456
|
+
|
|
457
|
+
prometheus:
|
|
458
|
+
image: prom/prometheus:v3.2.1
|
|
459
|
+
hosts:
|
|
460
|
+
- ams.upright.example.com
|
|
461
|
+
cmd: >-
|
|
462
|
+
--config.file=/etc/prometheus/prometheus.yml
|
|
463
|
+
--storage.tsdb.path=/prometheus
|
|
464
|
+
--storage.tsdb.retention.time=30d
|
|
465
|
+
--web.enable-otlp-receiver
|
|
466
|
+
files:
|
|
467
|
+
- config/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
|
|
468
|
+
- config/prometheus/rules/upright.rules.yml:/etc/prometheus/rules/upright.rules.yml
|
|
469
|
+
|
|
470
|
+
alertmanager:
|
|
471
|
+
image: prom/alertmanager:v0.28.1
|
|
472
|
+
hosts:
|
|
473
|
+
- ams.upright.example.com
|
|
474
|
+
cmd: --config.file=/etc/alertmanager/alertmanager.yml
|
|
475
|
+
files:
|
|
476
|
+
- config/alertmanager/alertmanager.yml:/etc/alertmanager/alertmanager.yml
|
|
361
477
|
```
|
|
362
478
|
|
|
363
479
|
## Observability
|
|
364
480
|
|
|
365
481
|
### Prometheus
|
|
366
482
|
|
|
367
|
-
|
|
483
|
+
Metrics are exposed via a Puma plugin at `http://0.0.0.0:9394/metrics`. Configure Prometheus to scrape:
|
|
368
484
|
|
|
369
485
|
```yaml
|
|
370
486
|
scrape_configs:
|
|
371
487
|
- job_name: upright
|
|
372
488
|
static_configs:
|
|
373
|
-
- targets: ['localhost:
|
|
374
|
-
metrics_path: /metrics
|
|
489
|
+
- targets: ['localhost:9394']
|
|
375
490
|
```
|
|
376
491
|
|
|
377
492
|
### Metrics Exposed
|
|
378
493
|
|
|
379
494
|
- `upright_probe_duration_seconds` - Probe execution duration
|
|
380
|
-
- `
|
|
381
|
-
- `
|
|
495
|
+
- `upright_probe_up` - Probe status (1 = up, 0 = down)
|
|
496
|
+
- `upright_http_response_status` - HTTP response status code
|
|
382
497
|
|
|
383
|
-
Labels include: `
|
|
498
|
+
Labels include: `type`, `name`, `site_code`, `site_city`, `site_country`
|
|
384
499
|
|
|
385
500
|
### AlertManager
|
|
386
501
|
|
|
@@ -391,20 +506,12 @@ groups:
|
|
|
391
506
|
- name: upright
|
|
392
507
|
rules:
|
|
393
508
|
- alert: ProbeDown
|
|
394
|
-
expr:
|
|
395
|
-
for: 5m
|
|
396
|
-
labels:
|
|
397
|
-
severity: critical
|
|
398
|
-
annotations:
|
|
399
|
-
summary: "Probe {{ $labels.probe_name }} is down"
|
|
400
|
-
|
|
401
|
-
- alert: ProbeSlow
|
|
402
|
-
expr: upright_probe_duration_seconds > 10
|
|
509
|
+
expr: upright_probe_up == 0
|
|
403
510
|
for: 5m
|
|
404
511
|
labels:
|
|
405
|
-
severity:
|
|
512
|
+
severity: "{{ $labels.alert_severity }}"
|
|
406
513
|
annotations:
|
|
407
|
-
summary: "Probe {{ $labels.
|
|
514
|
+
summary: "Probe {{ $labels.name }} is down"
|
|
408
515
|
```
|
|
409
516
|
|
|
410
517
|
### OpenTelemetry
|
|
@@ -443,20 +550,65 @@ bin/dev
|
|
|
443
550
|
|
|
444
551
|
Visit http://app.upright.localhost:3000 and sign in with:
|
|
445
552
|
- **Username**: `admin`
|
|
446
|
-
- **Password**:
|
|
553
|
+
- **Password**: the value of the `ADMIN_PASSWORD` env var, e.g. `ADMIN_PASSWORD=upright bin/dev` (a throwaway value like this is fine for local testing only — never for a deployed site)
|
|
447
554
|
|
|
448
555
|
### Testing Playwright Probes
|
|
449
556
|
|
|
450
557
|
Run probes with a visible browser window:
|
|
451
558
|
|
|
452
559
|
```bash
|
|
453
|
-
|
|
560
|
+
HEADLESS=false bin/rails console
|
|
454
561
|
```
|
|
455
562
|
|
|
456
563
|
```ruby
|
|
457
564
|
Probes::Playwright::MyServiceAuthProbe.check
|
|
458
565
|
```
|
|
459
566
|
|
|
567
|
+
### Upgrading Playwright
|
|
568
|
+
|
|
569
|
+
Playwright versions are pinned via `Upright::PLAYWRIGHT_VERSION` in `lib/upright/version.rb`. This drives the Ruby gem and npm package versions. To upgrade:
|
|
570
|
+
|
|
571
|
+
1. Update `PLAYWRIGHT_VERSION` in `lib/upright/version.rb`
|
|
572
|
+
2. Update the version in `package.json`
|
|
573
|
+
3. Run `bin/setup` (or manually: `npm install && npx playwright install chromium`)
|
|
574
|
+
4. Run `bin/rails test` to verify compatibility
|
|
575
|
+
5. Commit the updated `package.json` and `package-lock.json`
|
|
576
|
+
|
|
577
|
+
### Viewing Playwright Traces
|
|
578
|
+
|
|
579
|
+
Upright stores a Playwright trace as a probe result artifact and does not serve
|
|
580
|
+
the Playwright Trace Viewer. The viewer renders a trace's tags and attributes as
|
|
581
|
+
HTML in its own origin, so serving it from Upright's hostname would let a trace
|
|
582
|
+
run script against an admin session.
|
|
583
|
+
|
|
584
|
+
A trace artifact links to `config.trace_viewer_url`, which defaults to
|
|
585
|
+
upstream's hosted viewer at `https://trace.playwright.dev`. That viewer runs
|
|
586
|
+
entirely in the browser, and being on its own registrable domain is what keeps a
|
|
587
|
+
trace's contents off Upright's origin.
|
|
588
|
+
|
|
589
|
+
Following the link hands that origin a URL it can read for 24 hours. Set
|
|
590
|
+
`config.trace_viewer_url` to a viewer you host to keep traces to yourself, or
|
|
591
|
+
assign `nil` to keep them download-only:
|
|
592
|
+
|
|
593
|
+
```bash
|
|
594
|
+
npx playwright show-trace trace.zip
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
A URL under `config.hostname` raises `Upright::ConfigurationError`, since that
|
|
598
|
+
would put the trace back on the admin origin.
|
|
599
|
+
|
|
600
|
+
That trace URL is served by `Upright::TracesController`, not Active Storage. The
|
|
601
|
+
viewer fetches it from its own origin, so the request arrives without the admin
|
|
602
|
+
session and needs `Access-Control-Allow-Origin` to be readable at all. The
|
|
603
|
+
controller sends that header for the configured viewer's origin and nothing
|
|
604
|
+
else, and takes a signed id in place of the session: scoped to a purpose,
|
|
605
|
+
expiring after 24 hours, and resolvable only to a blob attached to an
|
|
606
|
+
`Upright::ProbeResult` under a `.zip` filename. With no viewer configured the
|
|
607
|
+
route 404s, so it exists only where it's used.
|
|
608
|
+
|
|
609
|
+
Range requests are answered, which is how the viewer reads a ZIP's central
|
|
610
|
+
directory without downloading the whole archive.
|
|
611
|
+
|
|
460
612
|
### Running Tests
|
|
461
613
|
|
|
462
614
|
```bash
|
data/Rakefile
CHANGED
|
@@ -4,3 +4,25 @@ APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
|
|
|
4
4
|
load "rails/tasks/engine.rake"
|
|
5
5
|
|
|
6
6
|
require "bundler/gem_tasks"
|
|
7
|
+
|
|
8
|
+
Dir.glob("lib/tasks/**/*.rake").each { |r| load r }
|
|
9
|
+
|
|
10
|
+
desc "Tag v<current version> and push it, which starts the Release workflow"
|
|
11
|
+
task :tag do
|
|
12
|
+
require_relative "lib/upright/version"
|
|
13
|
+
tag = "v#{Upright::VERSION}"
|
|
14
|
+
|
|
15
|
+
abort "tag: the working tree has uncommitted changes" unless `git status --porcelain`.empty?
|
|
16
|
+
|
|
17
|
+
branch = `git rev-parse --abbrev-ref HEAD`.strip
|
|
18
|
+
abort "tag: run this on main (currently on #{branch})" unless branch == "main"
|
|
19
|
+
|
|
20
|
+
sh "git fetch origin main --quiet"
|
|
21
|
+
abort "tag: HEAD differs from origin/main; pull or push first" unless `git rev-parse HEAD`.strip == `git rev-parse origin/main`.strip
|
|
22
|
+
abort "tag: #{tag} already exists locally" unless `git tag --list #{tag}`.strip.empty?
|
|
23
|
+
abort "tag: #{tag} already exists on origin" unless `git ls-remote --tags origin refs/tags/#{tag}`.strip.empty?
|
|
24
|
+
|
|
25
|
+
sh "git tag --annotate #{tag} --message 'upright #{tag}'"
|
|
26
|
+
sh "git push origin #{tag}"
|
|
27
|
+
puts "Pushed #{tag}. Approve the release-rubygems environment when the Release workflow pauses."
|
|
28
|
+
end
|
|
File without changes
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
|
|
36
36
|
--lch-green-dark: 55% 0.162 147;
|
|
37
37
|
--lch-red-dark: 59% 0.19 38;
|
|
38
|
+
--lch-orange-dark: 70% 0.17 65;
|
|
38
39
|
|
|
39
40
|
/* Colors: Semantic */
|
|
40
41
|
--color-black: oklch(var(--lch-black));
|
|
@@ -52,6 +53,7 @@
|
|
|
52
53
|
--color-link: oklch(var(--lch-blue-dark));
|
|
53
54
|
--color-positive: oklch(var(--lch-green-dark));
|
|
54
55
|
--color-negative: oklch(var(--lch-red-dark));
|
|
56
|
+
--color-warning: oklch(var(--lch-orange-dark));
|
|
55
57
|
--color-selected: oklch(var(--lch-blue-lighter));
|
|
56
58
|
|
|
57
59
|
/* Borders & Shadows */
|
|
@@ -22,17 +22,11 @@
|
|
|
22
22
|
|
|
23
23
|
.artifact__content {
|
|
24
24
|
background: var(--color-ink-lightest);
|
|
25
|
-
border-radius: 0.25rem;
|
|
26
|
-
box-shadow: var(--shadow);
|
|
27
25
|
display: flex;
|
|
28
26
|
flex-direction: column;
|
|
29
|
-
|
|
30
|
-
left: 50%;
|
|
27
|
+
inset: 0;
|
|
31
28
|
overflow: hidden;
|
|
32
29
|
position: fixed;
|
|
33
|
-
top: 50%;
|
|
34
|
-
transform: translate(-50%, -50%);
|
|
35
|
-
width: 80vw;
|
|
36
30
|
z-index: 1000;
|
|
37
31
|
}
|
|
38
32
|
|
|
@@ -123,26 +117,14 @@
|
|
|
123
117
|
}
|
|
124
118
|
}
|
|
125
119
|
|
|
126
|
-
|
|
127
|
-
|
|
120
|
+
.artifact__trace {
|
|
121
|
+
display: flex;
|
|
122
|
+
flex-direction: column;
|
|
123
|
+
gap: 0.5em;
|
|
128
124
|
}
|
|
129
125
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
width: calc(100vw - var(--block-space) * 2);
|
|
133
|
-
height: calc(100vh - var(--block-space) * 2);
|
|
134
|
-
left: 50%;
|
|
135
|
-
top: 50%;
|
|
136
|
-
}
|
|
126
|
+
td .artifact + .artifact {
|
|
127
|
+
margin-left: calc(var(--inline-space) / 2);
|
|
137
128
|
}
|
|
138
129
|
|
|
139
|
-
@media (max-width: 55ch) {
|
|
140
|
-
.artifact__content {
|
|
141
|
-
inset: 0;
|
|
142
|
-
width: 100%;
|
|
143
|
-
height: 100%;
|
|
144
|
-
transform: none;
|
|
145
|
-
border-radius: 0;
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
130
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
@layer components {
|
|
2
|
+
.flash {
|
|
3
|
+
border-radius: 0.5rem;
|
|
4
|
+
color: var(--color-ink);
|
|
5
|
+
font-size: var(--text-small);
|
|
6
|
+
margin-block-end: var(--block-space);
|
|
7
|
+
padding: 0.75rem 1rem;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.flash--notice {
|
|
11
|
+
background: color-mix(in oklab, var(--color-positive) 12%, var(--color-canvas));
|
|
12
|
+
border: 1px solid color-mix(in oklab, var(--color-positive) 40%, var(--color-canvas));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.flash--alert {
|
|
16
|
+
background: color-mix(in oklab, var(--color-negative) 12%, var(--color-canvas));
|
|
17
|
+
border: 1px solid color-mix(in oklab, var(--color-negative) 40%, var(--color-canvas));
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -38,6 +38,15 @@
|
|
|
38
38
|
text-transform: uppercase;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
.header__env {
|
|
42
|
+
color: var(--color-warning);
|
|
43
|
+
font-size: var(--text-x-small);
|
|
44
|
+
font-weight: 500;
|
|
45
|
+
letter-spacing: 0.06em;
|
|
46
|
+
margin-right: calc(var(--inline-space) * 2);
|
|
47
|
+
text-transform: uppercase;
|
|
48
|
+
}
|
|
49
|
+
|
|
41
50
|
.header__nav {
|
|
42
51
|
align-items: center;
|
|
43
52
|
display: flex;
|
|
@@ -62,6 +71,11 @@
|
|
|
62
71
|
&.active {
|
|
63
72
|
color: var(--color-positive);
|
|
64
73
|
}
|
|
74
|
+
|
|
75
|
+
&.unavailable {
|
|
76
|
+
color: var(--color-ink-light);
|
|
77
|
+
cursor: help;
|
|
78
|
+
}
|
|
65
79
|
}
|
|
66
80
|
|
|
67
81
|
.header__divider {
|