upright 0.1.2 → 0.2.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 +23 -10
- 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 +3 -49
- data/app/controllers/upright/alertmanager_proxy_controller.rb +1 -1
- data/app/controllers/upright/dashboards/probe_statuses_controller.rb +7 -0
- data/app/helpers/upright/application_helper.rb +10 -0
- data/app/helpers/upright/dashboards_helper.rb +12 -0
- data/app/javascript/upright/controllers/auto_refresh_controller.js +16 -0
- data/app/models/concerns/upright/playwright/lifecycle.rb +3 -2
- data/app/models/upright/playwright/storage_state.rb +12 -3
- 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/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 +3 -3
- 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/sites/index.html.erb +1 -1
- 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 +9 -0
- data/lib/generators/upright/install/templates/traceroute_probes.yml +9 -0
- data/lib/generators/upright/install/templates/upright.rb +1 -1
- data/lib/upright/geohash.rb +46 -0
- data/lib/upright/site.rb +1 -3
- data/lib/upright/version.rb +1 -1
- data/lib/upright.rb +1 -1
- metadata +14 -17
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1d1bb4a1f14f8e4dc16cf5bebcd5868917b2c0703f6dd94592d6f8c818dfa0b9
|
|
4
|
+
data.tar.gz: 4d3a0593d1f49f256145340f3e64c9ab6f467a759f1c0a60e81e58635528e0d1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5d06f946c9874af94e42bca38bb58671e227b3c4f2ac2f9a523d36b3efca04771a3d0a087bb2644ea067e5a8094d35e1370ee85ded8ea140af084afbf4a4e29a
|
|
7
|
+
data.tar.gz: c2ac9d3d2822197887e22308dbdce7be1b47692825dcb25cbb660f0de8e24643788b2cd5a63c72c4863041b619a35e3b31b9e241c69861b59ff66c8398fcd9b2
|
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
|
|
|
@@ -40,9 +56,9 @@ Create a new Rails application and install Upright:
|
|
|
40
56
|
```bash
|
|
41
57
|
rails new my-upright --database=sqlite3 --skip-test
|
|
42
58
|
cd my-upright
|
|
43
|
-
bundle add upright
|
|
59
|
+
bundle add upright
|
|
44
60
|
bin/rails generate upright:install
|
|
45
|
-
bin/rails db:
|
|
61
|
+
bin/rails db:migrate
|
|
46
62
|
```
|
|
47
63
|
|
|
48
64
|
Start the server:
|
|
@@ -227,18 +243,15 @@ Configure probe scheduling with Solid Queue in `config/recurring.yml`:
|
|
|
227
243
|
```yaml
|
|
228
244
|
production:
|
|
229
245
|
http_probes:
|
|
230
|
-
|
|
231
|
-
args: [http]
|
|
246
|
+
command: "Upright::Probes::HTTPProbe.check_and_record_all_later"
|
|
232
247
|
schedule: every 30 seconds
|
|
233
248
|
|
|
234
249
|
smtp_probes:
|
|
235
|
-
|
|
236
|
-
args: [smtp]
|
|
250
|
+
command: "Upright::Probes::SMTPProbe.check_and_record_all_later"
|
|
237
251
|
schedule: every 30 seconds
|
|
238
252
|
|
|
239
253
|
my_service_auth:
|
|
240
|
-
|
|
241
|
-
args: [playwright, MyServiceAuth]
|
|
254
|
+
command: "Probes::Playwright::MyServiceAuthProbe.check_and_record_later"
|
|
242
255
|
schedule: every 15 minutes
|
|
243
256
|
```
|
|
244
257
|
|
|
@@ -452,4 +465,4 @@ bin/rails test
|
|
|
452
465
|
|
|
453
466
|
## License
|
|
454
467
|
|
|
455
|
-
The gem is available under the terms of the [
|
|
468
|
+
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
|
}
|
|
@@ -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-
|
|
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,7 +37,7 @@
|
|
|
83
37
|
color: var(--color-negative);
|
|
84
38
|
}
|
|
85
39
|
|
|
86
|
-
/* Individual uptime bar
|
|
40
|
+
/* Individual uptime bar */
|
|
87
41
|
.uptime-bar {
|
|
88
42
|
border-radius: 2px;
|
|
89
43
|
cursor: default;
|
|
@@ -131,7 +85,7 @@
|
|
|
131
85
|
gap: calc(var(--block-space) * 0.5);
|
|
132
86
|
}
|
|
133
87
|
|
|
134
|
-
.
|
|
88
|
+
.data-table__probe .probe__badge {
|
|
135
89
|
display: none;
|
|
136
90
|
}
|
|
137
91
|
}
|
|
@@ -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
|
|
@@ -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
|
|
@@ -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
|
+
}
|
|
@@ -33,8 +33,9 @@ module Upright::Playwright::Lifecycle
|
|
|
33
33
|
run_callbacks :page_ready
|
|
34
34
|
yield
|
|
35
35
|
ensure
|
|
36
|
-
|
|
37
|
-
|
|
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
|
|
|
@@ -8,12 +8,16 @@ class Upright::Playwright::StorageState
|
|
|
8
8
|
end
|
|
9
9
|
|
|
10
10
|
def load
|
|
11
|
-
|
|
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
|
-
|
|
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}.
|
|
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
|
|
@@ -74,11 +74,15 @@ class Upright::Probes::HTTPProbe < FrozenRecord::Base
|
|
|
74
74
|
end
|
|
75
75
|
|
|
76
76
|
def proxy_credentials
|
|
77
|
-
if
|
|
78
|
-
Rails.application.credentials.dig(:proxies,
|
|
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
|
+
if probe_type.present?
|
|
21
|
+
"{type=\"#{probe_type}\"}"
|
|
22
|
+
end
|
|
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
|
|
@@ -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
|
|
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="
|
|
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>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
<div class="uptime-bars__row">
|
|
2
|
-
<%= link_to upright.site_root_url(subdomain: Upright.sites.first.code, probe_type: probe.type, probe_name: probe.name), class: "
|
|
1
|
+
<div class="data-table__row uptime-bars__row">
|
|
2
|
+
<%= link_to upright.site_root_url(subdomain: Upright.sites.first.code, probe_type: probe.type, probe_name: probe.name), class: "data-table__probe" do %>
|
|
3
3
|
<%= probe_type_icon(probe.type) %>
|
|
4
|
-
<span class="
|
|
4
|
+
<span class="data-table__probe-name"><%= probe.name %></span>
|
|
5
5
|
<% end %>
|
|
6
6
|
<div class="uptime-bars__days">
|
|
7
7
|
<% dates.each do |date| %>
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
<% if probes.empty? %>
|
|
2
|
+
<div class="no-data-message">
|
|
3
|
+
No probe data available for the selected type.
|
|
4
|
+
</div>
|
|
5
|
+
<% else %>
|
|
6
|
+
<div class="scrollable-x">
|
|
7
|
+
<div class="data-table" style="--cols: <%= sites.size %>">
|
|
8
|
+
<div class="data-table__header probe-status__header">
|
|
9
|
+
<div class="sticky-left">Probe</div>
|
|
10
|
+
<% sites.each do |site| %>
|
|
11
|
+
<div class="text-center"><%= site_name(site) %></div>
|
|
12
|
+
<% end %>
|
|
13
|
+
</div>
|
|
14
|
+
<% probes.each do |probe| %>
|
|
15
|
+
<div class="data-table__row probe-status__row <%= "probe-status-row--down" if probe.any_down? %>">
|
|
16
|
+
<div class="sticky-left">
|
|
17
|
+
<%= link_to upright.site_root_url(subdomain: Upright.sites.first.code, probe_type: probe.type, probe_name: probe.name), class: "data-table__probe" do %>
|
|
18
|
+
<%= probe_type_icon(probe.type) %>
|
|
19
|
+
<span><%= probe.name %></span>
|
|
20
|
+
<% end %>
|
|
21
|
+
</div>
|
|
22
|
+
<% sites.each do |site| %>
|
|
23
|
+
<% status = probe.status_for_site(site.code) %>
|
|
24
|
+
<div class="text-center <%= probe_status_css_class(status) %>" data-label="<%= site_name(site) %>">
|
|
25
|
+
<%= link_to upright.site_root_url(subdomain: site.code, probe_type: probe.type, probe_name: probe.name), class: "probe-status-cell-link" do %>
|
|
26
|
+
<% if status.nil? %>
|
|
27
|
+
<span class="probe-status-indicator probe-status-indicator--unknown">—</span>
|
|
28
|
+
<% elsif status.stale? %>
|
|
29
|
+
<span class="probe-status-indicator probe-status-indicator--stale">STALE</span>
|
|
30
|
+
<% elsif status.up? %>
|
|
31
|
+
<span class="probe-status-indicator probe-status-indicator--up">UP</span>
|
|
32
|
+
<% else %>
|
|
33
|
+
<span class="probe-status-indicator probe-status-indicator--down">
|
|
34
|
+
<% if status.down_since_known? %>
|
|
35
|
+
DOWN for <%= time_ago_in_words(status.down_since) %>
|
|
36
|
+
<% else %>
|
|
37
|
+
DOWN
|
|
38
|
+
<% end %>
|
|
39
|
+
</span>
|
|
40
|
+
<% end %>
|
|
41
|
+
<% end %>
|
|
42
|
+
</div>
|
|
43
|
+
<% end %>
|
|
44
|
+
</div>
|
|
45
|
+
<% end %>
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
<% end %>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<div class="dashboard probe-status-dashboard">
|
|
2
|
+
<header class="dashboard-header">
|
|
3
|
+
<h1>Probe Status</h1>
|
|
4
|
+
|
|
5
|
+
<div class="dashboard-filters">
|
|
6
|
+
<%= form_with url: dashboards_probe_status_path, method: :get, data: { controller: "form", turbo_frame: :probe_status_matrix } do %>
|
|
7
|
+
<%= collection_select nil, :probe_type, Upright::Probeable::TYPES, :itself, :titleize,
|
|
8
|
+
{ selected: @probe_type },
|
|
9
|
+
{ class: "input--select", data: { action: "change->form#submit" } } %>
|
|
10
|
+
<% end %>
|
|
11
|
+
</div>
|
|
12
|
+
</header>
|
|
13
|
+
|
|
14
|
+
<turbo-frame id="probe_status_matrix" data-controller="auto-refresh" data-auto-refresh-interval-value="60000">
|
|
15
|
+
<%= render "matrix", probes: @probes, sites: @sites %>
|
|
16
|
+
</turbo-frame>
|
|
17
|
+
</div>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
40451408e038d0fe36a8f4aed0cef429
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
40451408e038d0fe36a8f4aed0cef429
|
data/config/routes.rb
CHANGED
|
@@ -19,6 +19,7 @@ module Upright
|
|
|
19
19
|
empty_directory "probes/authenticators"
|
|
20
20
|
template "http_probes.yml", "probes/http_probes.yml"
|
|
21
21
|
template "smtp_probes.yml", "probes/smtp_probes.yml"
|
|
22
|
+
template "traceroute_probes.yml", "probes/traceroute_probes.yml"
|
|
22
23
|
end
|
|
23
24
|
|
|
24
25
|
def copy_observability_configs
|
|
@@ -46,6 +47,14 @@ module Upright
|
|
|
46
47
|
route 'mount Upright::Engine => "/", as: :upright'
|
|
47
48
|
end
|
|
48
49
|
|
|
50
|
+
def install_active_storage
|
|
51
|
+
rails_command "active_storage:install"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def configure_javascript
|
|
55
|
+
append_to_file "app/javascript/application.js", 'import "upright/application"'
|
|
56
|
+
end
|
|
57
|
+
|
|
49
58
|
def show_post_install_message
|
|
50
59
|
say ""
|
|
51
60
|
say "Upright has been installed!", :green
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Upright.configure do |config|
|
|
4
4
|
config.service_name = "<%= Rails.application.class.module_parent_name.underscore %>"
|
|
5
5
|
config.user_agent = "<%= Rails.application.class.module_parent_name.underscore %>/1.0"
|
|
6
|
-
config.hostname = Rails.env.local? ? "<%= Rails.application.class.module_parent_name.
|
|
6
|
+
config.hostname = Rails.env.local? ? "<%= Rails.application.class.module_parent_name.underscore.dasherize %>.localhost" : "<%= Rails.application.class.module_parent_name.underscore.dasherize %>.com"
|
|
7
7
|
|
|
8
8
|
# Playwright browser server URL
|
|
9
9
|
# config.playwright_server_url = ENV["PLAYWRIGHT_SERVER_URL"]
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
module Upright
|
|
2
|
+
module Geohash
|
|
3
|
+
BASE32 = "0123456789bcdefghjkmnpqrstuvwxyz"
|
|
4
|
+
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
def decode(geohash)
|
|
8
|
+
bounds = [ [ -90.0, +90.0 ], [ -180.0, +180.0 ] ]
|
|
9
|
+
|
|
10
|
+
geohash.downcase.each_char.with_index do |c, i|
|
|
11
|
+
d = BASE32.index c
|
|
12
|
+
|
|
13
|
+
5.times do |j|
|
|
14
|
+
bit = (d & (1 << (4 - j))) >> (4 - j)
|
|
15
|
+
k = (~i & 1) ^ (j & 1)
|
|
16
|
+
bounds[k][bit ^ 1] = (bounds[k][0] + bounds[k][1]) / 2
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
bounds.transpose
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def encode(latitude, longitude, precision = 12)
|
|
24
|
+
mids = [ latitude, longitude ]
|
|
25
|
+
bounds = [ [ -90.0, +90.0 ], [ -180.0, +180.0 ] ]
|
|
26
|
+
|
|
27
|
+
geohash = +""
|
|
28
|
+
|
|
29
|
+
precision.times do |i|
|
|
30
|
+
d = 0
|
|
31
|
+
|
|
32
|
+
5.times do |j|
|
|
33
|
+
k = (~i & 1) ^ (j & 1)
|
|
34
|
+
mid = (bounds[k][0] + bounds[k][1]) / 2
|
|
35
|
+
bit = mids[k] > mid ? 1 : 0
|
|
36
|
+
bounds[k][bit ^ 1] = mid
|
|
37
|
+
d |= bit << (4 - j)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
geohash << BASE32[d]
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
geohash
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
data/lib/upright/site.rb
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
require "geohash_ruby"
|
|
2
|
-
|
|
3
1
|
module Upright
|
|
4
2
|
class Site
|
|
5
3
|
attr_reader :code, :city, :country, :geohash, :stagger_index
|
|
@@ -43,7 +41,7 @@ module Upright
|
|
|
43
41
|
|
|
44
42
|
private
|
|
45
43
|
def coordinates
|
|
46
|
-
@coordinates ||= Geohash.decode(geohash).first
|
|
44
|
+
@coordinates ||= Upright::Geohash.decode(geohash).first
|
|
47
45
|
end
|
|
48
46
|
end
|
|
49
47
|
end
|
data/lib/upright/version.rb
CHANGED
data/lib/upright.rb
CHANGED
|
@@ -14,12 +14,12 @@ require "importmap-rails"
|
|
|
14
14
|
require "turbo-rails"
|
|
15
15
|
require "stimulus-rails"
|
|
16
16
|
require "geared_pagination"
|
|
17
|
-
require "geohash_ruby"
|
|
18
17
|
require "yabeda/prometheus"
|
|
19
18
|
require "yabeda/puma/plugin"
|
|
20
19
|
|
|
21
20
|
require "upright/version"
|
|
22
21
|
require "upright/configuration"
|
|
22
|
+
require "upright/geohash"
|
|
23
23
|
require "upright/site"
|
|
24
24
|
require "upright/metrics"
|
|
25
25
|
require "upright/tracing"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: upright
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Lewis Buckley
|
|
@@ -163,20 +163,6 @@ dependencies:
|
|
|
163
163
|
- - ">="
|
|
164
164
|
- !ruby/object:Gem::Version
|
|
165
165
|
version: '0'
|
|
166
|
-
- !ruby/object:Gem::Dependency
|
|
167
|
-
name: geohash_ruby
|
|
168
|
-
requirement: !ruby/object:Gem::Requirement
|
|
169
|
-
requirements:
|
|
170
|
-
- - ">="
|
|
171
|
-
- !ruby/object:Gem::Version
|
|
172
|
-
version: '0'
|
|
173
|
-
type: :runtime
|
|
174
|
-
prerelease: false
|
|
175
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
176
|
-
requirements:
|
|
177
|
-
- - ">="
|
|
178
|
-
- !ruby/object:Gem::Version
|
|
179
|
-
version: '0'
|
|
180
166
|
- !ruby/object:Gem::Dependency
|
|
181
167
|
name: playwright-ruby-client
|
|
182
168
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -390,6 +376,7 @@ files:
|
|
|
390
376
|
- app/controllers/upright/alertmanager_proxy_controller.rb
|
|
391
377
|
- app/controllers/upright/application_controller.rb
|
|
392
378
|
- app/controllers/upright/artifacts_controller.rb
|
|
379
|
+
- app/controllers/upright/dashboards/probe_statuses_controller.rb
|
|
393
380
|
- app/controllers/upright/dashboards/uptimes_controller.rb
|
|
394
381
|
- app/controllers/upright/jobs_controller.rb
|
|
395
382
|
- app/controllers/upright/probe_results_controller.rb
|
|
@@ -401,6 +388,7 @@ files:
|
|
|
401
388
|
- app/helpers/upright/probe_results_helper.rb
|
|
402
389
|
- app/javascript/upright/application.js
|
|
403
390
|
- app/javascript/upright/controllers/application.js
|
|
391
|
+
- app/javascript/upright/controllers/auto_refresh_controller.js
|
|
404
392
|
- app/javascript/upright/controllers/form_controller.js
|
|
405
393
|
- app/javascript/upright/controllers/index.js
|
|
406
394
|
- app/javascript/upright/controllers/popover_controller.js
|
|
@@ -431,6 +419,9 @@ files:
|
|
|
431
419
|
- app/models/upright/probes/http_probe.rb
|
|
432
420
|
- app/models/upright/probes/playwright/base.rb
|
|
433
421
|
- app/models/upright/probes/smtp_probe.rb
|
|
422
|
+
- app/models/upright/probes/status.rb
|
|
423
|
+
- app/models/upright/probes/status/probe.rb
|
|
424
|
+
- app/models/upright/probes/status/site_status.rb
|
|
434
425
|
- app/models/upright/probes/traceroute_probe.rb
|
|
435
426
|
- app/models/upright/probes/uptime.rb
|
|
436
427
|
- app/models/upright/probes/uptime/summary.rb
|
|
@@ -446,6 +437,8 @@ files:
|
|
|
446
437
|
- app/views/upright/artifacts/show.html.erb
|
|
447
438
|
- app/views/upright/dashboards/_uptime_bars.html.erb
|
|
448
439
|
- app/views/upright/dashboards/_uptime_probe_row.html.erb
|
|
440
|
+
- app/views/upright/dashboards/probe_statuses/_matrix.html.erb
|
|
441
|
+
- app/views/upright/dashboards/probe_statuses/show.html.erb
|
|
449
442
|
- app/views/upright/dashboards/uptimes/show.html.erb
|
|
450
443
|
- app/views/upright/jobs/show.html.erb
|
|
451
444
|
- app/views/upright/probe_results/_pagination.html.erb
|
|
@@ -455,6 +448,8 @@ files:
|
|
|
455
448
|
- app/views/upright/sites/index.html.erb
|
|
456
449
|
- config/brakeman.ignore
|
|
457
450
|
- config/ci.rb
|
|
451
|
+
- config/credentials/development.key
|
|
452
|
+
- config/credentials/test.key
|
|
458
453
|
- config/importmap.rb
|
|
459
454
|
- config/routes.rb
|
|
460
455
|
- db/migrate/20250114000001_create_upright_probe_results.rb
|
|
@@ -471,6 +466,7 @@ files:
|
|
|
471
466
|
- lib/generators/upright/install/templates/puma.rb
|
|
472
467
|
- lib/generators/upright/install/templates/sites.yml
|
|
473
468
|
- lib/generators/upright/install/templates/smtp_probes.yml
|
|
469
|
+
- lib/generators/upright/install/templates/traceroute_probes.yml
|
|
474
470
|
- lib/generators/upright/install/templates/upright.rb
|
|
475
471
|
- lib/generators/upright/install/templates/upright.rules.yml
|
|
476
472
|
- lib/generators/upright/playwright_probe/playwright_probe_generator.rb
|
|
@@ -481,6 +477,7 @@ files:
|
|
|
481
477
|
- lib/upright.rb
|
|
482
478
|
- lib/upright/configuration.rb
|
|
483
479
|
- lib/upright/engine.rb
|
|
480
|
+
- lib/upright/geohash.rb
|
|
484
481
|
- lib/upright/metrics.rb
|
|
485
482
|
- lib/upright/playwright/collect_performance_metrics.js
|
|
486
483
|
- lib/upright/site.rb
|
|
@@ -488,7 +485,7 @@ files:
|
|
|
488
485
|
- lib/upright/version.rb
|
|
489
486
|
homepage: https://github.com/basecamp/upright
|
|
490
487
|
licenses:
|
|
491
|
-
-
|
|
488
|
+
- MIT
|
|
492
489
|
metadata:
|
|
493
490
|
homepage_uri: https://github.com/basecamp/upright
|
|
494
491
|
source_code_uri: https://github.com/basecamp/upright
|
|
@@ -507,7 +504,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
507
504
|
- !ruby/object:Gem::Version
|
|
508
505
|
version: '0'
|
|
509
506
|
requirements: []
|
|
510
|
-
rubygems_version: 4.0.
|
|
507
|
+
rubygems_version: 4.0.3
|
|
511
508
|
specification_version: 4
|
|
512
509
|
summary: Synthetic monitoring engine with Playwright and Prometheus metrics
|
|
513
510
|
test_files: []
|