betterstackmanager 0.0.1
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.
- package/README.md +40 -0
- package/docs/classes/Client.md +17 -0
- package/docs/classes/Error.md +29 -0
- package/docs/classes/IncidentComment.md +49 -0
- package/docs/classes/Incidents.md +118 -0
- package/docs/objects/Incident.md +56 -0
- package/docs/objects/IncidentComment.md +44 -0
- package/docs/objects/Monitor.md +7 -0
- package/docs/readme.md +10 -0
- package/examples/acknowledgeIncident.js +15 -0
- package/examples/config.template.json +4 -0
- package/examples/createIncident.js +24 -0
- package/examples/createIncidentComment.js +18 -0
- package/examples/getSingleIncident.js +16 -0
- package/examples/readme.md +36 -0
- package/examples/resolveIncident.js +15 -0
- package/package.json +17 -0
- package/src/client/index.js +64 -0
- package/src/errors/Error.js +55 -0
- package/src/etc/Incident.js +145 -0
- package/src/etc/IncidentComment.js +72 -0
- package/src/index.js +8 -0
package/README.md
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# BSM
|
|
2
|
+
|
|
3
|
+
BSM is a Node.js module made to make managing Better Stack data easier and quicker.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
There is a required node version, and it is `>= 6.0.0`.
|
|
8
|
+
|
|
9
|
+
You must clone this repository (or, at least, the `src` directory) and require the Client object.
|
|
10
|
+
|
|
11
|
+
```js
|
|
12
|
+
const BSM = require('./src/client/index');
|
|
13
|
+
const client = new BSM();
|
|
14
|
+
const config = require('./config.json');
|
|
15
|
+
|
|
16
|
+
client.login(config.token);
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Once you've done this, you can use the `client` object.
|
|
20
|
+
|
|
21
|
+
The node version requirement actually depends on the `form-data` sub-dependency included by `axios`.
|
|
22
|
+
|
|
23
|
+
## Example usage
|
|
24
|
+
|
|
25
|
+
You should browse the `example` directory to get started with actual examples.
|
|
26
|
+
|
|
27
|
+
## Documentation
|
|
28
|
+
|
|
29
|
+
The documentation for this project is contained within the `docs` directory.
|
|
30
|
+
|
|
31
|
+
## Links
|
|
32
|
+
|
|
33
|
+
- [Documentation](https://codeberg.org/Cyanic76/BSM.js/src/branch/main/docs)
|
|
34
|
+
- [Discord server](https://cyanic.me/next)
|
|
35
|
+
|
|
36
|
+
## Contributing & Help
|
|
37
|
+
|
|
38
|
+
As always, if you're in need of help, don't hesitate to create a new issue or to [join my Discord server](https://cyanic.me/discord).
|
|
39
|
+
|
|
40
|
+
This project abides by the CCoC.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Client
|
|
2
|
+
|
|
3
|
+
The `Client` interface is the main entry point to interact with BetterStack.
|
|
4
|
+
|
|
5
|
+
> Before creating a new BetterStack client, you must first [get a **global API key**](https://betterstack.com/settings/global-api-tokens).
|
|
6
|
+
|
|
7
|
+
## `client`
|
|
8
|
+
|
|
9
|
+
`new Client(token)`
|
|
10
|
+
|
|
11
|
+
| Value | Type | Required | About |
|
|
12
|
+
|-|-|-|-|
|
|
13
|
+
| `token` | String | true | The global API key. |
|
|
14
|
+
|
|
15
|
+
## Classes
|
|
16
|
+
|
|
17
|
+
The Client constructor includes the `incidents` class.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Errors
|
|
2
|
+
|
|
3
|
+
The errors generated via this module fall in 3 categories:
|
|
4
|
+
|
|
5
|
+
- the API errors,
|
|
6
|
+
- the network errors,
|
|
7
|
+
- the request errors.
|
|
8
|
+
|
|
9
|
+
## APIError
|
|
10
|
+
|
|
11
|
+
| Value | Type |
|
|
12
|
+
|-|-|
|
|
13
|
+
| name | `APIError` |
|
|
14
|
+
| status | HTTP Response code (usually 4XX or 5XX) |
|
|
15
|
+
| data | The error data. |
|
|
16
|
+
|
|
17
|
+
# NetworkError
|
|
18
|
+
|
|
19
|
+
| Value | Type |
|
|
20
|
+
|-|-|
|
|
21
|
+
| name | `NetworkError` |
|
|
22
|
+
| message | The error message. |
|
|
23
|
+
|
|
24
|
+
# RequestError
|
|
25
|
+
|
|
26
|
+
| Value | Type |
|
|
27
|
+
|-|-|
|
|
28
|
+
| name | `RequestError` |
|
|
29
|
+
| message | The error message. |
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# IncidentComments
|
|
2
|
+
|
|
3
|
+
Class: `client.incidentComments`
|
|
4
|
+
|
|
5
|
+
This is the point from which all incident comments can be managed.
|
|
6
|
+
|
|
7
|
+
> **Markdown is supported within comments.**
|
|
8
|
+
|
|
9
|
+
## create(incidentId, incidentCommentText)
|
|
10
|
+
|
|
11
|
+
Add a comment to an existing incident.
|
|
12
|
+
|
|
13
|
+
| Value | Type | Required | About |
|
|
14
|
+
|-|-|-|-|
|
|
15
|
+
| `incidentId` | Number | true | Specify the ID of the incident. |
|
|
16
|
+
| `incidentCommentText` | String | true | The text of the comment. |
|
|
17
|
+
|
|
18
|
+
This can return:
|
|
19
|
+
|
|
20
|
+
- HTTP 201 if it was successful.
|
|
21
|
+
|
|
22
|
+
## get(incidentId, incidentCommentId)
|
|
23
|
+
|
|
24
|
+
Fetch one existing comment from an incident.
|
|
25
|
+
|
|
26
|
+
| Value | Type | Required | About |
|
|
27
|
+
|-|-|-|-|
|
|
28
|
+
| `incidentId` | Number | true | Specify the ID of the incident. |
|
|
29
|
+
| `incidentCommentId` | Number | true | Specify the ID of the incident comment. |
|
|
30
|
+
|
|
31
|
+
This can return:
|
|
32
|
+
|
|
33
|
+
- HTTP 200 with the comment object if it was successful.
|
|
34
|
+
- HTTP 404 if the given comment or incident doesn't exist.
|
|
35
|
+
|
|
36
|
+
## update(incidentId, incidentCommentId, incidentCommentText)
|
|
37
|
+
|
|
38
|
+
Edit an existing comment on an incident.
|
|
39
|
+
|
|
40
|
+
| Value | Type | Required | About |
|
|
41
|
+
|-|-|-|-|
|
|
42
|
+
| `incidentId` | Number | true | Specify the ID of the incident. |
|
|
43
|
+
| `incidentCommentId` | Number | true | Specify the ID of the incident comment. |
|
|
44
|
+
| `incidentCommentText` | String | true | New text of the incident comment. |
|
|
45
|
+
|
|
46
|
+
This can return:
|
|
47
|
+
|
|
48
|
+
- HTTP 200 if it was successful.
|
|
49
|
+
- HTTP 404 if the given comment or incident doesn't exist.
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# Incidents
|
|
2
|
+
|
|
3
|
+
Class: `client.incidents`
|
|
4
|
+
|
|
5
|
+
This is the point from which all incidents can be managed.
|
|
6
|
+
|
|
7
|
+
Status updates can't be managed from here.
|
|
8
|
+
|
|
9
|
+
## `acknowledge`
|
|
10
|
+
|
|
11
|
+
`acknowledge(incidentId)`
|
|
12
|
+
|
|
13
|
+
Acknowledge an existing incident.
|
|
14
|
+
|
|
15
|
+
| Value | Type | Required | About |
|
|
16
|
+
|-|-|-|-|
|
|
17
|
+
| `incidentId` | Number | true | Specify the ID of the incident. |
|
|
18
|
+
|
|
19
|
+
This can return:
|
|
20
|
+
|
|
21
|
+
- HTTP 200 if it was successful,
|
|
22
|
+
- HTTP 409 if that incident was already acknowledged.
|
|
23
|
+
|
|
24
|
+
## `create`
|
|
25
|
+
|
|
26
|
+
`create(incidentData)`
|
|
27
|
+
|
|
28
|
+
Create a new incident. `incidentData` is an object with the following properties.
|
|
29
|
+
|
|
30
|
+
| Value | Type | Required | About |
|
|
31
|
+
|-|-|-|-|
|
|
32
|
+
| `teamName` | String | true | Specify the team which should own the resource. |
|
|
33
|
+
| `name` | String | true | The title of the incident. |
|
|
34
|
+
| `summary` | String | true | A short summary. |
|
|
35
|
+
| `description` | String | true | A description of the incident. |
|
|
36
|
+
| `call` | Boolean | false | Whether the on-call person should be called. |
|
|
37
|
+
| `sms` | Boolean | false | Whether the on-call person should receive a text message. |
|
|
38
|
+
| `email` | Boolean | false | Whether the on-call person should be notified via email. |
|
|
39
|
+
| `critical_alert` | Boolean | false | Whether a notification ignoring the Do Not Disturb mode and the mute switch should be sent. |
|
|
40
|
+
| `team_wait` | Number | false | Wait time before escalating the incident to the team specified in `teamName`, in seconds. |
|
|
41
|
+
| `policy_id` | String | false | Policy escalation ID with which the incident should be escalated. |
|
|
42
|
+
|
|
43
|
+
This can return:
|
|
44
|
+
|
|
45
|
+
- HTTP 201 if the incident was successfully created,
|
|
46
|
+
- HTTP 404 if the token was not provided.
|
|
47
|
+
|
|
48
|
+
Most optional options can also be found in the [upstream](https://betterstack.com/docs/uptime/api/create-a-new-incident/) documentation.
|
|
49
|
+
|
|
50
|
+
## `escalate`
|
|
51
|
+
|
|
52
|
+
`escalate(incidentId, escalationData)`
|
|
53
|
+
|
|
54
|
+
Escalate an incident to someone else.
|
|
55
|
+
|
|
56
|
+
| Value | Type | Required | About |
|
|
57
|
+
|-|-|-|-|
|
|
58
|
+
| `incidentId` | Number | true | Specify the ID of the incident. |
|
|
59
|
+
|
|
60
|
+
Some properties in the `escalationData` value are only required depending on what is the escalation type defined in the `type` value.
|
|
61
|
+
|
|
62
|
+
| `escalationData` property | Type | Required | About |
|
|
63
|
+
|-|-|-|-|
|
|
64
|
+
| `type` | String | true | Either `User`, `Team`, `Schedule`, `Policy` or `Organization`. |
|
|
65
|
+
| `user_email` | String | | User to escalate this incident to. Either `user_email` or `user_id` is required if `type` is set to `User`. |
|
|
66
|
+
| `user_id` | String | | User to escalate this incident to. Either `user_email` or `user_id` is required if `type` is set to `User`. |
|
|
67
|
+
| `team_name` | String | | Team to escalate this incident to. Either `team_name` or `team_id` is required if `type` is set to `Team`. |
|
|
68
|
+
| `team_id` | String | | Team to escalate this incident to. Either `team_name` or `team_id` is required if `type` is set to `Team`. |
|
|
69
|
+
| `schedule_id` | String | | Schedule to escalate this incident to. Required if `type` is set to `Schedule`. |
|
|
70
|
+
| `policy_id` | String | | Policy to escalate this incident to. Required if `type` is set to `Policy`. |
|
|
71
|
+
|
|
72
|
+
Other properties of this value can be found in the [upstream](https://betterstack.com/docs/uptime/api/escalate-an-ongoing-incident/) documentation, though they're not required.
|
|
73
|
+
|
|
74
|
+
This can return:
|
|
75
|
+
|
|
76
|
+
- HTTP 200 if the incident was successfully escalated,
|
|
77
|
+
- HTTP 409 if the incident was already resolved.
|
|
78
|
+
|
|
79
|
+
## `get`
|
|
80
|
+
|
|
81
|
+
Fetch an existing incident.
|
|
82
|
+
|
|
83
|
+
| Value | Type | Required | About |
|
|
84
|
+
|-|-|-|-|
|
|
85
|
+
| `incidentId` | Number | true | Specify the ID of the incident. |
|
|
86
|
+
|
|
87
|
+
This can return:
|
|
88
|
+
|
|
89
|
+
- HTTP 200 with the incident data as an Object if it was successful.
|
|
90
|
+
|
|
91
|
+
## `remove`
|
|
92
|
+
|
|
93
|
+
`remove(incidentId)`
|
|
94
|
+
|
|
95
|
+
Delete an existing incident.
|
|
96
|
+
|
|
97
|
+
| Value | Type | Required | About |
|
|
98
|
+
|-|-|-|-|
|
|
99
|
+
| `incidentId` | Number | true | Specify the ID of the incident. |
|
|
100
|
+
|
|
101
|
+
This can return:
|
|
102
|
+
|
|
103
|
+
- HTTP 204 if it was successful.
|
|
104
|
+
|
|
105
|
+
## `resolve`
|
|
106
|
+
|
|
107
|
+
`resolve(incidentId)`
|
|
108
|
+
|
|
109
|
+
Resolve and close an existing incident.
|
|
110
|
+
|
|
111
|
+
| Value | Type | Required | About |
|
|
112
|
+
|-|-|-|-|
|
|
113
|
+
| `incidentId` | Number | true | Specify the ID of the incident. |
|
|
114
|
+
|
|
115
|
+
This can return:
|
|
116
|
+
|
|
117
|
+
- HTTP 200 if it was successful,
|
|
118
|
+
- HTTP 409 if that incident was already resolved.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Incident object
|
|
2
|
+
|
|
3
|
+
An Incident object has the following properties:
|
|
4
|
+
|
|
5
|
+
> The namespace is `data`.
|
|
6
|
+
|
|
7
|
+
## data
|
|
8
|
+
|
|
9
|
+
The `data` property has many sub-properties, including the following:
|
|
10
|
+
|
|
11
|
+
| Property | Type | Description |
|
|
12
|
+
|-|-|-|
|
|
13
|
+
| `id` | String | The ID of the incident. |
|
|
14
|
+
| `type` | String | The type that should be "incident". |
|
|
15
|
+
|
|
16
|
+
## data.attributes
|
|
17
|
+
|
|
18
|
+
| Property | Type | Description |
|
|
19
|
+
|-|-|-|
|
|
20
|
+
| `name` | String | The name of the incident. |
|
|
21
|
+
| `http_method` | String | Which HTTP method was used. |
|
|
22
|
+
| `cause` | String | The reason why this incident was created. |
|
|
23
|
+
| `url` | String | The URL to the incident. |
|
|
24
|
+
| `started_at` | String | The ISO-formatted timestamp at which the incident started. |
|
|
25
|
+
| `acknowledged_at` | String/null | The ISO-formatted timestamp at which the incident was first acknowledged. This will be `null` if it was automatic or not yet acknowledged. |
|
|
26
|
+
| `acknowledged_by` | String/null | The user who acknowledged that incident. This will be `null` if it was automatic or not yet acknowledged. |
|
|
27
|
+
| `resolved_at` | String/null | The ISO-formatted timestamp at which the incident was resolved. This will be `null` if it was automatic or not yet acknowledged. |
|
|
28
|
+
| `resolved_by` | String/null | The user who acknowledged that incident. This will be `null` if it was automatic or not yet resolved. |
|
|
29
|
+
| `status` | String | The current status of this incident. |
|
|
30
|
+
| `team_name` | String | The team this incident is in. |
|
|
31
|
+
| `response_content` | String | Body of the response that triggered the incident. **undefined/null if it exceeds 500 bytes.** |
|
|
32
|
+
| `response_options` | JSON String | The HTTP response headers. |
|
|
33
|
+
| `response_url` | String | Link to the logs of this incident. **Only if `response_content` is longer than 500 bytes.** |
|
|
34
|
+
| `screenshot_url` | String | A screenshot that was captured when the incident started. |
|
|
35
|
+
| `escalation_policy_id` | String/null | The policy with which that incident was escalated. This will be `null` if it was never or not yet escalated. |
|
|
36
|
+
| `call` | Boolean | If the on-call person was called. |
|
|
37
|
+
| `sms` | Boolean | If the on-call person was sent a text message. |
|
|
38
|
+
| `email` | Boolean | If the on-call person was sent a mail. |
|
|
39
|
+
| `push` | Boolean | If the on-call person was sent a push notification on the app. |
|
|
40
|
+
| `critical_alert` | Boolean | If the incident triggered a critical alert. |
|
|
41
|
+
| `regions` | Array of Strings | The regions in which the monitor that triggered this incident run. |
|
|
42
|
+
| `ssl_certificate_expires_at` | String | The ISO-formatted timestamp at which the SSL certificate will expire if the incident is about a SSL cert problem. |
|
|
43
|
+
| `domain_expires_at` | String | The ISO-formatted timestamp at which the domain will expire if the incident is about a domain expiration problem. |
|
|
44
|
+
|
|
45
|
+
## included
|
|
46
|
+
|
|
47
|
+
This is an Array of Objects containing the monitors that triggered that incident.
|
|
48
|
+
|
|
49
|
+
Each Object may have the following properties:
|
|
50
|
+
|
|
51
|
+
| Name | Type | Description |
|
|
52
|
+
|-|-|-|
|
|
53
|
+
| `id` | String | The ID of the resource. |
|
|
54
|
+
| `type` | String | The type of resource. |
|
|
55
|
+
|
|
56
|
+
The type may be "heartbeat" or "monitor". In any case, go to the corresponding page to learn more.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Incident Comment object
|
|
2
|
+
|
|
3
|
+
An Incident Comment object has the following properties:
|
|
4
|
+
|
|
5
|
+
> The namespace is `data`.
|
|
6
|
+
|
|
7
|
+
## data
|
|
8
|
+
|
|
9
|
+
The `data` property has many sub-properties, including the following:
|
|
10
|
+
|
|
11
|
+
| Property | Type | Description |
|
|
12
|
+
|-|-|-|
|
|
13
|
+
| `id` | String | The ID of the incident comment. |
|
|
14
|
+
| `type` | String | The type that should be "incident_comment". |
|
|
15
|
+
|
|
16
|
+
## attributes
|
|
17
|
+
|
|
18
|
+
| Property | Type | Description |
|
|
19
|
+
|-|-|-|
|
|
20
|
+
| `id` | String | The ID of the incident comment. |
|
|
21
|
+
| `comment` | String | The comment. |
|
|
22
|
+
| `user_id` | Number | The ID of the author. |
|
|
23
|
+
| `user_email` | String | The email address of the author. |
|
|
24
|
+
| `created_at` | String | The ISO-formatted timestamp at which the incident comment was created. |
|
|
25
|
+
| `updated_at` | String | The ISO-formatted timestamp at which the incident comment was last edited. |
|
|
26
|
+
|
|
27
|
+
## Example
|
|
28
|
+
|
|
29
|
+
```json
|
|
30
|
+
{
|
|
31
|
+
"data": {
|
|
32
|
+
"id": "1",
|
|
33
|
+
"type": "incident_comment",
|
|
34
|
+
"attributes": {
|
|
35
|
+
"id": 1,
|
|
36
|
+
"content": "content",
|
|
37
|
+
"user_id": 123,
|
|
38
|
+
"user_email": "test@example.com",
|
|
39
|
+
"created_at": "2025-06-03T12:10:28.357Z",
|
|
40
|
+
"updated_at": "2025-06-03T12:10:28.357Z"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
```
|
package/docs/readme.md
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# BSM - Better Stack Manager
|
|
2
|
+
|
|
3
|
+
Manage your Better Stack incidents & more using the API with this library.
|
|
4
|
+
|
|
5
|
+
You're in the **user-facing documentation**.
|
|
6
|
+
|
|
7
|
+
## Links
|
|
8
|
+
|
|
9
|
+
- [Source code](https://codeberg.org/Cyanic76/BSM.js)
|
|
10
|
+
- [Discord server](https://cyanic.me/next)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const BSM = require('../src/index');
|
|
2
|
+
const config = require('./config.json');
|
|
3
|
+
|
|
4
|
+
const client = new BSM.Client(config.token);
|
|
5
|
+
|
|
6
|
+
async function main(incidentId) {
|
|
7
|
+
try {
|
|
8
|
+
await client.incidents.acknowledge(incidentId);
|
|
9
|
+
console.log('Acknowledged incident ', incidentId);
|
|
10
|
+
} catch(e) {
|
|
11
|
+
console.error('Failed to acknowledge an incident: ', e.message);
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
main('YOUR_INCIDENT_ID');
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const BSM = require('../src/index');
|
|
2
|
+
const config = require('./config.json');
|
|
3
|
+
|
|
4
|
+
const client = new BSM.Client(config.token);
|
|
5
|
+
|
|
6
|
+
async function main(incidentData) {
|
|
7
|
+
try {
|
|
8
|
+
const incident = await client.incidents.create(incidentData);
|
|
9
|
+
console.log('My new incident: ', incident);
|
|
10
|
+
} catch(e) {
|
|
11
|
+
console.error('Failed to create an incident:', e.message);
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const incidentData = {
|
|
16
|
+
requester_email: config.email,
|
|
17
|
+
name: 'My testing incident',
|
|
18
|
+
summary: 'Something went wrong.',
|
|
19
|
+
description: 'Something went horribly wrong!',
|
|
20
|
+
email: true,
|
|
21
|
+
teamName: 'Main'
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
main(incidentData);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const BSM = require('../src/index');
|
|
2
|
+
const config = require('./config.json');
|
|
3
|
+
|
|
4
|
+
const client = new BSM.Client(config.token);
|
|
5
|
+
|
|
6
|
+
async function main(incidentId, incidentCommentText) {
|
|
7
|
+
try {
|
|
8
|
+
await client.incidentComments.create(incidentId, incidentCommentText);
|
|
9
|
+
console.log('Added this comment to the incident ', incidentId);
|
|
10
|
+
} catch(e) {
|
|
11
|
+
console.error('Failed to add a comment to this incident: ', e.message);
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
main(
|
|
16
|
+
'YOUR_INCIDENT_ID',
|
|
17
|
+
'YOUR_COMMENT_TEXT'
|
|
18
|
+
);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const BSM = require('../src/index');
|
|
2
|
+
const config = require('./config.json');
|
|
3
|
+
|
|
4
|
+
const client = new BSM.Client(config.token);
|
|
5
|
+
|
|
6
|
+
async function main(incidentId) {
|
|
7
|
+
try {
|
|
8
|
+
const incident = await client.incidents.get(incidentId);
|
|
9
|
+
console.log(incident.included);
|
|
10
|
+
} catch(e) {
|
|
11
|
+
console.error('Failed to fetch that incident!');
|
|
12
|
+
console.log(e);
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
main(875155703);
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# BSM - Better Stack Manager
|
|
2
|
+
|
|
3
|
+
Manage your Better Stack incidents & more using the API with this library.
|
|
4
|
+
|
|
5
|
+
You're in the **code examples**.
|
|
6
|
+
|
|
7
|
+
## Specifying correct configuration data
|
|
8
|
+
|
|
9
|
+
You'll need to have, at least,
|
|
10
|
+
- your **global API** key,
|
|
11
|
+
- a mail address.
|
|
12
|
+
|
|
13
|
+
## Following examples
|
|
14
|
+
|
|
15
|
+
All examples show how to get your BSM client up and running.
|
|
16
|
+
|
|
17
|
+
## acknowledgeIncident.js
|
|
18
|
+
|
|
19
|
+
This short example acknowledges an incident with the given ID.
|
|
20
|
+
|
|
21
|
+
## createIncident.js
|
|
22
|
+
|
|
23
|
+
This short example creates an incident with the given name, summary and explanation.
|
|
24
|
+
|
|
25
|
+
## createIncidentComment.js
|
|
26
|
+
|
|
27
|
+
This example appends a new comment to an existing incident.
|
|
28
|
+
|
|
29
|
+
## resolveIncident.js
|
|
30
|
+
|
|
31
|
+
This example resolves an existing incident with the given ID.
|
|
32
|
+
|
|
33
|
+
## Links
|
|
34
|
+
|
|
35
|
+
- [Source code](https://codeberg.org/Cyanic76/BSM.js)
|
|
36
|
+
- [Discord server](https://cyanic.me/next)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const BSM = require('../src/index');
|
|
2
|
+
const config = require('./config.json');
|
|
3
|
+
|
|
4
|
+
const client = new BSM.Client(config.token);
|
|
5
|
+
|
|
6
|
+
async function main(incidentId) {
|
|
7
|
+
try {
|
|
8
|
+
await client.incidents.acknowledge(incidentId);
|
|
9
|
+
console.log('Resolved incident ', incidentId);
|
|
10
|
+
} catch(e) {
|
|
11
|
+
console.error('Failed to resolve an incident: ', e.message);
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
main('YOUR_INCIDENT_ID');
|
package/package.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "betterstackmanager",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Interact with Better Stack in Node.JS",
|
|
5
|
+
"license": "GPL-3.0-only",
|
|
6
|
+
"author": "Cyanic76",
|
|
7
|
+
"type": "commonjs",
|
|
8
|
+
"main": "src/index.js",
|
|
9
|
+
"directories": {
|
|
10
|
+
"doc": "docs",
|
|
11
|
+
"example": "examples",
|
|
12
|
+
"lib": "src"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"axios": "^1.13.2"
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
const axios = require('axios');
|
|
2
|
+
|
|
3
|
+
const Incidents = require('../etc/Incident');
|
|
4
|
+
const IncidentComments = require('../etc/IncidentComment');
|
|
5
|
+
|
|
6
|
+
const { handleError } = require('../errors/Error');
|
|
7
|
+
|
|
8
|
+
class Client {
|
|
9
|
+
constructor(token, baseURL = 'https://uptime.betterstack.com/api/'){
|
|
10
|
+
|
|
11
|
+
if(!token || token == '') throw new Error('No token was provided.');
|
|
12
|
+
|
|
13
|
+
this.client = axios.create({
|
|
14
|
+
baseURL,
|
|
15
|
+
headers: {
|
|
16
|
+
'Authorization': `Bearer ${token}`,
|
|
17
|
+
'Content-Type': 'application/json'
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
// Get the right classes as found in the folder.
|
|
22
|
+
this.incidents = new Incidents(this);
|
|
23
|
+
this.incidentComments = new IncidentComments(this);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async delete (endpoint) {
|
|
27
|
+
try {
|
|
28
|
+
const response = await this.client.delete(endpoint);
|
|
29
|
+
return response.data;
|
|
30
|
+
} catch(error) {
|
|
31
|
+
handleError(error);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async get (endpoint, params = {}) {
|
|
36
|
+
try {
|
|
37
|
+
const response = await this.client.get(endpoint, { params });
|
|
38
|
+
return response.data;
|
|
39
|
+
} catch(error) {
|
|
40
|
+
handleError(error);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async patch (endpoint, data = {}) {
|
|
45
|
+
try {
|
|
46
|
+
const response = await this.client.patch(endpoint, data);
|
|
47
|
+
return response.data;
|
|
48
|
+
} catch(error) {
|
|
49
|
+
handleError(error);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async post (endpoint, data = {}) {
|
|
54
|
+
try {
|
|
55
|
+
const response = await this.client.post(endpoint, data);
|
|
56
|
+
return response.data;
|
|
57
|
+
} catch(error) {
|
|
58
|
+
handleError(error);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
module.exports = { Client };
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
class APIError extends Error {
|
|
2
|
+
constructor(message, status, data){
|
|
3
|
+
super(message);
|
|
4
|
+
this.name = 'APIError';
|
|
5
|
+
this.status = status;
|
|
6
|
+
this.data = data;
|
|
7
|
+
}
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
class NetworkError extends Error {
|
|
11
|
+
constructor(message) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.name = 'NetworkError';
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
class RequestError extends Error {
|
|
18
|
+
constructor(message) {
|
|
19
|
+
super(message);
|
|
20
|
+
this.name = 'RequestError';
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Manages API errors.
|
|
26
|
+
* @param {Error} error - The Axios error.
|
|
27
|
+
* @throws {APIError|NetworkError|RequestError}
|
|
28
|
+
*/
|
|
29
|
+
function handleError(error) {
|
|
30
|
+
if(error.response) {
|
|
31
|
+
// API replied with an error status.
|
|
32
|
+
throw new APIError(
|
|
33
|
+
`API Error: ${error.response.status}`,
|
|
34
|
+
error.response.status,
|
|
35
|
+
error.response.data
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// If request didn't reach API.
|
|
40
|
+
else if (error.request) {
|
|
41
|
+
throw new NetworkError('NetworkError: Did not get a response from API.');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// If a bad request was sent.
|
|
45
|
+
else {
|
|
46
|
+
throw new RequestError(`RequestError: ${error.message}`);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
module.exports = {
|
|
51
|
+
handleError,
|
|
52
|
+
APIError,
|
|
53
|
+
NetworkError,
|
|
54
|
+
RequestError
|
|
55
|
+
};
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
class Incidents {
|
|
2
|
+
/**
|
|
3
|
+
* @param {string} token
|
|
4
|
+
* @param {string} baseURL
|
|
5
|
+
*/
|
|
6
|
+
constructor(client) {
|
|
7
|
+
this.client = client;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Create a new incident.
|
|
12
|
+
* @param {object} incidentData
|
|
13
|
+
* @returns {Promise<Object>} - The new incident
|
|
14
|
+
*/
|
|
15
|
+
async create(incidentData) {
|
|
16
|
+
|
|
17
|
+
// Before trying anything, check some things right away.
|
|
18
|
+
if(!incidentData.teamName) throw new Error("Incident creation: teamName is a required incident option.");
|
|
19
|
+
if(!incidentData.name) throw new Error("Incident creation: name is a required incident option.");
|
|
20
|
+
if(!incidentData.summary) throw new Error("Incident creation: summary is a required incident option.");
|
|
21
|
+
if(!incidentData.description) throw new Error("Incident creation: description is a required incident option.");
|
|
22
|
+
if(!incidentData.email || typeof(incidentData.email) != 'boolean') throw new Error("Incident creation: email is a required boolean incident option.");
|
|
23
|
+
|
|
24
|
+
// Modify a copy of the provided object.
|
|
25
|
+
const data = { ...incidentData };
|
|
26
|
+
|
|
27
|
+
// Rename variables.
|
|
28
|
+
if('teamName' in data){ data.team_name = data.teamName; delete data.teamName };
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
return await this.client.post('/v3/incidents', data);
|
|
32
|
+
} catch(error) {
|
|
33
|
+
throw error;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Acknowledge an existing incident.
|
|
39
|
+
* @param {Number} incidentId - The ID of the incident.
|
|
40
|
+
* @returns
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
async acknowledge(incidentId) {
|
|
44
|
+
if(!incidentId) throw new Error("Incident acknowledgement: incidentId is a required incident option.");
|
|
45
|
+
if(isNaN(incidentId)) throw new Error("Incident acknowledgement: incidentId is not a Number.");
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
return await this.client.post(`/v3/incidents/${incidentId}/acknowledge`);
|
|
49
|
+
} catch(error) {
|
|
50
|
+
throw error;
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Fetch an existing incident.
|
|
56
|
+
* @param {Number} incidentId
|
|
57
|
+
* @returns
|
|
58
|
+
*/
|
|
59
|
+
|
|
60
|
+
async get(incidentId) {
|
|
61
|
+
if(!incidentId) throw new Error("Incident fetching: incidentId is a required incident option.");
|
|
62
|
+
if(isNaN(incidentId)) throw new Error("Incident acknowledgement: incidentId is not a Number.");
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
return await this.client.get(`/v3/incidents/${incidentId}`);
|
|
66
|
+
} catch(error) {
|
|
67
|
+
throw error;
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Resolve an existing incident.
|
|
73
|
+
* @param {Number} incidentId
|
|
74
|
+
* @returns
|
|
75
|
+
*/
|
|
76
|
+
|
|
77
|
+
async resolve(incidentId) {
|
|
78
|
+
if(!incidentId) throw new Error("Incident resolution: incidentId is a required incident option.");
|
|
79
|
+
if(isNaN(incidentId)) throw new Error("Incident acknowledgement: incidentId is not a Number.");
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
return await this.client.post(`/v3/incidents/${incidentId}/resolve`);
|
|
83
|
+
} catch(error) {
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Escalate an ongoing incident
|
|
90
|
+
* @param {Number} incidentId
|
|
91
|
+
* @param {Object} escalationData
|
|
92
|
+
* @returns
|
|
93
|
+
*/
|
|
94
|
+
|
|
95
|
+
async escalate(incidentId, escalationData){
|
|
96
|
+
|
|
97
|
+
// Before trying anything, check some things right away.
|
|
98
|
+
if(!incidentId || !escalationData) throw new Error("Incident resolution: incidentId and escalationData are required incident options.");
|
|
99
|
+
if(isNaN(incidentId)) throw new Error("Incident acknowledgement: incidentId is not a Number.");
|
|
100
|
+
const allowedTypes = ["User", "Team", "Schedule", "Policy", "Organization"];
|
|
101
|
+
if(!allowedTypes.includes(escalationData.type)) throw new Error("Incident resolution: Escalation Type is invalid.");
|
|
102
|
+
|
|
103
|
+
switch(escalationData.type){
|
|
104
|
+
case 'User':
|
|
105
|
+
if(!escalationData.user_email && !escalationData.user_id) throw new Error("Incident resolution: when escalating to User, either user_id or user_email must be passed.");
|
|
106
|
+
break;
|
|
107
|
+
case 'Team':
|
|
108
|
+
if(!escalationData.team_name && !escalationData.team_id) throw new Error("Incident resolution: when escalating to Team, either team_id or team_name must be passed.");
|
|
109
|
+
break;
|
|
110
|
+
case 'Schedule':
|
|
111
|
+
if(!escalationData.schedule_id) throw new Error("Incident resolution: when escalating to Schedule, schedule_id must be passed.");
|
|
112
|
+
break;
|
|
113
|
+
case 'Policy':
|
|
114
|
+
if(!escalationData.policy_id) throw new Error("Incident resolution: when escalating to Policy, policy_id must be passed.");
|
|
115
|
+
break;
|
|
116
|
+
default:break;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
return await this.client.post(`/v3/incidents/${incidentId}/escalate`, { escalation_type: escalationType });
|
|
121
|
+
} catch(error) {
|
|
122
|
+
throw error;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Delete an existing incident.
|
|
128
|
+
* @param {Number} incidentId
|
|
129
|
+
* @returns
|
|
130
|
+
*/
|
|
131
|
+
async remove(incidentId){
|
|
132
|
+
|
|
133
|
+
if(!incidentId) throw new Error("Incident deletion: incidentId is a required incident option.");
|
|
134
|
+
if(isNaN(incidentId)) throw new Error("Incident acknowledgement: incidentId is not a Number.");
|
|
135
|
+
|
|
136
|
+
try {
|
|
137
|
+
return await this.client.remove(`/v3/incidents/${incidentId}`);
|
|
138
|
+
} catch(error) {
|
|
139
|
+
throw error;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
module.exports = Incidents;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
class IncidentComments {
|
|
2
|
+
/**
|
|
3
|
+
* @param {string} token
|
|
4
|
+
* @param {string} baseURL
|
|
5
|
+
*/
|
|
6
|
+
constructor(client) {
|
|
7
|
+
this.client = client;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Add a comment to an existing incident.
|
|
12
|
+
* @param {Number} incidentId
|
|
13
|
+
* @param {String} incidentCommentText
|
|
14
|
+
* @returns
|
|
15
|
+
*/
|
|
16
|
+
async create(incidentId, incidentCommentText) {
|
|
17
|
+
|
|
18
|
+
if(!incidentId || !incidentCommentText) throw new Error("Incident comment creation: incidentId and incidentCommentText are required options.");
|
|
19
|
+
if(isNaN(incidentId)) throw new Error("Incident comment creation: incidentId must be a Number.");
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
return await this.client.post(`/v2/incidents/${incidentId}/comments`, {
|
|
23
|
+
content: incidentCommentText
|
|
24
|
+
});
|
|
25
|
+
} catch(error) {
|
|
26
|
+
throw error;
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Get an existing comment on an incident.
|
|
32
|
+
* @param {Number} incidentId
|
|
33
|
+
* @param {Number} incidentCommentId
|
|
34
|
+
* @returns
|
|
35
|
+
*/
|
|
36
|
+
async get(incidentId, incidentCommentId) {
|
|
37
|
+
|
|
38
|
+
if(!incidentId || !incidentCommentId) throw new Error("Incident comment fetch: incidentId, incidentCommentId are required options.");
|
|
39
|
+
if(isNaN(incidentId) || isNaN(incidentCommentId)) throw new Error("Incident comment fetch: incidentId and incidentCommentId must both be Numbers.");
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
return await this.client.get(`/v2/incidents/${incidentId}/comments/${incidentCommentId}`);
|
|
43
|
+
} catch(error) {
|
|
44
|
+
throw error;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Edit an existing incident comment.
|
|
51
|
+
* @param {Number} incidentId
|
|
52
|
+
* @param {Number} incidentCommentId
|
|
53
|
+
* @param {String} incidentCommentText
|
|
54
|
+
* @returns
|
|
55
|
+
*/
|
|
56
|
+
async update(incidentId, incidentCommentId, incidentCommentText) {
|
|
57
|
+
|
|
58
|
+
if(!incidentId || !incidentCommentId || !incidentCommentText ) throw new Error("Incident comment update: incidentId, incidentCommentId, incidentCommentText are required options.");
|
|
59
|
+
if(isNaN(incidentId) || isNaN(incidentCommentId)) throw new Error("Incident comment update: incidentId and incidentCommentId must both be Numbers.");
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
return await this.client.patch(`/v2/incidents/${incidentId}/comments/${incidentCommentId}`, {
|
|
63
|
+
content: incidentCommentText
|
|
64
|
+
});
|
|
65
|
+
} catch(error) {
|
|
66
|
+
throw error;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
module.exports = { IncidentComments };
|