brainzlab 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +6 -21
  3. data/README.md +24 -2
  4. data/lib/brainzlab/beacon/client.rb +207 -0
  5. data/lib/brainzlab/beacon/provisioner.rb +44 -0
  6. data/lib/brainzlab/beacon.rb +215 -0
  7. data/lib/brainzlab/configuration.rb +372 -32
  8. data/lib/brainzlab/context.rb +2 -3
  9. data/lib/brainzlab/cortex/cache.rb +59 -0
  10. data/lib/brainzlab/cortex/client.rb +139 -0
  11. data/lib/brainzlab/cortex/provisioner.rb +49 -0
  12. data/lib/brainzlab/cortex.rb +223 -0
  13. data/lib/brainzlab/dendrite/client.rb +230 -0
  14. data/lib/brainzlab/dendrite/provisioner.rb +44 -0
  15. data/lib/brainzlab/dendrite.rb +195 -0
  16. data/lib/brainzlab/devtools/assets/devtools.css +1106 -0
  17. data/lib/brainzlab/devtools/assets/devtools.js +322 -0
  18. data/lib/brainzlab/devtools/assets/logo.svg +6 -0
  19. data/lib/brainzlab/devtools/assets/templates/debug_panel.html.erb +500 -0
  20. data/lib/brainzlab/devtools/assets/templates/error_page.html.erb +1086 -0
  21. data/lib/brainzlab/devtools/data/collector.rb +248 -0
  22. data/lib/brainzlab/devtools/middleware/asset_server.rb +63 -0
  23. data/lib/brainzlab/devtools/middleware/database_handler.rb +177 -0
  24. data/lib/brainzlab/devtools/middleware/debug_panel.rb +126 -0
  25. data/lib/brainzlab/devtools/middleware/error_page.rb +377 -0
  26. data/lib/brainzlab/devtools/renderers/debug_panel_renderer.rb +159 -0
  27. data/lib/brainzlab/devtools/renderers/error_page_renderer.rb +98 -0
  28. data/lib/brainzlab/devtools.rb +75 -0
  29. data/lib/brainzlab/flux/buffer.rb +96 -0
  30. data/lib/brainzlab/flux/client.rb +68 -0
  31. data/lib/brainzlab/flux/provisioner.rb +57 -0
  32. data/lib/brainzlab/flux.rb +174 -0
  33. data/lib/brainzlab/instrumentation/action_mailer.rb +14 -13
  34. data/lib/brainzlab/instrumentation/active_record.rb +28 -13
  35. data/lib/brainzlab/instrumentation/aws.rb +183 -0
  36. data/lib/brainzlab/instrumentation/dalli.rb +108 -0
  37. data/lib/brainzlab/instrumentation/delayed_job.rb +27 -29
  38. data/lib/brainzlab/instrumentation/elasticsearch.rb +23 -24
  39. data/lib/brainzlab/instrumentation/excon.rb +152 -0
  40. data/lib/brainzlab/instrumentation/faraday.rb +3 -4
  41. data/lib/brainzlab/instrumentation/good_job.rb +102 -0
  42. data/lib/brainzlab/instrumentation/grape.rb +24 -24
  43. data/lib/brainzlab/instrumentation/graphql.rb +24 -23
  44. data/lib/brainzlab/instrumentation/httparty.rb +13 -14
  45. data/lib/brainzlab/instrumentation/mongodb.rb +7 -7
  46. data/lib/brainzlab/instrumentation/net_http.rb +6 -6
  47. data/lib/brainzlab/instrumentation/redis.rb +14 -21
  48. data/lib/brainzlab/instrumentation/resque.rb +114 -0
  49. data/lib/brainzlab/instrumentation/sidekiq.rb +29 -28
  50. data/lib/brainzlab/instrumentation/solid_queue.rb +194 -0
  51. data/lib/brainzlab/instrumentation/stripe.rb +163 -0
  52. data/lib/brainzlab/instrumentation/typhoeus.rb +106 -0
  53. data/lib/brainzlab/instrumentation.rb +84 -12
  54. data/lib/brainzlab/nerve/client.rb +215 -0
  55. data/lib/brainzlab/nerve/provisioner.rb +44 -0
  56. data/lib/brainzlab/nerve.rb +219 -0
  57. data/lib/brainzlab/pulse/client.rb +15 -11
  58. data/lib/brainzlab/pulse/instrumentation.rb +90 -53
  59. data/lib/brainzlab/pulse/propagation.rb +29 -29
  60. data/lib/brainzlab/pulse/provisioner.rb +12 -12
  61. data/lib/brainzlab/pulse/tracer.rb +4 -4
  62. data/lib/brainzlab/pulse.rb +14 -14
  63. data/lib/brainzlab/rails/log_formatter.rb +127 -121
  64. data/lib/brainzlab/rails/log_subscriber.rb +70 -77
  65. data/lib/brainzlab/rails/railtie.rb +96 -86
  66. data/lib/brainzlab/recall/buffer.rb +1 -1
  67. data/lib/brainzlab/recall/client.rb +14 -10
  68. data/lib/brainzlab/recall/logger.rb +16 -18
  69. data/lib/brainzlab/recall/provisioner.rb +29 -12
  70. data/lib/brainzlab/recall.rb +14 -11
  71. data/lib/brainzlab/reflex/breadcrumbs.rb +2 -2
  72. data/lib/brainzlab/reflex/client.rb +14 -10
  73. data/lib/brainzlab/reflex/provisioner.rb +12 -12
  74. data/lib/brainzlab/reflex.rb +31 -31
  75. data/lib/brainzlab/sentinel/client.rb +216 -0
  76. data/lib/brainzlab/sentinel/provisioner.rb +44 -0
  77. data/lib/brainzlab/sentinel.rb +165 -0
  78. data/lib/brainzlab/signal/client.rb +60 -0
  79. data/lib/brainzlab/signal/provisioner.rb +55 -0
  80. data/lib/brainzlab/signal.rb +136 -0
  81. data/lib/brainzlab/synapse/client.rb +288 -0
  82. data/lib/brainzlab/synapse/provisioner.rb +44 -0
  83. data/lib/brainzlab/synapse.rb +270 -0
  84. data/lib/brainzlab/utilities/circuit_breaker.rb +261 -0
  85. data/lib/brainzlab/utilities/health_check.rb +294 -0
  86. data/lib/brainzlab/utilities/log_formatter.rb +254 -0
  87. data/lib/brainzlab/utilities/rate_limiter.rb +230 -0
  88. data/lib/brainzlab/utilities.rb +17 -0
  89. data/lib/brainzlab/vault/cache.rb +80 -0
  90. data/lib/brainzlab/vault/client.rb +196 -0
  91. data/lib/brainzlab/vault/provisioner.rb +49 -0
  92. data/lib/brainzlab/vault.rb +262 -0
  93. data/lib/brainzlab/version.rb +1 -1
  94. data/lib/brainzlab/vision/client.rb +128 -0
  95. data/lib/brainzlab/vision/provisioner.rb +136 -0
  96. data/lib/brainzlab/vision.rb +155 -0
  97. data/lib/brainzlab-sdk.rb +1 -1
  98. data/lib/brainzlab.rb +112 -13
  99. data/lib/generators/brainzlab/install/install_generator.rb +29 -27
  100. metadata +60 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a23ea95f03ef25ac3386a0fbf07ea352a01440afc23ec340625f9932bc1cf6a6
