diff --git a/.gitignore b/.gitignore index 9afb9d55..c77e2eaf 100644 --- a/.gitignore +++ b/.gitignore @@ -86,3 +86,4 @@ src/config/production_2.yaml src/config/butler-config.yaml src/udp_client/udp-client.exe src/config/production_empty_arrays.yaml +src/config/production_template_filledin.yaml diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 00000000..d053c759 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,10 @@ +{ + "tabWidth": 4, + "semi": true, + "singleQuote": true, + "printWidth": 140, + "arrowParens": "always", + "bracketSpacing": true, + + "useTabs": false +} diff --git a/.prettierrc.yaml b/.prettierrc.yaml deleted file mode 100644 index 8f6f31a3..00000000 --- a/.prettierrc.yaml +++ /dev/null @@ -1,10 +0,0 @@ -printWidth: 140 -tabWidth: 4 -useTabs: false -semi: true -singleQuote: true -trailingComma: 'es5' -bracketSpacing: true -arrowParens: 'always' -requirePragma: false -insertPragma: false diff --git a/.vscode/launch.json b/.vscode/launch.json index 468882cb..15ea0543 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,36 +1,37 @@ { - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "type": "node", - "request": "launch", - "name": "Launch Program", - "program": "${workspaceFolder}/src/butler.js", - "runtimeVersion": "20", - "cwd": "${workspaceFolder}/src", - "env": { - "NODE_CONFIG_DIR": "${workspaceFolder}/src/config", - "NODE_ENV": "production" - }, - "args": [ - "--configfile", - // "config/production.yaml", - "config/production_template.yaml", - // "--new-relic-account-name", - // "'First NR account'", - // "--new-relic-account-id", - // "ACCOUNTID", - // "--new-relic-api-key", - // "APIKEY", - - // "-c", - // "./config/config-gen-api-docs.yaml", - // "--no-qs-connection" - ], - "outFiles": ["${workspaceFolder}/**/*.js"] - } - ] -} + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "Launch Program", + "program": "${workspaceFolder}/src/butler.js", + "runtimeVersion": "20", + "cwd": "${workspaceFolder}/src", + "env": { + "NODE_CONFIG_DIR": "${workspaceFolder}/src/config", + "NODE_ENV": "production" + }, + "args": [ + "--configfile", + "config/production.yaml", + // "config/production_template.yaml", + // "config/production_template_filledin.yaml", + // "--new-relic-account-name", + // "'First NR account'", + // "--new-relic-account-id", + // "ACCOUNTID", + // "--new-relic-api-key", + // "APIKEY", + + // "-c", + // "./config/config-gen-api-docs.yaml", + // "--no-qs-connection" + ], + "outFiles": ["${workspaceFolder}/**/*.js"] + } + ] + } \ No newline at end of file diff --git a/docs/api_doc/butler-api.html b/docs/api_doc/butler-api.html index 43d62aaa..463c356c 100644 --- a/docs/api_doc/butler-api.html +++ b/docs/api_doc/butler-api.html @@ -1,358 +1,2147 @@ - + + + + Butler API documentation + + + + + + + - - - Butler API documentation - - - - - - - - - -
+
+ +
+
+ + + + + +

Butler API documentation (12.4.2)

Download OpenAPI specification:Download

+ + +
+
+
+
+
+
+

+ Butler API documentation + (12.4.2) +

+

+ Download OpenAPI specification:Download +

+
+
+
+

+ Butler is a microservice that provides add-on features to Qlik Sense Enterprise on Windows. Butler + offers both a REST API and things like failed reload notifications etc. +

+

+ This page contains the API documentation. Full documentation is available at + https://butler.ptarmiganlabs.com +

+
+ +
+
+
+
+
+
+

+ Get an array of all enabled API endpoints. +

+
+

Get an array of all enabled API endpoints, using the key names from the Butler config file.

-

Note: Endpoints are enabled/disabled in the Butler main configuration file.

-

Responses

Response samples

Content type
application/json
[
  • "activeUserCount",
  • "activeUsers",
  • "apiListEnbledEndpoints"
]

Converts strings from base62 to base16.

Converts strings from base62 to base16.

-
query Parameters
base62
required
string
Example: base62=6DMW88LpSok9Z7P7hUK0wv7bF

The base62 encoded string that should be converted to base16

-

Responses

Response samples

Content type
application/json
{
  • "base62": "6DMW88LpSok9Z7P7hUK0wv7bF",
  • "base16": "3199af08bfeeaf5d420f27ed9c01e74370077"
}

Converts strings from base16 to base62.

Converts strings from base16 to base62.

-
query Parameters
base16
required
string
Example: base16=3199af08bfeeaf5d420f27ed9c01e74370077

The base16 encoded string that should be converted to base62

-

Responses

Response samples

Content type
application/json
{
  • "base16": "3199af08bfeeaf5d420f27ed9c01e74370077",
  • "base62": "6DMW88LpSok9Z7P7hUK0wv7bF"
}

Tests if Butler is alive and responding

Tests if Butler is alive and responding

-

Responses

Response samples

Content type
application/json
{
  • "response": "Butler reporting for duty",
  • "butlerVersion": "5.5.0"
}

Copy file(s) between well defined, approved locations.

+

Get an array of all enabled API endpoints, using the key names from the Butler config file.

+

Note: Endpoints are enabled/disabled in the Butler main configuration file.

+
+
+
+

Responses

+
+ +
+
+
+
+
+ + +
+
+

Response samples

+
+
    + +
+
+
+
+ Content type +
application/json
+
+
+
+
+ +
+
+
+ [ +
    +
  • +
    + "activeUserCount", +
    +
  • +
  • +
    + "activeUsers", +
    +
  • +
  • +
    + "apiListEnbledEndpoints" +
    +
  • +
+ ]
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ Converts strings from base62 to base16. +

+
+
+

Converts strings from base62 to base16.

+
+
+
+
+ query + Parameters +
+ + + + + + + +
+ base62 +
required
+
+
+
+ string +
+
+ Example: + base62=6DMW88LpSok9Z7P7hUK0wv7bF +
+
+
+

The base62 encoded string that should be converted to base16

+
+
+
+
+
+
+

Responses

+
+ +
+
+ +
+
+ +
+
+
+
+
+ + +
+
+

Response samples

+
+
    + + + +
+
+
+
+ Content type +
application/json
+
+
+
+
+ +
+
+
+ { +
    +
  • +
    + "base62": + "6DMW88LpSok9Z7P7hUK0wv7bF", +
    +
  • +
  • +
    + "base16": + "3199af08bfeeaf5d420f27ed9c01e74370077" +
    +
  • +
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ Converts strings from base16 to base62. +

+
+
+

Converts strings from base16 to base62.

+
+
+
+
+ query + Parameters +
+ + + + + + + +
+ base16 +
required
+
+
+
+ string +
+
+ Example: + base16=3199af08bfeeaf5d420f27ed9c01e74370077 +
+
+
+

The base16 encoded string that should be converted to base62

+
+
+
+
+
+
+

Responses

+
+ +
+
+ +
+
+ +
+
+
+
+
+ + +
+
+

Response samples

+
+
    + + + +
+
+
+
+ Content type +
application/json
+
+
+
+
+ +
+
+
+ { +
    +
  • +
    + "base16": + "3199af08bfeeaf5d420f27ed9c01e74370077", +
    +
  • +
  • +
    + "base62": + "6DMW88LpSok9Z7P7hUK0wv7bF" +
    +
  • +
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ Tests if Butler is alive and responding +

+
+
+

Tests if Butler is alive and responding

+
+
+
+

Responses

+
+ +
+
+ +
+
+
+
+
+ + +
+
+

Response samples

+
+
    + + +
+
+
+
+ Content type +
application/json
+
+
+
+
+ +
+
+
+ { +
    +
  • +
    + "response": + "Butler reporting for duty", +
    +
  • +
  • +
    + "butlerVersion": + "5.5.0" +
    +
  • +
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ Copy file(s) between well defined, approved locations. +

+
+

Copying of files is only posttible between pre-approved directories. -Defining approved source and destination directories is done in Butler's config file.

-

If the source directory contains subdirectories, these will be copied too.

-
Request Body schema: application/json
fromFile
string

Name of source file.

-
toFile
string

Name of destination file. Can be different from source file name, if needed.

-
overwrite
boolean

Controls whether destination file should be overwritten if it already exists. Note that the copy operation will silently fail if you set this to false and the destination exists. Defaults to false.

-
preserveTimestamp
boolean

When true, the timestamp of the source file(s) will be preserved on the destination file(s). When false, timestamp behaviour is OS-dependent. Defaults to false.

-

Responses

Request samples

Content type
application/json
{
  • "fromFile": "subfolder/file1.qvd",
  • "toFile": "archive/file1_20200925.qvd",
  • "overwrite": false,
  • "preserveTimestamp": false
}

Response samples

Content type
application/json
{
  • "fromFile": "subfolder/file1.qvd",
  • "toFile": "archive/file1_20200925.qvd",
  • "overwrite": false,
  • "preserveTimestamp": false
}

Move file(s) between well defined, approved locations.

+

+ Copying of files is only posttible between pre-approved directories. Defining approved source + and destination directories is done in Butler's config file. +

+

If the source directory contains subdirectories, these will be copied too.

+
+
+
+ Request Body schema: application/json +
+
+ + + + + + + + + + + + + + + + + + + +
+ fromFile + +
+
+ string +
+
+
+

Name of source file.

+
+
+
+
+ toFile + +
+
+ string +
+
+
+

+ Name of destination file. Can be different from source file name, if needed. +

+
+
+
+
+ overwrite + +
+
+ boolean +
+
+
+

+ Controls whether destination file should be overwritten if it already + exists. Note that the copy operation will silently fail if you set this to + false and the destination exists. Defaults to false. +

+
+
+
+
+ preserveTimestamp + +
+
+ boolean +
+
+
+

+ When true, the timestamp of the source file(s) will be preserved on the + destination file(s). When false, timestamp behaviour is OS-dependent. + Defaults to false. +

+
+
+
+
+
+

Responses

+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+
+ + +
+
+

Request samples

+
+
    + +
+
+
+
+ Content type +
application/json
+
+
+
+
+ +
+
+
+ { +
    +
  • +
    + "fromFile": + "subfolder/file1.qvd", +
    +
  • +
  • +
    + "toFile": + "archive/file1_20200925.qvd", +
    +
  • +
  • +
    + "overwrite": + false, +
    +
  • +
  • +
    + "preserveTimestamp": false +
    +
  • +
+ }
+
+
+
+
+
+
+
+
+
+

Response samples

+
+
    + + + + +
+
+
+
+ Content type +
application/json
+
+
+
+
+ +
+
+
+ { +
    +
  • +
    + "fromFile": + "subfolder/file1.qvd", +
    +
  • +
  • +
    + "toFile": + "archive/file1_20200925.qvd", +
    +
  • +
  • +
    + "overwrite": + false, +
    +
  • +
  • +
    + "preserveTimestamp": false +
    +
  • +
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ Move file(s) between well defined, approved locations. +

+
+

Moving of files is only posttible between pre-approved directories. -Defining approved source and destination directories is done in Butler's config file.

-

If the source directory contains subdirectories, these will be moved too.

-
Request Body schema: application/json
fromFile
string

Name of source file.

-
toFile
string

Name of destination file. Can be different from source file name, if needed.

-
overwrite
boolean

Controls whether destination file should be overwritten if it already exists. Defaults to false.

-

Responses

Request samples

Content type
application/json
{
  • "fromFile": "subfolder/file1.qvd",
  • "toFile": "archive/file1_20200925.qvd",
  • "overwrite": false
}

Response samples

Content type
application/json
{
  • "fromFile": "subfolder/file1.qvd",
  • "toFile": "archive/file1_20200925.qvd",
  • "overwrite": false
}

Delete file(s) in well defined, approved locations.

+

+ Moving of files is only posttible between pre-approved directories. Defining approved source and + destination directories is done in Butler's config file. +

+

If the source directory contains subdirectories, these will be moved too.

+
+
+
+ Request Body schema: application/json +
+
+ + + + + + + + + + + + + + + +
+ fromFile + +
+
+ string +
+
+
+

Name of source file.

+
+
+
+
+ toFile + +
+
+ string +
+
+
+

+ Name of destination file. Can be different from source file name, if needed. +

+
+
+
+
+ overwrite + +
+
+ boolean +
+
+
+

+ Controls whether destination file should be overwritten if it already + exists. Defaults to false. +

+
+
+
+
+
+

Responses

+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+
+ + +
+
+

Request samples

+
+
    + +
+
+
+
+ Content type +
application/json
+
+
+
+
+ +
+
+
+ { +
    +
  • +
    + "fromFile": + "subfolder/file1.qvd", +
    +
  • +
  • +
    + "toFile": + "archive/file1_20200925.qvd", +
    +
  • +
  • +
    + "overwrite": + false +
    +
  • +
+ }
+
+
+
+
+
+
+
+
+
+

Response samples

+
+
    + + + + +
+
+
+
+ Content type +
application/json
+
+
+
+
+ +
+
+
+ { +
    +
  • +
    + "fromFile": + "subfolder/file1.qvd", +
    +
  • +
  • +
    + "toFile": + "archive/file1_20200925.qvd", +
    +
  • +
  • +
    + "overwrite": + false +
    +
  • +
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ Delete file(s) in well defined, approved locations. +

+
+

It is only possible to delete files in pre-approved directories, or subdirectories thereof. -Defining approved directories is done in Butler's config file.

-
Request Body schema: application/json
deleteFile
string

Name of file to be deleted. Use forward/backward slashes in paths as needed, depending on whether Butler runs on Windows/non-Windows platform.

-

Responses

Request samples

Content type
application/json
{
  • "deleteFile": "data/qvdstore/sales/file1.qvd"
}

Response samples

Content type
application/json
{ }

Creates a directory in designated QVD directory.

Creates a directory in QVD directory (which is defined in Butler's config file).

-
Request Body schema: application/json
directory
string

Directory that should be created.

-

Responses

Request samples

Content type
application/json
{
  • "directory": "subfolder/2020-10"
}

Response samples

Content type
application/json
{
  • "directory": "subfolder/2020-10"
}

Creates a directory anywhere in the file system.

+

+ It is only possible to delete files in pre-approved directories, or subdirectories thereof. + Defining approved directories is done in Butler's config file. +

+
+
+
+ Request Body schema: application/json +
+
+ + + + + + + +
+ deleteFile + +
+
+ string +
+
+
+

+ Name of file to be deleted. Use forward/backward slashes in paths as needed, + depending on whether Butler runs on Windows/non-Windows platform. +

+
+
+
+
+
+

Responses

+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+
+ + +
+
+

Request samples

+
+
    + +
+
+
+
+ Content type +
application/json
+
+
+
+
+ +
+
+
+ { +
    +
  • +
    + "deleteFile": + "data/qvdstore/sales/file1.qvd" +
    +
  • +
+ }
+
+
+
+
+
+
+
+
+
+

Response samples

+
+
    + + + + +
+
+
+
+ Content type +
application/json
+
+
+
+
+ +
+
+
+ { } +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ Creates a directory in designated QVD directory. +

+
+
+

Creates a directory in QVD directory (which is defined in Butler's config file).

+
+
+
+ Request Body schema: application/json +
+
+ + + + + + + +
+ directory + +
+
+ string +
+
+
+

Directory that should be created.

+
+
+
+
+
+

Responses

+
+ +
+
+ +
+
+ +
+
+
+
+
+ + +
+
+

Request samples

+
+
    + +
+
+
+
+ Content type +
application/json
+
+
+
+
+ +
+
+
+ { +
    +
  • +
    + "directory": + "subfolder/2020-10" +
    +
  • +
+ }
+
+
+
+
+
+
+
+
+
+

Response samples

+
+
    + + + +
+
+
+
+ Content type +
application/json
+
+
+
+
+ +
+
+
+ { +
    +
  • +
    + "directory": + "subfolder/2020-10" +
    +
  • +
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ Creates a directory anywhere in the file system. +

+
+

If the directory already exists nothing will happen. -If permissions don't allow a directory to be created, or if the path is invalid, an error will be returned.

-
Request Body schema: application/json
directory
string

Path to directory that should be created. Can be a relative or absolute path.

-

Responses

Request samples

Content type
application/json
{
  • "directory": "/Users/joe/data/qvds/2020"
}

Response samples

Content type
application/json
{
  • "directory": "/Users/joe/data/qvds/2020"
}

List all currently defined namespaces.

Responses

Response samples

Content type
application/json
[
  • "Weekly sales app",
  • "Sales ETL step 1",
  • "Sales ETL step 2"
]

Get the value associated with a key, in a specific namespace.

path Parameters
namespace
required
string
Example: Sales ETL step 2
query Parameters
key
required
string
Example: key=Last extract timestamp

Responses

Response samples

Content type
application/json
{
  • "namespace": "Sales ETL step 2",
  • "key": "Last extract timestamp",
  • "value": "2020-09-29 17:14:56"
}

Create a new key-value pair in the specified namespace.

+

+ If the directory already exists nothing will happen. If permissions don't allow a directory + to be created, or if the path is invalid, an error will be returned. +

+
+
+
+ Request Body schema: application/json +
+
+ + + + + + + +
+ directory + +
+
+ string +
+
+
+

+ Path to directory that should be created. Can be a relative or absolute + path. +

+
+
+
+
+
+

Responses

+
+ +
+
+ +
+
+ +
+
+
+
+
+ + +
+
+

Request samples

+
+
    + +
+
+
+
+ Content type +
application/json
+
+
+
+
+ +
+
+
+ { +
    +
  • +
    + "directory": + "/Users/joe/data/qvds/2020" +
    +
  • +
+ }
+
+
+
+
+
+
+
+
+
+

Response samples

+
+
    + + + +
+
+
+
+ Content type +
application/json
+
+
+
+
+ +
+
+
+ { +
    +
  • +
    + "directory": + "/Users/joe/data/qvds/2020" +
    +
  • +
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ List all currently defined namespaces. +

+
+

Responses

+
+ +
+
+ +
+
+
+
+
+ + +
+
+

Response samples

+
+
    + + +
+
+
+
+ Content type +
application/json
+
+
+
+
+ +
+
+
+ [ +
    +
  • +
    + "Weekly sales app", +
    +
  • +
  • +
    + "Sales ETL step 1", +
    +
  • +
  • +
    + "Sales ETL step 2" +
    +
  • +
+ ]
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ Get the value associated with a key, in a specific namespace. +

+
+
+ path + Parameters +
+ + + + + + + +
+ namespace +
required
+
+
+
+ string +
+
+ Example: + Sales ETL step 2 +
+
+
+
+
+
+
+ query + Parameters +
+ + + + + + + +
+ key +
required
+
+
+
+ string +
+
+ Example: + key=Last extract timestamp +
+
+
+
+
+
+

Responses

+
+ +
+
+ +
+
+ +
+
+
+
+
+ + +
+
+

Response samples

+
+
    + + + +
+
+
+
+ Content type +
application/json
+
+
+
+
+ +
+
+
+ { +
    +
  • +
    + "namespace": + "Sales ETL step 2", +
    +
  • +
  • +
    + "key": + "Last extract timestamp", +
    +
  • +
  • +
    + "value": + "2020-09-29 17:14:56" +
    +
  • +
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ Create a new key-value pair in the specified namespace. +

+
+

If the specified key already exists it will be overwritten.

-

If the posted data has a TTL, it will start counting when the post occur. -I.e. if a previouly posted key also had a TTL, it will be replace with the most recent TTL.

-
path Parameters
namespace
required
string
Example: Sales ETL step 2

Name of namespace.

-
Request Body schema: application/json
key
string

Key to use

-
value
string

Value to set

-
ttl
number

Time to live = how long (milliseconds) the key-value pair should exist before being automatically deleted

-

Responses

Request samples

Content type
application/json
{
  • "key": "ce68c8ca-b3ff-4371-8285-7c9ce5040e42_parameter_1",
  • "value": "12345.789",
  • "ttl": 10000
}

Response samples

Content type
application/json
{
  • "namespace": "Sales ETL step 2",
  • "key": "Last extract timestamp",
  • "value": "2020-09-29 17:14:56",
  • "ttl": 60000
}

Delete a namespace and all key-value pairs in it.

path Parameters
namespace
required
string
Example: Sales ETL step 2

Name of namespace.

-

Responses

Response samples

Content type
application/json
""

Checks if a key exists in a namespace.

Returns true if the specified key exists, otherwise false.

-
path Parameters
namespace
required
string
Example: Sales ETL step 2
query Parameters
key
required
string
Example: key=Last extract timestamp

Responses

Response samples

Content type
application/json
{
  • "keyExists": true,
  • "keyValue": {
    }
}

Delete a key-value pair in a specific namespace.

path Parameters
namespace
required
string
Example: Sales ETL step 2

Name of namespace.

-
key
required
string
Example: ce68c8ca-b3ff-4371-8285-7c9ce5040e42_parameter_1

Key to use

-

Responses

Response samples

Content type
application/json
""

Retrieve a list of all keys present in the specified namespace.

path Parameters
namespace
required
string
Example: Sales ETL step 2

Name of namespace whose keys should be returned.

-

Responses

Response samples

Content type
application/json
{
  • "namespace": "Sales ETL step 2",
  • "keys": [
    ]
}

Publish a message to a MQTT topic.

Request Body schema: application/json
required
topic
required
string

Topic to which message should be published.

-
message
required
string

The message is a generic text string and can thus contain anything that can be represented in a string, including JSON, key-value pairs, plain text etc.

-

Responses

Request samples

Content type
application/json
{
  • "topic": "qliksense/new_data_notification/sales",
  • "message": "dt=20201028"
}

Response samples

Content type
application/json
{
  • "topic": "qliksense/new_data_notification/sales",
  • "message": "dt=20201028"
}

Post events to New Relic.

This endpoint posts events to the New Relic event API.

-
Request Body schema: application/json
required
eventType
required
string <= 254 characters

Event type. Can be a combination of alphanumeric characters, _ underscores, and : colons.

-
timestamp
number

The event's start time in Unix time. Uses UTC time zone. This field also support seconds, microseconds, and nanoseconds. However, the data will be converted to milliseconds for storage and query. Events reported with a timestamp older than 48 hours ago or newer than 24 hours from the time they are reported are dropped by New Relic. If left empty Butler will use the current time as timestamp.

-
Array of objects

Dimensions/attributs that will be associated with the event.

-

Responses

Request samples

Content type
application/json
{
  • "eventType": "relead-failed",
  • "timestamp": 1642164296053,
  • "attributes": [
    ]
}

Response samples

Content type
application/json
{
  • "newRelicResultCode": "202",
  • "newRelicResultText": "Data accepted."
}

Post metrics to New Relic.

This endpoint posts metrics to the New Relic metrics API.

-
Request Body schema: application/json
required
name
required
string <= 254 characters

Metric name.

-
type
required
string
Value: "gauge"

Metric type.

-
value
required
number

Value of the metric.

-
timestamp
number

The metric's start time in Unix time. Uses UTC time zone. This field also support seconds, microseconds, and nanoseconds. However, the data will be converted to milliseconds for storage and query. Metrics reported with a timestamp older than 48 hours ago or newer than 24 hours from the time they are reported are dropped by New Relic. If left empty Butler will use the current time as timestamp.

-
interval
number

The length of the time window (millisec). Required for count and summary metric types.

-
Array of objects

Dimensions that will be associated with the metric.

-

Responses

Request samples

Content type
application/json
{
  • "name": "memory.heap",
  • "type": "gauge",
  • "value": 2.3,
  • "timestamp": 1642164296053,
  • "interval": 0,
  • "attributes": [
    ]
}

Response samples

Content type
application/json
{
  • "newRelicResultCode": "202",
  • "newRelicResultText": "Data accepted."
}

Get all information available for existing schedule(s).

+

If the specified key already exists it will be overwritten.

+

+ If the posted data has a TTL, it will start counting when the post occur. I.e. if a previouly + posted key also had a TTL, it will be replace with the most recent TTL. +

+
+
+
+
+ path + Parameters +
+ + + + + + + +
+ namespace +
required
+
+
+
+ string +
+
+ Example: + Sales ETL step 2 +
+
+
+

Name of namespace.

+
+
+
+
+
+
+ Request Body schema: application/json +
+
+ + + + + + + + + + + + + + + +
+ key + +
+
+ string +
+
+
+

Key to use

+
+
+
+
+ value + +
+
+ string +
+
+
+

Value to set

+
+
+
+
+ ttl + +
+
+ number +
+
+
+

+ Time to live = how long (milliseconds) the key-value pair should exist + before being automatically deleted +

+
+
+
+
+
+

Responses

+
+ +
+
+ +
+
+ +
+
+
+
+
+ + +
+
+

Request samples

+
+
    + +
+
+
+
+ Content type +
application/json
+
+
+
+
+ +
+
+
+ { +
    +
  • +
    + "key": + "ce68c8ca-b3ff-4371-8285-7c9ce5040e42_parameter_1", +
    +
  • +
  • +
    + "value": + "12345.789", +
    +
  • +
  • +
    + "ttl": + 10000 +
    +
  • +
+ }
+
+
+
+
+
+
+
+
+
+

Response samples

+
+
    + + + +
+
+
+
+ Content type +
application/json
+
+
+
+
+ +
+
+
+ { +
    +
  • +
    + "namespace": + "Sales ETL step 2", +
    +
  • +
  • +
    + "key": + "Last extract timestamp", +
    +
  • +
  • +
    + "value": + "2020-09-29 17:14:56", +
    +
  • +
  • +
    + "ttl": + 60000 +
    +
  • +
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ Delete a namespace and all key-value pairs in it. +

+
+
+ path + Parameters +
+ + + + + + + +
+ namespace +
required
+
+
+
+ string +
+
+ Example: + Sales ETL step 2 +
+
+
+

Name of namespace.

+
+
+
+
+
+
+

Responses

+
+ +
+
+ +
+
+ +
+
+
+
+
+ + +
+
+

Response samples

+
+
    + + + +
+
+
+
+ Content type +
application/json
+
+
+
+
+ +
+
+
+ "" +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ Checks if a key exists in a namespace. +

+
+
+

Returns true if the specified key exists, otherwise false.

+
+
+
+
+ path + Parameters +
+ + + + + + + +
+ namespace +
required
+
+
+
+ string +
+
+ Example: + Sales ETL step 2 +
+
+
+
+
+
+
+ query + Parameters +
+ + + + + + + +
+ key +
required
+
+
+
+ string +
+
+ Example: + key=Last extract timestamp +
+
+
+
+
+
+

Responses

+
+ +
+
+ +
+
+ +
+
+
+
+
+ + +
+
+

Response samples

+
+
    + + + +
+
+
+
+ Content type +
application/json
+
+
+
+
+ +
+
+
+ { +
    +
  • +
    + "keyExists": + true, +
    +
  • +
  • +
    + "keyValue": + { +
      +
    • + +
    • +
    • + +
    • +
    • + +
    • +
    + } +
    +
  • +
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ Delete a key-value pair in a specific namespace. +

+
+
+ path + Parameters +
+ + + + + + + + + + + +
+ namespace +
required
+
+
+
+ string +
+
+ Example: + Sales ETL step 2 +
+
+
+

Name of namespace.

+
+
+
+
+ key +
required
+
+
+
+ string +
+
+ Example: + ce68c8ca-b3ff-4371-8285-7c9ce5040e42_parameter_1 +
+
+
+

Key to use

+
+
+
+
+
+
+

Responses

+
+ +
+
+ +
+
+ +
+
+
+
+
+ + +
+
+

Response samples

+
+
    + + + +
+
+
+
+ Content type +
application/json
+
+
+
+
+ +
+
+
+ "" +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ Retrieve a list of all keys present in the specified namespace. +

+
+
+ path + Parameters +
+ + + + + + + +
+ namespace +
required
+
+
+
+ string +
+
+ Example: + Sales ETL step 2 +
+
+
+

Name of namespace whose keys should be returned.

+
+
+
+
+
+
+

Responses

+
+ +
+
+ +
+
+ +
+
+
+
+
+ + +
+
+

Response samples

+
+
    + + + +
+
+
+
+ Content type +
application/json
+
+
+
+
+ +
+
+
+ { +
    +
  • +
    + "namespace": + "Sales ETL step 2", +
    +
  • +
  • +
    + "keys": + [ +
      +
    • + +
    • +
    • + +
    • +
    + ] +
    +
  • +
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ Publish a message to a MQTT topic. +

+
+ Request Body schema: application/json +
required
+
+
+ + + + + + + + + + + +
+ topic +
required
+
+
+
+ string +
+
+
+

Topic to which message should be published.

+
+
+
+
+ message +
required
+
+
+
+ string +
+
+
+

+ The message is a generic text string and can thus contain anything that can + be represented in a string, including JSON, key-value pairs, plain text etc. +

+
+
+
+
+
+

Responses

+
+ +
+
+ +
+
+ +
+
+
+
+
+ + +
+
+

Request samples

+
+
    + +
+
+
+
+ Content type +
application/json
+
+
+
+
+ +
+
+
+ { +
    +
  • +
    + "topic": + "qliksense/new_data_notification/sales", +
    +
  • +
  • +
    + "message": + "dt=20201028" +
    +
  • +
+ }
+
+
+
+
+
+
+
+
+
+

Response samples

+
+
    + + + +
+
+
+
+ Content type +
application/json
+
+
+
+
+ +
+
+
+ { +
    +
  • +
    + "topic": + "qliksense/new_data_notification/sales", +
    +
  • +
  • +
    + "message": + "dt=20201028" +
    +
  • +
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ Post events to New Relic. +

+
+
+

This endpoint posts events to the New Relic event API.

+
+
+
+ Request Body schema: application/json +
required
+
+
+ + + + + + + + + + + + + + + +
+ eventType +
required
+
+
+
+ string + + <= 254 characters + +
+
+
+

+ Event type. Can be a combination of alphanumeric characters, _ underscores, + and : colons. +

+
+
+
+
+ timestamp + +
+
+ number +
+
+
+

+ The event's start time in Unix time. Uses UTC time zone. This field also + support seconds, microseconds, and nanoseconds. However, the data will be + converted to milliseconds for storage and query. Events reported with a + timestamp older than 48 hours ago or newer than 24 hours from the time they + are reported are dropped by New Relic. If left empty Butler will use the + current time as timestamp. +

+
+
+
+
+ + +
+
+ Array of objects +
+
+
+

Dimensions/attributs that will be associated with the event.

+
+
+
+
+
+

Responses

+
+ +
+
+ +
+
+ +
+
+
+
+
+ + +
+
+

Request samples

+
+
    + +
+
+
+
+ Content type +
application/json
+
+
+
+
+ +
+
+
+ { +
    +
  • +
    + "eventType": + "relead-failed", +
    +
  • +
  • +
    + "timestamp": + 1642164296053, +
    +
  • +
  • +
    + "attributes": + [ +
      +
    • + +
    • +
    + ] +
    +
  • +
+ }
+
+
+
+
+
+
+
+
+
+

Response samples

+
+
    + + + +
+
+
+
+ Content type +
application/json
+
+
+
+
+ +
+
+
+ { +
    +
  • +
    + "newRelicResultCode": "202", +
    +
  • +
  • +
    + "newRelicResultText": + "Data accepted." +
    +
  • +
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ Post metrics to New Relic. +

+
+
+

This endpoint posts metrics to the New Relic metrics API.

+
+
+
+ Request Body schema: application/json +
required
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ name +
required
+
+
+
+ string + + <= 254 characters + +
+
+
+

Metric name.

+
+
+
+
+ type +
required
+
+
+
+ string +
+
+ Value: + "gauge" +
+
+
+

Metric type.

+
+
+
+
+ value +
required
+
+
+
+ number +
+
+
+

Value of the metric.

+
+
+
+
+ timestamp + +
+
+ number +
+
+
+

+ The metric's start time in Unix time. Uses UTC time zone. This field + also support seconds, microseconds, and nanoseconds. However, the data will + be converted to milliseconds for storage and query. Metrics reported with a + timestamp older than 48 hours ago or newer than 24 hours from the time they + are reported are dropped by New Relic. If left empty Butler will use the + current time as timestamp. +

+
+
+
+
+ interval + +
+
+ number +
+
+
+

+ The length of the time window (millisec). Required for count and summary + metric types. +

+
+
+
+
+ + +
+
+ Array of objects +
+
+
+

Dimensions that will be associated with the metric.

+
+
+
+
+
+

Responses

+
+ +
+
+ +
+
+ +
+
+
+
+
+ + +
+
+

Request samples

+
+
    + +
+
+
+
+ Content type +
application/json
+
+
+
+
+ +
+
+
+ { +
    +
  • +
    + "name": + "memory.heap", +
    +
  • +
  • +
    + "type": + "gauge", +
    +
  • +
  • +
    + "value": + 2.3, +
    +
  • +
  • +
    + "timestamp": + 1642164296053, +
    +
  • +
  • +
    + "interval": + 0, +
    +
  • +
  • +
    + "attributes": + [ +
      +
    • + +
    • +
    + ] +
    +
  • +
+ }
+
+
+
+
+
+
+
+
+
+

Response samples

+
+
    + + + +
+
+
+
+ Content type +
application/json
+
+
+
+
+ +
+
+
+ { +
    +
  • +
    + "newRelicResultCode": "202", +
    +
  • +
  • +
    + "newRelicResultText": + "Data accepted." +
    +
  • +
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ Get all information available for existing schedule(s). +

+
+

If a schedule ID is specified using a query parameter (and there exists a schedule with that ID), information about that schedule will be returned. -If no schedule ID is specified, all schedules will be returned.

-
query Parameters
id
string
Example: id=e4b1c455-aa15-4a51-a9cf-c5e4cfc91339

Scheduld ID

-

Responses

Response samples

Content type
application/json
[
  • {
    }
]

Create a new schedule.

Request Body schema: application/json
required
+ + + + + + + + + + + + + + + + + + +
name
required
string

Descriptive name for the schedule.

-
cronSchedule
required
string
+

+ If a schedule ID is specified using a query parameter (and there exists a schedule with that + ID), information about that schedule will be returned. If no schedule ID is specified, all + schedules will be returned. +

+
+
+
+
+ query + Parameters +
+ + + + + + + +
+ id + +
+
+ string +
+
+ Example: + id=e4b1c455-aa15-4a51-a9cf-c5e4cfc91339 +
+
+
+

Scheduld ID

+
+
+
+
+
+
+

Responses

+
+ +
+
+ +
+
+ +
+
+
+
+
+ + +
+
+

Response samples

+
+
    + + + +
+
+
+
+ Content type +
application/json
+
+
+
+
+ +
+
+
+ [ +
    +
  • +
    + { +
      +
    • + +
    • +
    • + +
    • +
    • + +
    • +
    • + +
    • +
    • + +
    • +
    • + +
    • +
    • + +
    • +
    • + +
    • +
    • + +
    • +
    + } +
    +
  • +
+ ]
+
+
+
+
+
+
+
+
+
+
+
+ + +
+
+
+

+ Create a new schedule. +

+
+ Request Body schema: application/json +
required
+
+
+ + + + + + + + +
+ name +
required
+
+
+
+ string +
+
+
+

Descriptive name for the schedule.

+
+
+
+
+ cronSchedule +
required
+
+
+
+ string +
+
+

5 or 6 position cron schedule.

-

If 6 positions used, the leftmost position represent seconds. -If 5 positions used, leftmost position is minutes.

-

The example schedule will trigger at 00 and 30 minutes past 6:00 on Mon-Fri.

-
timezone
required
string

Time zone the schedule should use. Ex "Europe/Stockholm".

-
qlikSenseTaskId
required
string

ID of Qlik Sense task that should be started when schedule triggers.

-
startupState
required
string
Enum: "start" "started" "stop" "stopped"

If set to "start" or "started", the schedule will be started upon creation. Otherwise it will remain in stopped state.

-
tags
Array of strings

Can be used to categorise schedules.

-

Responses

Request samples

Content type
application/json
{
  • "name": "Reload sales metrics",
  • "cronSchedule": "0,30 6 * * 1-5",
  • "timezone": "Europe/Stockholm",
  • "qlikSenseTaskId": "210832b5-6174-4572-bd19-3e61eda675ef",
  • "startupState": "started",
  • "tags": [
    ]
}

Response samples

Content type
application/json
[
  • {
    }
]

Delete a schedule.

path Parameters
scheduleId
required
string
Example: e4b1c455-aa15-4a51-a9cf-c5e4cfc91339

Schedule ID.

-

Responses

Response samples

Content type
application/json
""

Start a schedule.

Start a schedule, i.e. have the scheduler run the associated reload task according to the schedule's cron settings.

-
path Parameters
scheduleId
required
string
Example: e4b1c455-aa15-4a51-a9cf-c5e4cfc91339

Schedule ID.

-

Responses

+
+
+ timezone +
required
+
+
+
+ string +
+
+
+

Time zone the schedule should use. Ex "Europe/Stockholm".

+
+
+
+
+ qlikSenseTaskId +
required
+
+
+
+ string +
+
+
+

ID of Qlik Sense task that should be started when schedule triggers.

+
+
+
+
+ startupState +
required
+
+
+
+ string +
+
+ Enum: + "start" + "started" + "stop" + "stopped" +
+
+
+

+ If set to "start" or "started", the schedule will be + started upon creation. Otherwise it will remain in stopped state. +

+
+
+
+
+ tags + +
+
+ Array of strings +
+
+
+

Can be used to categorise schedules.

+
+
+
+
+
+

Responses

+
+ +
+
+ +
+
+
+
+
+ + +
+
+

Request samples

+
+
    + +
+
+
+
+ Content type +
application/json
+
+
+
+
+ +
+
+
+ { +
    +
  • +
    + "name": + "Reload sales metrics", +
    +
  • +
  • +
    + "cronSchedule": + "0,30 6 * * 1-5", +
    +
  • +
  • +
    + "timezone": + "Europe/Stockholm", +
    +
  • +
  • +
    + "qlikSenseTaskId": + "210832b5-6174-4572-bd19-3e61eda675ef", +
    +
  • +
  • +
    + "startupState": + "started", +
    +
  • +
  • +
    + "tags": + [ +
      +
    • + +
    • +
    • + +
    • +
    + ] +
    +
  • +
+ }
+
+
+
+
+
+
+
+
+
+

Response samples

+
+
    + + +
+
+
+
+ Content type +
application/json
+
+
+
+
+ +
+
+
+ [ +
    +
  • +
    + { +
      +
    • + +
    • +
    • + +
    • +
    • + +
    • +
    • + +
    • +
    • + +
    • +
    • + +
    • +
    • + +
    • +
    • + +
    • +
    • + +
    • +
    + } +
    +
  • +
+ ]
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ Delete a schedule. +

+
+
+ path + Parameters +
+ + + + + + + +
+ scheduleId +
required
+
+
+
+ string +
+
+ Example: + e4b1c455-aa15-4a51-a9cf-c5e4cfc91339 +
+
+
+

Schedule ID.

+
+
+
+
+
+
+

Responses

+
+ +
+
+ +
+
+ +
+
+
+
+
+ + +
+
+

Response samples

+
+
    + + + +
+
+
+
+ Content type +
application/json
+
+
+
+
+ +
+
+
+ "" +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ Start a schedule. +

+
+
+

+ Start a schedule, i.e. have the scheduler run the associated reload task according to the + schedule's cron settings. +

+
+
+
+
+ path + Parameters +
+ + + + + + + +
+ scheduleId +
required
+
+
+
+ string +
+
+ Example: + e4b1c455-aa15-4a51-a9cf-c5e4cfc91339 +
+
+
+

Schedule ID.

+
+
+
+
+
+
+

Responses

+
+

Response samples

Content type
application/json
{
  • "id": "e4b1c455-aa15-4a51-a9cf-c5e4cfc91339",
  • "created": "2020-09-29T14:29:12.283Z",
  • "name": "Reload sales metrics",
  • "cronSchedule": "0,30 6 * * 1-5",
  • "timezone": "Europe/Stockholm",
  • "qlikSenseTaskId": "210832b5-6174-4572-bd19-3e61eda675ef",
  • "startupState": "started",
  • "tags": [
    ],
  • "lastKnownState": "started"
}

Start all schedules.

Start all schedules, i.e. tell the scheduler to run each schedule and start associated tasks according to each schedule's settings.

-

Responses

+
+
+ +
+
+ +
+
+
+
+
+ + +
+
+

Response samples

+
+
    + + + +
+
+
+
+ Content type +
application/json
+
+
+
+
+ +
+
+
+ { +
    +
  • +
    + "id": + "e4b1c455-aa15-4a51-a9cf-c5e4cfc91339", +
    +
  • +
  • +
    + "created": + "2020-09-29T14:29:12.283Z", +
    +
  • +
  • +
    + "name": + "Reload sales metrics", +
    +
  • +
  • +
    + "cronSchedule": + "0,30 6 * * 1-5", +
    +
  • +
  • +
    + "timezone": + "Europe/Stockholm", +
    +
  • +
  • +
    + "qlikSenseTaskId": + "210832b5-6174-4572-bd19-3e61eda675ef", +
    +
  • +
  • +
    + "startupState": + "started", +
    +
  • +
  • +
    + "tags": + [ +
      +
    • + +
    • +
    • + +
    • +
    + ], +
    +
  • +
  • +
    + "lastKnownState": + "started" +
    +
  • +
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ Start all schedules. +

+
+
+

+ Start all schedules, i.e. tell the scheduler to run each schedule and start associated tasks + according to each schedule's settings. +

+
+
+
+

Responses

+
+

Response samples

Content type
application/json
[
  • {
    }
]

Stop a schedule.

Stop a schedule, i.e. tell the scheduler to no longer execute the schedule according to its cron settings.

-
path Parameters
scheduleId
required
string
Example: e4b1c455-aa15-4a51-a9cf-c5e4cfc91339

Schedule ID.

-

Responses

+
+
+ +
+
+
+
+
+ + +
+
+

Response samples

+
+
    + + +
+
+
+
+ Content type +
application/json
+
+
+
+
+ +
+
+
+ [ +
    +
  • +
    + { +
      +
    • + +
    • +
    • + +
    • +
    • + +
    • +
    • + +
    • +
    • + +
    • +
    • + +
    • +
    • + +
    • +
    • + +
    • +
    + } +
    +
  • +
+ ]
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ Stop a schedule. +

+
+
+

+ Stop a schedule, i.e. tell the scheduler to no longer execute the schedule according to its cron + settings. +

+
+
+
+
+ path + Parameters +
+ + + + + + + +
+ scheduleId +
required
+
+
+
+ string +
+
+ Example: + e4b1c455-aa15-4a51-a9cf-c5e4cfc91339 +
+
+
+

Schedule ID.

+
+
+
+
+
+
+

Responses

+
+

Response samples

Content type
application/json
{
  • "id": "e4b1c455-aa15-4a51-a9cf-c5e4cfc91339",
  • "created": "2020-09-29T14:29:12.283Z",
  • "name": "Reload sales metrics",
  • "cronSchedule": "0,30 6 * * 1-5",
  • "timezone": "Europe/Stockholm",
  • "qlikSenseTaskId": "210832b5-6174-4572-bd19-3e61eda675ef",
  • "startupState": "started",
  • "tags": [
    ],
  • "lastKnownState": "started"
}

Stop all schedules.

Stop all schedules, i.e. tell the scheduler to no longer execute any schedule according to its cron settings.

-

Responses

+
+
+ +
+
+ +
+
+
+
+
+ + +
+
+

Response samples

+
+
    + + + +
+
+
+
+ Content type +
application/json
+
+
+
+
+ +
+
+
+ { +
    +
  • +
    + "id": + "e4b1c455-aa15-4a51-a9cf-c5e4cfc91339", +
    +
  • +
  • +
    + "created": + "2020-09-29T14:29:12.283Z", +
    +
  • +
  • +
    + "name": + "Reload sales metrics", +
    +
  • +
  • +
    + "cronSchedule": + "0,30 6 * * 1-5", +
    +
  • +
  • +
    + "timezone": + "Europe/Stockholm", +
    +
  • +
  • +
    + "qlikSenseTaskId": + "210832b5-6174-4572-bd19-3e61eda675ef", +
    +
  • +
  • +
    + "startupState": + "started", +
    +
  • +
  • +
    + "tags": + [ +
      +
    • + +
    • +
    • + +
    • +
    + ], +
    +
  • +
  • +
    + "lastKnownState": + "started" +
    +
  • +
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ Stop all schedules. +

+
+
+

+ Stop all schedules, i.e. tell the scheduler to no longer execute any schedule according to its + cron settings. +

+
+
+
+

Responses

+
+

Response samples

Content type
application/json
[
  • {
    }
]

Get scheduler status.

+

Schedules successfully stopped.

+

An array with all information about the stopped schedules is returned.

+
+ +
+
+ +
+
+
+
+
+ + +
+
+

Response samples

+
+
    + + +
+
+
+
+ Content type +
application/json
+
+
+
+
+ +
+
+
+ [ +
    +
  • +
    + { +
      +
    • + +
    • +
    • + +
    • +
    • + +
    • +
    • + +
    • +
    • + +
    • +
    • + +
    • +
    • + +
    • +
    • + +
    • +
    + } +
    +
  • +
+ ]
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ Get scheduler status. +

+
+

Get basic status from the core scheduler.

-

No schedule metadata beyond ID, cron setting and job state will be returned, but as this comes from the core scheduler it is the authorative truth about what jobs are running (and which ones are not).

-

Responses

Response samples

Content type
text/plain
{
+"
+                                        class="sc-eeDSqt sc-eBMFzZ bSgSrX cWARBq"
+                                    >
+                                        

Get basic status from the core scheduler.

+

+ No schedule metadata beyond ID, cron setting and job state will be returned, but as this comes + from the core scheduler it is the authorative truth about what jobs are running (and which ones + are not). +

+
+
+
+

Responses

+
+ +
+
+ +
+
+
+
+
+ + +
+
+

Response samples

+
+
    + +
+
+
+
+ Content type +
text/plain
+
+
+
+
+ +
+
{
 '3702cec1-b6c8-463e-bda3-58d6a94dd9ac': * */2 * * * status: Running 
 '2d5dcebc-2440-4bd7-9aa1-fb69708715c8': */45 * * * * * status: Running 
 'a93ca0f3-7980-439b-9eda-723a167352e3': */10 * * * * * status: Running 
 'ad250f49-ffd8-45dc-9b2b-f76028e969a4': */5 * * * * * status: Running 
-}

Do a stand-alone reload of a Qlik Sense app, without using a task.

path Parameters
appId
required
string
Example: 210832b5-6174-4572-bd19-3e61eda675ef

ID of Qlik Sense app.

-
Request Body schema: application/json
reloadMode
integer

Reload mode that will be used. 0, 1 or 2. If not specified 0 will be used

-
partialReload
boolean

Should a full (=false) or partial (=true) reload be done? If not specified a full reload will be done.

-
startQSEoWTaskOnSuccess
Array of strings

Array of task IDs that should be started when the app has successfully reloaded.

-
startQSEoWTaskOnFailure
Array of strings

Array of task IDs that should be started if the app fails reloading.

-

Responses

Request samples

Content type
application/json
{
  • "reloadMode": 0,
  • "partialReload": true,
  • "startQSEoWTaskOnSuccess": [
    ],
  • "startQSEoWTaskOnFailure": [
    ]
}

Response samples

Content type
application/json
{
  • "appId": "210832b5-6174-4572-bd19-3e61eda675ef"
}

Dump a Sense app to JSON.

Does the same thing as /v4/app/:appId/dump

-
path Parameters
appId
required
string
Example: 210832b5-6174-4572-bd19-3e61eda675ef

ID of Qlik Sense app.

-

Responses

Response samples

Content type
application/json
{
  • "properties": { },
  • "loadScript": "",
  • "sheets": [ ],
  • "stories": [ ],
  • "masterobjects": [ ],
  • "appprops": [ ],
  • "dataconnections": [ ],
  • "dimensions": [ ],
  • "bookmarks": [ ],
  • "embeddedmedia": [ ],
  • "snapshots": [ ],
  • "fields": [ ],
  • "variables": [ ],
  • "measures": [ ]
}

Dump a Sense app to JSON.

Does the same thing as /v4/senseappdump/:appId

-
path Parameters
appId
required
string
Example: 210832b5-6174-4572-bd19-3e61eda675ef

ID of Qlik Sense app.

-

Responses

Response samples

Content type
application/json
{
  • "properties": { },
  • "loadScript": "",
  • "sheets": [ ],
  • "stories": [ ],
  • "masterobjects": [ ],
  • "appprops": [ ],
  • "dataconnections": [ ],
  • "dimensions": [ ],
  • "bookmarks": [ ],
  • "embeddedmedia": [ ],
  • "snapshots": [ ],
  • "fields": [ ],
  • "variables": [ ],
  • "measures": [ ]
}

Get a list of all apps in Sense environment.

Does the same thing as /v4/apps/list

-

Responses

Response samples

Content type
application/json
[
  • {
    }
]

Get a list of all apps in Sense environment.

Does the same thing as /v4/senselistapps

-

Responses

Response samples

Content type
application/json
[
  • {
    }
]

Start a Qlik Sense task.

} +
+
+
+
+
+
+
+
+
+
+
+
+

+ Do a stand-alone reload of a Qlik Sense app, without using a task. +

+
+
+ path + Parameters +
+ + + + + + + +
+ appId +
required
+
+
+
+ string +
+
+ Example: + 210832b5-6174-4572-bd19-3e61eda675ef +
+
+
+

ID of Qlik Sense app.

+
+
+
+
+
+
+ Request Body schema: application/json +
+
+ + + + + + + + + + + + + + + + + + + +
+ reloadMode + +
+
+ integer +
+
+
+

Reload mode that will be used. 0, 1 or 2. If not specified 0 will be used

+
+
+
+
+ partialReload + +
+
+ boolean +
+
+
+

+ Should a full (=false) or partial (=true) reload be done? If not specified a + full reload will be done. +

+
+
+
+
+ startQSEoWTaskOnSuccess + +
+
+ Array of strings +
+
+
+

+ Array of task IDs that should be started when the app has successfully + reloaded. +

+
+
+
+
+ startQSEoWTaskOnFailure + +
+
+ Array of strings +
+
+
+

Array of task IDs that should be started if the app fails reloading.

+
+
+
+
+
+

Responses

+
+ +
+
+ +
+
+ +
+
+
+
+
+ + +
+
+

Request samples

+
+
    + +
+
+
+
+ Content type +
application/json
+
+
+
+
+ +
+
+
+ { +
    +
  • +
    + "reloadMode": + 0, +
    +
  • +
  • +
    + "partialReload": + true, +
    +
  • +
  • +
    + "startQSEoWTaskOnSuccess": [ +
      +
    • + +
    • +
    • + +
    • +
    + ], +
    +
  • +
  • +
    + "startQSEoWTaskOnFailure": [ +
      +
    • + +
    • +
    • + +
    • +
    + ] +
    +
  • +
+ }
+
+
+
+
+
+
+
+
+
+

Response samples

+
+
    + + + +
+
+
+
+ Content type +
application/json
+
+
+
+
+ +
+
+
+ { +
    +
  • +
    + "appId": + "210832b5-6174-4572-bd19-3e61eda675ef" +
    +
  • +
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ Dump a Sense app to JSON. +

+
+
+

Does the same thing as /v4/app/:appId/dump

+
+
+
+
+ path + Parameters +
+ + + + + + + +
+ appId +
required
+
+
+
+ string +
+
+ Example: + 210832b5-6174-4572-bd19-3e61eda675ef +
+
+
+

ID of Qlik Sense app.

+
+
+
+
+
+
+

Responses

+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+
+ + +
+
+

Response samples

+
+
    + + + + +
+
+
+
+ Content type +
application/json
+
+
+
+
+ +
+
+
+ { +
    +
  • +
    + "properties": + { }, +
    +
  • +
  • +
    + "loadScript": + "", +
    +
  • +
  • +
    + "sheets": + [ ], +
    +
  • +
  • +
    + "stories": + [ ], +
    +
  • +
  • +
    + "masterobjects": + [ ], +
    +
  • +
  • +
    + "appprops": + [ ], +
    +
  • +
  • +
    + "dataconnections": [ ], +
    +
  • +
  • +
    + "dimensions": + [ ], +
    +
  • +
  • +
    + "bookmarks": + [ ], +
    +
  • +
  • +
    + "embeddedmedia": + [ ], +
    +
  • +
  • +
    + "snapshots": + [ ], +
    +
  • +
  • +
    + "fields": + [ ], +
    +
  • +
  • +
    + "variables": + [ ], +
    +
  • +
  • +
    + "measures": + [ ] +
    +
  • +
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ Dump a Sense app to JSON. +

+
+
+

Does the same thing as /v4/senseappdump/:appId

+
+
+
+
+ path + Parameters +
+ + + + + + + +
+ appId +
required
+
+
+
+ string +
+
+ Example: + 210832b5-6174-4572-bd19-3e61eda675ef +
+
+
+

ID of Qlik Sense app.

+
+
+
+
+
+
+

Responses

+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+
+ + +
+
+

Response samples

+
+
    + + + + +
+
+
+
+ Content type +
application/json
+
+
+
+
+ +
+
+
+ { +
    +
  • +
    + "properties": + { }, +
    +
  • +
  • +
    + "loadScript": + "", +
    +
  • +
  • +
    + "sheets": + [ ], +
    +
  • +
  • +
    + "stories": + [ ], +
    +
  • +
  • +
    + "masterobjects": + [ ], +
    +
  • +
  • +
    + "appprops": + [ ], +
    +
  • +
  • +
    + "dataconnections": [ ], +
    +
  • +
  • +
    + "dimensions": + [ ], +
    +
  • +
  • +
    + "bookmarks": + [ ], +
    +
  • +
  • +
    + "embeddedmedia": + [ ], +
    +
  • +
  • +
    + "snapshots": + [ ], +
    +
  • +
  • +
    + "fields": + [ ], +
    +
  • +
  • +
    + "variables": + [ ], +
    +
  • +
  • +
    + "measures": + [ ] +
    +
  • +
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ Get a list of all apps in Sense environment. +

+
+
+

Does the same thing as /v4/apps/list

+
+
+
+

Responses

+
+ +
+
+ +
+
+
+
+
+ + +
+
+

Response samples

+
+
    + + +
+
+
+
+ Content type +
application/json
+
+
+
+
+ +
+
+
+ [ +
    +
  • +
    + { +
      +
    • + +
    • +
    • + +
    • +
    + } +
    +
  • +
+ ]
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ Get a list of all apps in Sense environment. +

+
+
+

Does the same thing as /v4/senselistapps

+
+
+
+

Responses

+
+ +
+
+ +
+
+
+
+
+ + +
+
+

Response samples

+
+
    + + +
+
+
+
+ Content type +
application/json
+
+
+
+
+ +
+
+
+ [ +
    +
  • +
    + { +
      +
    • + +
    • +
    • + +
    • +
    + } +
    +
  • +
+ ]
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ Start a Qlik Sense task. +

+
+

An optional array of zero or more objects can be passed in the message body. It is used to pass additional info related to the reload task being started.

-

Currently supported values in this array are:

-
    -
  • A key-value pair that will be stored in Butler's KV store. If Butler's key-value store is not enabled, any key-value information passed in this parameter will simply be ignored. -Setting ttl=0 disables the TTL feature, i.e. the KV pair will not expire.
  • -
  • A task identified by its taskId that should be started.
  • -
  • Tasks identified by tags set on tasks in the QMC.
  • -
  • Tasks identified by custom properties set in the QMC.
  • -
-

This parameter uses a generic JSON/object format (type + payload). -It's thus possible to add new integrations in future Butler versions.

-
path Parameters
+ + +
taskId
required
string
Example: 210832b5-6174-4572-bd19-3e61eda675ef
+

+ An optional array of zero or more objects can be passed in the message body. It is used to pass + additional info related to the reload task being started. +

+

Currently supported values in this array are:

+
    +
  • + A key-value pair that will be stored in Butler's KV store. If Butler's key-value + store is not enabled, any key-value information passed in this parameter will simply be + ignored. Setting ttl=0 disables the TTL feature, i.e. the KV pair will not + expire. +
  • +
  • A task identified by its taskId that should be started.
  • +
  • Tasks identified by tags set on tasks in the QMC.
  • +
  • Tasks identified by custom properties set in the QMC.
  • +
+

+ This parameter uses a generic JSON/object format (type + payload). It's thus possible to add + new integrations in future Butler versions. +

+
+
+
+
+ path + Parameters +
+ + + + +
+ taskId +
required
+
+
+
+ string +
+
+ Example: + 210832b5-6174-4572-bd19-3e61eda675ef +
+
+

ID of Qlik Sense task. -Butler will ignore the "magic" task ID of "-" (=dash, hyphen). This ID will not be reported as invalid.

-
query Parameters
+ + +
allTaskIdsMustExist
boolean
Example: allTaskIdsMustExist=true
+

+ ID of Qlik Sense task. Butler will ignore the "magic" task ID + of "-" (=dash, hyphen). This ID will not be reported as + invalid. +

+
+
+
+
+
+
+
+ query + Parameters +
+ + + + +
+ allTaskIdsMustExist + +
+
+ boolean +
+
+ Example: + allTaskIdsMustExist=true +
+
+

If set to true, all specified taskIds must exist. If one or more taskIds does not exist in the Sense server, no tasks will be started.

-

If set to false, all existing taskIds will be started. Missing/invalid taskIds will be ignored.

-

In either case, missing/invalid taskIds will be reported in the result set back to the client calling the API.

-

Note: Tasks started by specifying tags and/or custom properties are not affected by this.

-
Request Body schema: application/json

Optional object with extra info.

-
Array
type
string
Enum: "keyvaluestore" "starttaskid" "starttasktag" "starttaskcustomproperty"
payload
object

Responses

Request samples

Content type
application/json
[
  • {
    },
  • {
    },
  • {
    },
  • {
    }
]

Response samples

Content type
application/json
{
  • "tasksId": {
    },
  • "tasksTag": [
    ],
  • "tasksTagDenied": [
    ],
  • "tasksCP": [
    ],
  • "tasksCPDenied": [
    ]
}

Start a Qlik Sense task.

+

+ If set to true, all specified taskIds must exist. If one or + more taskIds does not exist in the Sense server, no tasks will + be started. +

+

+ If set to false, all existing taskIds will be started. + Missing/invalid taskIds will be ignored. +

+

+ In either case, missing/invalid taskIds will be reported in the result + set back to the client calling the API. +

+

+ Note: Tasks started by specifying tags and/or custom properties are not + affected by this. +

+
+
+
+
+
+
+ Request Body schema: application/json +
+
+

Optional object with extra info.

+
+
+
Array
+
+ + + + + + + + + + + +
+ type + +
+
+ string +
+
+ Enum: + "keyvaluestore" + "starttaskid" + "starttasktag" + "starttaskcustomproperty" +
+
+
+
+ payload + +
+
+ object +
+
+
+
+
+
+
+
+

Responses

+
+ +
+
+ +
+
+ +
+
+
+
+
+ + +
+
+

Request samples

+
+
    + +
+
+
+
+ Content type +
application/json
+
+
+
+
+ +
+
+
+ [ +
    +
  • +
    + { +
      +
    • + +
    • +
    • + +
    • +
    + }, +
    +
  • +
  • +
    + { +
      +
    • + +
    • +
    • + +
    • +
    + }, +
    +
  • +
  • +
    + { +
      +
    • + +
    • +
    • + +
    • +
    + }, +
    +
  • +
  • +
    + { +
      +
    • + +
    • +
    • + +
    • +
    + } +
    +
  • +
+ ]
+
+
+
+
+
+
+
+
+
+

Response samples

+
+
    + + + +
+
+
+
+ Content type +
application/json
+
+
+
+
+ +
+
+
+ { +
    +
  • +
    + "tasksId": + { +
      +
    • + +
    • +
    • + +
    • +
    • + +
    • +
    + }, +
    +
  • +
  • +
    + "tasksTag": + [ +
      +
    • + +
    • +
    + ], +
    +
  • +
  • +
    + "tasksTagDenied": + [ +
      +
    • + +
    • +
    + ], +
    +
  • +
  • +
    + "tasksCP": + [ +
      +
    • + +
    • +
    + ], +
    +
  • +
  • +
    + "tasksCPDenied": + [ +
      +
    • + +
    • +
    + ] +
    +
  • +
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ Start a Qlik Sense task. +

+
+

An optional array of zero or more objects can be passed in the message body. It is used to pass additional info related to the reload task being started.

-

Currently supported values in this array are:

-
    -
  • A key-value pair that will be stored in Butler's KV store. If Butler's key-value store is not enabled, any key-value information passed in this parameter will simply be ignored. -Setting ttl=0 disables the TTL feature, i.e. the KV pair will not expire.
  • -
  • A task identified by its taskId that should be started.
  • -
  • Tasks identified by tags set on tasks in the QMC.
  • -
  • Tasks identified by custom properties set in the QMC.
  • -
-

This parameter uses a generic JSON/object format (type + payload). -It's thus possible to add new integrations in future Butler versions.

-
path Parameters
+ + +
taskId
required
string
Example: 210832b5-6174-4572-bd19-3e61eda675ef
+

+ An optional array of zero or more objects can be passed in the message body. It is used to pass + additional info related to the reload task being started. +

+

Currently supported values in this array are:

+
    +
  • + A key-value pair that will be stored in Butler's KV store. If Butler's key-value + store is not enabled, any key-value information passed in this parameter will simply be + ignored. Setting ttl=0 disables the TTL feature, i.e. the KV pair will not + expire. +
  • +
  • A task identified by its taskId that should be started.
  • +
  • Tasks identified by tags set on tasks in the QMC.
  • +
  • Tasks identified by custom properties set in the QMC.
  • +
+

+ This parameter uses a generic JSON/object format (type + payload). It's thus possible to add + new integrations in future Butler versions. +

+
+
+
+
+ path + Parameters +
+ + + + +
+ taskId +
required
+
+
+
+ string +
+
+ Example: + 210832b5-6174-4572-bd19-3e61eda675ef +
+
+

ID of Qlik Sense task. -Butler will ignore the "magic" task ID of "-" (=dash, hyphen). This ID will not be reported as invalid.

-
query Parameters
+ + +
allTaskIdsMustExist
boolean
Example: allTaskIdsMustExist=true
+

+ ID of Qlik Sense task. Butler will ignore the "magic" task ID + of "-" (=dash, hyphen). This ID will not be reported as + invalid. +

+
+
+
+
+
+
+
+ query + Parameters +
+ + + + +
+ allTaskIdsMustExist + +
+
+ boolean +
+
+ Example: + allTaskIdsMustExist=true +
+
+

If set to true, all specified taskIds must exist. If one or more taskIds does not exist in the Sense server, no tasks will be started.

-

If set to false, all existing taskIds will be started. Missing/invalid taskIds will be ignored.

-

In either case, missing/invalid taskIds will be reported in the result set back to the client calling the API.

-

Note: Tasks started by specifying tags and/or custom properties are not affected by this.

-
Request Body schema: application/json

Optional object with extra info.

-
Array
type
string
Enum: "keyvaluestore" "starttaskid" "starttasktag" "starttaskcustomproperty"
payload
object

Responses

Request samples

Content type
application/json
[
  • {
    },
  • {
    },
  • {
    },
  • {
    }
]

Response samples

Content type
application/json
{
  • "tasksId": {
    },
  • "tasksTag": [
    ],
  • "tasksTagDenied": [
    ],
  • "tasksCP": [
    ],
  • "tasksCPDenied": [
    ]
}

Send message to Slack.

Sends a basic message to Slack.

-
Request Body schema: application/json
required
channel
required
string

Slack channel to post message into. Prefix channel name with #.

-
from_user
required
string

Name of sending user, as shown in the Slack message

-
msg
required
string

Text going into the Slack message

-
emoji
string

Emoji to shown next to Slack message

-

Responses

Request samples

Content type
application/json
{
  • "channel": "#reload-notification",
  • "from_user": "Butler the Bot",
  • "msg": "This is a message from Qlik Sense",
  • "emoji": "thumbsup"
}

Response samples

Content type
application/json
{
  • "channel": "#reload-notification",
  • "from_user": "Butler the Bot",
  • "msg": "This is a message from Qlik Sense",
  • "emoji": "thumbsup"
}
- - +" + class="sc-eeDSqt sc-eBMFzZ bSgSrX gayXgA" + > +

+ If set to true, all specified taskIds must exist. If one or + more taskIds does not exist in the Sense server, no tasks will + be started. +

+

+ If set to false, all existing taskIds will be started. + Missing/invalid taskIds will be ignored. +

+

+ In either case, missing/invalid taskIds will be reported in the result + set back to the client calling the API. +

+

+ Note: Tasks started by specifying tags and/or custom properties are not + affected by this. +

+ + + +
+
+
+ Request Body schema: application/json +
+
+

Optional object with extra info.

+
+
+
Array
+
+ + + + + + + + + + + +
+ type + +
+
+ string +
+
+ Enum: + "keyvaluestore" + "starttaskid" + "starttasktag" + "starttaskcustomproperty" +
+
+
+
+ payload + +
+
+ object +
+
+
+
+
+
+
+
+

Responses

+
+ +
+
+ +
+
+ +
+
+
+
+
+ + +
+
+

Request samples

+
+
    + +
+
+
+
+ Content type +
application/json
+
+
+
+
+ +
+
+
+ [ +
    +
  • +
    + { +
      +
    • + +
    • +
    • + +
    • +
    + }, +
    +
  • +
  • +
    + { +
      +
    • + +
    • +
    • + +
    • +
    + }, +
    +
  • +
  • +
    + { +
      +
    • + +
    • +
    • + +
    • +
    + }, +
    +
  • +
  • +
    + { +
      +
    • + +
    • +
    • + +
    • +
    + } +
    +
  • +
+ ]
+
+
+
+
+
+
+
+
+
+

Response samples

+
+
    + + + +
+
+
+
+ Content type +
application/json
+
+
+
+
+ +
+
+
+ { +
    +
  • +
    + "tasksId": + { +
      +
    • + +
    • +
    • + +
    • +
    • + +
    • +
    + }, +
    +
  • +
  • +
    + "tasksTag": + [ +
      +
    • + +
    • +
    + ], +
    +
  • +
  • +
    + "tasksTagDenied": + [ +
      +
    • + +
    • +
    + ], +
    +
  • +
  • +
    + "tasksCP": + [ +
      +
    • + +
    • +
    + ], +
    +
  • +
  • +
    + "tasksCPDenied": + [ +
      +
    • + +
    • +
    + ] +
    +
  • +
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ Send message to Slack. +

+
+
+

Sends a basic message to Slack.

+
+
+
+ Request Body schema: application/json +
required
+
+
+ + + + + + + + + + + + + + + + + + + +
+ channel +
required
+
+
+
+ string +
+
+
+

Slack channel to post message into. Prefix channel name with #.

+
+
+
+
+ from_user +
required
+
+
+
+ string +
+
+
+

Name of sending user, as shown in the Slack message

+
+
+
+
+ msg +
required
+
+
+
+ string +
+
+
+

Text going into the Slack message

+
+
+
+
+ emoji + +
+
+ string +
+
+
+

Emoji to shown next to Slack message

+
+
+
+
+
+

Responses

+
+ +
+
+ +
+
+ +
+
+
+
+
+ + +
+
+

Request samples

+
+
    + +
+
+
+
+ Content type +
application/json
+
+
+
+
+ +
+
+
+ { +
    +
  • +
    + "channel": + "#reload-notification", +
    +
  • +
  • +
    + "from_user": + "Butler the Bot", +
    +
  • +
  • +
    + "msg": + "This is a message from Qlik Sense", +
    +
  • +
  • +
    + "emoji": + "thumbsup" +
    +
  • +
+ }
+
+
+
+
+
+
+
+
+
+

Response samples

+
+
    + + + +
+
+
+
+ Content type +
application/json
+
+
+
+
+ +
+
+
+ { +
    +
  • +
    + "channel": + "#reload-notification", +
    +
  • +
  • +
    + "from_user": + "Butler the Bot", +
    +
  • +
  • +
    + "msg": + "This is a message from Qlik Sense", +
    +
  • +
  • +
    + "emoji": + "thumbsup" +
    +
  • +
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + diff --git a/package-lock.json b/package-lock.json index 56421dca..3b4acdf7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,8 +14,9 @@ "@fastify/rate-limit": "^9.1.0", "@fastify/reply-from": "^9.8.0", "@fastify/sensible": "^5.6.0", + "@fastify/static": "^7.0.4", "@fastify/swagger": "^8.14.0", - "@fastify/swagger-ui": "^3.0.0", + "@fastify/swagger-ui": "^4.0.1", "@keyvhq/core": "^2.1.1", "@xstate/fsm": "^2.0.1", "any-base": "^1.1.0", @@ -29,7 +30,7 @@ "eslint": "^8.5.7", "eslint-config-airbnb-base": "^15.0.0", "eslint-config-prettier": "^9.1.0", - "eslint-plugin-prettier": "^5.1.3", + "eslint-plugin-prettier": "^5.2.1", "express-handlebars": "^7.1.3", "fastify": "^4.28.1", "fastify-healthcheck": "^4.4.0", @@ -40,6 +41,7 @@ "i": "^0.3.7", "influx": "^5.9.3", "is-unc-path": "^1.0.0", + "jjsontree.js": "^2.1.0", "js-yaml": "^4.1.0", "jshint": "^2.13.6", "lodash": "^4.17.21", @@ -47,7 +49,7 @@ "mkdirp": "^3.0.1", "moment": "^2.30.1", "moment-precise-range-plugin": "^1.3.0", - "mqtt": "^5.7.3", + "mqtt": "^5.8.1", "ms-teams-wrapper": "^1.0.2", "nodemailer": "^6.9.14", "nodemailer-express-handlebars": "^6.1.2", @@ -60,17 +62,17 @@ "systeminformation": "^5.22.11", "upath": "^2.0.1", "uuid": "^10.0.0", - "winston": "^3.13.0", + "winston": "^3.13.1", "winston-daily-rotate-file": "^5.0.0", - "ws": "^8.17.1", - "xstate": "^5.14.0" + "ws": "^8.18.0", + "xstate": "^5.15.0" }, "devDependencies": { - "@babel/eslint-parser": "^7.24.7", + "@babel/eslint-parser": "^7.24.8", "@babel/plugin-syntax-import-assertions": "^7.24.7", "eslint-plugin-import": "^2.29.1", "jest": "^29.7.0", - "prettier": "^3.3.2", + "prettier": "^3.3.3", "snyk": "^1.1292.1" } }, @@ -219,9 +221,9 @@ } }, "node_modules/@babel/eslint-parser": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.24.7.tgz", - "integrity": "sha512-SO5E3bVxDuxyNxM5agFv480YA2HO6ohZbGxbazZdIk3KQOPOGVNw6q78I9/lbviIf95eq6tPozeYnJLbjnC8IA==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.24.8.tgz", + "integrity": "sha512-nYAikI4XTGokU2QX7Jx+v4rxZKhKivaQaREZjuW3mrJrbdWJ5yUfohnoUULge+zEEaKjPYNxhoRgUKktjXtbwA==", "dev": true, "license": "MIT", "dependencies": { @@ -791,9 +793,10 @@ } }, "node_modules/@colors/colors": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", - "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", + "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", + "license": "MIT", "engines": { "node": ">=0.1.90" } @@ -1364,34 +1367,11 @@ "vary": "^1.1.2" } }, - "node_modules/@fastify/swagger": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/@fastify/swagger/-/swagger-8.14.0.tgz", - "integrity": "sha512-sGiznEb3rl6pKGGUZ+JmfI7ct5cwbTQGo+IjewaTvtzfrshnryu4dZwEsjw0YHABpBA+kCz3kpRaHB7qpa67jg==", - "dependencies": { - "fastify-plugin": "^4.0.0", - "json-schema-resolver": "^2.0.0", - "openapi-types": "^12.0.0", - "rfdc": "^1.3.0", - "yaml": "^2.2.2" - } - }, - "node_modules/@fastify/swagger-ui": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@fastify/swagger-ui/-/swagger-ui-3.0.0.tgz", - "integrity": "sha512-8P5OwHVv6QR4XSE6cW4fsENeMbW4yWWWj6Dz/5tvQN2pwNyTiSWxYpsY3+VP+uiZucNaDrAE2xm11rqytqAocA==", - "dependencies": { - "@fastify/static": "^7.0.0", - "fastify-plugin": "^4.0.0", - "openapi-types": "^12.0.2", - "rfdc": "^1.3.0", - "yaml": "^2.2.2" - } - }, - "node_modules/@fastify/swagger-ui/node_modules/@fastify/static": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@fastify/static/-/static-7.0.1.tgz", - "integrity": "sha512-i1p/nELMknAisNfnjo7yhfoUOdKzA+n92QaMirv2NkZrJ1Wl12v2nyTYlDwPN8XoStMBAnRK/Kx6zKmfrXUPXw==", + "node_modules/@fastify/static": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/@fastify/static/-/static-7.0.4.tgz", + "integrity": "sha512-p2uKtaf8BMOZWLs6wu+Ihg7bWNBdjNgCwDza4MJtTqg+5ovKmcbgbR9Xs5/smZ1YISfzKOCNYmZV8LaCj+eJ1Q==", + "license": "MIT", "dependencies": { "@fastify/accept-negotiator": "^1.0.0", "@fastify/send": "^2.0.0", @@ -1401,39 +1381,40 @@ "glob": "^10.3.4" } }, - "node_modules/@fastify/swagger-ui/node_modules/brace-expansion": { + "node_modules/@fastify/static/node_modules/brace-expansion": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } }, - "node_modules/@fastify/swagger-ui/node_modules/glob": { - "version": "10.3.10", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", - "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", + "node_modules/@fastify/static/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", - "jackspeak": "^2.3.5", - "minimatch": "^9.0.1", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", - "path-scurry": "^1.10.1" + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@fastify/swagger-ui/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "node_modules/@fastify/static/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -1444,6 +1425,31 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/@fastify/swagger": { + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/@fastify/swagger/-/swagger-8.14.0.tgz", + "integrity": "sha512-sGiznEb3rl6pKGGUZ+JmfI7ct5cwbTQGo+IjewaTvtzfrshnryu4dZwEsjw0YHABpBA+kCz3kpRaHB7qpa67jg==", + "dependencies": { + "fastify-plugin": "^4.0.0", + "json-schema-resolver": "^2.0.0", + "openapi-types": "^12.0.0", + "rfdc": "^1.3.0", + "yaml": "^2.2.2" + } + }, + "node_modules/@fastify/swagger-ui": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@fastify/swagger-ui/-/swagger-ui-4.0.1.tgz", + "integrity": "sha512-gG+T6ThV+uatmn5HsLA5TTnf6zOW5Ya+v7z9rFKQIclnJFK3rhgg4iIwEEi+kV6h8f32pj1kmBqD8ZeDefnd1Q==", + "license": "MIT", + "dependencies": { + "@fastify/static": "^7.0.0", + "fastify-plugin": "^4.0.0", + "openapi-types": "^12.0.2", + "rfdc": "^1.3.0", + "yaml": "^2.2.2" + } + }, "node_modules/@fastify/under-pressure": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/@fastify/under-pressure/-/under-pressure-8.3.0.tgz", @@ -2107,6 +2113,7 @@ "version": "0.1.1", "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", + "license": "MIT", "engines": { "node": "^12.20.0 || ^14.18.0 || >=16.0.0" }, @@ -3832,12 +3839,13 @@ } }, "node_modules/eslint-plugin-prettier": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.3.tgz", - "integrity": "sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.1.tgz", + "integrity": "sha512-gH3iR3g4JfF+yYPaJYkN7jEl9QbweL/YfkoRlNnuIEHEz1vHVlCmWOS+eGGiRuzHQXdJFCOTxRgvju9b8VUmrw==", + "license": "MIT", "dependencies": { "prettier-linter-helpers": "^1.0.0", - "synckit": "^0.8.6" + "synckit": "^0.9.1" }, "engines": { "node": "^14.18.0 || >=16.0.0" @@ -4062,24 +4070,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/express-handlebars/node_modules/jackspeak": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.0.tgz", - "integrity": "sha512-JVYhQnN59LVPFCEcVa2C3CrEKYacvjRfqIQl+h8oi91aLYQVWRYbxjPcv1bUiUy/kLmQaANrYfNMCO3kuEDHfw==", - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, "node_modules/express-handlebars/node_modules/minimatch": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", @@ -5383,15 +5373,13 @@ } }, "node_modules/jackspeak": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", - "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/cliui": "^8.0.2" }, - "engines": { - "node": ">=14" - }, "funding": { "url": "https://github.com/sponsors/isaacs" }, @@ -5987,6 +5975,12 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/jjsontree.js": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/jjsontree.js/-/jjsontree.js-2.1.0.tgz", + "integrity": "sha512-wowZUq2YVIltUKuQQ1SXCihsyrsSgM60IqYtvHMWtE2fdWTpBy9xPxvzWLrVQbRwvEj/ykhCpTdT+/WyGmMKYw==", + "license": "MIT" + }, "node_modules/js-sdsl": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.3.0.tgz", @@ -6006,6 +6000,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "license": "MIT", "dependencies": { "argparse": "^2.0.1" }, @@ -6213,16 +6208,20 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" }, "node_modules/logform": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/logform/-/logform-2.5.1.tgz", - "integrity": "sha512-9FyqAm9o9NKKfiAKfZoYo9bGXXuwMkxQiQttkT4YjjVtQVIQtK6LmVtlxmCaFswo6N4AfEkHqZTV0taDtPotNg==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.6.1.tgz", + "integrity": "sha512-CdaO738xRapbKIMVn2m4F6KTj4j7ooJ8POVnebSgKo3KBz5axNXRAL7ZdRjIV6NOr2Uf4vjtRkxrFETOioCqSA==", + "license": "MIT", "dependencies": { - "@colors/colors": "1.5.0", + "@colors/colors": "1.6.0", "@types/triple-beam": "^1.3.2", "fecha": "^4.2.0", "ms": "^2.1.1", "safe-stable-stringify": "^2.3.1", "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" } }, "node_modules/lru-cache": { @@ -6447,9 +6446,9 @@ } }, "node_modules/mqtt": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/mqtt/-/mqtt-5.7.3.tgz", - "integrity": "sha512-v+5la6Q6zjl0AWsI7ICDA/K3hclkNj7CMa0khMugCC+LKPLrQF+sSQb/9ckezZLMvcBC1tXhRzqmcagQoDl9fQ==", + "version": "5.8.1", + "resolved": "https://registry.npmjs.org/mqtt/-/mqtt-5.8.1.tgz", + "integrity": "sha512-EL5yY3yOdEBOyCTM41erawRxdWmGktc48eEGO74NpEBMUbTAPepo5Id4wi+/do85sACFfsycaURvoiCNxQRTHw==", "license": "MIT", "dependencies": { "@types/readable-stream": "^4.0.5", @@ -7026,9 +7025,9 @@ } }, "node_modules/prettier": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.2.tgz", - "integrity": "sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", + "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", "license": "MIT", "bin": { "prettier": "bin/prettier.cjs" @@ -7853,9 +7852,10 @@ } }, "node_modules/synckit": { - "version": "0.8.8", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.8.tgz", - "integrity": "sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ==", + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.1.tgz", + "integrity": "sha512-7gr8p9TQP6RAHusBOSLs46F4564ZrjV8xFmw5zCmgmhGUcw2hxsShhJ6CEiHQMgPDwAQ1fWHPM0ypc4RMAig4A==", + "license": "MIT", "dependencies": { "@pkgr/core": "^0.1.0", "tslib": "^2.6.2" @@ -8318,15 +8318,16 @@ } }, "node_modules/winston": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/winston/-/winston-3.13.0.tgz", - "integrity": "sha512-rwidmA1w3SE4j0E5MuIufFhyJPBDG7Nu71RkZor1p2+qHvJSZ9GYDA81AyleQcZbh/+V6HjeBdfnTZJm9rSeQQ==", + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.13.1.tgz", + "integrity": "sha512-SvZit7VFNvXRzbqGHsv5KSmgbEYR5EiQfDAL9gxYkRqa934Hnk++zze0wANKtMHcy/gI4W/3xmSDwlhf865WGw==", + "license": "MIT", "dependencies": { "@colors/colors": "^1.6.0", "@dabh/diagnostics": "^2.0.2", "async": "^3.2.3", "is-stream": "^2.0.0", - "logform": "^2.4.0", + "logform": "^2.6.0", "one-time": "^1.0.0", "readable-stream": "^3.4.0", "safe-stable-stringify": "^2.3.1", @@ -8381,14 +8382,6 @@ "node": ">= 6" } }, - "node_modules/winston/node_modules/@colors/colors": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", - "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", - "engines": { - "node": ">=0.1.90" - } - }, "node_modules/winston/node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -8491,9 +8484,9 @@ } }, "node_modules/ws": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", - "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", "license": "MIT", "engines": { "node": ">=10.0.0" @@ -8512,9 +8505,9 @@ } }, "node_modules/xstate": { - "version": "5.14.0", - "resolved": "https://registry.npmjs.org/xstate/-/xstate-5.14.0.tgz", - "integrity": "sha512-3c1H8dzebSODUSY1vM86zIE3Eg+VFzElvhklaF/ZTN5K2i6HsLc4j38qn9+TF2mHPTUFXrOn4M0Cxw9m63upLA==", + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/xstate/-/xstate-5.15.0.tgz", + "integrity": "sha512-0DGArbj+ych7PcCqHzhEvXH1qVG41lhenwdbEPBRJyxQP9TLooUsR7oiR4YWYHblLOVWvz/esiw8HUtHXp/kZA==", "license": "MIT", "funding": { "type": "opencollective", diff --git a/package.json b/package.json index 6eec1c4d..e6d97ad8 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "test2": "node_modules/jshint/bin/jshint butler.js", "format1": "npm run format", "format": "npm run format:prettier", - "format:prettier": "npx prettier --config .prettierrc \"./**/*.{ts,css,less,js}\" --write", + "format:prettier": "npx prettier --config .prettierrc \"./**/*.{ts,css,less,js,html}\" --write", "butler": "node src/butler.js", "lint": "npx eslint ./src/**/*.js" }, @@ -54,8 +54,9 @@ "@fastify/rate-limit": "^9.1.0", "@fastify/reply-from": "^9.8.0", "@fastify/sensible": "^5.6.0", + "@fastify/static": "^7.0.4", "@fastify/swagger": "^8.14.0", - "@fastify/swagger-ui": "^3.0.0", + "@fastify/swagger-ui": "^4.0.1", "@keyvhq/core": "^2.1.1", "@xstate/fsm": "^2.0.1", "any-base": "^1.1.0", @@ -69,7 +70,7 @@ "eslint": "^8.5.7", "eslint-config-airbnb-base": "^15.0.0", "eslint-config-prettier": "^9.1.0", - "eslint-plugin-prettier": "^5.1.3", + "eslint-plugin-prettier": "^5.2.1", "express-handlebars": "^7.1.3", "fastify": "^4.28.1", "fastify-healthcheck": "^4.4.0", @@ -80,6 +81,7 @@ "i": "^0.3.7", "influx": "^5.9.3", "is-unc-path": "^1.0.0", + "jjsontree.js": "^2.1.0", "js-yaml": "^4.1.0", "jshint": "^2.13.6", "lodash": "^4.17.21", @@ -87,7 +89,7 @@ "mkdirp": "^3.0.1", "moment": "^2.30.1", "moment-precise-range-plugin": "^1.3.0", - "mqtt": "^5.7.3", + "mqtt": "^5.8.1", "ms-teams-wrapper": "^1.0.2", "nodemailer": "^6.9.14", "nodemailer-express-handlebars": "^6.1.2", @@ -100,17 +102,17 @@ "systeminformation": "^5.22.11", "upath": "^2.0.1", "uuid": "^10.0.0", - "winston": "^3.13.0", + "winston": "^3.13.1", "winston-daily-rotate-file": "^5.0.0", - "ws": "^8.17.1", - "xstate": "^5.14.0" + "ws": "^8.18.0", + "xstate": "^5.15.0" }, "devDependencies": { - "@babel/eslint-parser": "^7.24.7", + "@babel/eslint-parser": "^7.24.8", "@babel/plugin-syntax-import-assertions": "^7.24.7", "eslint-plugin-import": "^2.29.1", "jest": "^29.7.0", - "prettier": "^3.3.2", + "prettier": "^3.3.3", "snyk": "^1.1292.1" }, "pkg": { diff --git a/src/app.js b/src/app.js index 48d2250c..5c19868d 100644 --- a/src/app.js +++ b/src/app.js @@ -2,15 +2,19 @@ /* eslint-disable global-require */ import path from 'path'; +import fs from 'fs'; +import handlebars from 'handlebars'; import { fileURLToPath } from 'url'; -import Fastify from 'fastify'; +import yaml from 'js-yaml'; +import Fastify from 'fastify'; // import AutoLoad from '@fastify/autoload'; import FastifySwagger from '@fastify/swagger'; import FastifySwaggerUi from '@fastify/swagger-ui'; import FastifyReplyFrom from '@fastify/reply-from'; import FastifyHealthcheck from 'fastify-healthcheck'; import FastifyRateLimit from '@fastify/rate-limit'; +import FastifyStatic from '@fastify/static'; import globals from './globals.js'; import setupHeartbeatTimer from './lib/heartbeat.js'; @@ -19,6 +23,7 @@ import serviceUptimeStart from './lib/service_uptime.js'; import setupAnonUsageReportTimer from './lib/telemetry.js'; import { configVerifyAllTaskId } from './lib/config_util.js'; import sendTestEmail from './lib/testemail.js'; +import configObfuscate from './lib/config_obfuscate.js'; async function build(opts = {}) { // Create two Fastify servers. One server is a REST server and the other is a reverse proxy server. @@ -28,6 +33,7 @@ async function build(opts = {}) { const restServer = Fastify({ logger: true }); const proxyRestServer = Fastify({ logger: true }); const dockerHealthCheckServer = Fastify({ logger: false }); + const configVisServer = Fastify({ logger: true }); // Set up connection to Influxdb (if enabled) globals.initInfluxDB(); @@ -48,9 +54,10 @@ async function build(opts = {}) { const certFile = path.resolve(dirname, globals.config.get('Butler.cert.clientCert')); const keyFile = path.resolve(dirname, globals.config.get('Butler.cert.clientCertKey')); const caFile = path.resolve(dirname, globals.config.get('Butler.cert.clientCertCA')); + globals.logger.verbose(`----------------1: ${dirname}`); - globals.logger.verbose(`MAIN: Executing JS filename: ${filename}`); globals.logger.verbose(`MAIN: Executing JS dirname: ${dirname}`); + globals.logger.verbose(`MAIN: Executing JS filename: ${filename}`); globals.logger.verbose(`MAIN: Using client cert file: ${certFile}`); globals.logger.verbose(`MAIN: Using client cert key file: ${keyFile}`); globals.logger.verbose(`MAIN: Using client cert CA file: ${caFile}`); @@ -69,9 +76,11 @@ async function build(opts = {}) { if (currLogLevel === 'debug' || currLogLevel === 'silly') { restServer.log.level = 'info'; proxyRestServer.log.level = 'info'; + configVisServer.log.level = 'info'; } else { restServer.log.level = 'silent'; proxyRestServer.log.level = 'silent'; + configVisServer.log.level = 'silent'; } // Get host info @@ -138,11 +147,11 @@ async function build(opts = {}) { if (globals.config.has('Butler.restServerConfig.enable') && globals.config.get('Butler.restServerConfig.enable') === true) { globals.logger.info( `REST API documentation available at http://${globals.config.get( - 'Butler.restServerConfig.serverHost' - )}:${globals.config.get('Butler.restServerConfig.serverPort')}/documentation` + 'Butler.restServerConfig.serverHost', + )}:${globals.config.get('Butler.restServerConfig.serverPort')}/documentation`, ); globals.logger.info( - '--> Note regarding API docs: If the line above mentions 0.0.0.0, this is the same as ANY server IP address.' + '--> Note regarding API docs: If the line above mentions 0.0.0.0, this is the same as ANY server IP address.', ); globals.logger.info("--> Replace 0.0.0.0 with one of the Butler host's IP addresses to view the API docs page."); } @@ -171,7 +180,7 @@ async function build(opts = {}) { restServer.setErrorHandler((error, request, reply) => { if (error.statusCode === 429) { globals.logger.warn( - `API: Rate limit exceeded for source IP address ${request.ip}. Method=${request.method}, endpoint=${request.url}` + `API: Rate limit exceeded for source IP address ${request.ip}. Method=${request.method}, endpoint=${request.url}`, ); } reply.send(error); @@ -198,7 +207,7 @@ async function build(opts = {}) { servers: [ { url: `http://${globals.config.get('Butler.restServerConfig.serverHost')}:${globals.config.get( - 'Butler.restServerConfig.serverPort' + 'Butler.restServerConfig.serverPort', )}`, }, ], @@ -217,27 +226,27 @@ async function build(opts = {}) { }); // Loads all plugins defined in routes - await restServer.register(import('./routes/api.js'), { options: Object.assign({}, opts) }); - await restServer.register(import('./routes/base_conversion.js'), { options: Object.assign({}, opts) }); - await restServer.register(import('./routes/butler_ping.js'), { options: Object.assign({}, opts) }); - await restServer.register(import('./routes/disk_utils.js'), { options: Object.assign({}, opts) }); - await restServer.register(import('./routes/key_value_store.js'), { options: Object.assign({}, opts) }); - await restServer.register(import('./routes/mqtt_publish_message.js'), { options: Object.assign({}, opts) }); - await restServer.register(import('./routes/newrelic_event.js'), { options: Object.assign({}, opts) }); - await restServer.register(import('./routes/newrelic_metric.js'), { options: Object.assign({}, opts) }); - await restServer.register(import('./routes/scheduler.js'), { options: Object.assign({}, opts) }); - await restServer.register(import('./routes/sense_app.js'), { options: Object.assign({}, opts) }); - await restServer.register(import('./routes/sense_app_dump.js'), { options: Object.assign({}, opts) }); - await restServer.register(import('./routes/sense_list_apps.js'), { options: Object.assign({}, opts) }); - await restServer.register(import('./routes/sense_start_task.js'), { options: Object.assign({}, opts) }); - await restServer.register(import('./routes/slack_post_message.js'), { options: Object.assign({}, opts) }); + await restServer.register(import('./routes/rest_server/api.js'), { options: Object.assign({}, opts) }); + await restServer.register(import('./routes/rest_server/base_conversion.js'), { options: Object.assign({}, opts) }); + await restServer.register(import('./routes/rest_server/butler_ping.js'), { options: Object.assign({}, opts) }); + await restServer.register(import('./routes/rest_server/disk_utils.js'), { options: Object.assign({}, opts) }); + await restServer.register(import('./routes/rest_server/key_value_store.js'), { options: Object.assign({}, opts) }); + await restServer.register(import('./routes/rest_server/mqtt_publish_message.js'), { options: Object.assign({}, opts) }); + await restServer.register(import('./routes/rest_server/newrelic_event.js'), { options: Object.assign({}, opts) }); + await restServer.register(import('./routes/rest_server/newrelic_metric.js'), { options: Object.assign({}, opts) }); + await restServer.register(import('./routes/rest_server/scheduler.js'), { options: Object.assign({}, opts) }); + await restServer.register(import('./routes/rest_server/sense_app.js'), { options: Object.assign({}, opts) }); + await restServer.register(import('./routes/rest_server/sense_app_dump.js'), { options: Object.assign({}, opts) }); + await restServer.register(import('./routes/rest_server/sense_list_apps.js'), { options: Object.assign({}, opts) }); + await restServer.register(import('./routes/rest_server/sense_start_task.js'), { options: Object.assign({}, opts) }); + await restServer.register(import('./routes/rest_server/slack_post_message.js'), { options: Object.assign({}, opts) }); // --------------------------------------------------- // Configure X-HTTP-Method-Override handling await proxyRestServer.register(FastifyReplyFrom, { // base: `http://localhost:${globals.config.get('Butler.restServerConfig.backgroundServerPort')}`, base: `http://${globals.config.get('Butler.restServerConfig.serverHost')}:${globals.config.get( - 'Butler.restServerConfig.backgroundServerPort' + 'Butler.restServerConfig.backgroundServerPort', )}`, http: true, }); @@ -294,6 +303,96 @@ async function build(opts = {}) { globals.logger.info('MAIN: Will not set up REST server as it is disabled in the config file.'); } + // Set up config server, if enabled + if (globals.config.get('Butler.configVisualisation.enable') === true) { + // Register rate limit for API + // 0 means no rate limit + + // This code registers the FastifyRateLimit plugin. + // The plugin limits the number of API requests that + // can be made from a given IP address within a given + // time window. + + // 30 requests per minute + await configVisServer.register(FastifyRateLimit, { + max: 300, + timeWindow: '1 minute', + }); + + // Add custom error handler for 429 errors (rate limit exceeded) + configVisServer.setErrorHandler((error, request, reply) => { + if (error.statusCode === 429) { + globals.logger.warn( + `CONFIG VIS: Rate limit exceeded for source IP address ${request.ip}. Method=${request.method}, endpoint=${request.url}`, + ); + } + reply.send(error); + }); + + // This loads all plugins defined in plugins. + // Those should be support plugins that are reused through your application + await configVisServer.register(import('./plugins/sensible.js'), { options: Object.assign({}, opts) }); + await configVisServer.register(import('./plugins/support.js'), { options: Object.assign({}, opts) }); + + // Create absolute path to the html directory + // dirname points to the directory where this file (app.js) is located, taking into account + // if the app is running as a packaged app or as a Node.js app. + globals.logger.verbose(`----------------2: ${globals.appBasePath}`); + + // Get directory contents of dirname + const dirContents = fs.readdirSync(globals.appBasePath); + globals.logger.verbose(`CONFIG VIS: Directory contents of "${globals.appBasePath}": ${dirContents}`); + + + const htmlDir = path.resolve(globals.appBasePath, 'static/configvis'); + globals.logger.info(`CONFIG VIS: Serving static files from ${htmlDir}`); + + await configVisServer.register(FastifyStatic, { + root: htmlDir, + constraints: {}, // optional: default {}. Example: { host: 'example.com' } + redirect: true, // Redirect to trailing '/' when the pathname is a dir + }); + + configVisServer.get('/', async (request, reply) => { + // Obfuscate the config object before sending it to the client + // First get clean copy of the config object + let newConfig = JSON.parse(JSON.stringify(globals.config)); + + if (globals.config.get('Butler.configVisualisation.obfuscate')) { + // Obfuscate config file before presenting it to the user + // This is done to avoid leaking sensitive information + // to users who should not have access to it. + // The obfuscation is done by replacing parts of the + // config file with masked strings. + newConfig = configObfuscate(newConfig); + } + + // Convert the (potentially obfuscated) config object to YAML format (=string) + const butlerConfigYaml = yaml.dump(newConfig); + + // Read index.html from disk + // dirname points to the directory where this file (app.js) is located, taking into account + // if the app is running as a packaged app or as a Node.js app. + globals.logger.verbose(`----------------3: ${globals.appBasePath}`); + const filePath = path.resolve(globals.appBasePath, 'static/configvis', 'index.html'); + const template = fs.readFileSync(filePath, 'utf8'); + + // Compile handlebars template + const compiledTemplate = handlebars.compile(template); + + // Get config as HTML encoded JSON string + const butlerConfigJsonEncoded = JSON.stringify(newConfig); + + // Render the template + const renderedText = compiledTemplate({ butlerConfigJsonEncoded, butlerConfigYaml }); + + globals.logger.debug(`CONFIG VIS: Rendered text: ${renderedText}`); + + // Send reply as HTML + reply.code(200).header('Content-Type', 'text/html; charset=utf-8').send(renderedText); + }); + } + // Load already defined schedules if (globals.config.has('Butler.scheduler')) { if (globals.config.get('Butler.scheduler.enable') === true) { @@ -307,7 +406,7 @@ async function build(opts = {}) { await dockerHealthCheckServer.register(FastifyHealthcheck); - return { restServer, proxyRestServer, dockerHealthCheckServer }; + return { restServer, proxyRestServer, dockerHealthCheckServer, configVisServer }; } export default build; diff --git a/src/butler.js b/src/butler.js index bac239bd..bba434e7 100644 --- a/src/butler.js +++ b/src/butler.js @@ -105,6 +105,27 @@ const start = async () => { const { restServer } = apps; const { proxyRestServer } = apps; const { dockerHealthCheckServer } = apps; + const { configVisServer } = apps; + + configVisServer.listen( + { + host: globals.config.get('Butler.configVisualisation.host'), + port: globals.config.get('Butler.configVisualisation.port'), + }, + (err, address) => { + if (err) { + globals.logger.error(`MAIN: Could not set up config visualisation server on ${address}`); + globals.logger.error(`MAIN: ${err.stack}`); + configVisServer.log.error(err); + process.exit(1); + } + globals.logger.verbose(`MAIN: Config visualisation server listening on ${address}`); + + configVisServer.ready((err2) => { + if (err2) throw err; + }); + }, + ); // --------------------------------------------------- // Start REST server on port 8080 @@ -131,7 +152,7 @@ const start = async () => { if (err2) throw err; restServer.swagger(); }); - } + }, ); proxyRestServer.listen( @@ -151,7 +172,7 @@ const start = async () => { proxyRestServer.ready((err2) => { if (err2) throw err; }); - } + }, ); } @@ -170,7 +191,7 @@ const start = async () => { globals.logger.info(`MAIN: Started Docker healthcheck server on port ${globals.config.get('Butler.dockerHealthCheck.port')}.`); } catch (err) { globals.logger.error( - `MAIN: Error while starting Docker healthcheck server on port ${globals.config.get('Butler.dockerHealthCheck.port')}.` + `MAIN: Error while starting Docker healthcheck server on port ${globals.config.get('Butler.dockerHealthCheck.port')}.`, ); dockerHealthCheckServer.log.error(err); process.exit(1); diff --git a/src/config/production_template.yaml b/src/config/production_template.yaml index a2de2531..bf816a63 100644 --- a/src/config/production_template.yaml +++ b/src/config/production_template.yaml @@ -914,6 +914,13 @@ Butler: webhook: # Send service alerts as outbound webhooks/http calls enable: true + # Should Butler start a web server that serves an obfuscated view of the Butler config file? + configVisualisation: + enable: true + host: localhost # Hostname or IP address where the web server will listen. Should be localhost in most cases. + port: 3100 # Port where the web server will listen. Change if port 3100 is already in use. + obfuscate: true # Should the config file shown in the web UI be obfuscated? + # Certificates to use when connecting to Sense. Get these from the Certificate Export in QMC. cert: clientCert: diff --git a/src/globals.js b/src/globals.js index 40310d60..0e902282 100644 --- a/src/globals.js +++ b/src/globals.js @@ -42,6 +42,9 @@ class Settings { // Add path to package.json file c = upath.join(b, filenamePackage); + + // Set base path of the executable + this.appBasePath = upath.join(b); } else { // Get path to JS file a = fileURLToPath(import.meta.url); @@ -51,6 +54,9 @@ class Settings { // Add path to package.json file c = upath.join(b, '..', filenamePackage); + + // Set base path of the executable + this.appBasePath = upath.join(b, '..'); } const { version } = JSON.parse(readFileSync(c)); @@ -62,26 +68,26 @@ class Settings { .version(this.appVersion) .name('butler') .description( - 'Butler gives superpowers to client-managed Qlik Sense Enterprise on Windows!\nAdvanced reload failure alerts, task scheduler, key-value store, file system access and much more.' + 'Butler gives superpowers to client-managed Qlik Sense Enterprise on Windows!\nAdvanced reload failure alerts, task scheduler, key-value store, file system access and much more.', ) .option('-c, --configfile ', 'path to config file') .addOption(new Option('-l, --loglevel ', 'log level').choices(['error', 'warn', 'info', 'verbose', 'debug', 'silly'])) .option( '--new-relic-account-name ', - 'New Relic account name. Used within Butler to differentiate between different target New Relic accounts' + 'New Relic account name. Used within Butler to differentiate between different target New Relic accounts', ) .option('--new-relic-api-key ', 'insert API key to use with New Relic') .option('--new-relic-account-id ', 'New Relic account ID') .option('--test-email-address
', 'send test email to this address. Used to verify email settings in the config file.') .option( '--test-email-from-address
', - 'send test email from this address. Only relevant when SMTP server allows from address to be set.' + 'send test email from this address. Only relevant when SMTP server allows from address to be set.', ) .option('--no-qs-connection', "don't connect to Qlik Sense server at all. Run in isolated mode") .option( '--api-rate-limit', 'set the API rate limit, per minute. Default is 100 calls/minute. Set to 0 to disable rate limiting.', - 100 + 100, ) .option('--skip-config-verification', 'Disable config file verification', false); @@ -188,9 +194,9 @@ class Settings { winston.format.timestamp(), winston.format.colorize(), winston.format.simple(), - winston.format.printf((info) => `${info.timestamp} ${info.level}: ${info.message}`) + winston.format.printf((info) => `${info.timestamp} ${info.level}: ${info.message}`), ), - }) + }), ); if ( @@ -227,7 +233,7 @@ class Settings { level: this.config.get('Butler.logLevel'), datePattern: 'YYYY-MM-DD', maxFiles: '30d', - }) + }), ); } @@ -235,7 +241,7 @@ class Settings { transports: this.logTransports, format: winston.format.combine( winston.format.timestamp(), - winston.format.printf((info) => `${info.timestamp} ${info.level}: ${info.message}`) + winston.format.printf((info) => `${info.timestamp} ${info.level}: ${info.message}`), ), }); @@ -251,8 +257,8 @@ class Settings { `New Relic account names/API keys/account IDs (via command line or config file): ${JSON.stringify( this.config.Butler.thirdPartyToolsCredentials.newRelic, null, - 2 - )}` + 2, + )}`, ); // Get certificate file paths for QRS connection @@ -426,12 +432,12 @@ class Settings { if (this.hostInfo.si.os.platform.toLowerCase() !== 'windows') { if (isUncPath(element.fromDirectory) === true) { this.logger.warn( - `FILE COPY CONFIG: UNC paths won't work on non-Windows OSs ("${element.fromDirectory}"). OS is "${this.hostInfo.si.os.platform}".` + `FILE COPY CONFIG: UNC paths won't work on non-Windows OSs ("${element.fromDirectory}"). OS is "${this.hostInfo.si.os.platform}".`, ); } if (isUncPath(element.toDirectory) === true) { this.logger.warn( - `FILE COPY CONFIG: UNC paths won't work on non-Windows OSs ("${element.toDirectory}"). OS is "${this.hostInfo.si.os.platform}".` + `FILE COPY CONFIG: UNC paths won't work on non-Windows OSs ("${element.toDirectory}"). OS is "${this.hostInfo.si.os.platform}".`, ); } } @@ -460,12 +466,12 @@ class Settings { if (this.hostInfo.si.os.platform.toLowerCase() !== 'windows') { if (isUncPath(element.fromDirectory) === true) { this.logger.warn( - `FILE MOVE CONFIG: UNC paths won't work on non-Windows OSs ("${element.fromDirectory}"). OS is "${this.hostInfo.si.os.platform}".` + `FILE MOVE CONFIG: UNC paths won't work on non-Windows OSs ("${element.fromDirectory}"). OS is "${this.hostInfo.si.os.platform}".`, ); } if (isUncPath(element.toDirectory) === true) { this.logger.warn( - `FILE MOVE CONFIG: UNC paths won't work on non-Windows OSs ("${element.toDirectory}"). OS is "${this.hostInfo.si.os.platform}".` + `FILE MOVE CONFIG: UNC paths won't work on non-Windows OSs ("${element.toDirectory}"). OS is "${this.hostInfo.si.os.platform}".`, ); } } @@ -498,7 +504,7 @@ class Settings { if (this.hostInfo.si.os.platform.toLowerCase() !== 'windows') { if (isUncPath(element) === true) { this.logger.warn( - `FILE DELETE CONFIG: UNC paths won't work on non-Windows OSs ("${element}"). OS is "${this.hostInfo.si.os.platform}".` + `FILE DELETE CONFIG: UNC paths won't work on non-Windows OSs ("${element}"). OS is "${this.hostInfo.si.os.platform}".`, ); } } @@ -647,7 +653,7 @@ class Settings { }) .catch((err) => { this.logger.error( - `CONFIG: Error creating new InfluxDB retention policy "${newPolicy.name}"! ${err.stack}` + `CONFIG: Error creating new InfluxDB retention policy "${newPolicy.name}"! ${err.stack}`, ); }); }) diff --git a/src/lib/assert/assert_config_file.js b/src/lib/assert/assert_config_file.js index 4b212eab..487f6a51 100644 --- a/src/lib/assert/assert_config_file.js +++ b/src/lib/assert/assert_config_file.js @@ -28,8 +28,8 @@ export const configFileInfluxDbAssert = async (config, configQRS, logger) => { ) { logger.error( `ASSERT CONFIG INFLUXDB: Custom property '${config.get( - 'Butler.influxDb.reloadTaskSuccess.byCustomProperty.customPropertyName' - )}' not found in Qlik Sense. Aborting.` + 'Butler.influxDb.reloadTaskSuccess.byCustomProperty.customPropertyName', + )}' not found in Qlik Sense. Aborting.`, ); return false; } @@ -37,12 +37,12 @@ export const configFileInfluxDbAssert = async (config, configQRS, logger) => { // Ensure that the CP value specified in the config file is found in the list of available CP values // CP value is case sensitive and found in the "choiceValues" array of the CP objects in res1 const res2 = res1.filter( - (cp) => cp.name === config.get('Butler.influxDb.reloadTaskSuccess.byCustomProperty.customPropertyName') + (cp) => cp.name === config.get('Butler.influxDb.reloadTaskSuccess.byCustomProperty.customPropertyName'), )[0].choiceValues; logger.debug( `ASSERT CONFIG INFLUXDB: The following values are available for custom property '${config.get( - 'Butler.influxDb.reloadTaskSuccess.byCustomProperty.customPropertyName' - )}': ${res2}` + 'Butler.influxDb.reloadTaskSuccess.byCustomProperty.customPropertyName', + )}': ${res2}`, ); if ( @@ -51,10 +51,10 @@ export const configFileInfluxDbAssert = async (config, configQRS, logger) => { ) { logger.error( `ASSERT CONFIG INFLUXDB: Custom property value '${config.get( - 'Butler.influxDb.reloadTaskSuccess.byCustomProperty.enabledValue' + 'Butler.influxDb.reloadTaskSuccess.byCustomProperty.enabledValue', )}' not found for custom property '${config.get( - 'Butler.influxDb.reloadTaskSuccess.byCustomProperty.customPropertyName' - )}'. Aborting.` + 'Butler.influxDb.reloadTaskSuccess.byCustomProperty.customPropertyName', + )}'. Aborting.`, ); return false; } @@ -100,19 +100,19 @@ export const configFileNewRelicAssert = async (config, configQRS, logger) => { try { logger.debug( `ASSERT CONFIG NEW RELIC 1: Custom property name: ${config.get( - 'Butler.incidentTool.newRelic.reloadTaskFailure.destination.event.sendToAccount.byCustomProperty.customPropertyName' - )}` + 'Butler.incidentTool.newRelic.reloadTaskFailure.destination.event.sendToAccount.byCustomProperty.customPropertyName', + )}`, ); logger.debug( `ASSERT CONFIG NEW RELIC 1: custompropertydefinition/full?filter=name eq '${config.get( - 'Butler.incidentTool.newRelic.reloadTaskFailure.destination.event.sendToAccount.byCustomProperty.customPropertyName' - )}` + 'Butler.incidentTool.newRelic.reloadTaskFailure.destination.event.sendToAccount.byCustomProperty.customPropertyName', + )}`, ); const result1 = await qrsInstance.Get( `custompropertydefinition/full?filter=name eq '${config.get( - 'Butler.incidentTool.newRelic.reloadTaskFailure.destination.event.sendToAccount.byCustomProperty.customPropertyName' - )}'` + 'Butler.incidentTool.newRelic.reloadTaskFailure.destination.event.sendToAccount.byCustomProperty.customPropertyName', + )}'`, ); // The choice values of the custom property should match the values in Butler.thirdPartyToolsCredentials.newRelic @@ -121,8 +121,8 @@ export const configFileNewRelicAssert = async (config, configQRS, logger) => { if (result1.body.length === 0) { logger.error( `ASSERT CONFIG NEW RELIC: Custom property specified in config file ('${config.get( - 'Butler.incidentTool.newRelic.reloadTaskFailure.destination.event.sendToAccount.byCustomProperty.customPropertyName' - )})' does not exist in Qlik Sense. Aborting.` + 'Butler.incidentTool.newRelic.reloadTaskFailure.destination.event.sendToAccount.byCustomProperty.customPropertyName', + )})' does not exist in Qlik Sense. Aborting.`, ); return false; } @@ -135,13 +135,13 @@ export const configFileNewRelicAssert = async (config, configQRS, logger) => { ) { logger.warn( `ASSERT CONFIG NEW RELIC: Custom property '${config.get( - 'Butler.incidentTool.newRelic.reloadTaskFailure.destination.event.sendToAccount.byCustomProperty.customPropertyName' - )}' does not have any values associated with it. New Relic monitoring may not work as a result of this.` + 'Butler.incidentTool.newRelic.reloadTaskFailure.destination.event.sendToAccount.byCustomProperty.customPropertyName', + )}' does not have any values associated with it. New Relic monitoring may not work as a result of this.`, ); } else if (config.get('Butler.thirdPartyToolsCredentials.newRelic') === null) { // New Relic account specified as destination for events, but no account(s) specified in config file or on command line logger.warn( - `ASSERT CONFIG NEW RELIC: New Relic is set as a destination for alert events, but no New Relic account(s) specified on either command line or in config file. Aborting,` + `ASSERT CONFIG NEW RELIC: New Relic is set as a destination for alert events, but no New Relic account(s) specified on either command line or in config file. Aborting,`, ); return false; } else { @@ -153,8 +153,8 @@ export const configFileNewRelicAssert = async (config, configQRS, logger) => { if (availableNewRelicAccounts.findIndex((account) => value === account.accountName) === -1) { logger.warn( `ASSERT CONFIG NEW RELIC: New Relic account name '${value}' of custom property '${config.get( - 'Butler.incidentTool.newRelic.reloadTaskFailure.destination.event.sendToAccount.byCustomProperty.customPropertyName' - )}' not found in Butler's config file` + 'Butler.incidentTool.newRelic.reloadTaskFailure.destination.event.sendToAccount.byCustomProperty.customPropertyName', + )}' not found in Butler's config file`, ); } } @@ -168,11 +168,11 @@ export const configFileNewRelicAssert = async (config, configQRS, logger) => { config.has('Butler.incidentTool.newRelic.reloadTaskFailure.destination.event.sendToAccount.byCustomProperty.enable') && config.get('Butler.incidentTool.newRelic.reloadTaskFailure.destination.event.sendToAccount.byCustomProperty.enable') && !config.has( - 'Butler.incidentTool.newRelic.reloadTaskFailure.destination.event.sendToAccount.byCustomProperty.customPropertyName' + 'Butler.incidentTool.newRelic.reloadTaskFailure.destination.event.sendToAccount.byCustomProperty.customPropertyName', ) ) { logger.error( - `ASSERT CONFIG NEW RELIC: Missing config file entry "Butler.incidentTool.newRelic.reloadTaskFailure.destination.event.sendToAccount.byCustomProperty.customPropertyName"` + `ASSERT CONFIG NEW RELIC: Missing config file entry "Butler.incidentTool.newRelic.reloadTaskFailure.destination.event.sendToAccount.byCustomProperty.customPropertyName"`, ); return false; } @@ -193,19 +193,19 @@ export const configFileNewRelicAssert = async (config, configQRS, logger) => { try { logger.debug( `ASSERT CONFIG NEW RELIC 1: Custom property name: ${config.get( - 'Butler.incidentTool.newRelic.reloadTaskFailure.destination.log.sendToAccount.byCustomProperty.customPropertyName' - )}` + 'Butler.incidentTool.newRelic.reloadTaskFailure.destination.log.sendToAccount.byCustomProperty.customPropertyName', + )}`, ); logger.debug( `ASSERT CONFIG NEW RELIC 1: custompropertydefinition/full?filter=name eq '${config.get( - 'Butler.incidentTool.newRelic.reloadTaskFailure.destination.log.sendToAccount.byCustomProperty.customPropertyName' - )}` + 'Butler.incidentTool.newRelic.reloadTaskFailure.destination.log.sendToAccount.byCustomProperty.customPropertyName', + )}`, ); const result1 = await qrsInstance.Get( `custompropertydefinition/full?filter=name eq '${config.get( - 'Butler.incidentTool.newRelic.reloadTaskFailure.destination.log.sendToAccount.byCustomProperty.customPropertyName' - )}'` + 'Butler.incidentTool.newRelic.reloadTaskFailure.destination.log.sendToAccount.byCustomProperty.customPropertyName', + )}'`, ); // The choice values of the custom property should match the values in Butler.thirdPartyToolsCredentials.newRelic @@ -213,8 +213,8 @@ export const configFileNewRelicAssert = async (config, configQRS, logger) => { if (result1.body.length === 0) { logger.error( `ASSERT CONFIG NEW RELIC: Custom property specified in config file ('${config.get( - 'Butler.incidentTool.newRelic.reloadTaskFailure.destination.log.sendToAccount.byCustomProperty.customPropertyName' - )})' does not exist in Qlik Sense. Aborting.` + 'Butler.incidentTool.newRelic.reloadTaskFailure.destination.log.sendToAccount.byCustomProperty.customPropertyName', + )})' does not exist in Qlik Sense. Aborting.`, ); return false; } @@ -227,13 +227,13 @@ export const configFileNewRelicAssert = async (config, configQRS, logger) => { ) { logger.warn( `ASSERT CONFIG NEW RELIC: Custom property '${config.get( - 'Butler.incidentTool.newRelic.reloadTaskFailure.destination.log.sendToAccount.byCustomProperty.customPropertyName' - )}' does not have any values associated with it. New Relic monitoring may not work as a result of this.` + 'Butler.incidentTool.newRelic.reloadTaskFailure.destination.log.sendToAccount.byCustomProperty.customPropertyName', + )}' does not have any values associated with it. New Relic monitoring may not work as a result of this.`, ); } else if (config.get('Butler.thirdPartyToolsCredentials.newRelic') === null) { // New Relic account specified as destination for events, but no account(s) specified in config file or on command line logger.error( - `ASSERT CONFIG NEW RELIC: New Relic is set as a destination for failed reload alert logs, but no New Relic account(s) specified on either command line or in config file. Aborting,` + `ASSERT CONFIG NEW RELIC: New Relic is set as a destination for failed reload alert logs, but no New Relic account(s) specified on either command line or in config file. Aborting,`, ); return false; } else { @@ -245,8 +245,8 @@ export const configFileNewRelicAssert = async (config, configQRS, logger) => { if (availableNewRelicAccounts.findIndex((account) => value === account.accountName) === -1) { logger.warn( `ASSERT CONFIG NEW RELIC: New Relic account name '${value}' of custom property '${config.get( - 'Butler.incidentTool.newRelic.reloadTaskFailure.destination.log.sendToAccount.byCustomProperty.customPropertyName' - )}' not found in Butler's config file` + 'Butler.incidentTool.newRelic.reloadTaskFailure.destination.log.sendToAccount.byCustomProperty.customPropertyName', + )}' not found in Butler's config file`, ); } } @@ -262,7 +262,7 @@ export const configFileNewRelicAssert = async (config, configQRS, logger) => { !config.has('Butler.incidentTool.newRelic.reloadTaskFailure.destination.log.sendToAccount.byCustomProperty.customPropertyName') ) { logger.error( - `ASSERT CONFIG NEW RELIC: Missing config file entry "Butler.incidentTool.newRelic.reloadTaskFailure.destination.log.sendToAccount.byCustomProperty.customPropertyName"` + `ASSERT CONFIG NEW RELIC: Missing config file entry "Butler.incidentTool.newRelic.reloadTaskFailure.destination.log.sendToAccount.byCustomProperty.customPropertyName"`, ); return false; } @@ -283,19 +283,19 @@ export const configFileNewRelicAssert = async (config, configQRS, logger) => { try { logger.debug( `ASSERT CONFIG NEW RELIC 1: Custom property name: ${config.get( - 'Butler.incidentTool.newRelic.reloadTaskAborted.destination.event.sendToAccount.byCustomProperty.customPropertyName' - )}` + 'Butler.incidentTool.newRelic.reloadTaskAborted.destination.event.sendToAccount.byCustomProperty.customPropertyName', + )}`, ); logger.debug( `ASSERT CONFIG NEW RELIC 1: custompropertydefinition/full?filter=name eq '${config.get( - 'Butler.incidentTool.newRelic.reloadTaskAborted.destination.event.sendToAccount.byCustomProperty.customPropertyName' - )}` + 'Butler.incidentTool.newRelic.reloadTaskAborted.destination.event.sendToAccount.byCustomProperty.customPropertyName', + )}`, ); const result1 = await qrsInstance.Get( `custompropertydefinition/full?filter=name eq '${config.get( - 'Butler.incidentTool.newRelic.reloadTaskAborted.destination.event.sendToAccount.byCustomProperty.customPropertyName' - )}'` + 'Butler.incidentTool.newRelic.reloadTaskAborted.destination.event.sendToAccount.byCustomProperty.customPropertyName', + )}'`, ); // The choice values of the custom property should match the values in Butler.thirdPartyToolsCredentials.newRelic @@ -303,8 +303,8 @@ export const configFileNewRelicAssert = async (config, configQRS, logger) => { if (result1.body.length === 0) { logger.error( `ASSERT CONFIG NEW RELIC: Custom property specified in config file ('${config.get( - 'Butler.incidentTool.newRelic.reloadTaskAborted.destination.event.sendToAccount.byCustomProperty.customPropertyName' - )})' does not exist in Qlik Sense. Aborting.` + 'Butler.incidentTool.newRelic.reloadTaskAborted.destination.event.sendToAccount.byCustomProperty.customPropertyName', + )})' does not exist in Qlik Sense. Aborting.`, ); return false; } @@ -317,13 +317,13 @@ export const configFileNewRelicAssert = async (config, configQRS, logger) => { ) { logger.warn( `ASSERT CONFIG NEW RELIC: Custom property '${config.get( - 'Butler.incidentTool.newRelic.reloadTaskAborted.destination.event.sendToAccount.byCustomProperty.customPropertyName' - )}' does not have any values associated with it. New Relic monitoring may not work as a result of this.` + 'Butler.incidentTool.newRelic.reloadTaskAborted.destination.event.sendToAccount.byCustomProperty.customPropertyName', + )}' does not have any values associated with it. New Relic monitoring may not work as a result of this.`, ); } else if (config.get('Butler.thirdPartyToolsCredentials.newRelic') === null) { // New Relic account specified as destination for events, but no account(s) specified in config file or on command line logger.warn( - `ASSERT CONFIG NEW RELIC: New Relic is set as a destination for alert events, but no New Relic account(s) specified on either command line or in config file. Aborting,` + `ASSERT CONFIG NEW RELIC: New Relic is set as a destination for alert events, but no New Relic account(s) specified on either command line or in config file. Aborting,`, ); return false; } else { @@ -335,8 +335,8 @@ export const configFileNewRelicAssert = async (config, configQRS, logger) => { if (availableNewRelicAccounts.findIndex((account) => value === account.accountName) === -1) { logger.warn( `ASSERT CONFIG NEW RELIC: New Relic account name '${value}' of custom property '${config.get( - 'Butler.incidentTool.newRelic.reloadTaskAborted.destination.event.sendToAccount.byCustomProperty.customPropertyName' - )}' not found in Butler's config file` + 'Butler.incidentTool.newRelic.reloadTaskAborted.destination.event.sendToAccount.byCustomProperty.customPropertyName', + )}' not found in Butler's config file`, ); } } @@ -350,11 +350,11 @@ export const configFileNewRelicAssert = async (config, configQRS, logger) => { config.has('Butler.incidentTool.newRelic.reloadTaskAborted.destination.event.sendToAccount.byCustomProperty.enable') && config.get('Butler.incidentTool.newRelic.reloadTaskAborted.destination.event.sendToAccount.byCustomProperty.enable') && !config.has( - 'Butler.incidentTool.newRelic.reloadTaskAborted.destination.event.sendToAccount.byCustomProperty.customPropertyName' + 'Butler.incidentTool.newRelic.reloadTaskAborted.destination.event.sendToAccount.byCustomProperty.customPropertyName', ) ) { logger.error( - `ASSERT CONFIG NEW RELIC: Missing config file entry "Butler.incidentTool.newRelic.reloadTaskAborted.destination.event.sendToAccount.byCustomProperty.customPropertyName"` + `ASSERT CONFIG NEW RELIC: Missing config file entry "Butler.incidentTool.newRelic.reloadTaskAborted.destination.event.sendToAccount.byCustomProperty.customPropertyName"`, ); return false; } @@ -375,19 +375,19 @@ export const configFileNewRelicAssert = async (config, configQRS, logger) => { try { logger.debug( `ASSERT CONFIG NEW RELIC 1: Custom property name: ${config.get( - 'Butler.incidentTool.newRelic.reloadTaskAborted.destination.log.sendToAccount.byCustomProperty.customPropertyName' - )}` + 'Butler.incidentTool.newRelic.reloadTaskAborted.destination.log.sendToAccount.byCustomProperty.customPropertyName', + )}`, ); logger.debug( `ASSERT CONFIG NEW RELIC 1: custompropertydefinition/full?filter=name eq '${config.get( - 'Butler.incidentTool.newRelic.reloadTaskAborted.destination.log.sendToAccount.byCustomProperty.customPropertyName' - )}` + 'Butler.incidentTool.newRelic.reloadTaskAborted.destination.log.sendToAccount.byCustomProperty.customPropertyName', + )}`, ); const result1 = await qrsInstance.Get( `custompropertydefinition/full?filter=name eq '${config.get( - 'Butler.incidentTool.newRelic.reloadTaskAborted.destination.log.sendToAccount.byCustomProperty.customPropertyName' - )}'` + 'Butler.incidentTool.newRelic.reloadTaskAborted.destination.log.sendToAccount.byCustomProperty.customPropertyName', + )}'`, ); // The choice values of the custom property should match the values in Butler.thirdPartyToolsCredentials.newRelic @@ -395,8 +395,8 @@ export const configFileNewRelicAssert = async (config, configQRS, logger) => { if (result1.body.length === 0) { logger.error( `ASSERT CONFIG NEW RELIC: Custom property specified in config file ('${config.get( - 'Butler.incidentTool.newRelic.reloadTaskAborted.destination.log.sendToAccount.byCustomProperty.customPropertyName' - )})' does not exist in Qlik Sense. Aborting.` + 'Butler.incidentTool.newRelic.reloadTaskAborted.destination.log.sendToAccount.byCustomProperty.customPropertyName', + )})' does not exist in Qlik Sense. Aborting.`, ); return false; } @@ -409,13 +409,13 @@ export const configFileNewRelicAssert = async (config, configQRS, logger) => { ) { logger.warn( `ASSERT CONFIG NEW RELIC: Custom property '${config.get( - 'Butler.incidentTool.newRelic.reloadTaskAborted.destination.log.sendToAccount.byCustomProperty.customPropertyName' - )}' does not have any values associated with it. New Relic monitoring may not work as a result of this.` + 'Butler.incidentTool.newRelic.reloadTaskAborted.destination.log.sendToAccount.byCustomProperty.customPropertyName', + )}' does not have any values associated with it. New Relic monitoring may not work as a result of this.`, ); } else if (config.get('Butler.thirdPartyToolsCredentials.newRelic') === null) { // New Relic account specified as destination for events, but no account(s) specified in config file or on command line logger.error( - `ASSERT CONFIG NEW RELIC: New Relic is set as a destination for aborted reload alert logs, but no New Relic account(s) specified on either command line or in config file. Aborting,` + `ASSERT CONFIG NEW RELIC: New Relic is set as a destination for aborted reload alert logs, but no New Relic account(s) specified on either command line or in config file. Aborting,`, ); return false; } else { @@ -427,8 +427,8 @@ export const configFileNewRelicAssert = async (config, configQRS, logger) => { if (availableNewRelicAccounts.findIndex((account) => value === account.accountName) === -1) { logger.warn( `ASSERT CONFIG NEW RELIC: New Relic account name '${value}' of custom property '${config.get( - 'Butler.incidentTool.newRelic.reloadTaskAborted.destination.log.sendToAccount.byCustomProperty.customPropertyName' - )}' not found in Butler's config file` + 'Butler.incidentTool.newRelic.reloadTaskAborted.destination.log.sendToAccount.byCustomProperty.customPropertyName', + )}' not found in Butler's config file`, ); } } @@ -444,7 +444,7 @@ export const configFileNewRelicAssert = async (config, configQRS, logger) => { !config.has('Butler.incidentTool.newRelic.reloadTaskAborted.destination.log.sendToAccount.byCustomProperty.customPropertyName') ) { logger.error( - `ASSERT CONFIG NEW RELIC: Missing config file entry "Butler.incidentTool.newRelic.reloadTaskAborted.destination.log.sendToAccount.byCustomProperty.customPropertyName"` + `ASSERT CONFIG NEW RELIC: Missing config file entry "Butler.incidentTool.newRelic.reloadTaskAborted.destination.log.sendToAccount.byCustomProperty.customPropertyName"`, ); return false; } @@ -572,19 +572,19 @@ export const configFileStructureAssert = async (config, logger) => { configFileCorrect = false; } else if (typeof header.name !== 'string') { logger.error( - `ASSERT CONFIG: "name" property in "Butler.uptimeMonitor.storeNewRelic.header[${index}]" is not a string` + `ASSERT CONFIG: "name" property in "Butler.uptimeMonitor.storeNewRelic.header[${index}]" is not a string`, ); configFileCorrect = false; } if (!Object.prototype.hasOwnProperty.call(header, 'value')) { logger.error( - `ASSERT CONFIG: Missing "value" property in "Butler.uptimeMonitor.storeNewRelic.header[${index}]"` + `ASSERT CONFIG: Missing "value" property in "Butler.uptimeMonitor.storeNewRelic.header[${index}]"`, ); configFileCorrect = false; } else if (typeof header.value !== 'string') { logger.error( - `ASSERT CONFIG: "value" property in "Butler.uptimeMonitor.storeNewRelic.header[${index}]" is not a string` + `ASSERT CONFIG: "value" property in "Butler.uptimeMonitor.storeNewRelic.header[${index}]" is not a string`, ); configFileCorrect = false; } @@ -599,7 +599,7 @@ export const configFileStructureAssert = async (config, logger) => { if (!config.has('Butler.uptimeMonitor.storeNewRelic.metric.dynamic.butlerMemoryUsage.enable')) { logger.error( - 'ASSERT CONFIG: Missing config file entry "Butler.uptimeMonitor.storeNewRelic.metric.dynamic.butlerMemoryUsage.enable"' + 'ASSERT CONFIG: Missing config file entry "Butler.uptimeMonitor.storeNewRelic.metric.dynamic.butlerMemoryUsage.enable"', ); configFileCorrect = false; } @@ -630,24 +630,24 @@ export const configFileStructureAssert = async (config, logger) => { } else { if (!Object.prototype.hasOwnProperty.call(attribute, 'name')) { logger.error( - `ASSERT CONFIG: Missing "name" property in "Butler.uptimeMonitor.storeNewRelic.attribute.static[${index}]"` + `ASSERT CONFIG: Missing "name" property in "Butler.uptimeMonitor.storeNewRelic.attribute.static[${index}]"`, ); configFileCorrect = false; } else if (typeof attribute.name !== 'string') { logger.error( - `ASSERT CONFIG: "name" property in "Butler.uptimeMonitor.storeNewRelic.attribute.static[${index}]" is not a string` + `ASSERT CONFIG: "name" property in "Butler.uptimeMonitor.storeNewRelic.attribute.static[${index}]" is not a string`, ); configFileCorrect = false; } if (!Object.prototype.hasOwnProperty.call(attribute, 'value')) { logger.error( - `ASSERT CONFIG: Missing "value" property in "Butler.uptimeMonitor.storeNewRelic.attribute.static[${index}]"` + `ASSERT CONFIG: Missing "value" property in "Butler.uptimeMonitor.storeNewRelic.attribute.static[${index}]"`, ); configFileCorrect = false; } else if (typeof attribute.value !== 'string') { logger.error( - `ASSERT CONFIG: "value" property in "Butler.uptimeMonitor.storeNewRelic.attribute.static[${index}]" is not a string` + `ASSERT CONFIG: "value" property in "Butler.uptimeMonitor.storeNewRelic.attribute.static[${index}]" is not a string`, ); configFileCorrect = false; } @@ -662,7 +662,7 @@ export const configFileStructureAssert = async (config, logger) => { if (!config.has('Butler.uptimeMonitor.storeNewRelic.attribute.dynamic.butlerVersion.enable')) { logger.error( - 'ASSERT CONFIG: Missing config file entry "Butler.uptimeMonitor.storeNewRelic.attribute.dynamic.butlerVersion.enable"' + 'ASSERT CONFIG: Missing config file entry "Butler.uptimeMonitor.storeNewRelic.attribute.dynamic.butlerVersion.enable"', ); configFileCorrect = false; } @@ -688,36 +688,36 @@ export const configFileStructureAssert = async (config, logger) => { } else { if (!Object.prototype.hasOwnProperty.call(account, 'accountName')) { logger.error( - `ASSERT CONFIG: Missing "accountName" property in "Butler.thirdPartyToolsCredentials.newRelic[${index}]"` + `ASSERT CONFIG: Missing "accountName" property in "Butler.thirdPartyToolsCredentials.newRelic[${index}]"`, ); configFileCorrect = false; } else if (typeof account.accountName !== 'string') { logger.error( - `ASSERT CONFIG: "accountName" property in "Butler.thirdPartyToolsCredentials.newRelic[${index}]" is not a string` + `ASSERT CONFIG: "accountName" property in "Butler.thirdPartyToolsCredentials.newRelic[${index}]" is not a string`, ); configFileCorrect = false; } if (!Object.prototype.hasOwnProperty.call(account, 'insertApiKey')) { logger.error( - `ASSERT CONFIG: Missing "insertApiKey" property in "Butler.thirdPartyToolsCredentials.newRelic[${index}]"` + `ASSERT CONFIG: Missing "insertApiKey" property in "Butler.thirdPartyToolsCredentials.newRelic[${index}]"`, ); configFileCorrect = false; } else if (typeof account.insertApiKey !== 'string') { logger.error( - `ASSERT CONFIG: "insertApiKey" property in "Butler.thirdPartyToolsCredentials.newRelic[${index}]" is not a string` + `ASSERT CONFIG: "insertApiKey" property in "Butler.thirdPartyToolsCredentials.newRelic[${index}]" is not a string`, ); configFileCorrect = false; } if (!Object.prototype.hasOwnProperty.call(account, 'accountId')) { logger.error( - `ASSERT CONFIG: Missing "accountId" property in "Butler.thirdPartyToolsCredentials.newRelic[${index}]"` + `ASSERT CONFIG: Missing "accountId" property in "Butler.thirdPartyToolsCredentials.newRelic[${index}]"`, ); configFileCorrect = false; } else if (typeof account.accountId !== 'number') { logger.error( - `ASSERT CONFIG: "accountId" property in "Butler.thirdPartyToolsCredentials.newRelic[${index}]" is not a number` + `ASSERT CONFIG: "accountId" property in "Butler.thirdPartyToolsCredentials.newRelic[${index}]" is not a number`, ); configFileCorrect = false; } @@ -810,24 +810,24 @@ export const configFileStructureAssert = async (config, logger) => { } else { if (!Object.prototype.hasOwnProperty.call(tag, 'name')) { logger.error( - `ASSERT CONFIG: Missing "name" property in "Butler.influxDb.reloadTaskFailure.tag.static[${index}]"` + `ASSERT CONFIG: Missing "name" property in "Butler.influxDb.reloadTaskFailure.tag.static[${index}]"`, ); configFileCorrect = false; } else if (typeof tag.name !== 'string') { logger.error( - `ASSERT CONFIG: "name" property in "Butler.influxDb.reloadTaskFailure.tag.static[${index}]" is not a string` + `ASSERT CONFIG: "name" property in "Butler.influxDb.reloadTaskFailure.tag.static[${index}]" is not a string`, ); configFileCorrect = false; } if (!Object.prototype.hasOwnProperty.call(tag, 'value')) { logger.error( - `ASSERT CONFIG: Missing "value" property in "Butler.influxDb.reloadTaskFailure.tag.static[${index}]"` + `ASSERT CONFIG: Missing "value" property in "Butler.influxDb.reloadTaskFailure.tag.static[${index}]"`, ); configFileCorrect = false; } else if (typeof tag.value !== 'string') { logger.error( - `ASSERT CONFIG: "value" property in "Butler.influxDb.reloadTaskFailure.tag.static[${index}]" is not a string` + `ASSERT CONFIG: "value" property in "Butler.influxDb.reloadTaskFailure.tag.static[${index}]" is not a string`, ); configFileCorrect = false; } @@ -895,24 +895,24 @@ export const configFileStructureAssert = async (config, logger) => { } else { if (!Object.prototype.hasOwnProperty.call(tag, 'name')) { logger.error( - `ASSERT CONFIG: Missing "name" property in "Butler.influxDb.reloadTaskSuccess.tag.static[${index}]"` + `ASSERT CONFIG: Missing "name" property in "Butler.influxDb.reloadTaskSuccess.tag.static[${index}]"`, ); configFileCorrect = false; } else if (typeof tag.name !== 'string') { logger.error( - `ASSERT CONFIG: "name" property in "Butler.influxDb.reloadTaskSuccess.tag.static[${index}]" is not a string` + `ASSERT CONFIG: "name" property in "Butler.influxDb.reloadTaskSuccess.tag.static[${index}]" is not a string`, ); configFileCorrect = false; } if (!Object.prototype.hasOwnProperty.call(tag, 'value')) { logger.error( - `ASSERT CONFIG: Missing "value" property in "Butler.influxDb.reloadTaskSuccess.tag.static[${index}]"` + `ASSERT CONFIG: Missing "value" property in "Butler.influxDb.reloadTaskSuccess.tag.static[${index}]"`, ); configFileCorrect = false; } else if (typeof tag.value !== 'string') { logger.error( - `ASSERT CONFIG: "value" property in "Butler.influxDb.reloadTaskSuccess.tag.static[${index}]" is not a string` + `ASSERT CONFIG: "value" property in "Butler.influxDb.reloadTaskSuccess.tag.static[${index}]" is not a string`, ); configFileCorrect = false; } @@ -997,30 +997,30 @@ export const configFileStructureAssert = async (config, logger) => { tagsStatic.forEach((tag, index) => { if (typeof tag !== 'object') { logger.error( - `ASSERT CONFIG: "Butler.qlikSenseVersion.versionMonitor.destination.influxDb.tag.static[${index}]" is not an object` + `ASSERT CONFIG: "Butler.qlikSenseVersion.versionMonitor.destination.influxDb.tag.static[${index}]" is not an object`, ); configFileCorrect = false; } else { if (!Object.prototype.hasOwnProperty.call(tag, 'name')) { logger.error( - `ASSERT CONFIG: Missing "name" property in "Butler.qlikSenseVersion.versionMonitor.destination.influxDb.tag.static[${index}]"` + `ASSERT CONFIG: Missing "name" property in "Butler.qlikSenseVersion.versionMonitor.destination.influxDb.tag.static[${index}]"`, ); configFileCorrect = false; } else if (typeof tag.name !== 'string') { logger.error( - `ASSERT CONFIG: "name" property in "Butler.qlikSenseVersion.versionMonitor.destination.influxDb.tag.static[${index}]" is not a string` + `ASSERT CONFIG: "name" property in "Butler.qlikSenseVersion.versionMonitor.destination.influxDb.tag.static[${index}]" is not a string`, ); configFileCorrect = false; } if (!Object.prototype.hasOwnProperty.call(tag, 'value')) { logger.error( - `ASSERT CONFIG: Missing "value" property in "Butler.qlikSenseVersion.versionMonitor.destination.influxDb.tag.static[${index}]"` + `ASSERT CONFIG: Missing "value" property in "Butler.qlikSenseVersion.versionMonitor.destination.influxDb.tag.static[${index}]"`, ); configFileCorrect = false; } else if (typeof tag.value !== 'string') { logger.error( - `ASSERT CONFIG: "value" property in "Butler.qlikSenseVersion.versionMonitor.destination.influxDb.tag.static[${index}]" is not a string` + `ASSERT CONFIG: "value" property in "Butler.qlikSenseVersion.versionMonitor.destination.influxDb.tag.static[${index}]" is not a string`, ); configFileCorrect = false; } @@ -1051,7 +1051,7 @@ export const configFileStructureAssert = async (config, logger) => { if (!config.has('Butler.qlikSenseLicense.serverLicenseMonitor.destination.influxDb.enable')) { logger.error( - 'ASSERT CONFIG: Missing config file entry "Butler.qlikSenseLicense.serverLicenseMonitor.destination.influxDb.enabled"' + 'ASSERT CONFIG: Missing config file entry "Butler.qlikSenseLicense.serverLicenseMonitor.destination.influxDb.enabled"', ); configFileCorrect = false; } @@ -1067,37 +1067,37 @@ export const configFileStructureAssert = async (config, logger) => { if (tagsStatic) { if (!Array.isArray(tagsStatic)) { logger.error( - 'ASSERT CONFIG: "Butler.qlikSenseLicense.serverLicenseMonitor.destination.influxDb.tag.static" is not an array' + 'ASSERT CONFIG: "Butler.qlikSenseLicense.serverLicenseMonitor.destination.influxDb.tag.static" is not an array', ); configFileCorrect = false; } else { tagsStatic.forEach((tag, index) => { if (typeof tag !== 'object') { logger.error( - `ASSERT CONFIG: "Butler.qlikSenseLicense.serverLicenseMonitor.destination.influxDb.tag.static[${index}]" is not an object` + `ASSERT CONFIG: "Butler.qlikSenseLicense.serverLicenseMonitor.destination.influxDb.tag.static[${index}]" is not an object`, ); configFileCorrect = false; } else { if (!Object.prototype.hasOwnProperty.call(tag, 'name')) { logger.error( - `ASSERT CONFIG: Missing "name" property in "Butler.qlikSenseLicense.serverLicenseMonitor.destination.influxDb.tag.static[${index}]"` + `ASSERT CONFIG: Missing "name" property in "Butler.qlikSenseLicense.serverLicenseMonitor.destination.influxDb.tag.static[${index}]"`, ); configFileCorrect = false; } else if (typeof tag.name !== 'string') { logger.error( - `ASSERT CONFIG: "name" property in "Butler.qlikSenseLicense.serverLicenseMonitor.destination.influxDb.tag.static[${index}]" is not a string` + `ASSERT CONFIG: "name" property in "Butler.qlikSenseLicense.serverLicenseMonitor.destination.influxDb.tag.static[${index}]" is not a string`, ); configFileCorrect = false; } if (!Object.prototype.hasOwnProperty.call(tag, 'value')) { logger.error( - `ASSERT CONFIG: Missing "value" property in "Butler.qlikSenseLicense.serverLicenseMonitor.destination.influxDb.tag.static[${index}]"` + `ASSERT CONFIG: Missing "value" property in "Butler.qlikSenseLicense.serverLicenseMonitor.destination.influxDb.tag.static[${index}]"`, ); configFileCorrect = false; } else if (typeof tag.value !== 'string') { logger.error( - `ASSERT CONFIG: "value" property in "Butler.qlikSenseLicense.serverLicenseMonitor.destination.influxDb.tag.static[${index}]" is not a string` + `ASSERT CONFIG: "value" property in "Butler.qlikSenseLicense.serverLicenseMonitor.destination.influxDb.tag.static[${index}]" is not a string`, ); configFileCorrect = false; } @@ -1107,7 +1107,7 @@ export const configFileStructureAssert = async (config, logger) => { } } else { logger.error( - 'ASSERT CONFIG: Missing config file entry "Butler.qlikSenseLicense.serverLicenseMonitor.destination.influxDb.tag.static"' + 'ASSERT CONFIG: Missing config file entry "Butler.qlikSenseLicense.serverLicenseMonitor.destination.influxDb.tag.static"', ); configFileCorrect = false; } @@ -1119,14 +1119,14 @@ export const configFileStructureAssert = async (config, logger) => { if (!config.has('Butler.qlikSenseLicense.serverLicenseMonitor.destination.mqtt.sendRecurring.enable')) { logger.error( - 'ASSERT CONFIG: Missing config file entry "Butler.qlikSenseLicense.serverLicenseMonitor.destination.mqtt.sendRecurring.enable"' + 'ASSERT CONFIG: Missing config file entry "Butler.qlikSenseLicense.serverLicenseMonitor.destination.mqtt.sendRecurring.enable"', ); configFileCorrect = false; } if (!config.has('Butler.qlikSenseLicense.serverLicenseMonitor.destination.mqtt.sendAlert.enable')) { logger.error( - 'ASSERT CONFIG: Missing config file entry "Butler.qlikSenseLicense.serverLicenseMonitor.destination.mqtt.sendAlert.enable"' + 'ASSERT CONFIG: Missing config file entry "Butler.qlikSenseLicense.serverLicenseMonitor.destination.mqtt.sendAlert.enable"', ); configFileCorrect = false; } @@ -1138,14 +1138,14 @@ export const configFileStructureAssert = async (config, logger) => { if (!config.has('Butler.qlikSenseLicense.serverLicenseMonitor.destination.webhook.sendRecurring.enable')) { logger.error( - 'ASSERT CONFIG: Missing config file entry "Butler.qlikSenseLicense.serverLicenseMonitor.destination.webhook.sendRecurring.enable"' + 'ASSERT CONFIG: Missing config file entry "Butler.qlikSenseLicense.serverLicenseMonitor.destination.webhook.sendRecurring.enable"', ); configFileCorrect = false; } if (!config.has('Butler.qlikSenseLicense.serverLicenseMonitor.destination.webhook.sendAlert.enable')) { logger.error( - 'ASSERT CONFIG: Missing config file entry "Butler.qlikSenseLicense.serverLicenseMonitor.destination.webhook.sendAlert.enable"' + 'ASSERT CONFIG: Missing config file entry "Butler.qlikSenseLicense.serverLicenseMonitor.destination.webhook.sendAlert.enable"', ); configFileCorrect = false; } @@ -1182,30 +1182,30 @@ export const configFileStructureAssert = async (config, logger) => { tagsStatic.forEach((tag, index) => { if (typeof tag !== 'object') { logger.error( - `ASSERT CONFIG: "Butler.qlikSenseLicense.licenseMonitor.destination.influxDb.tag.static[${index}]" is not an object` + `ASSERT CONFIG: "Butler.qlikSenseLicense.licenseMonitor.destination.influxDb.tag.static[${index}]" is not an object`, ); configFileCorrect = false; } else { if (!Object.prototype.hasOwnProperty.call(tag, 'name')) { logger.error( - `ASSERT CONFIG: Missing "name" property in "Butler.qlikSenseLicense.licenseMonitor.destination.influxDb.tag.static[${index}]"` + `ASSERT CONFIG: Missing "name" property in "Butler.qlikSenseLicense.licenseMonitor.destination.influxDb.tag.static[${index}]"`, ); configFileCorrect = false; } else if (typeof tag.name !== 'string') { logger.error( - `ASSERT CONFIG: "name" property in "Butler.qlikSenseLicense.licenseMonitor.destination.influxDb.tag.static[${index}]" is not a string` + `ASSERT CONFIG: "name" property in "Butler.qlikSenseLicense.licenseMonitor.destination.influxDb.tag.static[${index}]" is not a string`, ); configFileCorrect = false; } if (!Object.prototype.hasOwnProperty.call(tag, 'value')) { logger.error( - `ASSERT CONFIG: Missing "value" property in "Butler.qlikSenseLicense.licenseMonitor.destination.influxDb.tag.static[${index}]"` + `ASSERT CONFIG: Missing "value" property in "Butler.qlikSenseLicense.licenseMonitor.destination.influxDb.tag.static[${index}]"`, ); configFileCorrect = false; } else if (typeof tag.value !== 'string') { logger.error( - `ASSERT CONFIG: "value" property in "Butler.qlikSenseLicense.licenseMonitor.destination.influxDb.tag.static[${index}]" is not a string` + `ASSERT CONFIG: "value" property in "Butler.qlikSenseLicense.licenseMonitor.destination.influxDb.tag.static[${index}]" is not a string`, ); configFileCorrect = false; } @@ -1251,30 +1251,30 @@ export const configFileStructureAssert = async (config, logger) => { neverReleaseUsers.forEach((user, index) => { if (typeof user !== 'object') { logger.error( - `ASSERT CONFIG: "Butler.qlikSenseLicense.licenseRelease.neverRelease.user[${index}]" is not an object` + `ASSERT CONFIG: "Butler.qlikSenseLicense.licenseRelease.neverRelease.user[${index}]" is not an object`, ); configFileCorrect = false; } else { if (!Object.prototype.hasOwnProperty.call(user, 'userId')) { logger.error( - `ASSERT CONFIG: Missing "userId" property in "Butler.qlikSenseLicense.licenseRelease.neverRelease.user[${index}]"` + `ASSERT CONFIG: Missing "userId" property in "Butler.qlikSenseLicense.licenseRelease.neverRelease.user[${index}]"`, ); configFileCorrect = false; } else if (typeof user.userId !== 'string') { logger.error( - `ASSERT CONFIG: "userId" property in "Butler.qlikSenseLicense.licenseRelease.neverRelease.user[${index}]" is not a string` + `ASSERT CONFIG: "userId" property in "Butler.qlikSenseLicense.licenseRelease.neverRelease.user[${index}]" is not a string`, ); configFileCorrect = false; } if (!Object.prototype.hasOwnProperty.call(user, 'userDir')) { logger.error( - `ASSERT CONFIG: Missing "userDir" property in "Butler.qlikSenseLicense.licenseRelease.neverRelease.user[${index}]"` + `ASSERT CONFIG: Missing "userDir" property in "Butler.qlikSenseLicense.licenseRelease.neverRelease.user[${index}]"`, ); configFileCorrect = false; } else if (typeof user.userDir !== 'string') { logger.error( - `ASSERT CONFIG: "userDir" property in "Butler.qlikSenseLicense.licenseRelease.neverRelease.user[${index}]" is not a string` + `ASSERT CONFIG: "userDir" property in "Butler.qlikSenseLicense.licenseRelease.neverRelease.user[${index}]" is not a string`, ); configFileCorrect = false; } @@ -1331,7 +1331,7 @@ export const configFileStructureAssert = async (config, logger) => { neverReleaseUserDirectories.forEach((userDirectory, index) => { if (typeof userDirectory !== 'string') { logger.error( - `ASSERT CONFIG: "Butler.qlikSenseLicense.licenseRelease.neverRelease.userDirectory[${index}]" is not a string` + `ASSERT CONFIG: "Butler.qlikSenseLicense.licenseRelease.neverRelease.userDirectory[${index}]" is not a string`, ); configFileCorrect = false; } @@ -1395,7 +1395,7 @@ export const configFileStructureAssert = async (config, logger) => { configFileCorrect = false; } else if (!['yes', 'no', 'ignore'].includes(removedExternally.toLowerCase())) { logger.error( - 'ASSERT CONFIG: "Butler.qlikSenseLicense.licenseRelease.neverRelease.removedExternally" must be either "Yes" or "No"' + 'ASSERT CONFIG: "Butler.qlikSenseLicense.licenseRelease.neverRelease.removedExternally" must be either "Yes" or "No"', ); configFileCorrect = false; } @@ -1412,7 +1412,7 @@ export const configFileStructureAssert = async (config, logger) => { if (!config.has('Butler.qlikSenseLicense.licenseRelease.licenseType.analyzer.releaseThresholdDays')) { logger.error( - 'ASSERT CONFIG: Missing config file entry "Butler.qlikSenseLicense.licenseRelease.licenseType.analyzer.releaseThresholdDays"' + 'ASSERT CONFIG: Missing config file entry "Butler.qlikSenseLicense.licenseRelease.licenseType.analyzer.releaseThresholdDays"', ); configFileCorrect = false; } @@ -1424,7 +1424,7 @@ export const configFileStructureAssert = async (config, logger) => { if (!config.has('Butler.qlikSenseLicense.licenseRelease.licenseType.professional.releaseThresholdDays')) { logger.error( - 'ASSERT CONFIG: Missing config file entry "Butler.qlikSenseLicense.licenseRelease.licenseType.professional.releaseThresholdDays"' + 'ASSERT CONFIG: Missing config file entry "Butler.qlikSenseLicense.licenseRelease.licenseType.professional.releaseThresholdDays"', ); configFileCorrect = false; } @@ -1445,30 +1445,30 @@ export const configFileStructureAssert = async (config, logger) => { tagsStatic.forEach((tag, index) => { if (typeof tag !== 'object') { logger.error( - `ASSERT CONFIG: "Butler.qlikSenseLicense.licenseRelease.destination.influxDb.tag.static[${index}]" is not an object` + `ASSERT CONFIG: "Butler.qlikSenseLicense.licenseRelease.destination.influxDb.tag.static[${index}]" is not an object`, ); configFileCorrect = false; } else { if (!Object.prototype.hasOwnProperty.call(tag, 'name')) { logger.error( - `ASSERT CONFIG: Missing "name" property in "Butler.qlikSenseLicense.licenseRelease.destination.influxDb.tag.static[${index}]"` + `ASSERT CONFIG: Missing "name" property in "Butler.qlikSenseLicense.licenseRelease.destination.influxDb.tag.static[${index}]"`, ); configFileCorrect = false; } else if (typeof tag.name !== 'string') { logger.error( - `ASSERT CONFIG: "name" property in "Butler.qlikSenseLicense.licenseRelease.destination.influxDb.tag.static[${index}]" is not a string` + `ASSERT CONFIG: "name" property in "Butler.qlikSenseLicense.licenseRelease.destination.influxDb.tag.static[${index}]" is not a string`, ); configFileCorrect = false; } if (!Object.prototype.hasOwnProperty.call(tag, 'value')) { logger.error( - `ASSERT CONFIG: Missing "value" property in "Butler.qlikSenseLicense.licenseRelease.destination.influxDb.tag.static[${index}]"` + `ASSERT CONFIG: Missing "value" property in "Butler.qlikSenseLicense.licenseRelease.destination.influxDb.tag.static[${index}]"`, ); configFileCorrect = false; } else if (typeof tag.value !== 'string') { logger.error( - `ASSERT CONFIG: "value" property in "Butler.qlikSenseLicense.licenseRelease.destination.influxDb.tag.static[${index}]" is not a string` + `ASSERT CONFIG: "value" property in "Butler.qlikSenseLicense.licenseRelease.destination.influxDb.tag.static[${index}]" is not a string`, ); configFileCorrect = false; } @@ -1824,7 +1824,7 @@ export const configFileStructureAssert = async (config, logger) => { if (!config.has('Butler.emailNotification.reloadTaskAborted.appOwnerAlert.includeOwner.includeAll')) { logger.error( - 'ASSERT CONFIG: Missing config file entry "Butler.emailNotification.reloadTaskAborted.appOwnerAlert.includeOwner.includeAll"' + 'ASSERT CONFIG: Missing config file entry "Butler.emailNotification.reloadTaskAborted.appOwnerAlert.includeOwner.includeAll"', ); configFileCorrect = false; } @@ -1845,30 +1845,30 @@ export const configFileStructureAssert = async (config, logger) => { users.forEach((user, index) => { if (typeof user !== 'object') { logger.error( - `ASSERT CONFIG: "Butler.emailNotification.reloadTaskAborted.appOwnerAlert.includeOwner.user[${index}]" is not an object` + `ASSERT CONFIG: "Butler.emailNotification.reloadTaskAborted.appOwnerAlert.includeOwner.user[${index}]" is not an object`, ); configFileCorrect = false; } else { if (!Object.prototype.hasOwnProperty.call(user, 'directory')) { logger.error( - `ASSERT CONFIG: Missing "directory" property in "Butler.emailNotification.reloadTaskAborted.appOwnerAlert.includeOwner.user[${index}]"` + `ASSERT CONFIG: Missing "directory" property in "Butler.emailNotification.reloadTaskAborted.appOwnerAlert.includeOwner.user[${index}]"`, ); configFileCorrect = false; } else if (typeof user.directory !== 'string') { logger.error( - `ASSERT CONFIG: "directory" property in "Butler.emailNotification.reloadTaskAborted.appOwnerAlert.includeOwner.user[${index}]" is not a string` + `ASSERT CONFIG: "directory" property in "Butler.emailNotification.reloadTaskAborted.appOwnerAlert.includeOwner.user[${index}]" is not a string`, ); configFileCorrect = false; } if (!Object.prototype.hasOwnProperty.call(user, 'userId')) { logger.error( - `ASSERT CONFIG: Missing "userId" property in "Butler.emailNotification.reloadTaskAborted.appOwnerAlert.includeOwner.user[${index}]"` + `ASSERT CONFIG: Missing "userId" property in "Butler.emailNotification.reloadTaskAborted.appOwnerAlert.includeOwner.user[${index}]"`, ); configFileCorrect = false; } else if (typeof user.userId !== 'string') { logger.error( - `ASSERT CONFIG: "userId" property in "Butler.emailNotification.reloadTaskAborted.appOwnerAlert.includeOwner.user[${index}]" is not a string` + `ASSERT CONFIG: "userId" property in "Butler.emailNotification.reloadTaskAborted.appOwnerAlert.includeOwner.user[${index}]" is not a string`, ); configFileCorrect = false; } @@ -1878,7 +1878,7 @@ export const configFileStructureAssert = async (config, logger) => { } } else { logger.error( - 'ASSERT CONFIG: Missing config file entry "Butler.emailNotification.reloadTaskAborted.appOwnerAlert.includeOwner.user"' + 'ASSERT CONFIG: Missing config file entry "Butler.emailNotification.reloadTaskAborted.appOwnerAlert.includeOwner.user"', ); configFileCorrect = false; } @@ -1899,30 +1899,30 @@ export const configFileStructureAssert = async (config, logger) => { users.forEach((user, index) => { if (typeof user !== 'object') { logger.error( - `ASSERT CONFIG: "Butler.emailNotification.reloadTaskAborted.appOwnerAlert.excludeOwner.user[${index}]" is not an object` + `ASSERT CONFIG: "Butler.emailNotification.reloadTaskAborted.appOwnerAlert.excludeOwner.user[${index}]" is not an object`, ); configFileCorrect = false; } else { if (!Object.prototype.hasOwnProperty.call(user, 'directory')) { logger.error( - `ASSERT CONFIG: Missing "directory" property in "Butler.emailNotification.reloadTaskAborted.appOwnerAlert.excludeOwner.user[${index}]"` + `ASSERT CONFIG: Missing "directory" property in "Butler.emailNotification.reloadTaskAborted.appOwnerAlert.excludeOwner.user[${index}]"`, ); configFileCorrect = false; } else if (typeof user.directory !== 'string') { logger.error( - `ASSERT CONFIG: "directory" property in "Butler.emailNotification.reloadTaskAborted.appOwnerAlert.excludeOwner.user[${index}]" is not a string` + `ASSERT CONFIG: "directory" property in "Butler.emailNotification.reloadTaskAborted.appOwnerAlert.excludeOwner.user[${index}]" is not a string`, ); configFileCorrect = false; } if (!Object.prototype.hasOwnProperty.call(user, 'userId')) { logger.error( - `ASSERT CONFIG: Missing "userId" property in "Butler.emailNotification.reloadTaskAborted.appOwnerAlert.excludeOwner.user[${index}]"` + `ASSERT CONFIG: Missing "userId" property in "Butler.emailNotification.reloadTaskAborted.appOwnerAlert.excludeOwner.user[${index}]"`, ); configFileCorrect = false; } else if (typeof user.userId !== 'string') { logger.error( - `ASSERT CONFIG: "userId" property in "Butler.emailNotification.reloadTaskAborted.appOwnerAlert.excludeOwner.user[${index}]" is not a string` + `ASSERT CONFIG: "userId" property in "Butler.emailNotification.reloadTaskAborted.appOwnerAlert.excludeOwner.user[${index}]" is not a string`, ); configFileCorrect = false; } @@ -1932,35 +1932,35 @@ export const configFileStructureAssert = async (config, logger) => { } } else { logger.error( - 'ASSERT CONFIG: Missing config file entry "Butler.emailNotification.reloadTaskAborted.appOwnerAlert.excludeOwner.user"' + 'ASSERT CONFIG: Missing config file entry "Butler.emailNotification.reloadTaskAborted.appOwnerAlert.excludeOwner.user"', ); configFileCorrect = false; } if (!config.has('Butler.emailNotification.reloadTaskAborted.alertEnableByCustomProperty.enable')) { logger.error( - 'ASSERT CONFIG: Missing config file entry "Butler.emailNotification.reloadTaskAborted.alertEnableByCustomProperty.enable"' + 'ASSERT CONFIG: Missing config file entry "Butler.emailNotification.reloadTaskAborted.alertEnableByCustomProperty.enable"', ); configFileCorrect = false; } if (!config.has('Butler.emailNotification.reloadTaskAborted.alertEnableByCustomProperty.customPropertyName')) { logger.error( - 'ASSERT CONFIG: Missing config file entry "Butler.emailNotification.reloadTaskAborted.alertEnableByCustomProperty.customPropertyName"' + 'ASSERT CONFIG: Missing config file entry "Butler.emailNotification.reloadTaskAborted.alertEnableByCustomProperty.customPropertyName"', ); configFileCorrect = false; } if (!config.has('Butler.emailNotification.reloadTaskAborted.alertEnableByCustomProperty.enabledValue')) { logger.error( - 'ASSERT CONFIG: Missing config file entry "Butler.emailNotification.reloadTaskAborted.alertEnableByCustomProperty.enabledValue"' + 'ASSERT CONFIG: Missing config file entry "Butler.emailNotification.reloadTaskAborted.alertEnableByCustomProperty.enabledValue"', ); configFileCorrect = false; } if (!config.has('Butler.emailNotification.reloadTaskAborted.alertEnabledByEmailAddress.customPropertyName')) { logger.error( - 'ASSERT CONFIG: Missing config file entry "Butler.emailNotification.reloadTaskAborted.alertEnabledByEmailAddress.customPropertyName"' + 'ASSERT CONFIG: Missing config file entry "Butler.emailNotification.reloadTaskAborted.alertEnabledByEmailAddress.customPropertyName"', ); configFileCorrect = false; } @@ -2040,7 +2040,7 @@ export const configFileStructureAssert = async (config, logger) => { if (!config.has('Butler.emailNotification.reloadTaskFailure.appOwnerAlert.includeOwner.includeAll')) { logger.error( - 'ASSERT CONFIG: Missing config file entry "Butler.emailNotification.reloadTaskFailure.appOwnerAlert.includeOwner.includeAll"' + 'ASSERT CONFIG: Missing config file entry "Butler.emailNotification.reloadTaskFailure.appOwnerAlert.includeOwner.includeAll"', ); configFileCorrect = false; } @@ -2061,30 +2061,30 @@ export const configFileStructureAssert = async (config, logger) => { users.forEach((user, index) => { if (typeof user !== 'object') { logger.error( - `ASSERT CONFIG: "Butler.emailNotification.reloadTaskFailure.appOwnerAlert.includeOwner.user[${index}]" is not an object` + `ASSERT CONFIG: "Butler.emailNotification.reloadTaskFailure.appOwnerAlert.includeOwner.user[${index}]" is not an object`, ); configFileCorrect = false; } else { if (!Object.prototype.hasOwnProperty.call(user, 'directory')) { logger.error( - `ASSERT CONFIG: Missing "directory" property in "Butler.emailNotification.reloadTaskFailure.appOwnerAlert.includeOwner.user[${index}]"` + `ASSERT CONFIG: Missing "directory" property in "Butler.emailNotification.reloadTaskFailure.appOwnerAlert.includeOwner.user[${index}]"`, ); configFileCorrect = false; } else if (typeof user.directory !== 'string') { logger.error( - `ASSERT CONFIG: "directory" property in "Butler.emailNotification.reloadTaskFailure.appOwnerAlert.includeOwner.user[${index}]" is not a string` + `ASSERT CONFIG: "directory" property in "Butler.emailNotification.reloadTaskFailure.appOwnerAlert.includeOwner.user[${index}]" is not a string`, ); configFileCorrect = false; } if (!Object.prototype.hasOwnProperty.call(user, 'userId')) { logger.error( - `ASSERT CONFIG: Missing "userId" property in "Butler.emailNotification.reloadTaskFailure.appOwnerAlert.includeOwner.user[${index}]"` + `ASSERT CONFIG: Missing "userId" property in "Butler.emailNotification.reloadTaskFailure.appOwnerAlert.includeOwner.user[${index}]"`, ); configFileCorrect = false; } else if (typeof user.userId !== 'string') { logger.error( - `ASSERT CONFIG: "userId" property in "Butler.emailNotification.reloadTaskFailure.appOwnerAlert.includeOwner.user[${index}]" is not a string` + `ASSERT CONFIG: "userId" property in "Butler.emailNotification.reloadTaskFailure.appOwnerAlert.includeOwner.user[${index}]" is not a string`, ); configFileCorrect = false; } @@ -2094,7 +2094,7 @@ export const configFileStructureAssert = async (config, logger) => { } } else { logger.error( - 'ASSERT CONFIG: Missing config file entry "Butler.emailNotification.reloadTaskFailure.appOwnerAlert.includeOwner.user"' + 'ASSERT CONFIG: Missing config file entry "Butler.emailNotification.reloadTaskFailure.appOwnerAlert.includeOwner.user"', ); configFileCorrect = false; } @@ -2115,30 +2115,30 @@ export const configFileStructureAssert = async (config, logger) => { users.forEach((user, index) => { if (typeof user !== 'object') { logger.error( - `ASSERT CONFIG: "Butler.emailNotification.reloadTaskFailure.appOwnerAlert.excludeOwner.user[${index}]" is not an object` + `ASSERT CONFIG: "Butler.emailNotification.reloadTaskFailure.appOwnerAlert.excludeOwner.user[${index}]" is not an object`, ); configFileCorrect = false; } else { if (!Object.prototype.hasOwnProperty.call(user, 'directory')) { logger.error( - `ASSERT CONFIG: Missing "directory" property in "Butler.emailNotification.reloadTaskFailure.appOwnerAlert.excludeOwner.user[${index}]"` + `ASSERT CONFIG: Missing "directory" property in "Butler.emailNotification.reloadTaskFailure.appOwnerAlert.excludeOwner.user[${index}]"`, ); configFileCorrect = false; } else if (typeof user.directory !== 'string') { logger.error( - `ASSERT CONFIG: "directory" property in "Butler.emailNotification.reloadTaskFailure.appOwnerAlert.excludeOwner.user[${index}]" is not a string` + `ASSERT CONFIG: "directory" property in "Butler.emailNotification.reloadTaskFailure.appOwnerAlert.excludeOwner.user[${index}]" is not a string`, ); configFileCorrect = false; } if (!Object.prototype.hasOwnProperty.call(user, 'userId')) { logger.error( - `ASSERT CONFIG: Missing "userId" property in "Butler.emailNotification.reloadTaskFailure.appOwnerAlert.excludeOwner.user[${index}]"` + `ASSERT CONFIG: Missing "userId" property in "Butler.emailNotification.reloadTaskFailure.appOwnerAlert.excludeOwner.user[${index}]"`, ); configFileCorrect = false; } else if (typeof user.userId !== 'string') { logger.error( - `ASSERT CONFIG: "userId" property in "Butler.emailNotification.reloadTaskFailure.appOwnerAlert.excludeOwner.user[${index}]" is not a string` + `ASSERT CONFIG: "userId" property in "Butler.emailNotification.reloadTaskFailure.appOwnerAlert.excludeOwner.user[${index}]" is not a string`, ); configFileCorrect = false; } @@ -2148,35 +2148,35 @@ export const configFileStructureAssert = async (config, logger) => { } } else { logger.error( - 'ASSERT CONFIG: Missing config file entry "Butler.emailNotification.reloadTaskFailure.appOwnerAlert.excludeOwner.user"' + 'ASSERT CONFIG: Missing config file entry "Butler.emailNotification.reloadTaskFailure.appOwnerAlert.excludeOwner.user"', ); configFileCorrect = false; } if (!config.has('Butler.emailNotification.reloadTaskFailure.alertEnableByCustomProperty.enable')) { logger.error( - 'ASSERT CONFIG: Missing config file entry "Butler.emailNotification.reloadTaskFailure.alertEnableByCustomProperty.enable"' + 'ASSERT CONFIG: Missing config file entry "Butler.emailNotification.reloadTaskFailure.alertEnableByCustomProperty.enable"', ); configFileCorrect = false; } if (!config.has('Butler.emailNotification.reloadTaskFailure.alertEnableByCustomProperty.customPropertyName')) { logger.error( - 'ASSERT CONFIG: Missing config file entry "Butler.emailNotification.reloadTaskFailure.alertEnableByCustomProperty.customPropertyName"' + 'ASSERT CONFIG: Missing config file entry "Butler.emailNotification.reloadTaskFailure.alertEnableByCustomProperty.customPropertyName"', ); configFileCorrect = false; } if (!config.has('Butler.emailNotification.reloadTaskFailure.alertEnableByCustomProperty.enabledValue')) { logger.error( - 'ASSERT CONFIG: Missing config file entry "Butler.emailNotification.reloadTaskFailure.alertEnableByCustomProperty.enabledValue"' + 'ASSERT CONFIG: Missing config file entry "Butler.emailNotification.reloadTaskFailure.alertEnableByCustomProperty.enabledValue"', ); configFileCorrect = false; } if (!config.has('Butler.emailNotification.reloadTaskFailure.alertEnabledByEmailAddress.customPropertyName')) { logger.error( - 'ASSERT CONFIG: Missing config file entry "Butler.emailNotification.reloadTaskFailure.alertEnabledByEmailAddress.customPropertyName"' + 'ASSERT CONFIG: Missing config file entry "Butler.emailNotification.reloadTaskFailure.alertEnabledByEmailAddress.customPropertyName"', ); configFileCorrect = false; } @@ -2448,7 +2448,7 @@ export const configFileStructureAssert = async (config, logger) => { appIds.forEach((appId, index) => { if (typeof appId !== 'string') { logger.error( - `ASSERT CONFIG: "Butler.incidentTool.signl4.reloadTaskFailure.includeApp.appId[${index}]" is not a string` + `ASSERT CONFIG: "Butler.incidentTool.signl4.reloadTaskFailure.includeApp.appId[${index}]" is not a string`, ); configFileCorrect = false; } @@ -2543,21 +2543,21 @@ export const configFileStructureAssert = async (config, logger) => { if (!config.has('Butler.incidentTool.newRelic.reloadTaskFailure.destination.event.sendToAccount.byCustomProperty.enable')) { logger.error( - 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.reloadTaskFailure.destination.event.sendToAccount.byCustomProperty.enable"' + 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.reloadTaskFailure.destination.event.sendToAccount.byCustomProperty.enable"', ); configFileCorrect = false; } if (!config.has('Butler.incidentTool.newRelic.reloadTaskFailure.destination.event.sendToAccount.byCustomProperty.customPropertyName')) { logger.error( - 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.reloadTaskFailure.destination.event.sendToAccount.byCustomProperty.customPropertyName"' + 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.reloadTaskFailure.destination.event.sendToAccount.byCustomProperty.customPropertyName"', ); configFileCorrect = false; } if (!config.has('Butler.incidentTool.newRelic.reloadTaskFailure.destination.event.sendToAccount.always.enable')) { logger.error( - 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.reloadTaskFailure.destination.event.sendToAccount.always.enable"' + 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.reloadTaskFailure.destination.event.sendToAccount.always.enable"', ); configFileCorrect = false; } @@ -2570,14 +2570,14 @@ export const configFileStructureAssert = async (config, logger) => { if (accounts) { if (!Array.isArray(accounts)) { logger.error( - 'ASSERT CONFIG: "Butler.incidentTool.newRelic.reloadTaskFailure.destination.event.sendToAccount.always.account" is not an array' + 'ASSERT CONFIG: "Butler.incidentTool.newRelic.reloadTaskFailure.destination.event.sendToAccount.always.account" is not an array', ); configFileCorrect = false; } else { accounts.forEach((account, index) => { if (typeof account !== 'string') { logger.error( - `ASSERT CONFIG: "Butler.incidentTool.newRelic.reloadTaskFailure.destination.event.sendToAccount.always.account[${index}]" is not a string` + `ASSERT CONFIG: "Butler.incidentTool.newRelic.reloadTaskFailure.destination.event.sendToAccount.always.account[${index}]" is not a string`, ); configFileCorrect = false; } @@ -2586,7 +2586,7 @@ export const configFileStructureAssert = async (config, logger) => { } } else { logger.error( - 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.reloadTaskFailure.destination.event.sendToAccount.always.account"' + 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.reloadTaskFailure.destination.event.sendToAccount.always.account"', ); configFileCorrect = false; } @@ -2602,37 +2602,37 @@ export const configFileStructureAssert = async (config, logger) => { if (attributes) { if (!Array.isArray(attributes)) { logger.error( - 'ASSERT CONFIG: "Butler.incidentTool.newRelic.reloadTaskFailure.destination.event.attribute.static" is not an array' + 'ASSERT CONFIG: "Butler.incidentTool.newRelic.reloadTaskFailure.destination.event.attribute.static" is not an array', ); configFileCorrect = false; } else { attributes.forEach((attribute, index) => { if (typeof attribute !== 'object') { logger.error( - `ASSERT CONFIG: "Butler.incidentTool.newRelic.reloadTaskFailure.destination.event.attribute.static[${index}]" is not an object` + `ASSERT CONFIG: "Butler.incidentTool.newRelic.reloadTaskFailure.destination.event.attribute.static[${index}]" is not an object`, ); configFileCorrect = false; } else { if (!Object.prototype.hasOwnProperty.call(attribute, 'name')) { logger.error( - `ASSERT CONFIG: Missing "name" property in "Butler.incidentTool.newRelic.reloadTaskFailure.destination.event.attribute.static[${index}]"` + `ASSERT CONFIG: Missing "name" property in "Butler.incidentTool.newRelic.reloadTaskFailure.destination.event.attribute.static[${index}]"`, ); configFileCorrect = false; } else if (typeof attribute.name !== 'string') { logger.error( - `ASSERT CONFIG: "name" property in "Butler.incidentTool.newRelic.reloadTaskFailure.destination.event.attribute.static[${index}]" is not a string` + `ASSERT CONFIG: "name" property in "Butler.incidentTool.newRelic.reloadTaskFailure.destination.event.attribute.static[${index}]" is not a string`, ); configFileCorrect = false; } if (!Object.prototype.hasOwnProperty.call(attribute, 'value')) { logger.error( - `ASSERT CONFIG: Missing "value" property in "Butler.incidentTool.newRelic.reloadTaskFailure.destination.event.attribute.static[${index}]"` + `ASSERT CONFIG: Missing "value" property in "Butler.incidentTool.newRelic.reloadTaskFailure.destination.event.attribute.static[${index}]"`, ); configFileCorrect = false; } else if (typeof attribute.value !== 'string') { logger.error( - `ASSERT CONFIG: "value" property in "Butler.incidentTool.newRelic.reloadTaskFailure.destination.event.attribute.static[${index}]" is not a string` + `ASSERT CONFIG: "value" property in "Butler.incidentTool.newRelic.reloadTaskFailure.destination.event.attribute.static[${index}]" is not a string`, ); configFileCorrect = false; } @@ -2642,21 +2642,21 @@ export const configFileStructureAssert = async (config, logger) => { } } else { logger.error( - 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.reloadTaskFailure.destination.event.attribute.static"' + 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.reloadTaskFailure.destination.event.attribute.static"', ); configFileCorrect = false; } if (!config.has('Butler.incidentTool.newRelic.reloadTaskFailure.destination.event.attribute.dynamic.useAppTags')) { logger.error( - 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.reloadTaskFailure.destination.event.attribute.dynamic.useAppTags"' + 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.reloadTaskFailure.destination.event.attribute.dynamic.useAppTags"', ); configFileCorrect = false; } if (!config.has('Butler.incidentTool.newRelic.reloadTaskFailure.destination.event.attribute.dynamic.useTaskTags')) { logger.error( - 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.reloadTaskFailure.destination.event.attribute.dynamic.useTaskTags"' + 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.reloadTaskFailure.destination.event.attribute.dynamic.useTaskTags"', ); configFileCorrect = false; } @@ -2668,28 +2668,28 @@ export const configFileStructureAssert = async (config, logger) => { if (!config.has('Butler.incidentTool.newRelic.reloadTaskFailure.destination.log.tailScriptLogLines')) { logger.error( - 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.reloadTaskFailure.destination.log.tailScriptLogLines"' + 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.reloadTaskFailure.destination.log.tailScriptLogLines"', ); configFileCorrect = false; } if (!config.has('Butler.incidentTool.newRelic.reloadTaskFailure.destination.log.sendToAccount.byCustomProperty.enable')) { logger.error( - 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.reloadTaskFailure.destination.log.sendToAccount.byCustomProperty.enable"' + 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.reloadTaskFailure.destination.log.sendToAccount.byCustomProperty.enable"', ); configFileCorrect = false; } if (!config.has('Butler.incidentTool.newRelic.reloadTaskFailure.destination.log.sendToAccount.byCustomProperty.customPropertyName')) { logger.error( - 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.reloadTaskFailure.destination.log.sendToAccount.byCustomProperty.customPropertyName"' + 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.reloadTaskFailure.destination.log.sendToAccount.byCustomProperty.customPropertyName"', ); configFileCorrect = false; } if (!config.has('Butler.incidentTool.newRelic.reloadTaskFailure.destination.log.sendToAccount.always.enable')) { logger.error( - 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.reloadTaskFailure.destination.log.sendToAccount.always.enable"' + 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.reloadTaskFailure.destination.log.sendToAccount.always.enable"', ); configFileCorrect = false; } @@ -2702,14 +2702,14 @@ export const configFileStructureAssert = async (config, logger) => { if (accounts) { if (!Array.isArray(accounts)) { logger.error( - 'ASSERT CONFIG: "Butler.incidentTool.newRelic.reloadTaskFailure.destination.log.sendToAccount.always.account" is not an array' + 'ASSERT CONFIG: "Butler.incidentTool.newRelic.reloadTaskFailure.destination.log.sendToAccount.always.account" is not an array', ); configFileCorrect = false; } else { accounts.forEach((account, index) => { if (typeof account !== 'string') { logger.error( - `ASSERT CONFIG: "Butler.incidentTool.newRelic.reloadTaskFailure.destination.log.sendToAccount.always.account[${index}]" is not a string` + `ASSERT CONFIG: "Butler.incidentTool.newRelic.reloadTaskFailure.destination.log.sendToAccount.always.account[${index}]" is not a string`, ); configFileCorrect = false; } @@ -2718,7 +2718,7 @@ export const configFileStructureAssert = async (config, logger) => { } } else { logger.error( - 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.reloadTaskFailure.destination.log.sendToAccount.always.account"' + 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.reloadTaskFailure.destination.log.sendToAccount.always.account"', ); configFileCorrect = false; } @@ -2734,37 +2734,37 @@ export const configFileStructureAssert = async (config, logger) => { if (attributes) { if (!Array.isArray(attributes)) { logger.error( - 'ASSERT CONFIG: "Butler.incidentTool.newRelic.reloadTaskFailure.destination.log.attribute.static" is not an array' + 'ASSERT CONFIG: "Butler.incidentTool.newRelic.reloadTaskFailure.destination.log.attribute.static" is not an array', ); configFileCorrect = false; } else { attributes.forEach((attribute, index) => { if (typeof attribute !== 'object') { logger.error( - `ASSERT CONFIG: "Butler.incidentTool.newRelic.reloadTaskFailure.destination.log.attribute.static[${index}]" is not an object` + `ASSERT CONFIG: "Butler.incidentTool.newRelic.reloadTaskFailure.destination.log.attribute.static[${index}]" is not an object`, ); configFileCorrect = false; } else { if (!Object.prototype.hasOwnProperty.call(attribute, 'name')) { logger.error( - `ASSERT CONFIG: Missing "name" property in "Butler.incidentTool.newRelic.reloadTaskFailure.destination.log.attribute.static[${index}]"` + `ASSERT CONFIG: Missing "name" property in "Butler.incidentTool.newRelic.reloadTaskFailure.destination.log.attribute.static[${index}]"`, ); configFileCorrect = false; } else if (typeof attribute.name !== 'string') { logger.error( - `ASSERT CONFIG: "name" property in "Butler.incidentTool.newRelic.reloadTaskFailure.destination.log.attribute.static[${index}]" is not a string` + `ASSERT CONFIG: "name" property in "Butler.incidentTool.newRelic.reloadTaskFailure.destination.log.attribute.static[${index}]" is not a string`, ); configFileCorrect = false; } if (!Object.prototype.hasOwnProperty.call(attribute, 'value')) { logger.error( - `ASSERT CONFIG: Missing "value" property in "Butler.incidentTool.newRelic.reloadTaskFailure.destination.log.attribute.static[${index}]"` + `ASSERT CONFIG: Missing "value" property in "Butler.incidentTool.newRelic.reloadTaskFailure.destination.log.attribute.static[${index}]"`, ); configFileCorrect = false; } else if (typeof attribute.value !== 'string') { logger.error( - `ASSERT CONFIG: "value" property in "Butler.incidentTool.newRelic.reloadTaskFailure.destination.log.attribute.static[${index}]" is not a string` + `ASSERT CONFIG: "value" property in "Butler.incidentTool.newRelic.reloadTaskFailure.destination.log.attribute.static[${index}]" is not a string`, ); configFileCorrect = false; } @@ -2774,21 +2774,21 @@ export const configFileStructureAssert = async (config, logger) => { } } else { logger.error( - 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.reloadTaskFailure.destination.log.attribute.static"' + 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.reloadTaskFailure.destination.log.attribute.static"', ); configFileCorrect = false; } if (!config.has('Butler.incidentTool.newRelic.reloadTaskFailure.destination.log.attribute.dynamic.useAppTags')) { logger.error( - 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.reloadTaskFailure.destination.log.attribute.dynamic.useAppTags"' + 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.reloadTaskFailure.destination.log.attribute.dynamic.useAppTags"', ); configFileCorrect = false; } if (!config.has('Butler.incidentTool.newRelic.reloadTaskFailure.destination.log.attribute.dynamic.useTaskTags')) { logger.error( - 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.reloadTaskFailure.destination.log.attribute.dynamic.useTaskTags"' + 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.reloadTaskFailure.destination.log.attribute.dynamic.useTaskTags"', ); configFileCorrect = false; } @@ -2814,30 +2814,30 @@ export const configFileStructureAssert = async (config, logger) => { headers.forEach((header, index) => { if (typeof header !== 'object') { logger.error( - `ASSERT CONFIG: "Butler.incidentTool.newRelic.reloadTaskFailure.sharedSettings.header[${index}]" is not an object` + `ASSERT CONFIG: "Butler.incidentTool.newRelic.reloadTaskFailure.sharedSettings.header[${index}]" is not an object`, ); configFileCorrect = false; } else { if (!Object.prototype.hasOwnProperty.call(header, 'name')) { logger.error( - `ASSERT CONFIG: Missing "name" property in "Butler.incidentTool.newRelic.reloadTaskFailure.sharedSettings.header[${index}]"` + `ASSERT CONFIG: Missing "name" property in "Butler.incidentTool.newRelic.reloadTaskFailure.sharedSettings.header[${index}]"`, ); configFileCorrect = false; } else if (typeof header.name !== 'string') { logger.error( - `ASSERT CONFIG: "name" property in "Butler.incidentTool.newRelic.reloadTaskFailure.sharedSettings.header[${index}]" is not a string` + `ASSERT CONFIG: "name" property in "Butler.incidentTool.newRelic.reloadTaskFailure.sharedSettings.header[${index}]" is not a string`, ); configFileCorrect = false; } if (!Object.prototype.hasOwnProperty.call(header, 'value')) { logger.error( - `ASSERT CONFIG: Missing "value" property in "Butler.incidentTool.newRelic.reloadTaskFailure.sharedSettings.header[${index}]"` + `ASSERT CONFIG: Missing "value" property in "Butler.incidentTool.newRelic.reloadTaskFailure.sharedSettings.header[${index}]"`, ); configFileCorrect = false; } else if (typeof header.value !== 'string') { logger.error( - `ASSERT CONFIG: "value" property in "Butler.incidentTool.newRelic.reloadTaskFailure.sharedSettings.header[${index}]" is not a string` + `ASSERT CONFIG: "value" property in "Butler.incidentTool.newRelic.reloadTaskFailure.sharedSettings.header[${index}]" is not a string`, ); configFileCorrect = false; } @@ -2852,7 +2852,7 @@ export const configFileStructureAssert = async (config, logger) => { if (!config.has('Butler.incidentTool.newRelic.reloadTaskFailure.sharedSettings.attribute.static')) { logger.error( - 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.reloadTaskFailure.sharedSettings.attribute.static"' + 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.reloadTaskFailure.sharedSettings.attribute.static"', ); configFileCorrect = false; } @@ -2864,21 +2864,21 @@ export const configFileStructureAssert = async (config, logger) => { if (!config.has('Butler.incidentTool.newRelic.reloadTaskAborted.destination.event.sendToAccount.byCustomProperty.enable')) { logger.error( - 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.reloadTaskAborted.destination.event.sendToAccount.byCustomProperty.enable"' + 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.reloadTaskAborted.destination.event.sendToAccount.byCustomProperty.enable"', ); configFileCorrect = false; } if (!config.has('Butler.incidentTool.newRelic.reloadTaskAborted.destination.event.sendToAccount.byCustomProperty.customPropertyName')) { logger.error( - 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.reloadTaskAborted.destination.event.sendToAccount.byCustomProperty.customPropertyName"' + 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.reloadTaskAborted.destination.event.sendToAccount.byCustomProperty.customPropertyName"', ); configFileCorrect = false; } if (!config.has('Butler.incidentTool.newRelic.reloadTaskAborted.destination.event.sendToAccount.always.enable')) { logger.error( - 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.reloadTaskAborted.destination.event.sendToAccount.always.enable"' + 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.reloadTaskAborted.destination.event.sendToAccount.always.enable"', ); configFileCorrect = false; } @@ -2891,14 +2891,14 @@ export const configFileStructureAssert = async (config, logger) => { if (accounts) { if (!Array.isArray(accounts)) { logger.error( - 'ASSERT CONFIG: "Butler.incidentTool.newRelic.reloadTaskAborted.destination.event.sendToAccount.always.account" is not an array' + 'ASSERT CONFIG: "Butler.incidentTool.newRelic.reloadTaskAborted.destination.event.sendToAccount.always.account" is not an array', ); configFileCorrect = false; } else { accounts.forEach((account, index) => { if (typeof account !== 'string') { logger.error( - `ASSERT CONFIG: "Butler.incidentTool.newRelic.reloadTaskAborted.destination.event.sendToAccount.always.account[${index}]" is not a string` + `ASSERT CONFIG: "Butler.incidentTool.newRelic.reloadTaskAborted.destination.event.sendToAccount.always.account[${index}]" is not a string`, ); configFileCorrect = false; } @@ -2907,7 +2907,7 @@ export const configFileStructureAssert = async (config, logger) => { } } else { logger.error( - 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.reloadTaskAborted.destination.event.sendToAccount.always.account"' + 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.reloadTaskAborted.destination.event.sendToAccount.always.account"', ); configFileCorrect = false; } @@ -2923,37 +2923,37 @@ export const configFileStructureAssert = async (config, logger) => { if (attributes) { if (!Array.isArray(attributes)) { logger.error( - 'ASSERT CONFIG: "Butler.incidentTool.newRelic.reloadTaskAborted.destination.event.attribute.static" is not an array' + 'ASSERT CONFIG: "Butler.incidentTool.newRelic.reloadTaskAborted.destination.event.attribute.static" is not an array', ); configFileCorrect = false; } else { attributes.forEach((attribute, index) => { if (typeof attribute !== 'object') { logger.error( - `ASSERT CONFIG: "Butler.incidentTool.newRelic.reloadTaskAborted.destination.event.attribute.static[${index}]" is not an object` + `ASSERT CONFIG: "Butler.incidentTool.newRelic.reloadTaskAborted.destination.event.attribute.static[${index}]" is not an object`, ); configFileCorrect = false; } else { if (!Object.prototype.hasOwnProperty.call(attribute, 'name')) { logger.error( - `ASSERT CONFIG: Missing "name" property in "Butler.incidentTool.newRelic.reloadTaskAborted.destination.event.attribute.static[${index}]"` + `ASSERT CONFIG: Missing "name" property in "Butler.incidentTool.newRelic.reloadTaskAborted.destination.event.attribute.static[${index}]"`, ); configFileCorrect = false; } else if (typeof attribute.name !== 'string') { logger.error( - `ASSERT CONFIG: "name" property in "Butler.incidentTool.newRelic.reloadTaskAborted.destination.event.attribute.static[${index}]" is not a string` + `ASSERT CONFIG: "name" property in "Butler.incidentTool.newRelic.reloadTaskAborted.destination.event.attribute.static[${index}]" is not a string`, ); configFileCorrect = false; } if (!Object.prototype.hasOwnProperty.call(attribute, 'value')) { logger.error( - `ASSERT CONFIG: Missing "value" property in "Butler.incidentTool.newRelic.reloadTaskAborted.destination.event.attribute.static[${index}]"` + `ASSERT CONFIG: Missing "value" property in "Butler.incidentTool.newRelic.reloadTaskAborted.destination.event.attribute.static[${index}]"`, ); configFileCorrect = false; } else if (typeof attribute.value !== 'string') { logger.error( - `ASSERT CONFIG: "value" property in "Butler.incidentTool.newRelic.reloadTaskAborted.destination.event.attribute.static[${index}]" is not a string` + `ASSERT CONFIG: "value" property in "Butler.incidentTool.newRelic.reloadTaskAborted.destination.event.attribute.static[${index}]" is not a string`, ); configFileCorrect = false; } @@ -2963,21 +2963,21 @@ export const configFileStructureAssert = async (config, logger) => { } } else { logger.error( - 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.reloadTaskAborted.destination.event.attribute.static"' + 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.reloadTaskAborted.destination.event.attribute.static"', ); configFileCorrect = false; } if (!config.has('Butler.incidentTool.newRelic.reloadTaskAborted.destination.event.attribute.dynamic.useAppTags')) { logger.error( - 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.reloadTaskAborted.destination.event.attribute.dynamic.useAppTags"' + 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.reloadTaskAborted.destination.event.attribute.dynamic.useAppTags"', ); configFileCorrect = false; } if (!config.has('Butler.incidentTool.newRelic.reloadTaskAborted.destination.event.attribute.dynamic.useTaskTags')) { logger.error( - 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.reloadTaskAborted.destination.event.attribute.dynamic.useTaskTags"' + 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.reloadTaskAborted.destination.event.attribute.dynamic.useTaskTags"', ); configFileCorrect = false; } @@ -2989,28 +2989,28 @@ export const configFileStructureAssert = async (config, logger) => { if (!config.has('Butler.incidentTool.newRelic.reloadTaskAborted.destination.log.tailScriptLogLines')) { logger.error( - 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.reloadTaskAborted.destination.log.tailScriptLogLines"' + 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.reloadTaskAborted.destination.log.tailScriptLogLines"', ); configFileCorrect = false; } if (!config.has('Butler.incidentTool.newRelic.reloadTaskAborted.destination.log.sendToAccount.byCustomProperty.enable')) { logger.error( - 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.reloadTaskAborted.destination.log.sendToAccount.byCustomProperty.enable"' + 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.reloadTaskAborted.destination.log.sendToAccount.byCustomProperty.enable"', ); configFileCorrect = false; } if (!config.has('Butler.incidentTool.newRelic.reloadTaskAborted.destination.log.sendToAccount.byCustomProperty.customPropertyName')) { logger.error( - 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.reloadTaskAborted.destination.log.sendToAccount.byCustomProperty.customPropertyName"' + 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.reloadTaskAborted.destination.log.sendToAccount.byCustomProperty.customPropertyName"', ); configFileCorrect = false; } if (!config.has('Butler.incidentTool.newRelic.reloadTaskAborted.destination.log.sendToAccount.always.enable')) { logger.error( - 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.reloadTaskAborted.destination.log.sendToAccount.always.enable"' + 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.reloadTaskAborted.destination.log.sendToAccount.always.enable"', ); configFileCorrect = false; } @@ -3023,14 +3023,14 @@ export const configFileStructureAssert = async (config, logger) => { if (accounts) { if (!Array.isArray(accounts)) { logger.error( - 'ASSERT CONFIG: "Butler.incidentTool.newRelic.reloadTaskAborted.destination.log.sendToAccount.always.account" is not an array' + 'ASSERT CONFIG: "Butler.incidentTool.newRelic.reloadTaskAborted.destination.log.sendToAccount.always.account" is not an array', ); configFileCorrect = false; } else { accounts.forEach((account, index) => { if (typeof account !== 'string') { logger.error( - `ASSERT CONFIG: "Butler.incidentTool.newRelic.reloadTaskAborted.destination.log.sendToAccount.always.account[${index}]" is not a string` + `ASSERT CONFIG: "Butler.incidentTool.newRelic.reloadTaskAborted.destination.log.sendToAccount.always.account[${index}]" is not a string`, ); configFileCorrect = false; } @@ -3039,7 +3039,7 @@ export const configFileStructureAssert = async (config, logger) => { } } else { logger.error( - 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.reloadTaskAborted.destination.log.sendToAccount.always.account"' + 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.reloadTaskAborted.destination.log.sendToAccount.always.account"', ); configFileCorrect = false; } @@ -3055,37 +3055,37 @@ export const configFileStructureAssert = async (config, logger) => { if (attributes) { if (!Array.isArray(attributes)) { logger.error( - 'ASSERT CONFIG: "Butler.incidentTool.newRelic.reloadTaskAborted.destination.log.attribute.static" is not an array' + 'ASSERT CONFIG: "Butler.incidentTool.newRelic.reloadTaskAborted.destination.log.attribute.static" is not an array', ); configFileCorrect = false; } else { attributes.forEach((attribute, index) => { if (typeof attribute !== 'object') { logger.error( - `ASSERT CONFIG: "Butler.incidentTool.newRelic.reloadTaskAborted.destination.log.attribute.static[${index}]" is not an object` + `ASSERT CONFIG: "Butler.incidentTool.newRelic.reloadTaskAborted.destination.log.attribute.static[${index}]" is not an object`, ); configFileCorrect = false; } else { if (!Object.prototype.hasOwnProperty.call(attribute, 'name')) { logger.error( - `ASSERT CONFIG: Missing "name" property in "Butler.incidentTool.newRelic.reloadTaskAborted.destination.log.attribute.static[${index}]"` + `ASSERT CONFIG: Missing "name" property in "Butler.incidentTool.newRelic.reloadTaskAborted.destination.log.attribute.static[${index}]"`, ); configFileCorrect = false; } else if (typeof attribute.name !== 'string') { logger.error( - `ASSERT CONFIG: "name" property in "Butler.incidentTool.newRelic.reloadTaskAborted.destination.log.attribute.static[${index}]" is not a string` + `ASSERT CONFIG: "name" property in "Butler.incidentTool.newRelic.reloadTaskAborted.destination.log.attribute.static[${index}]" is not a string`, ); configFileCorrect = false; } if (!Object.prototype.hasOwnProperty.call(attribute, 'value')) { logger.error( - `ASSERT CONFIG: Missing "value" property in "Butler.incidentTool.newRelic.reloadTaskAborted.destination.log.attribute.static[${index}]"` + `ASSERT CONFIG: Missing "value" property in "Butler.incidentTool.newRelic.reloadTaskAborted.destination.log.attribute.static[${index}]"`, ); configFileCorrect = false; } else if (typeof attribute.value !== 'string') { logger.error( - `ASSERT CONFIG: "value" property in "Butler.incidentTool.newRelic.reloadTaskAborted.destination.log.attribute.static[${index}]" is not a string` + `ASSERT CONFIG: "value" property in "Butler.incidentTool.newRelic.reloadTaskAborted.destination.log.attribute.static[${index}]" is not a string`, ); configFileCorrect = false; } @@ -3095,21 +3095,21 @@ export const configFileStructureAssert = async (config, logger) => { } } else { logger.error( - 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.reloadTaskAborted.destination.log.attribute.static"' + 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.reloadTaskAborted.destination.log.attribute.static"', ); configFileCorrect = false; } if (!config.has('Butler.incidentTool.newRelic.reloadTaskAborted.destination.log.attribute.dynamic.useAppTags')) { logger.error( - 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.reloadTaskAborted.destination.log.attribute.dynamic.useAppTags"' + 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.reloadTaskAborted.destination.log.attribute.dynamic.useAppTags"', ); configFileCorrect = false; } if (!config.has('Butler.incidentTool.newRelic.reloadTaskAborted.destination.log.attribute.dynamic.useTaskTags')) { logger.error( - 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.reloadTaskAborted.destination.log.attribute.dynamic.useTaskTags"' + 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.reloadTaskAborted.destination.log.attribute.dynamic.useTaskTags"', ); configFileCorrect = false; } @@ -3135,30 +3135,30 @@ export const configFileStructureAssert = async (config, logger) => { headers.forEach((header, index) => { if (typeof header !== 'object') { logger.error( - `ASSERT CONFIG: "Butler.incidentTool.newRelic.reloadTaskAborted.sharedSettings.header[${index}]" is not an object` + `ASSERT CONFIG: "Butler.incidentTool.newRelic.reloadTaskAborted.sharedSettings.header[${index}]" is not an object`, ); configFileCorrect = false; } else { if (!Object.prototype.hasOwnProperty.call(header, 'name')) { logger.error( - `ASSERT CONFIG: Missing "name" property in "Butler.incidentTool.newRelic.reloadTaskAborted.sharedSettings.header[${index}]"` + `ASSERT CONFIG: Missing "name" property in "Butler.incidentTool.newRelic.reloadTaskAborted.sharedSettings.header[${index}]"`, ); configFileCorrect = false; } else if (typeof header.name !== 'string') { logger.error( - `ASSERT CONFIG: "name" property in "Butler.incidentTool.newRelic.reloadTaskAborted.sharedSettings.header[${index}]" is not a string` + `ASSERT CONFIG: "name" property in "Butler.incidentTool.newRelic.reloadTaskAborted.sharedSettings.header[${index}]" is not a string`, ); configFileCorrect = false; } if (!Object.prototype.hasOwnProperty.call(header, 'value')) { logger.error( - `ASSERT CONFIG: Missing "value" property in "Butler.incidentTool.newRelic.reloadTaskAborted.sharedSettings.header[${index}]"` + `ASSERT CONFIG: Missing "value" property in "Butler.incidentTool.newRelic.reloadTaskAborted.sharedSettings.header[${index}]"`, ); configFileCorrect = false; } else if (typeof header.value !== 'string') { logger.error( - `ASSERT CONFIG: "value" property in "Butler.incidentTool.newRelic.reloadTaskAborted.sharedSettings.header[${index}]" is not a string` + `ASSERT CONFIG: "value" property in "Butler.incidentTool.newRelic.reloadTaskAborted.sharedSettings.header[${index}]" is not a string`, ); configFileCorrect = false; } @@ -3182,37 +3182,37 @@ export const configFileStructureAssert = async (config, logger) => { if (attributes) { if (!Array.isArray(attributes)) { logger.error( - 'ASSERT CONFIG: "Butler.incidentTool.newRelic.reloadTaskAborted.sharedSettings.attribute.static" is not an array' + 'ASSERT CONFIG: "Butler.incidentTool.newRelic.reloadTaskAborted.sharedSettings.attribute.static" is not an array', ); configFileCorrect = false; } else { attributes.forEach((attribute, index) => { if (typeof attribute !== 'object') { logger.error( - `ASSERT CONFIG: "Butler.incidentTool.newRelic.reloadTaskAborted.sharedSettings.attribute.static[${index}]" is not an object` + `ASSERT CONFIG: "Butler.incidentTool.newRelic.reloadTaskAborted.sharedSettings.attribute.static[${index}]" is not an object`, ); configFileCorrect = false; } else { if (!Object.prototype.hasOwnProperty.call(attribute, 'name')) { logger.error( - `ASSERT CONFIG: Missing "name" property in "Butler.incidentTool.newRelic.reloadTaskAborted.sharedSettings.attribute.static[${index}]"` + `ASSERT CONFIG: Missing "name" property in "Butler.incidentTool.newRelic.reloadTaskAborted.sharedSettings.attribute.static[${index}]"`, ); configFileCorrect = false; } else if (typeof attribute.name !== 'string') { logger.error( - `ASSERT CONFIG: "name" property in "Butler.incidentTool.newRelic.reloadTaskAborted.sharedSettings.attribute.static[${index}]" is not a string` + `ASSERT CONFIG: "name" property in "Butler.incidentTool.newRelic.reloadTaskAborted.sharedSettings.attribute.static[${index}]" is not a string`, ); configFileCorrect = false; } if (!Object.prototype.hasOwnProperty.call(attribute, 'value')) { logger.error( - `ASSERT CONFIG: Missing "value" property in "Butler.incidentTool.newRelic.reloadTaskAborted.sharedSettings.attribute.static[${index}]"` + `ASSERT CONFIG: Missing "value" property in "Butler.incidentTool.newRelic.reloadTaskAborted.sharedSettings.attribute.static[${index}]"`, ); configFileCorrect = false; } else if (typeof attribute.value !== 'string') { logger.error( - `ASSERT CONFIG: "value" property in "Butler.incidentTool.newRelic.reloadTaskAborted.sharedSettings.attribute.static[${index}]" is not a string` + `ASSERT CONFIG: "value" property in "Butler.incidentTool.newRelic.reloadTaskAborted.sharedSettings.attribute.static[${index}]" is not a string`, ); configFileCorrect = false; } @@ -3222,7 +3222,7 @@ export const configFileStructureAssert = async (config, logger) => { } } else { logger.error( - 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.reloadTaskAborted.sharedSettings.attribute.static"' + 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.reloadTaskAborted.sharedSettings.attribute.static"', ); configFileCorrect = false; } @@ -3240,14 +3240,14 @@ export const configFileStructureAssert = async (config, logger) => { if (accounts) { if (!Array.isArray(accounts)) { logger.error( - 'ASSERT CONFIG: "Butler.incidentTool.newRelic.serviceMonitor.destination.event.sendToAccount" is not an array' + 'ASSERT CONFIG: "Butler.incidentTool.newRelic.serviceMonitor.destination.event.sendToAccount" is not an array', ); configFileCorrect = false; } else { accounts.forEach((account, index) => { if (typeof account !== 'string') { logger.error( - `ASSERT CONFIG: "Butler.incidentTool.newRelic.serviceMonitor.destination.event.sendToAccount[${index}]" is not a string` + `ASSERT CONFIG: "Butler.incidentTool.newRelic.serviceMonitor.destination.event.sendToAccount[${index}]" is not a string`, ); configFileCorrect = false; } @@ -3256,7 +3256,7 @@ export const configFileStructureAssert = async (config, logger) => { } } else { logger.error( - 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.serviceMonitor.destination.event.sendToAccount"' + 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.serviceMonitor.destination.event.sendToAccount"', ); configFileCorrect = false; } @@ -3272,37 +3272,37 @@ export const configFileStructureAssert = async (config, logger) => { if (attributes) { if (!Array.isArray(attributes)) { logger.error( - 'ASSERT CONFIG: "Butler.incidentTool.newRelic.serviceMonitor.destination.event.attribute.static" is not an array' + 'ASSERT CONFIG: "Butler.incidentTool.newRelic.serviceMonitor.destination.event.attribute.static" is not an array', ); configFileCorrect = false; } else { attributes.forEach((attribute, index) => { if (typeof attribute !== 'object') { logger.error( - `ASSERT CONFIG: "Butler.incidentTool.newRelic.serviceMonitor.destination.event.attribute.static[${index}]" is not an object` + `ASSERT CONFIG: "Butler.incidentTool.newRelic.serviceMonitor.destination.event.attribute.static[${index}]" is not an object`, ); configFileCorrect = false; } else { if (!Object.prototype.hasOwnProperty.call(attribute, 'name')) { logger.error( - `ASSERT CONFIG: Missing "name" property in "Butler.incidentTool.newRelic.serviceMonitor.destination.event.attribute.static[${index}]"` + `ASSERT CONFIG: Missing "name" property in "Butler.incidentTool.newRelic.serviceMonitor.destination.event.attribute.static[${index}]"`, ); configFileCorrect = false; } else if (typeof attribute.name !== 'string') { logger.error( - `ASSERT CONFIG: "name" property in "Butler.incidentTool.newRelic.serviceMonitor.destination.event.attribute.static[${index}]" is not a string` + `ASSERT CONFIG: "name" property in "Butler.incidentTool.newRelic.serviceMonitor.destination.event.attribute.static[${index}]" is not a string`, ); configFileCorrect = false; } if (!Object.prototype.hasOwnProperty.call(attribute, 'value')) { logger.error( - `ASSERT CONFIG: Missing "value" property in "Butler.incidentTool.newRelic.serviceMonitor.destination.event.attribute.static[${index}]"` + `ASSERT CONFIG: Missing "value" property in "Butler.incidentTool.newRelic.serviceMonitor.destination.event.attribute.static[${index}]"`, ); configFileCorrect = false; } else if (typeof attribute.value !== 'string') { logger.error( - `ASSERT CONFIG: "value" property in "Butler.incidentTool.newRelic.serviceMonitor.destination.event.attribute.static[${index}]" is not a string` + `ASSERT CONFIG: "value" property in "Butler.incidentTool.newRelic.serviceMonitor.destination.event.attribute.static[${index}]" is not a string`, ); configFileCorrect = false; } @@ -3312,35 +3312,35 @@ export const configFileStructureAssert = async (config, logger) => { } } else { logger.error( - 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.serviceMonitor.destination.event.attribute.static"' + 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.serviceMonitor.destination.event.attribute.static"', ); configFileCorrect = false; } if (!config.has('Butler.incidentTool.newRelic.serviceMonitor.destination.event.attribute.dynamic.serviceHost')) { logger.error( - 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.serviceMonitor.destination.event.attribute.dynamic.serviceHost"' + 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.serviceMonitor.destination.event.attribute.dynamic.serviceHost"', ); configFileCorrect = false; } if (!config.has('Butler.incidentTool.newRelic.serviceMonitor.destination.event.attribute.dynamic.serviceName')) { logger.error( - 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.serviceMonitor.destination.event.attribute.dynamic.serviceName"' + 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.serviceMonitor.destination.event.attribute.dynamic.serviceName"', ); configFileCorrect = false; } if (!config.has('Butler.incidentTool.newRelic.serviceMonitor.destination.event.attribute.dynamic.serviceDisplayName')) { logger.error( - 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.serviceMonitor.destination.event.attribute.dynamic.serviceDisplayName"' + 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.serviceMonitor.destination.event.attribute.dynamic.serviceDisplayName"', ); configFileCorrect = false; } if (!config.has('Butler.incidentTool.newRelic.serviceMonitor.destination.event.attribute.dynamic.serviceState')) { logger.error( - 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.serviceMonitor.destination.event.attribute.dynamic.serviceState"' + 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.serviceMonitor.destination.event.attribute.dynamic.serviceState"', ); configFileCorrect = false; } @@ -3363,7 +3363,7 @@ export const configFileStructureAssert = async (config, logger) => { accounts.forEach((account, index) => { if (typeof account !== 'string') { logger.error( - `ASSERT CONFIG: "Butler.incidentTool.newRelic.serviceMonitor.destination.log.sendToAccount[${index}]" is not a string` + `ASSERT CONFIG: "Butler.incidentTool.newRelic.serviceMonitor.destination.log.sendToAccount[${index}]" is not a string`, ); configFileCorrect = false; } @@ -3372,7 +3372,7 @@ export const configFileStructureAssert = async (config, logger) => { } } else { logger.error( - 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.serviceMonitor.destination.log.sendToAccount"' + 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.serviceMonitor.destination.log.sendToAccount"', ); configFileCorrect = false; } @@ -3388,37 +3388,37 @@ export const configFileStructureAssert = async (config, logger) => { if (attributes) { if (!Array.isArray(attributes)) { logger.error( - 'ASSERT CONFIG: "Butler.incidentTool.newRelic.serviceMonitor.destination.log.attribute.static" is not an array' + 'ASSERT CONFIG: "Butler.incidentTool.newRelic.serviceMonitor.destination.log.attribute.static" is not an array', ); configFileCorrect = false; } else { attributes.forEach((attribute, index) => { if (typeof attribute !== 'object') { logger.error( - `ASSERT CONFIG: "Butler.incidentTool.newRelic.serviceMonitor.destination.log.attribute.static[${index}]" is not an object` + `ASSERT CONFIG: "Butler.incidentTool.newRelic.serviceMonitor.destination.log.attribute.static[${index}]" is not an object`, ); configFileCorrect = false; } else { if (!Object.prototype.hasOwnProperty.call(attribute, 'name')) { logger.error( - `ASSERT CONFIG: Missing "name" property in "Butler.incidentTool.newRelic.serviceMonitor.destination.log.attribute.static[${index}]"` + `ASSERT CONFIG: Missing "name" property in "Butler.incidentTool.newRelic.serviceMonitor.destination.log.attribute.static[${index}]"`, ); configFileCorrect = false; } else if (typeof attribute.name !== 'string') { logger.error( - `ASSERT CONFIG: "name" property in "Butler.incidentTool.newRelic.serviceMonitor.destination.log.attribute.static[${index}]" is not a string` + `ASSERT CONFIG: "name" property in "Butler.incidentTool.newRelic.serviceMonitor.destination.log.attribute.static[${index}]" is not a string`, ); configFileCorrect = false; } if (!Object.prototype.hasOwnProperty.call(attribute, 'value')) { logger.error( - `ASSERT CONFIG: Missing "value" property in "Butler.incidentTool.newRelic.serviceMonitor.destination.log.attribute.static[${index}]"` + `ASSERT CONFIG: Missing "value" property in "Butler.incidentTool.newRelic.serviceMonitor.destination.log.attribute.static[${index}]"`, ); configFileCorrect = false; } else if (typeof attribute.value !== 'string') { logger.error( - `ASSERT CONFIG: "value" property in "Butler.incidentTool.newRelic.serviceMonitor.destination.log.attribute.static[${index}]" is not a string` + `ASSERT CONFIG: "value" property in "Butler.incidentTool.newRelic.serviceMonitor.destination.log.attribute.static[${index}]" is not a string`, ); configFileCorrect = false; } @@ -3428,49 +3428,49 @@ export const configFileStructureAssert = async (config, logger) => { } } else { logger.error( - 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.serviceMonitor.destination.log.attribute.static"' + 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.serviceMonitor.destination.log.attribute.static"', ); configFileCorrect = false; } if (!config.has('Butler.incidentTool.newRelic.serviceMonitor.destination.log.attribute.dynamic.serviceHost')) { logger.error( - 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.serviceMonitor.destination.log.attribute.dynamic.serviceHost"' + 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.serviceMonitor.destination.log.attribute.dynamic.serviceHost"', ); configFileCorrect = false; } if (!config.has('Butler.incidentTool.newRelic.serviceMonitor.destination.log.attribute.dynamic.serviceName')) { logger.error( - 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.serviceMonitor.destination.log.attribute.dynamic.serviceName"' + 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.serviceMonitor.destination.log.attribute.dynamic.serviceName"', ); configFileCorrect = false; } if (!config.has('Butler.incidentTool.newRelic.serviceMonitor.destination.log.attribute.dynamic.serviceDisplayName')) { logger.error( - 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.serviceMonitor.destination.log.attribute.dynamic.serviceDisplayName"' + 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.serviceMonitor.destination.log.attribute.dynamic.serviceDisplayName"', ); configFileCorrect = false; } if (!config.has('Butler.incidentTool.newRelic.serviceMonitor.destination.log.attribute.dynamic.serviceState')) { logger.error( - 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.serviceMonitor.destination.log.attribute.dynamic.serviceState"' + 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.serviceMonitor.destination.log.attribute.dynamic.serviceState"', ); configFileCorrect = false; } if (!config.has('Butler.incidentTool.newRelic.serviceMonitor.monitorServiceState.running.enable')) { logger.error( - 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.serviceMonitor.monitorServiceState.running.enable"' + 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.serviceMonitor.monitorServiceState.running.enable"', ); configFileCorrect = false; } if (!config.has('Butler.incidentTool.newRelic.serviceMonitor.monitorServiceState.stopped.enable')) { logger.error( - 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.serviceMonitor.monitorServiceState.stopped.enable"' + 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.serviceMonitor.monitorServiceState.stopped.enable"', ); configFileCorrect = false; } @@ -3496,30 +3496,30 @@ export const configFileStructureAssert = async (config, logger) => { headers.forEach((header, index) => { if (typeof header !== 'object') { logger.error( - `ASSERT CONFIG: "Butler.incidentTool.newRelic.serviceMonitor.sharedSettings.header[${index}]" is not an object` + `ASSERT CONFIG: "Butler.incidentTool.newRelic.serviceMonitor.sharedSettings.header[${index}]" is not an object`, ); configFileCorrect = false; } else { if (!Object.prototype.hasOwnProperty.call(header, 'name')) { logger.error( - `ASSERT CONFIG: Missing "name" property in "Butler.incidentTool.newRelic.serviceMonitor.sharedSettings.header[${index}]"` + `ASSERT CONFIG: Missing "name" property in "Butler.incidentTool.newRelic.serviceMonitor.sharedSettings.header[${index}]"`, ); configFileCorrect = false; } else if (typeof header.name !== 'string') { logger.error( - `ASSERT CONFIG: "name" property in "Butler.incidentTool.newRelic.serviceMonitor.sharedSettings.header[${index}]" is not a string` + `ASSERT CONFIG: "name" property in "Butler.incidentTool.newRelic.serviceMonitor.sharedSettings.header[${index}]" is not a string`, ); configFileCorrect = false; } if (!Object.prototype.hasOwnProperty.call(header, 'value')) { logger.error( - `ASSERT CONFIG: Missing "value" property in "Butler.incidentTool.newRelic.serviceMonitor.sharedSettings.header[${index}]"` + `ASSERT CONFIG: Missing "value" property in "Butler.incidentTool.newRelic.serviceMonitor.sharedSettings.header[${index}]"`, ); configFileCorrect = false; } else if (typeof header.value !== 'string') { logger.error( - `ASSERT CONFIG: "value" property in "Butler.incidentTool.newRelic.serviceMonitor.sharedSettings.header[${index}]" is not a string` + `ASSERT CONFIG: "value" property in "Butler.incidentTool.newRelic.serviceMonitor.sharedSettings.header[${index}]" is not a string`, ); configFileCorrect = false; } @@ -3543,37 +3543,37 @@ export const configFileStructureAssert = async (config, logger) => { if (attributes) { if (!Array.isArray(attributes)) { logger.error( - 'ASSERT CONFIG: "Butler.incidentTool.newRelic.serviceMonitor.sharedSettings.attribute.static" is not an array' + 'ASSERT CONFIG: "Butler.incidentTool.newRelic.serviceMonitor.sharedSettings.attribute.static" is not an array', ); configFileCorrect = false; } else { attributes.forEach((attribute, index) => { if (typeof attribute !== 'object') { logger.error( - `ASSERT CONFIG: "Butler.incidentTool.newRelic.serviceMonitor.sharedSettings.attribute.static[${index}]" is not an object` + `ASSERT CONFIG: "Butler.incidentTool.newRelic.serviceMonitor.sharedSettings.attribute.static[${index}]" is not an object`, ); configFileCorrect = false; } else { if (!Object.prototype.hasOwnProperty.call(attribute, 'name')) { logger.error( - `ASSERT CONFIG: Missing "name" property in "Butler.incidentTool.newRelic.serviceMonitor.sharedSettings.attribute.static[${index}]"` + `ASSERT CONFIG: Missing "name" property in "Butler.incidentTool.newRelic.serviceMonitor.sharedSettings.attribute.static[${index}]"`, ); configFileCorrect = false; } else if (typeof attribute.name !== 'string') { logger.error( - `ASSERT CONFIG: "name" property in "Butler.incidentTool.newRelic.serviceMonitor.sharedSettings.attribute.static[${index}]" is not a string` + `ASSERT CONFIG: "name" property in "Butler.incidentTool.newRelic.serviceMonitor.sharedSettings.attribute.static[${index}]" is not a string`, ); configFileCorrect = false; } if (!Object.prototype.hasOwnProperty.call(attribute, 'value')) { logger.error( - `ASSERT CONFIG: Missing "value" property in "Butler.incidentTool.newRelic.serviceMonitor.sharedSettings.attribute.static[${index}]"` + `ASSERT CONFIG: Missing "value" property in "Butler.incidentTool.newRelic.serviceMonitor.sharedSettings.attribute.static[${index}]"`, ); configFileCorrect = false; } else if (typeof attribute.value !== 'string') { logger.error( - `ASSERT CONFIG: "value" property in "Butler.incidentTool.newRelic.serviceMonitor.sharedSettings.attribute.static[${index}]" is not a string` + `ASSERT CONFIG: "value" property in "Butler.incidentTool.newRelic.serviceMonitor.sharedSettings.attribute.static[${index}]" is not a string`, ); configFileCorrect = false; } @@ -3583,7 +3583,7 @@ export const configFileStructureAssert = async (config, logger) => { } } else { logger.error( - 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.serviceMonitor.sharedSettings.attribute.static"' + 'ASSERT CONFIG: Missing config file entry "Butler.incidentTool.newRelic.serviceMonitor.sharedSettings.attribute.static"', ); configFileCorrect = false; } @@ -3631,48 +3631,48 @@ export const configFileStructureAssert = async (config, logger) => { } else { if (!Object.prototype.hasOwnProperty.call(webhook, 'description')) { logger.error( - `ASSERT CONFIG: Missing property "description" in "Butler.webhookNotification.reloadTaskFailure.webhooks[${index}]"` + `ASSERT CONFIG: Missing property "description" in "Butler.webhookNotification.reloadTaskFailure.webhooks[${index}]"`, ); configFileCorrect = false; } if (!Object.prototype.hasOwnProperty.call(webhook, 'webhookURL')) { logger.error( - `ASSERT CONFIG: Missing property "webhookURL" in "Butler.webhookNotification.reloadTaskFailure.webhooks[${index}]"` + `ASSERT CONFIG: Missing property "webhookURL" in "Butler.webhookNotification.reloadTaskFailure.webhooks[${index}]"`, ); configFileCorrect = false; } if (!Object.prototype.hasOwnProperty.call(webhook, 'httpMethod')) { logger.error( - `ASSERT CONFIG: Missing property "httpMethod" in "Butler.webhookNotification.reloadTaskFailure.webhooks[${index}]"` + `ASSERT CONFIG: Missing property "httpMethod" in "Butler.webhookNotification.reloadTaskFailure.webhooks[${index}]"`, ); configFileCorrect = false; } if (!Object.prototype.hasOwnProperty.call(webhook, 'cert')) { logger.error( - `ASSERT CONFIG: Missing property "cert" in "Butler.webhookNotification.reloadTaskFailure.webhooks[${index}]"` + `ASSERT CONFIG: Missing property "cert" in "Butler.webhookNotification.reloadTaskFailure.webhooks[${index}]"`, ); configFileCorrect = false; } else if (typeof webhook.cert !== 'object') { logger.error( - `ASSERT CONFIG: "Butler.webhookNotification.reloadTaskFailure.webhooks[${index}].cert" must be an object` + `ASSERT CONFIG: "Butler.webhookNotification.reloadTaskFailure.webhooks[${index}].cert" must be an object`, ); configFileCorrect = false; } else { if (!Object.prototype.hasOwnProperty.call(webhook.cert, 'enable')) { logger.error( - `ASSERT CONFIG: Missing property "enable" in "Butler.webhookNotification.reloadTaskFailure.webhooks[${index}].cert"` + `ASSERT CONFIG: Missing property "enable" in "Butler.webhookNotification.reloadTaskFailure.webhooks[${index}].cert"`, ); configFileCorrect = false; } if (!Object.prototype.hasOwnProperty.call(webhook.cert, 'rejectUnauthorized')) { logger.error( - `ASSERT CONFIG: Missing property "rejectUnauthorized" in "Butler.webhookNotification.reloadTaskFailure.webhooks[${index}].cert"` + `ASSERT CONFIG: Missing property "rejectUnauthorized" in "Butler.webhookNotification.reloadTaskFailure.webhooks[${index}].cert"`, ); configFileCorrect = false; } if (!Object.prototype.hasOwnProperty.call(webhook.cert, 'certCA')) { logger.error( - `ASSERT CONFIG: Missing property "certCA" in "Butler.webhookNotification.reloadTaskFailure.webhooks[${index}].cert"` + `ASSERT CONFIG: Missing property "certCA" in "Butler.webhookNotification.reloadTaskFailure.webhooks[${index}].cert"`, ); configFileCorrect = false; } @@ -3722,48 +3722,48 @@ export const configFileStructureAssert = async (config, logger) => { } else { if (!Object.prototype.hasOwnProperty.call(webhook, 'description')) { logger.error( - `ASSERT CONFIG: Missing property "description" in "Butler.webhookNotification.reloadTaskAborted.webhooks[${index}]"` + `ASSERT CONFIG: Missing property "description" in "Butler.webhookNotification.reloadTaskAborted.webhooks[${index}]"`, ); configFileCorrect = false; } if (!Object.prototype.hasOwnProperty.call(webhook, 'webhookURL')) { logger.error( - `ASSERT CONFIG: Missing property "webhookURL" in "Butler.webhookNotification.reloadTaskAborted.webhooks[${index}]"` + `ASSERT CONFIG: Missing property "webhookURL" in "Butler.webhookNotification.reloadTaskAborted.webhooks[${index}]"`, ); configFileCorrect = false; } if (!Object.prototype.hasOwnProperty.call(webhook, 'httpMethod')) { logger.error( - `ASSERT CONFIG: Missing property "httpMethod" in "Butler.webhookNotification.reloadTaskAborted.webhooks[${index}]"` + `ASSERT CONFIG: Missing property "httpMethod" in "Butler.webhookNotification.reloadTaskAborted.webhooks[${index}]"`, ); configFileCorrect = false; } if (!Object.prototype.hasOwnProperty.call(webhook, 'cert')) { logger.error( - `ASSERT CONFIG: Missing property "cert" in "Butler.webhookNotification.reloadTaskAborted.webhooks[${index}]"` + `ASSERT CONFIG: Missing property "cert" in "Butler.webhookNotification.reloadTaskAborted.webhooks[${index}]"`, ); configFileCorrect = false; } else if (typeof webhook.cert !== 'object') { logger.error( - `ASSERT CONFIG: "Butler.webhookNotification.reloadTaskAborted.webhooks[${index}].cert" must be an object` + `ASSERT CONFIG: "Butler.webhookNotification.reloadTaskAborted.webhooks[${index}].cert" must be an object`, ); configFileCorrect = false; } else { if (!Object.prototype.hasOwnProperty.call(webhook.cert, 'enable')) { logger.error( - `ASSERT CONFIG: Missing property "enable" in "Butler.webhookNotification.reloadTaskAborted.webhooks[${index}].cert"` + `ASSERT CONFIG: Missing property "enable" in "Butler.webhookNotification.reloadTaskAborted.webhooks[${index}].cert"`, ); configFileCorrect = false; } if (!Object.prototype.hasOwnProperty.call(webhook.cert, 'rejectUnauthorized')) { logger.error( - `ASSERT CONFIG: Missing property "rejectUnauthorized" in "Butler.webhookNotification.reloadTaskAborted.webhooks[${index}].cert"` + `ASSERT CONFIG: Missing property "rejectUnauthorized" in "Butler.webhookNotification.reloadTaskAborted.webhooks[${index}].cert"`, ); configFileCorrect = false; } if (!Object.prototype.hasOwnProperty.call(webhook.cert, 'certCA')) { logger.error( - `ASSERT CONFIG: Missing property "certCA" in "Butler.webhookNotification.reloadTaskAborted.webhooks[${index}].cert"` + `ASSERT CONFIG: Missing property "certCA" in "Butler.webhookNotification.reloadTaskAborted.webhooks[${index}].cert"`, ); configFileCorrect = false; } @@ -3813,48 +3813,48 @@ export const configFileStructureAssert = async (config, logger) => { } else { if (!Object.prototype.hasOwnProperty.call(webhook, 'description')) { logger.error( - `ASSERT CONFIG: Missing property "description" in "Butler.webhookNotification.serviceMonitor.webhooks[${index}]"` + `ASSERT CONFIG: Missing property "description" in "Butler.webhookNotification.serviceMonitor.webhooks[${index}]"`, ); configFileCorrect = false; } if (!Object.prototype.hasOwnProperty.call(webhook, 'webhookURL')) { logger.error( - `ASSERT CONFIG: Missing property "webhookURL" in "Butler.webhookNotification.serviceMonitor.webhooks[${index}]"` + `ASSERT CONFIG: Missing property "webhookURL" in "Butler.webhookNotification.serviceMonitor.webhooks[${index}]"`, ); configFileCorrect = false; } if (!Object.prototype.hasOwnProperty.call(webhook, 'httpMethod')) { logger.error( - `ASSERT CONFIG: Missing property "httpMethod" in "Butler.webhookNotification.serviceMonitor.webhooks[${index}]"` + `ASSERT CONFIG: Missing property "httpMethod" in "Butler.webhookNotification.serviceMonitor.webhooks[${index}]"`, ); configFileCorrect = false; } if (!Object.prototype.hasOwnProperty.call(webhook, 'cert')) { logger.error( - `ASSERT CONFIG: Missing property "cert" in "Butler.webhookNotification.serviceMonitor.webhooks[${index}]"` + `ASSERT CONFIG: Missing property "cert" in "Butler.webhookNotification.serviceMonitor.webhooks[${index}]"`, ); configFileCorrect = false; } else if (typeof webhook.cert !== 'object') { logger.error( - `ASSERT CONFIG: "Butler.webhookNotification.serviceMonitor.webhooks[${index}].cert" must be an object` + `ASSERT CONFIG: "Butler.webhookNotification.serviceMonitor.webhooks[${index}].cert" must be an object`, ); configFileCorrect = false; } else { if (!Object.prototype.hasOwnProperty.call(webhook.cert, 'enable')) { logger.error( - `ASSERT CONFIG: Missing property "enable" in "Butler.webhookNotification.serviceMonitor.webhooks[${index}].cert"` + `ASSERT CONFIG: Missing property "enable" in "Butler.webhookNotification.serviceMonitor.webhooks[${index}].cert"`, ); configFileCorrect = false; } if (!Object.prototype.hasOwnProperty.call(webhook.cert, 'rejectUnauthorized')) { logger.error( - `ASSERT CONFIG: Missing property "rejectUnauthorized" in "Butler.webhookNotification.serviceMonitor.webhooks[${index}].cert"` + `ASSERT CONFIG: Missing property "rejectUnauthorized" in "Butler.webhookNotification.serviceMonitor.webhooks[${index}].cert"`, ); configFileCorrect = false; } if (!Object.prototype.hasOwnProperty.call(webhook.cert, 'certCA')) { logger.error( - `ASSERT CONFIG: Missing property "certCA" in "Butler.webhookNotification.serviceMonitor.webhooks[${index}].cert"` + `ASSERT CONFIG: Missing property "certCA" in "Butler.webhookNotification.serviceMonitor.webhooks[${index}].cert"`, ); configFileCorrect = false; } @@ -3895,53 +3895,55 @@ export const configFileStructureAssert = async (config, logger) => { } else { webhooks.forEach((webhook, index) => { if (typeof webhook !== 'object') { - logger.error(`ASSERT CONFIG: "Butler.webhookNotification.qlikSenseServerLicenseMonitor.webhooks[${index}]" must be an object`); + logger.error( + `ASSERT CONFIG: "Butler.webhookNotification.qlikSenseServerLicenseMonitor.webhooks[${index}]" must be an object`, + ); configFileCorrect = false; } else { if (!Object.prototype.hasOwnProperty.call(webhook, 'description')) { logger.error( - `ASSERT CONFIG: Missing property "description" in "Butler.webhookNotification.qlikSenseServerLicenseMonitor.webhooks[${index}]"` + `ASSERT CONFIG: Missing property "description" in "Butler.webhookNotification.qlikSenseServerLicenseMonitor.webhooks[${index}]"`, ); configFileCorrect = false; } if (!Object.prototype.hasOwnProperty.call(webhook, 'webhookURL')) { logger.error( - `ASSERT CONFIG: Missing property "webhookURL" in "Butler.webhookNotification.qlikSenseServerLicenseMonitor.webhooks[${index}]"` + `ASSERT CONFIG: Missing property "webhookURL" in "Butler.webhookNotification.qlikSenseServerLicenseMonitor.webhooks[${index}]"`, ); configFileCorrect = false; } if (!Object.prototype.hasOwnProperty.call(webhook, 'httpMethod')) { logger.error( - `ASSERT CONFIG: Missing property "httpMethod" in "Butler.webhookNotification.qlikSenseServerLicenseMonitor.webhooks[${index}]"` + `ASSERT CONFIG: Missing property "httpMethod" in "Butler.webhookNotification.qlikSenseServerLicenseMonitor.webhooks[${index}]"`, ); configFileCorrect = false; } if (!Object.prototype.hasOwnProperty.call(webhook, 'cert')) { logger.error( - `ASSERT CONFIG: Missing property "cert" in "Butler.webhookNotification.qlikSenseServerLicenseMonitor.webhooks[${index}]"` + `ASSERT CONFIG: Missing property "cert" in "Butler.webhookNotification.qlikSenseServerLicenseMonitor.webhooks[${index}]"`, ); configFileCorrect = false; } else if (typeof webhook.cert !== 'object') { logger.error( - `ASSERT CONFIG: "Butler.webhookNotification.qlikSenseServerLicenseMonitor.webhooks[${index}].cert" must be an object` + `ASSERT CONFIG: "Butler.webhookNotification.qlikSenseServerLicenseMonitor.webhooks[${index}].cert" must be an object`, ); configFileCorrect = false; } else { if (!Object.prototype.hasOwnProperty.call(webhook.cert, 'enable')) { logger.error( - `ASSERT CONFIG: Missing property "enable" in "Butler.webhookNotification.qlikSenseServerLicenseMonitor.webhooks[${index}].cert"` + `ASSERT CONFIG: Missing property "enable" in "Butler.webhookNotification.qlikSenseServerLicenseMonitor.webhooks[${index}].cert"`, ); configFileCorrect = false; } if (!Object.prototype.hasOwnProperty.call(webhook.cert, 'rejectUnauthorized')) { logger.error( - `ASSERT CONFIG: Missing property "rejectUnauthorized" in "Butler.webhookNotification.qlikSenseServerLicenseMonitor.webhooks[${index}].cert"` + `ASSERT CONFIG: Missing property "rejectUnauthorized" in "Butler.webhookNotification.qlikSenseServerLicenseMonitor.webhooks[${index}].cert"`, ); configFileCorrect = false; } if (!Object.prototype.hasOwnProperty.call(webhook.cert, 'certCA')) { logger.error( - `ASSERT CONFIG: Missing property "certCA" in "Butler.webhookNotification.qlikSenseServerLicenseMonitor.webhooks[${index}].cert"` + `ASSERT CONFIG: Missing property "certCA" in "Butler.webhookNotification.qlikSenseServerLicenseMonitor.webhooks[${index}].cert"`, ); configFileCorrect = false; } @@ -3982,53 +3984,55 @@ export const configFileStructureAssert = async (config, logger) => { } else { webhooks.forEach((webhook, index) => { if (typeof webhook !== 'object') { - logger.error(`ASSERT CONFIG: "Butler.webhookNotification.qlikSenseServerLicenseExpiryAlert.webhooks[${index}]" must be an object`); + logger.error( + `ASSERT CONFIG: "Butler.webhookNotification.qlikSenseServerLicenseExpiryAlert.webhooks[${index}]" must be an object`, + ); configFileCorrect = false; } else { if (!Object.prototype.hasOwnProperty.call(webhook, 'description')) { logger.error( - `ASSERT CONFIG: Missing property "description" in "Butler.webhookNotification.qlikSenseServerLicenseExpiryAlert.webhooks[${index}]"` + `ASSERT CONFIG: Missing property "description" in "Butler.webhookNotification.qlikSenseServerLicenseExpiryAlert.webhooks[${index}]"`, ); configFileCorrect = false; } if (!Object.prototype.hasOwnProperty.call(webhook, 'webhookURL')) { logger.error( - `ASSERT CONFIG: Missing property "webhookURL" in "Butler.webhookNotification.qlikSenseServerLicenseExpiryAlert.webhooks[${index}]"` + `ASSERT CONFIG: Missing property "webhookURL" in "Butler.webhookNotification.qlikSenseServerLicenseExpiryAlert.webhooks[${index}]"`, ); configFileCorrect = false; } if (!Object.prototype.hasOwnProperty.call(webhook, 'httpMethod')) { logger.error( - `ASSERT CONFIG: Missing property "httpMethod" in "Butler.webhookNotification.qlikSenseServerLicenseExpiryAlert.webhooks[${index}]"` + `ASSERT CONFIG: Missing property "httpMethod" in "Butler.webhookNotification.qlikSenseServerLicenseExpiryAlert.webhooks[${index}]"`, ); configFileCorrect = false; } if (!Object.prototype.hasOwnProperty.call(webhook, 'cert')) { logger.error( - `ASSERT CONFIG: Missing property "cert" in "Butler.webhookNotification.qlikSenseServerLicenseExpiryAlert.webhooks[${index}]"` + `ASSERT CONFIG: Missing property "cert" in "Butler.webhookNotification.qlikSenseServerLicenseExpiryAlert.webhooks[${index}]"`, ); configFileCorrect = false; } else if (typeof webhook.cert !== 'object') { logger.error( - `ASSERT CONFIG: "Butler.webhookNotification.qlikSenseServerLicenseExpiryAlert.webhooks[${index}].cert" must be an object` + `ASSERT CONFIG: "Butler.webhookNotification.qlikSenseServerLicenseExpiryAlert.webhooks[${index}].cert" must be an object`, ); configFileCorrect = false; } else { if (!Object.prototype.hasOwnProperty.call(webhook.cert, 'enable')) { logger.error( - `ASSERT CONFIG: Missing property "enable" in "Butler.webhookNotification.qlikSenseServerLicenseExpiryAlert.webhooks[${index}].cert"` + `ASSERT CONFIG: Missing property "enable" in "Butler.webhookNotification.qlikSenseServerLicenseExpiryAlert.webhooks[${index}].cert"`, ); configFileCorrect = false; } if (!Object.prototype.hasOwnProperty.call(webhook.cert, 'rejectUnauthorized')) { logger.error( - `ASSERT CONFIG: Missing property "rejectUnauthorized" in "Butler.webhookNotification.qlikSenseServerLicenseExpiryAlert.webhooks[${index}].cert"` + `ASSERT CONFIG: Missing property "rejectUnauthorized" in "Butler.webhookNotification.qlikSenseServerLicenseExpiryAlert.webhooks[${index}].cert"`, ); configFileCorrect = false; } if (!Object.prototype.hasOwnProperty.call(webhook.cert, 'certCA')) { logger.error( - `ASSERT CONFIG: Missing property "certCA" in "Butler.webhookNotification.qlikSenseServerLicenseExpiryAlert.webhooks[${index}].cert"` + `ASSERT CONFIG: Missing property "certCA" in "Butler.webhookNotification.qlikSenseServerLicenseExpiryAlert.webhooks[${index}].cert"`, ); configFileCorrect = false; } @@ -4210,12 +4214,12 @@ export const configFileStructureAssert = async (config, logger) => { } else { if (!Object.prototype.hasOwnProperty.call(directory, 'fromDirectory')) { logger.error( - `ASSERT CONFIG: Missing "fromDirectory" property in "Butler.fileCopyApprovedDirectories[${index}]"` + `ASSERT CONFIG: Missing "fromDirectory" property in "Butler.fileCopyApprovedDirectories[${index}]"`, ); configFileCorrect = false; } else if (typeof directory.fromDirectory !== 'string') { logger.error( - `ASSERT CONFIG: "fromDirectory" property in "Butler.fileCopyApprovedDirectories[${index}]" is not a string` + `ASSERT CONFIG: "fromDirectory" property in "Butler.fileCopyApprovedDirectories[${index}]" is not a string`, ); configFileCorrect = false; } @@ -4225,7 +4229,7 @@ export const configFileStructureAssert = async (config, logger) => { configFileCorrect = false; } else if (typeof directory.toDirectory !== 'string') { logger.error( - `ASSERT CONFIG: "toDirectory" property in "Butler.fileCopyApprovedDirectories[${index}]" is not a string` + `ASSERT CONFIG: "toDirectory" property in "Butler.fileCopyApprovedDirectories[${index}]" is not a string`, ); configFileCorrect = false; } @@ -4258,12 +4262,12 @@ export const configFileStructureAssert = async (config, logger) => { } else { if (!Object.prototype.hasOwnProperty.call(directory, 'fromDirectory')) { logger.error( - `ASSERT CONFIG: Missing "fromDirectory" property in "Butler.fileMoveApprovedDirectories[${index}]"` + `ASSERT CONFIG: Missing "fromDirectory" property in "Butler.fileMoveApprovedDirectories[${index}]"`, ); configFileCorrect = false; } else if (typeof directory.fromDirectory !== 'string') { logger.error( - `ASSERT CONFIG: "fromDirectory" property in "Butler.fileMoveApprovedDirectories[${index}]" is not a string` + `ASSERT CONFIG: "fromDirectory" property in "Butler.fileMoveApprovedDirectories[${index}]" is not a string`, ); configFileCorrect = false; } @@ -4273,7 +4277,7 @@ export const configFileStructureAssert = async (config, logger) => { configFileCorrect = false; } else if (typeof directory.toDirectory !== 'string') { logger.error( - `ASSERT CONFIG: "toDirectory" property in "Butler.fileMoveApprovedDirectories[${index}]" is not a string` + `ASSERT CONFIG: "toDirectory" property in "Butler.fileMoveApprovedDirectories[${index}]" is not a string`, ); configFileCorrect = false; } @@ -4447,14 +4451,14 @@ export const configFileStructureAssert = async (config, logger) => { if (destinationAccounts) { if (!Array.isArray(destinationAccounts)) { logger.error( - 'ASSERT CONFIG: "Butler.restServerEndpointsConfig.newRelic.postNewRelicMetric.destinationAccount" is not an array' + 'ASSERT CONFIG: "Butler.restServerEndpointsConfig.newRelic.postNewRelicMetric.destinationAccount" is not an array', ); configFileCorrect = false; } else { destinationAccounts.forEach((account, index) => { if (typeof account !== 'string') { logger.error( - `ASSERT CONFIG: "Butler.restServerEndpointsConfig.newRelic.postNewRelicMetric.destinationAccount[${index}]" is not a string` + `ASSERT CONFIG: "Butler.restServerEndpointsConfig.newRelic.postNewRelicMetric.destinationAccount[${index}]" is not a string`, ); configFileCorrect = false; } @@ -4463,7 +4467,7 @@ export const configFileStructureAssert = async (config, logger) => { } } else { logger.error( - 'ASSERT CONFIG: Missing config file entry "Butler.restServerEndpointsConfig.newRelic.postNewRelicMetric.destinationAccount"' + 'ASSERT CONFIG: Missing config file entry "Butler.restServerEndpointsConfig.newRelic.postNewRelicMetric.destinationAccount"', ); configFileCorrect = false; } @@ -4489,30 +4493,30 @@ export const configFileStructureAssert = async (config, logger) => { headers.forEach((header, index) => { if (typeof header !== 'object') { logger.error( - `ASSERT CONFIG: "Butler.restServerEndpointsConfig.newRelic.postNewRelicMetric.header[${index}]" is not an object` + `ASSERT CONFIG: "Butler.restServerEndpointsConfig.newRelic.postNewRelicMetric.header[${index}]" is not an object`, ); configFileCorrect = false; } else { if (!Object.prototype.hasOwnProperty.call(header, 'name')) { logger.error( - `ASSERT CONFIG: Missing "name" property in "Butler.restServerEndpointsConfig.newRelic.postNewRelicMetric.header[${index}]"` + `ASSERT CONFIG: Missing "name" property in "Butler.restServerEndpointsConfig.newRelic.postNewRelicMetric.header[${index}]"`, ); configFileCorrect = false; } else if (typeof header.name !== 'string') { logger.error( - `ASSERT CONFIG: "name" property in "Butler.restServerEndpointsConfig.newRelic.postNewRelicMetric.header[${index}]" is not a string` + `ASSERT CONFIG: "name" property in "Butler.restServerEndpointsConfig.newRelic.postNewRelicMetric.header[${index}]" is not a string`, ); configFileCorrect = false; } if (!Object.prototype.hasOwnProperty.call(header, 'value')) { logger.error( - `ASSERT CONFIG: Missing "value" property in "Butler.restServerEndpointsConfig.newRelic.postNewRelicMetric.header[${index}]"` + `ASSERT CONFIG: Missing "value" property in "Butler.restServerEndpointsConfig.newRelic.postNewRelicMetric.header[${index}]"`, ); configFileCorrect = false; } else if (typeof header.value !== 'string') { logger.error( - `ASSERT CONFIG: "value" property in "Butler.restServerEndpointsConfig.newRelic.postNewRelicMetric.header[${index}]" is not a string` + `ASSERT CONFIG: "value" property in "Butler.restServerEndpointsConfig.newRelic.postNewRelicMetric.header[${index}]" is not a string`, ); configFileCorrect = false; } @@ -4536,37 +4540,37 @@ export const configFileStructureAssert = async (config, logger) => { if (attributes) { if (!Array.isArray(attributes)) { logger.error( - 'ASSERT CONFIG: "Butler.restServerEndpointsConfig.newRelic.postNewRelicMetric.attribute.static" is not an array' + 'ASSERT CONFIG: "Butler.restServerEndpointsConfig.newRelic.postNewRelicMetric.attribute.static" is not an array', ); configFileCorrect = false; } else { attributes.forEach((attribute, index) => { if (typeof attribute !== 'object') { logger.error( - `ASSERT CONFIG: "Butler.restServerEndpointsConfig.newRelic.postNewRelicMetric.attribute.static[${index}]" is not an object` + `ASSERT CONFIG: "Butler.restServerEndpointsConfig.newRelic.postNewRelicMetric.attribute.static[${index}]" is not an object`, ); configFileCorrect = false; } else { if (!Object.prototype.hasOwnProperty.call(attribute, 'name')) { logger.error( - `ASSERT CONFIG: Missing "name" property in "Butler.restServerEndpointsConfig.newRelic.postNewRelicMetric.attribute.static[${index}]"` + `ASSERT CONFIG: Missing "name" property in "Butler.restServerEndpointsConfig.newRelic.postNewRelicMetric.attribute.static[${index}]"`, ); configFileCorrect = false; } else if (typeof attribute.name !== 'string') { logger.error( - `ASSERT CONFIG: "name" property in "Butler.restServerEndpointsConfig.newRelic.postNewRelicMetric.attribute.static[${index}]" is not a string` + `ASSERT CONFIG: "name" property in "Butler.restServerEndpointsConfig.newRelic.postNewRelicMetric.attribute.static[${index}]" is not a string`, ); configFileCorrect = false; } if (!Object.prototype.hasOwnProperty.call(attribute, 'value')) { logger.error( - `ASSERT CONFIG: Missing "value" property in "Butler.restServerEndpointsConfig.newRelic.postNewRelicMetric.attribute.static[${index}]"` + `ASSERT CONFIG: Missing "value" property in "Butler.restServerEndpointsConfig.newRelic.postNewRelicMetric.attribute.static[${index}]"`, ); configFileCorrect = false; } else if (typeof attribute.value !== 'string') { logger.error( - `ASSERT CONFIG: "value" property in "Butler.restServerEndpointsConfig.newRelic.postNewRelicMetric.attribute.static[${index}]" is not a string` + `ASSERT CONFIG: "value" property in "Butler.restServerEndpointsConfig.newRelic.postNewRelicMetric.attribute.static[${index}]" is not a string`, ); configFileCorrect = false; } @@ -4576,7 +4580,7 @@ export const configFileStructureAssert = async (config, logger) => { } } else { logger.error( - 'ASSERT CONFIG: Missing config file entry "Butler.restServerEndpointsConfig.newRelic.postNewRelicMetric.attribute.static"' + 'ASSERT CONFIG: Missing config file entry "Butler.restServerEndpointsConfig.newRelic.postNewRelicMetric.attribute.static"', ); configFileCorrect = false; } @@ -4589,14 +4593,14 @@ export const configFileStructureAssert = async (config, logger) => { if (destinationAccounts) { if (!Array.isArray(destinationAccounts)) { logger.error( - 'ASSERT CONFIG: "Butler.restServerEndpointsConfig.newRelic.postNewRelicEvent.destinationAccount" is not an array' + 'ASSERT CONFIG: "Butler.restServerEndpointsConfig.newRelic.postNewRelicEvent.destinationAccount" is not an array', ); configFileCorrect = false; } else { destinationAccounts.forEach((account, index) => { if (typeof account !== 'string') { logger.error( - `ASSERT CONFIG: "Butler.restServerEndpointsConfig.newRelic.postNewRelicEvent.destinationAccount[${index}]" is not a string` + `ASSERT CONFIG: "Butler.restServerEndpointsConfig.newRelic.postNewRelicEvent.destinationAccount[${index}]" is not a string`, ); configFileCorrect = false; } @@ -4605,7 +4609,7 @@ export const configFileStructureAssert = async (config, logger) => { } } else { logger.error( - 'ASSERT CONFIG: Missing config file entry "Butler.restServerEndpointsConfig.newRelic.postNewRelicEvent.destinationAccount"' + 'ASSERT CONFIG: Missing config file entry "Butler.restServerEndpointsConfig.newRelic.postNewRelicEvent.destinationAccount"', ); configFileCorrect = false; } @@ -4631,30 +4635,30 @@ export const configFileStructureAssert = async (config, logger) => { headers.forEach((header, index) => { if (typeof header !== 'object') { logger.error( - `ASSERT CONFIG: "Butler.restServerEndpointsConfig.newRelic.postNewRelicEvent.header[${index}]" is not an object` + `ASSERT CONFIG: "Butler.restServerEndpointsConfig.newRelic.postNewRelicEvent.header[${index}]" is not an object`, ); configFileCorrect = false; } else { if (!Object.prototype.hasOwnProperty.call(header, 'name')) { logger.error( - `ASSERT CONFIG: Missing "name" property in "Butler.restServerEndpointsConfig.newRelic.postNewRelicEvent.header[${index}]"` + `ASSERT CONFIG: Missing "name" property in "Butler.restServerEndpointsConfig.newRelic.postNewRelicEvent.header[${index}]"`, ); configFileCorrect = false; } else if (typeof header.name !== 'string') { logger.error( - `ASSERT CONFIG: "name" property in "Butler.restServerEndpointsConfig.newRelic.postNewRelicEvent.header[${index}]" is not a string` + `ASSERT CONFIG: "name" property in "Butler.restServerEndpointsConfig.newRelic.postNewRelicEvent.header[${index}]" is not a string`, ); configFileCorrect = false; } if (!Object.prototype.hasOwnProperty.call(header, 'value')) { logger.error( - `ASSERT CONFIG: Missing "value" property in "Butler.restServerEndpointsConfig.newRelic.postNewRelicEvent.header[${index}]"` + `ASSERT CONFIG: Missing "value" property in "Butler.restServerEndpointsConfig.newRelic.postNewRelicEvent.header[${index}]"`, ); configFileCorrect = false; } else if (typeof header.value !== 'string') { logger.error( - `ASSERT CONFIG: "value" property in "Butler.restServerEndpointsConfig.newRelic.postNewRelicEvent.header[${index}]" is not a string` + `ASSERT CONFIG: "value" property in "Butler.restServerEndpointsConfig.newRelic.postNewRelicEvent.header[${index}]" is not a string`, ); configFileCorrect = false; } @@ -4678,37 +4682,37 @@ export const configFileStructureAssert = async (config, logger) => { if (attributes) { if (!Array.isArray(attributes)) { logger.error( - 'ASSERT CONFIG: "Butler.restServerEndpointsConfig.newRelic.postNewRelicEvent.attribute.static" is not an array' + 'ASSERT CONFIG: "Butler.restServerEndpointsConfig.newRelic.postNewRelicEvent.attribute.static" is not an array', ); configFileCorrect = false; } else { attributes.forEach((attribute, index) => { if (typeof attribute !== 'object') { logger.error( - `ASSERT CONFIG: "Butler.restServerEndpointsConfig.newRelic.postNewRelicEvent.attribute.static[${index}]" is not an object` + `ASSERT CONFIG: "Butler.restServerEndpointsConfig.newRelic.postNewRelicEvent.attribute.static[${index}]" is not an object`, ); configFileCorrect = false; } else { if (!Object.prototype.hasOwnProperty.call(attribute, 'name')) { logger.error( - `ASSERT CONFIG: Missing "name" property in "Butler.restServerEndpointsConfig.newRelic.postNewRelicEvent.attribute.static[${index}]"` + `ASSERT CONFIG: Missing "name" property in "Butler.restServerEndpointsConfig.newRelic.postNewRelicEvent.attribute.static[${index}]"`, ); configFileCorrect = false; } else if (typeof attribute.name !== 'string') { logger.error( - `ASSERT CONFIG: "name" property in "Butler.restServerEndpointsConfig.newRelic.postNewRelicEvent.attribute.static[${index}]" is not a string` + `ASSERT CONFIG: "name" property in "Butler.restServerEndpointsConfig.newRelic.postNewRelicEvent.attribute.static[${index}]" is not a string`, ); configFileCorrect = false; } if (!Object.prototype.hasOwnProperty.call(attribute, 'value')) { logger.error( - `ASSERT CONFIG: Missing "value" property in "Butler.restServerEndpointsConfig.newRelic.postNewRelicEvent.attribute.static[${index}]"` + `ASSERT CONFIG: Missing "value" property in "Butler.restServerEndpointsConfig.newRelic.postNewRelicEvent.attribute.static[${index}]"`, ); configFileCorrect = false; } else if (typeof attribute.value !== 'string') { logger.error( - `ASSERT CONFIG: "value" property in "Butler.restServerEndpointsConfig.newRelic.postNewRelicEvent.attribute.static[${index}]" is not a string` + `ASSERT CONFIG: "value" property in "Butler.restServerEndpointsConfig.newRelic.postNewRelicEvent.attribute.static[${index}]" is not a string`, ); configFileCorrect = false; } @@ -4718,7 +4722,7 @@ export const configFileStructureAssert = async (config, logger) => { } } else { logger.error( - 'ASSERT CONFIG: Missing config file entry "Butler.restServerEndpointsConfig.newRelic.postNewRelicEvent.attribute.static"' + 'ASSERT CONFIG: Missing config file entry "Butler.restServerEndpointsConfig.newRelic.postNewRelicEvent.attribute.static"', ); configFileCorrect = false; } @@ -4794,24 +4798,24 @@ export const configFileStructureAssert = async (config, logger) => { } else { if (!Object.prototype.hasOwnProperty.call(customProperty, 'name')) { logger.error( - `ASSERT CONFIG: Missing "name" property in "Butler.startTaskFilter.allowTask.customProperty[${index}]"` + `ASSERT CONFIG: Missing "name" property in "Butler.startTaskFilter.allowTask.customProperty[${index}]"`, ); configFileCorrect = false; } else if (typeof customProperty.name !== 'string') { logger.error( - `ASSERT CONFIG: "name" property in "Butler.startTaskFilter.allowTask.customProperty[${index}]" is not a string` + `ASSERT CONFIG: "name" property in "Butler.startTaskFilter.allowTask.customProperty[${index}]" is not a string`, ); configFileCorrect = false; } if (!Object.prototype.hasOwnProperty.call(customProperty, 'value')) { logger.error( - `ASSERT CONFIG: Missing "value" property in "Butler.startTaskFilter.allowTask.customProperty[${index}]"` + `ASSERT CONFIG: Missing "value" property in "Butler.startTaskFilter.allowTask.customProperty[${index}]"`, ); configFileCorrect = false; } else if (typeof customProperty.value !== 'string') { logger.error( - `ASSERT CONFIG: "value" property in "Butler.startTaskFilter.allowTask.customProperty[${index}]" is not a string` + `ASSERT CONFIG: "value" property in "Butler.startTaskFilter.allowTask.customProperty[${index}]" is not a string`, ); configFileCorrect = false; } @@ -4873,30 +4877,30 @@ export const configFileStructureAssert = async (config, logger) => { monitor.services.forEach((service, serviceIndex) => { if (typeof service !== 'object') { logger.error( - `ASSERT CONFIG: "Butler.serviceMonitor.monitor[${index}].services[${serviceIndex}]" is not an object` + `ASSERT CONFIG: "Butler.serviceMonitor.monitor[${index}].services[${serviceIndex}]" is not an object`, ); configFileCorrect = false; } else { if (!Object.prototype.hasOwnProperty.call(service, 'name')) { logger.error( - `ASSERT CONFIG: Missing "name" property in "Butler.serviceMonitor.monitor[${index}].services[${serviceIndex}]"` + `ASSERT CONFIG: Missing "name" property in "Butler.serviceMonitor.monitor[${index}].services[${serviceIndex}]"`, ); configFileCorrect = false; } else if (typeof service.name !== 'string') { logger.error( - `ASSERT CONFIG: "name" property in "Butler.serviceMonitor.monitor[${index}].services[${serviceIndex}]" is not a string` + `ASSERT CONFIG: "name" property in "Butler.serviceMonitor.monitor[${index}].services[${serviceIndex}]" is not a string`, ); configFileCorrect = false; } if (!Object.prototype.hasOwnProperty.call(service, 'friendlyName')) { logger.error( - `ASSERT CONFIG: Missing "friendlyName" property in "Butler.serviceMonitor.monitor[${index}].services[${serviceIndex}]"` + `ASSERT CONFIG: Missing "friendlyName" property in "Butler.serviceMonitor.monitor[${index}].services[${serviceIndex}]"`, ); configFileCorrect = false; } else if (typeof service.friendlyName !== 'string') { logger.error( - `ASSERT CONFIG: "friendlyName" property in "Butler.serviceMonitor.monitor[${index}].services[${serviceIndex}]" is not a string` + `ASSERT CONFIG: "friendlyName" property in "Butler.serviceMonitor.monitor[${index}].services[${serviceIndex}]" is not a string`, ); configFileCorrect = false; } @@ -4947,6 +4951,27 @@ export const configFileStructureAssert = async (config, logger) => { configFileCorrect = false; } + // Config visualisation setttings + if (!config.has('Butler.configVisualisation.enable')) { + logger.error('ASSERT CONFIG: Missing config file entry "Butler.configVisualisation.enable"'); + configFileCorrect = false; + } + + if (!config.has('Butler.configVisualisation.host')) { + logger.error('ASSERT CONFIG: Missing config file entry "Butler.configVisualisation.host"'); + configFileCorrect = false; + } + + if (!config.has('Butler.configVisualisation.port')) { + logger.error('ASSERT CONFIG: Missing config file entry "Butler.configVisualisation.port"'); + configFileCorrect = false; + } + + if (!config.has('Butler.configVisualisation.obfuscate')) { + logger.error('ASSERT CONFIG: Missing config file entry "Butler.configVisualisation.obfuscate"'); + configFileCorrect = false; + } + if (!config.has('Butler.cert.clientCert')) { logger.error('ASSERT CONFIG: Missing config file entry "Butler.cert.clientCert"'); configFileCorrect = false; diff --git a/src/lib/config_obfuscate.js b/src/lib/config_obfuscate.js new file mode 100644 index 00000000..b8fd88a2 --- /dev/null +++ b/src/lib/config_obfuscate.js @@ -0,0 +1,156 @@ +import globals from '../globals.js'; + +function configObfuscate(config) { + try { + const obfuscatedConfig = { ...config }; + + // Keep first 10 chars of remote URL, mask the rest with * + obfuscatedConfig.Butler.heartbeat.remoteURL = obfuscatedConfig.Butler.heartbeat.remoteURL.substring(0, 10) + '*'.repeat(10); + + // Update entries in the array obfuscatedConfig.Butler.thirdPartyToolsCredentials.newRelic + obfuscatedConfig.Butler.thirdPartyToolsCredentials.newRelic = obfuscatedConfig.Butler.thirdPartyToolsCredentials.newRelic?.map( + (element) => ({ + ...element, + insertApiKey: element.insertApiKey.substring(0, 5) + '*'.repeat(10), + accountId: element.accountId.toString().substring(0, 3) + '*'.repeat(10), + }), + ); + + // Obfuscate Butler.influxDb.hostIP, keep first 3 chars, mask the rest with * + obfuscatedConfig.Butler.influxDb.hostIP = obfuscatedConfig.Butler.influxDb.hostIP.substring(0, 3) + '*'.repeat(10); + + // Obfuscate Butler.influxDb.auth.username, keep first 3 chars, mask the rest with * + obfuscatedConfig.Butler.influxDb.auth.username = obfuscatedConfig.Butler.influxDb.auth.username.substring(0, 3) + '*'.repeat(10); + + // Obfuscate Butler.influxDb.auth.password, keep first 0 chars, mask the rest with * + obfuscatedConfig.Butler.influxDb.auth.password = '*'.repeat(10); + + // Obfuscate Butler.qlikSenseVersion.versionMonitor.host, keep first 3 chars, mask the rest with * + obfuscatedConfig.Butler.qlikSenseVersion.versionMonitor.host = + obfuscatedConfig.Butler.qlikSenseVersion.versionMonitor.host.substring(0, 3) + '*'.repeat(10); + + // Obfuscate Butler.teamsNotification.reloadTaskFailure.webhookURL, keep first 10 chars, mask the rest with * + obfuscatedConfig.Butler.teamsNotification.reloadTaskFailure.webhookURL = + obfuscatedConfig.Butler.teamsNotification.reloadTaskFailure.webhookURL.substring(0, 10) + '*'.repeat(10); + + // Obfuscate Butler.teamsNotification.reloadTaskAborted.webhookURL, keep first 10 chars, mask the rest with * + obfuscatedConfig.Butler.teamsNotification.reloadTaskAborted.webhookURL = + obfuscatedConfig.Butler.teamsNotification.reloadTaskAborted.webhookURL.substring(0, 10) + '*'.repeat(10); + + // Obfuscate Butler.teamsNotification.serviceStopped.webhookURL, keep first 10 chars, mask the rest with * + obfuscatedConfig.Butler.teamsNotification.serviceStopped.webhookURL = + obfuscatedConfig.Butler.teamsNotification.serviceStopped.webhookURL.substring(0, 10) + '*'.repeat(10); + + // Obfuscate Butler.teamsNotification.serviceStarted.webhookURL, keep first 10 chars, mask the rest with * + obfuscatedConfig.Butler.teamsNotification.serviceStarted.webhookURL = + obfuscatedConfig.Butler.teamsNotification.serviceStarted.webhookURL.substring(0, 10) + '*'.repeat(10); + + // Obfuscate Butler.slackNotification.restMessage.webhookURL, keep first 10 chars, mask the rest with * + obfuscatedConfig.Butler.slackNotification.restMessage.webhookURL = + obfuscatedConfig.Butler.slackNotification.restMessage.webhookURL.substring(0, 10) + '*'.repeat(10); + + // Obfuscate Butler.slackNotification.reloadTaskFailure.webhookURL, keep first 10 chars, mask the rest with * + obfuscatedConfig.Butler.slackNotification.reloadTaskFailure.webhookURL = + obfuscatedConfig.Butler.slackNotification.reloadTaskFailure.webhookURL.substring(0, 10) + '*'.repeat(10); + + // Obfuscate Butler.slackNotification.reloadTaskAborted.webhookURL, keep first 10 chars, mask the rest with * + obfuscatedConfig.Butler.slackNotification.reloadTaskAborted.webhookURL = + obfuscatedConfig.Butler.slackNotification.reloadTaskAborted.webhookURL.substring(0, 10) + '*'.repeat(10); + + // Obfuscate Butler.slackNotification.serviceStopped.webhookURL, keep first 10 chars, mask the rest with * + obfuscatedConfig.Butler.slackNotification.serviceStopped.webhookURL = + obfuscatedConfig.Butler.slackNotification.serviceStopped.webhookURL.substring(0, 10) + '*'.repeat(10); + + // Obfuscate Butler.slackNotification.serviceStarted.webhookURL, keep first 10 chars, mask the rest with * + obfuscatedConfig.Butler.slackNotification.serviceStarted.webhookURL = + obfuscatedConfig.Butler.slackNotification.serviceStarted.webhookURL.substring(0, 10) + '*'.repeat(10); + + // Obfuscate Butler.emailNotification.smtp.host, keep first 3 chars, mask the rest with * + obfuscatedConfig.Butler.emailNotification.smtp.host = + obfuscatedConfig.Butler.emailNotification.smtp.host.substring(0, 3) + '*'.repeat(10); + + // Obfuscate Butler.emailNotification.smtp.auth.user, keep first 3 chars, mask the rest with * + obfuscatedConfig.Butler.emailNotification.smtp.auth.user = + obfuscatedConfig.Butler.emailNotification.smtp.auth.user.substring(0, 3) + '*'.repeat(10); + + // Obfuscate Butler.emailNotification.smtp.auth.password, keep first 0 chars, mask the rest with * + obfuscatedConfig.Butler.emailNotification.smtp.auth.password = '*'.repeat(10); + + // Butler.webhookNotification.reloadTaskFailure is an array of objects + // Obfuscate webhookURL property, keep first 10 chars, mask the rest with * + obfuscatedConfig.Butler.webhookNotification.reloadTaskFailure.webhooks = + obfuscatedConfig.Butler.webhookNotification.reloadTaskFailure.webhooks?.map((element) => ({ + ...element, + webhookURL: element.webhookURL.substring(0, 10) + '*'.repeat(10), + })); + + // Butler.webhookNotification.reloadTaskAborted is an array of objects + // Obfuscate webhookURL property, keep first 10 chars, mask the rest with * + obfuscatedConfig.Butler.webhookNotification.reloadTaskAborted.webhooks = + obfuscatedConfig.Butler.webhookNotification.reloadTaskAborted.webhooks?.map((element) => ({ + ...element, + webhookURL: element.webhookURL.substring(0, 10) + '*'.repeat(10), + })); + // Butler.webhookNotification.serviceMonitor is an array of objects + // Obfuscate webhookURL property, keep first 10 chars, mask the rest with * + obfuscatedConfig.Butler.webhookNotification.serviceMonitor.webhooks = + obfuscatedConfig.Butler.webhookNotification.serviceMonitor.webhooks?.map((element) => ({ + ...element, + webhookURL: element.webhookURL.substring(0, 10) + '*'.repeat(10), + })); + + // Butler.webhookNotification.qlikSenseServerLicenseMonitor is an array of objects + // Obfuscate webhookURL property, keep first 10 chars, mask the rest with * + obfuscatedConfig.Butler.webhookNotification.qlikSenseServerLicenseMonitor.webhooks = + obfuscatedConfig.Butler.webhookNotification.qlikSenseServerLicenseMonitor.webhooks?.map((element) => ({ + ...element, + webhookURL: element.webhookURL.substring(0, 10) + '*'.repeat(10), + })); + + // Butler.webhookNotification.qlikSenseServerLicenseExpiryAlert is an array of objects + // Obfuscate webhookURL property, keep first 10 chars, mask the rest with * + obfuscatedConfig.Butler.webhookNotification.qlikSenseServerLicenseExpiryAlert.webhooks = + obfuscatedConfig.Butler.webhookNotification.qlikSenseServerLicenseExpiryAlert.webhooks?.map((element) => ({ + ...element, + webhookURL: element.webhookURL.substring(0, 10) + '*'.repeat(10), + })); + + // Obfuscate Butler.mqttConfig.brokerHost, keep first 3 chars, mask the rest with * + obfuscatedConfig.Butler.mqttConfig.brokerHost = obfuscatedConfig.Butler.mqttConfig.brokerHost.substring(0, 3) + '*'.repeat(10); + + // Obfuscate Butler.mqttConfig.azureEventGrid.clientId, keep first 3 chars, mask the rest with * + obfuscatedConfig.Butler.mqttConfig.azureEventGrid.clientId = + obfuscatedConfig.Butler.mqttConfig.azureEventGrid.clientId.substring(0, 3) + '*'.repeat(10); + + // Obfuscate Butler.udpServerConfig.serverHost, keep first 3 chars, mask the rest with * + obfuscatedConfig.Butler.udpServerConfig.serverHost = + obfuscatedConfig.Butler.udpServerConfig.serverHost.substring(0, 3) + '*'.repeat(10); + + // Obfuscate Butler.restServerConfig.serverHost, keep first 3 chars, mask the rest with * + obfuscatedConfig.Butler.restServerConfig.serverHost = + obfuscatedConfig.Butler.restServerConfig.serverHost.substring(0, 3) + '*'.repeat(10); + + // Butler.serviceMonitor.monitor is an array + // Obfuscate host property, keep first 3 chars, mask the rest with * + obfuscatedConfig.Butler.serviceMonitor.monitor = obfuscatedConfig.Butler.serviceMonitor.monitor?.map((element) => ({ + ...element, + host: element.host.substring(0, 3) + '*'.repeat(10), + })); + + // Obfuscate Butler.configEngine.host, keep first 3 chars, mask the rest with * + obfuscatedConfig.Butler.configEngine.host = obfuscatedConfig.Butler.configEngine.host.substring(0, 3) + '*'.repeat(10); + + // Obfuscate Butler.configQRS.host, keep first 3 chars, mask the rest with * + obfuscatedConfig.Butler.configQRS.host = obfuscatedConfig.Butler.configQRS.host.substring(0, 3) + '*'.repeat(10); + + return obfuscatedConfig; + } catch (err) { + globals.logger.error(`CONFIG OBFUSCATE: Error obfuscating config: ${err.message}`); + if (err.stack) { + globals.logger.error(`CONFIG OBFUSCATE: ${err.stack}`); + } + throw err; + } +} + +export default configObfuscate; diff --git a/src/lib/incident_mgmt/new_relic.js b/src/lib/incident_mgmt/new_relic.js index 5190905a..f063b574 100644 --- a/src/lib/incident_mgmt/new_relic.js +++ b/src/lib/incident_mgmt/new_relic.js @@ -433,7 +433,7 @@ export async function sendNewRelicEvent(incidentConfig, reloadParams, destNewRel // eslint-disable-next-line no-await-in-loop const res = await axios.request(axiosRequest); globals.logger.debug( - `NEW RELIC EVENT: Result code from posting event to New Relic account ${newRelicConfig[0].accountId}: ${res.status}, ${res.statusText}` + `NEW RELIC EVENT: Result code from posting event to New Relic account ${newRelicConfig[0].accountId}: ${res.status}, ${res.statusText}`, ); if (res.status === 200 || res.status === 202) { @@ -441,7 +441,7 @@ export async function sendNewRelicEvent(incidentConfig, reloadParams, destNewRel globals.logger.verbose(`NEW RELIC EVENT: Sent event New Relic account ${newRelicConfig[0].accountId}`); } else { globals.logger.error( - `NEW RELIC EVENT: Error code from posting event to New Relic account ${newRelicConfig[0].accountId}: ${res.status}, ${res.statusText}` + `NEW RELIC EVENT: Error code from posting event to New Relic account ${newRelicConfig[0].accountId}: ${res.status}, ${res.statusText}`, ); } } @@ -481,7 +481,7 @@ export async function sendNewRelicLog(incidentConfig, reloadParams, destNewRelic // Reduce script log lines to only the ones we want to send to New Relic scriptLogData.scriptLogHeadCount = 0; scriptLogData.scriptLogTailCount = globals.config.get( - 'Butler.incidentTool.newRelic.reloadTaskFailure.destination.log.tailScriptLogLines' + 'Butler.incidentTool.newRelic.reloadTaskFailure.destination.log.tailScriptLogLines', ); scriptLogData.scriptLogHead = ''; @@ -568,7 +568,7 @@ export async function sendNewRelicLog(incidentConfig, reloadParams, destNewRelic // eslint-disable-next-line no-await-in-loop const res = await axios.request(axiosRequest); globals.logger.debug( - `NEW RELIC LOG: Result code from posting log to New Relic account ${newRelicConfig[0].accountId}: ${res.status}, ${res.statusText}` + `NEW RELIC LOG: Result code from posting log to New Relic account ${newRelicConfig[0].accountId}: ${res.status}, ${res.statusText}`, ); if (res.status === 200 || res.status === 202) { @@ -576,7 +576,7 @@ export async function sendNewRelicLog(incidentConfig, reloadParams, destNewRelic globals.logger.verbose(`NEW RELIC LOG: Sent log New Relic account ${newRelicConfig[0].accountId}`); } else { globals.logger.error( - `NEW RELIC LOG: Error code from posting log to New Relic account ${newRelicConfig[0].accountId}: ${res.status}, ${res.statusText}` + `NEW RELIC LOG: Error code from posting log to New Relic account ${newRelicConfig[0].accountId}: ${res.status}, ${res.statusText}`, ); } } @@ -605,7 +605,7 @@ export async function sendReloadTaskFailureEvent(reloadParams) { .then(async (rateLimiterRes) => { try { globals.logger.info( - `TASK FAILED NEWRELIC: Rate limiting check passed for failed task event. Task name: "${params.qs_taskName}"` + `TASK FAILED NEWRELIC: Rate limiting check passed for failed task event. Task name: "${params.qs_taskName}"`, ); globals.logger.verbose(`TASK FAILED NEWRELIC: Rate limiting details "${JSON.stringify(rateLimiterRes, null, 2)}"`); @@ -661,7 +661,7 @@ export async function sendReloadTaskFailureEvent(reloadParams) { ) { // eslint-disable-next-line no-restricted-syntax for (const acct of globals.config.get( - 'Butler.incidentTool.newRelic.reloadTaskFailure.destination.event.sendToAccount.always.account' + 'Butler.incidentTool.newRelic.reloadTaskFailure.destination.event.sendToAccount.always.account', )) { tmpDestNewRelicAccounts.push(acct); } @@ -670,10 +670,10 @@ export async function sendReloadTaskFailureEvent(reloadParams) { // Send event to NR accounts specified by reload task custom property if ( globals.config.has( - 'Butler.incidentTool.newRelic.reloadTaskFailure.destination.event.sendToAccount.byCustomProperty.enable' + 'Butler.incidentTool.newRelic.reloadTaskFailure.destination.event.sendToAccount.byCustomProperty.enable', ) && globals.config.has( - 'Butler.incidentTool.newRelic.reloadTaskFailure.destination.event.sendToAccount.byCustomProperty.customPropertyName' + 'Butler.incidentTool.newRelic.reloadTaskFailure.destination.event.sendToAccount.byCustomProperty.customPropertyName', ) ) { // Get values of custom property @@ -685,7 +685,7 @@ export async function sendReloadTaskFailureEvent(reloadParams) { if ( cp.definition.name === globals.config.get( - 'Butler.incidentTool.newRelic.reloadTaskFailure.destination.event.sendToAccount.byCustomProperty.customPropertyName' + 'Butler.incidentTool.newRelic.reloadTaskFailure.destination.event.sendToAccount.byCustomProperty.customPropertyName', ) ) { tmpDestNewRelicAccounts.push(cp.value); @@ -733,7 +733,7 @@ export async function sendReloadTaskFailureEvent(reloadParams) { }) .catch((rateLimiterRes) => { globals.logger.verbose( - `TASK FAILED NEWRELIC: Rate limiting failed. Not sending reload failure event to New Relic for task "${params.qs_taskName}"` + `TASK FAILED NEWRELIC: Rate limiting failed. Not sending reload failure event to New Relic for task "${params.qs_taskName}"`, ); globals.logger.verbose(`TASK FAILED NEWRELIC: Rate limiting details "${JSON.stringify(rateLimiterRes, null, 2)}"`); }); @@ -747,7 +747,7 @@ export async function sendReloadTaskFailureLog(reloadParams) { .then(async (rateLimiterRes) => { try { globals.logger.info( - `TASK FAILED NEWRELIC: Rate limiting check passed for failed task log entry. Task name: "${params.qs_taskName}"` + `TASK FAILED NEWRELIC: Rate limiting check passed for failed task log entry. Task name: "${params.qs_taskName}"`, ); globals.logger.verbose(`TASK FAILED NEWRELIC: Rate limiting details "${JSON.stringify(rateLimiterRes, null, 2)}"`); @@ -801,7 +801,7 @@ export async function sendReloadTaskFailureLog(reloadParams) { ) { // eslint-disable-next-line no-restricted-syntax for (const acct of globals.config.get( - 'Butler.incidentTool.newRelic.reloadTaskFailure.destination.log.sendToAccount.always.account' + 'Butler.incidentTool.newRelic.reloadTaskFailure.destination.log.sendToAccount.always.account', )) { tmpDestNewRelicAccounts.push(acct); } @@ -810,10 +810,10 @@ export async function sendReloadTaskFailureLog(reloadParams) { // Send log entry to NR accounts specified by reload task custom property if ( globals.config.has( - 'Butler.incidentTool.newRelic.reloadTaskFailure.destination.log.sendToAccount.byCustomProperty.enable' + 'Butler.incidentTool.newRelic.reloadTaskFailure.destination.log.sendToAccount.byCustomProperty.enable', ) && globals.config.has( - 'Butler.incidentTool.newRelic.reloadTaskFailure.destination.log.sendToAccount.byCustomProperty.customPropertyName' + 'Butler.incidentTool.newRelic.reloadTaskFailure.destination.log.sendToAccount.byCustomProperty.customPropertyName', ) ) { // Get values of custom property @@ -825,7 +825,7 @@ export async function sendReloadTaskFailureLog(reloadParams) { if ( cp.definition.name === globals.config.get( - 'Butler.incidentTool.newRelic.reloadTaskFailure.destination.log.sendToAccount.byCustomProperty.customPropertyName' + 'Butler.incidentTool.newRelic.reloadTaskFailure.destination.log.sendToAccount.byCustomProperty.customPropertyName', ) ) { tmpDestNewRelicAccounts.push(cp.value); @@ -873,7 +873,7 @@ export async function sendReloadTaskFailureLog(reloadParams) { }) .catch((rateLimiterRes) => { globals.logger.verbose( - `TASK FAILED NEWRELIC: Rate limiting failed. Not sending reload failure log entry to New Relic for task "${params.qs_taskName}"` + `TASK FAILED NEWRELIC: Rate limiting failed. Not sending reload failure log entry to New Relic for task "${params.qs_taskName}"`, ); globals.logger.verbose(`TASK FAILED NEWRELIC: Rate limiting details "${JSON.stringify(rateLimiterRes, null, 2)}"`); }); @@ -887,7 +887,7 @@ export function sendReloadTaskAbortedEvent(reloadParams) { .then(async (rateLimiterRes) => { try { globals.logger.info( - `TASK ABORT NEWRELIC: Rate limiting check passed for abort task event. Task name: "${params.qs_taskName}"` + `TASK ABORT NEWRELIC: Rate limiting check passed for abort task event. Task name: "${params.qs_taskName}"`, ); globals.logger.verbose(`TASK ABORT NEWRELIC: Rate limiting details "${JSON.stringify(rateLimiterRes, null, 2)}"`); @@ -943,7 +943,7 @@ export function sendReloadTaskAbortedEvent(reloadParams) { ) { // eslint-disable-next-line no-restricted-syntax for (const acct of globals.config.get( - 'Butler.incidentTool.newRelic.reloadTaskAborted.destination.event.sendToAccount.always.account' + 'Butler.incidentTool.newRelic.reloadTaskAborted.destination.event.sendToAccount.always.account', )) { tmpDestNewRelicAccounts.push(acct); } @@ -952,10 +952,10 @@ export function sendReloadTaskAbortedEvent(reloadParams) { // Send event to NR accounts specified by reload task custom property if ( globals.config.has( - 'Butler.incidentTool.newRelic.reloadTaskAborted.destination.event.sendToAccount.byCustomProperty.enable' + 'Butler.incidentTool.newRelic.reloadTaskAborted.destination.event.sendToAccount.byCustomProperty.enable', ) && globals.config.has( - 'Butler.incidentTool.newRelic.reloadTaskAborted.destination.event.sendToAccount.byCustomProperty.customPropertyName' + 'Butler.incidentTool.newRelic.reloadTaskAborted.destination.event.sendToAccount.byCustomProperty.customPropertyName', ) ) { // Get values of custom property @@ -967,7 +967,7 @@ export function sendReloadTaskAbortedEvent(reloadParams) { if ( cp.definition.name === globals.config.get( - 'Butler.incidentTool.newRelic.reloadTaskAborted.destination.event.sendToAccount.byCustomProperty.customPropertyName' + 'Butler.incidentTool.newRelic.reloadTaskAborted.destination.event.sendToAccount.byCustomProperty.customPropertyName', ) ) { tmpDestNewRelicAccounts.push(cp.value); @@ -1015,7 +1015,7 @@ export function sendReloadTaskAbortedEvent(reloadParams) { }) .catch((rateLimiterRes) => { globals.logger.verbose( - `TASK ABORT NEWRELIC: Rate limiting failed. Not sending reload aborted event to New Relic for task "${params.qs_taskName}"` + `TASK ABORT NEWRELIC: Rate limiting failed. Not sending reload aborted event to New Relic for task "${params.qs_taskName}"`, ); globals.logger.verbose(`TASK ABORT NEWRELIC: Rate limiting details "${JSON.stringify(rateLimiterRes, null, 2)}"`); }); @@ -1029,7 +1029,7 @@ export function sendReloadTaskAbortedLog(reloadParams) { .then(async (rateLimiterRes) => { try { globals.logger.info( - `TASK ABORT NEWRELIC: Rate limiting check passed for abort task log entry. Task name: "${params.qs_taskName}"` + `TASK ABORT NEWRELIC: Rate limiting check passed for abort task log entry. Task name: "${params.qs_taskName}"`, ); globals.logger.verbose(`TASK ABORT NEWRELIC: Rate limiting details "${JSON.stringify(rateLimiterRes, null, 2)}"`); @@ -1085,7 +1085,7 @@ export function sendReloadTaskAbortedLog(reloadParams) { ) { // eslint-disable-next-line no-restricted-syntax for (const acct of globals.config.get( - 'Butler.incidentTool.newRelic.reloadTaskAborted.destination.log.sendToAccount.always.account' + 'Butler.incidentTool.newRelic.reloadTaskAborted.destination.log.sendToAccount.always.account', )) { tmpDestNewRelicAccounts.push(acct); } @@ -1094,10 +1094,10 @@ export function sendReloadTaskAbortedLog(reloadParams) { // Send log entry to NR accounts specified by reload task custom property if ( globals.config.has( - 'Butler.incidentTool.newRelic.reloadTaskAborted.destination.log.sendToAccount.byCustomProperty.enable' + 'Butler.incidentTool.newRelic.reloadTaskAborted.destination.log.sendToAccount.byCustomProperty.enable', ) && globals.config.has( - 'Butler.incidentTool.newRelic.reloadTaskAborted.destination.log.sendToAccount.byCustomProperty.customPropertyName' + 'Butler.incidentTool.newRelic.reloadTaskAborted.destination.log.sendToAccount.byCustomProperty.customPropertyName', ) ) { // Get values of custom property @@ -1109,7 +1109,7 @@ export function sendReloadTaskAbortedLog(reloadParams) { if ( cp.definition.name === globals.config.get( - 'Butler.incidentTool.newRelic.reloadTaskAborted.destination.log.sendToAccount.byCustomProperty.customPropertyName' + 'Butler.incidentTool.newRelic.reloadTaskAborted.destination.log.sendToAccount.byCustomProperty.customPropertyName', ) ) { tmpDestNewRelicAccounts.push(cp.value); @@ -1157,7 +1157,7 @@ export function sendReloadTaskAbortedLog(reloadParams) { }) .catch((rateLimiterRes) => { globals.logger.verbose( - `TASK ABORT NEWRELIC: Rate limiting failed. Not sending reload abort log entry to New Relic for task "${params.qs_taskName}"` + `TASK ABORT NEWRELIC: Rate limiting failed. Not sending reload abort log entry to New Relic for task "${params.qs_taskName}"`, ); globals.logger.verbose(`TASK ABORT NEWRELIC: Rate limiting details "${JSON.stringify(rateLimiterRes, null, 2)}"`); }); diff --git a/src/lib/incident_mgmt/new_relic_service_monitor.js b/src/lib/incident_mgmt/new_relic_service_monitor.js index 8d532578..39a0dfa1 100644 --- a/src/lib/incident_mgmt/new_relic_service_monitor.js +++ b/src/lib/incident_mgmt/new_relic_service_monitor.js @@ -167,7 +167,7 @@ async function sendServiceMonitorEvent(serviceStatusParams) { .then(async (rateLimiterRes) => { try { globals.logger.info( - `SERVICE MONITOR NEWRELIC: Rate limiting check passed for service state event. Service name: "${params.serviceName}"` + `SERVICE MONITOR NEWRELIC: Rate limiting check passed for service state event. Service name: "${params.serviceName}"`, ); globals.logger.verbose(`SERVICE MONITOR NEWRELIC: Rate limiting details "${JSON.stringify(rateLimiterRes, null, 2)}"`); @@ -192,7 +192,7 @@ async function sendServiceMonitorEvent(serviceStatusParams) { } if ( globals.config.get( - 'Butler.incidentTool.newRelic.serviceMonitor.destination.event.attribute.dynamic.serviceDisplayName' + 'Butler.incidentTool.newRelic.serviceMonitor.destination.event.attribute.dynamic.serviceDisplayName', ) === true ) { serviceStateConfig.attributes.butler_serviceDisplayName = serviceStatusParams.serviceDetails.displayName; @@ -226,7 +226,7 @@ async function sendServiceMonitorEvent(serviceStatusParams) { globals.logger.verbose( `SERVICE MONITOR NEWRELIC: Sending service state event to New Relic for service "${ params.serviceName - }" to accounts: ${JSON.stringify(destNewRelicAccounts, null, 2)}` + }" to accounts: ${JSON.stringify(destNewRelicAccounts, null, 2)}`, ); sendNewRelicEvent( @@ -240,7 +240,7 @@ async function sendServiceMonitorEvent(serviceStatusParams) { serviceExePath: params.serviceDetails.exePath, serviceDependencies: params.serviceDetails.dependencies, }, - destNewRelicAccounts + destNewRelicAccounts, ); return null; } catch (err) { @@ -250,7 +250,7 @@ async function sendServiceMonitorEvent(serviceStatusParams) { }) .catch((rateLimiterRes) => { globals.logger.verbose( - `SERVICE MONITOR NEWRELIC: Rate limiting failed. Not sending service state event to New Relic for service "${params.serviceName}"` + `SERVICE MONITOR NEWRELIC: Rate limiting failed. Not sending service state event to New Relic for service "${params.serviceName}"`, ); globals.logger.verbose(`SERVICE MONITOR NEWRELIC: Rate limiting details "${JSON.stringify(rateLimiterRes, null, 2)}"`); }); @@ -264,7 +264,7 @@ async function sendServiceMonitorLog(serviceStatusParams) { .then(async (rateLimiterRes) => { try { globals.logger.info( - `SERVICE MONITOR NEWRELIC: Rate limiting check passed for service state log entry. Service name: "${params.serviceName}"` + `SERVICE MONITOR NEWRELIC: Rate limiting check passed for service state log entry. Service name: "${params.serviceName}"`, ); globals.logger.verbose(`SERVICE MONITOR NEWRELIC: Rate limiting details "${JSON.stringify(rateLimiterRes, null, 2)}"`); @@ -287,7 +287,7 @@ async function sendServiceMonitorLog(serviceStatusParams) { } if ( globals.config.get( - 'Butler.incidentTool.newRelic.serviceMonitor.destination.log.attribute.dynamic.serviceDisplayName' + 'Butler.incidentTool.newRelic.serviceMonitor.destination.log.attribute.dynamic.serviceDisplayName', ) === true ) { serviceStateConfig.attributes.butler_serviceDisplayName = serviceStatusParams.serviceDetails.displayName; @@ -321,7 +321,7 @@ async function sendServiceMonitorLog(serviceStatusParams) { globals.logger.verbose( `SERVICE MONITOR NEWRELIC: Sending service state log to New Relic for service "${ params.serviceName - }" to accounts: ${JSON.stringify(destNewRelicAccounts, null, 2)}` + }" to accounts: ${JSON.stringify(destNewRelicAccounts, null, 2)}`, ); sendNewRelicLog( @@ -335,7 +335,7 @@ async function sendServiceMonitorLog(serviceStatusParams) { serviceExePath: params.serviceDetails.exePath, serviceDependencies: params.serviceDetails.dependencies, }, - destNewRelicAccounts + destNewRelicAccounts, ); return null; } catch (err) { @@ -345,7 +345,7 @@ async function sendServiceMonitorLog(serviceStatusParams) { }) .catch((rateLimiterRes) => { globals.logger.verbose( - `SERVICE MONITOR NEWRELIC: Rate limiting failed. Not sending service state log entry to New Relic for service "${params.serviceName}"` + `SERVICE MONITOR NEWRELIC: Rate limiting failed. Not sending service state log entry to New Relic for service "${params.serviceName}"`, ); globals.logger.verbose(`SERVICE MONITOR NEWRELIC: Rate limiting details "${JSON.stringify(rateLimiterRes, null, 2)}"`); }); diff --git a/src/lib/incident_mgmt/signl4.js b/src/lib/incident_mgmt/signl4.js index 0db6304c..d2a06f6a 100644 --- a/src/lib/incident_mgmt/signl4.js +++ b/src/lib/incident_mgmt/signl4.js @@ -130,10 +130,10 @@ export function sendReloadTaskFailureNotification(reloadParams) { .then(async (rateLimiterRes) => { try { globals.logger.info( - `SIGNL4 RELOAD TASK FAILED ALERT: Rate limiting check passed for failed task notification. Task name: "${reloadParams.taskName}"` + `SIGNL4 RELOAD TASK FAILED ALERT: Rate limiting check passed for failed task notification. Task name: "${reloadParams.taskName}"`, ); globals.logger.verbose( - `SIGNL4 RELOAD TASK FAILED ALERT: Rate limiting details "${JSON.stringify(rateLimiterRes, null, 2)}"` + `SIGNL4 RELOAD TASK FAILED ALERT: Rate limiting details "${JSON.stringify(rateLimiterRes, null, 2)}"`, ); // Make sure Slack sending is enabled in the config file and that we have all required settings @@ -151,7 +151,7 @@ export function sendReloadTaskFailureNotification(reloadParams) { }) .catch((rateLimiterRes) => { globals.logger.verbose( - `SIGNL4 RELOAD TASK FAILED ALERT: Rate limiting failed. Not sending reload failure notification to Signl4 for task "${reloadParams.taskName}"` + `SIGNL4 RELOAD TASK FAILED ALERT: Rate limiting failed. Not sending reload failure notification to Signl4 for task "${reloadParams.taskName}"`, ); globals.logger.verbose(`SIGNL4 RELOAD TASK FAILED ALERT: Rate limiting details "${JSON.stringify(rateLimiterRes, null, 2)}"`); }); @@ -163,10 +163,10 @@ export function sendReloadTaskAbortedNotification(reloadParams) { .then(async (rateLimiterRes) => { try { globals.logger.info( - `SIGNL4 RELOAD TASK ABORTED ALERT: Rate limiting check passed for aborted task notification. Task name: "${reloadParams.taskName}"` + `SIGNL4 RELOAD TASK ABORTED ALERT: Rate limiting check passed for aborted task notification. Task name: "${reloadParams.taskName}"`, ); globals.logger.verbose( - `SIGNL4 RELOAD TASK ABORTED ALERT: Rate limiting details "${JSON.stringify(rateLimiterRes, null, 2)}"` + `SIGNL4 RELOAD TASK ABORTED ALERT: Rate limiting details "${JSON.stringify(rateLimiterRes, null, 2)}"`, ); // Make sure outgoing webhooks are enabled in the config file and that we have all required settings @@ -184,7 +184,7 @@ export function sendReloadTaskAbortedNotification(reloadParams) { }) .catch((rateLimiterRes) => { globals.logger.verbose( - `SIGNL4 RELOAD TASK ABORTED ALERT: Rate limiting failed. Not sending reload aborted notification to Signl4 for task "${reloadParams.taskName}"` + `SIGNL4 RELOAD TASK ABORTED ALERT: Rate limiting failed. Not sending reload aborted notification to Signl4 for task "${reloadParams.taskName}"`, ); globals.logger.verbose(`SIGNL4 RELOAD TASK ABORTED ALERT: Rate limiting details "${JSON.stringify(rateLimiterRes, null, 2)}"`); }); diff --git a/src/lib/mqtt_handlers.js b/src/lib/mqtt_handlers.js index abdc28dc..d0934c4f 100644 --- a/src/lib/mqtt_handlers.js +++ b/src/lib/mqtt_handlers.js @@ -85,7 +85,7 @@ function mqttInitHandlers() { // Connect to MQTT broker mqttClient = mqtt.connect( `mqtts://${config.get('Butler.mqttConfig.brokerHost')}:${config.get('Butler.mqttConfig.brokerPort')}`, - mqttOptions + mqttOptions, ); globals.mqttClient = mqttClient; @@ -96,8 +96,8 @@ function mqttInitHandlers() { if (!mqttClient.connected) { logger.verbose( `MQTT INIT HANDLERS: Created (but not yet connected) MQTT object for ${config.get( - 'Butler.mqttConfig.brokerHost' - )}:${config.get('Butler.mqttConfig.brokerPort')}` + 'Butler.mqttConfig.brokerHost', + )}:${config.get('Butler.mqttConfig.brokerPort')}`, ); } @@ -107,8 +107,8 @@ function mqttInitHandlers() { try { logger.info( `Connected to MQTT server ${config.get('Butler.mqttConfig.brokerHost')}:${config.get( - 'Butler.mqttConfig.brokerPort' - )}, with client ID ${mqttClient.options.clientId}` + 'Butler.mqttConfig.brokerPort', + )}, with client ID ${mqttClient.options.clientId}`, ); // Let the world know that Butler is connected to MQTT @@ -116,8 +116,8 @@ function mqttInitHandlers() { mqttClient.publish( 'qliksense/butler/mqtt/status', `Connected to MQTT broker ${config.get('Butler.mqttConfig.brokerHost')}:${config.get( - 'Butler.mqttConfig.brokerPort' - )} with client ID ${mqttClient.options.clientId}` + 'Butler.mqttConfig.brokerPort', + )} with client ID ${mqttClient.options.clientId}`, ); // Have Butler listen to all messages in the topic subtree specified in the config file diff --git a/src/lib/msteams_notification.js b/src/lib/msteams_notification.js index f467c5bc..8fe28692 100644 --- a/src/lib/msteams_notification.js +++ b/src/lib/msteams_notification.js @@ -64,7 +64,7 @@ function getTeamsReloadFailedNotificationConfigOk() { if (!globals.config.get('Butler.teamsNotification.reloadTaskFailure.enable')) { // Teams task falure notifications are disabled globals.logger.error( - "TEAMS RELOAD TASK FAILED: Reload failure Teams notifications are disabled in config file - won't send Teams message" + "TEAMS RELOAD TASK FAILED: Reload failure Teams notifications are disabled in config file - won't send Teams message", ); return false; } @@ -76,8 +76,8 @@ function getTeamsReloadFailedNotificationConfigOk() { // Invalid Teams message type globals.logger.error( `TEAMS RELOAD TASK FAILED: Invalid Teams message type: ${globals.config.get( - 'Butler.teamsNotification.reloadTaskFailure.messageType' - )}` + 'Butler.teamsNotification.reloadTaskFailure.messageType', + )}`, ); return false; } @@ -137,7 +137,7 @@ function getTeamsReloadAbortedNotificationConfigOk() { if (!globals.config.get('Butler.teamsNotification.reloadTaskAborted.enable')) { // Teams task aborted notifications are disabled globals.logger.error( - "TEAMS RELOAD TASK ABORTED: Reload aborted Teams notifications are disabled in config file - won't send Teams message" + "TEAMS RELOAD TASK ABORTED: Reload aborted Teams notifications are disabled in config file - won't send Teams message", ); return false; } @@ -149,8 +149,8 @@ function getTeamsReloadAbortedNotificationConfigOk() { // Invalid Teams message type globals.logger.error( `TEAMS RELOAD TASK ABORTED: Invalid Teams message type: ${globals.config.get( - 'Butler.teamsNotification.reloadTaskAborted.messageType' - )}` + 'Butler.teamsNotification.reloadTaskAborted.messageType', + )}`, ); return false; } @@ -215,7 +215,7 @@ function getTeamsServiceMonitorNotificationConfig(serviceStatus) { if (!globals.config.get('Butler.serviceMonitor.alertDestination.teams.enable')) { // Teams notifications are disabled globals.logger.error( - "TEAMS SERVICE MONITOR: TEAMS SERVICE MONITOR notifications are disabled in config file - won't send Teams message" + "TEAMS SERVICE MONITOR: TEAMS SERVICE MONITOR notifications are disabled in config file - won't send Teams message", ); return false; } @@ -227,8 +227,8 @@ function getTeamsServiceMonitorNotificationConfig(serviceStatus) { // Invalid Teams message type globals.logger.error( `TEAMS SERVICE MONITOR: Invalid Teams message type: ${globals.config.get( - 'Butler.teamsNotification.serviceStopped.messageType' - )}` + 'Butler.teamsNotification.serviceStopped.messageType', + )}`, ); return false; } @@ -240,8 +240,8 @@ function getTeamsServiceMonitorNotificationConfig(serviceStatus) { // Invalid Teams message type globals.logger.error( `TEAMS SERVICE MONITOR: Invalid Teams message type: ${globals.config.get( - 'Butler.teamsNotification.serviceStopped.messageType' - )}` + 'Butler.teamsNotification.serviceStopped.messageType', + )}`, ); return false; } @@ -427,7 +427,7 @@ export function sendReloadTaskFailureNotificationTeams(reloadParams) { .then(async (rateLimiterRes) => { try { globals.logger.info( - `TEAMS RELOAD TASK FAILED: Rate limiting check passed for failed task notification. Task name: "${reloadParams.taskName}"` + `TEAMS RELOAD TASK FAILED: Rate limiting check passed for failed task notification. Task name: "${reloadParams.taskName}"`, ); globals.logger.verbose(`TEAMS RELOAD TASK FAILED: Rate limiting details "${JSON.stringify(rateLimiterRes, null, 2)}"`); @@ -507,7 +507,7 @@ export function sendReloadTaskFailureNotificationTeams(reloadParams) { // Check if script log is longer than 3000 characters. Truncate if so. if (templateContext.scriptLogHead.length >= 3000) { globals.logger.warn( - `TEAMS: Script log head field is too long (${templateContext.scriptLogHead.length}), will truncate before posting to Teams.` + `TEAMS: Script log head field is too long (${templateContext.scriptLogHead.length}), will truncate before posting to Teams.`, ); templateContext.scriptLogHead = templateContext.scriptLogHead .replaceAll('&', '&') @@ -531,7 +531,7 @@ export function sendReloadTaskFailureNotificationTeams(reloadParams) { if (templateContext.scriptLogTail.length >= 3000) { globals.logger.warn( - `TEAMS: Script log head field is too long (${templateContext.scriptLogTail.length}), will truncate before posting to Teams.` + `TEAMS: Script log head field is too long (${templateContext.scriptLogTail.length}), will truncate before posting to Teams.`, ); templateContext.scriptLogTail = templateContext.scriptLogTail .replaceAll('&', '&') @@ -562,7 +562,7 @@ export function sendReloadTaskFailureNotificationTeams(reloadParams) { }) .catch((rateLimiterRes) => { globals.logger.warn( - `TEAMS RELOAD TASK FAILED: Rate limiting failed. Not sending reload notification Teams for task "${reloadParams.taskName}"` + `TEAMS RELOAD TASK FAILED: Rate limiting failed. Not sending reload notification Teams for task "${reloadParams.taskName}"`, ); globals.logger.debug(`TEAMS RELOAD TASK FAILED: Rate limiting details "${JSON.stringify(rateLimiterRes, null, 2)}"`); }); @@ -574,7 +574,7 @@ export function sendReloadTaskAbortedNotificationTeams(reloadParams) { .then(async (rateLimiterRes) => { try { globals.logger.info( - `TEAMS RELOAD TASK ABORTED: Rate limiting check passed for aborted task notification. Task name: "${reloadParams.taskName}"` + `TEAMS RELOAD TASK ABORTED: Rate limiting check passed for aborted task notification. Task name: "${reloadParams.taskName}"`, ); globals.logger.verbose(`TEAMS RELOAD TASK ABORTED: Rate limiting details "${JSON.stringify(rateLimiterRes, null, 2)}"`); @@ -653,7 +653,7 @@ export function sendReloadTaskAbortedNotificationTeams(reloadParams) { // Check if script log is longer than 3000 characters. Truncate if so. if (templateContext.scriptLogHead.length >= 3000) { globals.logger.warn( - `TEAMS: Script log head field is too long (${templateContext.scriptLogHead.length}), will truncate before posting to Teams.` + `TEAMS: Script log head field is too long (${templateContext.scriptLogHead.length}), will truncate before posting to Teams.`, ); templateContext.scriptLogHead = templateContext.scriptLogHead .replaceAll('&', '&') @@ -677,7 +677,7 @@ export function sendReloadTaskAbortedNotificationTeams(reloadParams) { if (templateContext.scriptLogTail.length >= 3000) { globals.logger.warn( - `TEAMS: Script log head field is too long (${templateContext.scriptLogTail.length}), will truncate before posting to Teams.` + `TEAMS: Script log head field is too long (${templateContext.scriptLogTail.length}), will truncate before posting to Teams.`, ); templateContext.scriptLogTail = templateContext.scriptLogTail .replaceAll('&', '&') @@ -708,7 +708,7 @@ export function sendReloadTaskAbortedNotificationTeams(reloadParams) { }) .catch((rateLimiterRes) => { globals.logger.verbose( - `TEAMS RELOAD TASK ABORTED: Rate limiting failed. Not sending reload notification Teams for task "${reloadParams.taskName}"` + `TEAMS RELOAD TASK ABORTED: Rate limiting failed. Not sending reload notification Teams for task "${reloadParams.taskName}"`, ); globals.logger.verbose(`TEAMS RELOAD TASK ABORTED: Rate limiting details "${JSON.stringify(rateLimiterRes, null, 2)}"`); }); @@ -720,7 +720,7 @@ export function sendServiceMonitorNotificationTeams(serviceParams) { .then(async (rateLimiterRes) => { try { globals.logger.info( - `TEAMS SERVICE MONITOR: Rate limiting check passed for service monitor notification. Host: "${serviceParams.host}", service: "${serviceParams.serviceName}"` + `TEAMS SERVICE MONITOR: Rate limiting check passed for service monitor notification. Host: "${serviceParams.host}", service: "${serviceParams.serviceName}"`, ); globals.logger.verbose(`TEAMS SERVICE MONITOR: Rate limiting details "${JSON.stringify(rateLimiterRes, null, 2)}"`); @@ -756,7 +756,7 @@ export function sendServiceMonitorNotificationTeams(serviceParams) { }) .catch((rateLimiterRes) => { globals.logger.verbose( - `TEAMS SERVICE MONITOR: Rate limiting failed. Not sending service monitor notification for service "${serviceParams.serviceName}" on host "${serviceParams.host}"` + `TEAMS SERVICE MONITOR: Rate limiting failed. Not sending service monitor notification for service "${serviceParams.serviceName}" on host "${serviceParams.host}"`, ); globals.logger.verbose(`TEAMS SERVICE MONITOR: Rate limiting details "${JSON.stringify(rateLimiterRes, null, 2)}"`); }); diff --git a/src/lib/post_to_influxdb.js b/src/lib/post_to_influxdb.js index 86a7e697..93b08529 100755 --- a/src/lib/post_to_influxdb.js +++ b/src/lib/post_to_influxdb.js @@ -27,7 +27,7 @@ export function postButlerMemoryUsageToInfluxdb(memory) { .then(() => { globals.logger.silly( - `INFLUXDB MEMORY USAGE: Influxdb datapoint for Butler INFLUXDB MEMORY USAGE: ${JSON.stringify(datapoint, null, 2)}` + `INFLUXDB MEMORY USAGE: Influxdb datapoint for Butler INFLUXDB MEMORY USAGE: ${JSON.stringify(datapoint, null, 2)}`, ); datapoint = null; @@ -156,8 +156,8 @@ export async function postQlikSenseServerLicenseStatusToInfluxDB(qlikSenseServer `INFLUXDB QLIK SENSE SERVER LICENSE STATUS: Influxdb datapoint for Qlik Sense server license status: ${JSON.stringify( datapoint, null, - 2 - )}` + 2, + )}`, ); datapoint = null; @@ -359,7 +359,7 @@ export async function postQlikSenseLicenseStatusToInfluxDB(qlikSenseLicenseStatu await globals.influx.writePoints(deepClonedDatapoint); globals.logger.silly( - `INFLUXDB QLIK SENSE LICENSE STATUS: Influxdb datapoint for Qlik Sense license status: ${JSON.stringify(datapoint, null, 2)}` + `INFLUXDB QLIK SENSE LICENSE STATUS: Influxdb datapoint for Qlik Sense license status: ${JSON.stringify(datapoint, null, 2)}`, ); datapoint = null; @@ -407,7 +407,7 @@ export async function postQlikSenseLicenseReleasedToInfluxDB(licenseInfo) { await globals.influx.writePoints(deepClonedDatapoint); globals.logger.silly( - `INFLUXDB QLIK SENSE LICENSE RELEASE: Influxdb datapoint for released Qlik Sense license: ${JSON.stringify(datapoint, null, 2)}` + `INFLUXDB QLIK SENSE LICENSE RELEASE: Influxdb datapoint for released Qlik Sense license: ${JSON.stringify(datapoint, null, 2)}`, ); datapoint = null; @@ -417,7 +417,7 @@ export async function postQlikSenseLicenseReleasedToInfluxDB(licenseInfo) { // Function to store windows service status to InfluxDB export function postWindowsServiceStatusToInfluxDB(serviceStatus) { globals.logger.verbose( - `INFLUXDB WINDOWS SERVICE STATUS: Sending service status to InfluxDB: service="${serviceStatus.serviceFriendlyName}", status="${serviceStatus.serviceStatus}"` + `INFLUXDB WINDOWS SERVICE STATUS: Sending service status to InfluxDB: service="${serviceStatus.serviceFriendlyName}", status="${serviceStatus.serviceStatus}"`, ); // Create lookup table for Windows service state to numeric value, starting with 1 for stopped @@ -471,8 +471,8 @@ export function postWindowsServiceStatusToInfluxDB(serviceStatus) { `INFLUXDB WINDOWS SERVICE STATUS: Influxdb datapoint for INFLUXDB WINDOWS SERVICE STATUS: ${JSON.stringify( datapoint, null, - 2 - )}` + 2, + )}`, ); datapoint = null; @@ -571,7 +571,7 @@ export function postReloadTaskSuccessNotificationInfluxDb(reloadParams) { .then(() => { globals.logger.silly( - `INFLUXDB RELOAD TASK SUCCESS: Influxdb datapoint for reload task notification: ${JSON.stringify(datapoint, null, 2)}` + `INFLUXDB RELOAD TASK SUCCESS: Influxdb datapoint for reload task notification: ${JSON.stringify(datapoint, null, 2)}`, ); datapoint = null; @@ -696,7 +696,7 @@ export function postReloadTaskFailureNotificationInfluxDb(reloadParams) { .then(() => { globals.logger.silly( - `INFLUXDB RELOAD TASK FAILED: Influxdb datapoint for reload task notification: ${JSON.stringify(datapoint, null, 2)}` + `INFLUXDB RELOAD TASK FAILED: Influxdb datapoint for reload task notification: ${JSON.stringify(datapoint, null, 2)}`, ); datapoint = null; diff --git a/src/lib/post_to_new_relic.js b/src/lib/post_to_new_relic.js index e000eb9e..3350556a 100755 --- a/src/lib/post_to_new_relic.js +++ b/src/lib/post_to_new_relic.js @@ -131,17 +131,17 @@ export async function postButlerUptimeToNewRelic(fields) { const res = await axios.post(remoteUrl, payload, { headers, timeout: 5000 }); globals.logger.debug( - `NEW RELIC UPTIME: Result code from posting to New Relic account ${newRelicConfig[0].accountId}: ${res.status}, ${res.statusText}` + `NEW RELIC UPTIME: Result code from posting to New Relic account ${newRelicConfig[0].accountId}: ${res.status}, ${res.statusText}`, ); if (res.status === 200 || res.status === 202) { // Posting done without error globals.logger.verbose( - `NEW RELIC UPTIME: Sent Butler memory usage data to New Relic account ${newRelicConfig[0].accountId}` + `NEW RELIC UPTIME: Sent Butler memory usage data to New Relic account ${newRelicConfig[0].accountId}`, ); // reply.type('application/json; charset=utf-8').code(201).send(JSON.stringify(request.body)); } else { globals.logger.error( - `NEW RELIC UPTIME: Error code from posting memory usage data to New Relic account ${newRelicConfig[0].accountId}: ${res.status}, ${res.statusText}` + `NEW RELIC UPTIME: Error code from posting memory usage data to New Relic account ${newRelicConfig[0].accountId}: ${res.status}, ${res.statusText}`, ); } } diff --git a/src/lib/qliksense_license.js b/src/lib/qliksense_license.js index b879faa3..57c48667 100644 --- a/src/lib/qliksense_license.js +++ b/src/lib/qliksense_license.js @@ -152,7 +152,7 @@ async function checkQlikSenseServerLicenseStatus(config, logger) { if (licenseExpired === true) { globals.mqttClient.publish( config.get('Butler.mqttConfig.qlikSenseServerLicenseExpireTopic'), - `Qlik Sense server license expired on ${expiryDateStr}` + `Qlik Sense server license expired on ${expiryDateStr}`, ); } else if (daysUntilExpiry <= config.get('Butler.qlikSenseLicense.serverLicenseMonitor.alert.thresholdDays')) { const mqttAlertPayload = `Qlik Sense server license is about to expire in ${daysUntilExpiry} days, on ${expiryDateStr}`; @@ -280,7 +280,7 @@ async function licenseReleaseProfessional(config, logger, qrsInstance) { // Is status code other than 200 or body is empty? if (result1.statusCode !== 200 || !result1.body) { logger.error( - `QLIKSENSE LICENSE RELEASE PROFESSIONAL: Could not get list of assigned professional licenses. HTTP status code ${result1.statusCode}` + `QLIKSENSE LICENSE RELEASE PROFESSIONAL: Could not get list of assigned professional licenses. HTTP status code ${result1.statusCode}`, ); return false; } @@ -308,18 +308,18 @@ async function licenseReleaseProfessional(config, logger, qrsInstance) { const res = await qrsInstance.Get(`user/${license.user.id}`); if (res.statusCode !== 200 || !res.body) { logger.error( - `QLIKSENSE LICENSE RELEASE PROFESSIONAL: Failed getting user info for user [${license.user.id}] ${license.user.userDirectory}\\${license.user.userId}` + `QLIKSENSE LICENSE RELEASE PROFESSIONAL: Failed getting user info for user [${license.user.id}] ${license.user.userDirectory}\\${license.user.userId}`, ); return false; } currentUser = res.body; } catch (err) { logger.error( - `QLIKSENSE LICENSE RELEASE PROFESSIONAL: Failed getting user info for user [${license.user.id}] ${license.user.userDirectory}\\${license.user.userId}` + `QLIKSENSE LICENSE RELEASE PROFESSIONAL: Failed getting user info for user [${license.user.id}] ${license.user.userDirectory}\\${license.user.userId}`, ); if (err.stack) { logger.error( - `QLIKSENSE LICENSE RELEASE PROFESSIONAL: Failed getting user info for user [${license.user.id}] ${license.user.userDirectory}\\${license.user.userId}. ${err.stack}` + `QLIKSENSE LICENSE RELEASE PROFESSIONAL: Failed getting user info for user [${license.user.id}] ${license.user.userDirectory}\\${license.user.userId}. ${err.stack}`, ); } return false; @@ -476,7 +476,7 @@ async function licenseReleaseProfessional(config, logger, qrsInstance) { // Should currentUser be released? if (!doNotRelease) { logger.info( - `QLIKSENSE LICENSE RELEASE PROFESSIONAL: Adding user ${license.user.userDirectory}\\${license.user.userId} (days since last use: ${daysSinceLastUse}) to releaseProfessional array` + `QLIKSENSE LICENSE RELEASE PROFESSIONAL: Adding user ${license.user.userDirectory}\\${license.user.userId} (days since last use: ${daysSinceLastUse}) to releaseProfessional array`, ); releaseProfessional.push({ licenseId: license.id, @@ -486,14 +486,14 @@ async function licenseReleaseProfessional(config, logger, qrsInstance) { }); } else { logger.info( - `QLIKSENSE LICENSE RELEASE PROFESSIONAL: License for user ${license.user.userDirectory}\\${license.user.userId} not released because: ${doNotReleaseReason}` + `QLIKSENSE LICENSE RELEASE PROFESSIONAL: License for user ${license.user.userDirectory}\\${license.user.userId} not released because: ${doNotReleaseReason}`, ); } } } logger.verbose( - `QLIKSENSE LICENSE RELEASE PROFESSIONAL: Professional licenses to be released: ${JSON.stringify(releaseProfessional, null, 2)}` + `QLIKSENSE LICENSE RELEASE PROFESSIONAL: Professional licenses to be released: ${JSON.stringify(releaseProfessional, null, 2)}`, ); // Is license release dry-run enabled? If so, do not release any licenses @@ -504,7 +504,7 @@ async function licenseReleaseProfessional(config, logger, qrsInstance) { // eslint-disable-next-line no-restricted-syntax for (const licenseRelease of releaseProfessional) { logger.info( - `QLIKSENSE LICENSE RELEASE PROFESSIONAL: Releasing license for user ${licenseRelease.userDir}\\${licenseRelease.userId} (days since last use: ${licenseRelease.daysSinceLastUse})` + `QLIKSENSE LICENSE RELEASE PROFESSIONAL: Releasing license for user ${licenseRelease.userDir}\\${licenseRelease.userId} (days since last use: ${licenseRelease.daysSinceLastUse})`, ); // Release license @@ -566,7 +566,7 @@ async function licenseReleaseAnalyzer(config, logger, qrsInstance) { // Is status code 200 or body is empty? if (result3.statusCode !== 200 || !result3.body) { logger.error( - `QLIKSENSE LICENSE RELEASE ANALYZER: Could not get list of assigned analyzer licenses. HTTP status code ${result3.statusCode}` + `QLIKSENSE LICENSE RELEASE ANALYZER: Could not get list of assigned analyzer licenses. HTTP status code ${result3.statusCode}`, ); return false; } @@ -594,18 +594,18 @@ async function licenseReleaseAnalyzer(config, logger, qrsInstance) { const res = await qrsInstance.Get(`user/${license.user.id}`); if (res.statusCode !== 200 || !res.body) { logger.error( - `QLIKSENSE LICENSE RELEASE ANALYZER: Failed getting user info for user [${license.user.id}] ${license.user.userDirectory}\\${license.user.userId}` + `QLIKSENSE LICENSE RELEASE ANALYZER: Failed getting user info for user [${license.user.id}] ${license.user.userDirectory}\\${license.user.userId}`, ); return false; } currentUser = res.body; } catch (err) { logger.error( - `QLIKSENSE LICENSE RELEASE ANALYZER: Failed getting user info for user [${license.user.id}] ${license.user.userDirectory}\\${license.user.userId}` + `QLIKSENSE LICENSE RELEASE ANALYZER: Failed getting user info for user [${license.user.id}] ${license.user.userDirectory}\\${license.user.userId}`, ); if (err.stack) { logger.error( - `QLIKSENSE LICENSE RELEASE ANALYZER: Failed getting user info for user [${license.user.id}] ${license.user.userDirectory}\\${license.user.userId}. ${err.stack}` + `QLIKSENSE LICENSE RELEASE ANALYZER: Failed getting user info for user [${license.user.id}] ${license.user.userDirectory}\\${license.user.userId}. ${err.stack}`, ); } return false; @@ -756,7 +756,7 @@ async function licenseReleaseAnalyzer(config, logger, qrsInstance) { // Should currentUser be released? if (!doNotRelease) { logger.info( - `QLIKSENSE LICENSE RELEASE ANALYZER: Adding user ${license.user.userDirectory}\\${license.user.userId} (days since last use: ${daysSinceLastUse}) to releaseAnalyzer array` + `QLIKSENSE LICENSE RELEASE ANALYZER: Adding user ${license.user.userDirectory}\\${license.user.userId} (days since last use: ${daysSinceLastUse}) to releaseAnalyzer array`, ); releaseAnalyzer.push({ licenseId: license.id, @@ -766,7 +766,7 @@ async function licenseReleaseAnalyzer(config, logger, qrsInstance) { }); } else { logger.info( - `QLIKSENSE LICENSE RELEASE ANALYZER: License for user ${license.user.userDirectory}\\${license.user.userId} not released because: ${doNotReleaseReason}` + `QLIKSENSE LICENSE RELEASE ANALYZER: License for user ${license.user.userDirectory}\\${license.user.userId} not released because: ${doNotReleaseReason}`, ); } } @@ -782,7 +782,7 @@ async function licenseReleaseAnalyzer(config, logger, qrsInstance) { // eslint-disable-next-line no-restricted-syntax for (const licenseRelease of releaseAnalyzer) { logger.info( - `QLIKSENSE LICENSE RELEASE ANALYZER: Releasing license for user ${licenseRelease.userDir}\\${licenseRelease.userId} (days since last use: ${licenseRelease.daysSinceLastUse})` + `QLIKSENSE LICENSE RELEASE ANALYZER: Releasing license for user ${licenseRelease.userDir}\\${licenseRelease.userId} (days since last use: ${licenseRelease.daysSinceLastUse})`, ); // Release license diff --git a/src/lib/scheduler.js b/src/lib/scheduler.js index 954a7128..b04afb9c 100644 --- a/src/lib/scheduler.js +++ b/src/lib/scheduler.js @@ -26,7 +26,7 @@ function addCronEntry(newSchedule) { // eslint-disable-next-line no-unneeded-ternary newSchedule.startupState === 'started' || newSchedule.startupState === 'start' ? true : false, timeZone: newSchedule.timeZone, - } + }, ); } @@ -50,7 +50,7 @@ export function addSchedule(newSchedule) { globals.logger.verbose(`SCHEDULER: Added new schedule: ${JSON.stringify(newSchedule, null, 2)}`); } catch (err) { globals.logger.error( - `SCHEDULER: Failed adding new schedule ${JSON.stringify(newSchedule, null, 2)}: ${JSON.stringify(err, null, 2)}` + `SCHEDULER: Failed adding new schedule ${JSON.stringify(newSchedule, null, 2)}: ${JSON.stringify(err, null, 2)}`, ); } } diff --git a/src/lib/scriptlog.js b/src/lib/scriptlog.js index e6aee7bb..10de9ee5 100644 --- a/src/lib/scriptlog.js +++ b/src/lib/scriptlog.js @@ -301,7 +301,7 @@ export async function failedTaskStoreLogOnDisk(reloadParams) { reloadLogDir, `${reloadParams.logTimeStamp.slice(0, 19).replace(/ /g, '_').replace(/:/g, '-')}_appId=${reloadParams.appId}_taskId=${ reloadParams.taskId - }.log` + }.log`, ); globals.logger.info(`SCRIPTLOG STORE: Writing failed task script log: ${fileName}`); diff --git a/src/lib/service_monitor.js b/src/lib/service_monitor.js index 0b143c83..5996127e 100644 --- a/src/lib/service_monitor.js +++ b/src/lib/service_monitor.js @@ -43,7 +43,7 @@ const serviceMonitorMqttSend1 = (config, logger, svc) => { config.get('Butler.mqttConfig.serviceStoppedTopic').length === 0 ) { logger.verbose( - `"${svc.serviceName}" Windows service on host "${svc.host}" stopped. No MQTT topic defined in config entry "Butler.mqttConfig.serviceStoppedTopic"` + `"${svc.serviceName}" Windows service on host "${svc.host}" stopped. No MQTT topic defined in config entry "Butler.mqttConfig.serviceStoppedTopic"`, ); } else { logger.verbose(`Sending service stopped alert to MQTT for service "${svc.serviceName}" on host "${svc.host}"`); @@ -60,7 +60,7 @@ const serviceMonitorMqttSend1 = (config, logger, svc) => { serviceStatus: svc.serviceStatus, servicePrevStatus: svc.prevState, serviceStatusChanged: svc.stateChanged, - }) + }), ); } } else if (svc.serviceStatus === 'RUNNING') { @@ -70,7 +70,7 @@ const serviceMonitorMqttSend1 = (config, logger, svc) => { config.get('Butler.mqttConfig.serviceRunningTopic').length === 0 ) { logger.verbose( - `"${svc.serviceName}" Windows service on host "${svc.host}" is running. No MQTT topic defined in config entry "Butler.mqttConfig.serviceRunningTopic"` + `"${svc.serviceName}" Windows service on host "${svc.host}" is running. No MQTT topic defined in config entry "Butler.mqttConfig.serviceRunningTopic"`, ); } else { logger.verbose(`Sending service running message to MQTT for service "${svc.serviceName}" on host "${svc.host}"`); @@ -87,7 +87,7 @@ const serviceMonitorMqttSend1 = (config, logger, svc) => { serviceStatus: svc.serviceStatus, servicePrevStatus: svc.prevState, serviceStatusChanged: svc.stateChanged, - }) + }), ); } } @@ -102,7 +102,7 @@ const serviceMonitorMqttSend2 = (config, logger, svc) => { logger.verbose(`"${svc.serviceName}"No MQTT topic defined in config entry "Butler.mqttConfig.serviceStatusTopic"`); } else { logger.verbose( - `MQTT WINDOWS SERVICE STATUS: Sending service status to MQTT: service="${svc.serviceDetails.displayName}", status="${svc.serviceStatus}"` + `MQTT WINDOWS SERVICE STATUS: Sending service status to MQTT: service="${svc.serviceDetails.displayName}", status="${svc.serviceStatus}"`, ); globals.mqttClient.publish( @@ -116,7 +116,7 @@ const serviceMonitorMqttSend2 = (config, logger, svc) => { serviceStartType: svc.serviceDetails.startType, serviceExePath: svc.serviceDetails.exePath, serviceStatus: svc.serviceStatus, - }) + }), ); } }; @@ -141,7 +141,7 @@ const verifyServicesExist = async (config, logger) => { // eslint-disable-next-line no-restricted-syntax for (const service of servicesToCheck) { logger.verbose( - `VERIFY WIN SERVICES EXIST: Checking status of Windows service ${service.name} (="${service.friendlyName}") on host ${host.host}` + `VERIFY WIN SERVICES EXIST: Checking status of Windows service ${service.name} (="${service.friendlyName}") on host ${host.host}`, ); let serviceExists; @@ -150,18 +150,18 @@ const verifyServicesExist = async (config, logger) => { serviceExists = serviceStatusAll.find((svc) => svc.name === service.name); } catch (err) { logger.error( - `VERIFY WIN SERVICES EXIST: Error verifying existence and reachability of service ${service.name} on host ${host.host}: ${err}` + `VERIFY WIN SERVICES EXIST: Error verifying existence and reachability of service ${service.name} on host ${host.host}: ${err}`, ); result = false; } if (serviceExists) { logger.verbose( - `VERIFY WIN SERVICES EXIST: Windows service ${service.name} (="${service.friendlyName}") on host ${host.host} exists.` + `VERIFY WIN SERVICES EXIST: Windows service ${service.name} (="${service.friendlyName}") on host ${host.host} exists.`, ); } else { logger.error( - `VERIFY WIN SERVICES EXIST: Windows service ${service.name} (="${service.friendlyName}") on host ${host.host} does not exist or cannot be reached.` + `VERIFY WIN SERVICES EXIST: Windows service ${service.name} (="${service.friendlyName}") on host ${host.host} does not exist or cannot be reached.`, ); result = false; } @@ -195,7 +195,7 @@ const checkServiceStatus = async (config, logger, isFirstCheck = false) => { if (svcMonitored === undefined) { logger.error( - `Service ${service.name} (="${service.friendlyName}") on host ${host.host} does not exist or cannot be reached.` + `Service ${service.name} (="${service.friendlyName}") on host ${host.host} does not exist or cannot be reached.`, ); return; } @@ -602,12 +602,12 @@ async function setupServiceMonitorTimer(config, logger) { } } else { logger.error( - 'At least one Windows service does not exist or could not be reached. Monitoring of Windows services is disabled.' + 'At least one Windows service does not exist or could not be reached. Monitoring of Windows services is disabled.', ); } } else { logger.warn( - `Not running on Windows, service monitoring will not work. Current platform is: ${hostInfo.si.os.platform}, distro: ${hostInfo.si.os.distro}, release: ${hostInfo.si.os.release}` + `Not running on Windows, service monitoring will not work. Current platform is: ${hostInfo.si.os.platform}, distro: ${hostInfo.si.os.distro}, release: ${hostInfo.si.os.release}`, ); } } diff --git a/src/lib/service_uptime.js b/src/lib/service_uptime.js index f07f531a..e0a486d9 100644 --- a/src/lib/service_uptime.js +++ b/src/lib/service_uptime.js @@ -61,8 +61,8 @@ function serviceUptimeStart() { globals.logger.log( uptimeLogLevel, `Iteration # ${formatter.format( - startIterations - )}, Uptime: ${uptimeString}, Heap used ${heapUsedMByte} MB of total heap ${heapTotalMByte} MB. External (off-heap): ${externalMemoryMByte} MB. Memory allocated to process: ${processMemoryMByte} MB.` + startIterations, + )}, Uptime: ${uptimeString}, Heap used ${heapUsedMByte} MB of total heap ${heapTotalMByte} MB. External (off-heap): ${externalMemoryMByte} MB. Memory allocated to process: ${processMemoryMByte} MB.`, ); // Store to Influxdb if enabled diff --git a/src/lib/slack_notification.js b/src/lib/slack_notification.js index 2a3d577c..6a4d7088 100644 --- a/src/lib/slack_notification.js +++ b/src/lib/slack_notification.js @@ -64,7 +64,7 @@ function getSlackReloadFailedNotificationConfigOk() { if (!globals.config.get('Butler.slackNotification.reloadTaskFailure.enable')) { // Slack task falure notifications are disabled globals.logger.error( - "SLACK RELOAD TASK FAILED: Reload failure Slack notifications are disabled in config file - won't send Slack message" + "SLACK RELOAD TASK FAILED: Reload failure Slack notifications are disabled in config file - won't send Slack message", ); return false; } @@ -76,8 +76,8 @@ function getSlackReloadFailedNotificationConfigOk() { // Invalid Slack message type globals.logger.error( `SLACK RELOAD TASK FAILED: Invalid Slack message type: ${globals.config.get( - 'Butler.slackNotification.reloadTaskFailure.messageType' - )}` + 'Butler.slackNotification.reloadTaskFailure.messageType', + )}`, ); return false; } @@ -146,7 +146,7 @@ function getSlackReloadAbortedNotificationConfigOk() { if (!globals.config.get('Butler.slackNotification.reloadTaskAborted.enable')) { // Slack task aborted notifications are disabled globals.logger.error( - "SLACK RELOAD TASK ABORTED: Reload aborted Slack notifications are disabled in config file - won't send Slack message" + "SLACK RELOAD TASK ABORTED: Reload aborted Slack notifications are disabled in config file - won't send Slack message", ); return false; } @@ -158,8 +158,8 @@ function getSlackReloadAbortedNotificationConfigOk() { // Invalid Slack message type globals.logger.error( `SLACK RELOAD TASK ABORTED: Invalid Slack message type: ${globals.config.get( - 'Butler.slackNotification.reloadTaskAborted.messageType' - )}` + 'Butler.slackNotification.reloadTaskAborted.messageType', + )}`, ); return false; } @@ -230,7 +230,7 @@ function getSlackServiceMonitorNotificationConfig(serviceStatus) { if (!globals.config.get('Butler.serviceMonitor.alertDestination.slack.enable')) { // Slack notifications are disabled globals.logger.error( - "SLACK SERVICE MONITOR: SLACK SERVICE MONITOR notifications are disabled in config file - won't send Slack message" + "SLACK SERVICE MONITOR: SLACK SERVICE MONITOR notifications are disabled in config file - won't send Slack message", ); return false; } @@ -242,8 +242,8 @@ function getSlackServiceMonitorNotificationConfig(serviceStatus) { // Invalid Slack message type globals.logger.error( `SLACK SERVICE MONITOR: Invalid Slack message type: ${globals.config.get( - 'Butler.slackNotification.serviceStopped.messageType' - )}` + 'Butler.slackNotification.serviceStopped.messageType', + )}`, ); return false; } @@ -255,8 +255,8 @@ function getSlackServiceMonitorNotificationConfig(serviceStatus) { // Invalid Slack message type globals.logger.error( `SLACK SERVICE MONITOR: Invalid Slack message type: ${globals.config.get( - 'Butler.slackNotification.serviceStarted.messageType' - )}` + 'Butler.slackNotification.serviceStarted.messageType', + )}`, ); return false; } @@ -466,7 +466,7 @@ export function sendReloadTaskFailureNotificationSlack(reloadParams) { .then(async (rateLimiterRes) => { try { globals.logger.info( - `SLACK RELOAD TASK FAILED: Rate limiting check passed for failed task notification. Task name: "${reloadParams.taskName}"` + `SLACK RELOAD TASK FAILED: Rate limiting check passed for failed task notification. Task name: "${reloadParams.taskName}"`, ); globals.logger.verbose(`SLACK RELOAD TASK FAILED: Rate limiting details "${JSON.stringify(rateLimiterRes, null, 2)}"`); @@ -562,7 +562,7 @@ export function sendReloadTaskFailureNotificationSlack(reloadParams) { // https://api.slack.com/reference/block-kit/blocks#section_fields if (templateContext.scriptLogHead.length >= 3000) { globals.logger.warn( - `SLACK: Script log head field is too long (${templateContext.scriptLogHead.length}), will truncate before posting to Slack.` + `SLACK: Script log head field is too long (${templateContext.scriptLogHead.length}), will truncate before posting to Slack.`, ); templateContext.scriptLogHead = templateContext.scriptLogHead .replaceAll('&', '&') @@ -586,7 +586,7 @@ export function sendReloadTaskFailureNotificationSlack(reloadParams) { if (templateContext.scriptLogTail.length >= 3000) { globals.logger.warn( - `SLACK: Script log head field is too long (${templateContext.scriptLogTail.length}), will truncate before posting to Slack.` + `SLACK: Script log head field is too long (${templateContext.scriptLogTail.length}), will truncate before posting to Slack.`, ); templateContext.scriptLogTail = templateContext.scriptLogTail .replaceAll('&', '&') @@ -616,7 +616,7 @@ export function sendReloadTaskFailureNotificationSlack(reloadParams) { }) .catch((err) => { globals.logger.warn( - `SLACK RELOAD TASK FAILED: Rate limiting failed. Not sending reload notification Slack for task "${reloadParams.taskName}"` + `SLACK RELOAD TASK FAILED: Rate limiting failed. Not sending reload notification Slack for task "${reloadParams.taskName}"`, ); globals.logger.debug(`SLACK RELOAD TASK FAILED: Rate limiting details "${JSON.stringify(err, null, 2)}"`); }); @@ -628,7 +628,7 @@ export function sendReloadTaskAbortedNotificationSlack(reloadParams) { .then(async (rateLimiterRes) => { try { globals.logger.info( - `SLACK RELOAD TASK ABORTED: Rate limiting check passed for aborted task notification. Task name: "${reloadParams.taskName}"` + `SLACK RELOAD TASK ABORTED: Rate limiting check passed for aborted task notification. Task name: "${reloadParams.taskName}"`, ); globals.logger.verbose(`SLACK RELOAD TASK ABORTED: Rate limiting details "${JSON.stringify(rateLimiterRes, null, 2)}"`); @@ -709,7 +709,7 @@ export function sendReloadTaskAbortedNotificationSlack(reloadParams) { // https://api.slack.com/reference/block-kit/blocks#section_fields if (templateContext.scriptLogHead.length >= 3000) { globals.logger.warn( - `SLACK: Script log head field is too long (${templateContext.scriptLogHead.length}), will truncate before posting to Slack.` + `SLACK: Script log head field is too long (${templateContext.scriptLogHead.length}), will truncate before posting to Slack.`, ); templateContext.scriptLogHead = templateContext.scriptLogHead .replaceAll('&', '&') @@ -733,7 +733,7 @@ export function sendReloadTaskAbortedNotificationSlack(reloadParams) { if (templateContext.scriptLogTail.length >= 3000) { globals.logger.warn( - `SLACK: Script log head field is too long (${templateContext.scriptLogTail.length}), will truncate before posting to Slack.` + `SLACK: Script log head field is too long (${templateContext.scriptLogTail.length}), will truncate before posting to Slack.`, ); templateContext.scriptLogTail = templateContext.scriptLogTail .replaceAll('&', '&') @@ -763,7 +763,7 @@ export function sendReloadTaskAbortedNotificationSlack(reloadParams) { }) .catch((err) => { globals.logger.verbose( - `SLACK RELOAD TASK ABORTED: Rate limiting failed. Not sending reload notification Slack for task "${reloadParams.taskName}"` + `SLACK RELOAD TASK ABORTED: Rate limiting failed. Not sending reload notification Slack for task "${reloadParams.taskName}"`, ); globals.logger.verbose(`SLACK RELOAD TASK ABORTED: Rate limiting details "${JSON.stringify(err, null, 2)}"`); }); @@ -775,7 +775,7 @@ export function sendServiceMonitorNotificationSlack(serviceParams) { .then(async (rateLimiterRes) => { try { globals.logger.info( - `SLACK SERVICE MONITOR: Rate limiting check passed for service monitor notification. Host: "${serviceParams.host}", service: "${serviceParams.serviceName}"` + `SLACK SERVICE MONITOR: Rate limiting check passed for service monitor notification. Host: "${serviceParams.host}", service: "${serviceParams.serviceName}"`, ); globals.logger.verbose(`SLACK SERVICE MONITOR: Rate limiting details "${JSON.stringify(rateLimiterRes, null, 2)}"`); @@ -809,7 +809,7 @@ export function sendServiceMonitorNotificationSlack(serviceParams) { }) .catch((err) => { globals.logger.warn( - `SLACK SERVICE MONITOR: Rate limiting failed. Not sending service monitor notification for service "${serviceParams.serviceName}" on host "${serviceParams.host}"` + `SLACK SERVICE MONITOR: Rate limiting failed. Not sending service monitor notification for service "${serviceParams.serviceName}" on host "${serviceParams.host}"`, ); globals.logger.debug(`SLACK SERVICE MONITOR: Rate limiting details "${JSON.stringify(err, null, 2)}"`); }); diff --git a/src/lib/smtp.js b/src/lib/smtp.js index 05767d79..be1fcfbe 100644 --- a/src/lib/smtp.js +++ b/src/lib/smtp.js @@ -360,13 +360,13 @@ export async function sendReloadTaskFailureNotificationEmail(reloadParams) { // 4 Remove any duplicate recipients in send list const emailAlertCpName = globals.config.get( - 'Butler.emailNotification.reloadTaskFailure.alertEnableByCustomProperty.customPropertyName' + 'Butler.emailNotification.reloadTaskFailure.alertEnableByCustomProperty.customPropertyName', ); const emailAlertCpEnabledValue = globals.config.get( - 'Butler.emailNotification.reloadTaskFailure.alertEnableByCustomProperty.enabledValue' + 'Butler.emailNotification.reloadTaskFailure.alertEnableByCustomProperty.enabledValue', ); const emailAlertCpTaskSpecificEmailAddressName = globals.config.get( - 'Butler.emailNotification.reloadTaskFailure.alertEnabledByEmailAddress.customPropertyName' + 'Butler.emailNotification.reloadTaskFailure.alertEnabledByEmailAddress.customPropertyName', ); const globalSendList = globals.config.get('Butler.emailNotification.reloadTaskFailure.recipients'); @@ -376,12 +376,12 @@ export async function sendReloadTaskFailureNotificationEmail(reloadParams) { // 1. Add task-specific notfication email adressess (set via custom property on reload tasks) to send list. const taskSpecificAlertEmailAddresses = await getTaskCustomPropertyValues( reloadParams.taskId, - emailAlertCpTaskSpecificEmailAddressName + emailAlertCpTaskSpecificEmailAddressName, ); if (taskSpecificAlertEmailAddresses?.length > 0) { globals.logger.debug( - `EMAIL RELOAD TASK FAILED ALERT: Added task specific send list: ${JSON.stringify(taskSpecificAlertEmailAddresses, null, 2)}` + `EMAIL RELOAD TASK FAILED ALERT: Added task specific send list: ${JSON.stringify(taskSpecificAlertEmailAddresses, null, 2)}`, ); mainSendList = mainSendList.concat(taskSpecificAlertEmailAddresses); @@ -395,7 +395,7 @@ export async function sendReloadTaskFailureNotificationEmail(reloadParams) { if (globalSendList?.length > 0) { globals.logger.debug( - `EMAIL RELOAD TASK FAILED ALERT: Added global send list for failed task: ${JSON.stringify(globalSendList, null, 2)}` + `EMAIL RELOAD TASK FAILED ALERT: Added global send list for failed task: ${JSON.stringify(globalSendList, null, 2)}`, ); mainSendList = mainSendList.concat(globalSendList); emailRecipientsVerbose.common = emailRecipientsVerbose.common.concat(globalSendList); @@ -404,14 +404,14 @@ export async function sendReloadTaskFailureNotificationEmail(reloadParams) { // Only send alert email if the failed task has email alerts enabled // 2.2 No : Does the failed reload task have alerts turned on (using custom property)? globals.logger.verbose( - `EMAIL RELOAD TASK FAILED ALERT: Only send alert emails for tasks with email-alert-CP "${emailAlertCpName}" set` + `EMAIL RELOAD TASK FAILED ALERT: Only send alert emails for tasks with email-alert-CP "${emailAlertCpName}" set`, ); const sendAlert = await isCustomPropertyValueSet(reloadParams.taskId, emailAlertCpName, emailAlertCpEnabledValue); if (sendAlert === true) { globals.logger.debug( - `EMAIL RELOAD TASK FAILED ALERT: Added send list based on email-alert-CP: ${JSON.stringify(globalSendList, null, 2)}` + `EMAIL RELOAD TASK FAILED ALERT: Added send list based on email-alert-CP: ${JSON.stringify(globalSendList, null, 2)}`, ); // 2.2.1 Yes: Add system-wide list of recipients to send list if (globalSendList?.length > 0) { @@ -457,7 +457,7 @@ export async function sendReloadTaskFailureNotificationEmail(reloadParams) { } else { // No app owners on include list. Warn about this. globals.logger.warn( - `EMAIL RELOAD TASK FAILED ALERT: No app owners on include list for failed task. No app owners will receive notification emails.` + `EMAIL RELOAD TASK FAILED ALERT: No app owners on include list for failed task. No app owners will receive notification emails.`, ); } } @@ -466,14 +466,14 @@ export async function sendReloadTaskFailureNotificationEmail(reloadParams) { // 3.2 Is there an app owner exclude list? const excludeUsers = globals.config.get('Butler.emailNotification.reloadTaskFailure.appOwnerAlert.excludeOwner.user'); const matchExcludedUsers = excludeUsers?.filter( - (user) => user.directory === appOwner.directory && user.userId === appOwner.userId + (user) => user.directory === appOwner.directory && user.userId === appOwner.userId, ); if (matchExcludedUsers?.length > 0) { // App owner is in list of app owners that should NOT receive notification emails // Remove app owner email address from app owner send list (if it's already there) // 3.2.1 Yes: Remove entries on the exclude list from app owner send list appOwnerSendList = appOwnerSendList?.filter( - (user) => user.directory !== appOwner.directory && user.userId !== appOwner.userId + (user) => user.directory !== appOwner.directory && user.userId !== appOwner.userId, ); } // 3.3 Add app owner send list to main send list @@ -486,12 +486,12 @@ export async function sendReloadTaskFailureNotificationEmail(reloadParams) { // Does the main sendlist contain any email addresses? Warn if not if (mainSendList?.length === 0) { globals.logger.warn( - `EMAIL RELOAD TASK FAILED ALERT: No email addresses defined for app owner's alert email for app "${reloadParams.appName}", ID=${reloadParams.appId}` + `EMAIL RELOAD TASK FAILED ALERT: No email addresses defined for app owner's alert email for app "${reloadParams.appName}", ID=${reloadParams.appId}`, ); } } else { globals.logger.warn( - `EMAIL RELOAD TASK FAILED ALERT: No email address for owner of app "${reloadParams.appName}", ID=${reloadParams.appId}` + `EMAIL RELOAD TASK FAILED ALERT: No email address for owner of app "${reloadParams.appName}", ID=${reloadParams.appId}`, ); } } @@ -504,8 +504,8 @@ export async function sendReloadTaskFailureNotificationEmail(reloadParams) { `EMAIL RELOAD TASK FAILED ALERT: Final send list for failed task "${reloadParams.taskName}": ${JSON.stringify( mainSendListUnique, null, - 2 - )}` + 2, + )}`, ); globals.logger.verbose(`EMAIL RELOAD TASK FAILED ALERT: App owner recipients: ${emailRecipientsVerbose.appOwner}`); globals.logger.verbose(`EMAIL RELOAD TASK FAILED ALERT: Shared all tasks recipients: ${emailRecipientsVerbose.shared}`); @@ -581,10 +581,10 @@ export async function sendReloadTaskFailureNotificationEmail(reloadParams) { .then(async (rateLimiterRes) => { try { globals.logger.info( - `EMAIL RELOAD TASK FAILED ALERT: Rate limiting check passed for failed task notification. Task name: "${reloadParams.taskName}", Recipient: "${recipientEmailAddress}"` + `EMAIL RELOAD TASK FAILED ALERT: Rate limiting check passed for failed task notification. Task name: "${reloadParams.taskName}", Recipient: "${recipientEmailAddress}"`, ); globals.logger.debug( - `EMAIL RELOAD TASK FAILED ALERT: Rate limiting details "${JSON.stringify(rateLimiterRes, null, 2)}"` + `EMAIL RELOAD TASK FAILED ALERT: Rate limiting details "${JSON.stringify(rateLimiterRes, null, 2)}"`, ); // Only send email if there is at least one recipient @@ -596,7 +596,7 @@ export async function sendReloadTaskFailureNotificationEmail(reloadParams) { globals.config.get('Butler.emailNotification.reloadTaskFailure.subject'), globals.config.get('Butler.emailNotification.reloadTaskFailure.bodyFileDirectory'), globals.config.get('Butler.emailNotification.reloadTaskFailure.htmlTemplateFile'), - templateContext + templateContext, ); } else { globals.logger.warn(`EMAIL RELOAD TASK FAILED ALERT: No recipients to send alert email to.`); @@ -607,7 +607,7 @@ export async function sendReloadTaskFailureNotificationEmail(reloadParams) { }) .catch((err) => { globals.logger.warn( - `EMAIL RELOAD TASK FAILED ALERT: Rate limiting failed. Not sending reload notification email for task "${reloadParams.taskName}" to "${recipientEmailAddress}"` + `EMAIL RELOAD TASK FAILED ALERT: Rate limiting failed. Not sending reload notification email for task "${reloadParams.taskName}" to "${recipientEmailAddress}"`, ); globals.logger.debug(`EMAIL RELOAD TASK FAILED ALERT: Rate limiting details "${JSON.stringify(err, null, 2)}"`); }); @@ -638,13 +638,13 @@ export async function sendReloadTaskAbortedNotificationEmail(reloadParams) { // 4 Remove any duplicate recipients in send list const emailAlertCpName = globals.config.get( - 'Butler.emailNotification.reloadTaskAborted.alertEnableByCustomProperty.customPropertyName' + 'Butler.emailNotification.reloadTaskAborted.alertEnableByCustomProperty.customPropertyName', ); const emailAlertCpEnabledValue = globals.config.get( - 'Butler.emailNotification.reloadTaskAborted.alertEnableByCustomProperty.enabledValue' + 'Butler.emailNotification.reloadTaskAborted.alertEnableByCustomProperty.enabledValue', ); const emailAlertCpTaskSpecificEmailAddressName = globals.config.get( - 'Butler.emailNotification.reloadTaskAborted.alertEnabledByEmailAddress.customPropertyName' + 'Butler.emailNotification.reloadTaskAborted.alertEnabledByEmailAddress.customPropertyName', ); const globalSendList = globals.config.get('Butler.emailNotification.reloadTaskAborted.recipients'); @@ -654,12 +654,12 @@ export async function sendReloadTaskAbortedNotificationEmail(reloadParams) { // 1. Add task-specific notfication email adressess (set via custom property on reload tasks) to send list. const taskSpecificAlertEmailAddresses = await getTaskCustomPropertyValues( reloadParams.taskId, - emailAlertCpTaskSpecificEmailAddressName + emailAlertCpTaskSpecificEmailAddressName, ); if (taskSpecificAlertEmailAddresses?.length > 0) { globals.logger.debug( - `EMAIL RELOAD TASK ABORTED ALERT: Added task specific send list: ${JSON.stringify(taskSpecificAlertEmailAddresses, null, 2)}` + `EMAIL RELOAD TASK ABORTED ALERT: Added task specific send list: ${JSON.stringify(taskSpecificAlertEmailAddresses, null, 2)}`, ); mainSendList = mainSendList.concat(taskSpecificAlertEmailAddresses); @@ -673,7 +673,7 @@ export async function sendReloadTaskAbortedNotificationEmail(reloadParams) { if (globalSendList?.length > 0) { globals.logger.debug( - `EMAIL RELOAD TASK ABORTED ALERT: Added global send list for failed task: ${JSON.stringify(globalSendList, null, 2)}` + `EMAIL RELOAD TASK ABORTED ALERT: Added global send list for failed task: ${JSON.stringify(globalSendList, null, 2)}`, ); mainSendList = mainSendList.concat(globalSendList); emailRecipientsVerbose.common = emailRecipientsVerbose.common.concat(globalSendList); @@ -682,14 +682,14 @@ export async function sendReloadTaskAbortedNotificationEmail(reloadParams) { // Only send alert email if the aborted task has email alerts enabled // 2.2 No : Does the aborted reload task have alerts turned on (using custom property)? globals.logger.verbose( - `EMAIL RELOAD TASK ABORTED ALERT: Only send alert emails for tasks with email-alert-CP "${emailAlertCpName}" set` + `EMAIL RELOAD TASK ABORTED ALERT: Only send alert emails for tasks with email-alert-CP "${emailAlertCpName}" set`, ); const sendAlert = await isCustomPropertyValueSet(reloadParams.taskId, emailAlertCpName, emailAlertCpEnabledValue); if (sendAlert === true) { globals.logger.debug( - `EMAIL RELOAD TASK ABORTED ALERT: Added send list based on email-alert-CP: ${JSON.stringify(globalSendList, null, 2)}` + `EMAIL RELOAD TASK ABORTED ALERT: Added send list based on email-alert-CP: ${JSON.stringify(globalSendList, null, 2)}`, ); // 2.2.1 Yes: Add system-wide list of recipients to send list if (globalSendList?.length > 0) { @@ -739,14 +739,14 @@ export async function sendReloadTaskAbortedNotificationEmail(reloadParams) { // 3.2 Is there an app owner exclude list? const excludeUsers = globals.config.get('Butler.emailNotification.reloadTaskAborted.appOwnerAlert.excludeOwner.user'); const matchExcludedUsers = excludeUsers?.filter( - (user) => user.directory === appOwner.directory && user.userId === appOwner.userId + (user) => user.directory === appOwner.directory && user.userId === appOwner.userId, ); if (matchExcludedUsers?.length > 0) { // App owner is in list of app owners that should NOT receive notification emails // Remove app owner email address from app owner send list (if it's already there) // 3.2.1 Yes: Remove entries on the exclude list from app owner send list appOwnerSendList = appOwnerSendList?.filter( - (user) => user.directory !== appOwner.directory && user.userId !== appOwner.userId + (user) => user.directory !== appOwner.directory && user.userId !== appOwner.userId, ); } // 3.3 Add app owner send list to main send list @@ -759,12 +759,12 @@ export async function sendReloadTaskAbortedNotificationEmail(reloadParams) { // Does the main sendlist contain any email addresses? Warn if not if (mainSendList?.length === 0) { globals.logger.warn( - `EMAIL RELOAD TASK ABORTED ALERT: No email addresses defined for alert email to app "${reloadParams.appName}", ID=${reloadParams.appId}` + `EMAIL RELOAD TASK ABORTED ALERT: No email addresses defined for alert email to app "${reloadParams.appName}", ID=${reloadParams.appId}`, ); } } else { globals.logger.warn( - `EMAIL RELOAD TASK ABORTED ALERT: No email address for owner of app "${reloadParams.appName}", ID=${reloadParams.appId}` + `EMAIL RELOAD TASK ABORTED ALERT: No email address for owner of app "${reloadParams.appName}", ID=${reloadParams.appId}`, ); } } @@ -777,8 +777,8 @@ export async function sendReloadTaskAbortedNotificationEmail(reloadParams) { `EMAIL RELOAD TASK ABORTED ALERT: Final send list for failed task "${reloadParams.taskName}": ${JSON.stringify( mainSendListUnique, null, - 2 - )}` + 2, + )}`, ); globals.logger.verbose(`EMAIL RELOAD TASK ABORTED ALERT: App owner recipients: ${emailRecipientsVerbose.appOwner}`); globals.logger.verbose(`EMAIL RELOAD TASK ABORTED ALERT: Shared all tasks recipients: ${emailRecipientsVerbose.shared}`); @@ -854,10 +854,10 @@ export async function sendReloadTaskAbortedNotificationEmail(reloadParams) { .then(async (rateLimiterRes) => { try { globals.logger.info( - `EMAIL RELOAD TASK ABORTED ALERT: Rate limiting check passed for aborted task notification. Task name: "${reloadParams.taskName}", Recipient: "${recipientEmailAddress}"` + `EMAIL RELOAD TASK ABORTED ALERT: Rate limiting check passed for aborted task notification. Task name: "${reloadParams.taskName}", Recipient: "${recipientEmailAddress}"`, ); globals.logger.debug( - `EMAIL RELOAD TASK ABORTED ALERT: Rate limiting details "${JSON.stringify(rateLimiterRes, null, 2)}"` + `EMAIL RELOAD TASK ABORTED ALERT: Rate limiting details "${JSON.stringify(rateLimiterRes, null, 2)}"`, ); // Only send email if there is at least one recipient @@ -869,7 +869,7 @@ export async function sendReloadTaskAbortedNotificationEmail(reloadParams) { globals.config.get('Butler.emailNotification.reloadTaskAborted.subject'), globals.config.get('Butler.emailNotification.reloadTaskAborted.bodyFileDirectory'), globals.config.get('Butler.emailNotification.reloadTaskAborted.htmlTemplateFile'), - templateContext + templateContext, ); } else { globals.logger.warn(`EMAIL RELOAD TASK ABORTED ALERT: No recipients to send alert email to.`); @@ -880,7 +880,7 @@ export async function sendReloadTaskAbortedNotificationEmail(reloadParams) { }) .catch((err) => { globals.logger.warn( - `EMAIL RELOAD TASK ABORTED ALERT: Rate limiting failed. Not sending reload notification email for task "${reloadParams.taskName}" to "${recipientEmailAddress}"` + `EMAIL RELOAD TASK ABORTED ALERT: Rate limiting failed. Not sending reload notification email for task "${reloadParams.taskName}" to "${recipientEmailAddress}"`, ); globals.logger.debug(`EMAIL RELOAD TASK ABORTED ALERT: Rate limiting details "${JSON.stringify(err, null, 2)}"`); }); @@ -935,7 +935,7 @@ export async function sendServiceMonitorNotificationEmail(serviceParams) { .then(async (rateLimiterRes) => { try { globals.logger.info( - `EMAIL SERVICE MONITOR: Rate limiting check passed for service monitor notification. Host: "${serviceParams.host}", service: "${serviceParams.serviceName}", recipient: "${recipientEmailAddress}"` + `EMAIL SERVICE MONITOR: Rate limiting check passed for service monitor notification. Host: "${serviceParams.host}", service: "${serviceParams.serviceName}", recipient: "${recipientEmailAddress}"`, ); globals.logger.debug(`EMAIL SERVICE MONITOR: Rate limiting details "${JSON.stringify(rateLimiterRes, null, 2)}"`); @@ -949,7 +949,7 @@ export async function sendServiceMonitorNotificationEmail(serviceParams) { globals.config.get('Butler.emailNotification.serviceStopped.subject'), globals.config.get('Butler.emailNotification.serviceStopped.bodyFileDirectory'), globals.config.get('Butler.emailNotification.serviceStopped.htmlTemplateFile'), - templateContext + templateContext, ); } else if (serviceParams.serviceStatus === 'RUNNING') { sendEmail( @@ -959,7 +959,7 @@ export async function sendServiceMonitorNotificationEmail(serviceParams) { globals.config.get('Butler.emailNotification.serviceStarted.subject'), globals.config.get('Butler.emailNotification.serviceStarted.bodyFileDirectory'), globals.config.get('Butler.emailNotification.serviceStarted.htmlTemplateFile'), - templateContext + templateContext, ); } } else { @@ -972,7 +972,7 @@ export async function sendServiceMonitorNotificationEmail(serviceParams) { // eslint-disable-next-line no-loop-func .catch((err) => { globals.logger.warn( - `EMAIL SERVICE MONITOR: Rate limiting failed. Not sending reload notification email for service service "${serviceParams.serviceName}" on host "${serviceParams.host}" to "${recipientEmailAddress}"` + `EMAIL SERVICE MONITOR: Rate limiting failed. Not sending reload notification email for service service "${serviceParams.serviceName}" on host "${serviceParams.host}" to "${recipientEmailAddress}"`, ); globals.logger.debug(`EMAIL SERVICE MONITOR: Rate limiting details "${JSON.stringify(err, null, 2)}"`); }); diff --git a/src/lib/telemetry.js b/src/lib/telemetry.js index 44830336..5c4681e4 100644 --- a/src/lib/telemetry.js +++ b/src/lib/telemetry.js @@ -451,7 +451,7 @@ const callRemoteURL = async () => { globals.logger.error(' While not mandatory the telemetry data greatly helps the Butler developers.'); globals.logger.error(' It provides insights into which features are used most and what hardware/OSs are used out there.'); globals.logger.error( - ' This information makes it possible to focus development efforts where they will make most impact and be most valuable.' + ' This information makes it possible to focus development efforts where they will make most impact and be most valuable.', ); if (err.response) { globals.logger.error(` Error: ${err.response.status} (${err.response.statusText}).`); @@ -477,7 +477,7 @@ export default function setupAnonUsageReportTimer(logger, hostInfo) { () => { callRemoteURL(logger, hostInfo); }, - 1000 * 60 * 60 * 12 + 1000 * 60 * 60 * 12, ); // Report anon telemetry every 12 hours // Do an initial report to the remote URL diff --git a/src/lib/testemail.js b/src/lib/testemail.js index 685bc1e1..e0b7bbaa 100644 --- a/src/lib/testemail.js +++ b/src/lib/testemail.js @@ -9,7 +9,7 @@ function sendTestEmail(emailAddress, fromAddress) { [emailAddress], 'normal', 'Test email from Butler for Qlik Sense', - "This is a test email sent from your friendly Butler for Qlik Sense Enterprise on Windows.\n\nIf you get this email Butler's email configuration is correct and working." + "This is a test email sent from your friendly Butler for Qlik Sense Enterprise on Windows.\n\nIf you get this email Butler's email configuration is correct and working.", ); } else { sendEmailBasic( @@ -17,7 +17,7 @@ function sendTestEmail(emailAddress, fromAddress) { [emailAddress], 'normal', 'Test email from Butler for Qlik Sense', - "This is a test email sent from your friendly Butler for Qlik Sense Enterprise on Windows.\n\nIf you get this email Butler's email configuration is correct and working." + "This is a test email sent from your friendly Butler for Qlik Sense Enterprise on Windows.\n\nIf you get this email Butler's email configuration is correct and working.", ); } } catch (err) { diff --git a/src/lib/webhook_notification.js b/src/lib/webhook_notification.js index f694a5e0..1a8c9841 100644 --- a/src/lib/webhook_notification.js +++ b/src/lib/webhook_notification.js @@ -84,7 +84,7 @@ function getOutgoingWebhookReloadFailedNotificationConfigOk() { ) { // Not enough info in config file globals.logger.error( - 'WEBHOOK OUT RELOAD TASK FAILED: Reload failure outgoing webhook config info missing in Butler config file' + 'WEBHOOK OUT RELOAD TASK FAILED: Reload failure outgoing webhook config info missing in Butler config file', ); return false; } @@ -111,7 +111,7 @@ function getOutgoingWebhookReloadAbortedNotificationConfigOk() { ) { // Not enough info in config file globals.logger.error( - 'WEBHOOK OUT RELOAD TASK ABORTED: Reload aborted outgoing webhook config info missing in Butler config file' + 'WEBHOOK OUT RELOAD TASK ABORTED: Reload aborted outgoing webhook config info missing in Butler config file', ); return false; } @@ -155,7 +155,7 @@ function getOutgoingWebhookServiceMonitorConfig() { if (!globals.config.has('Butler.serviceMonitor.alertDestination.webhook.enable')) { globals.logger.error( - 'SERVICE MONITOR WEBHOOK: Missing config entry "Butler.serviceMonitor.alertDestination.webhook.enable"' + 'SERVICE MONITOR WEBHOOK: Missing config entry "Butler.serviceMonitor.alertDestination.webhook.enable"', ); } @@ -465,7 +465,7 @@ async function sendOutgoingWebhookServiceMonitor(webhookConfig, serviceParams) { } } catch (err) { globals.logger.error( - `SERVICE MONITOR WEBHOOKOUT: ${err}. Invalid outgoing webhook config: ${JSON.stringify(webhook, null, 2)}` + `SERVICE MONITOR WEBHOOKOUT: ${err}. Invalid outgoing webhook config: ${JSON.stringify(webhook, null, 2)}`, ); throw err; } @@ -627,14 +627,14 @@ async function sendOutgoingWebhookQlikSenseServerLicense(webhookConfig, serverLi // Make sure webhook.cert.rejectUnauthorized is a boolean if (typeof webhook.cert.rejectUnauthorized !== 'boolean') { throw new Error( - 'WEBHOOKOUT QLIK SENSE SERVER LICENSE MONITOR: Webhook cert.rejectUnauthorized property should be a boolean ' + 'WEBHOOKOUT QLIK SENSE SERVER LICENSE MONITOR: Webhook cert.rejectUnauthorized property should be a boolean ', ); } // Make sure CA cert file in webhook.cert.certCA is a string if (typeof webhook.cert.certCA !== 'string') { throw new Error( - 'WEBHOOKOUT QLIK SENSE SERVER LICENSE MONITOR: Webhook cert.certCA property should be a string' + 'WEBHOOKOUT QLIK SENSE SERVER LICENSE MONITOR: Webhook cert.certCA property should be a string', ); } @@ -648,8 +648,8 @@ async function sendOutgoingWebhookQlikSenseServerLicense(webhookConfig, serverLi `WEBHOOKOUT QLIK SENSE SERVER LICENSE MONITOR: ${err}. Invalid outgoing webhook config: ${JSON.stringify( webhook, null, - 2 - )}` + 2, + )}`, ); throw err; } @@ -770,10 +770,10 @@ export function sendReloadTaskFailureNotificationWebhook(reloadParams) { .then(async (rateLimiterRes) => { try { globals.logger.info( - `WEBHOOK OUT RELOAD TASK FAILED: Rate limiting check passed for failed task notification. Task name: "${reloadParams.taskName}"` + `WEBHOOK OUT RELOAD TASK FAILED: Rate limiting check passed for failed task notification. Task name: "${reloadParams.taskName}"`, ); globals.logger.verbose( - `WEBHOOK OUT RELOAD TASK FAILED: Rate limiting details "${JSON.stringify(rateLimiterRes, null, 2)}"` + `WEBHOOK OUT RELOAD TASK FAILED: Rate limiting details "${JSON.stringify(rateLimiterRes, null, 2)}"`, ); // Make sure Slack sending is enabled in the config file and that we have all required settings @@ -790,7 +790,7 @@ export function sendReloadTaskFailureNotificationWebhook(reloadParams) { }) .catch((rateLimiterRes) => { globals.logger.verbose( - `WEBHOOK OUT RELOAD TASK FAILED: Rate limiting failed. Not sending reload failure notification via outgoing webhook for task "${reloadParams.taskName}"` + `WEBHOOK OUT RELOAD TASK FAILED: Rate limiting failed. Not sending reload failure notification via outgoing webhook for task "${reloadParams.taskName}"`, ); globals.logger.verbose(`WEBHOOK OUT RELOAD TASK FAILED: Rate limiting details "${JSON.stringify(rateLimiterRes, null, 2)}"`); }); @@ -802,10 +802,10 @@ export function sendReloadTaskAbortedNotificationWebhook(reloadParams) { .then(async (rateLimiterRes) => { try { globals.logger.info( - `WEBHOOK OUT RELOAD TASK ABORTED: Rate limiting check passed for aborted task notification. Task name: "${reloadParams.taskName}"` + `WEBHOOK OUT RELOAD TASK ABORTED: Rate limiting check passed for aborted task notification. Task name: "${reloadParams.taskName}"`, ); globals.logger.verbose( - `WEBHOOK OUT RELOAD TASK ABORTED: Rate limiting details "${JSON.stringify(rateLimiterRes, null, 2)}"` + `WEBHOOK OUT RELOAD TASK ABORTED: Rate limiting details "${JSON.stringify(rateLimiterRes, null, 2)}"`, ); // Make sure outgoing webhooks are enabled in the config file and that we have all required settings @@ -822,7 +822,7 @@ export function sendReloadTaskAbortedNotificationWebhook(reloadParams) { }) .catch((rateLimiterRes) => { globals.logger.verbose( - `WEBHOOK OUT RELOAD TASK ABORTED: Rate limiting failed. Not sending reload aborted notification via outgoing webhook for task "${reloadParams.taskName}"` + `WEBHOOK OUT RELOAD TASK ABORTED: Rate limiting failed. Not sending reload aborted notification via outgoing webhook for task "${reloadParams.taskName}"`, ); globals.logger.verbose(`WEBHOOK OUT RELOAD TASK ABORTED: Rate limiting details "${JSON.stringify(rateLimiterRes, null, 2)}"`); }); @@ -834,7 +834,7 @@ export function sendServiceMonitorWebhook(svc) { .then(async (rateLimiterRes) => { try { globals.logger.info( - `SERVICE MONITOR WEBHOOK: Rate limiting check passed for service monitor notification. Service name: "${svc.serviceName}"` + `SERVICE MONITOR WEBHOOK: Rate limiting check passed for service monitor notification. Service name: "${svc.serviceName}"`, ); globals.logger.verbose(`SERVICE MONITOR WEBHOOK: Rate limiting details "${JSON.stringify(rateLimiterRes, null, 2)}"`); @@ -862,7 +862,7 @@ export function sendServiceMonitorWebhook(svc) { }) .catch((rateLimiterRes) => { globals.logger.verbose( - `WEBHOOK OUT RELOAD TASK FAILED: Rate limiting failed. Not sending service monitor notification via outgoing webhook for service "${svc.serviceName}"` + `WEBHOOK OUT RELOAD TASK FAILED: Rate limiting failed. Not sending service monitor notification via outgoing webhook for service "${svc.serviceName}"`, ); globals.logger.verbose(`WEBHOOK OUT RELOAD TASK FAILED: Rate limiting details "${JSON.stringify(rateLimiterRes, null, 2)}"`); }); @@ -887,10 +887,10 @@ export async function callQlikSenseServerLicenseWebhook(serverLicenseInfo) { .then(async (rateLimiterRes) => { try { globals.logger.info( - `WEBHOOK OUT QLIK SENSE SERVER LICENSE MONITOR: Rate limiting check passed for Qlik Sense server license monitor notification` + `WEBHOOK OUT QLIK SENSE SERVER LICENSE MONITOR: Rate limiting check passed for Qlik Sense server license monitor notification`, ); globals.logger.verbose( - `WEBHOOK OUT QLIK SENSE SERVER LICENSE MONITOR: Rate limiting details "${JSON.stringify(rateLimiterRes, null, 2)}"` + `WEBHOOK OUT QLIK SENSE SERVER LICENSE MONITOR: Rate limiting details "${JSON.stringify(rateLimiterRes, null, 2)}"`, ); // Make sure outgoing webhooks are enabled in the config file and that we have all required settings @@ -907,10 +907,10 @@ export async function callQlikSenseServerLicenseWebhook(serverLicenseInfo) { }) .catch((rateLimiterRes) => { globals.logger.verbose( - `WEBHOOK OUT QLIK SENSE SERVER LICENSE MONITOR: Rate limiting failed. Not sending Qlik Sense server license monitor notification via outgoing webhook` + `WEBHOOK OUT QLIK SENSE SERVER LICENSE MONITOR: Rate limiting failed. Not sending Qlik Sense server license monitor notification via outgoing webhook`, ); globals.logger.verbose( - `WEBHOOK OUT QLIK SENSE SERVER LICENSE MONITOR: Rate limiting details "${JSON.stringify(rateLimiterRes, null, 2)}"` + `WEBHOOK OUT QLIK SENSE SERVER LICENSE MONITOR: Rate limiting details "${JSON.stringify(rateLimiterRes, null, 2)}"`, ); }); } else if ( @@ -923,10 +923,10 @@ export async function callQlikSenseServerLicenseWebhook(serverLicenseInfo) { .then(async (rateLimiterRes) => { try { globals.logger.info( - `WEBHOOK OUT QLIK SENSE SERVER LICENSE EXPIRY ALERT: Rate limiting check passed for Qlik Sense server license expiry alert` + `WEBHOOK OUT QLIK SENSE SERVER LICENSE EXPIRY ALERT: Rate limiting check passed for Qlik Sense server license expiry alert`, ); globals.logger.verbose( - `WEBHOOK OUT QLIK SENSE SERVER LICENSE EXPIRY ALERT: Rate limiting details "${JSON.stringify(rateLimiterRes, null, 2)}"` + `WEBHOOK OUT QLIK SENSE SERVER LICENSE EXPIRY ALERT: Rate limiting details "${JSON.stringify(rateLimiterRes, null, 2)}"`, ); // Make sure outgoing webhooks are enabled in the config file and that we have all required settings @@ -943,10 +943,10 @@ export async function callQlikSenseServerLicenseWebhook(serverLicenseInfo) { }) .catch((rateLimiterRes) => { globals.logger.verbose( - `WEBHOOK OUT QLIK SENSE SERVER LICENSE EXPIRY ALERT: Rate limiting failed. Not sending Qlik Sense server license expiry alert via outgoing webhook` + `WEBHOOK OUT QLIK SENSE SERVER LICENSE EXPIRY ALERT: Rate limiting failed. Not sending Qlik Sense server license expiry alert via outgoing webhook`, ); globals.logger.verbose( - `WEBHOOK OUT QLIK SENSE SERVER LICENSE EXPIRY ALERT: Rate limiting details "${JSON.stringify(rateLimiterRes, null, 2)}"` + `WEBHOOK OUT QLIK SENSE SERVER LICENSE EXPIRY ALERT: Rate limiting details "${JSON.stringify(rateLimiterRes, null, 2)}"`, ); }); } diff --git a/src/lib/winsvc.js b/src/lib/winsvc.js index 1ab58721..6ba91902 100644 --- a/src/lib/winsvc.js +++ b/src/lib/winsvc.js @@ -112,7 +112,7 @@ export function exists(logger, serviceName, host = null) { } rejectExists(err); - } + }, ); }); } @@ -391,17 +391,17 @@ export function details(logger, serviceName, host = null) { logger.verbose( `WINSVC DETAILS: Service ${serviceName} has exe path ${lines .find((line) => line.indexOf('BINARY_PATH_NAME') !== -1) - .replace(/\s*BINARY_PATH_NAME\s*: /, '')}` + .replace(/\s*BINARY_PATH_NAME\s*: /, '')}`, ); logger.verbose( `WINSVC DETAILS: Service ${serviceName} has display name ${lines .find((line) => line.indexOf('DISPLAY_NAME') !== -1) - .replace(/\s*DISPLAY_NAME\s*: /, '')}` + .replace(/\s*DISPLAY_NAME\s*: /, '')}`, ); logger.verbose( `WINSVC DETAILS: Service ${serviceName} has name ${lines .find((line) => line.indexOf('SERVICE_NAME: ') !== -1) - .replace('SERVICE_NAME: ', '')}` + .replace('SERVICE_NAME: ', '')}`, ); resolve({ diff --git a/src/qrs_util/get_tasks.js b/src/qrs_util/get_tasks.js index ae20d1d1..6684e6e1 100644 --- a/src/qrs_util/get_tasks.js +++ b/src/qrs_util/get_tasks.js @@ -41,10 +41,10 @@ const getTasks = async (filter) => { // Handle custom properties filter if (filter.customProperty) { globals.logger.debug( - `GETTASKS 2: task/full?filter=(customProperties.definition.name eq '${filter.customProperty.name}') and (customProperties.value eq '${filter.customProperty.value}')` + `GETTASKS 2: task/full?filter=(customProperties.definition.name eq '${filter.customProperty.name}') and (customProperties.value eq '${filter.customProperty.value}')`, ); const result = await qrsInstance.Get( - `task/full?filter=(customProperties.definition.name eq '${filter.customProperty.name}') and (customProperties.value eq '${filter.customProperty.value}')` + `task/full?filter=(customProperties.definition.name eq '${filter.customProperty.name}') and (customProperties.value eq '${filter.customProperty.value}')`, ); globals.logger.debug(`GETTASKS: Got response: ${result.statusCode} for tag filter ${filter.customProperty.name}`); diff --git a/src/qrs_util/task_cp_util.js b/src/qrs_util/task_cp_util.js index d3c9a2aa..c4c25a1e 100644 --- a/src/qrs_util/task_cp_util.js +++ b/src/qrs_util/task_cp_util.js @@ -30,11 +30,11 @@ export async function isCustomPropertyValueSet(taskId, cpName, cpValue, logger) // Get info about the task try { localLogger.debug( - `ISCPVALUESET: task/full?filter=id eq ${taskId} and customProperties.definition.name eq '${cpName}' and customProperties.value eq '${cpValue}'` + `ISCPVALUESET: task/full?filter=id eq ${taskId} and customProperties.definition.name eq '${cpName}' and customProperties.value eq '${cpValue}'`, ); const result = await qrsInstance.Get( - `task/full?filter=id eq ${taskId} and customProperties.definition.name eq '${cpName}' and customProperties.value eq '${cpValue}'` + `task/full?filter=id eq ${taskId} and customProperties.definition.name eq '${cpName}' and customProperties.value eq '${cpValue}'`, ); localLogger.debug(`ISCPVALUESET: Got response: ${result.statusCode} for CP ${cpName}`); diff --git a/src/routes/README.md b/src/routes/rest_server/README.md similarity index 100% rename from src/routes/README.md rename to src/routes/rest_server/README.md diff --git a/src/routes/api.js b/src/routes/rest_server/api.js similarity index 86% rename from src/routes/api.js rename to src/routes/rest_server/api.js index 548bd7a5..cc814c67 100644 --- a/src/routes/api.js +++ b/src/routes/rest_server/api.js @@ -1,10 +1,10 @@ import httpErrors from 'http-errors'; // Load global variables and functions -import globals from '../globals.js'; +import globals from '../../globals.js'; -import { logRESTCall } from '../lib/log_rest_call.js'; -import apiGetAPIEndpointsEnabled from '../api/api.js'; +import { logRESTCall } from '../../lib/log_rest_call.js'; +import apiGetAPIEndpointsEnabled from '../../api/api.js'; async function handlerGetAPIEndpointsEnabled(request, reply) { try { diff --git a/src/routes/base_conversion.js b/src/routes/rest_server/base_conversion.js similarity index 92% rename from src/routes/base_conversion.js rename to src/routes/rest_server/base_conversion.js index eb888025..e240a6cd 100644 --- a/src/routes/base_conversion.js +++ b/src/routes/rest_server/base_conversion.js @@ -4,9 +4,9 @@ import httpErrors from 'http-errors'; import anyBase from 'any-base'; // Load global variables and functions -import globals from '../globals.js'; -import { logRESTCall } from '../lib/log_rest_call.js'; -import { apiGetBase16ToBase62, apiGetBase62ToBase16 } from '../api/base_conversion.js'; +import globals from '../../globals.js'; +import { logRESTCall } from '../../lib/log_rest_call.js'; +import { apiGetBase16ToBase62, apiGetBase62ToBase16 } from '../../api/base_conversion.js'; const base62_to_Hex = anyBase('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', '0123456789abcdef'); const hex_to_base62 = anyBase('0123456789abcdef', '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'); @@ -26,7 +26,7 @@ async function handlerGetBase62ToBase16(request, reply) { return null; } catch (err) { globals.logger.error( - `BASECONVERT: Failed converting from base62 to base16: ${request.query.base62}, error is: ${JSON.stringify(err, null, 2)}` + `BASECONVERT: Failed converting from base62 to base16: ${request.query.base62}, error is: ${JSON.stringify(err, null, 2)}`, ); reply.send(httpErrors(500, 'Failed converting from base62 to base16')); return null; @@ -48,7 +48,7 @@ async function handlerGetBase16ToBase62(request, reply) { return null; } catch (err) { globals.logger.error( - `BASECONVERT: Failed converting from base16 to base62: ${request.query.base16}, error is: ${JSON.stringify(err, null, 2)}` + `BASECONVERT: Failed converting from base16 to base62: ${request.query.base16}, error is: ${JSON.stringify(err, null, 2)}`, ); reply.send(httpErrors(500, 'Failed converting from base16 to base62')); return null; diff --git a/src/routes/butler_ping.js b/src/routes/rest_server/butler_ping.js similarity index 85% rename from src/routes/butler_ping.js rename to src/routes/rest_server/butler_ping.js index 780cbf28..28534e70 100644 --- a/src/routes/butler_ping.js +++ b/src/routes/rest_server/butler_ping.js @@ -1,10 +1,10 @@ import httpErrors from 'http-errors'; // Load global variables and functions -import globals from '../globals.js'; +import globals from '../../globals.js'; -import { logRESTCall } from '../lib/log_rest_call.js'; -import apiGetButlerPing from '../api/butler_ping.js'; +import { logRESTCall } from '../../lib/log_rest_call.js'; +import apiGetButlerPing from '../../api/butler_ping.js'; async function handlerGetButlerPing(request, reply) { try { diff --git a/src/routes/disk_utils.js b/src/routes/rest_server/disk_utils.js similarity index 95% rename from src/routes/disk_utils.js rename to src/routes/rest_server/disk_utils.js index 61ddb8a0..78ff3dcd 100644 --- a/src/routes/disk_utils.js +++ b/src/routes/rest_server/disk_utils.js @@ -5,11 +5,11 @@ import { mkdirp } from 'mkdirp'; import isUncPath from 'is-unc-path'; // Load global variables and functions -import globals from '../globals.js'; +import globals from '../../globals.js'; -import { logRESTCall } from '../lib/log_rest_call.js'; -import isDirectoryChildOf from '../lib/disk_utils.js'; -import { apiFileCopy, apiFileMove, apiFileDelete, apiCreateDir, apiCreateDirQvd } from '../api/disk_utils.js'; +import { logRESTCall } from '../../lib/log_rest_call.js'; +import isDirectoryChildOf from '../../lib/disk_utils.js'; +import { apiFileCopy, apiFileMove, apiFileDelete, apiCreateDir, apiCreateDirQvd } from '../../api/disk_utils.js'; async function handlerFileCopy(request, reply) { try { @@ -40,24 +40,24 @@ async function handlerFileCopy(request, reply) { if (globals.hostInfo.si.os.platform.toLowerCase() !== 'windows') { if (isUncPath(request.body.fromFile) === true) { globals.logger.warn( - `FILE COPY FROM: UNC paths not supported work on non-Windows OSs ("${request.body.fromFile}"). OS is "${globals.hostInfo.si.os.platform}".` + `FILE COPY FROM: UNC paths not supported work on non-Windows OSs ("${request.body.fromFile}"). OS is "${globals.hostInfo.si.os.platform}".`, ); reply.send( httpErrors( 400, - `UNC paths not supported for file copy operations when running Butler on non-Windows OS. Path: ${request.body.fromFile}` - ) + `UNC paths not supported for file copy operations when running Butler on non-Windows OS. Path: ${request.body.fromFile}`, + ), ); } if (isUncPath(request.body.toFile) === true) { globals.logger.warn( - `FILE COPY TO: UNC paths not supported on non-Windows OSs ("${request.body.toFile}"). OS is "${globals.hostInfo.si.os.platform}".` + `FILE COPY TO: UNC paths not supported on non-Windows OSs ("${request.body.toFile}"). OS is "${globals.hostInfo.si.os.platform}".`, ); reply.send( httpErrors( 400, - `UNC paths not supported for file copy operations when running Butler on non-Windows OS. Path: ${request.body.toFile}` - ) + `UNC paths not supported for file copy operations when running Butler on non-Windows OS. Path: ${request.body.toFile}`, + ), ); } } @@ -88,7 +88,7 @@ async function handlerFileCopy(request, reply) { if (copyIsOk) { globals.logger.debug( - `FILECOPY: About to copy file from ${fromFile} to ${toFile}, overwrite=${overwrite}, preserve timestamp=${preserveTimestamp}` + `FILECOPY: About to copy file from ${fromFile} to ${toFile}, overwrite=${overwrite}, preserve timestamp=${preserveTimestamp}`, ); await fs.copySync(fromFile, toFile, { @@ -97,7 +97,7 @@ async function handlerFileCopy(request, reply) { }); globals.logger.verbose( - `FILECOPY: Copied file from ${fromFile} to ${toFile}, overwrite=${overwrite}, preserve timestamp=${preserveTimestamp}` + `FILECOPY: Copied file from ${fromFile} to ${toFile}, overwrite=${overwrite}, preserve timestamp=${preserveTimestamp}`, ); reply.code(201).send({ @@ -108,7 +108,7 @@ async function handlerFileCopy(request, reply) { }); } else { globals.logger.error( - `FILECOPY: No approved fromDir/toDir for file copy ${request.body.fromFile} to ${request.body.toFile}` + `FILECOPY: No approved fromDir/toDir for file copy ${request.body.fromFile} to ${request.body.toFile}`, ); reply.send(httpErrors(403, 'No approved fromDir/toDir for file copy')); } @@ -120,7 +120,7 @@ async function handlerFileCopy(request, reply) { } } catch (err) { globals.logger.error( - `FILECOPY: Failed copying file ${request.body.fromFile} to ${request.body.toFile}, error is: ${JSON.stringify(err, null, 2)}` + `FILECOPY: Failed copying file ${request.body.fromFile} to ${request.body.toFile}, error is: ${JSON.stringify(err, null, 2)}`, ); reply.send(httpErrors(500, 'Failed copying file')); } @@ -149,24 +149,24 @@ async function handlerFileMove(request, reply) { if (globals.hostInfo.si.os.platform.toLowerCase() !== 'windows') { if (isUncPath(request.body.fromFile) === true) { globals.logger.warn( - `FILE MOVE FROM: UNC paths not supported work on non-Windows OSs ("${request.body.fromFile}"). OS is "${globals.hostInfo.si.os.platform}".` + `FILE MOVE FROM: UNC paths not supported work on non-Windows OSs ("${request.body.fromFile}"). OS is "${globals.hostInfo.si.os.platform}".`, ); reply.send( httpErrors( 400, - `UNC paths not supported for file move operations when running Butler on non-Windows OS. Path: ${request.body.fromFile}` - ) + `UNC paths not supported for file move operations when running Butler on non-Windows OS. Path: ${request.body.fromFile}`, + ), ); } if (isUncPath(request.body.toFile) === true) { globals.logger.warn( - `FILE MOVE TO: UNC paths not supported on non-Windows OSs ("${request.body.toFile}"). OS is "${globals.hostInfo.si.os.platform}".` + `FILE MOVE TO: UNC paths not supported on non-Windows OSs ("${request.body.toFile}"). OS is "${globals.hostInfo.si.os.platform}".`, ); reply.send( httpErrors( 400, - `UNC paths not supported for file move operations when running Butler on non-Windows OS. Path: ${request.body.toFile}` - ) + `UNC paths not supported for file move operations when running Butler on non-Windows OS. Path: ${request.body.toFile}`, + ), ); } } @@ -203,7 +203,7 @@ async function handlerFileMove(request, reply) { reply.code(201).send({ fromFile, toFile, overwrite }); } else { globals.logger.error( - `FILEMOVE: No approved fromDir/toDir for file move ${request.body.fromFile} to ${request.body.toFile}` + `FILEMOVE: No approved fromDir/toDir for file move ${request.body.fromFile} to ${request.body.toFile}`, ); reply.send(httpErrors(403, 'No approved fromDir/toDir for file move')); } @@ -232,13 +232,13 @@ async function handlerFileDelete(request, reply) { if (globals.hostInfo.si.os.platform.toLowerCase() !== 'windows') { if (isUncPath(request.body.deleteFile) === true) { globals.logger.warn( - `FILE DELETE: UNC paths not supported work on non-Windows OSs ("${request.body.deleteFile}"). OS is "${globals.hostInfo.si.os.platform}".` + `FILE DELETE: UNC paths not supported work on non-Windows OSs ("${request.body.deleteFile}"). OS is "${globals.hostInfo.si.os.platform}".`, ); reply.send( httpErrors( 400, - `UNC paths not supported for file copy operations when running Butler on non-Windows OS. Path: ${request.body.deleteFile}` - ) + `UNC paths not supported for file copy operations when running Butler on non-Windows OS. Path: ${request.body.deleteFile}`, + ), ); return; diff --git a/src/routes/key_value_store.js b/src/routes/rest_server/key_value_store.js similarity index 96% rename from src/routes/key_value_store.js rename to src/routes/rest_server/key_value_store.js index 6dc5e451..51bba6ca 100644 --- a/src/routes/key_value_store.js +++ b/src/routes/rest_server/key_value_store.js @@ -2,10 +2,17 @@ import httpErrors from 'http-errors'; // Load global variables and functions -import globals from '../globals.js'; +import globals from '../../globals.js'; -import { logRESTCall } from '../lib/log_rest_call.js'; -import { getNamespaceList, getNamespace, deleteNamespace, addKeyValuePair, deleteKeyValuePair, getValue } from '../lib/key_value_store.js'; +import { logRESTCall } from '../../lib/log_rest_call.js'; +import { + getNamespaceList, + getNamespace, + deleteNamespace, + addKeyValuePair, + deleteKeyValuePair, + getValue, +} from '../../lib/key_value_store.js'; import { apiGetAllNamespaces, apiGetKVPair, @@ -14,7 +21,7 @@ import { apiDeleteKVPair, apiDeleteNamespace, apiGetKeysInNamespace, -} from '../api/key_value_store.js'; +} from '../../api/key_value_store.js'; async function handlerGetNamespaceList(request, reply) { try { @@ -75,8 +82,8 @@ async function handlerGetKeyValueInNamespace(request, reply) { `KEYVALUE: Failed getting key '${request.query.key}' in namespace: ${request.params.namespace}, error is: ${JSON.stringify( err, null, - 2 - )}` + 2, + )}`, ); reply.send(httpErrors(500, 'Failed getting key-value data')); } @@ -130,7 +137,7 @@ async function handlerKeyExists(request, reply) { globals.logger.error( `KEYVALUE: Failed checking if key '${request.query.key}' exists in namespace: ${ request.params.namespace - }, error is: ${JSON.stringify(err, null, 2)}` + }, error is: ${JSON.stringify(err, null, 2)}`, ); reply.send(httpErrors(500, 'Failed getting key-value data')); } @@ -162,7 +169,7 @@ async function handlerPostKeyValueInNamespace(request, reply) { } } catch (err) { globals.logger.error( - `KEYVALUE: Failed adding key-value to namespace: ${request.params.namespace}, error is: ${JSON.stringify(err, null, 2)}` + `KEYVALUE: Failed adding key-value to namespace: ${request.params.namespace}, error is: ${JSON.stringify(err, null, 2)}`, ); reply.send(httpErrors(500, 'Failed adding key-value to namespace')); } @@ -212,8 +219,8 @@ async function handlerDeleteKeyValueInNamespace(request, reply) { `KEYVALUE: Failed deleting key '${request.params.key}' in namespace: ${request.params.namespace}, error is: ${JSON.stringify( err, null, - 2 - )}` + 2, + )}`, ); reply.send(httpErrors(500, 'Failed deleting key-value data')); } @@ -303,7 +310,7 @@ async function handlerGetKeyList(request, reply) { } } catch (err) { globals.logger.error( - `KEYVALUE: Failed getting list of keys in namespace: ${request.params.namespace}, error is: ${JSON.stringify(err, null, 2)}` + `KEYVALUE: Failed getting list of keys in namespace: ${request.params.namespace}, error is: ${JSON.stringify(err, null, 2)}`, ); reply.send(httpErrors(500, 'Failed getting list of keys in namespace')); } diff --git a/src/routes/mqtt_publish_message.js b/src/routes/rest_server/mqtt_publish_message.js similarity index 88% rename from src/routes/mqtt_publish_message.js rename to src/routes/rest_server/mqtt_publish_message.js index f146311c..12fd4ef9 100644 --- a/src/routes/mqtt_publish_message.js +++ b/src/routes/rest_server/mqtt_publish_message.js @@ -1,10 +1,10 @@ import httpErrors from 'http-errors'; // Load global variables and functions -import globals from '../globals.js'; +import globals from '../../globals.js'; -import { logRESTCall } from '../lib/log_rest_call.js'; -import apiPutMqttMessage from '../api/mqtt_publish_message.js'; +import { logRESTCall } from '../../lib/log_rest_call.js'; +import apiPutMqttMessage from '../../api/mqtt_publish_message.js'; // eslint-disable-next-line consistent-return function handlerPutMqttMessage(request, reply) { @@ -28,8 +28,8 @@ function handlerPutMqttMessage(request, reply) { `PUBLISHMQTT: Failed publishing MQTT message: ${JSON.stringify(request.body, null, 2)}, error is: ${JSON.stringify( err, null, - 2 - )}` + 2, + )}`, ); reply.send(httpErrors(500, 'Failed publishing MQTT message')); } @@ -39,8 +39,8 @@ function handlerPutMqttMessage(request, reply) { `PUBLISHMQTT: Failed publishing MQTT message: ${JSON.stringify(request.body, null, 2)}, error is: ${JSON.stringify( err, null, - 2 - )}` + 2, + )}`, ); reply.send(httpErrors(500, 'Failed publishing MQTT message')); } diff --git a/src/routes/newrelic_event.js b/src/routes/rest_server/newrelic_event.js similarity index 96% rename from src/routes/newrelic_event.js rename to src/routes/rest_server/newrelic_event.js index 2873e852..c0f5fda8 100644 --- a/src/routes/newrelic_event.js +++ b/src/routes/rest_server/newrelic_event.js @@ -2,10 +2,10 @@ import httpErrors from 'http-errors'; import axios from 'axios'; // Load global variables and functions -import globals from '../globals.js'; +import globals from '../../globals.js'; -import { logRESTCall } from '../lib/log_rest_call.js'; -import apiPostNewRelicEvent from '../api/newrelic_event.js'; +import { logRESTCall } from '../../lib/log_rest_call.js'; +import apiPostNewRelicEvent from '../../api/newrelic_event.js'; // eslint-disable-next-line consistent-return async function handlerPostNewRelicEvent(request, reply) { @@ -109,7 +109,7 @@ async function handlerPostNewRelicEvent(request, reply) { // eslint-disable-next-line no-await-in-loop const res = await axios.request(axiosRequest); globals.logger.debug( - `NEWRELIC EVENT: Result code from posting event to New Relic account ${newRelicConfig[0].accountId}: ${res.status}, ${res.statusText}` + `NEWRELIC EVENT: Result code from posting event to New Relic account ${newRelicConfig[0].accountId}: ${res.status}, ${res.statusText}`, ); if (res.status === 200 || res.status === 202) { @@ -127,8 +127,8 @@ async function handlerPostNewRelicEvent(request, reply) { `NEWRELIC EVENT: Failed posting event to New Relic: ${JSON.stringify(request.body, null, 2)}, error is: ${JSON.stringify( err, null, - 2 - )}` + 2, + )}`, ); reply.send(httpErrors(500, 'Failed posting event to New Relic')); } diff --git a/src/routes/newrelic_metric.js b/src/routes/rest_server/newrelic_metric.js similarity index 96% rename from src/routes/newrelic_metric.js rename to src/routes/rest_server/newrelic_metric.js index 48f6cc82..8e31d853 100644 --- a/src/routes/newrelic_metric.js +++ b/src/routes/rest_server/newrelic_metric.js @@ -2,10 +2,10 @@ import httpErrors from 'http-errors'; import axios from 'axios'; // Load global variables and functions -import globals from '../globals.js'; +import globals from '../../globals.js'; -import { logRESTCall } from '../lib/log_rest_call.js'; -import apiPostNewRelicMetric from '../api/newrelic_metric.js'; +import { logRESTCall } from '../../lib/log_rest_call.js'; +import apiPostNewRelicMetric from '../../api/newrelic_metric.js'; // eslint-disable-next-line consistent-return async function handlerPostNewRelicMetric(request, reply) { @@ -102,7 +102,7 @@ async function handlerPostNewRelicMetric(request, reply) { // eslint-disable-next-line no-await-in-loop const res = await axios.post(remoteUrl, payload, { headers, timeout: 5000 }); globals.logger.debug( - `NEWRELIC METRIC: Result code from posting metric to New Relic account ${newRelicConfig[0].accountId}: ${res.status}, ${res.statusText}` + `NEWRELIC METRIC: Result code from posting metric to New Relic account ${newRelicConfig[0].accountId}: ${res.status}, ${res.statusText}`, ); if (res.status === 202) { @@ -122,8 +122,8 @@ async function handlerPostNewRelicMetric(request, reply) { `NEWRELIC METRIC: Failed posting metric to New Relic: ${JSON.stringify(request.body, null, 2)}, error is: ${JSON.stringify( err, null, - 2 - )}` + 2, + )}`, ); reply.send(httpErrors(500, 'Failed posting metric to New Relic')); } diff --git a/src/routes/scheduler.js b/src/routes/rest_server/scheduler.js similarity index 95% rename from src/routes/scheduler.js rename to src/routes/rest_server/scheduler.js index 1fea5e72..a3042947 100644 --- a/src/routes/scheduler.js +++ b/src/routes/rest_server/scheduler.js @@ -2,9 +2,9 @@ import httpErrors from 'http-errors'; import { v4 as uuidv4 } from 'uuid'; // Load global variables and functions -import globals from '../globals.js'; +import globals from '../../globals.js'; -import { logRESTCall } from '../lib/log_rest_call.js'; +import { logRESTCall } from '../../lib/log_rest_call.js'; import { getSchedulesStatus, existsSchedule, @@ -16,7 +16,7 @@ import { getSchedule, startAllSchedules, stopAllSchedules, -} from '../lib/scheduler.js'; +} from '../../lib/scheduler.js'; import { apiGETSchedules, @@ -27,7 +27,7 @@ import { apiPUTSchedulesStop, apiPUTSchedulesStopAll, apiGETSchedulerStatus, -} from '../api/scheduler.js'; +} from '../../api/scheduler.js'; async function handlerGETSchedules(request, reply) { try { @@ -50,7 +50,7 @@ async function handlerGETSchedules(request, reply) { } } catch (err) { globals.logger.error( - `REST SCHEDULER: Failed retrieving schedule with ID ${request.query.id}, error is: ${JSON.stringify(err, null, 2)}` + `REST SCHEDULER: Failed retrieving schedule with ID ${request.query.id}, error is: ${JSON.stringify(err, null, 2)}`, ); reply.send(httpErrors(500, 'Failed retrieving schedule')); } @@ -70,7 +70,7 @@ async function handlerPOSTSchedules(request, reply) { reply.code(201).send(JSON.stringify(newSchedule)); } catch (err) { globals.logger.error( - `REST SCHEDULER: Failed adding new schedule ${JSON.stringify(request.body, null, 2)}, error is: ${JSON.stringify(err, null, 2)}` + `REST SCHEDULER: Failed adding new schedule ${JSON.stringify(request.body, null, 2)}, error is: ${JSON.stringify(err, null, 2)}`, ); reply.send(httpErrors(500, 'Failed adding new schedule')); } @@ -90,7 +90,7 @@ async function handlerDELETESchedules(request, reply) { } } catch (err) { globals.logger.error( - `REST SCHEDULER: Failed deleting schedule ${request.params.scheduleId}, error is: ${JSON.stringify(err, null, 2)}` + `REST SCHEDULER: Failed deleting schedule ${request.params.scheduleId}, error is: ${JSON.stringify(err, null, 2)}`, ); reply.send(httpErrors(500, 'Failed deleting schedule')); } @@ -125,7 +125,7 @@ async function handlerPUTSchedulesStart(request, reply) { } } catch (err) { globals.logger.error( - `REST SCHEDULER: Failed starting schedule ${request.params.scheduleId}, error is: ${JSON.stringify(err, null, 2)}` + `REST SCHEDULER: Failed starting schedule ${request.params.scheduleId}, error is: ${JSON.stringify(err, null, 2)}`, ); reply.send(httpErrors(500, 'Failed starting schedule')); } @@ -157,7 +157,7 @@ async function handlerPUTSchedulesStop(request, reply) { } } catch (err) { globals.logger.error( - `REST SCHEDULER: Failed stopping schedule ${request.params.scheduleId}, error is: ${JSON.stringify(err, null, 2)}` + `REST SCHEDULER: Failed stopping schedule ${request.params.scheduleId}, error is: ${JSON.stringify(err, null, 2)}`, ); reply.send(httpErrors(500, 'Failed stopping schedule')); } diff --git a/src/routes/sense_app.js b/src/routes/rest_server/sense_app.js similarity index 94% rename from src/routes/sense_app.js rename to src/routes/rest_server/sense_app.js index 140db6df..65f9b005 100644 --- a/src/routes/sense_app.js +++ b/src/routes/rest_server/sense_app.js @@ -7,11 +7,11 @@ import { fileURLToPath } from 'url'; import upath from 'upath'; // Load global variables and functions -import globals from '../globals.js'; +import globals from '../../globals.js'; -import senseStartTask from '../qrs_util/sense_start_task.js'; -import { logRESTCall } from '../lib/log_rest_call.js'; -import apiPutAppReload from '../api/sense_app.js'; +import senseStartTask from '../../qrs_util/sense_start_task.js'; +import { logRESTCall } from '../../lib/log_rest_call.js'; +import apiPutAppReload from '../../api/sense_app.js'; async function handlerPutAppReload(request, reply) { try { @@ -110,7 +110,7 @@ async function handlerPutAppReload(request, reply) { const engineVersion = await global.engineVersion(); globals.logger.verbose( - `APPRELOAD: Starting reload of app ${request.params.appId} on host ${globals.configEngine.host}, engine version is ${engineVersion.qComponentVersion}.` + `APPRELOAD: Starting reload of app ${request.params.appId} on host ${globals.configEngine.host}, engine version is ${engineVersion.qComponentVersion}.`, ); const app = await global.openDoc(request.params.appId, '', '', '', false); @@ -121,28 +121,28 @@ async function handlerPutAppReload(request, reply) { await app.doSave(); globals.logger.verbose( - `APPRELOAD: Reload success of app ${request.params.appId} on host ${globals.configEngine.host}, engine version is ${engineVersion.qComponentVersion}.` + `APPRELOAD: Reload success of app ${request.params.appId} on host ${globals.configEngine.host}, engine version is ${engineVersion.qComponentVersion}.`, ); // Start downstream tasks, if such were specified in the request body // eslint-disable-next-line no-restricted-syntax for (const taskId of startQSEoWTaskOnSuccess) { globals.logger.verbose( - `APPRELOAD: Starting task ${taskId} after reloading success of ${request.params.appId} on host ${globals.configEngine.host}, engine version is ${engineVersion.qComponentVersion}.` + `APPRELOAD: Starting task ${taskId} after reloading success of ${request.params.appId} on host ${globals.configEngine.host}, engine version is ${engineVersion.qComponentVersion}.`, ); senseStartTask(taskId); } } else { globals.logger.warn( - `APPRELOAD: Reload failure of app ${request.params.appId} on host ${globals.configEngine.host}, engine version is ${engineVersion.qComponentVersion}.` + `APPRELOAD: Reload failure of app ${request.params.appId} on host ${globals.configEngine.host}, engine version is ${engineVersion.qComponentVersion}.`, ); // Start downstream tasks, if such were specified in the request body // eslint-disable-next-line no-restricted-syntax for (const taskId of startQSEoWTaskOnFailure) { globals.logger.verbose( - `APPRELOAD: Starting task ${taskId} after reloading failure of ${request.params.appId} on host ${globals.configEngine.host}, engine version is ${engineVersion.qComponentVersion}.` + `APPRELOAD: Starting task ${taskId} after reloading failure of ${request.params.appId} on host ${globals.configEngine.host}, engine version is ${engineVersion.qComponentVersion}.`, ); senseStartTask(taskId); @@ -152,11 +152,11 @@ async function handlerPutAppReload(request, reply) { try { if ((await session.close()) === true) { globals.logger.debug( - `APPRELOAD: Closed session after reloading app ${request.params.appId} on host ${globals.configEngine.host}` + `APPRELOAD: Closed session after reloading app ${request.params.appId} on host ${globals.configEngine.host}`, ); } else { globals.logger.error( - `APPRELOAD: Error closing session after reloading app ${request.params.appId} on host ${globals.configEngine.host}` + `APPRELOAD: Error closing session after reloading app ${request.params.appId} on host ${globals.configEngine.host}`, ); } } catch (err) { @@ -169,8 +169,8 @@ async function handlerPutAppReload(request, reply) { `APPRELOAD: Failed reloading app ${request.params.appId} on host ${globals.configEngine.host}, error is: ${JSON.stringify( err, null, - 2 - )}, stack: ${err.stack}.` + 2, + )}, stack: ${err.stack}.`, ); reply.send(httpErrors(500, 'Failed getting list of Sense apps')); } diff --git a/src/routes/sense_app_dump.js b/src/routes/rest_server/sense_app_dump.js similarity index 95% rename from src/routes/sense_app_dump.js rename to src/routes/rest_server/sense_app_dump.js index 5e7c1c7c..673bae99 100644 --- a/src/routes/sense_app_dump.js +++ b/src/routes/rest_server/sense_app_dump.js @@ -6,9 +6,9 @@ import { readFileSync } from 'fs'; import { fileURLToPath } from 'url'; import upath from 'upath'; -import globals from '../globals.js'; -import { logRESTCall } from '../lib/log_rest_call.js'; -import { apiGetSenseAppDump, apiGetAppDump } from '../api/sense_app_dump.js'; +import globals from '../../globals.js'; +import { logRESTCall } from '../../lib/log_rest_call.js'; +import { apiGetSenseAppDump, apiGetAppDump } from '../../api/sense_app_dump.js'; async function handlerGetSenseAppDump(request, reply) { try { diff --git a/src/routes/sense_list_apps.js b/src/routes/rest_server/sense_list_apps.js similarity index 96% rename from src/routes/sense_list_apps.js rename to src/routes/rest_server/sense_list_apps.js index 8cb7d1ef..c88d0c7b 100644 --- a/src/routes/sense_list_apps.js +++ b/src/routes/rest_server/sense_list_apps.js @@ -6,9 +6,9 @@ import { fileURLToPath } from 'url'; import upath from 'upath'; // Load global variables and functions -import globals from '../globals.js'; -import { logRESTCall } from '../lib/log_rest_call.js'; -import { apiGetSenseListApps, apiGetAppsList } from '../api/sense_list_apps.js'; +import globals from '../../globals.js'; +import { logRESTCall } from '../../lib/log_rest_call.js'; +import { apiGetSenseListApps, apiGetAppsList } from '../../api/sense_list_apps.js'; async function handlerGetSenseListApps(request, reply) { try { diff --git a/src/routes/sense_start_task.js b/src/routes/rest_server/sense_start_task.js similarity index 96% rename from src/routes/sense_start_task.js rename to src/routes/rest_server/sense_start_task.js index 76cd33e6..4042398f 100644 --- a/src/routes/sense_start_task.js +++ b/src/routes/rest_server/sense_start_task.js @@ -1,15 +1,15 @@ import httpErrors from 'http-errors'; // Load global variables and functions -import globals from '../globals.js'; +import globals from '../../globals.js'; -import doesTaskExist from '../qrs_util/does_task_exist.js'; -import senseStartTask from '../qrs_util/sense_start_task.js'; -import getTasks from '../qrs_util/get_tasks.js'; -import { logRESTCall } from '../lib/log_rest_call.js'; -import { addKeyValuePair } from '../lib/key_value_store.js'; -import apiPutStartTask from '../api/sense_start_task.js'; -import { verifyTaskId } from '../lib/config_util.js'; +import doesTaskExist from '../../qrs_util/does_task_exist.js'; +import senseStartTask from '../../qrs_util/sense_start_task.js'; +import getTasks from '../../qrs_util/get_tasks.js'; +import { logRESTCall } from '../../lib/log_rest_call.js'; +import { addKeyValuePair } from '../../lib/key_value_store.js'; +import apiPutStartTask from '../../api/sense_start_task.js'; +import { verifyTaskId } from '../../lib/config_util.js'; // Get array of allowed task IDs function getTaskIdAllowed() { @@ -126,12 +126,12 @@ async function handlerPutStartTask(request, reply) { if (taskExists.exists) { tasksToStartTaskId.push(taskExists.task); globals.logger.silly( - `STARTTASK: Added task to taskId start array, now ${tasksToStartTaskId.length} entries in that array` + `STARTTASK: Added task to taskId start array, now ${tasksToStartTaskId.length} entries in that array`, ); } else { tasksInvalid.push({ taskId: request.params.taskId }); globals.logger.silly( - `STARTTASK: Added task to invalid taskId array, now ${tasksInvalid.length} entries in that array` + `STARTTASK: Added task to invalid taskId array, now ${tasksInvalid.length} entries in that array`, ); } } else { @@ -182,12 +182,12 @@ async function handlerPutStartTask(request, reply) { if (taskExists.exists) { tasksToStartTaskId.push(taskExists.task); globals.logger.silly( - `STARTTASK: Added task to start array, now ${tasksToStartTaskId.length} entries in that array` + `STARTTASK: Added task to start array, now ${tasksToStartTaskId.length} entries in that array`, ); } else { tasksInvalid.push({ taskId: item.payload.taskId }); globals.logger.silly( - `STARTTASK: Added task to invalid taskId array, now ${tasksInvalid.length} entries in that array` + `STARTTASK: Added task to invalid taskId array, now ${tasksInvalid.length} entries in that array`, ); } } else { @@ -246,7 +246,7 @@ async function handlerPutStartTask(request, reply) { } else { // Task filtering is enabled and task ID is not on allowed list globals.logger.warn( - `STARTTASK: Task custom property is not allowed. Name: ${item.payload.customPropertyName}, value: ${item.payload.customPropertyValue}` + `STARTTASK: Task custom property is not allowed. Name: ${item.payload.customPropertyName}, value: ${item.payload.customPropertyValue}`, ); tasksCPDenied.push({ name: item.payload.customPropertyName, diff --git a/src/routes/slack_post_message.js b/src/routes/rest_server/slack_post_message.js similarity index 89% rename from src/routes/slack_post_message.js rename to src/routes/rest_server/slack_post_message.js index 368abbfd..85323eda 100644 --- a/src/routes/slack_post_message.js +++ b/src/routes/rest_server/slack_post_message.js @@ -1,11 +1,11 @@ import httpErrors from 'http-errors'; // Load global variables and functions -import globals from '../globals.js'; +import globals from '../../globals.js'; -import { logRESTCall } from '../lib/log_rest_call.js'; -import slackSend from '../lib/slack_api.js'; -import apiPutSlackPostMessage from '../api/slack_post_message.js'; +import { logRESTCall } from '../../lib/log_rest_call.js'; +import slackSend from '../../lib/slack_api.js'; +import apiPutSlackPostMessage from '../../api/slack_post_message.js'; async function handlerPutSlackPostMessage(request, reply) { try { @@ -40,7 +40,7 @@ async function handlerPutSlackPostMessage(request, reply) { } } catch (err) { globals.logger.error( - `SLACK: Failed sending Slack message: ${JSON.stringify(request.body, null, 2)}, error is: ${JSON.stringify(err, null, 2)}` + `SLACK: Failed sending Slack message: ${JSON.stringify(request.body, null, 2)}, error is: ${JSON.stringify(err, null, 2)}`, ); reply.send(httpErrors(500, 'Failed sending Slack message')); } diff --git a/src/test/routes/butlerping.test.js b/src/test/routes/butlerping.test.js index 151075f8..2375ee91 100644 --- a/src/test/routes/butlerping.test.js +++ b/src/test/routes/butlerping.test.js @@ -1,9 +1,6 @@ import axios from 'axios'; import config from 'config'; -// Load global variables and functions -import globals from '../../globals.js'; - const instance = axios.create({ baseURL: `http://localhost:${config.get('Butler.restServerConfig.serverPort')}`, timeout: 15000, @@ -11,6 +8,10 @@ const instance = axios.create({ let result; +// Load globals dynamically/async to ensure singleton pattern works +const settingsObj = (await import('../../globals.js')).default; +const globals = await settingsObj.init(); + // Get app version from package.json const { appVersion } = globals; diff --git a/src/test/routes/keyvaluestore.test.js b/src/test/routes/keyvaluestore.test.js index e474e658..ff65ce8b 100644 --- a/src/test/routes/keyvaluestore.test.js +++ b/src/test/routes/keyvaluestore.test.js @@ -333,7 +333,7 @@ describe('F6: POST /v4/keyvalues/:namespace', () => { }, { headers: { Expect: '100-Continue' }, - } + }, ); } catch (err) { // eslint-disable-next-line no-console diff --git a/src/test/routes/scheduler.test.js b/src/test/routes/scheduler.test.js index c8e91442..e1763614 100644 --- a/src/test/routes/scheduler.test.js +++ b/src/test/routes/scheduler.test.js @@ -212,7 +212,7 @@ describe('H4: PUT /v4/schedules/:scheduleId/start', () => { expect(typeof result.data).toBe('object'); }); - test('Response should contain correct fields', () => { + test('Response should contain correct fields', () => { expect(result.data[0].name).toEqual(scheduleName1); expect(result.data[0].cronSchedule).toEqual(scheduleCron1); expect(result.data[0].timezone).toEqual(scheduleTimezone1); @@ -272,7 +272,7 @@ describe('H5: PUT /v4/schedules/startall', () => { expect.objectContaining({ lastKnownState: 'stopped', }), - ]) + ]), ); }); }); @@ -386,7 +386,7 @@ describe('H7: PUT /v4/schedules/stopall', () => { expect.objectContaining({ lastKnownState: 'started', }), - ]) + ]), ); }); }); diff --git a/src/test/routes/sensestarttask_start-task-filter_off.test.js b/src/test/routes/sensestarttask_start-task-filter_off.test.js index a111f6a2..7a2d8040 100644 --- a/src/test/routes/sensestarttask_start-task-filter_off.test.js +++ b/src/test/routes/sensestarttask_start-task-filter_off.test.js @@ -257,7 +257,7 @@ if (config.get('Butler.startTaskFilter.enable') === false) { params: { allTaskIdsMustExist: false, }, - } + }, ); expect(result.status).toBe(200); @@ -310,7 +310,7 @@ if (config.get('Butler.startTaskFilter.enable') === false) { params: { allTaskIdsMustExist: true, }, - } + }, ); expect(result.status).toBe(200); @@ -363,7 +363,7 @@ if (config.get('Butler.startTaskFilter.enable') === false) { params: { allTaskIdsMustExist: false, }, - } + }, ); expect(result.status).toBe(200); @@ -866,7 +866,7 @@ if (config.get('Butler.startTaskFilter.enable') === false) { params: { allTaskIdsMustExist: true, }, - } + }, ); expect(result.status).toBe(200); @@ -923,7 +923,7 @@ if (config.get('Butler.startTaskFilter.enable') === false) { params: { allTaskIdsMustExist: true, }, - } + }, ); expect(result.status).toBe(200); @@ -980,7 +980,7 @@ if (config.get('Butler.startTaskFilter.enable') === false) { params: { allTaskIdsMustExist: false, }, - } + }, ); expect(result.status).toBe(200); @@ -1059,7 +1059,7 @@ if (config.get('Butler.startTaskFilter.enable') === false) { ], { headers: { Expect: '100-Continue' }, - } + }, ); expect(result.status).toBe(200); diff --git a/src/test/routes/sensestarttask_start-task-filter_on.test.js b/src/test/routes/sensestarttask_start-task-filter_on.test.js index 596f2bad..5e066332 100644 --- a/src/test/routes/sensestarttask_start-task-filter_on.test.js +++ b/src/test/routes/sensestarttask_start-task-filter_on.test.js @@ -256,7 +256,7 @@ if (config.get('Butler.startTaskFilter.enable') === true) { params: { allTaskIdsMustExist: false, }, - } + }, ); expect(result.status).toBe(200); @@ -310,7 +310,7 @@ if (config.get('Butler.startTaskFilter.enable') === true) { params: { allTaskIdsMustExist: true, }, - } + }, ); expect(result.status).toBe(200); @@ -363,7 +363,7 @@ if (config.get('Butler.startTaskFilter.enable') === true) { params: { allTaskIdsMustExist: false, }, - } + }, ); expect(result.status).toBe(200); @@ -872,7 +872,7 @@ if (config.get('Butler.startTaskFilter.enable') === true) { params: { allTaskIdsMustExist: true, }, - } + }, ); expect(result.status).toBe(200); @@ -929,7 +929,7 @@ if (config.get('Butler.startTaskFilter.enable') === true) { params: { allTaskIdsMustExist: true, }, - } + }, ); expect(result.status).toBe(200); @@ -986,7 +986,7 @@ if (config.get('Butler.startTaskFilter.enable') === true) { params: { allTaskIdsMustExist: false, }, - } + }, ); expect(result.status).toBe(200); @@ -1066,7 +1066,7 @@ if (config.get('Butler.startTaskFilter.enable') === true) { ], { headers: { Expect: '100-Continue' }, - } + }, ); expect(result.status).toBe(200); diff --git a/src/udp/udp_handlers.js b/src/udp/udp_handlers.js index dc9d94b7..84859375 100644 --- a/src/udp/udp_handlers.js +++ b/src/udp/udp_handlers.js @@ -22,7 +22,7 @@ import { postReloadTaskFailureNotificationInfluxDb, postReloadTaskSuccessNotific // Handler for failed scheduler initiated reloads const schedulerAborted = async (msg) => { globals.logger.verbose( - `TASKABORTED: Received reload aborted UDP message from scheduler: UDP msg=${msg[0]}, Host=${msg[1]}, App name=${msg[3]}, Task name=${msg[2]}, Log level=${msg[8]}, Log msg=${msg[10]}` + `TASKABORTED: Received reload aborted UDP message from scheduler: UDP msg=${msg[0]}, Host=${msg[1]}, App name=${msg[3]}, Task name=${msg[2]}, Log level=${msg[8]}, Log msg=${msg[10]}`, ); // Get script log for failed reloads. @@ -227,8 +227,8 @@ const schedulerAborted = async (msg) => { } else { globals.logger.warn( `MQTT: MQTT client not connected. Unable to publish message to topic ${globals.config.get( - 'Butler.mqttConfig.taskAbortedTopic' - )}` + 'Butler.mqttConfig.taskAbortedTopic', + )}`, ); } } @@ -256,7 +256,7 @@ const schedulerAborted = async (msg) => { logMessage: msg[10], qs_appTags: appTags, qs_taskTags: taskTags, - }) + }), ); } }; @@ -279,7 +279,7 @@ const schedulerFailed = async (msg) => { } globals.logger.verbose( - `TASKFAILURE: Received reload failed UDP message from scheduler: UDP msg=${msg[0]}, Host=${msg[1]}, App name=${msg[3]}, Task name=${msg[2]}, Log level=${msg[8]}, Log msg=${msg[10]}` + `TASKFAILURE: Received reload failed UDP message from scheduler: UDP msg=${msg[0]}, Host=${msg[1]}, App name=${msg[3]}, Task name=${msg[2]}, Log level=${msg[8]}, Log msg=${msg[10]}`, ); // First field in message (msg[0]) is message category (this is the modern/recent message format) @@ -516,8 +516,8 @@ const schedulerFailed = async (msg) => { } else { globals.logger.warn( `MQTT: MQTT client not connected. Unable to publish message to topic ${globals.config.get( - 'Butler.mqttConfig.taskFailureTopic' - )}` + 'Butler.mqttConfig.taskFailureTopic', + )}`, ); } } @@ -545,7 +545,7 @@ const schedulerFailed = async (msg) => { logMessage: msg[10], qs_appTags: appTags, qs_taskTags: taskTags, - }) + }), ); } }; @@ -555,7 +555,7 @@ const schedulerFailed = async (msg) => { // -------------------------------------------------------- const schedulerReloadTaskSuccess = async (msg) => { globals.logger.verbose( - `RELOAD TASK SUCCESS: Received reload task success UDP message from scheduler: UDP msg=${msg[0]}, Host=${msg[1]}, App name=${msg[3]}, Task name=${msg[2]}, Log level=${msg[8]}, Log msg=${msg[10]}` + `RELOAD TASK SUCCESS: Received reload task success UDP message from scheduler: UDP msg=${msg[0]}, Host=${msg[1]}, App name=${msg[3]}, Task name=${msg[2]}, Log level=${msg[8]}, Log msg=${msg[10]}`, ); const reloadTaskId = msg[5]; @@ -591,7 +591,7 @@ const schedulerReloadTaskSuccess = async (msg) => { reloadTaskId, customPropertyName, customPropertyValue, - globals.logger + globals.logger, ); if (customPropertyValueForTask) { @@ -626,12 +626,12 @@ const schedulerReloadTaskSuccess = async (msg) => { taskInfo.executionDuration.seconds === 0 ) { globals.logger.warn( - `RELOAD TASK SUCCESS: Task info for reload task ${reloadTaskId} retrieved successfully after ${retryCount} attempts, but duration is 0 seconds. This is likely caused by the QRS not having updated the execution details yet.` + `RELOAD TASK SUCCESS: Task info for reload task ${reloadTaskId} retrieved successfully after ${retryCount} attempts, but duration is 0 seconds. This is likely caused by the QRS not having updated the execution details yet.`, ); } globals.logger.debug( - `RELOAD TASK SUCCESS: Task info for reload task ${reloadTaskId} retrieved successfully after ${retryCount} attempts` + `RELOAD TASK SUCCESS: Task info for reload task ${reloadTaskId} retrieved successfully after ${retryCount} attempts`, ); break; } @@ -639,7 +639,7 @@ const schedulerReloadTaskSuccess = async (msg) => { retryCount += 1; globals.logger.verbose( - `RELOAD TASK SUCCESS: Unable to get task info for reload task ${reloadTaskId}. Attempt ${retryCount} of 5. Waiting 1 second before trying again` + `RELOAD TASK SUCCESS: Unable to get task info for reload task ${reloadTaskId}. Attempt ${retryCount} of 5. Waiting 1 second before trying again`, ); // eslint-disable-next-line no-await-in-loop @@ -648,7 +648,7 @@ const schedulerReloadTaskSuccess = async (msg) => { if (!taskInfo) { globals.logger.warn( - `RELOAD TASK SUCCESS: Unable to get task info for reload task ${reloadTaskId}. Not storing task info in InfluxDB` + `RELOAD TASK SUCCESS: Unable to get task info for reload task ${reloadTaskId}. Not storing task info in InfluxDB`, ); return false; } @@ -717,8 +717,8 @@ const udpInitTaskErrorServer = () => { } else { globals.logger.warn( `UDP SERVER INIT: MQTT client not connected. Unable to publish message to topic ${globals.config.get( - 'Butler.mqttConfig.taskFailureServerStatusTopic' - )}` + 'Butler.mqttConfig.taskFailureServerStatusTopic', + )}`, ); } } @@ -738,8 +738,8 @@ const udpInitTaskErrorServer = () => { } else { globals.logger.warn( `UDP SERVER ERROR: MQTT client not connected. Unable to publish message to topic ${globals.config.get( - 'Butler.mqttConfig.taskFailureServerStatusTopic' - )}` + 'Butler.mqttConfig.taskFailureServerStatusTopic', + )}`, ); } } @@ -829,7 +829,7 @@ const udpInitTaskErrorServer = () => { // There should be exactly 11 fields in the message if (msg.length !== 9) { globals.logger.warn( - `UDP HANDLER ENGINE RELOAD FAILED: Invalid number of fields in UDP message. Expected 9, got ${msg.length}.` + `UDP HANDLER ENGINE RELOAD FAILED: Invalid number of fields in UDP message. Expected 9, got ${msg.length}.`, ); globals.logger.warn(`UDP HANDLER ENGINE RELOAD FAILED: Incoming log message was:\n${message.toString()}`); globals.logger.warn(`UDP HANDLER ENGINE RELOAD FAILED: Aborting processing of this message.`); @@ -837,7 +837,7 @@ const udpInitTaskErrorServer = () => { } globals.logger.verbose( - `UDP HANDLER ENGINE RELOAD FAILED: Received reload failed UDP message from engine: Host=${msg[1]}, AppID=${msg[2]}, User directory=${msg[4]}, User=${msg[5]}` + `UDP HANDLER ENGINE RELOAD FAILED: Received reload failed UDP message from engine: Host=${msg[1]}, AppID=${msg[2]}, User directory=${msg[4]}, User=${msg[5]}`, ); } else if (msg[0].toLowerCase() === '/scheduler-reload-failed/') { // Scheduler log appender detecting failed scheduler-started reload @@ -846,7 +846,7 @@ const udpInitTaskErrorServer = () => { // There should be exactly 11 fields in the message if (msg.length !== 11) { globals.logger.warn( - `UDP HANDLER SCHEDULER RELOAD FAILED: Invalid number of fields in UDP message. Expected 11, got ${msg.length}.` + `UDP HANDLER SCHEDULER RELOAD FAILED: Invalid number of fields in UDP message. Expected 11, got ${msg.length}.`, ); globals.logger.warn(`UDP HANDLER SCHEDULER RELOAD FAILED: Incoming log message was:\n${message.toString()}`); globals.logger.warn(`UDP HANDLER SCHEDULER RELOAD FAILED: Aborting processing of this message.`); @@ -861,7 +861,7 @@ const udpInitTaskErrorServer = () => { // There should be exactly 11 fields in the message if (msg.length !== 11) { globals.logger.warn( - `UDP HANDLER SCHEDULER RELOAD ABORTED: Invalid number of fields in UDP message. Expected 11, got ${msg.length}.` + `UDP HANDLER SCHEDULER RELOAD ABORTED: Invalid number of fields in UDP message. Expected 11, got ${msg.length}.`, ); globals.logger.warn(`UDP HANDLER SCHEDULER RELOAD ABORTED: Incoming log message was:\n${message.toString()}`); globals.logger.warn(`UDP HANDLER SCHEDULER RELOAD ABORTED: Aborting processing of this message.`); @@ -876,7 +876,7 @@ const udpInitTaskErrorServer = () => { // There should be exactly 11 fields in the message if (msg.length !== 11) { globals.logger.warn( - `UDP HANDLER SCHEDULER RELOAD TASK SUCCESS: Invalid number of fields in UDP message. Expected 11, got ${msg.length}.` + `UDP HANDLER SCHEDULER RELOAD TASK SUCCESS: Invalid number of fields in UDP message. Expected 11, got ${msg.length}.`, ); globals.logger.warn(`UDP HANDLER SCHEDULER RELOAD TASK SUCCESS: Incoming log message was:\n${message.toString()}`); globals.logger.warn(`UDP HANDLER SCHEDULER RELOAD TASK SUCCESS: Aborting processing of this message.`); diff --git a/static/configvis/butler.png b/static/configvis/butler.png new file mode 100644 index 00000000..5ecc6944 Binary files /dev/null and b/static/configvis/butler.png differ diff --git a/static/configvis/download-solid.svg b/static/configvis/download-solid.svg new file mode 100644 index 00000000..34fdf86d --- /dev/null +++ b/static/configvis/download-solid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/configvis/index.html b/static/configvis/index.html new file mode 100644 index 00000000..f5b03a79 --- /dev/null +++ b/static/configvis/index.html @@ -0,0 +1,247 @@ + + + + + Ctrl-Q + + + + + + + + + + + + + + + + + + +
+

YAML configuration

+
{{butlerConfigYaml}}
+
+ +
+

JSON tree view

+
+ Your HTML. +
+
+ + + + diff --git a/static/configvis/jsontree.js b/static/configvis/jsontree.js new file mode 100644 index 00000000..cc09af64 --- /dev/null +++ b/static/configvis/jsontree.js @@ -0,0 +1,927 @@ +'use strict'; + +var Is; + +((e) => { + function t(e) { + return e !== null && e !== void 0 && e.toString() !== ''; + } + e.defined = t; + function n(e) { + return t(e) && typeof e === 'object'; + } + e.definedObject = n; + function r(e) { + return t(e) && typeof e === 'boolean'; + } + e.definedBoolean = r; + function o(e) { + return t(e) && typeof e === 'string'; + } + e.definedString = o; + function l(e) { + return t(e) && typeof e === 'function'; + } + e.definedFunction = l; + function a(e) { + return t(e) && typeof e === 'number'; + } + e.definedNumber = a; + function i(e) { + return n(e) && e instanceof Array; + } + e.definedArray = i; + function s(e) { + return n(e) && e instanceof Date; + } + e.definedDate = s; + function u(e) { + return t(e) && typeof e === 'number' && e % 1 !== 0; + } + e.definedDecimal = u; + function c(e, t = 1) { + return !i(e) || e.length < t; + } + e.invalidOptionArray = c; + function d(e) { + let t = e.length >= 2 && e.length <= 7; + if (t && e[0] === '#') { + t = isNaN(+e.substring(1, e.length - 1)); + } + return t; + } + e.hexColor = d; +})(Is || (Is = {})); + +var Default; + +((e) => { + function t(e, t) { + return typeof e === 'string' ? e : t; + } + e.getAnyString = t; + function n(e, t) { + return Is.definedString(e) ? e : t; + } + e.getString = n; + function r(e, t) { + return Is.definedBoolean(e) ? e : t; + } + e.getBoolean = r; + function o(e, t) { + return Is.definedNumber(e) ? e : t; + } + e.getNumber = o; + function l(e, t) { + return Is.definedFunction(e) ? e : t; + } + e.getFunction = l; + function a(e, t) { + return Is.definedArray(e) ? e : t; + } + e.getArray = a; + function i(e, t) { + return Is.definedObject(e) ? e : t; + } + e.getObject = i; + function s(e, t) { + let n = t; + if (Is.definedString(e)) { + const r = e.toString().split(' '); + if (r.length === 0) { + e = t; + } else { + n = r; + } + } else { + n = a(e, t); + } + return n; + } + e.getStringOrArray = s; + function u(e, t) { + var n; + const r = new RegExp(`^-?\\d+(?:.\\d{0,${t || -1}})?`); + return ((n = e.toString().match(r)) == null ? void 0 : n[0]) || ''; + } + e.getFixedDecimalPlacesValue = u; + function c(e) { + let t; + const n = e.toString().split('('); + const r = n[0].split(' '); + if (r.length === 2) { + t = r[1]; + } else { + t = r[0]; + } + t += '()'; + return t; + } + e.getFunctionName = c; +})(Default || (Default = {})); + +var DomElement; + +((e) => { + function t(e, t, n = '', r = null) { + const o = t.toLowerCase(); + const l = o === 'text'; + let a = l ? document.createTextNode('') : document.createElement(o); + if (Is.defined(n)) { + a.className = n; + } + if (Is.defined(r)) { + e.insertBefore(a, r); + } else { + e.appendChild(a); + } + return a; + } + e.create = t; + function n(e, n, r, o, l = null) { + const a = t(e, n, r, l); + a.innerHTML = o; + return a; + } + e.createWithHTML = n; + function r(e, t) { + e.classList.add(t); + } + e.addClass = r; +})(DomElement || (DomElement = {})); + +var Str; + +((e) => { + function t() { + const e = []; + for (let t = 0; t < 32; t++) { + if (t === 8 || t === 12 || t === 16 || t === 20) { + e.push('-'); + } + const n = Math.floor(Math.random() * 16).toString(16); + e.push(n); + } + return e.join(''); + } + e.newGuid = t; + function n(e, t = 1) { + const n = e.toString(); + let r = n; + if (n.length < t) { + const e = t - n.length + 1; + r = Array(e).join('0') + n; + } + return r; + } + e.padNumber = n; +})(Str || (Str = {})); + +var DateTime; + +((e) => { + function t(e) { + return e.getDay() - 1 < 0 ? 6 : e.getDay() - 1; + } + e.getWeekdayNumber = t; + function n(e, t) { + let n = e.text.thText; + if (t === 31 || t === 21 || t === 1) { + n = e.text.stText; + } else if (t === 22 || t === 2) { + n = e.text.ndText; + } else if (t === 23 || t === 3) { + n = e.text.rdText; + } + return n; + } + e.getDayOrdinal = n; + function r(e, r, o) { + let l = o; + const a = t(r); + l = l.replace('{hh}', Str.padNumber(r.getHours(), 2)); + l = l.replace('{h}', r.getHours().toString()); + l = l.replace('{MM}', Str.padNumber(r.getMinutes(), 2)); + l = l.replace('{M}', r.getMinutes().toString()); + l = l.replace('{ss}', Str.padNumber(r.getSeconds(), 2)); + l = l.replace('{s}', r.getSeconds().toString()); + l = l.replace('{dddd}', e.text.dayNames[a]); + l = l.replace('{ddd}', e.text.dayNamesAbbreviated[a]); + l = l.replace('{dd}', Str.padNumber(r.getDate())); + l = l.replace('{d}', r.getDate().toString()); + l = l.replace('{o}', n(e, r.getDate())); + l = l.replace('{mmmm}', e.text.monthNames[r.getMonth()]); + l = l.replace('{mmm}', e.text.monthNamesAbbreviated[r.getMonth()]); + l = l.replace('{mm}', Str.padNumber(r.getMonth() + 1)); + l = l.replace('{m}', (r.getMonth() + 1).toString()); + l = l.replace('{yyyy}', r.getFullYear().toString()); + l = l.replace('{yyy}', r.getFullYear().toString().substring(1)); + l = l.replace('{yy}', r.getFullYear().toString().substring(2)); + l = l.replace('{y}', Number.parseInt(r.getFullYear().toString().substring(2)).toString()); + return l; + } + e.getCustomFormattedDateText = r; +})(DateTime || (DateTime = {})); + +var Constants; + +((e) => { + e.JSONTREE_JS_ATTRIBUTE_NAME = 'data-jsontree-js'; +})(Constants || (Constants = {})); + +var Binding; + +((e) => { + let t; + ((t) => { + function n(t, n) { + const r = e.Options.get(t); + r._currentView = {}; + r._currentView.element = n; + r._currentView.dataArrayCurrentIndex = 0; + return r; + } + t.getForNewInstance = n; + function r(e) { + let t = Default.getObject(e, {}); + t.data = Default.getObject(t.data, null); + t.showCounts = Default.getBoolean(t.showCounts, true); + t.useZeroIndexingForArrays = Default.getBoolean(t.useZeroIndexingForArrays, true); + t.dateTimeFormat = Default.getString(t.dateTimeFormat, '{dd}{o} {mmmm} {yyyy} {hh}:{MM}:{ss}'); + t.showArrowToggles = Default.getBoolean(t.showArrowToggles, true); + t.showStringQuotes = Default.getBoolean(t.showStringQuotes, true); + t.showAllAsClosed = Default.getBoolean(t.showAllAsClosed, false); + t.sortPropertyNames = Default.getBoolean(t.sortPropertyNames, true); + t.sortPropertyNamesInAlphabeticalOrder = Default.getBoolean(t.sortPropertyNamesInAlphabeticalOrder, true); + t.showCommas = Default.getBoolean(t.showCommas, false); + t.reverseArrayValues = Default.getBoolean(t.reverseArrayValues, false); + t.addArrayIndexPadding = Default.getBoolean(t.addArrayIndexPadding, false); + t.showValueColors = Default.getBoolean(t.showValueColors, true); + t.maximumDecimalPlaces = Default.getNumber(t.maximumDecimalPlaces, 2); + t.maximumStringLength = Default.getNumber(t.maximumStringLength, 0); + t.showStringHexColors = Default.getBoolean(t.showStringHexColors, false); + t.showArrayItemsAsSeparateObjects = Default.getBoolean(t.showArrayItemsAsSeparateObjects, false); + t.copyOnlyCurrentPage = Default.getBoolean(t.copyOnlyCurrentPage, false); + t = o(t); + t = l(t); + t = a(t); + return t; + } + t.get = r; + function o(e) { + e.title = Default.getObject(e.title, {}); + e.title.text = Default.getString(e.title.text, 'JsonTree.js'); + e.title.show = Default.getBoolean(e.title.show, true); + e.title.showTreeControls = Default.getBoolean(e.title.showTreeControls, true); + e.title.showCopyButton = Default.getBoolean(e.title.showCopyButton, true); + return e; + } + function l(e) { + e.ignore = Default.getObject(e.ignore, {}); + e.ignore.nullValues = Default.getBoolean(e.ignore.nullValues, false); + e.ignore.functionValues = Default.getBoolean(e.ignore.functionValues, false); + e.ignore.unknownValues = Default.getBoolean(e.ignore.unknownValues, false); + e.ignore.booleanValues = Default.getBoolean(e.ignore.booleanValues, false); + e.ignore.decimalValues = Default.getBoolean(e.ignore.decimalValues, false); + e.ignore.numberValues = Default.getBoolean(e.ignore.numberValues, false); + e.ignore.stringValues = Default.getBoolean(e.ignore.stringValues, false); + e.ignore.dateValues = Default.getBoolean(e.ignore.dateValues, false); + e.ignore.objectValues = Default.getBoolean(e.ignore.objectValues, false); + e.ignore.arrayValues = Default.getBoolean(e.ignore.arrayValues, false); + return e; + } + function a(e) { + e.events = Default.getObject(e.events, {}); + e.events.onBeforeRender = Default.getFunction(e.events.onBeforeRender, null); + e.events.onRenderComplete = Default.getFunction(e.events.onRenderComplete, null); + e.events.onValueClick = Default.getFunction(e.events.onValueClick, null); + e.events.onRefresh = Default.getFunction(e.events.onRefresh, null); + e.events.onCopyAll = Default.getFunction(e.events.onCopyAll, null); + e.events.onOpenAll = Default.getFunction(e.events.onOpenAll, null); + e.events.onCloseAll = Default.getFunction(e.events.onCloseAll, null); + e.events.onDestroy = Default.getFunction(e.events.onDestroy, null); + e.events.onBooleanRender = Default.getFunction(e.events.onBooleanRender, null); + e.events.onDecimalRender = Default.getFunction(e.events.onDecimalRender, null); + e.events.onNumberRender = Default.getFunction(e.events.onNumberRender, null); + e.events.onStringRender = Default.getFunction(e.events.onStringRender, null); + e.events.onDateRender = Default.getFunction(e.events.onDateRender, null); + e.events.onFunctionRender = Default.getFunction(e.events.onFunctionRender, null); + e.events.onNullRender = Default.getFunction(e.events.onNullRender, null); + e.events.onUnknownRender = Default.getFunction(e.events.onUnknownRender, null); + return e; + } + })((t = e.Options || (e.Options = {}))); +})(Binding || (Binding = {})); + +var Config; + +((e) => { + let t; + ((e) => { + function t(e = null) { + let t = Default.getObject(e, {}); + t.safeMode = Default.getBoolean(t.safeMode, true); + t.domElementTypes = Default.getStringOrArray(t.domElementTypes, ['*']); + t = n(t); + return t; + } + e.get = t; + function n(e) { + e.text = Default.getObject(e.text, {}); + e.text.objectText = Default.getAnyString(e.text.objectText, 'object'); + e.text.arrayText = Default.getAnyString(e.text.arrayText, 'array'); + e.text.closeAllButtonText = Default.getAnyString(e.text.closeAllButtonText, 'Close All'); + e.text.openAllButtonText = Default.getAnyString(e.text.openAllButtonText, 'Open All'); + e.text.copyAllButtonText = Default.getAnyString(e.text.copyAllButtonText, 'Copy All'); + e.text.objectErrorText = Default.getAnyString(e.text.objectErrorText, 'Errors in object: {{error_1}}, {{error_2}}'); + e.text.attributeNotValidErrorText = Default.getAnyString( + e.text.attributeNotValidErrorText, + "The attribute '{{attribute_name}}' is not a valid object.", + ); + e.text.attributeNotSetErrorText = Default.getAnyString( + e.text.attributeNotSetErrorText, + "The attribute '{{attribute_name}}' has not been set correctly.", + ); + e.text.stText = Default.getAnyString(e.text.stText, 'st'); + e.text.ndText = Default.getAnyString(e.text.ndText, 'nd'); + e.text.rdText = Default.getAnyString(e.text.rdText, 'rd'); + e.text.thText = Default.getAnyString(e.text.thText, 'th'); + e.text.ellipsisText = Default.getAnyString(e.text.ellipsisText, '...'); + e.text.closeAllButtonSymbolText = Default.getAnyString(e.text.closeAllButtonSymbolText, '↑'); + e.text.openAllButtonSymbolText = Default.getAnyString(e.text.openAllButtonSymbolText, '↓'); + e.text.copyAllButtonSymbolText = Default.getAnyString(e.text.copyAllButtonSymbolText, '❐'); + e.text.backButtonText = Default.getAnyString(e.text.backButtonText, 'Back'); + e.text.nextButtonText = Default.getAnyString(e.text.nextButtonText, 'Next'); + e.text.backButtonSymbolText = Default.getAnyString(e.text.backButtonSymbolText, '←'); + e.text.nextButtonSymbolText = Default.getAnyString(e.text.nextButtonSymbolText, '→'); + if (Is.invalidOptionArray(e.text.dayNames, 7)) { + e.text.dayNames = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']; + } + if (Is.invalidOptionArray(e.text.dayNamesAbbreviated, 7)) { + e.text.dayNamesAbbreviated = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']; + } + if (Is.invalidOptionArray(e.text.monthNames, 12)) { + e.text.monthNames = [ + 'January', + 'February', + 'March', + 'April', + 'May', + 'June', + 'July', + 'August', + 'September', + 'October', + 'November', + 'December', + ]; + } + if (Is.invalidOptionArray(e.text.monthNamesAbbreviated, 12)) { + e.text.monthNamesAbbreviated = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; + } + return e; + } + })((t = e.Options || (e.Options = {}))); +})(Config || (Config = {})); + +var Trigger; + +((e) => { + function t(e, ...t) { + let n = null; + if (Is.definedFunction(e)) { + n = e.apply(null, [].slice.call(t, 0)); + } + return n; + } + e.customEvent = t; +})(Trigger || (Trigger = {})); + +(() => { + let _configuration = {}; + let _elements_Data = {}; + function render() { + const e = _configuration.domElementTypes; + const t = e.length; + for (let n = 0; n < t; n++) { + const t = document.getElementsByTagName(e[n]); + const r = [].slice.call(t); + const o = r.length; + for (let e = 0; e < o; e++) { + if (!renderElement(r[e])) { + break; + } + } + } + } + function renderElement(e) { + let t = true; + if (Is.defined(e) && e.hasAttribute(Constants.JSONTREE_JS_ATTRIBUTE_NAME)) { + const n = e.getAttribute(Constants.JSONTREE_JS_ATTRIBUTE_NAME); + if (Is.definedString(n)) { + const r = getObjectFromString(n); + if (r.parsed && Is.definedObject(r.object)) { + renderControl(Binding.Options.getForNewInstance(r.object, e)); + } else { + if (!_configuration.safeMode) { + console.error( + _configuration.text.attributeNotValidErrorText.replace( + '{{attribute_name}}', + Constants.JSONTREE_JS_ATTRIBUTE_NAME, + ), + ); + t = false; + } + } + } else { + if (!_configuration.safeMode) { + console.error( + _configuration.text.attributeNotSetErrorText.replace('{{attribute_name}}', Constants.JSONTREE_JS_ATTRIBUTE_NAME), + ); + t = false; + } + } + } + return t; + } + function renderControl(e) { + Trigger.customEvent(e.events.onBeforeRender, e._currentView.element); + if (!Is.definedString(e._currentView.element.id)) { + e._currentView.element.id = Str.newGuid(); + } + e._currentView.element.className = 'json-tree-js'; + e._currentView.element.removeAttribute(Constants.JSONTREE_JS_ATTRIBUTE_NAME); + if (!_elements_Data.hasOwnProperty(e._currentView.element.id)) { + _elements_Data[e._currentView.element.id] = e; + } + renderControlContainer(e); + Trigger.customEvent(e.events.onRenderComplete, e._currentView.element); + } + function renderControlContainer(e) { + let t = _elements_Data[e._currentView.element.id].data; + e._currentView.element.innerHTML = ''; + renderControlTitleBar(e, t); + if (e.showArrayItemsAsSeparateObjects) { + t = t[e._currentView.dataArrayCurrentIndex]; + } + if (Is.definedObject(t) && !Is.definedArray(t)) { + renderObject(e._currentView.element, e, t, true); + } else if (Is.definedArray(t)) { + renderArray(e._currentView.element, e, t); + } + } + function renderControlTitleBar(e, t) { + if (e.title.show || e.title.showTreeControls || e.title.showCopyButton) { + const n = DomElement.create(e._currentView.element, 'div', 'title-bar'); + const r = DomElement.create(n, 'div', 'controls'); + if (e.title.show) { + DomElement.createWithHTML(n, 'div', 'title', e.title.text, r); + } + if (e.title.showCopyButton) { + const t = DomElement.createWithHTML(r, 'button', 'copy-all', _configuration.text.copyAllButtonSymbolText); + t.title = _configuration.text.copyAllButtonText; + t.onclick = () => { + let t = null; + if (e.copyOnlyCurrentPage && e.showArrayItemsAsSeparateObjects) { + t = JSON.stringify(_elements_Data[e._currentView.element.id].data[e._currentView.dataArrayCurrentIndex], null, 2); + } else { + t = JSON.stringify(_elements_Data[e._currentView.element.id].data, null, 2); + } + navigator.clipboard.writeText(t); + Trigger.customEvent(e.events.onCopyAll, t); + }; + } + if (e.title.showTreeControls) { + const t = DomElement.createWithHTML(r, 'button', 'openAll', _configuration.text.openAllButtonSymbolText); + t.title = _configuration.text.openAllButtonText; + const n = DomElement.createWithHTML(r, 'button', 'closeAll', _configuration.text.closeAllButtonSymbolText); + n.title = _configuration.text.closeAllButtonText; + t.onclick = () => { + openAllNodes(e); + }; + n.onclick = () => { + closeAllNodes(e); + }; + } + if (e.showArrayItemsAsSeparateObjects && Is.definedArray(t) && t.length > 1) { + const n = DomElement.createWithHTML(r, 'button', 'back', _configuration.text.backButtonSymbolText); + n.title = _configuration.text.backButtonText; + if (e._currentView.dataArrayCurrentIndex > 0) { + n.onclick = () => { + e._currentView.dataArrayCurrentIndex--; + renderControlContainer(e); + }; + } else { + n.disabled = true; + } + const o = DomElement.createWithHTML(r, 'button', 'next', _configuration.text.nextButtonSymbolText); + o.title = _configuration.text.nextButtonText; + if (e._currentView.dataArrayCurrentIndex < t.length - 1) { + o.onclick = () => { + e._currentView.dataArrayCurrentIndex++; + renderControlContainer(e); + }; + } else { + o.disabled = true; + } + } else { + e.showArrayItemsAsSeparateObjects = false; + } + } + } + function openAllNodes(e) { + e.showAllAsClosed = false; + renderControlContainer(e); + Trigger.customEvent(e.events.onOpenAll, e._currentView.element); + } + function closeAllNodes(e) { + e.showAllAsClosed = true; + renderControlContainer(e); + Trigger.customEvent(e.events.onCloseAll, e._currentView.element); + } + function renderObject(e, t, n, r = false) { + const o = DomElement.create(e, 'div', 'object-type-title'); + const l = DomElement.create(e, 'div', 'object-type-contents'); + const a = t.showArrowToggles ? DomElement.create(o, 'div', 'down-arrow') : null; + const i = renderObjectValues(a, l, t, n); + const s = DomElement.createWithHTML(o, 'span', t.showValueColors ? 'object' : '', _configuration.text.objectText); + if (r && t.showArrayItemsAsSeparateObjects) { + let e = t.useZeroIndexingForArrays + ? t._currentView.dataArrayCurrentIndex.toString() + : (t._currentView.dataArrayCurrentIndex + 1).toString(); + DomElement.createWithHTML(o, 'span', t.showValueColors ? 'object data-array-index' : 'data-array-index', `[${e}]:`, s); + } + if (t.showCounts && i > 0) { + DomElement.createWithHTML(o, 'span', t.showValueColors ? 'object count' : 'count', `{${i}}`); + } + } + function renderArray(e, t, n) { + const r = DomElement.create(e, 'div', 'object-type-title'); + const o = DomElement.create(e, 'div', 'object-type-contents'); + const l = t.showArrowToggles ? DomElement.create(r, 'div', 'down-arrow') : null; + DomElement.createWithHTML(r, 'span', t.showValueColors ? 'array' : '', _configuration.text.arrayText); + renderArrayValues(l, o, t, n); + if (t.showCounts) { + DomElement.createWithHTML(r, 'span', t.showValueColors ? 'array count' : 'count', `[${n.length}]`); + } + } + function renderObjectValues(e, t, n, r) { + let o = 0; + let l = []; + for (let e in r) { + if (r.hasOwnProperty(e)) { + l.push(e); + } + } + if (n.sortPropertyNames) { + l = l.sort(); + if (!n.sortPropertyNamesInAlphabeticalOrder) { + l = l.reverse(); + } + } + const a = l.length; + for (let e = 0; e < a; e++) { + const i = l[e]; + if (r.hasOwnProperty(i)) { + renderValue(t, n, i, r[i], e === a - 1); + o++; + } + } + addArrowEvent(n, e, t); + return o; + } + function renderArrayValues(e, t, n, r) { + const o = r.length; + if (!n.reverseArrayValues) { + for (let e = 0; e < o; e++) { + renderValue(t, n, getIndexName(n, e, o), r[e], e === o - 1); + } + } else { + for (let e = o; e--; ) { + renderValue(t, n, getIndexName(n, e, o), r[e], e === 0); + } + } + addArrowEvent(n, e, t); + } + function renderValue(e, t, n, r, o) { + const l = DomElement.create(e, 'div', 'object-type-value'); + const a = t.showArrowToggles ? DomElement.create(l, 'div', 'no-arrow') : null; + let i = null; + let s = null; + let u = false; + let c = null; + let d = true; + DomElement.createWithHTML(l, 'span', 'title', n); + DomElement.createWithHTML(l, 'span', 'split', ':'); + if (!Is.defined(r)) { + if (!t.ignore.nullValues) { + i = t.showValueColors ? 'null' : ''; + s = DomElement.createWithHTML(l, 'span', i, 'null'); + d = false; + if (Is.definedFunction(t.events.onNullRender)) { + Trigger.customEvent(t.events.onNullRender, s); + } + createComma(t, l, o); + } else { + u = true; + } + } else if (Is.definedFunction(r)) { + if (!t.ignore.functionValues) { + i = t.showValueColors ? 'function' : ''; + s = DomElement.createWithHTML(l, 'span', i, Default.getFunctionName(r)); + c = 'function'; + if (Is.definedFunction(t.events.onFunctionRender)) { + Trigger.customEvent(t.events.onFunctionRender, s); + } + createComma(t, l, o); + } else { + u = true; + } + } else if (Is.definedBoolean(r)) { + if (!t.ignore.booleanValues) { + i = t.showValueColors ? 'boolean' : ''; + s = DomElement.createWithHTML(l, 'span', i, r); + c = 'boolean'; + if (Is.definedFunction(t.events.onBooleanRender)) { + Trigger.customEvent(t.events.onBooleanRender, s); + } + createComma(t, l, o); + } else { + u = true; + } + } else if (Is.definedDecimal(r)) { + if (!t.ignore.decimalValues) { + const e = Default.getFixedDecimalPlacesValue(r, t.maximumDecimalPlaces); + i = t.showValueColors ? 'decimal' : ''; + s = DomElement.createWithHTML(l, 'span', i, e); + c = 'decimal'; + if (Is.definedFunction(t.events.onDecimalRender)) { + Trigger.customEvent(t.events.onDecimalRender, s); + } + createComma(t, l, o); + } else { + u = true; + } + } else if (Is.definedNumber(r)) { + if (!t.ignore.numberValues) { + i = t.showValueColors ? 'number' : ''; + s = DomElement.createWithHTML(l, 'span', i, r); + c = 'number'; + if (Is.definedFunction(t.events.onNumberRender)) { + Trigger.customEvent(t.events.onNumberRender, s); + } + createComma(t, l, o); + } else { + u = true; + } + } else if (Is.definedString(r)) { + if (!t.ignore.stringValues) { + let e = null; + if (t.showValueColors && t.showStringHexColors && Is.hexColor(r)) { + e = r; + } else { + if (t.maximumStringLength > 0 && r.length > t.maximumStringLength) { + r = r.substring(0, t.maximumStringLength) + _configuration.text.ellipsisText; + } + } + const n = t.showStringQuotes ? `"${r}"` : r; + i = t.showValueColors ? 'string' : ''; + s = DomElement.createWithHTML(l, 'span', i, n); + c = 'string'; + if (Is.definedString(e)) { + s.style.color = e; + } + if (Is.definedFunction(t.events.onStringRender)) { + Trigger.customEvent(t.events.onStringRender, s); + } + createComma(t, l, o); + } else { + u = true; + } + } else if (Is.definedDate(r)) { + if (!t.ignore.dateValues) { + i = t.showValueColors ? 'date' : ''; + s = DomElement.createWithHTML(l, 'span', i, DateTime.getCustomFormattedDateText(_configuration, r, t.dateTimeFormat)); + c = 'date'; + if (Is.definedFunction(t.events.onDateRender)) { + Trigger.customEvent(t.events.onDateRender, s); + } + createComma(t, l, o); + } else { + u = true; + } + } else if (Is.definedObject(r) && !Is.definedArray(r)) { + if (!t.ignore.objectValues) { + const e = DomElement.create(l, 'span', t.showValueColors ? 'object' : ''); + const n = DomElement.create(l, 'div', 'object-type-contents'); + const i = renderObjectValues(a, n, t, r); + DomElement.createWithHTML(e, 'span', 'title', _configuration.text.objectText); + if (t.showCounts && i > 0) { + DomElement.createWithHTML(e, 'span', 'count', `{${i}}`); + } + createComma(t, e, o); + c = 'object'; + } else { + u = true; + } + } else if (Is.definedArray(r)) { + if (!t.ignore.arrayValues) { + const e = DomElement.create(l, 'span', t.showValueColors ? 'array' : ''); + const n = DomElement.create(l, 'div', 'object-type-contents'); + DomElement.createWithHTML(e, 'span', 'title', _configuration.text.arrayText); + if (t.showCounts) { + DomElement.createWithHTML(e, 'span', 'count', `[${r.length}]`); + } + createComma(t, e, o); + renderArrayValues(a, n, t, r); + c = 'array'; + } else { + u = true; + } + } else { + if (!t.ignore.unknownValues) { + i = t.showValueColors ? 'unknown' : ''; + s = DomElement.createWithHTML(l, 'span', i, r.toString()); + c = 'unknown'; + if (Is.definedFunction(t.events.onUnknownRender)) { + Trigger.customEvent(t.events.onUnknownRender, s); + } + createComma(t, l, o); + } else { + u = true; + } + } + if (u) { + e.removeChild(l); + } else { + if (Is.defined(s)) { + addValueClickEvent(t, s, r, c, d); + } + } + } + function addValueClickEvent(e, t, n, r, o) { + if (o && Is.definedFunction(e.events.onValueClick)) { + t.onclick = () => { + Trigger.customEvent(e.events.onValueClick, n, r); + }; + } else { + DomElement.addClass(t, 'no-hover'); + } + } + function addArrowEvent(e, t, n) { + if (Is.defined(t)) { + t.onclick = () => { + if (t.className === 'down-arrow') { + n.style.display = 'none'; + t.className = 'right-arrow'; + } else { + n.style.display = 'block'; + t.className = 'down-arrow'; + } + }; + if (e.showAllAsClosed) { + n.style.display = 'none'; + t.className = 'right-arrow'; + } else { + t.className = 'down-arrow'; + } + } + } + function createComma(e, t, n) { + if (e.showCommas && !n) { + DomElement.createWithHTML(t, 'span', 'comma', ','); + } + } + function getIndexName(e, t, n) { + let r = e.useZeroIndexingForArrays ? t.toString() : (t + 1).toString(); + if (!e.addArrayIndexPadding) { + r = Str.padNumber(parseInt(r), n.toString().length); + } + return `[${r}]`; + } + function getObjectFromString(objectString) { + const result = { + parsed: true, + object: null, + }; + try { + if (Is.definedString(objectString)) { + result.object = JSON.parse(objectString); + } + } catch (e1) { + try { + result.object = eval(`(${objectString})`); + if (Is.definedFunction(result.object)) { + result.object = result.object(); + } + } catch (e) { + if (!_configuration.safeMode) { + console.error(_configuration.text.objectErrorText.replace('{{error_1}}', e1.message).replace('{{error_2}}', e.message)); + result.parsed = false; + } + result.object = null; + } + } + return result; + } + function destroyElement(e) { + e._currentView.element.innerHTML = ''; + e._currentView.element.className = ''; + Trigger.customEvent(e.events.onDestroy, e._currentView.element); + } + const _public = { + refresh: function (e) { + if (Is.definedString(e) && _elements_Data.hasOwnProperty(e)) { + const t = _elements_Data[e]; + renderControlContainer(t); + Trigger.customEvent(t.events.onRefresh, t._currentView.element); + } + return _public; + }, + refreshAll: function () { + for (let e in _elements_Data) { + if (_elements_Data.hasOwnProperty(e)) { + const t = _elements_Data[e]; + renderControlContainer(t); + Trigger.customEvent(t.events.onRefresh, t._currentView.element); + } + } + return _public; + }, + render: function (e, t) { + if (Is.definedObject(e) && Is.definedObject(t)) { + renderControl(Binding.Options.getForNewInstance(t, e)); + } + return _public; + }, + renderAll: function () { + render(); + return _public; + }, + openAll: function (e) { + if (Is.definedString(e) && _elements_Data.hasOwnProperty(e)) { + openAllNodes(_elements_Data[e]); + } + return _public; + }, + closeAll: function (e) { + if (Is.definedString(e) && _elements_Data.hasOwnProperty(e)) { + closeAllNodes(_elements_Data[e]); + } + return _public; + }, + destroy: function (e) { + if (Is.definedString(e) && _elements_Data.hasOwnProperty(e)) { + destroyElement(_elements_Data[e]); + delete _elements_Data[e]; + } + return _public; + }, + destroyAll: function () { + for (let e in _elements_Data) { + if (_elements_Data.hasOwnProperty(e)) { + destroyElement(_elements_Data[e]); + } + } + _elements_Data = {}; + return _public; + }, + setConfiguration: function (e) { + if (Is.definedObject(e)) { + let t = false; + const n = _configuration; + for (let r in e) { + if (e.hasOwnProperty(r) && _configuration.hasOwnProperty(r) && n[r] !== e[r]) { + n[r] = e[r]; + t = true; + } + } + if (t) { + _configuration = Config.Options.get(n); + } + } + return _public; + }, + getIds: function () { + const e = []; + for (let t in _elements_Data) { + if (_elements_Data.hasOwnProperty(t)) { + e.push(t); + } + } + return e; + }, + getVersion: function () { + return '2.1.0'; + }, + }; + (() => { + _configuration = Config.Options.get(); + document.addEventListener('DOMContentLoaded', function () { + render(); + }); + if (!Is.defined(window.$jsontree)) { + window.$jsontree = _public; + } + })(); +})(); //# sourceMappingURL=jsontree.js.map diff --git a/static/configvis/jsontree.js.css b/static/configvis/jsontree.js.css new file mode 100644 index 00000000..f3d4d3d4 --- /dev/null +++ b/static/configvis/jsontree.js.css @@ -0,0 +1,330 @@ +/* + * JsonTree.js Library v2.1.0 + * + * Copyright 2024 Bunoon + * Released under the MIT License + */ +:root { + --json-tree-js-default-font: system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', 'Noto Sans', 'Liberation Sans', Arial, + sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; + --json-tree-js-text-bold-weight: 400; + --json-tree-js-header-bold-weight: 900; + --json-tree-js-title-bold-weight: var(--json-tree-js-header-bold-weight); + --json-tree-js-text-bold-weight-active: var(--json-tree-js-header-bold-weight); + --json-tree-js-color-black: #3b3a3a; + --json-tree-js-color-white: #f5f5f5; + --json-tree-js-color-snow-white: #f5f5f5; + --json-tree-js-color-boolean: #ff0000; + --json-tree-js-color-decimal: #e3c868; + --json-tree-js-color-number: #666bf9; + --json-tree-js-color-string: #78b13f; + --json-tree-js-color-date: #a656f5; + --json-tree-js-color-array: #f28c28; + --json-tree-js-color-object: #c0c0c0; + --json-tree-js-color-null: #bbbbbb; + --json-tree-js-color-function: var(--json-tree-js-color-null); + --json-tree-js-color-unknown: var(--json-tree-js-color-null); + --json-tree-js-container-background-color: #22272e; + --json-tree-js-container-border-color: #454c56; + --json-tree-js-button-background-color: #2d333b; + --json-tree-js-button-border-color: var(--json-tree-js-container-border-color); + --json-tree-js-button-text-color: var(--json-tree-js-color-white); + --json-tree-js-button-background-color-hover: var(--json-tree-js-container-border-color); + --json-tree-js-button-text-color-hover: var(--json-tree-js-color-snow-white); + --json-tree-js-button-background-color-active: #616b79; + --json-tree-js-button-text-color-active: var(--json-tree-js-color-snow-white); + --json-tree-js-border-radius: 0.5rem; + --json-tree-js-border-style-scrollbar: inset 0 0 6px var(--json-tree-js-color-dark-gray); + --json-tree-js-border-size: 0.5px; + --json-tree-js-spacing: 10px; + --json-tree-js-spacing-font-size: 0.85rem; + --json-tree-js-transition: all 0.3s; +} + +/* + ------------------------------------------------------------------------- + JsonTree.js - Container + ------------------------------------------------------------------------- +*/ +div.json-tree-js { + -moz-user-select: none; + -webkit-user-select: none; + -ms-user-select: none; + -o-user-select: none; + user-select: none; + cursor: default; + box-sizing: border-box; + line-height: normal; + font-family: var(--json-tree-js-default-font) !important; + display: inline-block; + position: relative; + border-radius: var(--json-tree-js-border-radius); + background-color: var(--json-tree-js-container-background-color); + color: var(--json-tree-js-color-white); + border: var(--json-tree-js-border-size) solid var(--json-tree-js-container-border-color); + padding: var(--json-tree-js-spacing); + font-size: var(--json-tree-js-spacing-font-size); + font-weight: var(--json-tree-js-text-bold-weight); + width: auto; + overflow: hidden; + margin: 0 !important; +} +div.json-tree-js button { + font-family: var(--heat-js-default-font); +} +div.json-tree-js div.no-click { + pointer-events: none !important; +} +div.json-tree-js * { + box-sizing: border-box; + line-height: normal; +} +div.json-tree-js *::before, +div.json-tree-js *::after { + box-sizing: border-box; + line-height: normal; +} + +/* + ------------------------------------------------------------------------- + JsonTree.js - Arrows + ------------------------------------------------------------------------- +*/ +div.json-tree-js div.no-arrow { + display: inline-block; + width: 12px; + height: 8px; + margin-right: calc(var(--json-tree-js-spacing)); +} +div.json-tree-js div.down-arrow, +div.json-tree-js div.right-arrow { + display: inline-block; + width: 0; + height: 0; + margin-right: calc(var(--json-tree-js-spacing)); + cursor: pointer; + transition: var(--json-tree-js-transition); + transition-property: opacity; +} +div.json-tree-js div.down-arrow:hover, +div.json-tree-js div.right-arrow:hover { + opacity: 0.7; +} +div.json-tree-js div.down-arrow { + border-left: 6px solid transparent; + border-right: 6px solid transparent; + border-top: 8px solid var(--json-tree-js-color-white); +} +div.json-tree-js div.right-arrow { + border-top: 5px solid transparent; + border-bottom: 5px solid transparent; + border-left: 12px solid var(--json-tree-js-color-white); +} + +/* + ------------------------------------------------------------------------- + JsonTree.js - Title Bar + ------------------------------------------------------------------------- +*/ +div.json-tree-js div.title-bar { + display: flex; + margin-bottom: var(--json-tree-js-spacing); +} +div.json-tree-js div.title-bar div.title { + text-align: left; + width: auto; + font-weight: var(--json-tree-js-title-bold-weight); + font-size: 1.2rem; +} +div.json-tree-js div.title-bar div.controls { + margin-left: calc(var(--json-tree-js-spacing) * 6); + flex-grow: 1; + text-align: right; +} +@media (min-width: 768px) { + div.json-tree-js div.title-bar div.controls { + margin-left: calc(var(--json-tree-js-spacing) * 12); + } +} +div.json-tree-js div.title-bar div.controls button { + background-color: var(--json-tree-js-button-background-color); + border: var(--json-tree-js-border-size) solid var(--json-tree-js-button-border-color); + color: var(--json-tree-js-button-text-color); + border-radius: var(--json-tree-js-border-radius); + padding-top: 5px; + padding-bottom: 5px; + padding-left: 9px; + padding-right: 9px; + outline: none; + transition: var(--json-tree-js-transition); +} +div.json-tree-js div.title-bar div.controls button:disabled { + color: var(--json-tree-js-button-border-color); +} +div.json-tree-js div.title-bar div.controls button:not(.active):not(:disabled):active { + background: var(--json-tree-js-button-background-color-active) !important; + color: var(--json-tree-js-button-text-color-active) !important; +} +div.json-tree-js div.title-bar div.controls button:not(.active):not(:disabled):hover { + cursor: pointer; + background: var(--json-tree-js-button-background-color-hover); + color: var(--json-tree-js-button-text-color-hover); +} +div.json-tree-js div.title-bar div.controls button { + margin-left: calc(var(--json-tree-js-spacing) / 2) !important; +} +div.json-tree-js div.title-bar div.controls button.back { + margin-left: calc(var(--json-tree-js-spacing) * 2) !important; +} +div.json-tree-js div.title-bar div.controls button.copy-all { + display: none; +} +@media (min-width: 768px) { + div.json-tree-js div.title-bar div.controls button.copy-all { + display: inline-block; + } +} + +/* + ------------------------------------------------------------------------- + JsonTree.js - Object Type Title + ------------------------------------------------------------------------- +*/ +div.json-tree-js div.object-type-title { + font-weight: var(--json-tree-js-header-bold-weight); + text-align: left !important; +} +div.json-tree-js div.object-type-title span.array { + color: var(--json-tree-js-color-array); +} +div.json-tree-js div.object-type-title span.object { + color: var(--json-tree-js-color-object); +} +div.json-tree-js div.object-type-title span.count { + margin-left: calc(var(--json-tree-js-spacing) / 2); + font-weight: var(--json-tree-js-text-bold-weight); +} +div.json-tree-js div.object-type-title span.data-array-index { + margin-right: calc(var(--json-tree-js-spacing) / 2); + font-weight: var(--json-tree-js-text-bold-weight); +} + +/* + ------------------------------------------------------------------------- + JsonTree.js - Object Type Contents + ------------------------------------------------------------------------- +*/ +div.json-tree-js div.object-type-contents { + margin-top: calc(var(--json-tree-js-spacing) / 2); +} + +/* + ------------------------------------------------------------------------- + JsonTree.js - Object Type Contents - Values + ------------------------------------------------------------------------- +*/ +div.json-tree-js div.object-type-contents { + margin-left: calc(var(--json-tree-js-spacing) * 2); + text-align: left !important; +} +div.json-tree-js div.object-type-contents div.object-type-value { + white-space: nowrap; + overflow: hidden; + margin-top: calc(var(--json-tree-js-spacing) / 2); + margin-bottom: calc(var(--json-tree-js-spacing) / 2); +} +div.json-tree-js div.object-type-contents div.object-type-value span.split { + margin-left: calc(var(--json-tree-js-spacing) / 2); + margin-right: calc(var(--json-tree-js-spacing) / 2); +} +div.json-tree-js div.object-type-contents div.object-type-value span.boolean, +div.json-tree-js div.object-type-contents div.object-type-value span.decimal, +div.json-tree-js div.object-type-contents div.object-type-value span.number, +div.json-tree-js div.object-type-contents div.object-type-value span.string, +div.json-tree-js div.object-type-contents div.object-type-value span.date, +div.json-tree-js div.object-type-contents div.object-type-value span.null, +div.json-tree-js div.object-type-contents div.object-type-value span.function, +div.json-tree-js div.object-type-contents div.object-type-value span.unknown { + transition: var(--json-tree-js-transition); + transition-property: opacity; +} +div.json-tree-js div.object-type-contents div.object-type-value span.boolean:not(.no-hover):hover, +div.json-tree-js div.object-type-contents div.object-type-value span.decimal:not(.no-hover):hover, +div.json-tree-js div.object-type-contents div.object-type-value span.number:not(.no-hover):hover, +div.json-tree-js div.object-type-contents div.object-type-value span.string:not(.no-hover):hover, +div.json-tree-js div.object-type-contents div.object-type-value span.date:not(.no-hover):hover, +div.json-tree-js div.object-type-contents div.object-type-value span.null:not(.no-hover):hover, +div.json-tree-js div.object-type-contents div.object-type-value span.function:not(.no-hover):hover, +div.json-tree-js div.object-type-contents div.object-type-value span.unknown:not(.no-hover):hover { + cursor: pointer; + opacity: 0.7; +} +div.json-tree-js div.object-type-contents div.object-type-value span.comma { + color: var(--json-tree-js-color-white); + font-weight: var(--json-tree-js-text-bold-weight); +} +div.json-tree-js div.object-type-contents div.object-type-value span.boolean { + color: var(--json-tree-js-color-boolean); +} +div.json-tree-js div.object-type-contents div.object-type-value span.decimal { + color: var(--json-tree-js-color-decimal); +} +div.json-tree-js div.object-type-contents div.object-type-value span.number { + color: var(--json-tree-js-color-number); +} +div.json-tree-js div.object-type-contents div.object-type-value span.string { + color: var(--json-tree-js-color-string); +} +div.json-tree-js div.object-type-contents div.object-type-value span.date { + color: var(--json-tree-js-color-date); +} +div.json-tree-js div.object-type-contents div.object-type-value span.array { + font-weight: var(--json-tree-js-header-bold-weight); + color: var(--json-tree-js-color-array); +} +div.json-tree-js div.object-type-contents div.object-type-value span.object { + font-weight: var(--json-tree-js-header-bold-weight); + color: var(--json-tree-js-color-object); +} +div.json-tree-js div.object-type-contents div.object-type-value span.null { + color: var(--json-tree-js-color-null); + font-style: italic; +} +div.json-tree-js div.object-type-contents div.object-type-value span.function { + color: var(--json-tree-js-color-function); + font-style: italic; +} +div.json-tree-js div.object-type-contents div.object-type-value span.unknown { + color: var(--json-tree-js-color-unknown); + font-style: italic; +} +div.json-tree-js div.object-type-contents div.object-type-value span.count { + margin-left: calc(var(--json-tree-js-spacing) / 2); + font-weight: var(--json-tree-js-text-bold-weight); +} + +/* + ------------------------------------------------------------------------- + JsonTree.js - Custom Scroll Bar + ------------------------------------------------------------------------- +*/ +.custom-scroll-bars::-webkit-scrollbar { + width: 12px; +} +.custom-scroll-bars::-webkit-scrollbar-track { + -webkit-box-shadow: var(--json-tree-js-border-style-scrollbar); + box-shadow: var(--json-tree-js-border-style-scrollbar); +} +.custom-scroll-bars::-webkit-scrollbar-thumb { + -webkit-box-shadow: var(--json-tree-js-border-style-scrollbar); + box-shadow: var(--json-tree-js-border-style-scrollbar); + background: var(--json-tree-js-color-white); +} +.custom-scroll-bars::-webkit-scrollbar-thumb:hover { + background-color: var(--json-tree-js-color-white); +} +.custom-scroll-bars::-webkit-scrollbar-thumb:active { + background-color: var(--json-tree-js-color-lighter-gray); +} + +/*# sourceMappingURL=jsontree.js.css.map */ diff --git a/static/configvis/jsontree.js.css.map b/static/configvis/jsontree.js.css.map new file mode 100644 index 00000000..782f92e0 --- /dev/null +++ b/static/configvis/jsontree.js.css.map @@ -0,0 +1 @@ +{"version":3,"sourceRoot":"","sources":["../src/jsontree.js.scss","../src/sass/_styles.scss"],"names":[],"mappings":"AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAUA;EAEI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;EAcA;EACA;EACA;EACA;EAGA;EACA;EACA;EAGA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EAGA;EACA;EAGA;EACA;EACA;EACA;EACA;EACA;EACA;EAGA;EACA;EACA;EAGA;EACA;EAGA;;;AAIJ;AAAA;AAAA;AAAA;AAAA;AAMA;EC9EI;EACA;EACA;EACA;EACA;EACA;EAUA;EACA;EDiEA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI;;AAGJ;EACI;;AAGJ;ECxFA;EACA;;AD0FI;EC3FJ;EACA;;;ADkGJ;AAAA;AAAA;AAAA;AAAA;AAOI;EACI;EACA;EACA;EACA;;AAGJ;AAAA;EAEI;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;AAAA;EACI;;AAIR;EACI;EACA;EACA;;AAGJ;EACI;EACA;EACA;;;AAKR;AAAA;AAAA;AAAA;AAAA;AAOI;EACI;EACA;;AAEA;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;;AAEA;EALJ;IAMQ;;;ACjKZ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI;;AAGJ;EACI;EACA;;AAGJ;EACI;EACA;EACA;;AD8IA;EACI;;AAGJ;EACI;;AAGJ;EACI;;AAEA;EAHJ;IAIQ;;;;AAQpB;AAAA;AAAA;AAAA;AAAA;AAOI;EACI;EACA;;AAEA;EACI;;AAGJ;EACI;;AAGJ;EACI;EACA;;AAGJ;EACI;EACA;;;AAKZ;AAAA;AAAA;AAAA;AAAA;AAOI;EACI;;;AAKR;AAAA;AAAA;AAAA;AAAA;AAOI;EACI;EACA;;AAEA;EACI;EACA;EACA;EACA;;AAEA;EACI;EACA;;AAGJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;EAQI;EACA;;AAEA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;EACI;EACA;;AAIR;EACI;EACA;;AAGJ;EACI;;AAGJ;EACI;;AAGJ;EACI;;AAGJ;EACI;;AAGJ;EACI;;AAGJ;EACI;EACA;;AAGJ;EACI;EACA;;AAGJ;EACI;EACA;;AAGJ;EACI;EACA;;AAGJ;EACI;EACA;;AAGJ;EACI;EACA;;;AAOhB;AAAA;AAAA;AAAA;AAAA;AAOI;EACI;;AAGJ;EACI;EACA;;AAGJ;EACI;EACA;EACA;;AAEA;EACI;;AAGJ;EACI","file":"jsontree.js.css"} \ No newline at end of file diff --git a/static/configvis/jsontree.js.map b/static/configvis/jsontree.js.map new file mode 100644 index 00000000..21e76574 --- /dev/null +++ b/static/configvis/jsontree.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["/Users/williamtroup/Documents/GitHub%20Repos/JsonTree.js/dist/jsontree.js"],"names":["Is","Is2","defined","value","toString","definedObject","object","definedBoolean","definedString","definedFunction","definedNumber","definedArray","Array","definedDate","Date","definedDecimal","invalidOptionArray","array","minimumLength","length","hexColor","valid","isNaN","substring","Default","Default2","getAnyString","defaultValue","getString","getBoolean","getNumber","getFunction","getArray","getObject","getStringOrArray","result2","values","split","getFixedDecimalPlacesValue","decimalPlaces","_a","regExp","RegExp","match","getFunctionName","valueParts","valueNameParts","DomElement","DomElement2","create","container","type","className","beforeNode","nodeType","toLowerCase","isText","document","createTextNode","createElement","insertBefore","appendChild","createWithHTML","html","element","innerHTML","addClass","classList","add","Str","Str2","newGuid","charIndex","push","character","Math","floor","random","join","padNumber","number","numberString","numberResult","arrayLength","DateTime","DateTime2","getWeekdayNumber","date","getDay","getDayOrdinal","configuration","text","thText","stText","ndText","rdText","getCustomFormattedDateText","dateFormat","weekDayNumber","replace","getHours","getMinutes","getSeconds","dayNames","dayNamesAbbreviated","getDate","monthNames","getMonth","monthNamesAbbreviated","getFullYear","Number","parseInt","Constants","Constants2","JSONTREE_JS_ATTRIBUTE_NAME","Binding","Binding2","Options","Options2","getForNewInstance","data","bindingOptions","get","_currentView","dataArrayCurrentIndex","newOptions","options","showCounts","useZeroIndexingForArrays","dateTimeFormat","showArrowToggles","showStringQuotes","showAllAsClosed","sortPropertyNames","sortPropertyNamesInAlphabeticalOrder","showCommas","reverseArrayValues","addArrayIndexPadding","showValueColors","maximumDecimalPlaces","maximumStringLength","showStringHexColors","showArrayItemsAsSeparateObjects","copyOnlyCurrentPage","getTitle","getIgnore","getCustomTriggers","title","show","showTreeControls","showCopyButton","ignore","nullValues","functionValues","unknownValues","booleanValues","decimalValues","numberValues","stringValues","dateValues","objectValues","arrayValues","events","onBeforeRender","onRenderComplete","onValueClick","onRefresh","onCopyAll","onOpenAll","onCloseAll","onDestroy","onBooleanRender","onDecimalRender","onNumberRender","onStringRender","onDateRender","onFunctionRender","onNullRender","onUnknownRender","Config","Config2","newConfiguration","safeMode","domElementTypes","getText","objectText","arrayText","closeAllButtonText","openAllButtonText","copyAllButtonText","objectErrorText","attributeNotValidErrorText","attributeNotSetErrorText","ellipsisText","closeAllButtonSymbolText","openAllButtonSymbolText","copyAllButtonSymbolText","backButtonText","nextButtonText","backButtonSymbolText","nextButtonSymbolText","Trigger","Trigger2","customEvent","triggerFunction","args","apply","slice","call","_configuration","_elements_Data","render","tagTypes","tagTypesLength","tagTypeIndex","domElements","getElementsByTagName","elements","elementsLength","elementIndex","renderElement","hasAttribute","bindingOptionsData","getAttribute","getObjectFromString","parsed","renderControl","console","error","id","removeAttribute","hasOwnProperty","renderControlContainer","renderControlTitleBar","renderObject","renderArray","titleBar","controls","copy","onclick","copyData","JSON","stringify","navigator","clipboard","writeText","openAll","closeAll","openAllNodes","closeAllNodes","back","disabled","next","showPagingIndex","objectTypeTitle","objectTypeContents","arrow","propertyCount","renderObjectValues","titleText","dataArrayIndex","renderArrayValues","properties","key","sort","reverse","propertiesLength","propertyIndex","propertyName","renderValue","addArrowEvent","dataLength","dataIndex1","getIndexName","dataIndex2","name","isLastItem","objectTypeValue","valueClass","valueElement","ignored","addClickEvent","createComma","newValue","color","newStringValue","style","objectTitle","arrayTitle","arrayTypeContents","removeChild","addValueClickEvent","display","index","largestValue","objectString","result","parse","e1","eval","e2","message","destroyElement","_public","refresh","elementId","refreshAll","renderAll","destroy","destroyAll","setConfiguration","configurationHasChanged","newInternalConfiguration","getIds","getVersion","addEventListener","window","$jsontree"],"mappings":"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","sourcesContent":[null]} \ No newline at end of file diff --git a/static/configvis/prism.css b/static/configvis/prism.css new file mode 100644 index 00000000..5d87cd19 --- /dev/null +++ b/static/configvis/prism.css @@ -0,0 +1,174 @@ +/* PrismJS 1.29.0 +https://prismjs.com/download.html#themes=prism-twilight&languages=markup+yaml&plugins=line-numbers */ +code[class*='language-'], +pre[class*='language-'] { + color: #fff; + background: 0 0; + font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; + font-size: 1em; + text-align: left; + text-shadow: 0 -0.1em 0.2em #000; + white-space: pre; + word-spacing: normal; + word-break: normal; + word-wrap: normal; + line-height: 1.5; + -moz-tab-size: 4; + -o-tab-size: 4; + tab-size: 4; + -webkit-hyphens: none; + -moz-hyphens: none; + -ms-hyphens: none; + hyphens: none; +} +:not(pre) > code[class*='language-'], +pre[class*='language-'] { + background: #141414; +} +pre[class*='language-'] { + border-radius: 0.5em; + border: 0.3em solid #545454; + box-shadow: 1px 1px 0.5em #000 inset; + margin: 0.5em 0; + overflow: auto; + padding: 1em; +} +pre[class*='language-']::-moz-selection { + background: #27292a; +} +pre[class*='language-']::selection { + background: #27292a; +} +code[class*='language-'] ::-moz-selection, +code[class*='language-']::-moz-selection, +pre[class*='language-'] ::-moz-selection, +pre[class*='language-']::-moz-selection { + text-shadow: none; + background: hsla(0, 0%, 93%, 0.15); +} +code[class*='language-'] ::selection, +code[class*='language-']::selection, +pre[class*='language-'] ::selection, +pre[class*='language-']::selection { + text-shadow: none; + background: hsla(0, 0%, 93%, 0.15); +} +:not(pre) > code[class*='language-'] { + border-radius: 0.3em; + border: 0.13em solid #545454; + box-shadow: 1px 1px 0.3em -0.1em #000 inset; + padding: 0.15em 0.2em 0.05em; + white-space: normal; +} +.token.cdata, +.token.comment, +.token.doctype, +.token.prolog { + color: #777; +} +.token.punctuation { + opacity: 0.7; +} +.token.namespace { + opacity: 0.7; +} +.token.boolean, +.token.deleted, +.token.number, +.token.tag { + color: #ce6849; +} +.token.builtin, +.token.constant, +.token.keyword, +.token.property, +.token.selector, +.token.symbol { + color: #f9ed99; +} +.language-css .token.string, +.style .token.string, +.token.attr-name, +.token.attr-value, +.token.char, +.token.entity, +.token.inserted, +.token.operator, +.token.string, +.token.url, +.token.variable { + color: #909e6a; +} +.token.atrule { + color: #7385a5; +} +.token.important, +.token.regex { + color: #e8c062; +} +.token.bold, +.token.important { + font-weight: 700; +} +.token.italic { + font-style: italic; +} +.token.entity { + cursor: help; +} +.language-markup .token.attr-name, +.language-markup .token.punctuation, +.language-markup .token.tag { + color: #ac885c; +} +.token { + position: relative; + z-index: 1; +} +.line-highlight.line-highlight { + background: hsla(0, 0%, 33%, 0.25); + background: linear-gradient(to right, hsla(0, 0%, 33%, 0.1) 70%, hsla(0, 0%, 33%, 0)); + border-bottom: 1px dashed #545454; + border-top: 1px dashed #545454; + margin-top: 0.75em; + z-index: 0; +} +.line-highlight.line-highlight:before, +.line-highlight.line-highlight[data-end]:after { + background-color: #8693a6; + color: #f4f1ef; +} +pre[class*='language-'].line-numbers { + position: relative; + padding-left: 3.8em; + counter-reset: linenumber; +} +pre[class*='language-'].line-numbers > code { + position: relative; + white-space: inherit; +} +.line-numbers .line-numbers-rows { + position: absolute; + pointer-events: none; + top: 0; + font-size: 100%; + left: -3.8em; + width: 3em; + letter-spacing: -1px; + border-right: 1px solid #999; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} +.line-numbers-rows > span { + display: block; + counter-increment: linenumber; +} +.line-numbers-rows > span:before { + content: counter(linenumber); + color: #999; + display: block; + padding-right: 0.8em; + text-align: right; +} diff --git a/static/configvis/prism.js b/static/configvis/prism.js new file mode 100644 index 00000000..4b96b4a7 --- /dev/null +++ b/static/configvis/prism.js @@ -0,0 +1,625 @@ +/* PrismJS 1.29.0 +https://prismjs.com/download.html#themes=prism-twilight&languages=markup+yaml&plugins=line-numbers */ +var _self = + 'undefined' != typeof window ? window : 'undefined' != typeof WorkerGlobalScope && self instanceof WorkerGlobalScope ? self : {}, + Prism = (function (e) { + var n = /(?:^|\s)lang(?:uage)?-([\w-]+)(?=\s|$)/i, + t = 0, + r = {}, + a = { + manual: e.Prism && e.Prism.manual, + disableWorkerMessageHandler: e.Prism && e.Prism.disableWorkerMessageHandler, + util: { + encode: function e(n) { + return n instanceof i + ? new i(n.type, e(n.content), n.alias) + : Array.isArray(n) + ? n.map(e) + : n + .replace(/&/g, '&') + .replace(/= g.reach); + A += w.value.length, w = w.next + ) { + var E = w.value; + if (n.length > e.length) return; + if (!(E instanceof i)) { + var P, + L = 1; + if (y) { + if (!(P = l(b, A, e, m)) || P.index >= e.length) break; + var S = P.index, + O = P.index + P[0].length, + j = A; + for (j += w.value.length; S >= j; ) j += (w = w.next).value.length; + if (((A = j -= w.value.length), w.value instanceof i)) continue; + for (var C = w; C !== n.tail && (j < O || 'string' == typeof C.value); C = C.next) + L++, (j += C.value.length); + L--, (E = e.slice(A, j)), (P.index -= A); + } else if (!(P = l(b, 0, E, m))) continue; + S = P.index; + var N = P[0], + _ = E.slice(0, S), + M = E.slice(S + N.length), + W = A + E.length; + g && W > g.reach && (g.reach = W); + var z = w.prev; + if ( + (_ && ((z = u(n, z, _)), (A += _.length)), + c(n, z, L), + (w = u(n, z, new i(f, p ? a.tokenize(N, p) : N, k, N))), + M && u(n, w, M), + L > 1) + ) { + var I = { cause: f + ',' + d, reach: W }; + o(e, n, t, w.prev, A, I), g && I.reach > g.reach && (g.reach = I.reach); + } + } + } + } + } + } + function s() { + var e = { value: null, prev: null, next: null }, + n = { value: null, prev: e, next: null }; + (e.next = n), (this.head = e), (this.tail = n), (this.length = 0); + } + function u(e, n, t) { + var r = n.next, + a = { value: t, prev: n, next: r }; + return (n.next = a), (r.prev = a), e.length++, a; + } + function c(e, n, t) { + for (var r = n.next, a = 0; a < t && r !== e.tail; a++) r = r.next; + (n.next = r), (r.prev = n), (e.length -= a); + } + if ( + ((e.Prism = a), + (i.stringify = function e(n, t) { + if ('string' == typeof n) return n; + if (Array.isArray(n)) { + var r = ''; + return ( + n.forEach(function (n) { + r += e(n, t); + }), + r + ); + } + var i = { type: n.type, content: e(n.content, t), tag: 'span', classes: ['token', n.type], attributes: {}, language: t }, + l = n.alias; + l && (Array.isArray(l) ? Array.prototype.push.apply(i.classes, l) : i.classes.push(l)), a.hooks.run('wrap', i); + var o = ''; + for (var s in i.attributes) o += ' ' + s + '="' + (i.attributes[s] || '').replace(/"/g, '"') + '"'; + return '<' + i.tag + ' class="' + i.classes.join(' ') + '"' + o + '>' + i.content + ''; + }), + !e.document) + ) + return e.addEventListener + ? (a.disableWorkerMessageHandler || + e.addEventListener( + 'message', + function (n) { + var t = JSON.parse(n.data), + r = t.language, + i = t.code, + l = t.immediateClose; + e.postMessage(a.highlight(i, a.languages[r], r)), l && e.close(); + }, + !1, + ), + a) + : a; + var g = a.util.currentScript(); + function f() { + a.manual || a.highlightAll(); + } + if ((g && ((a.filename = g.src), g.hasAttribute('data-manual') && (a.manual = !0)), !a.manual)) { + var h = document.readyState; + 'loading' === h || ('interactive' === h && g && g.defer) + ? document.addEventListener('DOMContentLoaded', f) + : window.requestAnimationFrame + ? window.requestAnimationFrame(f) + : window.setTimeout(f, 16); + } + return a; + })(_self); +'undefined' != typeof module && module.exports && (module.exports = Prism), 'undefined' != typeof global && (global.Prism = Prism); +(Prism.languages.markup = { + comment: { pattern: //, greedy: !0 }, + prolog: { pattern: /<\?[\s\S]+?\?>/, greedy: !0 }, + doctype: { + pattern: /"'[\]]|"[^"]*"|'[^']*')+(?:\[(?:[^<"'\]]|"[^"]*"|'[^']*'|<(?!!--)|)*\]\s*)?>/i, + greedy: !0, + inside: { + 'internal-subset': { pattern: /(^[^\[]*\[)[\s\S]+(?=\]>$)/, lookbehind: !0, greedy: !0, inside: null }, + string: { pattern: /"[^"]*"|'[^']*'/, greedy: !0 }, + punctuation: /^$|[[\]]/, + 'doctype-tag': /^DOCTYPE/i, + name: /[^\s<>'"]+/, + }, + }, + cdata: { pattern: //i, greedy: !0 }, + tag: { + pattern: /<\/?(?!\d)[^\s>\/=$<%]+(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?\s*\/?>/, + greedy: !0, + inside: { + tag: { pattern: /^<\/?[^\s>\/]+/, inside: { punctuation: /^<\/?/, namespace: /^[^\s>\/:]+:/ } }, + 'special-attr': [], + 'attr-value': { + pattern: /=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/, + inside: { + punctuation: [ + { pattern: /^=/, alias: 'attr-equals' }, + { pattern: /^(\s*)["']|["']$/, lookbehind: !0 }, + ], + }, + }, + punctuation: /\/?>/, + 'attr-name': { pattern: /[^\s>\/]+/, inside: { namespace: /^[^\s>\/:]+:/ } }, + }, + }, + entity: [{ pattern: /&[\da-z]{1,8};/i, alias: 'named-entity' }, /&#x?[\da-f]{1,8};/i], +}), + (Prism.languages.markup.tag.inside['attr-value'].inside.entity = Prism.languages.markup.entity), + (Prism.languages.markup.doctype.inside['internal-subset'].inside = Prism.languages.markup), + Prism.hooks.add('wrap', function (a) { + 'entity' === a.type && (a.attributes.title = a.content.replace(/&/, '&')); + }), + Object.defineProperty(Prism.languages.markup.tag, 'addInlined', { + value: function (a, e) { + var s = {}; + (s['language-' + e] = { pattern: /(^$)/i, lookbehind: !0, inside: Prism.languages[e] }), + (s.cdata = /^$/i); + var t = { 'included-cdata': { pattern: //i, inside: s } }; + t['language-' + e] = { pattern: /[\s\S]+/, inside: Prism.languages[e] }; + var n = {}; + (n[a] = { + pattern: RegExp( + '(<__[^>]*>)(?:))*\\]\\]>|(?!)'.replace( + /__/g, + function () { + return a; + }, + ), + 'i', + ), + lookbehind: !0, + greedy: !0, + inside: t, + }), + Prism.languages.insertBefore('markup', 'cdata', n); + }, + }), + Object.defineProperty(Prism.languages.markup.tag, 'addAttribute', { + value: function (a, e) { + Prism.languages.markup.tag.inside['special-attr'].push({ + pattern: RegExp('(^|["\'\\s])(?:' + a + ')\\s*=\\s*(?:"[^"]*"|\'[^\']*\'|[^\\s\'">=]+(?=[\\s>]))', 'i'), + lookbehind: !0, + inside: { + 'attr-name': /^[^\s=]+/, + 'attr-value': { + pattern: /=[\s\S]+/, + inside: { + value: { + pattern: /(^=\s*(["']|(?!["'])))\S[\s\S]*(?=\2$)/, + lookbehind: !0, + alias: [e, 'language-' + e], + inside: Prism.languages[e], + }, + punctuation: [{ pattern: /^=/, alias: 'attr-equals' }, /"|'/], + }, + }, + }, + }); + }, + }), + (Prism.languages.html = Prism.languages.markup), + (Prism.languages.mathml = Prism.languages.markup), + (Prism.languages.svg = Prism.languages.markup), + (Prism.languages.xml = Prism.languages.extend('markup', {})), + (Prism.languages.ssml = Prism.languages.xml), + (Prism.languages.atom = Prism.languages.xml), + (Prism.languages.rss = Prism.languages.xml); +!(function (e) { + var n = /[*&][^\s[\]{},]+/, + r = /!(?:<[\w\-%#;/?:@&=+$,.!~*'()[\]]+>|(?:[a-zA-Z\d-]*!)?[\w\-%#;/?:@&=+$.~*'()]+)?/, + t = '(?:' + r.source + '(?:[ \t]+' + n.source + ')?|' + n.source + '(?:[ \t]+' + r.source + ')?)', + a = + '(?:[^\\s\\x00-\\x08\\x0e-\\x1f!"#%&\'*,\\-:>?@[\\]`{|}\\x7f-\\x84\\x86-\\x9f\\ud800-\\udfff\\ufffe\\uffff]|[?:-])(?:[ \t]*(?:(?![#:])|:))*'.replace( + //g, + function () { + return '[^\\s\\x00-\\x08\\x0e-\\x1f,[\\]{}\\x7f-\\x84\\x86-\\x9f\\ud800-\\udfff\\ufffe\\uffff]'; + }, + ), + d = '"(?:[^"\\\\\r\n]|\\\\.)*"|\'(?:[^\'\\\\\r\n]|\\\\.)*\''; + function o(e, n) { + n = (n || '').replace(/m/g, '') + 'm'; + var r = '([:\\-,[{]\\s*(?:\\s<>[ \t]+)?)(?:<>)(?=[ \t]*(?:$|,|\\]|\\}|(?:[\r\n]\\s*)?#))' + .replace(/<>/g, function () { + return t; + }) + .replace(/<>/g, function () { + return e; + }); + return RegExp(r, n); + } + (e.languages.yaml = { + scalar: { + pattern: RegExp( + '([\\-:]\\s*(?:\\s<>[ \t]+)?[|>])[ \t]*(?:((?:\r?\n|\r)[ \t]+)\\S[^\r\n]*(?:\\2[^\r\n]+)*)'.replace( + /<>/g, + function () { + return t; + }, + ), + ), + lookbehind: !0, + alias: 'string', + }, + comment: /#.*/, + key: { + pattern: RegExp( + '((?:^|[:\\-,[{\r\n?])[ \t]*(?:<>[ \t]+)?)<>(?=\\s*:\\s)' + .replace(/<>/g, function () { + return t; + }) + .replace(/<>/g, function () { + return '(?:' + a + '|' + d + ')'; + }), + ), + lookbehind: !0, + greedy: !0, + alias: 'atrule', + }, + directive: { pattern: /(^[ \t]*)%.+/m, lookbehind: !0, alias: 'important' }, + datetime: { + pattern: o( + '\\d{4}-\\d\\d?-\\d\\d?(?:[tT]|[ \t]+)\\d\\d?:\\d{2}:\\d{2}(?:\\.\\d*)?(?:[ \t]*(?:Z|[-+]\\d\\d?(?::\\d{2})?))?|\\d{4}-\\d{2}-\\d{2}|\\d\\d?:\\d{2}(?::\\d{2}(?:\\.\\d*)?)?', + ), + lookbehind: !0, + alias: 'number', + }, + boolean: { pattern: o('false|true', 'i'), lookbehind: !0, alias: 'important' }, + null: { pattern: o('null|~', 'i'), lookbehind: !0, alias: 'important' }, + string: { pattern: o(d), lookbehind: !0, greedy: !0 }, + number: { + pattern: o('[+-]?(?:0x[\\da-f]+|0o[0-7]+|(?:\\d+(?:\\.\\d*)?|\\.\\d+)(?:e[+-]?\\d+)?|\\.inf|\\.nan)', 'i'), + lookbehind: !0, + }, + tag: r, + important: n, + punctuation: /---|[:[\]{}\-,|>?]|\.\.\./, + }), + (e.languages.yml = e.languages.yaml); +})(Prism); +!(function () { + if ('undefined' != typeof Prism && 'undefined' != typeof document) { + var e = 'line-numbers', + n = /\n(?!$)/g, + t = (Prism.plugins.lineNumbers = { + getLine: function (n, t) { + if ('PRE' === n.tagName && n.classList.contains(e)) { + var i = n.querySelector('.line-numbers-rows'); + if (i) { + var r = parseInt(n.getAttribute('data-start'), 10) || 1, + s = r + (i.children.length - 1); + t < r && (t = r), t > s && (t = s); + var l = t - r; + return i.children[l]; + } + } + }, + resize: function (e) { + r([e]); + }, + assumeViewportIndependence: !0, + }), + i = void 0; + window.addEventListener('resize', function () { + (t.assumeViewportIndependence && i === window.innerWidth) || + ((i = window.innerWidth), r(Array.prototype.slice.call(document.querySelectorAll('pre.line-numbers')))); + }), + Prism.hooks.add('complete', function (t) { + if (t.code) { + var i = t.element, + s = i.parentNode; + if (s && /pre/i.test(s.nodeName) && !i.querySelector('.line-numbers-rows') && Prism.util.isActive(i, e)) { + i.classList.remove(e), s.classList.add(e); + var l, + o = t.code.match(n), + a = o ? o.length + 1 : 1, + u = new Array(a + 1).join(''); + (l = document.createElement('span')).setAttribute('aria-hidden', 'true'), + (l.className = 'line-numbers-rows'), + (l.innerHTML = u), + s.hasAttribute('data-start') && + (s.style.counterReset = 'linenumber ' + (parseInt(s.getAttribute('data-start'), 10) - 1)), + t.element.appendChild(l), + r([s]), + Prism.hooks.run('line-numbers', t); + } + } + }), + Prism.hooks.add('line-numbers', function (e) { + (e.plugins = e.plugins || {}), (e.plugins.lineNumbers = !0); + }); + } + function r(e) { + if ( + 0 != + (e = e.filter(function (e) { + var n, + t = ((n = e), n ? (window.getComputedStyle ? getComputedStyle(n) : n.currentStyle || null) : null)['white-space']; + return 'pre-wrap' === t || 'pre-line' === t; + })).length + ) { + var t = e + .map(function (e) { + var t = e.querySelector('code'), + i = e.querySelector('.line-numbers-rows'); + if (t && i) { + var r = e.querySelector('.line-numbers-sizer'), + s = t.textContent.split(n); + r || (((r = document.createElement('span')).className = 'line-numbers-sizer'), t.appendChild(r)), + (r.innerHTML = '0'), + (r.style.display = 'block'); + var l = r.getBoundingClientRect().height; + return (r.innerHTML = ''), { element: e, lines: s, lineHeights: [], oneLinerHeight: l, sizer: r }; + } + }) + .filter(Boolean); + t.forEach(function (e) { + var n = e.sizer, + t = e.lines, + i = e.lineHeights, + r = e.oneLinerHeight; + (i[t.length - 1] = void 0), + t.forEach(function (e, t) { + if (e && e.length > 1) { + var s = n.appendChild(document.createElement('span')); + (s.style.display = 'block'), (s.textContent = e); + } else i[t] = r; + }); + }), + t.forEach(function (e) { + for (var n = e.sizer, t = e.lineHeights, i = 0, r = 0; r < t.length; r++) + void 0 === t[r] && (t[r] = n.children[i++].getBoundingClientRect().height); + }), + t.forEach(function (e) { + var n = e.sizer, + t = e.element.querySelector('.line-numbers-rows'); + (n.style.display = 'none'), + (n.innerHTML = ''), + e.lineHeights.forEach(function (e, n) { + t.children[n].style.height = e + 'px'; + }); + }); + } + } +})();