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.
Files changed (121) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.md +10 -0
  3. data/README.md +455 -0
  4. data/Rakefile +6 -0
  5. data/app/assets/stylesheets/upright/_global.css +104 -0
  6. data/app/assets/stylesheets/upright/artifact.css +148 -0
  7. data/app/assets/stylesheets/upright/base.css +68 -0
  8. data/app/assets/stylesheets/upright/buttons.css +21 -0
  9. data/app/assets/stylesheets/upright/dashboard.css +287 -0
  10. data/app/assets/stylesheets/upright/forms.css +104 -0
  11. data/app/assets/stylesheets/upright/header.css +124 -0
  12. data/app/assets/stylesheets/upright/layout.css +100 -0
  13. data/app/assets/stylesheets/upright/map.css +25 -0
  14. data/app/assets/stylesheets/upright/pagination.css +45 -0
  15. data/app/assets/stylesheets/upright/probes.css +72 -0
  16. data/app/assets/stylesheets/upright/reset.css +26 -0
  17. data/app/assets/stylesheets/upright/tables.css +63 -0
  18. data/app/assets/stylesheets/upright/typography.css +27 -0
  19. data/app/assets/stylesheets/upright/uptime-bars.css +154 -0
  20. data/app/controllers/concerns/upright/authentication.rb +21 -0
  21. data/app/controllers/concerns/upright/subdomain_scoping.rb +18 -0
  22. data/app/controllers/upright/alertmanager_proxy_controller.rb +21 -0
  23. data/app/controllers/upright/application_controller.rb +12 -0
  24. data/app/controllers/upright/artifacts_controller.rb +5 -0
  25. data/app/controllers/upright/dashboards/uptimes_controller.rb +6 -0
  26. data/app/controllers/upright/jobs_controller.rb +4 -0
  27. data/app/controllers/upright/probe_results_controller.rb +17 -0
  28. data/app/controllers/upright/prometheus_proxy_controller.rb +62 -0
  29. data/app/controllers/upright/sessions_controller.rb +29 -0
  30. data/app/controllers/upright/sites_controller.rb +5 -0
  31. data/app/helpers/upright/application_helper.rb +11 -0
  32. data/app/helpers/upright/dashboards_helper.rb +31 -0
  33. data/app/helpers/upright/probe_results_helper.rb +49 -0
  34. data/app/javascript/upright/application.js +2 -0
  35. data/app/javascript/upright/controllers/application.js +5 -0
  36. data/app/javascript/upright/controllers/form_controller.js +7 -0
  37. data/app/javascript/upright/controllers/index.js +4 -0
  38. data/app/javascript/upright/controllers/popover_controller.js +15 -0
  39. data/app/javascript/upright/controllers/probe_results_chart_controller.js +79 -0
  40. data/app/javascript/upright/controllers/results_table_controller.js +16 -0
  41. data/app/javascript/upright/controllers/sites_map_controller.js +33 -0
  42. data/app/jobs/upright/application_job.rb +2 -0
  43. data/app/jobs/upright/probe_check_job.rb +42 -0
  44. data/app/models/concerns/upright/exception_recording.rb +38 -0
  45. data/app/models/concerns/upright/playwright/form_authentication.rb +27 -0
  46. data/app/models/concerns/upright/playwright/helpers.rb +7 -0
  47. data/app/models/concerns/upright/playwright/lifecycle.rb +44 -0
  48. data/app/models/concerns/upright/playwright/logging.rb +87 -0
  49. data/app/models/concerns/upright/playwright/otel_tracing.rb +137 -0
  50. data/app/models/concerns/upright/playwright/video_recording.rb +60 -0
  51. data/app/models/concerns/upright/probe_yaml_source.rb +10 -0
  52. data/app/models/concerns/upright/probeable.rb +125 -0
  53. data/app/models/concerns/upright/staggerable.rb +22 -0
  54. data/app/models/concerns/upright/traceroute/otel_tracing.rb +108 -0
  55. data/app/models/upright/application_record.rb +3 -0
  56. data/app/models/upright/artifact.rb +61 -0
  57. data/app/models/upright/current.rb +9 -0
  58. data/app/models/upright/http/request.rb +59 -0
  59. data/app/models/upright/http/response.rb +55 -0
  60. data/app/models/upright/playwright/authenticator/base.rb +128 -0
  61. data/app/models/upright/playwright/storage_state.rb +31 -0
  62. data/app/models/upright/probe_result.rb +31 -0
  63. data/app/models/upright/probes/http_probe.rb +102 -0
  64. data/app/models/upright/probes/playwright/base.rb +48 -0
  65. data/app/models/upright/probes/smtp_probe.rb +48 -0
  66. data/app/models/upright/probes/traceroute_probe.rb +32 -0
  67. data/app/models/upright/probes/uptime/summary.rb +36 -0
  68. data/app/models/upright/probes/uptime.rb +36 -0
  69. data/app/models/upright/traceroute/hop.rb +49 -0
  70. data/app/models/upright/traceroute/ip_metadata_lookup.rb +107 -0
  71. data/app/models/upright/traceroute/mtr_parser.rb +47 -0
  72. data/app/models/upright/traceroute/result.rb +57 -0
  73. data/app/models/upright/user.rb +14 -0
  74. data/app/views/layouts/upright/_header.html.erb +23 -0
  75. data/app/views/layouts/upright/application.html.erb +25 -0
  76. data/app/views/upright/active_storage/attachments/_attachment.html.erb +21 -0
  77. data/app/views/upright/alertmanager_proxy/show.html.erb +1 -0
  78. data/app/views/upright/artifacts/show.html.erb +9 -0
  79. data/app/views/upright/dashboards/_uptime_bars.html.erb +17 -0
  80. data/app/views/upright/dashboards/_uptime_probe_row.html.erb +22 -0
  81. data/app/views/upright/dashboards/uptimes/show.html.erb +17 -0
  82. data/app/views/upright/jobs/show.html.erb +1 -0
  83. data/app/views/upright/probe_results/_pagination.html.erb +19 -0
  84. data/app/views/upright/probe_results/index.html.erb +72 -0
  85. data/app/views/upright/prometheus_proxy/show.html.erb +1 -0
  86. data/app/views/upright/sessions/new.html.erb +6 -0
  87. data/app/views/upright/sites/index.html.erb +22 -0
  88. data/config/brakeman.ignore +39 -0
  89. data/config/ci.rb +7 -0
  90. data/config/importmap.rb +18 -0
  91. data/config/routes.rb +41 -0
  92. data/db/migrate/20250114000001_create_upright_probe_results.rb +19 -0
  93. data/lib/generators/upright/install/install_generator.rb +83 -0
  94. data/lib/generators/upright/install/templates/alertmanager.yml +14 -0
  95. data/lib/generators/upright/install/templates/deploy.yml +118 -0
  96. data/lib/generators/upright/install/templates/development_alertmanager.yml +11 -0
  97. data/lib/generators/upright/install/templates/development_prometheus.yml +12 -0
  98. data/lib/generators/upright/install/templates/docker-compose.yml +38 -0
  99. data/lib/generators/upright/install/templates/http_probes.yml +14 -0
  100. data/lib/generators/upright/install/templates/omniauth.rb +8 -0
  101. data/lib/generators/upright/install/templates/otel_collector.yml +24 -0
  102. data/lib/generators/upright/install/templates/prometheus.yml +10 -0
  103. data/lib/generators/upright/install/templates/puma.rb +40 -0
  104. data/lib/generators/upright/install/templates/sites.yml +26 -0
  105. data/lib/generators/upright/install/templates/smtp_probes.yml +9 -0
  106. data/lib/generators/upright/install/templates/upright.rb +21 -0
  107. data/lib/generators/upright/install/templates/upright.rules.yml +256 -0
  108. data/lib/generators/upright/playwright_probe/playwright_probe_generator.rb +30 -0
  109. data/lib/generators/upright/playwright_probe/templates/authenticator.rb.tt +14 -0
  110. data/lib/generators/upright/playwright_probe/templates/probe.rb.tt +14 -0
  111. data/lib/omniauth/strategies/static_credentials.rb +57 -0
  112. data/lib/tasks/upright_tasks.rake +4 -0
  113. data/lib/upright/configuration.rb +106 -0
  114. data/lib/upright/engine.rb +157 -0
  115. data/lib/upright/metrics.rb +62 -0
  116. data/lib/upright/playwright/collect_performance_metrics.js +36 -0
  117. data/lib/upright/site.rb +49 -0
  118. data/lib/upright/tracing.rb +49 -0
  119. data/lib/upright/version.rb +3 -0
  120. data/lib/upright.rb +68 -0
  121. metadata +513 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 68ab49fec5d7d1e512a1f639de64f0ff2d856d6b753cf9ff8593fb8d2011f4dd