4
- data.tar.gz: c6f73bc4d7c99a1c4eae51dd34a525e8e3d48b4d0ad4a5502a96baf931f5e44a
3
+ metadata.gz: a2ccfb8ed0a1aefc1a67e709438812194e6a6a1ce79f7044824ab3f2accd3aca
4
+ data.tar.gz: 99df1dbd2842a09ab90165755746a8248c5b103854dcddd8c28c1f8ee7e239f9
5
5
  SHA512:
6
- metadata.gz: abf6406497997a9de152038c1f7764ba66165681091ce9f2c91fe3414097beb2599976bcd0ffb0f5bd175cf2fc8d1909a8931cd50794f7a049569a6fa3914140
7
- data.tar.gz: 5dde805af2709726635c798aec9029dff143ffd63ca1de0166328d7ab68c892ab507a02b9127e6d1af3abd89d18774cf4f4d197dfd7656ae474d493821a54433
6
+ metadata.gz: 49a8f8a4be96f4f917fef27661caa12bde990cfa88dcc19d8917c4c74c4446b63cff26ad52ee524c577d8c9bc3f6767315245e1dace168a46422858f43af2620
7
+ data.tar.gz: d64004b9afc21429742c61deebbfd5cfe244b83ff477bb4c4809d9fd156917652e150f0236aca3abba6d42df53a231fd6deb54c57b7914f1267d40cc4d1f9a75
data/LICENSE CHANGED
@@ -1,26 +1,11 @@
1
- Ossassy License
1
+ OSAaSy License
2
2
 
