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.
- checksums.yaml +4 -4
- data/LICENSE.md +2 -3
- data/README.md +166 -60
- data/app/assets/stylesheets/upright/dashboard.css +65 -193
- data/app/assets/stylesheets/upright/tables.css +60 -0
- data/app/assets/stylesheets/upright/uptime-bars.css +13 -50
- data/app/controllers/upright/alertmanager_proxy_controller.rb +1 -1
- data/app/controllers/upright/dashboards/probe_statuses_controller.rb +7 -0
- data/app/controllers/upright/probe_results_controller.rb +1 -0
- data/app/controllers/upright/sessions_controller.rb +1 -0
- data/app/helpers/upright/application_helper.rb +10 -0
- data/app/helpers/upright/dashboards_helper.rb +12 -0
- data/app/helpers/upright/probe_results_helper.rb +3 -10
- data/app/javascript/upright/controllers/auto_refresh_controller.js +16 -0
- data/app/models/concerns/upright/playwright/form_authentication.rb +0 -3
- data/app/models/concerns/upright/playwright/lifecycle.rb +3 -2
- data/app/models/concerns/upright/probe_result/stale_cleanup.rb +23 -0
- data/app/models/concerns/upright/probeable.rb +7 -1
- data/app/models/upright/http/request.rb +1 -1
- data/app/models/upright/playwright/storage_state.rb +12 -3
- data/app/models/upright/probe_result.rb +6 -1
- data/app/models/upright/probes/http_probe.rb +6 -2
- data/app/models/upright/probes/status/probe.rb +24 -0
- data/app/models/upright/probes/status/site_status.rb +71 -0
- data/app/models/upright/probes/status.rb +54 -0
- data/app/models/upright/probes/uptime.rb +4 -4
- data/app/models/upright/traceroute/ip_metadata_lookup.rb +1 -2
- data/app/views/layouts/upright/_header.html.erb +2 -1
- data/app/views/upright/dashboards/_uptime_bars.html.erb +2 -2
- data/app/views/upright/dashboards/_uptime_probe_row.html.erb +7 -5
- data/app/views/upright/dashboards/probe_statuses/_matrix.html.erb +48 -0
- data/app/views/upright/dashboards/probe_statuses/show.html.erb +17 -0
- data/app/views/upright/dashboards/uptimes/show.html.erb +1 -1
- data/app/views/upright/probe_results/index.html.erb +7 -4
- data/app/views/upright/sites/index.html.erb +1 -1
- data/config/ci.rb +2 -0
- data/config/credentials/development.key +1 -0
- data/config/credentials/test.key +1 -0
- data/config/routes.rb +1 -0
- data/lib/generators/upright/install/install_generator.rb +52 -2
- data/lib/generators/upright/install/templates/http_probes.yml +1 -0
- data/lib/generators/upright/install/templates/recurring.yml +25 -0
- data/lib/generators/upright/install/templates/smtp_probes.yml +3 -0
- data/lib/generators/upright/install/templates/traceroute_probes.yml +12 -0
- data/lib/generators/upright/install/templates/upright.rb +4 -1
- data/lib/generators/upright/install/templates/upright.rules.yml +5 -5
- data/lib/upright/configuration.rb +14 -0
- data/lib/upright/engine.rb +7 -0
- data/lib/upright/geohash.rb +46 -0
- data/lib/upright/metrics.rb +2 -2
- data/lib/upright/probe_type_registry.rb +33 -0
- data/lib/upright/site.rb +1 -3
- data/lib/upright/version.rb +1 -1
- data/lib/upright.rb +6 -1
- metadata +17 -17
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ccb561e794bdafa5e31caed2347f7d371d02e5230ca875c5f507d1d0620d7363
|
|
4
|
+
data.tar.gz: 3bc20100e850cdd93a81e58b7e9b6f3ef9377c44b17bbd7891dbe697699b23a6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 474970b3ccbce05a396ceb2a3575a58272b602d679cf70d55d10538cc546aa31d6142e1de401ee5098c6c3a2733a624cba54e34b79a0dcd9c6af7ad7e8124f0d
|
|
7
|
+
data.tar.gz: 35ed0971379b1c50dd9583f95ed27aa2586f53002e8acd81c05851797d6f2d1c59ced8d6a08817613ecb89ab376d453a9697cf241b82be1409c11f1caa87c6d3
|
data/LICENSE.md
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
#
|
|
1
|
+
# MIT License
|
|
2
2
|
|
|
3
3
|
Copyright © 2026, 37signals LLC.
|
|
4
4
|
|
|
5
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
6
|
|
|
7
|
-
|
|
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.
|
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
9
8
|
|
|
10
9
|
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
CHANGED
|
@@ -2,6 +2,23 @@
|
|
|
2
2
|
|
|
3
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
4
|
|
|
5
|
+
<table>
|
|
6
|
+
<tr>
|
|
7
|
+
<td><img src="docs/screenshots/dashboard.png" alt="Dashboard" width="100%"></td>
|
|
8
|
+
<td><img src="docs/screenshots/uptime.png" alt="Uptime" width="100%"></td>
|
|
9
|
+
</tr>
|
|
10
|
+
<tr>
|
|
11
|
+
<td><em>Site overview with world map</em></td>
|
|
12
|
+
<td><em>30-day uptime history</em></td>
|
|
13
|
+
</tr>
|
|
14
|
+
<tr>
|
|
15
|
+
<td colspan="2"><img src="docs/screenshots/probe-status.png" alt="Probe status" width="100%"></td>
|
|
16
|
+
</tr>
|
|
17
|
+
<tr>
|
|
18
|
+
<td colspan="2"><em>Probe status across all sites</em></td>
|
|
19
|
+
</tr>
|
|
20
|
+
</table>
|
|
21
|
+
|
|
5
22
|
## Features
|
|
6
23
|
|
|
7
24
|
- **Playwright Probes** - Browser-based probes for user flows with video recording and logs
|
|
@@ -14,7 +31,6 @@ Upright is a self-hosted synthetic monitoring system. It provides a framework fo
|
|
|
14
31
|
|
|
15
32
|
### Not Included
|
|
16
33
|
|
|
17
|
-
- **Dashboards** - Instead, Grafana is suggested for monitoring the Prometheus metrics generated here
|
|
18
34
|
- **Notifications** - Instead, Alertmanager is included for alerting and notifications
|
|
19
35
|
- **Hosting** - Instead, you can use a VPS from DigitalOcean, Hetzner, etc.
|
|
20
36
|
|
|
@@ -31,7 +47,8 @@ Upright is a self-hosted synthetic monitoring system. It provides a framework fo
|
|
|
31
47
|
|
|
32
48
|
## Installation
|
|
33
49
|
|
|
34
|
-
|
|
50
|
+
> [!NOTE]
|
|
51
|
+
> Upright is designed to be run in its own Rails app and deployed with Kamal.
|
|
35
52
|
|
|
36
53
|
### Quick Start (New Project)
|
|
37
54
|
|
|
@@ -40,9 +57,9 @@ Create a new Rails application and install Upright:
|
|
|
40
57
|
```bash
|
|
41
58
|
rails new my-upright --database=sqlite3 --skip-test
|
|
42
59
|
cd my-upright
|
|
43
|
-
bundle add upright
|
|
60
|
+
bundle add upright
|
|
44
61
|
bin/rails generate upright:install
|
|
45
|
-
bin/rails db:
|
|
62
|
+
bin/rails db:migrate
|
|
46
63
|
```
|
|
47
64
|
|
|
48
65
|
Start the server:
|
|
@@ -141,6 +158,91 @@ Upright.configure do |config|
|
|
|
141
158
|
end
|
|
142
159
|
```
|
|
143
160
|
|
|
161
|
+
### Probe Result Cleanup
|
|
162
|
+
|
|
163
|
+
Upright automatically cleans up old probe results on a recurring schedule. You can configure the retention thresholds:
|
|
164
|
+
|
|
165
|
+
```ruby
|
|
166
|
+
Upright.configure do |config|
|
|
167
|
+
config.stale_success_threshold = 24.hours # Delete successful results older than this (default: 24 hours)
|
|
168
|
+
config.stale_failure_threshold = 30.days # Delete failed results older than this (default: 30 days)
|
|
169
|
+
config.failure_retention_limit = 20_000 # Keep at most this many failed results (default: 20,000)
|
|
170
|
+
end
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Custom Probe Types
|
|
174
|
+
|
|
175
|
+
Upright ships with four built-in probe types: HTTP, Playwright, SMTP, and Traceroute. You can register your own to extend the system.
|
|
176
|
+
|
|
177
|
+
### 1. Register the type
|
|
178
|
+
|
|
179
|
+
Add it to your initializer so Upright knows about its name and icon:
|
|
180
|
+
|
|
181
|
+
```ruby
|
|
182
|
+
# config/initializers/upright.rb
|
|
183
|
+
Upright.configure do |config|
|
|
184
|
+
config.probe_types.register :ping, name: "Ping", icon: "📶"
|
|
185
|
+
end
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### 2. Create the probe class
|
|
189
|
+
|
|
190
|
+
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`:
|
|
191
|
+
|
|
192
|
+
```ruby
|
|
193
|
+
# probes/ping_probe.rb
|
|
194
|
+
class PingProbe < FrozenRecord::Base
|
|
195
|
+
include Upright::Probeable
|
|
196
|
+
include Upright::ProbeYamlSource
|
|
197
|
+
|
|
198
|
+
stagger_by_site 3.seconds
|
|
199
|
+
|
|
200
|
+
def check
|
|
201
|
+
@ping_output, status = Open3.capture2e("ping", "-c", "1", "-W", "5", host)
|
|
202
|
+
status.success?
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def on_check_recorded(probe_result)
|
|
206
|
+
if @ping_output.present?
|
|
207
|
+
Upright::Artifact.new(name: "ping.log", content: @ping_output).attach_to(probe_result)
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def probe_type = "ping"
|
|
212
|
+
def probe_target = host
|
|
213
|
+
end
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
The `check` method should return a truthy value on success and a falsy value on failure.
|
|
217
|
+
|
|
218
|
+
### 3. Define probes in YAML
|
|
219
|
+
|
|
220
|
+
Create a YAML file matching the class name (e.g., `PingProbe` → `probes/ping_probes.yml`):
|
|
221
|
+
|
|
222
|
+
```yaml
|
|
223
|
+
# probes/ping_probes.yml
|
|
224
|
+
- name: "Cloudflare DNS"
|
|
225
|
+
host: "1.1.1.1"
|
|
226
|
+
|
|
227
|
+
- name: "Google DNS"
|
|
228
|
+
host: "8.8.8.8"
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
Fields defined in the YAML are available as methods on the probe instance (e.g., `host`).
|
|
232
|
+
|
|
233
|
+
### 4. Schedule it
|
|
234
|
+
|
|
235
|
+
Add a recurring job in `config/recurring.yml`:
|
|
236
|
+
|
|
237
|
+
```yaml
|
|
238
|
+
production:
|
|
239
|
+
ping_probes:
|
|
240
|
+
command: "PingProbe.check_and_record_all_later"
|
|
241
|
+
schedule: every 30 seconds
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
The custom type will automatically appear in all dashboard dropdowns and filter links.
|
|
245
|
+
|
|
144
246
|
## Defining Probes
|
|
145
247
|
|
|
146
248
|
### HTTP Probes
|
|
@@ -155,12 +257,15 @@ Add probes to `probes/http_probes.yml`:
|
|
|
155
257
|
- name: API Health
|
|
156
258
|
url: https://api.example.com/health
|
|
157
259
|
expected_status: 200
|
|
260
|
+
alert_severity: critical
|
|
158
261
|
|
|
159
262
|
- name: Admin Panel
|
|
160
263
|
url: https://admin.example.com
|
|
161
264
|
basic_auth_credentials: admin_auth # Key in Rails credentials
|
|
162
265
|
```
|
|
163
266
|
|
|
267
|
+
The optional `alert_severity` field controls the Prometheus alert severity when a probe fails. Values: `medium`, `high` (default), `critical`.
|
|
268
|
+
|
|
164
269
|
### SMTP Probes
|
|
165
270
|
|
|
166
271
|
Add probes to `probes/smtp_probes.yml`:
|
|
@@ -227,18 +332,15 @@ Configure probe scheduling with Solid Queue in `config/recurring.yml`:
|
|
|
227
332
|
```yaml
|
|
228
333
|
production:
|
|
229
334
|
http_probes:
|
|
230
|
-
|
|
231
|
-
args: [http]
|
|
335
|
+
command: "Upright::Probes::HTTPProbe.check_and_record_all_later"
|
|
232
336
|
schedule: every 30 seconds
|
|
233
337
|
|
|
234
338
|
smtp_probes:
|
|
235
|
-
|
|
236
|
-
args: [smtp]
|
|
339
|
+
command: "Upright::Probes::SMTPProbe.check_and_record_all_later"
|
|
237
340
|
schedule: every 30 seconds
|
|
238
341
|
|
|
239
342
|
my_service_auth:
|
|
240
|
-
|
|
241
|
-
args: [playwright, MyServiceAuth]
|
|
343
|
+
command: "Probes::Playwright::MyServiceAuthProbe.check_and_record_later"
|
|
242
344
|
schedule: every 15 minutes
|
|
243
345
|
```
|
|
244
346
|
|
|
@@ -304,70 +406,82 @@ image: your-org/upright
|
|
|
304
406
|
servers:
|
|
305
407
|
web:
|
|
306
408
|
hosts:
|
|
307
|
-
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
hosts:
|
|
313
|
-
- ams.upright.example.com
|
|
314
|
-
env:
|
|
315
|
-
tags:
|
|
316
|
-
SITE_SUBDOMAIN: ams
|
|
317
|
-
|
|
409
|
+
- ams.upright.example.com: [amsterdam]
|
|
410
|
+
- nyc.upright.example.com: [new_york]
|
|
411
|
+
- sfo.upright.example.com: [san_francisco]
|
|
412
|
+
jobs:
|
|
318
413
|
hosts:
|
|
319
|
-
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
414
|
+
- ams.upright.example.com: [amsterdam]
|
|
415
|
+
- nyc.upright.example.com: [new_york]
|
|
416
|
+
- sfo.upright.example.com: [san_francisco]
|
|
417
|
+
cmd: bin/jobs
|
|
323
418
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
-
|
|
419
|
+
proxy:
|
|
420
|
+
app_port: 3000
|
|
421
|
+
ssl: true
|
|
422
|
+
hosts:
|
|
423
|
+
- "*.upright.example.com"
|
|
329
424
|
|
|
330
425
|
env:
|
|
331
|
-
clear:
|
|
332
|
-
RAILS_ENV: production
|
|
333
|
-
RAILS_LOG_TO_STDOUT: true
|
|
334
426
|
secret:
|
|
335
427
|
- RAILS_MASTER_KEY
|
|
336
|
-
|
|
337
|
-
|
|
428
|
+
tags:
|
|
429
|
+
amsterdam:
|
|
430
|
+
SITE_SUBDOMAIN: ams
|
|
431
|
+
new_york:
|
|
432
|
+
SITE_SUBDOMAIN: nyc
|
|
433
|
+
san_francisco:
|
|
434
|
+
SITE_SUBDOMAIN: sfo
|
|
338
435
|
|
|
339
436
|
accessories:
|
|
340
437
|
playwright:
|
|
341
|
-
image:
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
438
|
+
image: jacoblincool/playwright:chromium-server-1.55.0
|
|
439
|
+
port: "127.0.0.1:53333:53333"
|
|
440
|
+
roles:
|
|
441
|
+
- jobs
|
|
442
|
+
|
|
443
|
+
prometheus:
|
|
444
|
+
image: prom/prometheus:v3.2.1
|
|
445
|
+
hosts:
|
|
446
|
+
- ams.upright.example.com
|
|
447
|
+
cmd: >-
|
|
448
|
+
--config.file=/etc/prometheus/prometheus.yml
|
|
449
|
+
--storage.tsdb.path=/prometheus
|
|
450
|
+
--storage.tsdb.retention.time=30d
|
|
451
|
+
--web.enable-otlp-receiver
|
|
452
|
+
files:
|
|
453
|
+
- config/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
|
|
454
|
+
- config/prometheus/rules/upright.rules.yml:/etc/prometheus/rules/upright.rules.yml
|
|
455
|
+
|
|
456
|
+
alertmanager:
|
|
457
|
+
image: prom/alertmanager:v0.28.1
|
|
458
|
+
hosts:
|
|
459
|
+
- ams.upright.example.com
|
|
460
|
+
cmd: --config.file=/etc/alertmanager/alertmanager.yml
|
|
461
|
+
files:
|
|
462
|
+
- config/alertmanager/alertmanager.yml:/etc/alertmanager/alertmanager.yml
|
|
348
463
|
```
|
|
349
464
|
|
|
350
465
|
## Observability
|
|
351
466
|
|
|
352
467
|
### Prometheus
|
|
353
468
|
|
|
354
|
-
|
|
469
|
+
Metrics are exposed via a Puma plugin at `http://0.0.0.0:9394/metrics`. Configure Prometheus to scrape:
|
|
355
470
|
|
|
356
471
|
```yaml
|
|
357
472
|
scrape_configs:
|
|
358
473
|
- job_name: upright
|
|
359
474
|
static_configs:
|
|
360
|
-
- targets: ['localhost:
|
|
361
|
-
metrics_path: /metrics
|
|
475
|
+
- targets: ['localhost:9394']
|
|
362
476
|
```
|
|
363
477
|
|
|
364
478
|
### Metrics Exposed
|
|
365
479
|
|
|
366
480
|
- `upright_probe_duration_seconds` - Probe execution duration
|
|
367
|
-
- `
|
|
368
|
-
- `
|
|
481
|
+
- `upright_probe_up` - Probe status (1 = up, 0 = down)
|
|
482
|
+
- `upright_http_response_status` - HTTP response status code
|
|
369
483
|
|
|
370
|
-
Labels include: `
|
|
484
|
+
Labels include: `type`, `name`, `site_code`, `site_city`, `site_country`
|
|
371
485
|
|
|
372
486
|
### AlertManager
|
|
373
487
|
|
|
@@ -378,20 +492,12 @@ groups:
|
|
|
378
492
|
- name: upright
|
|
379
493
|
rules:
|
|
380
494
|
- alert: ProbeDown
|
|
381
|
-
expr:
|
|
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
|
|
495
|
+
expr: upright_probe_up == 0
|
|
390
496
|
for: 5m
|
|
391
497
|
labels:
|
|
392
|
-
severity:
|
|
498
|
+
severity: "{{ $labels.alert_severity }}"
|
|
393
499
|
annotations:
|
|
394
|
-
summary: "Probe {{ $labels.
|
|
500
|
+
summary: "Probe {{ $labels.name }} is down"
|
|
395
501
|
```
|
|
396
502
|
|
|
397
503
|
### OpenTelemetry
|
|
@@ -452,4 +558,4 @@ bin/rails test
|
|
|
452
558
|
|
|
453
559
|
## License
|
|
454
560
|
|
|
455
|
-
The gem is available under the terms of the [
|
|
561
|
+
The gem is available under the terms of the [MIT License](LICENSE.md).
|
|
@@ -34,227 +34,83 @@
|
|
|
34
34
|
width: auto;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
.
|
|
38
|
-
margin-bottom: calc(var(--block-space) * 2);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
.dashboard-section h2 {
|
|
42
|
-
font-size: var(--text-large);
|
|
43
|
-
margin-bottom: var(--block-space);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
.dashboard-section h3 {
|
|
47
|
-
font-size: var(--text-normal);
|
|
48
|
-
font-weight: 500;
|
|
49
|
-
margin-bottom: var(--block-space);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
.dashboard-loading {
|
|
53
|
-
align-items: center;
|
|
54
|
-
background: oklch(var(--lch-ink-lightest) / 85%);
|
|
55
|
-
border-radius: 0.25rem;
|
|
56
|
-
display: flex;
|
|
57
|
-
gap: var(--inline-space);
|
|
58
|
-
justify-content: center;
|
|
59
|
-
margin-bottom: var(--block-space);
|
|
60
|
-
padding: var(--block-space);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
.dashboard-loading.hidden {
|
|
64
|
-
display: none;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
.loading-spinner {
|
|
68
|
-
animation: spin 1s linear infinite;
|
|
69
|
-
border: 2px solid var(--color-ink-light);
|
|
70
|
-
border-radius: 50%;
|
|
71
|
-
border-top-color: var(--color-link);
|
|
72
|
-
height: 1rem;
|
|
73
|
-
width: 1rem;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
@keyframes spin {
|
|
77
|
-
to { transform: rotate(360deg); }
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
.uptime-card {
|
|
37
|
+
.no-data-message {
|
|
81
38
|
background: oklch(var(--lch-ink-lightest) / 85%);
|
|
82
39
|
border-radius: 0.25rem;
|
|
83
40
|
box-shadow: var(--shadow);
|
|
84
|
-
display: inline-block;
|
|
85
|
-
padding: calc(var(--block-space) * 1.5) calc(var(--block-space) * 2);
|
|
86
|
-
text-align: center;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
.uptime-value {
|
|
90
|
-
font-size: 3rem;
|
|
91
|
-
font-weight: 600;
|
|
92
|
-
letter-spacing: -0.02em;
|
|
93
|
-
line-height: 1;
|
|
94
|
-
margin-bottom: 0.25rem;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
.uptime-label {
|
|
98
41
|
color: var(--color-ink-dark);
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
text-transform: uppercase;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
.uptime-period {
|
|
105
|
-
color: var(--color-ink-medium);
|
|
106
|
-
font-size: var(--text-x-small);
|
|
107
|
-
margin-top: 0.25rem;
|
|
42
|
+
padding: calc(var(--block-space) * 2);
|
|
43
|
+
text-align: center;
|
|
108
44
|
}
|
|
109
45
|
|
|
110
|
-
.
|
|
111
|
-
background: oklch(var(--lch-
|
|
46
|
+
.error-message {
|
|
47
|
+
background: oklch(var(--lch-red-dark) / 15%);
|
|
112
48
|
border-radius: 0.25rem;
|
|
113
|
-
box-shadow: var(--shadow);
|
|
114
|
-
min-height: 250px;
|
|
115
|
-
padding: var(--block-space);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
.dashboard-chart circle {
|
|
119
|
-
stroke: none;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
.dashboard-chart text {
|
|
123
|
-
font-family: var(--font-mono);
|
|
124
|
-
font-size: var(--text-x-small);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
.uptime-table {
|
|
128
|
-
width: 100%;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
.uptime-table tbody tr {
|
|
132
|
-
cursor: default;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
.status-2xx {
|
|
136
|
-
color: var(--color-positive);
|
|
137
|
-
font-weight: 500;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
.status-3xx {
|
|
141
|
-
color: oklch(70% 0.12 250);
|
|
142
|
-
font-weight: 500;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
.status-4xx {
|
|
146
|
-
color: oklch(75% 0.15 85);
|
|
147
|
-
font-weight: 500;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
.status-5xx {
|
|
151
49
|
color: var(--color-negative);
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
.status-unknown {
|
|
156
|
-
color: var(--color-ink-medium);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
.heatmap-wrapper {
|
|
160
|
-
background: oklch(var(--lch-ink-lightest) / 85%);
|
|
161
|
-
border-radius: 0.25rem;
|
|
162
|
-
box-shadow: var(--shadow);
|
|
163
|
-
overflow-x: auto;
|
|
50
|
+
padding: var(--block-space);
|
|
51
|
+
text-align: center;
|
|
164
52
|
}
|
|
165
53
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
54
|
+
/* Probe Status Matrix */
|
|
55
|
+
.probe-status__header,
|
|
56
|
+
.probe-status__row {
|
|
57
|
+
display: grid;
|
|
58
|
+
gap: 0;
|
|
59
|
+
grid-template-columns: minmax(200px, 1fr) repeat(var(--cols), 1fr);
|
|
170
60
|
}
|
|
171
61
|
|
|
172
|
-
.
|
|
173
|
-
.
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
padding: 0.5em 0.75em;
|
|
177
|
-
text-align: center;
|
|
62
|
+
.probe-status__header > *,
|
|
63
|
+
.probe-status__row > * {
|
|
64
|
+
min-width: 8em;
|
|
65
|
+
padding: calc(var(--block-space) * 0.75) var(--block-space);
|
|
178
66
|
white-space: nowrap;
|
|
179
67
|
}
|
|
180
68
|
|
|
181
|
-
.
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
left: 0;
|
|
186
|
-
position: sticky;
|
|
187
|
-
text-align: left;
|
|
69
|
+
.probe-status-cell-link {
|
|
70
|
+
color: inherit;
|
|
71
|
+
display: block;
|
|
72
|
+
text-decoration: none;
|
|
188
73
|
}
|
|
189
74
|
|
|
190
|
-
.
|
|
191
|
-
|
|
192
|
-
font-weight: 400;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
.heatmap-cell {
|
|
196
|
-
font-weight: 500;
|
|
197
|
-
min-width: 3em;
|
|
75
|
+
.probe-status-cell-link:hover {
|
|
76
|
+
opacity: 0.8;
|
|
198
77
|
}
|
|
199
78
|
|
|
200
|
-
.
|
|
201
|
-
background: oklch(var(--lch-
|
|
79
|
+
.probe-status-row--down {
|
|
80
|
+
background: oklch(var(--lch-red-dark) / 8%);
|
|
202
81
|
}
|
|
203
82
|
|
|
204
|
-
.
|
|
205
|
-
background: oklch(
|
|
83
|
+
.probe-status-cell--up {
|
|
84
|
+
background: oklch(var(--lch-green-dark) / 10%);
|
|
206
85
|
}
|
|
207
86
|
|
|
208
|
-
.
|
|
209
|
-
background: oklch(
|
|
87
|
+
.probe-status-cell--down {
|
|
88
|
+
background: oklch(var(--lch-red-dark) / 15%);
|
|
210
89
|
}
|
|
211
90
|
|
|
212
|
-
.
|
|
213
|
-
background: oklch(
|
|
91
|
+
.probe-status-cell--stale {
|
|
92
|
+
background: oklch(75% 0.1 85 / 15%);
|
|
214
93
|
}
|
|
215
94
|
|
|
216
|
-
.
|
|
217
|
-
|
|
95
|
+
.probe-status-indicator {
|
|
96
|
+
font-weight: 600;
|
|
97
|
+
display: block;
|
|
218
98
|
}
|
|
219
99
|
|
|
220
|
-
.
|
|
221
|
-
|
|
100
|
+
.probe-status-indicator--up {
|
|
101
|
+
color: var(--color-positive);
|
|
222
102
|
}
|
|
223
103
|
|
|
224
|
-
.
|
|
225
|
-
color: var(--color-
|
|
226
|
-
white-space: nowrap;
|
|
104
|
+
.probe-status-indicator--down {
|
|
105
|
+
color: var(--color-negative);
|
|
227
106
|
}
|
|
228
107
|
|
|
229
|
-
.
|
|
230
|
-
|
|
108
|
+
.probe-status-indicator--stale {
|
|
109
|
+
color: oklch(75% 0.15 85);
|
|
231
110
|
}
|
|
232
111
|
|
|
233
|
-
.
|
|
112
|
+
.probe-status-indicator--unknown {
|
|
234
113
|
color: var(--color-ink-medium);
|
|
235
|
-
font-size: var(--text-x-small);
|
|
236
|
-
max-width: 300px;
|
|
237
|
-
overflow: hidden;
|
|
238
|
-
text-overflow: ellipsis;
|
|
239
|
-
white-space: nowrap;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
.no-data-message,
|
|
243
|
-
.no-errors-message {
|
|
244
|
-
background: oklch(var(--lch-ink-lightest) / 85%);
|
|
245
|
-
border-radius: 0.25rem;
|
|
246
|
-
box-shadow: var(--shadow);
|
|
247
|
-
color: var(--color-ink-dark);
|
|
248
|
-
padding: calc(var(--block-space) * 2);
|
|
249
|
-
text-align: center;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
.error-message {
|
|
253
|
-
background: oklch(var(--lch-red-dark) / 15%);
|
|
254
|
-
border-radius: 0.25rem;
|
|
255
|
-
color: var(--color-negative);
|
|
256
|
-
padding: var(--block-space);
|
|
257
|
-
text-align: center;
|
|
258
114
|
}
|
|
259
115
|
|
|
260
116
|
@media (max-width: 85ch) {
|
|
@@ -271,17 +127,33 @@
|
|
|
271
127
|
.dashboard-filters {
|
|
272
128
|
flex-wrap: wrap;
|
|
273
129
|
}
|
|
274
|
-
|
|
275
|
-
.uptime-value {
|
|
276
|
-
font-size: 2rem;
|
|
277
|
-
}
|
|
278
130
|
}
|
|
279
131
|
|
|
280
132
|
@media (max-width: 55ch) {
|
|
281
|
-
.
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
133
|
+
.probe-status__header {
|
|
134
|
+
display: none;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.probe-status__row {
|
|
138
|
+
grid-template-columns: 1fr;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.probe-status__row > .sticky-left {
|
|
142
|
+
position: static;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.probe-status__row > [data-label] {
|
|
146
|
+
display: flex;
|
|
147
|
+
align-items: center;
|
|
148
|
+
gap: var(--inline-space);
|
|
149
|
+
text-align: left;
|
|
150
|
+
|
|
151
|
+
&::before {
|
|
152
|
+
content: attr(data-label);
|
|
153
|
+
font-size: var(--text-x-small);
|
|
154
|
+
font-weight: 500;
|
|
155
|
+
min-width: 10em;
|
|
156
|
+
}
|
|
285
157
|
}
|
|
286
158
|
}
|
|
287
159
|
}
|