4
+ data.tar.gz: 4841c873eadfa45a515f15bd6c1a12648cc923b72d93bb6efec1e62f3601e94c
5
+ SHA512:
6
+ metadata.gz: e69af1adfbf82119f843ade06fe6c06b5ee3bc7c595fe0e338351d7ad13383f0e10223391bbde959c1437649fe176d3807034ef325c7ab1178cf24f4f1f58de7
7
+ data.tar.gz: '011039880bc90c861b67a8c315e9ad1b3e25379db5dcf0bce931bb47ce5625d98ba967659b879773cd8c773c2c5728f08d9ea80c09b64377cf52e0623292e63c'
data/LICENSE.md ADDED
@@ -0,0 +1,10 @@
1
+ # O'Saasy License Agreement
2
+
3
+ Copyright © 2026, 37signals LLC.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ 1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+ 2. No licensee or downstream recipient may use the Software (including any modified or derivative versions) to directly compete with the original Licensor by offering it to third parties as a hosted, managed, or Software-as-a-Service (SaaS) product or cloud service where the primary value of the service is the functionality of the Software itself.
9
+
10
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,455 @@
1
+ # Upright
2
+
3
+ Upright is a self-hosted synthetic monitoring system. It provides a framework for running health check probes from multiple geographic sites and reporting metrics via Prometheus. Alerts can then be configured with AlertManager.
4
+
5
+ ## Features
6
+
7
+ - **Playwright Probes** - Browser-based probes for user flows with video recording and logs
8
+ - **HTTP Probes** - Simple HTTP health checks with configurable expected status codes
9
+ - **SMTP Probes** - EHLO handshake verification for mail servers
10
+ - **Traceroute Probes** - Network path analysis with hop-by-hop latency tracking
11
+ - **Multi-Site Support** - Run probes from multiple geographic locations with staggered scheduling
12
+ - **Observability** - OTLP compatible, Prometheus metrics, OpenTelemetry tracing, and AlertManager support
13
+ - **Configurable Authentication** - OmniAuth integration with support for any OIDC provider
14
+
15
+ ### Not Included
16
+
17
+ - **Dashboards** - Instead, Grafana is suggested for monitoring the Prometheus metrics generated here
18
+ - **Notifications** - Instead, Alertmanager is included for alerting and notifications
19
+ - **Hosting** - Instead, you can use a VPS from DigitalOcean, Hetzner, etc.
20
+
21
+ ### Components
22
+
23
+ - Rails engine
24
+ - SQLite
25
+ - Solid Queue for background and recurring jobs
26
+ - Mission Control - Jobs to monitor Solid Queue and manually enqueue probes
27
+ - Kamal for deployments
28
+ - Prometheus metrics for uptime queries and alerting
29
+ - AlertManager for notifications
30
+ - Open Telemetry Collector - logs, metrics and traces can be shipped to any OTLP compatible endpoint
31
+
32
+ ## Installation
33
+
34
+ Upright is designed to be run in it's own Rails app and deployed with Kamal.
35
+
36
+ ### Quick Start (New Project)
37
+
38
+ Create a new Rails application and install Upright:
39
+
40
+ ```bash
41
+ rails new my-upright --database=sqlite3 --skip-test
42
+ cd my-upright
43
+ bundle add upright --github=basecamp/upright
44
+ bin/rails generate upright:install
45
+ bin/rails db:setup
46
+ ```
47
+
48
+ Start the server:
49
+
50
+ ```bash
51
+ bin/dev
52
+ ```
53
+
54
+ Visit http://app.my-upright.localhost:3000 to see your Upright instance.
55
+
56
+ > **Note**: Upright uses subdomain-based routing. The `app` subdomain is the admin interface, while site-specific subdomains (e.g., `nyc`, `lon`) show probe results for each location. The `.localhost` TLD resolves to 127.0.0.1 on most systems.
57
+
58
+ ### What the Generator Creates
59
+
60
+ The `upright:install` generator creates:
61
+
62
+ - `config/initializers/upright.rb` - Engine configuration
63
+ - `config/sites.yml` - Site definitions for each VPS you host Upright on
64
+ - `config/prometheus/prometheus.yml` - Prometheus configuration
65
+ - `config/alertmanager/alertmanager.yml` - AlertManager configuration
66
+ - `config/otel_collector.yml` - OpenTelemetry Collector configuration
67
+ - `probes/` - Directory for all HTTP, SMTP, Traceroute YAML config as well as Playwright probe classes
68
+
69
+ It also mounts the engine at `/` in your routes.
70
+
71
+ ## Configuration
72
+
73
+ ### Basic Setup
74
+
75
+ See `config/initializers/upright.rb`
76
+
77
+ ### Hostname Configuration
78
+
79
+ Upright uses subdomain-based routing. Configure your production hostname:
80
+
81
+ ```ruby
82
+ # config/initializers/upright.rb
83
+ Upright.configure do |config|
84
+ config.hostname = "upright.com"
85
+ end
86
+ ```
87
+
88
+ For local development, the hostname defaults to `{service_name}.localhost` (e.g., `upright.localhost`).
89
+
90
+ ### Site Configuration
91
+
92
+ Define your monitoring locations in `config/sites.yml`:
93
+
94
+ ```yaml
95
+ shared:
96
+ sites:
97
+ - code: nyc
98
+ city: New York City
99
+ country: US
100
+ geohash: dr5reg
101
+ provider: digitalocean
102
+
103
+ - code: ams
104
+ city: Amsterdam
105
+ country: NL
106
+ geohash: u17982
107
+ provider: digitalocean
108
+
109
+ - code: sfo
110
+ city: San Francisco
111
+ country: US
112
+ geohash: 9q8yy
113
+ provider: hetzner
114
+ ```
115
+
116
+ Each site node identifies itself via the `SITE_SUBDOMAIN` environment variable, configured in your Kamal deploy.yml.
117
+
118
+ ### Authentication
119
+
120
+ #### Static Credentials
121
+
122
+ Upright uses static credentials by default with username `admin` and password `upright`.
123
+
124
+ > [!WARNING]
125
+ > Change the default password before deploying to production by setting the `ADMIN_PASSWORD` environment variable.
126
+
127
+
128
+ #### OpenID Connect
129
+
130
+ For production environments, Upright supports OpenID Connect (Logto, Keycloak, Duo, Okta, etc.):
131
+
132
+ ```ruby
133
+ # config/initializers/upright.rb
134
+ Upright.configure do |config|
135
+ config.auth_provider = :openid_connect
136
+ config.auth_options = {
137
+ issuer: "https://your-tenant.logto.app/oidc",
138
+ client_id: ENV["OIDC_CLIENT_ID"],
139
+ client_secret: ENV["OIDC_CLIENT_SECRET"]
140
+ }
141
+ end
142
+ ```
143
+
144
+ ## Defining Probes
145
+
146
+ ### HTTP Probes
147
+
148
+ Add probes to `probes/http_probes.yml`:
149
+
150
+ ```yaml
151
+ - name: Main Website
152
+ url: https://example.com
153
+ expected_status: 200
154
+
155
+ - name: API Health
156
+ url: https://api.example.com/health
157
+ expected_status: 200
158
+
159
+ - name: Admin Panel
160
+ url: https://admin.example.com
161
+ basic_auth_credentials: admin_auth # Key in Rails credentials
162
+ ```
163
+
164
+ ### SMTP Probes
165
+
166
+ Add probes to `probes/smtp_probes.yml`:
167
+
168
+ ```yaml
169
+ - name: Primary Mail Server
170
+ host: mail.example.com
171
+
172
+ - name: Backup Mail Server
173
+ host: mail2.example.com
174
+ ```
175
+
176
+ ### Playwright Probes
177
+
178
+ Generate a new browser-based probe:
179
+
180
+ ```bash
181
+ bin/rails generate upright:playwright_probe MyServiceAuth
182
+ ```
183
+
184
+ This creates a probe class:
185
+
186
+ ```ruby
187
+ # probes/my_service_auth_probe.rb
188
+ class Probes::Playwright::MyServiceAuthProbe < Upright::Probes::Playwright::Base
189
+ # Optionally authenticate before running
190
+ # authenticate_with_form :my_service
191
+
192
+ def check
193
+ page.goto("https://app.example.com")
194
+ page.fill('[name="email"]', "test@example.com")
195
+ page.click('button[type="submit"]')
196
+ page.wait_for_selector(".dashboard")
197
+ end
198
+ end
199
+ ```
200
+
201
+ See https://playwright-ruby-client.vercel.app/docs/api/page for how to create Playwright tests.
202
+
203
+ #### Creating Authenticators
204
+
205
+ For probes that require authentication, create an authenticator:
206
+
207
+ ```ruby
208
+ # probes/authenticators/my_service.rb
209
+ class Playwright::Authenticator::MyService < Upright::Playwright::Authenticator::Base
210
+ def signin_redirect_url = "https://app.example.com/dashboard"
211
+ def signin_path = "/login"
212
+ def service_name = :my_service
213
+
214
+ def authenticate
215
+ page.goto("https://app.example.com/login")
216
+ page.get_by_label("Email").fill(credentials.my_service.email)
217
+ page.get_by_label("Password").fill(credentials.my_service.password)
218
+ page.get_by_role("button", name: "Sign in").click
219
+ end
220
+ end
221
+ ```
222
+
223
+ ## Scheduling
224
+
225
+ Configure probe scheduling with Solid Queue in `config/recurring.yml`:
226
+
227
+ ```yaml
228
+ production:
229
+ http_probes:
230
+ class: Upright::ProbeCheckJob
231
+ args: [http]
232
+ schedule: every 30 seconds
233
+
234
+ smtp_probes:
235
+ class: Upright::ProbeCheckJob
236
+ args: [smtp]
237
+ schedule: every 30 seconds
238
+
239
+ my_service_auth:
240
+ class: Upright::ProbeCheckJob
241
+ args: [playwright, MyServiceAuth]
242
+ schedule: every 15 minutes
243
+ ```
244
+
245
+ ## System Requirements
246
+
247
+ ### Minimum VM Specifications
248
+
249
+ | Resource | Minimum | Recommended |
250
+ |----------|---------|-------------|
251
+ | CPU | 2 vCPU | 2 vCPU |
252
+ | RAM | 2 GB | 4 GB |
253
+ | Disk | 25 GB | 50 GB |
254
+
255
+ Playwright browser automation is memory-intensive. For sites running many Playwright probes concurrently, consider 4 GB RAM.
256
+
257
+ ### Software Requirements
258
+
259
+ - **OS**: Ubuntu 24.04+ or Debian 12+ (any Linux with Docker support)
260
+ - **Docker**: 24.0+ (installed automatically by Kamal)
261
+ - **Ruby**: 3.4+ (for local development only; production runs in Docker)
262
+ - **Rails**: 8.0+
263
+
264
+
265
+ ### Firewall Rules
266
+
267
+ Open the following ports:
268
+
269
+ | Port | Protocol | Direction | Purpose |
270
+ |------|----------|-----------|---------|
271
+ | 22 | TCP | Inbound | SSH access |
272
+ | 80 | TCP | Inbound | HTTP (redirects to HTTPS) |
273
+ | 443 | TCP | Inbound | HTTPS |
274
+ | 25 | TCP | Outbound | SMTP probes (if used) |
275
+
276
+ ### SMTP Port 25 Note
277
+
278
+ Most cloud providers block outbound port 25 by default to prevent spam. If you plan to use SMTP probes, you must request port 25 to be unblocked.
279
+
280
+ This is not required for HTTP or Playwright probes.
281
+
282
+ ## DNS Setup
283
+
284
+ For multiple geographic locations, use subdomains for each site. Each subdomain points to a different server:
285
+
286
+ ```
287
+ ; Primary dashboard
288
+ app.upright.example.com A 203.0.113.10
289
+
290
+ ; Monitoring nodes
291
+ ams.upright.example.com A 203.0.113.10 ; Amsterdam
292
+ nyc.upright.example.com A 198.51.100.20 ; New York
293
+ sfo.upright.example.com A 192.0.2.30 ; San Francisco
294
+ ```
295
+
296
+ ## Deployment with Kamal
297
+
298
+ ### Example `config/deploy.yml`
299
+
300
+ ```yaml
301
+ service: upright
302
+ image: your-org/upright
303
+
304
+ servers:
305
+ web:
306
+ hosts:
307
+ - nyc.upright.example.com
308
+ env:
309
+ tags:
310
+ SITE_SUBDOMAIN: nyc
311
+
312
+ hosts:
313
+ - ams.upright.example.com
314
+ env:
315
+ tags:
316
+ SITE_SUBDOMAIN: ams
317
+
318
+ hosts:
319
+ - sfo.upright.example.com
320
+ env:
321
+ tags:
322
+ SITE_SUBDOMAIN: sfo
323
+
324
+ registry:
325
+ server: ghcr.io
326
+ username: your-org
327
+ password:
328
+ - KAMAL_REGISTRY_PASSWORD
329
+
330
+ env:
331
+ clear:
332
+ RAILS_ENV: production
333
+ RAILS_LOG_TO_STDOUT: true
334
+ secret:
335
+ - RAILS_MASTER_KEY
336
+ - OIDC_CLIENT_ID
337
+ - OIDC_CLIENT_SECRET
338
+
339
+ accessories:
340
+ playwright:
341
+ image: mcr.microsoft.com/playwright:v1.55.0-noble
342
+ cmd: npx -y playwright run-server --port 53333
343
+ host: nyc.upright.example.com
344
+ port: 53333
345
+ env:
346
+ clear:
347
+ DEBUG: pw:api
348
+ ```
349
+
350
+ ## Observability
351
+
352
+ ### Prometheus
353
+
354
+ The engine exposes metrics at `/metrics`. Configure Prometheus to scrape:
355
+
356
+ ```yaml
357
+ scrape_configs:
358
+ - job_name: upright
359
+ static_configs:
360
+ - targets: ['localhost:3000']
361
+ metrics_path: /metrics
362
+ ```
363
+
364
+ ### Metrics Exposed
365
+
366
+ - `upright_probe_duration_seconds` - Probe execution duration
367
+ - `upright_probe_success` - Probe success/failure (1/0)
368
+ - `upright_probe_status_code` - HTTP status code for HTTP probes
369
+
370
+ Labels include: `probe_name`, `probe_type`, `site_code`, `site_city`, `site_country`
371
+
372
+ ### AlertManager
373
+
374
+ Example alert rules (`prometheus/rules/upright.rules`):
375
+
376
+ ```yaml
377
+ groups:
378
+ - name: upright
379
+ rules:
380
+ - alert: ProbeDown
381
+ expr: upright_probe_success == 0
382
+ for: 5m
383
+ labels:
384
+ severity: critical
385
+ annotations:
386
+ summary: "Probe {{ $labels.probe_name }} is down"
387
+
388
+ - alert: ProbeSlow
389
+ expr: upright_probe_duration_seconds > 10
390
+ for: 5m
391
+ labels:
392
+ severity: warning
393
+ annotations:
394
+ summary: "Probe {{ $labels.probe_name }} is slow"
395
+ ```
396
+
397
+ ### OpenTelemetry
398
+
399
+ Traces are automatically created for each probe execution. Configure your collector endpoint:
400
+
401
+ ```ruby
402
+ Upright.configure do |config|
403
+ config.otel_endpoint = "https://otel.example.com:4318"
404
+ end
405
+ ```
406
+
407
+ ## Local Development
408
+
409
+ ### Setup
410
+
411
+ ```bash
412
+ bin/setup
413
+ ```
414
+
415
+ This installs dependencies, prepares the database, and starts the dev server.
416
+
417
+ ### Running Services
418
+
419
+ Start supporting Docker services (Playwright server, etc.):
420
+
421
+ ```bash
422
+ bin/services
423
+ ```
424
+
425
+ ### Running the Server
426
+
427
+ ```bash
428
+ bin/dev
429
+ ```
430
+
431
+ Visit http://app.upright.localhost:3000 and sign in with:
432
+ - **Username**: `admin`
433
+ - **Password**: `upright` (or value of `ADMIN_PASSWORD` env var)
434
+
435
+ ### Testing Playwright Probes
436
+
437
+ Run probes with a visible browser window:
438
+
439
+ ```bash
440
+ LOCAL_PLAYWRIGHT=1 bin/rails console
441
+ ```
442
+
443
+ ```ruby
444
+ Probes::Playwright::MyServiceAuthProbe.check
445
+ ```
446
+
447
+ ### Running Tests
448
+
449
+ ```bash
450
+ bin/rails test
451
+ ```
452
+
453
+ ## License
454
+
455
+ The gem is available under the terms of the [O'Saasy License](LICENSE.md).
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/setup"
2
+
3
+ APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
4
+ load "rails/tasks/engine.rake"
5
+
6
+ require "bundler/gem_tasks"
@@ -0,0 +1,104 @@
1
+ @layer reset, base, components, utilities;
2
+
3
+ :root {
4
+ /* Spacing */
5
+ --inline-space: 1ch;
6
+ --block-space: 1rem;
7
+
8
+ /* Text */
9
+ --font-sans: -apple-system, BlinkMacSystemFont, sans-serif;
10
+ --font-mono: "IBM Plex Mono", "JetBrains Mono", ui-monospace, "Cascadia Code", "Fira Code", monospace;
11
+
12
+ --text-x-small: 0.7rem;
13
+ --text-small: 0.8rem;
14
+ --text-normal: 0.9rem;
15
+ --text-large: 1.25rem;
16
+ --text-x-large: 1.5rem;
17
+
18
+ /* OKLCH colors: Primitives */
19
+ --lch-black: 0% 0 0;
20
+ --lch-white: 100% 0 0;
21
+ --lch-canvas: var(--lch-white);
22
+ --lch-canvas-deep: var(--lch-white);
23
+ --lch-ink-inverted: var(--lch-white);
24
+
25
+ --lch-ink-darkest: 26% 0.05 264;
26
+ --lch-ink-darker: 40% 0.026 262;
27
+ --lch-ink-dark: 56% 0.014 260;
28
+ --lch-ink-medium: 66% 0.008 258;
29
+ --lch-ink-light: 84% 0.005 256;
30
+ --lch-ink-lighter: 92% 0.003 254;
31
+ --lch-ink-lightest: 96% 0.002 252;
32
+
33
+ --lch-blue-dark: 57.02% 0.1895 260.46;
34
+ --lch-blue-lighter: 92% 0.026 254;
35
+
36
+ --lch-green-dark: 55% 0.162 147;
37
+ --lch-red-dark: 59% 0.19 38;
38
+
39
+ /* Colors: Semantic */
40
+ --color-black: oklch(var(--lch-black));
41
+ --color-white: oklch(var(--lch-white));
42
+ --color-canvas: oklch(var(--lch-canvas));
43
+ --color-ink: oklch(var(--lch-ink-darkest));
44
+ --color-ink-dark: oklch(var(--lch-ink-dark));
45
+ --color-ink-medium: oklch(var(--lch-ink-medium));
46
+ --color-ink-light: oklch(var(--lch-ink-light));
47
+ --color-ink-lighter: oklch(var(--lch-ink-lighter));
48
+ --color-ink-lightest: oklch(var(--lch-ink-lightest));
49
+ --color-ink-inverted: oklch(var(--lch-ink-inverted));
50
+
51
+ /* Colors: Abstractions */
52
+ --color-link: oklch(var(--lch-blue-dark));
53
+ --color-positive: oklch(var(--lch-green-dark));
54
+ --color-negative: oklch(var(--lch-red-dark));
55
+ --color-selected: oklch(var(--lch-blue-lighter));
56
+
57
+ /* Borders & Shadows */
58
+ --border: 1px solid var(--color-ink-lighter);
59
+ --shadow: 0 0 0 1px oklch(var(--lch-black) / 5%),
60
+ 0 0.2em 0.2em oklch(var(--lch-black) / 5%),
61
+ 0 0.4em 0.4em oklch(var(--lch-black) / 5%),
62
+ 0 0.8em 0.8em oklch(var(--lch-black) / 5%);
63
+
64
+ /* Focus rings */
65
+ --focus-ring-color: var(--color-link);
66
+ --focus-ring-size: 2px;
67
+
68
+ /* Layout */
69
+ --main-padding: clamp(var(--inline-space), 3vw, calc(var(--inline-space) * 3));
70
+ --main-width: 1400px;
71
+
72
+ /* Grain */
73
+ --grain-opacity: 0.04;
74
+ }
75
+
76
+ /* Dark mode: Update primitive values only */
77
+ @media (prefers-color-scheme: dark) {
78
+ :root {
79
+ --lch-canvas: 18% 0.04 250;
80
+ --lch-canvas-deep: 12% 0.035 255;
81
+ --lch-ink-inverted: var(--lch-black);
82
+
83
+ --lch-ink-darkest: 92% 0.01 250;
84
+ --lch-ink-darker: 80% 0.015 250;
85
+ --lch-ink-dark: 65% 0.02 250;
86
+ --lch-ink-medium: 50% 0.025 250;
87
+ --lch-ink-light: 35% 0.03 250;
88
+ --lch-ink-lighter: 24% 0.035 250;
89
+ --lch-ink-lightest: 20% 0.04 250;
90
+
91
+ --lch-blue-dark: 70% 0.14 250;
92
+ --lch-blue-lighter: 25% 0.05 250;
93
+
94
+ --lch-green-dark: 75% 0.15 145;
95
+ --lch-red-dark: 70% 0.16 42;
96
+
97
+ --shadow: 0 0 0 1px oklch(var(--lch-black) / 0.4),
98
+ 0 .2em 1.6em -0.8em oklch(var(--lch-black) / 0.5),
99
+ 0 .4em 2.4em -1em oklch(var(--lch-black) / 0.6),
100
+ 0 .4em .8em -1.2em oklch(var(--lch-black) / 0.7);
101
+
102
+ --grain-opacity: 0.1;
103
+ }
104
+ }