3
- Copyright (c) 2025, Brainz Lab
3
+ Copyright © 2025, Brainz Lab.
4
4
 
5
- Permission is hereby granted, free of charge, to any person obtaining a copy of
6
- this software and associated documentation files (the "Software"), to deal in
7
- the Software without restriction, including without limitation the rights to
8
- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
9
- of the Software, and to permit persons to whom the Software is furnished to do
10
- so, subject to the following conditions:
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:
11
6
 
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
14
8
 
15
- No licensee or downstream recipient may use the Software (including any modified
16
- or derivative versions) to directly compete with the original Licensor by
17
- offering it to third parties as a hosted, managed, or Software-as-a-Service
18
- (SaaS) product or cloud service where the primary value of the service is the
19
- functionality of the Software itself.
9
+ 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.
20
10
 
21
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
23
- FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
24
- COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
25
- IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
26
- CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
11
+ 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,7 +2,10 @@
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/brainzlab.svg)](https://rubygems.org/gems/brainzlab)
4
4
  [![CI](https://github.com/brainz-lab/brainzlab-ruby/actions/workflows/ci.yml/badge.svg)](https://github.com/brainz-lab/brainzlab-ruby/actions/workflows/ci.yml)
5
- [![License](https://img.shields.io/badge/license-Ossassy-blue.svg)](LICENSE)
5
+ [![CodeQL](https://github.com/brainz-lab/brainzlab-ruby/actions/workflows/codeql.yml/badge.svg)](https://github.com/brainz-lab/brainzlab-ruby/actions/workflows/codeql.yml)
6
+ [![codecov](https://codecov.io/gh/brainz-lab/brainzlab-ruby/graph/badge.svg)](https://codecov.io/gh/brainz-lab/brainzlab-ruby)
7
+ [![Docs](https://img.shields.io/badge/docs-brainzlab.ai-orange)](https://docs.brainzlab.ai/sdk/ruby/installation)
8
+ [![License: OSAaSy](https://img.shields.io/badge/License-OSAaSy-blue.svg)](LICENSE)
6
9
 
7
10
  Official Ruby SDK for [BrainzLab](https://brainzlab.ai) - the complete observability platform.
8
11
 
@@ -328,6 +331,25 @@ Full documentation: [docs.brainzlab.ai](https://docs.brainzlab.ai)
328
331
  - [Reflex (Errors)](https://docs.brainzlab.ai/sdk/ruby/reflex)
329
332
  - [Pulse (APM)](https://docs.brainzlab.ai/sdk/ruby/pulse)
330
333
 
334
+ ## Related
335
+
336
+ - [Recall](https://github.com/brainz-lab/recall) - Logging service
337
+ - [Reflex](https://github.com/brainz-lab/reflex) - Error tracking service
338
+ - [Pulse](https://github.com/brainz-lab/pulse) - APM service
339
+ - [Stack](https://github.com/brainz-lab/stack) - Self-hosted deployment
340
+
341
+ ## Contributors
342
+
343
+ <!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
344
+ <!-- prettier-ignore-start -->
345
+ <!-- markdownlint-disable -->
346
+ <!-- markdownlint-restore -->
347
+ <!-- prettier-ignore-end -->
348
+ <!-- ALL-CONTRIBUTORS-LIST:END -->
349
+
350
+ Thanks to all our contributors! See [all-contributors](https://allcontributors.org) for how to add yourself.
351
+
352
+
331
353
  ## License
332
354
 
333
- Ossassy License - see [LICENSE](LICENSE) for details.
355
+ OSAaSy License - see [LICENSE](LICENSE) for details.
@@ -0,0 +1,207 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'net/http'
4
+ require 'json'
5
+ require 'uri'
6
+ require 'cgi'
7
+
8
+ module BrainzLab
9
+ module Beacon
10
+ class Client
11
+ def initialize(config)
12
+ @config = config
13
+ @base_url = config.beacon_url || 'https://beacon.brainzlab.ai'
14
+ end
15
+
16
+ # Create a new monitor
17
+ def create_monitor(name:, url:, type: 'http', interval: 60, **options)
18
+ response = request(
19
+ :post,
20
+ '/api/v1/monitors',
21
+ body: {
22
+ name: name,
23
+ url: url,
24
+ monitor_type: type,
25
+ interval: interval,
26
+ **options
27
+ }
28
+ )
29
+
30
+ return nil unless response.is_a?(Net::HTTPSuccess) || response.is_a?(Net::HTTPCreated)
31
+
32
+ JSON.parse(response.body, symbolize_names: true)
33
+ rescue StandardError => e
34
+ log_error('create_monitor', e)
35
+ nil
36
+ end
37
+
38
+ # Get monitor status
39
+ def get_monitor(id)
40
+ response = request(:get, "/api/v1/monitors/#{id}")
41
+
42
+ return nil unless response.is_a?(Net::HTTPSuccess)
43
+
44
+ JSON.parse(response.body, symbolize_names: true)
45
+ rescue StandardError => e
46
+ log_error('get_monitor', e)
47
+ nil
48
+ end
49
+
50
+ # List all monitors
51
+ def list_monitors
52
+ response = request(:get, '/api/v1/monitors')
53
+
54
+ return [] unless response.is_a?(Net::HTTPSuccess)
55
+
56
+ data = JSON.parse(response.body, symbolize_names: true)
57
+ data[:monitors] || []
58
+ rescue StandardError => e
59
+ log_error('list_monitors', e)
60
+ []
61
+ end
62
+
63
+ # Update a monitor
64
+ def update_monitor(id, **attributes)
65
+ response = request(
66
+ :put,
67
+ "/api/v1/monitors/#{id}",
68
+ body: attributes
69
+ )
70
+
71
+ response.is_a?(Net::HTTPSuccess)
72
+ rescue StandardError => e
73
+ log_error('update_monitor', e)
74
+ false
75
+ end
76
+
77
+ # Delete a monitor
78
+ def delete_monitor(id)
79
+ response = request(:delete, "/api/v1/monitors/#{id}")
80
+ response.is_a?(Net::HTTPSuccess) || response.is_a?(Net::HTTPNoContent)
81
+ rescue StandardError => e
82
+ log_error('delete_monitor', e)
83
+ false
84
+ end
85
+
86
+ # Pause a monitor
87
+ def pause_monitor(id)
88
+ response = request(:post, "/api/v1/monitors/#{id}/pause")
89
+ response.is_a?(Net::HTTPSuccess)
90
+ rescue StandardError => e
91
+ log_error('pause_monitor', e)
92
+ false
93
+ end
94
+
95
+ # Resume a monitor
96
+ def resume_monitor(id)
97
+ response = request(:post, "/api/v1/monitors/#{id}/resume")
98
+ response.is_a?(Net::HTTPSuccess)
99
+ rescue StandardError => e
100
+ log_error('resume_monitor', e)
101
+ false
102
+ end
103
+
104
+ # Get check history
105
+ def check_history(monitor_id, limit: 100)
106
+ response = request(
107
+ :get,
108
+ "/api/v1/monitors/#{monitor_id}/checks",
109
+ params: { limit: limit }
110
+ )
111
+
112
+ return [] unless response.is_a?(Net::HTTPSuccess)
113
+
114
+ data = JSON.parse(response.body, symbolize_names: true)
115
+ data[:checks] || []
116
+ rescue StandardError => e
117
+ log_error('check_history', e)
118
+ []
119
+ end
120
+
121
+ # Get current status summary
122
+ def status_summary
123
+ response = request(:get, '/api/v1/status')
124
+
125
+ return nil unless response.is_a?(Net::HTTPSuccess)
126
+
127
+ JSON.parse(response.body, symbolize_names: true)
128
+ rescue StandardError => e
129
+ log_error('status_summary', e)
130
+ nil
131
+ end
132
+
133
+ # List active incidents
134
+ def list_incidents(status: nil)
135
+ params = {}
136
+ params[:status] = status if status
137
+
138
+ response = request(:get, '/api/v1/incidents', params: params)
139
+
140
+ return [] unless response.is_a?(Net::HTTPSuccess)
141
+
142
+ data = JSON.parse(response.body, symbolize_names: true)
143
+ data[:incidents] || []
144
+ rescue StandardError => e
145
+ log_error('list_incidents', e)
146
+ []
147
+ end
148
+
149
+ def provision(project_id:, app_name:)
150
+ response = request(
151
+ :post,
152
+ '/api/v1/projects/provision',
153
+ body: { project_id: project_id, app_name: app_name },
154
+ use_service_key: true
155
+ )
156
+
157
+ response.is_a?(Net::HTTPSuccess) || response.is_a?(Net::HTTPCreated)
158
+ rescue StandardError => e
159
+ log_error('provision', e)
160
+ false
161
+ end
162
+
163
+ private
164
+
165
+ def request(method, path, headers: {}, body: nil, params: nil, use_service_key: false)
166
+ uri = URI.parse("#{@base_url}#{path}")
167
+
168
+ uri.query = URI.encode_www_form(params) if params
169
+
170
+ http = Net::HTTP.new(uri.host, uri.port)
171
+ http.use_ssl = uri.scheme == 'https'
172
+ http.open_timeout = 10
173
+ http.read_timeout = 30
174
+
175
+ request = case method
176
+ when :get
177
+ Net::HTTP::Get.new(uri)
178
+ when :post
179
+ Net::HTTP::Post.new(uri)
180
+ when :put
181
+ Net::HTTP::Put.new(uri)
182
+ when :delete
183
+ Net::HTTP::Delete.new(uri)
184
+ end
185
+
186
+ request['Content-Type'] = 'application/json'
187
+ request['Accept'] = 'application/json'
188
+
189
+ if use_service_key
190
+ request['X-Service-Key'] = @config.beacon_master_key || @config.secret_key
191
+ else
192
+ auth_key = @config.beacon_api_key || @config.secret_key
193
+ request['Authorization'] = "Bearer #{auth_key}" if auth_key
194
+ end
195
+
196
+ headers.each { |k, v| request[k] = v }
197
+ request.body = body.to_json if body
198
+
199
+ http.request(request)
200
+ end
201
+
202
+ def log_error(operation, error)
203
+ BrainzLab.debug_log("[Beacon::Client] #{operation} failed: #{error.message}")
204
+ end
205
+ end
206
+ end
207
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BrainzLab
4
+ module Beacon
5
+ class Provisioner
6
+ def initialize(config)
7
+ @config = config
8
+ @provisioned = false
9
+ end
10
+
11
+ def ensure_project!
12
+ return if @provisioned
13
+ return unless @config.beacon_auto_provision
14
+ return unless valid_auth?
15
+
16
+ @provisioned = true
17
+
18
+ project_id = detect_project_id
19
+ return unless project_id
20
+
21
+ client = Client.new(@config)
22
+ client.provision(
23
+ project_id: project_id,
24
+ app_name: @config.app_name || @config.service
25
+ )
26
+
27
+ BrainzLab.debug_log("[Beacon::Provisioner] Project provisioned: #{project_id}")
28
+ rescue StandardError => e
29
+ BrainzLab.debug_log("[Beacon::Provisioner] Provisioning failed: #{e.message}")
30
+ end
31
+
32
+ private
33
+
34
+ def valid_auth?
35
+ key = @config.beacon_api_key || @config.beacon_master_key || @config.secret_key
36
+ !key.nil? && !key.empty?
37
+ end
38
+
39
+ def detect_project_id
40
+ ENV.fetch('BRAINZLAB_PROJECT_ID', nil)
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,215 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'beacon/client'
4
+ require_relative 'beacon/provisioner'
5
+
6
+ module BrainzLab
7
+ module Beacon
8
+ class << self
9
+ # Create an HTTP monitor
10
+ # @param name [String] Monitor name
11
+ # @param url [String] URL to monitor
12
+ # @param interval [Integer] Check interval in seconds (default: 60)
13
+ # @param options [Hash] Additional options
14
+ # @return [Hash, nil] Created monitor or nil
15
+ #
16
+ # @example
17
+ # BrainzLab::Beacon.create_http_monitor(
18
+ # "Production API",
19
+ # "https://api.example.com/health",
20
+ # interval: 30,
21
+ # expected_status: 200,
22
+ # timeout: 5
23
+ # )
24
+ #
25
+ def create_http_monitor(name, url, interval: 60, **)
26
+ return nil unless enabled?
27
+
28
+ ensure_provisioned!
29
+ return nil unless BrainzLab.configuration.beacon_valid?
30
+
31
+ client.create_monitor(
32
+ name: name,
33
+ url: url,
34
+ type: 'http',
35
+ interval: interval,
36
+ **
37
+ )
38
+ end
39
+
40
+ # Create an SSL certificate monitor
41
+ def create_ssl_monitor(name, domain, warn_days: 30, **)
42
+ return nil unless enabled?
43
+
44
+ ensure_provisioned!
45
+ return nil unless BrainzLab.configuration.beacon_valid?
46
+
47
+ client.create_monitor(
48
+ name: name,
49
+ url: "https://#{domain}",
50
+ type: 'ssl',
51
+ ssl_warn_days: warn_days,
52
+ **
53
+ )
54
+ end
55
+
56
+ # Create a TCP port monitor
57
+ def create_tcp_monitor(name, host, port, **)
58
+ return nil unless enabled?
59
+
60
+ ensure_provisioned!
61
+ return nil unless BrainzLab.configuration.beacon_valid?
62
+
63
+ client.create_monitor(
64
+ name: name,
65
+ url: "#{host}:#{port}",
66
+ type: 'tcp',
67
+ **
68
+ )
69
+ end
70
+
71
+ # Create a DNS monitor
72
+ def create_dns_monitor(name, domain, expected_record: nil, **)
73
+ return nil unless enabled?
74
+
75
+ ensure_provisioned!
76
+ return nil unless BrainzLab.configuration.beacon_valid?
77
+
78
+ client.create_monitor(
79
+ name: name,
80
+ url: domain,
81
+ type: 'dns',
82
+ expected_record: expected_record,
83
+ **
84
+ )
85
+ end
86
+
87
+ # Get monitor by ID
88
+ def get(id)
89
+ return nil unless enabled?
90
+
91
+ ensure_provisioned!
92
+ return nil unless BrainzLab.configuration.beacon_valid?
93
+
94
+ client.get_monitor(id)
95
+ end
96
+
97
+ # List all monitors
98
+ def list
99
+ return [] unless enabled?
100
+
101
+ ensure_provisioned!
102
+ return [] unless BrainzLab.configuration.beacon_valid?
103
+
104
+ client.list_monitors
105
+ end
106
+
107
+ # Update a monitor
108
+ def update(id, **attributes)
109
+ return false unless enabled?
110
+
111
+ ensure_provisioned!
112
+ return false unless BrainzLab.configuration.beacon_valid?
113
+
114
+ client.update_monitor(id, **attributes)
115
+ end
116
+
117
+ # Delete a monitor
118
+ def delete(id)
119
+ return false unless enabled?
120
+
121
+ ensure_provisioned!
122
+ return false unless BrainzLab.configuration.beacon_valid?
123
+
124
+ client.delete_monitor(id)
125
+ end
126
+
127
+ # Pause a monitor
128
+ def pause(id)
129
+ return false unless enabled?
130
+
131
+ ensure_provisioned!
132
+ return false unless BrainzLab.configuration.beacon_valid?
133
+
134
+ client.pause_monitor(id)
135
+ end
136
+
137
+ # Resume a paused monitor
138
+ def resume(id)
139
+ return false unless enabled?
140
+
141
+ ensure_provisioned!
142
+ return false unless BrainzLab.configuration.beacon_valid?
143
+
144
+ client.resume_monitor(id)
145
+ end
146
+
147
+ # Get check history for a monitor
148
+ def history(monitor_id, limit: 100)
149
+ return [] unless enabled?
150
+
151
+ ensure_provisioned!
152
+ return [] unless BrainzLab.configuration.beacon_valid?
153
+
154
+ client.check_history(monitor_id, limit: limit)
155
+ end
156
+
157
+ # Get overall status summary
158
+ def status
159
+ return nil unless enabled?
160
+
161
+ ensure_provisioned!
162
+ return nil unless BrainzLab.configuration.beacon_valid?
163
+
164
+ client.status_summary
165
+ end
166
+
167
+ # Check if all monitors are up
168
+ def all_up?
169
+ summary = status
170
+ return false unless summary
171
+
172
+ %w[up operational].include?(summary[:status])
173
+ end
174
+
175
+ # List active incidents
176
+ def incidents(status: nil)
177
+ return [] unless enabled?
178
+
179
+ ensure_provisioned!
180
+ return [] unless BrainzLab.configuration.beacon_valid?
181
+
182
+ client.list_incidents(status: status)
183
+ end
184
+
185
+ # === INTERNAL ===
186
+
187
+ def ensure_provisioned!
188
+ return if @provisioned
189
+
190
+ @provisioned = true
191
+ provisioner.ensure_project!
192
+ end
193
+
194
+ def provisioner
195
+ @provisioner ||= Provisioner.new(BrainzLab.configuration)
196
+ end
197
+
198
+ def client
199
+ @client ||= Client.new(BrainzLab.configuration)
200
+ end
201
+
202
+ def reset!
203
+ @client = nil
204
+ @provisioner = nil
205
+ @provisioned = false
206
+ end
207
+
208
+ private
209
+
210
+ def enabled?
211
+ BrainzLab.configuration.beacon_enabled
212
+ end
213
+ end
214
+ end
215
+ end