diff --git a/changelog.md b/changelog.md
index a61037f8..f899f958 100644
--- a/changelog.md
+++ b/changelog.md
@@ -19,6 +19,8 @@
9. Slack task fail/abort messages can now - optionally - use same templating concept that was previously available for emails. Better looking Slack alert messages thus! ([#120](https://github.com/ptarmiganlabs/butler/issues/120)).
10. Added rate limiting option for task fail/aborts sent to Slack and MS Teams. ([#119](https://github.com/ptarmiganlabs/butler/issues/119)).
11. Add MQTT message containing all available info about failed/aborted tasks. Format is stringified JSON. ([#128](https://github.com/ptarmiganlabs/butler/issues/128)).
+12. Add exclude list to user activity events sent to Slack and MS Teams. Using this feature it's possible to prevent session start/stop etc notifications for specific users to be sent to Teams and Slack. Useful for example if a system account is used to do API calls - that probably should not be reported as user activity. ([#132](https://github.com/ptarmiganlabs/butler/issues/132)).
+13. Improvements around handling of inbound MQTT messages. The root MQTT topic that Butler subscribes to is now configurable ([#137](https://github.com/ptarmiganlabs/butler/issues/137)). The topic through which Sense tasks can be started is now also configurable ([#136](https://github.com/ptarmiganlabs/butler/issues/136)) rather than hard coded.
### Fixes and patches
@@ -37,7 +39,7 @@
3. **BREAKING**: Similar to 1 above, all MS Teams configuration has been moved from `Butler.teamsConfig.*` into the larger/more generic `Butler.teamsNotification.*` section of the config file.
-4. UDP message format for user session start/stop log appender has changed to follow same principles as other log appender UDP messages sent to Butler. ([#126](https://github.com/ptarmiganlabs/butler/issues/126)).
+4. UDP message format for user session start/stop log appender has changed to follow same principles as other log appender UDP messages sent to Butler. ([#126](https://github.com/ptarmiganlabs/butler/issues/126), [#134](https://github.com/ptarmiganlabs/butler/issues/134)).
## 4.3.0
diff --git a/docs/log4net_task-failed/scheduler/LocalLogConfig.xml b/docs/log4net_task-failed/scheduler/LocalLogConfig.xml
index 03d47585..53b8d9c4 100644
--- a/docs/log4net_task-failed/scheduler/LocalLogConfig.xml
+++ b/docs/log4net_task-failed/scheduler/LocalLogConfig.xml
@@ -1,6 +1,6 @@
-
+
@@ -13,7 +13,7 @@
-
+
@@ -31,7 +31,7 @@
-
+
@@ -44,7 +44,7 @@
-
+
@@ -55,11 +55,12 @@
-
+
+
-
+
@@ -74,8 +75,8 @@
-
-
+
+
diff --git a/docs/log4net_user-audit-event/LocalLogConfig.xml b/docs/log4net_user-audit-event/LocalLogConfig.xml
index e76bc1c9..303d38da 100644
--- a/docs/log4net_user-audit-event/LocalLogConfig.xml
+++ b/docs/log4net_user-audit-event/LocalLogConfig.xml
@@ -1,24 +1,48 @@
-
+
-
+
+
-
+
-
+
+
-
-
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/sense_apps/Butler 4.3 demo app.qvf b/docs/sense_apps/Butler 4.3 demo app.qvf
deleted file mode 100644
index ce684b53..00000000
Binary files a/docs/sense_apps/Butler 4.3 demo app.qvf and /dev/null differ
diff --git a/docs/sense_apps/Butler 5.0 - demo app (many APIs!).qvf b/docs/sense_apps/Butler 5.0 - demo app (many APIs!).qvf
new file mode 100644
index 00000000..28b98d02
Binary files /dev/null and b/docs/sense_apps/Butler 5.0 - demo app (many APIs!).qvf differ
diff --git a/docs/sense_apps/Butler 5.0 - partial reload demo (step 1).qvf b/docs/sense_apps/Butler 5.0 - partial reload demo (step 1).qvf
new file mode 100644
index 00000000..1734b700
Binary files /dev/null and b/docs/sense_apps/Butler 5.0 - partial reload demo (step 1).qvf differ
diff --git a/docs/sense_apps/Butler 5.0 - partial reload demo (step 2).qvf b/docs/sense_apps/Butler 5.0 - partial reload demo (step 2).qvf
new file mode 100644
index 00000000..4a00e436
Binary files /dev/null and b/docs/sense_apps/Butler 5.0 - partial reload demo (step 2).qvf differ
diff --git a/docs/sense_apps/Butler 5.0 - post message to Slack.qvf b/docs/sense_apps/Butler 5.0 - post message to Slack.qvf
new file mode 100644
index 00000000..cf99946b
Binary files /dev/null and b/docs/sense_apps/Butler 5.0 - post message to Slack.qvf differ
diff --git a/docs/sense_apps/Butler 5.0 - publish messages to MQTT.qvf b/docs/sense_apps/Butler 5.0 - publish messages to MQTT.qvf
new file mode 100644
index 00000000..b12604bc
Binary files /dev/null and b/docs/sense_apps/Butler 5.0 - publish messages to MQTT.qvf differ
diff --git a/docs/sense_script/butler_subs.qvs b/docs/sense_script/butler_subs.qvs
index 9cd65f92..b98ba78d 100644
--- a/docs/sense_script/butler_subs.qvs
+++ b/docs/sense_script/butler_subs.qvs
@@ -1,11 +1,16 @@
// ------------------------
// INSTRUCTIONS
//
-// All of thee subs below that deal with Butler's API assume two variables exist:
+// All of thee subs below deal with Butler's API. It isassumed that two variables exist:
// vButlerHost : The host, fully-qualified-domain-name or IP of the server where Butler is running
// vButlerPort : The port on which Butler's REST API is exposed
//
-// Both the settings above are set in Butler's main config file.
+// If unsure what the proper values for those variables are, check Butler's YAML config file,
+// or the log messages printed when Butler starts. Look for a log line like this one:
+//
+// MAIN: REST server listening on http://192.168.1.168:8080
+//
+// In this case vButlerHost should be set to 'http://192.168.1.168' and vButlerPort to '8080'
// ------------------------
@@ -54,6 +59,116 @@ end sub
+// ------------------------------------------------------------
+// ** List enabled API endpoints **
+//
+// Parameters:
+// vResultTable : Name of table to which names of enabled API
+// endpoints will be added
+// ------------------------------------------------------------
+sub GetEnabledButlerAPIEndpoints(vResultTable)
+ LIB CONNECT TO 'Butler_GET';
+
+ RestConnectorMasterTable:
+ SQL SELECT
+ "@Value"
+ FROM JSON (wrap on) "root" ArrayValueAlias "@Value"
+ WITH CONNECTION (
+ Url "$(vButlerHost):$(vButlerPort)/v4/configfile/endpointsenabled",
+ );
+
+ [$(vResultTable)]:
+ NoConcatenate LOAD
+ [@Value] as "Endpoint name"
+ RESIDENT RestConnectorMasterTable;
+
+ set vResultTable=;
+ DROP TABLE RestConnectorMasterTable;
+end sub
+
+
+
+// ------------------------------------------------------------
+// ** Reload QSEoW apps **
+//
+// NOTE: The reload is done directly in the engine, i.e. no reload
+// tasks are used for the reload.
+// This means that the reload cannot be triggered using QMC tasks.
+//
+// The options for starting the reload are using the sub below or
+// via direct calls to Butler's API.
+//
+// Parameters:
+// vAppId: ID of QSEoW app that should be reloaded.
+// vPartialReload: true/false
+// vReloadMode: 0, 1, 2.
+// vStartQSEoWTaskOnSuccess: Array of Sense task IDs. Will be started if the reload completes successfully.
+// Format is '["taskid1","taskid2","taskid3"]'
+//
+// vStartQSEoWTaskOnFailure: Array of Sense task IDs. Will be started if the reload fails.
+// Format is '["taskid1","taskid2","taskid3"]'
+//
+// ------------------------------------------------------------
+sub ReloadSenseApp(vAppId, vPartialReload, vReloadMode, vStartQSEoWTaskOnSuccess, vStartQSEoWTaskOnFailure)
+ // Make task IDs double double-quoted
+ let vStartQSEoWTaskOnSuccess = Replace('$(vStartQSEoWTaskOnSuccess)', '"', '""');
+ let vStartQSEoWTaskOnFailure = Replace('$(vStartQSEoWTaskOnFailure)', '"', '""');
+
+ // Build request body
+ let vRequestBody = '{""partialReload"":""$(vPartialReload)"", ""reloadMode"":$(vReloadMode), ""startQSEoWTaskOnSuccess"":$(vStartQSEoWTaskOnSuccess), ""startQSEoWTaskOnFailure"":$(vStartQSEoWTaskOnFailure)}';
+
+ LIB CONNECT TO 'Butler_POST';
+
+ RestConnectorMasterTable:
+ SQL SELECT
+ "appId"
+ FROM JSON (wrap on) "root"
+ WITH CONNECTION (
+ Url "$(vButlerHost):$(vButlerPort)/v4/app/$(vAppId)/reload",
+ BODY "$(vRequestBody)",
+ HTTPHEADER "X-HTTP-Method-Override" "PUT"
+ );
+
+ set vAppId=;
+ set vPartialReload=;
+ set vReloadMode=;
+ set vStartQSEoWTaskOnSuccess=;
+ set vStartQSEoWTaskOnFailure=;
+ set vRequestBody=;
+ DROP TABLE RestConnectorMasterTable;
+end sub
+
+
+
+// ------------------------------------------------------------
+// ** Start QSEoW task **
+//
+// Use Butler to start the task identified by the ID in parameter vTaskId.
+//
+// Paramaters:
+// vTaskId : ID of task to be started. Can be found in QMC task view.
+// ------------------------------------------------------------
+sub StartTask(vTaskId)
+ LIB CONNECT TO 'Butler_POST';
+
+ RestConnectorMasterTable:
+ SQL SELECT
+ "taskId"
+ FROM JSON (wrap on) "root"
+ WITH CONNECTION (
+ Url "$(vButlerHost):$(vButlerPort)/v4/reloadtask/$(vTaskId)/start",
+ HTTPHEADER "X-HTTP-Method-Override" "PUT"
+ );
+
+ set vTaskId=;
+ DROP TABLE RestConnectorMasterTable;
+end sub
+
+
+
+
+
+
// ------------------------------------------------------------
// ** Copy files/directories **
//
@@ -85,7 +200,7 @@ sub CopyFile(vSource, vDest, vOverwrite, vPreserveTimestamp)
"preserveTimestamp"
FROM JSON (wrap on) "root"
WITH CONNECTION (
- Url "http://$(vButlerHost):$(vButlerPort)/v4/filecopy",
+ Url "$(vButlerHost):$(vButlerPort)/v4/filecopy",
BODY "$(vRequestBody)",
HTTPHEADER "X-HTTP-Method-Override" "PUT"
);
@@ -94,7 +209,7 @@ sub CopyFile(vSource, vDest, vOverwrite, vPreserveTimestamp)
set vDest=;
set vOverwrite=;
set vPreserveTimestamp=;
-
+ set vRequestBody=;
DROP TABLE RestConnectorMasterTable;
end sub
@@ -130,7 +245,7 @@ sub MoveFile(vSource, vDest, vOverwrite)
"overwrite"
FROM JSON (wrap on) "root"
WITH CONNECTION (
- Url "http://$(vButlerHost):$(vButlerPort)/v4/filemove",
+ Url "$(vButlerHost):$(vButlerPort)/v4/filemove",
BODY "$(vRequestBody)",
HTTPHEADER "X-HTTP-Method-Override" "PUT"
);
@@ -138,7 +253,7 @@ sub MoveFile(vSource, vDest, vOverwrite)
set vSource=;
set vDest=;
set vOverwrite=;
-
+ set vRequestBody=;
DROP TABLE RestConnectorMasterTable;
end sub
@@ -165,13 +280,13 @@ sub DeleteFile(vFile)
"vFile"
FROM JSON (wrap on) "root"
WITH CONNECTION (
- Url "http://$(vButlerHost):$(vButlerPort)/v4/filedelete",
+ Url "$(vButlerHost):$(vButlerPort)/v4/filedelete",
BODY "$(vRequestBody)",
HTTPHEADER "X-HTTP-Method-Override" "DELETE"
);
set vFile=;
-
+ set vRequestBody=;
DROP TABLE RestConnectorMasterTable;
end sub
@@ -200,11 +315,12 @@ sub CreateDir(vDir)
"directory"
FROM JSON (wrap on) "root"
WITH CONNECTION (
- Url "http://$(vButlerHost):$(vButlerPort)/v4/createdir",
+ Url "$(vButlerHost):$(vButlerPort)/v4/createdir",
BODY "$(vRequestBody)",
HTTPHEADER "Content-Type" "application/json"
);
+ set vDir=;
set vRequestBody=;
DROP TABLE RestConnectorMasterTable;
end sub
@@ -231,44 +347,18 @@ sub CreateDirQVD(vDir)
"directory"
FROM JSON (wrap on) "root"
WITH CONNECTION (
- Url "http://$(vButlerHost):$(vButlerPort)/v4/createdirqvd",
+ Url "$(vButlerHost):$(vButlerPort)/v4/createdirqvd",
BODY "$(vRequestBody)",
HTTPHEADER "Content-Type" "application/json"
);
+ set vDir=;
set vRequestBody=;
DROP TABLE RestConnectorMasterTable;
end sub
-// ------------------------------------------------------------
-// ** List enabled API endpoints **
-//
-// Parameters:
-// vResultTable : Name of table to which names of enabled API
-// endpoints will be added
-// ------------------------------------------------------------
-sub GetEnabledButlerAPIEndpoints(vResultTable)
- LIB CONNECT TO 'Butler_GET';
-
- RestConnectorMasterTable:
- SQL SELECT
- "@Value"
- FROM JSON (wrap on) "root" ArrayValueAlias "@Value"
- WITH CONNECTION (
- Url "http://$(vButlerHost):$(vButlerPort)/v4/configfile/endpointsenabled",
- );
-
- [$(vResultTable)]:
- NoConcatenate LOAD
- [@Value] as "Endpoint name"
- RESIDENT RestConnectorMasterTable;
-
- set vResultTable=;
- DROP TABLE RestConnectorMasterTable;
-end sub
-
// ------------------------------------------------------------
@@ -301,7 +391,7 @@ sub AddKeyValue(vNamespace, vKey, vValue, vTimeToLive)
"ttl"
FROM JSON (wrap on) "root"
WITH CONNECTION (
- Url "http://$(vButlerHost):$(vButlerPort)/v4/keyvalues/$(vNamespace)",
+ Url "$(vButlerHost):$(vButlerPort)/v4/keyvalues/$(vNamespace)",
BODY "$(vRequestBody)",
HTTPHEADER "Content-Type" "application/json"
);
@@ -310,6 +400,7 @@ sub AddKeyValue(vNamespace, vKey, vValue, vTimeToLive)
set vKey=;
set vValue=;
set vTimeToLive=;
+ set vRequestBody=;
DROP TABLE RestConnectorMasterTable;
end sub
@@ -332,7 +423,7 @@ sub GetKeyValue(vNamespace, vKey, vResultVarName)
"value"
FROM JSON (wrap on) "root"
WITH CONNECTION (
- Url "http://$(vButlerHost):$(vButlerPort)/v4/keyvalues/$(vNamespace)?key=$(vKey)"
+ Url "$(vButlerHost):$(vButlerPort)/v4/keyvalues/$(vNamespace)?key=$(vKey)"
);
let $(vResultVarName) = Peek('value', 0, 'RestConnectorMasterTable');
@@ -340,6 +431,7 @@ sub GetKeyValue(vNamespace, vKey, vResultVarName)
set vResultVarName=;
set vNamespace=;
set vKey=;
+ set vRequestBody=;
DROP TABLE RestConnectorMasterTable;
end sub
@@ -359,7 +451,7 @@ sub DeleteKeyValue(vNamespace, vKey)
SQL SELECT
FROM JSON (wrap on) "root"
WITH CONNECTION (
- Url "http://$(vButlerHost):$(vButlerPort)/v4/keyvalues/$(vNamespace)/$(vKey)",
+ Url "$(vButlerHost):$(vButlerPort)/v4/keyvalues/$(vNamespace)/$(vKey)",
HTTPHEADER "X-HTTP-Method-Override" "DELETE"
);
@@ -370,6 +462,14 @@ end sub
+// ------------------------------------------------------------
+// ** Checks if a key exists in a particular namespace **
+//
+// Paramaters:
+// vNamespace : Namespace in which to look for key
+// vKey : Key name
+// vResultVarName : Name of variable in wich value will be placed
+// ------------------------------------------------------------
sub ExistsKeyValue(vNamespace, vKey, vResultVarName)
LIB CONNECT TO 'Butler_GET';
@@ -386,7 +486,7 @@ sub ExistsKeyValue(vNamespace, vKey, vResultVarName)
FROM "keyValue" FK "__FK_keyValue")
FROM JSON (wrap on) "root" PK "__KEY_root"
WITH CONNECTION (
- Url "http://$(vButlerHost):$(vButlerPort)/v4/keyvalues/$(vNamespace)/keyexists?key=$(vKey)",
+ Url "$(vButlerHost):$(vButlerPort)/v4/keyvalues/$(vNamespace)/keyexists?key=$(vKey)",
);
[keyValue]:
@@ -416,7 +516,12 @@ end sub
-
+// ------------------------------------------------------------
+// ** Delete a namespace and all key-value pairs in it. **
+//
+// Paramaters:
+// vNamespace : Namespace to be deleted
+// ------------------------------------------------------------
sub DeleteKeyValueNamespace(vNamespace)
LIB CONNECT TO 'Butler_POST';
@@ -424,7 +529,7 @@ sub DeleteKeyValueNamespace(vNamespace)
SQL SELECT
FROM JSON (wrap on) "root"
WITH CONNECTION (
- Url "http://$(vButlerHost):$(vButlerPort)/v4/keyvalues/$(vNamespace)",
+ Url "$(vButlerHost):$(vButlerPort)/v4/keyvalues/$(vNamespace)",
HTTPHEADER "X-HTTP-Method-Override" "DELETE"
);
@@ -433,6 +538,13 @@ sub DeleteKeyValueNamespace(vNamespace)
end sub
+
+// ------------------------------------------------------------
+// ** Get a list of all currently defined namespaces. **
+//
+// Paramaters:
+// vResultTableName : Name of table in which namespaces will be returned
+// ------------------------------------------------------------
sub GetKeyValueNamespaces(vResultTableName)
LIB CONNECT TO 'Butler_GET';
@@ -441,7 +553,7 @@ sub GetKeyValueNamespaces(vResultTableName)
"@Value"
FROM JSON (wrap on) "root" ArrayValueAlias "@Value"
WITH CONNECTION (
- Url "http://$(vButlerHost):$(vButlerPort)/v4/keyvaluesnamespaces",
+ Url "$(vButlerHost):$(vButlerPort)/v4/keyvaluesnamespaces",
);
[$(vResultTableName)]:
@@ -456,30 +568,88 @@ end sub
+
// ------------------------------------------------------------
-// ** Start Sense task **
+// ** Publish message to MQTT topic **
//
-// Use Butler to start the task identified by the ID in parameter vTaskId.
+// Publishes a message to a MQTT topic.
+// The address of the MQTT server is configured in the Butler config file.
//
// Paramaters:
-// vTaskId :
+// vTopic : MQTT topic
+// vMessage : MQTT message
// ------------------------------------------------------------
-sub StartTask(vTaskId)
- LIB CONNECT TO 'Butler_POST';
+sub PostToMQTT(vTopic, vMessage)
+ // URL encode the parameters passed to Butler, to ensure spaces, international characters etc are handled correctly.
+ // I.e. call ButlerInit before calling this Sub...
+ let vRequestBody = '{""topic"":""$(vTopic)"", ""message"":""$(vMessage)""}';
+
+ LIB CONNECT TO 'Butler_POST';
RestConnectorMasterTable:
SQL SELECT
- "taskId"
+ "directory"
FROM JSON (wrap on) "root"
WITH CONNECTION (
- Url "http://$(vButlerHost):$(vButlerPort)/v4/reloadtask/$(vTaskId)/start",
+ Url "$(vButlerHost):$(vButlerPort)/v4/mqttpublishmessage",
+ BODY "$(vRequestBody)",
+ HTTPHEADER "Content-Type" "application/json",
HTTPHEADER "X-HTTP-Method-Override" "PUT"
);
- set vTaskId=;
+ set vTopic=;
+ set vMessage=;
+ set vRequestBody=;
+ DROP TABLE RestConnectorMasterTable;
+end sub
- DROP TABLE RestConnectorMasterTable;
+
+
+// ------------------------------------------------------------
+// ** Publish message to Slack **
+//
+// Publishes a basic message to Slack.
+// The Slack API access URL is configured in the Butler config file.
+//
+// No advanced layout can be used, available formatting options for messages: https://api.slack.com/docs/formatting
+// Available emojis: http://www.emoji-cheat-sheet.com/
+// Slack API docs: https://api.slack.com/incoming-webhooks
+//
+// Paramaters:
+// vSlackChannel : Channel to which message will be posted
+// vFromUser : Name of user that will appear as sender of message
+// vMessage : Message to send
+// vEmoji : Emoji to show next to message in Slack
+// ------------------------------------------------------------
+sub PostToSlack(vToSlackChannel, vFromUser, vMessage, vEmoji)
+ // URL encode the parameters passed to Butler, to ensure spaces, international characters etc are handled correctly.
+ // If neither # nor @ is specified as first character of the Slack channel name, # will be added.
+ if ( (left('$(vToSlackChannel)', 1) <> '@') and (left('$(vToSlackChannel)', 1) <> '#') ) then
+ let vToSlackChannel = '#$(vToSlackChannel)';
+ endif
+
+ // Build request body
+ let vRequestBody = '{""channel"":""$(vToSlackChannel)"", ""from_user"":""$(vFromUser)"", ""msg"":""$(vMessage)"", ""emoji"":""$(vEmoji)""}';
+
+ LIB CONNECT TO 'Butler_POST';
+
+ RestConnectorMasterTable:
+ SQL SELECT
+ "directory"
+ FROM JSON (wrap on) "root"
+ WITH CONNECTION (
+ Url "$(vButlerHost):$(vButlerPort)/v4/slackpostmessage",
+ BODY "$(vRequestBody)",
+ HTTPHEADER "Content-Type" "application/json",
+ HTTPHEADER "X-HTTP-Method-Override" "PUT"
+ );
+
+ set vToSlackChannel=;
+ set vFromUser=;
+ set vMessage=;
+ set vEmoji=;
+ set vRequestBody=;
+ drop table RestConnectorMasterTable;
end sub
-
diff --git a/docs/sense_script/post_to_mqtt.qvs b/docs/sense_script/post_to_mqtt.qvs
deleted file mode 100644
index 16901091..00000000
--- a/docs/sense_script/post_to_mqtt.qvs
+++ /dev/null
@@ -1,21 +0,0 @@
-// ------------------------------------------------------------
-// ** Publish message to MQTT topic **
-//
-// Publishes a message to a MQTT topic.
-// The address of the MQTT server is configured in Butler.
-//
-// ------------------------------------------------------------
-
-Sub PostToMQTT(vTopic, vMessage)
- // URL encode the parameters passed to Butler, to ensure spaces, international characters etc are handled correctly.
- let vTopic = MapSubstring('URL_EncodingReferenceMap', '$(vTopic)');
- let vMessage = MapSubstring('URL_EncodingReferenceMap', '$(vMessage)');
-
- tmpMQTT:
- LOAD
- *
- FROM [http://localhost:8080/mqttPublishMessage?topic=$(vTopic)&message=$(vMessage)]
- (txt, codepage is 1252, embedded labels, delimiter is '\t', msq);
-
- drop table tmpMQTT;
-end sub
diff --git a/docs/sense_script/post_to_slack.qvs b/docs/sense_script/post_to_slack.qvs
deleted file mode 100644
index 97b89c9b..00000000
--- a/docs/sense_script/post_to_slack.qvs
+++ /dev/null
@@ -1,39 +0,0 @@
-// ------------------------------------------------------------
-// ** Publish message to a Slack channel **
-//
-// Publishes a message to Slack.
-// The Slack API access URL is hard coded in Butler
-//
-// Formatting options for messages: https://api.slack.com/docs/formatting
-// Available emojis: http://www.emoji-cheat-sheet.com/
-// Slack API docs: https://api.slack.com/incoming-webhooks
-//
-// ------------------------------------------------------------
-
-Sub PostToSlack(vToSlackChannel, vFromUser, vMessage, vEmoji)
- // URL encode the parameters passed to Butler, to ensure spaces, international characters etc are handled correctly.
- // If neither # nor @ is specified as first character of the Slack channel name, # will be added.
-
- if ( (left('$(vToSlackChannel)', 1) <> '@') and (left('$(vToSlackChannel)', 1) <> '#') ) then
- let vToSlackChannel = '#$(vToSlackChannel)';
- endif
-
- let vToSlackChannel2 = '$(vToSlackChannel)';
- let vToSlackChannel = MapSubstring('URL_EncodingReferenceMap', '$(vToSlackChannel)');
- let vFromUser2 = '$(vFromUser)';
- let vFromUser = MapSubstring('URL_EncodingReferenceMap', '$(vFromUser)');
- let vMessage2 = '$(vMessage)';
- let vMessage = MapSubstring('URL_EncodingReferenceMap', '$(vMessage)');
-
- tmpSlack:
- LOAD
- *
- FROM [http://localhost:8080/v2/slackPostMessage?channel=$(vToSlackChannel)&from_user=$(vFromUser)&msg=$(vMessage)&emoji=$(vEmoji)]
- (txt, codepage is 1252, embedded labels, delimiter is '\t', msq);
-
- drop table tmpSlack;
-
- let vToSlackChannel = '$(vToSlackChannel2)';
- let vFromUser = '$(vFromUser2)';
- let vMessage = '$(vMessage2)';
-end sub
diff --git a/src/config/production_template.yaml b/src/config/production_template.yaml
index 3bc6c44b..5b84700b 100644
--- a/src/config/production_template.yaml
+++ b/src/config/production_template.yaml
@@ -51,43 +51,53 @@ Butler:
# Settings for notifications and messages sent to MS Teams
teamsNotification:
- enable: true
+ enable: false
userSessionEvents:
- enable: true
+ enable: false
webhookURL:
+ excludeUser:
+ - directory:
+ userId:
+ - directory:
+ userId:
reloadTaskFailure:
- enable: true
+ enable: false
webhookURL:
messageType: formatted # formatted / basic. Formatted means that template file below will be used to create the message.
basicMsgTemplate: 'Qlik Sense reload failed: "{{taskName}}"' # Only needed if message type = basic
- rateLimit: 15 # Min seconds between emails for a given taskID. Defaults to 5 minutes.
+ rateLimit: 300 # Min seconds between emails for a given taskID. Defaults to 5 minutes.
headScriptLogLines: 10
tailScriptLogLines: 10
templateFile: /path/to/teams/template/directory/failed-reload.handlebars
reloadTaskAborted:
- enable: true
+ enable: false
webhookURL:
messageType: formatted # formatted / basic. Formatted means that template file below will be used to create the message.
basicMsgTemplate: 'Qlik Sense reload aborted: "{{taskName}}"' # Only needed if message type = basic
- rateLimit: 15 # Min seconds between emails for a given taskID. Defaults to 5 minutes.
+ rateLimit: 300 # Min seconds between emails for a given taskID. Defaults to 5 minutes.
headScriptLogLines: 10
tailScriptLogLines: 10
templateFile: /path/to/teams/template/directory/aborted-reload.handlebars
# Settings for notifications and messages sent to Slack
slackNotification:
- enable: true
+ enable: false
restMessage:
- enable: true
+ enable: false
webhookURL:
userSessionEvents:
- enable: true
+ enable: false
webhookURL:
channel: sense-user-activity # Slack channel to which user activity data is sent
fromUser: Qlik Sense
iconEmoji: ':thumbsup:'
+ excludeUser:
+ - directory:
+ userId:
+ - directory:
+ userId:
reloadTaskFailure:
- enable: true
+ enable: false
webhookURL:
channel: sense-task-failure # Slack channel to which task failure notifications are sent
messageType: formatted # formatted / basic. Formatted means that template file below will be used to create the message.
@@ -99,7 +109,7 @@ Butler:
fromUser: Qlik Sense
iconEmoji: ':ghost:'
reloadTaskAborted:
- enable: true
+ enable: false
webhookURL:
channel: sense-task-aborted # Slack channel to which task stopped notifications are sent
messageType: formatted # formatted / basic. Formatted means that template file below will be used to create the message.
@@ -134,14 +144,14 @@ Butler:
- directory:
userId:
rateLimit: 600 # Min seconds between emails for a given taskID. Defaults to 5 minutes.
- headScriptLogLines: 15 # # of lines from start of script to include in email
- tailScriptLogLines: 15 # # of lines from end of script to include in email
+ headScriptLogLines: 15 # Number of lines from start of script to include in email
+ tailScriptLogLines: 15 # Number of lines from end of script to include in email
priority: high # high/normal/low
subject: 'Qlik Sense reload aborted: "{{taskName}}"' # Email subject. Can use template fields
bodyFileDirectory: config/email_templates # Directory where email body template files are stored
htmlTemplateFile: aborted-reload # Name of email body template file to use
fromAdress: Qlik Sense (no-reply)
- toAdress: # Array of email addresses to which the notification email will be sent
+ recipients: # Array of email addresses to which the notification email will be sent
-
-
reloadTaskFailure:
@@ -163,14 +173,14 @@ Butler:
- directory:
userId:
rateLimit: 600 # Min seconds between emails for a given taskID. Defaults to 5 minutes.
- headScriptLogLines: 15 # # of lines from start of script to include in email
- tailScriptLogLines: 15 # # of lines from end of script to include in email
+ headScriptLogLines: 15 # Number of lines from start of script to include in email
+ tailScriptLogLines: 15 # Number of lines from end of script to include in email
priority: high # high/normal/low
subject: 'Qlik Sense reload failed: "{{taskName}}"' # Email subject. Can use template fields
bodyFileDirectory: config/email_templates # Directory where email body template files are stored
htmlTemplateFile: failed-reload # Name of email body template file to use
fromAdress: Qlik Sense (no-reply)
- toAdress: # Array of email addresses to which the notification email will be sent
+ recipients: # Array of email addresses to which the notification email will be sent
-
-
smtp: # Email server settings. See https://nodemailer.com/smtp/ for details on the meaning of these fields.
@@ -189,29 +199,29 @@ Butler:
# Settings for notifications and messages sent using outgoing webhooks
webhookNotification:
- enable: true
+ enable: false
reloadTaskFailure:
rateLimit: 300 # Min seconds between outgoing webhook calls for a given taskID. Defaults to 5 minutes.
webhooks:
- - description: 'This outgoing webhook is used to...'
+ - description: 'This outgoing webhook is used to...' # Informational only
webhookURL: http://host.my.domain:port/some/path # outgoing webhook that Butler will call
httpMethod: POST # GET/POST/PUT
- - description: 'This outgoing webhook is used to...'
+ - description: 'This outgoing webhook is used to...' # Informational only
webhookURL: http://host.my.domain:port/some/path # outgoing webhook that Butler will call
httpMethod: PUT # GET/POST/PUT.
- - description: 'This outgoing webhook is used to...'
+ - description: 'This outgoing webhook is used to...' # Informational only
webhookURL: http://host.my.domain:port/some/path # outgoing webhook that Butler will call
httpMethod: GET # GET/POST/PUT
reloadTaskAborted:
rateLimit: 300 # Min seconds between outgoing webhook calls for a given taskID. Defaults to 5 minutes.
webhooks:
- - description: 'This outgoing webhook is used to...'
+ - description: 'This outgoing webhook is used to...' # Informational only
webhookURL: http://host.my.domain:port/some/path # outgoing webhook that Butler will call
httpMethod: PUT # GET/POST/PUT
- - description: 'This outgoing webhook is used to...'
+ - description: 'This outgoing webhook is used to...' # Informational only
webhookURL: http://host.my.domain:port/some/path # outgoing webhook that Butler will call
httpMethod: POST # GET/POST/PUT
- - description: 'This outgoing webhook is used to...'
+ - description: 'This outgoing webhook is used to...' # Informational only
webhookURL: http://host.my.domain:port/some/path # outgoing webhook that Butler will call
httpMethod: GET # GET/POST/PUT
@@ -231,6 +241,8 @@ Butler:
brokerPort: 1883
taskFailureSendFull: true
taskAbortedSendFull: true
+ subscriptionRootTopic: qliksense/# # Topic that Butler will subscribe to
+ taskStartTopic: qliksense/start_task # Topic for incoming messages used to start Sense tasks. Should be subtopic to subscriptionRootTopic
taskFailureTopic: qliksense/task_failure
taskFailureFullTopic: qliksense/task_failure_full
taskFailureServerStatusTopic: qliksense/butler/task_failure_server
diff --git a/src/globals.js b/src/globals.js
index 571e4eae..3c6b3c23 100644
--- a/src/globals.js
+++ b/src/globals.js
@@ -59,8 +59,6 @@ const getLoggingLevel = () => {
}).level;
};
-// Load our own libs
-// var qrsUtil = require('./qrsUtil');
// Helper function to read the contents of the certificate files:
const readCert = filename => fs.readFileSync(filename);
@@ -330,7 +328,6 @@ function initInfluxDB() {
module.exports = {
config,
- // qrsUtil,
configEngine,
configQRS,
teamsTaskFailureObj,
diff --git a/src/lib/logRESTCall.js b/src/lib/logRESTCall.js
index 841b4544..29b2447b 100644
--- a/src/lib/logRESTCall.js
+++ b/src/lib/logRESTCall.js
@@ -9,6 +9,7 @@ var globals = require('../globals');
// Function for logging info about REST call
module.exports.logRESTCall = function (req) {
globals.logger.info(`${req.url} called from ${req.client.remoteAddress}`);
- globals.logger.verbose(`Query: ${JSON.stringify(req.query, null, 2)}`);
- globals.logger.verbose(`Headers: ${JSON.stringify(req.headers, null, 2)}`);
+ globals.logger.debug(`Query: ${JSON.stringify(req.query, null, 2)}`);
+ globals.logger.debug(`Body: ${JSON.stringify(req.body, null, 2)}`);
+ globals.logger.debug(`Headers: ${JSON.stringify(req.headers, null, 2)}`);
};
diff --git a/src/lib/smtp.js b/src/lib/smtp.js
index 7410bea0..731c5af4 100644
--- a/src/lib/smtp.js
+++ b/src/lib/smtp.js
@@ -79,8 +79,8 @@ function isEmailReloadFailedNotificationConfigOk() {
!globals.config.has('Butler.emailNotification.reloadTaskFailure.bodyFileDirectory') ||
!globals.config.has('Butler.emailNotification.reloadTaskFailure.htmlTemplateFile') ||
!globals.config.has('Butler.emailNotification.reloadTaskFailure.fromAdress') ||
- !globals.config.has('Butler.emailNotification.reloadTaskFailure.toAdress') ||
- globals.config.get('Butler.emailNotification.reloadTaskFailure.toAdress').length == 0
+ !globals.config.has('Butler.emailNotification.reloadTaskFailure.recipients') ||
+ globals.config.get('Butler.emailNotification.reloadTaskFailure.recipients').length == 0
) {
// Not enough info in config file
globals.logger.error('SMTP: Reload failure email config info missing in Butler config file');
@@ -114,8 +114,8 @@ function isEmailReloadAbortedNotificationConfigOk() {
!globals.config.has('Butler.emailNotification.reloadTaskAborted.bodyFileDirectory') ||
!globals.config.has('Butler.emailNotification.reloadTaskAborted.htmlTemplateFile') ||
!globals.config.has('Butler.emailNotification.reloadTaskAborted.fromAdress') ||
- !globals.config.has('Butler.emailNotification.reloadTaskAborted.toAdress') ||
- globals.config.get('Butler.emailNotification.reloadTaskAborted.toAdress').length == 0
+ !globals.config.has('Butler.emailNotification.reloadTaskAborted.recipients') ||
+ globals.config.get('Butler.emailNotification.reloadTaskAborted.recipients').length == 0
) {
// Not enough info in config file
globals.logger.error('SMTP: Reload aborted email config info missing in Butler config file');
@@ -321,7 +321,7 @@ function sendReloadTaskFailureNotificationEmail(reloadParams) {
sendEmail(
globals.config.get('Butler.emailNotification.reloadTaskFailure.fromAdress'),
- recipientEmails.concat(globals.config.get('Butler.emailNotification.reloadTaskFailure.toAdress')),
+ recipientEmails.concat(globals.config.get('Butler.emailNotification.reloadTaskFailure.recipients')),
globals.config.get('Butler.emailNotification.reloadTaskFailure.priority'),
globals.config.get('Butler.emailNotification.reloadTaskFailure.subject'),
globals.config.get('Butler.emailNotification.reloadTaskFailure.bodyFileDirectory'),
@@ -424,10 +424,10 @@ function sendReloadTaskAbortedNotificationEmail(reloadParams) {
}
}
- // Note: Butler.emailNotification.reloadTaskAborted.toAdress is an array, sendEmail() should send individual emails to everyone in that array
+ // Note: Butler.emailNotification.reloadTaskAborted.recipients is an array, sendEmail() should send individual emails to everyone in that array
sendEmail(
globals.config.get('Butler.emailNotification.reloadTaskAborted.fromAdress'),
- recipientEmails.concat(globals.config.get('Butler.emailNotification.reloadTaskAborted.toAdress')),
+ recipientEmails.concat(globals.config.get('Butler.emailNotification.reloadTaskAborted.recipients')),
globals.config.get('Butler.emailNotification.reloadTaskAborted.priority'),
globals.config.get('Butler.emailNotification.reloadTaskAborted.subject'),
globals.config.get('Butler.emailNotification.reloadTaskAborted.bodyFileDirectory'),
diff --git a/src/mqtt/mqtt_handlers.js b/src/mqtt/mqtt_handlers.js
index 76f5272b..7307e6b0 100644
--- a/src/mqtt/mqtt_handlers.js
+++ b/src/mqtt/mqtt_handlers.js
@@ -7,6 +7,8 @@ var dict = require('dict');
// Load global variables and functions
var globals = require('../globals');
+var qrsUtil = require('../qrs_util');
+
module.exports.mqttInitHandlers = function () {
// Handler for MQTT connect messages. Called when connection to MQTT broker has been established
@@ -26,22 +28,20 @@ module.exports.mqttInitHandlers = function () {
)} with client ID ${globals.mqttClient.options.clientId}`,
);
- // Have Butler listen to all messages in the qliksense/ subtree
- globals.mqttClient.subscribe('qliksense/#');
+ // Have Butler listen to all messages in the topic subtree specified in the config file
+ globals.mqttClient.subscribe(globals.config.get('Butler.mqttConfig.subscriptionRootTopic'));
});
// Handler for MQTT messages matching the previously set up subscription
globals.mqttClient.on('message', function (topic, message) {
try {
- globals.logger.info('MQTT message received');
- globals.logger.info(topic.toString());
- globals.logger.info(message.toString());
+ globals.logger.verbose(`MQTT message received. Topic=${topic.toString()}, Message=${message.toString()}`);
// **MQTT message dispatch**
// Start Sense task
- if (topic == 'qliksense/start_task') {
- globals.logger.verbose(`MQTT: Starting task ID ${message.toString()}.`);
- globals.qrsUtil.senseStartTask.senseStartTask(message.toString());
+ if (topic == globals.config.get('Butler.mqttConfig.taskStartTopic')) {
+ globals.logger.info(`MQTT: Starting task ID ${message.toString()}.`);
+ qrsUtil.senseStartTask.senseStartTask(message.toString());
}
var array1, array2, serverName, userName;
@@ -62,8 +62,7 @@ module.exports.mqttInitHandlers = function () {
// directoryName = array2[0];
userName = array2[1];
- globals.logger.info('MQTT: Adding active user');
- // console.info('Adding active user');
+ globals.logger.verbose(`MQTT: Adding active user: ${userName} on server ${serverName}`);
globals.currentUsers.set(userName, serverName); // Add user as active
// Build JSON of all active users
@@ -86,7 +85,7 @@ module.exports.mqttInitHandlers = function () {
// Send active user count messages to MQTT, one for each proxy node
globals.currentUsersPerServer.forEach(function (value, key) {
- globals.logger.info('MQTT: server:' + key + ', users:' + JSON.stringify(value));
+ globals.logger.verbose('MQTT: server:' + key + ', users:' + JSON.stringify(value));
// console.info('server:' + key + ', users:' + JSON.stringify(value));
// console.log('=========');
// console.log('server:' + key + ', # of users=' + globals.currentUsersPerServer.size);
@@ -120,7 +119,7 @@ module.exports.mqttInitHandlers = function () {
// directoryName = array2[0];
userName = array2[1];
- globals.logger.info('MQTT: Removing active user');
+ globals.logger.verbose(`MQTT: Removing active user: ${userName}`);
globals.currentUsers.delete(userName); // Remove user from list of active users
// Build JSON of all active users
diff --git a/src/package-lock.json b/src/package-lock.json
index 884ff858..958d5396 100644
--- a/src/package-lock.json
+++ b/src/package-lock.json
@@ -16,7 +16,7 @@
"dict": "^1.4.0",
"email-validator": "^2.0.4",
"enigma.js": "^2.7.2",
- "express-handlebars": "^5.2.0",
+ "express-handlebars": "^5.2.1",
"fs-extra": "^9.1.0",
"handlebars": "^4.7.7",
"influx": "^5.7.0",
@@ -25,7 +25,7 @@
"keyv": "^4.0.3",
"keyv-file": "^0.2.0",
"later": "^1.2.0",
- "lodash": "^4.17.20",
+ "lodash": "^4.17.21",
"luxon": "^1.26.0",
"mkdirp": "^1.0.4",
"moment": "^2.29.1",
@@ -48,7 +48,7 @@
"ws": "^7.4.3"
},
"devDependencies": {
- "eslint": "7.19.0",
+ "eslint": "7.20.0",
"eslint-config-google": "0.14.0"
},
"engines": {
@@ -56,12 +56,12 @@
}
},
"node_modules/@babel/code-frame": {
- "version": "7.12.13",
- "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz",
- "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==",
+ "version": "7.12.11",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz",
+ "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==",
"dev": true,
"dependencies": {
- "@babel/highlight": "^7.12.13"
+ "@babel/highlight": "^7.10.4"
}
},
"node_modules/@babel/helper-validator-identifier": {
@@ -90,6 +90,9 @@
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
+ },
+ "engines": {
+ "node": ">=4"
}
},
"node_modules/@dabh/diagnostics": {
@@ -209,6 +212,9 @@
"dev": true,
"dependencies": {
"color-convert": "^1.9.0"
+ },
+ "engines": {
+ "node": ">=4"
}
},
"node_modules/any-base": {
@@ -881,15 +887,18 @@
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
- "dev": true
+ "dev": true,
+ "engines": {
+ "node": ">=0.8.0"
+ }
},
"node_modules/eslint": {
- "version": "7.19.0",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.19.0.tgz",
- "integrity": "sha512-CGlMgJY56JZ9ZSYhJuhow61lMPPjUzWmChFya71Z/jilVos7mR/jPgaEfVGgMBY5DshbKdG8Ezb8FDCHcoMEMg==",
+ "version": "7.20.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.20.0.tgz",
+ "integrity": "sha512-qGi0CTcOGP2OtCQBgWZlQjcTuP0XkIpYFj25XtRTQSHC+umNnp7UMshr2G8SLsRFYDdAPFeHOsiteadmMH02Yw==",
"dev": true,
"dependencies": {
- "@babel/code-frame": "^7.0.0",
+ "@babel/code-frame": "7.12.11",
"@eslint/eslintrc": "^0.3.0",
"ajv": "^6.10.0",
"chalk": "^4.0.0",
@@ -901,7 +910,7 @@
"eslint-utils": "^2.1.0",
"eslint-visitor-keys": "^2.0.0",
"espree": "^7.3.1",
- "esquery": "^1.2.0",
+ "esquery": "^1.4.0",
"esutils": "^2.0.2",
"file-entry-cache": "^6.0.0",
"functional-red-black-tree": "^1.0.1",
@@ -926,6 +935,15 @@
"table": "^6.0.4",
"text-table": "^0.2.0",
"v8-compile-cache": "^2.0.3"
+ },
+ "bin": {
+ "eslint": "bin/eslint.js"
+ },
+ "engines": {
+ "node": "^10.12.0 || >=12.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
}
},
"node_modules/eslint-config-google": {
@@ -1121,13 +1139,13 @@
}
},
"node_modules/express-handlebars": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/express-handlebars/-/express-handlebars-5.2.0.tgz",
- "integrity": "sha512-kkty9fsldSuqDI/5ohU+EWNOWaPhJOPmVUZewMH+7522atj8QF8WbXzjZKBRloafxRcsPDG68jmfW7MGCYT/1g==",
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/express-handlebars/-/express-handlebars-5.2.1.tgz",
+ "integrity": "sha512-PTp5YZDcuJPqE01Qv5VAAS1DevNJLn6vwglerCwPKQC7wfLH6zr2uh1gwgTlWCqIjqJLLzMgOEE9YA5o0nBCmA==",
"dependencies": {
"glob": "^7.1.6",
- "graceful-fs": "^4.2.4",
- "handlebars": "^4.7.6"
+ "graceful-fs": "^4.2.6",
+ "handlebars": "^4.7.7"
},
"engines": {
"node": ">=10"
@@ -1375,9 +1393,9 @@
}
},
"node_modules/graceful-fs": {
- "version": "4.2.4",
- "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz",
- "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw=="
+ "version": "4.2.6",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz",
+ "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ=="
},
"node_modules/handle-thing": {
"version": "2.0.0",
@@ -1408,7 +1426,10 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
- "dev": true
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
},
"node_modules/help-me": {
"version": "1.1.0",
@@ -1852,9 +1873,9 @@
}
},
"node_modules/lodash": {
- "version": "4.17.20",
- "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
- "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA=="
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
"node_modules/lodash.get": {
"version": "4.4.2",
@@ -2914,6 +2935,9 @@
"dev": true,
"dependencies": {
"has-flag": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=4"
}
},
"node_modules/swagger-jsdoc": {
@@ -3380,12 +3404,12 @@
},
"dependencies": {
"@babel/code-frame": {
- "version": "7.12.13",
- "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz",
- "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==",
+ "version": "7.12.11",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz",
+ "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==",
"dev": true,
"requires": {
- "@babel/highlight": "^7.12.13"
+ "@babel/highlight": "^7.10.4"
}
},
"@babel/helper-validator-identifier": {
@@ -4165,12 +4189,12 @@
"dev": true
},
"eslint": {
- "version": "7.19.0",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.19.0.tgz",
- "integrity": "sha512-CGlMgJY56JZ9ZSYhJuhow61lMPPjUzWmChFya71Z/jilVos7mR/jPgaEfVGgMBY5DshbKdG8Ezb8FDCHcoMEMg==",
+ "version": "7.20.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.20.0.tgz",
+ "integrity": "sha512-qGi0CTcOGP2OtCQBgWZlQjcTuP0XkIpYFj25XtRTQSHC+umNnp7UMshr2G8SLsRFYDdAPFeHOsiteadmMH02Yw==",
"dev": true,
"requires": {
- "@babel/code-frame": "^7.0.0",
+ "@babel/code-frame": "7.12.11",
"@eslint/eslintrc": "^0.3.0",
"ajv": "^6.10.0",
"chalk": "^4.0.0",
@@ -4182,7 +4206,7 @@
"eslint-utils": "^2.1.0",
"eslint-visitor-keys": "^2.0.0",
"espree": "^7.3.1",
- "esquery": "^1.2.0",
+ "esquery": "^1.4.0",
"esutils": "^2.0.2",
"file-entry-cache": "^6.0.0",
"functional-red-black-tree": "^1.0.1",
@@ -4396,13 +4420,13 @@
"integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw="
},
"express-handlebars": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/express-handlebars/-/express-handlebars-5.2.0.tgz",
- "integrity": "sha512-kkty9fsldSuqDI/5ohU+EWNOWaPhJOPmVUZewMH+7522atj8QF8WbXzjZKBRloafxRcsPDG68jmfW7MGCYT/1g==",
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/express-handlebars/-/express-handlebars-5.2.1.tgz",
+ "integrity": "sha512-PTp5YZDcuJPqE01Qv5VAAS1DevNJLn6vwglerCwPKQC7wfLH6zr2uh1gwgTlWCqIjqJLLzMgOEE9YA5o0nBCmA==",
"requires": {
"glob": "^7.1.6",
- "graceful-fs": "^4.2.4",
- "handlebars": "^4.7.6"
+ "graceful-fs": "^4.2.6",
+ "handlebars": "^4.7.7"
}
},
"extend": {
@@ -4630,9 +4654,9 @@
}
},
"graceful-fs": {
- "version": "4.2.4",
- "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz",
- "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw=="
+ "version": "4.2.6",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz",
+ "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ=="
},
"handle-thing": {
"version": "2.0.0",
@@ -5054,9 +5078,9 @@
}
},
"lodash": {
- "version": "4.17.20",
- "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
- "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA=="
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
"lodash.get": {
"version": "4.4.2",
diff --git a/src/package.json b/src/package.json
index 496354c6..65b47ce6 100644
--- a/src/package.json
+++ b/src/package.json
@@ -10,7 +10,7 @@
"dict": "^1.4.0",
"email-validator": "^2.0.4",
"enigma.js": "^2.7.2",
- "express-handlebars": "^5.2.0",
+ "express-handlebars": "^5.2.1",
"fs-extra": "^9.1.0",
"handlebars": "^4.7.7",
"influx": "^5.7.0",
@@ -19,7 +19,7 @@
"keyv": "^4.0.3",
"keyv-file": "^0.2.0",
"later": "^1.2.0",
- "lodash": "^4.17.20",
+ "lodash": "^4.17.21",
"luxon": "^1.26.0",
"mkdirp": "^1.0.4",
"moment": "^2.29.1",
@@ -42,7 +42,7 @@
"ws": "^7.4.3"
},
"devDependencies": {
- "eslint": "7.19.0",
+ "eslint": "7.20.0",
"eslint-config-google": "0.14.0"
},
"homepage": "https://github.com/ptarmiganlabs/butler",
diff --git a/src/udp/udp_handlers.js b/src/udp/udp_handlers.js
index b5e089a9..b0cf293f 100644
--- a/src/udp/udp_handlers.js
+++ b/src/udp/udp_handlers.js
@@ -488,10 +488,22 @@ module.exports.udpInitSessionConnectionServer = function () {
// Main handler for UDP messages relating to session and connection events
globals.udpServerSessionConnectionSocket.on('message', async function (message, remote) {
try {
- var msg = message.toString().split(';');
- globals.logger.info(`SESSIONS: ${msg[0]}: ${msg[1]} for user ${msg[2]}/${msg[3]}`);
-
- // Send notification to MS Teams, if enabled
+ // Message parts
+ // 0: Message type. Possible values are /proxy-connection/, /proxy-session/
+ // 1: Host
+ // 2: Command
+ // 3: User directory
+ // 4: user ID
+ // 5: Origin
+ // 6: Context
+ // 7: Message. Can contain single quotes and semicolon - handle with care
+
+ var msgTmp1 = message.toString().split(';'),
+ msg = msgTmp1.slice(0, 7);
+
+ globals.logger.info(`SESSIONS: ${msg[1]}: ${msg[2]} for user ${msg[3]}/${msg[4]}`);
+
+ // Send notification to Slack, if enabled
if (
globals.config.has('Butler.slackNotification.enable') &&
globals.config.has('Butler.slackNotification.userSessionEvents.enable') &&
@@ -505,7 +517,7 @@ module.exports.udpInitSessionConnectionServer = function () {
type: 'section',
text: {
type: 'plain_text',
- text: msg[1] + ', user: ' + msg[2] + '/' + msg[3] + ' on server ' + msg[0],
+ text: msg[2] + ', user: ' + msg[3] + '/' + msg[4] + ' on server ' + msg[1],
},
},
],
@@ -517,7 +529,23 @@ module.exports.udpInitSessionConnectionServer = function () {
webhookUrl: globals.config.get('Butler.slackNotification.userSessionEvents.webhookURL'),
};
- let res = slackApi.slackSend(slackConfig, globals.logger);
+ // Is the user referenced by this event on the exclude list? If so don't sent a notification
+ // msg[3] = user directory
+ // msg[4] = userId
+ let sendMsg = true;
+ if (globals.config.has('Butler.slackNotification.userSessionEvents.excludeUser')) {
+ let excludeUsers = globals.config.get('Butler.slackNotification.userSessionEvents.excludeUser');
+ if ( excludeUsers.some(item => {
+ return (item.directory == msg[3] && item.userId == msg[4]);
+ })) {
+ // User found in exclude list
+ sendMsg = false;
+ }
+ }
+
+ if (sendMsg) {
+ let res = slackApi.slackSend(slackConfig, globals.logger);
+ }
}
// Send notification to MS Teams, if enabled
@@ -527,33 +555,42 @@ module.exports.udpInitSessionConnectionServer = function () {
globals.config.get('Butler.teamsNotification.enable') == true &&
globals.config.get('Butler.teamsNotification.userSessionEvents.enable') == true
) {
- await globals.teamsUserSessionObj.send(
- JSON.stringify({
- '@type': 'MessageCard',
- '@context': 'https://schema.org/extensions',
- summary: msg[1] + ' for user ' + msg[2] + '/' + msg[3],
- themeColor: '0078D7',
- title: msg[1] + ' for user ' + msg[2] + '/' + msg[3] + ' on server ' + msg[0],
- }),
- );
- }
-
- // Handle session events
- if (msg[1] == 'Start session') {
- globals.mqttClient.publish(globals.config.get('Butler.mqttConfig.sessionStartTopic'), msg[0] + ': ' + msg[2] + '/' + msg[3]);
- }
-
- if (msg[1] == 'Stop session') {
- globals.mqttClient.publish(globals.config.get('Butler.mqttConfig.sessionStopTopic'), msg[0] + ': ' + msg[2] + '/' + msg[3]);
- }
-
- // Handle connection events
- if (msg[1] == 'Open connection') {
- globals.mqttClient.publish(globals.config.get('Butler.mqttConfig.connectionOpenTopic'), msg[0] + ': ' + msg[2] + '/' + msg[3]);
+ // Is the user referenced by this event on the exclude list? If so don't sent a notification
+ // msg[3] = user directory
+ // msg[4] = userId
+ let sendMsg = true;
+ if (globals.config.has('Butler.teamsNotification.userSessionEvents.excludeUser')) {
+ let excludeUsers = globals.config.get('Butler.teamsNotification.userSessionEvents.excludeUser');
+ if ( excludeUsers.some(item => {
+ return (item.directory == msg[3] && item.userId == msg[4]);
+ })) {
+ // User found in exclude list
+ sendMsg = false;
+ }
+ }
+
+ if (sendMsg) {
+ await globals.teamsUserSessionObj.send(
+ JSON.stringify({
+ '@type': 'MessageCard',
+ '@context': 'https://schema.org/extensions',
+ summary: msg[2] + ' for user ' + msg[3] + '/' + msg[4],
+ themeColor: '0078D7',
+ title: msg[2] + ' for user ' + msg[3] + '/' + msg[4] + ' on server ' + msg[1],
+ }),
+ );
+ }
}
- if (msg[1] == 'Close connection') {
- globals.mqttClient.publish(globals.config.get('Butler.mqttConfig.connectionCloseTopic'), msg[0] + ': ' + msg[2] + '/' + msg[3]);
+ // Send MQTT messages
+ if (msg[2] == 'Start session') {
+ globals.mqttClient.publish(globals.config.get('Butler.mqttConfig.sessionStartTopic'), msg[1] + ': ' + msg[3] + '/' + msg[4]);
+ } else if (msg[2] == 'Stop session') {
+ globals.mqttClient.publish(globals.config.get('Butler.mqttConfig.sessionStopTopic'), msg[1] + ': ' + msg[3] + '/' + msg[4]);
+ } else if (msg[2] == 'Open connection') {
+ globals.mqttClient.publish(globals.config.get('Butler.mqttConfig.connectionOpenTopic'), msg[1] + ': ' + msg[3] + '/' + msg[4]);
+ } else if (msg[2] == 'Close connection') {
+ globals.mqttClient.publish(globals.config.get('Butler.mqttConfig.connectionCloseTopic'), msg[1] + ': ' + msg[3] + '/' + msg[4]);
}
} catch (err) {
globals.logger.error(`SESSIONS: Error processing user session event: ${err}`);