watchfor 0.13.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +63 -5
- data/exe/watchfor +23 -3
- data/lib/watchfor/client.rb +113 -13
- data/lib/watchfor/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f5ad40afc1187be8bcdf41c4c418b3c18b304b097a901219edfe777ae41087d1
|
|
4
|
+
data.tar.gz: b4da046bc763240825953b51a69e13e50838272d7e642bf78f450b4f7914810d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 73e41e8dc6673385ab4bfca74bb41a6e2d83d08429b88ff1bf826c835201295eda5c5b1edd76cf32518cb86ba9829b4b8d9b4641e9f1ee0f5a50743e2fe848e6
|
|
7
|
+
data.tar.gz: 1a4951d407a131eec5206d4b909de80a26f4833aaa916a08aa914846c6f71b8b6161cb5ceb90b982c3a0a2ce91fb465d5f7c297ce7bac2db92215e3b3481160d
|
data/README.md
CHANGED
|
@@ -40,8 +40,8 @@ wf.incidents.exclude(19351, reason: "Probe-side DNS hiccup")
|
|
|
40
40
|
# Irreversible: wipe a monitor's checks and incidents; uptime restarts "since reset"
|
|
41
41
|
wf.monitors.reset(mon["id"])
|
|
42
42
|
|
|
43
|
-
# Page integrity: after a deploy,
|
|
44
|
-
wf.monitors.accept_baseline(integrity_monitor_id)
|
|
43
|
+
# Page integrity: after a deploy, check the pages and accept what that check saw
|
|
44
|
+
wf.monitors.accept_baseline(integrity_monitor_id, fresh: true)
|
|
45
45
|
|
|
46
46
|
# Browser checks: try a Playwright script once (nothing saved), then read the result
|
|
47
47
|
run = wf.playwright.test_run(script: script, target: "https://shop.example.com")
|
|
@@ -59,11 +59,24 @@ wf.monitors.checks(mon["id"], hours: 24, limit: 100, cursor: page["next_cursor"]
|
|
|
59
59
|
|
|
60
60
|
# Delete a contact group that alert rules still use (409 without force)
|
|
61
61
|
wf.contact_groups.delete("<group-id>", force: true)
|
|
62
|
+
|
|
63
|
+
# What the alerting engine sees right now: alerted, recovering 1/3, confirming 2 of 3…
|
|
64
|
+
state = wf.monitors.get(mon["id"])["alerting_state"]
|
|
65
|
+
|
|
66
|
+
# Response times on a real time axis (5-min buckets for 24h, 15 min for 7d)
|
|
67
|
+
up = wf.monitors.uptime(mon["id"], "24h", series: true)
|
|
68
|
+
(up["response_time_series"] || []).each { |b| puts "#{b['at']} #{b['avg_ms']}" }
|
|
69
|
+
|
|
70
|
+
# Pause up to 100 monitors at once; post a status-page announcement
|
|
71
|
+
wf.monitors.bulk("pause", ["<id-1>", "<id-2>"])
|
|
72
|
+
notice = wf.status_pages.create_announcement("<page-id>",
|
|
73
|
+
{ title: "Checkout errors", body: "We are looking into it." })
|
|
74
|
+
wf.status_pages.update_announcement("<page-id>", notice["id"], { body: "Fixed.", status: "resolved" })
|
|
62
75
|
```
|
|
63
76
|
|
|
64
77
|
### Live diagnostics
|
|
65
78
|
|
|
66
|
-
Run any of
|
|
79
|
+
Run any of 24 checks from WatchFor's probe fleet against **any public
|
|
67
80
|
target**, monitored or not — this measures right now, rather than reading
|
|
68
81
|
what WatchFor recorded:
|
|
69
82
|
|
|
@@ -78,6 +91,18 @@ dns = wf.diagnostics.run("dns-lookup", "example.com",
|
|
|
78
91
|
puts "finding: #{dns['error']}" unless dns["success"]
|
|
79
92
|
puts "runs left this hour: #{dns['remaining']}"
|
|
80
93
|
|
|
94
|
+
# Graded checks (tls-grade, http-headers, cwv-check, mcp-check, email-health,
|
|
95
|
+
# mail-server-check, cdn-check, smart-audit, dns-health) also return the
|
|
96
|
+
# dashboard's WatchFor Grade: letter, rubric id and what holds it down
|
|
97
|
+
headers = wf.diagnostics.run("http-headers", "https://example.com")
|
|
98
|
+
grade = headers["grade"] || {}
|
|
99
|
+
if grade["letter"]
|
|
100
|
+
puts "grade #{grade['letter']} (#{grade['rubric']})"
|
|
101
|
+
grade["capped_by"].each { |c| puts " max #{c['cap']}: #{c['title']} — fix: #{c['fix']}" }
|
|
102
|
+
else
|
|
103
|
+
puts "not graded: #{grade['not_graded_reason']}"
|
|
104
|
+
end
|
|
105
|
+
|
|
81
106
|
# The whole picture in one call: DNS + propagation + TLS + HTTP + ping
|
|
82
107
|
# from up to 3 regions, aggregated into one verdict
|
|
83
108
|
report = wf.diagnostics.diagnose_target("example.com")
|
|
@@ -90,11 +115,38 @@ Details: <https://watchfor.io/docs/api/diagnostics>
|
|
|
90
115
|
|
|
91
116
|
Namespaces: `wf.monitors`, `wf.alert_rules`, `wf.incidents`,
|
|
92
117
|
`wf.hosts`, `wf.status_pages`, `wf.on_call`, `wf.notification_channels`, `wf.maintenance_windows`, `wf.contacts`, `wf.contact_groups`,
|
|
93
|
-
`wf.diagnostics`. Top-level:
|
|
118
|
+
`wf.diagnostics`, `wf.playwright`, `wf.variables`. Top-level:
|
|
94
119
|
`wf.summary`, `wf.plan`, `wf.me`, `wf.locations`, `wf.monitor_types`,
|
|
95
|
-
`wf.incident_stats(period)`, `wf.notifications`, `wf.activity
|
|
120
|
+
`wf.incident_stats(period)`, `wf.report`, `wf.notifications`, `wf.activity`,
|
|
121
|
+
`wf.batch([...])`, `wf.metrics`. Errors raise
|
|
96
122
|
`Watchfor::Error` with `#status`, `#code`, `#message`.
|
|
97
123
|
|
|
124
|
+
## Organizations
|
|
125
|
+
|
|
126
|
+
An API key belongs to one organization. An **OAuth access token** acts as
|
|
127
|
+
the user who approved it, so it reaches every organization that user
|
|
128
|
+
belongs to; choose one with `organization:` — the SDK sends it as the
|
|
129
|
+
`X-WatchFor-Organization` header on every request:
|
|
130
|
+
|
|
131
|
+
```ruby
|
|
132
|
+
acme = Watchfor::Client.new(api_key: oauth_access_token, organization: "<organization-id>")
|
|
133
|
+
acme.me # {"organization" => {"id", "name", "slug"}, "scope" => ...}
|
|
134
|
+
other = acme.with_organization("<another-organization-id>") # new client, same token
|
|
135
|
+
other.monitors.list
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
- Without `organization:`, an OAuth token acts on the user's oldest owned
|
|
139
|
+
organization (else their oldest membership).
|
|
140
|
+
- The user must be a member of that organization (otherwise `403
|
|
141
|
+
forbidden`), and their role there applies: members read, acknowledge
|
|
142
|
+
incidents and run diagnostics; changes need owner or admin.
|
|
143
|
+
- With an API key leave it unset; it may only repeat the key's own
|
|
144
|
+
organization id, and any other id is `403 forbidden` (a key never
|
|
145
|
+
switches organization).
|
|
146
|
+
- `with_organization(nil)` drops the header again.
|
|
147
|
+
|
|
148
|
+
Details: <https://watchfor.io/docs/api/authentication#how-oauth-maps-to-your-data>
|
|
149
|
+
|
|
98
150
|
## Prometheus metrics (Pro plan and above)
|
|
99
151
|
|
|
100
152
|
```ruby
|
|
@@ -112,6 +164,12 @@ watchfor monitors
|
|
|
112
164
|
watchfor incidents
|
|
113
165
|
watchfor checks <monitor_id> [--cursor <next_cursor>]
|
|
114
166
|
watchfor report --period 30d
|
|
167
|
+
watchfor me # which organization + scope
|
|
168
|
+
|
|
169
|
+
# OAuth token: pick the organization per call or via the environment
|
|
170
|
+
export WATCHFOR_API_KEY=<oauth-access-token>
|
|
171
|
+
watchfor --organization <organization-id> monitors
|
|
172
|
+
WATCHFOR_ORGANIZATION=<organization-id> watchfor summary
|
|
115
173
|
```
|
|
116
174
|
|
|
117
175
|
Reference: <https://watchfor.io/openapi.json> · <https://watchfor.io/docs/api>
|
data/exe/watchfor
CHANGED
|
@@ -8,6 +8,22 @@ api_key = ENV["WATCHFOR_API_KEY"]
|
|
|
8
8
|
base_url = ENV.fetch("WATCHFOR_BASE_URL", Watchfor::DEFAULT_BASE_URL)
|
|
9
9
|
|
|
10
10
|
args = ARGV.dup
|
|
11
|
+
|
|
12
|
+
# Global --organization <id> / --organization=<id>, anywhere on the line;
|
|
13
|
+
# WATCHFOR_ORGANIZATION is the default. OAuth tokens reach every
|
|
14
|
+
# organization their user belongs to; an API key only accepts its own id.
|
|
15
|
+
organization = ENV.fetch("WATCHFOR_ORGANIZATION", nil)
|
|
16
|
+
if (i = args.index("--organization"))
|
|
17
|
+
organization = args[i + 1].to_s
|
|
18
|
+
args.slice!(i, 2)
|
|
19
|
+
if organization.strip.empty?
|
|
20
|
+
warn "--organization needs an organization id"
|
|
21
|
+
exit 2
|
|
22
|
+
end
|
|
23
|
+
elsif (i = args.index { |a| a.start_with?("--organization=") })
|
|
24
|
+
organization = args.delete_at(i).split("=", 2)[1].to_s
|
|
25
|
+
end
|
|
26
|
+
|
|
11
27
|
command = args.shift
|
|
12
28
|
|
|
13
29
|
if command == "--version"
|
|
@@ -16,15 +32,16 @@ if command == "--version"
|
|
|
16
32
|
end
|
|
17
33
|
|
|
18
34
|
if api_key.nil? || api_key.empty?
|
|
19
|
-
warn "No API key. Set WATCHFOR_API_KEY."
|
|
35
|
+
warn "No API key. Set WATCHFOR_API_KEY (an API key or an OAuth access token)."
|
|
20
36
|
exit 2
|
|
21
37
|
end
|
|
22
38
|
|
|
23
|
-
wf = Watchfor::Client.new(api_key: api_key, base_url: base_url)
|
|
39
|
+
wf = Watchfor::Client.new(api_key: api_key, base_url: base_url, organization: organization)
|
|
24
40
|
|
|
25
41
|
begin
|
|
26
42
|
result =
|
|
27
43
|
case command
|
|
44
|
+
when "me" then wf.me
|
|
28
45
|
when "summary" then wf.summary
|
|
29
46
|
when "plan" then wf.plan
|
|
30
47
|
when "monitors" then wf.monitors.list
|
|
@@ -42,7 +59,10 @@ begin
|
|
|
42
59
|
print wf.metrics(tag: tag, type: type)
|
|
43
60
|
exit 0
|
|
44
61
|
else
|
|
45
|
-
warn "Usage: watchfor [
|
|
62
|
+
warn "Usage: watchfor [--organization <id>] " \
|
|
63
|
+
"[me|summary|plan|monitors|locations|monitor-types|incidents|checks <monitor_id> [--cursor c]|" \
|
|
64
|
+
"report [--period 7d|30d]|metrics [--tag t] [--type t]]\n" \
|
|
65
|
+
"Env: WATCHFOR_API_KEY (API key or OAuth access token), WATCHFOR_ORGANIZATION, WATCHFOR_BASE_URL"
|
|
46
66
|
exit 2
|
|
47
67
|
end
|
|
48
68
|
puts JSON.pretty_generate(result)
|
data/lib/watchfor/client.rb
CHANGED
|
@@ -6,6 +6,8 @@ require "uri"
|
|
|
6
6
|
|
|
7
7
|
module Watchfor
|
|
8
8
|
DEFAULT_BASE_URL = "https://watchfor.io/api/v1"
|
|
9
|
+
# The request header that selects the organization an OAuth token acts on.
|
|
10
|
+
ORGANIZATION_HEADER = "X-WatchFor-Organization"
|
|
9
11
|
|
|
10
12
|
# Raised when the API returns a non-2xx response. Carries the HTTP
|
|
11
13
|
# +status+ and the API's structured +code+ and +message+.
|
|
@@ -24,17 +26,28 @@ module Watchfor
|
|
|
24
26
|
# wf = Watchfor::Client.new(api_key: "wf_live_...")
|
|
25
27
|
# wf.summary
|
|
26
28
|
# wf.monitors.list["data"].each { |m| puts "#{m['name']} #{m['status']}" }
|
|
29
|
+
#
|
|
30
|
+
# An OAuth access token reaches every organization its user belongs to;
|
|
31
|
+
# pick one with +organization:+ (sent as X-WatchFor-Organization):
|
|
32
|
+
#
|
|
33
|
+
# acme = Watchfor::Client.new(api_key: oauth_token, organization: "<org id>")
|
|
34
|
+
# other = acme.with_organization("<another org id>")
|
|
35
|
+
#
|
|
36
|
+
# An API key belongs to one organization: leave +organization:+ unset, or
|
|
37
|
+
# pass that same id (any other id is 403 forbidden).
|
|
27
38
|
class Client
|
|
28
39
|
attr_reader :monitors, :alert_rules, :incidents, :hosts, :status_pages, :on_call, :notification_channels,
|
|
29
40
|
:maintenance_windows, :contacts, :contact_groups,
|
|
30
|
-
:diagnostics, :playwright, :variables
|
|
41
|
+
:diagnostics, :playwright, :variables, :organization
|
|
31
42
|
|
|
32
|
-
def initialize(api_key:, base_url: DEFAULT_BASE_URL, timeout: 30)
|
|
43
|
+
def initialize(api_key:, base_url: DEFAULT_BASE_URL, timeout: 30, organization: nil)
|
|
33
44
|
raise ArgumentError, "api_key is required" if api_key.nil? || api_key.empty?
|
|
34
45
|
|
|
35
46
|
@api_key = api_key
|
|
36
47
|
@base_url = base_url.chomp("/")
|
|
37
48
|
@timeout = timeout
|
|
49
|
+
org = organization.to_s.strip
|
|
50
|
+
@organization = org.empty? ? nil : org
|
|
38
51
|
@monitors = Monitors.new(self)
|
|
39
52
|
@alert_rules = AlertRules.new(self)
|
|
40
53
|
@incidents = Incidents.new(self)
|
|
@@ -50,6 +63,14 @@ module Watchfor
|
|
|
50
63
|
@variables = Variables.new(self)
|
|
51
64
|
end
|
|
52
65
|
|
|
66
|
+
# A new client acting on another organization (same credential and
|
|
67
|
+
# settings); nil drops the header so the credential's default
|
|
68
|
+
# organization applies. This client is left unchanged.
|
|
69
|
+
def with_organization(organization)
|
|
70
|
+
self.class.new(api_key: @api_key, base_url: @base_url, timeout: @timeout,
|
|
71
|
+
organization: organization)
|
|
72
|
+
end
|
|
73
|
+
|
|
53
74
|
# Core request. +query+ and +body+ are optional hashes.
|
|
54
75
|
def request(method, path, query: nil, body: nil, idempotency_key: nil, text: false)
|
|
55
76
|
uri = URI("#{@base_url}#{path}")
|
|
@@ -68,6 +89,7 @@ module Watchfor
|
|
|
68
89
|
req["Accept"] = text ? "text/plain, application/json" : "application/json"
|
|
69
90
|
req["User-Agent"] = "watchfor-ruby/#{Watchfor::VERSION}"
|
|
70
91
|
req["Idempotency-Key"] = idempotency_key if idempotency_key
|
|
92
|
+
req[ORGANIZATION_HEADER] = @organization if @organization
|
|
71
93
|
unless body.nil?
|
|
72
94
|
req["Content-Type"] = "application/json"
|
|
73
95
|
req.body = JSON.generate(body)
|
|
@@ -92,7 +114,17 @@ module Watchfor
|
|
|
92
114
|
end
|
|
93
115
|
|
|
94
116
|
# Top-level reads
|
|
117
|
+
# The credential's scope and the organization it acts on (with an OAuth
|
|
118
|
+
# token, the one +organization:+ selected).
|
|
95
119
|
def me = request("GET", "/me")
|
|
120
|
+
|
|
121
|
+
# Run up to 20 operations ({ method:, path:, body: } with a relative
|
|
122
|
+
# /api/v1 path) in one request, with the same credential and
|
|
123
|
+
# organization; "results" come back in request order as {status, body}.
|
|
124
|
+
def batch(operations) = request("POST", "/batch", body: { operations: operations })
|
|
125
|
+
|
|
126
|
+
# The OpenAPI 3.1 document this client wraps (no key needed).
|
|
127
|
+
def openapi_spec = request("GET", "/openapi.json")
|
|
96
128
|
def summary = request("GET", "/summary")
|
|
97
129
|
|
|
98
130
|
# The organization in Prometheus text format (Pro plan and above):
|
|
@@ -108,11 +140,24 @@ module Watchfor
|
|
|
108
140
|
# Org report vs the previous period (period: "7d"/"30d", or from/to ISO 8601
|
|
109
141
|
# on advanced-reports plans): uptime, incidents, MTTR/MTBF, response-time
|
|
110
142
|
# trend, per-monitor rows, type-aware sections and plain-language insights.
|
|
111
|
-
|
|
143
|
+
#
|
|
144
|
+
# Scoping (advanced-reports plans): monitor_ids:/tags: (union), sections:
|
|
145
|
+
# (locations, ssl, domains, cwv, heartbeat, blacklist, mcp, hosts),
|
|
146
|
+
# incident_status: (acknowledged, resolved, open) — arrays or
|
|
147
|
+
# comma-separated strings. false for response_time:/monitors_table:/
|
|
148
|
+
# insights: drops that block.
|
|
149
|
+
def report(period: nil, from: nil, to: nil, monitor_ids: nil, tags: nil, sections: nil,
|
|
150
|
+
response_time: nil, monitors_table: nil, insights: nil, incident_status: nil)
|
|
151
|
+
csv = ->(v) { v.is_a?(Array) ? v.join(",") : v }
|
|
152
|
+
query = { monitor_ids: csv.call(monitor_ids), tags: csv.call(tags), sections: csv.call(sections),
|
|
153
|
+
incident_status: csv.call(incident_status),
|
|
154
|
+
response_time: response_time.nil? ? nil : response_time.to_s,
|
|
155
|
+
monitors_table: monitors_table.nil? ? nil : monitors_table.to_s,
|
|
156
|
+
insights: insights.nil? ? nil : insights.to_s }
|
|
112
157
|
if from || to
|
|
113
|
-
request("GET", "/reports", query:
|
|
158
|
+
request("GET", "/reports", query: query.merge(from: from, to: to))
|
|
114
159
|
else
|
|
115
|
-
request("GET", "/reports", query:
|
|
160
|
+
request("GET", "/reports", query: query.merge(period: period))
|
|
116
161
|
end
|
|
117
162
|
end
|
|
118
163
|
# The org's two report-email cadences (weekly + monthly); set toggles
|
|
@@ -131,8 +176,17 @@ module Watchfor
|
|
|
131
176
|
|
|
132
177
|
class Monitors < Namespace
|
|
133
178
|
def list(**query) = @c.request("GET", "/monitors", query: query)
|
|
134
|
-
# The monitor plus available_metrics (alert metrics valid for its type)
|
|
179
|
+
# The monitor plus available_metrics (alert metrics valid for its type)
|
|
180
|
+
# and alerting_state — what the alerting engine sees right now (alerted,
|
|
181
|
+
# recovering n of m, failing/confirming n of m; nil when every rule is
|
|
182
|
+
# healthy).
|
|
135
183
|
def get(id) = @c.request("GET", "/monitors/#{id}")
|
|
184
|
+
# Pause, resume or delete (+action+) up to 100 monitors in one call; ids
|
|
185
|
+
# that are not yours or need no change come back in "skipped".
|
|
186
|
+
def bulk(action, ids, idempotency_key: nil)
|
|
187
|
+
@c.request("POST", "/monitors/bulk", body: { action: action, ids: ids },
|
|
188
|
+
idempotency_key: idempotency_key)
|
|
189
|
+
end
|
|
136
190
|
# Credentials (catalog fields marked secret: mailbox/SMTP/FTP passwords,
|
|
137
191
|
# SSH keys, HTTP/API/MCP auth passwords, bearer tokens and credential
|
|
138
192
|
# header values) are write-only: stored encrypted, returned as
|
|
@@ -142,7 +196,9 @@ module Watchfor
|
|
|
142
196
|
# config is merged (omitted keys kept, nil removes). A "[redacted]"
|
|
143
197
|
# credential is kept when omitted or sent back; changing the server,
|
|
144
198
|
# port, TLS mode or login it is used with (HTTP: the URL's host, port
|
|
145
|
-
# or scheme) needs it again (else 400).
|
|
199
|
+
# or scheme) needs it again (else 400). "check_queued" => true in the
|
|
200
|
+
# result means the change dispatched one immediate check, outside the
|
|
201
|
+
# Run-now budget.
|
|
146
202
|
def update(id, body) = @c.request("PATCH", "/monitors/#{id}", body: body)
|
|
147
203
|
def delete(id) = @c.request("DELETE", "/monitors/#{id}")
|
|
148
204
|
def pause(id) = @c.request("POST", "/monitors/#{id}/pause")
|
|
@@ -153,7 +209,13 @@ module Watchfor
|
|
|
153
209
|
# monitor and starts uptime over ("since reset"); the monitor and its
|
|
154
210
|
# configuration, alert rules, tags and locations stay.
|
|
155
211
|
def reset(id) = @c.request("POST", "/monitors/#{id}/reset")
|
|
156
|
-
|
|
212
|
+
# Uptime (confirmed critical incidents only), check success and
|
|
213
|
+
# response-time stats. series: true (periods up to 7d) adds
|
|
214
|
+
# "response_time_series" — buckets of "series_bucket_seconds" (300 for
|
|
215
|
+
# 24h, 900 for 7d) on a real time axis, oldest first.
|
|
216
|
+
def uptime(id, period = nil, series: false)
|
|
217
|
+
@c.request("GET", "/monitors/#{id}/uptime", query: { period: period, series: series ? "true" : nil })
|
|
218
|
+
end
|
|
157
219
|
# Recent probe results, newest first. Page with the response's next_cursor -> cursor:
|
|
158
220
|
# (keep the same hours/success); "locations" is the per-location breakdown for the whole window.
|
|
159
221
|
def checks(id, hours: nil, limit: nil, success: nil, cursor: nil, **query)
|
|
@@ -173,11 +235,18 @@ module Watchfor
|
|
|
173
235
|
# Page integrity monitors: the accepted baseline of every watched page.
|
|
174
236
|
def baseline(id) = @c.request("GET", "/monitors/#{id}/baseline")
|
|
175
237
|
# Page integrity monitors: accept what the newest check saw as the new
|
|
176
|
-
# baseline — all pages, or only `pages:`.
|
|
177
|
-
#
|
|
178
|
-
|
|
238
|
+
# baseline — all pages, or only `pages:`. Open integrity incidents
|
|
239
|
+
# resolve at once when nothing else differs ("incidents_resolved").
|
|
240
|
+
# From a deploy pipeline pass `fresh: true`: the pages are checked first
|
|
241
|
+
# and that check is accepted ("status" "pending" = still running, it
|
|
242
|
+
# finishes on its own — see baseline(id)["fresh_accept"]); unfetched
|
|
243
|
+
# pages come back in "skipped".
|
|
244
|
+
def accept_baseline(id, pages: nil, idempotency_key: nil, fresh: false)
|
|
245
|
+
body = {}
|
|
246
|
+
body[:pages] = pages if pages
|
|
247
|
+
body[:fresh] = true if fresh
|
|
179
248
|
@c.request("POST", "/monitors/#{id}/baseline",
|
|
180
|
-
body:
|
|
249
|
+
body: body, idempotency_key: idempotency_key)
|
|
181
250
|
end
|
|
182
251
|
end
|
|
183
252
|
|
|
@@ -202,7 +271,9 @@ module Watchfor
|
|
|
202
271
|
class AlertRules < Namespace
|
|
203
272
|
def list(monitor_id) = @c.request("GET", "/monitors/#{monitor_id}/alert-rules")
|
|
204
273
|
def get(monitor_id, rule_id) = @c.request("GET", "/monitors/#{monitor_id}/alert-rules/#{rule_id}")
|
|
205
|
-
def create(monitor_id, body
|
|
274
|
+
def create(monitor_id, body, idempotency_key: nil)
|
|
275
|
+
@c.request("POST", "/monitors/#{monitor_id}/alert-rules", body: body, idempotency_key: idempotency_key)
|
|
276
|
+
end
|
|
206
277
|
def update(monitor_id, rule_id, body) = @c.request("PATCH", "/monitors/#{monitor_id}/alert-rules/#{rule_id}", body: body)
|
|
207
278
|
def delete(monitor_id, rule_id) = @c.request("DELETE", "/monitors/#{monitor_id}/alert-rules/#{rule_id}")
|
|
208
279
|
end
|
|
@@ -218,12 +289,40 @@ module Watchfor
|
|
|
218
289
|
def exclude(id, reason: nil) = @c.request("POST", "/incidents/#{id}/exclude", body: reason.nil? ? {} : { reason: reason })
|
|
219
290
|
# Undo #exclude - the incident counts as downtime again.
|
|
220
291
|
def include(id) = @c.request("POST", "/incidents/#{id}/include")
|
|
292
|
+
# Retroactively count a RESOLVED incident as planned maintenance: it
|
|
293
|
+
# leaves uptime and SLA. Returns the "maintenance_window" recorded for it
|
|
294
|
+
# (marking again returns the same one); delete that window to undo.
|
|
295
|
+
def mark_maintenance(id, idempotency_key: nil)
|
|
296
|
+
@c.request("POST", "/incidents/#{id}/mark-maintenance", idempotency_key: idempotency_key)
|
|
297
|
+
end
|
|
221
298
|
end
|
|
222
299
|
|
|
223
300
|
class StatusPages < Namespace
|
|
224
301
|
def list(**query) = @c.request("GET", "/status-pages", query: query)
|
|
225
302
|
def get(id) = @c.request("GET", "/status-pages/#{id}")
|
|
303
|
+
# Create a page (slug, name, optional components...). Password protection,
|
|
304
|
+
# logo upload and custom subdomains are dashboard-only.
|
|
305
|
+
def create(body, idempotency_key: nil) = @c.request("POST", "/status-pages", body: body, idempotency_key: idempotency_key)
|
|
306
|
+
# Partial update; a slug change frees the old URL at once.
|
|
307
|
+
def update(id, body) = @c.request("PATCH", "/status-pages/#{id}", body: body)
|
|
308
|
+
def delete(id) = @c.request("DELETE", "/status-pages/#{id}")
|
|
309
|
+
# Announcements, newest first.
|
|
226
310
|
def announcements(id, **query) = @c.request("GET", "/status-pages/#{id}/announcements", query: query)
|
|
311
|
+
def get_announcement(id, announcement_id) = @c.request("GET", "/status-pages/#{id}/announcements/#{announcement_id}")
|
|
312
|
+
# Publish an incident notice (title, body, optional status, severity,
|
|
313
|
+
# affectedComponentIds); body becomes the first timeline entry.
|
|
314
|
+
def create_announcement(id, body, idempotency_key: nil)
|
|
315
|
+
@c.request("POST", "/status-pages/#{id}/announcements", body: body, idempotency_key: idempotency_key)
|
|
316
|
+
end
|
|
317
|
+
# Post an update (body + status): appends to the public timeline;
|
|
318
|
+
# "resolved" closes it.
|
|
319
|
+
def update_announcement(id, announcement_id, body)
|
|
320
|
+
@c.request("PATCH", "/status-pages/#{id}/announcements/#{announcement_id}", body: body)
|
|
321
|
+
end
|
|
322
|
+
# Remove from the public page (soft delete).
|
|
323
|
+
def delete_announcement(id, announcement_id)
|
|
324
|
+
@c.request("DELETE", "/status-pages/#{id}/announcements/#{announcement_id}")
|
|
325
|
+
end
|
|
227
326
|
end
|
|
228
327
|
|
|
229
328
|
class OnCall < Namespace
|
|
@@ -242,6 +341,7 @@ module Watchfor
|
|
|
242
341
|
end
|
|
243
342
|
|
|
244
343
|
class MaintenanceWindows < Namespace
|
|
344
|
+
# state: "active" / "upcoming" keeps only live windows.
|
|
245
345
|
def list(**query) = @c.request("GET", "/maintenance-windows", query: query)
|
|
246
346
|
def get(id) = @c.request("GET", "/maintenance-windows/#{id}")
|
|
247
347
|
def create(body, idempotency_key: nil) = @c.request("POST", "/maintenance-windows", body: body, idempotency_key: idempotency_key)
|
data/lib/watchfor/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: watchfor
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.15.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- WatchFor
|
|
@@ -24,11 +24,11 @@ files:
|
|
|
24
24
|
- lib/watchfor.rb
|
|
25
25
|
- lib/watchfor/client.rb
|
|
26
26
|
- lib/watchfor/version.rb
|
|
27
|
-
homepage: https://watchfor.io
|
|
27
|
+
homepage: https://watchfor.io
|
|
28
28
|
licenses:
|
|
29
29
|
- MIT
|
|
30
30
|
metadata:
|
|
31
|
-
homepage_uri: https://watchfor.io
|
|
31
|
+
homepage_uri: https://watchfor.io
|
|
32
32
|
documentation_uri: https://watchfor.io/docs/api
|
|
33
33
|
changelog_uri: https://watchfor.io/changelog
|
|
34
34
|
rubygems_mfa_required: 'true'
|