With HCL Connections 6.5 and later, we got the add-on HCL Connections Engagement Center (aka CEC, HCEC, ICEC or XCC) included in a normal HCL Connections deployment.
The HCL Connections license contains the supplement that HCEC can be used within Communities and is the base for the Highlights application. All other options are hidden and could be enabled in LotusConnections-config.xml
(set <genericProperty name="icec.light">false</genericProperty>
), but then you need to order the HCL Connections Engagement Center license.
Now, the users with the admin
role in ICEC
(ISC - Enterprise Applications > ICEC > Security role to user/group mapping) are allowed to use the dialog or API to upload a file.
I’m not responsible for your licenses! Do not enable the full version if you aren’t allowed to.
As long as icec.light
is set to true
, you have no option to add a customized JavaScript file with additional widgets or to hide some of the default widgets of HCEC.
This is documented in “Include custom.js” and “Creating a custom widget with the custom widget API” .
The customization files for HCEC are stored in the XCC database, so there is no option to just upload a file to the customization folder in your shared directory or use the customizer to achieve this. You can download the file from your database, it is stored in a BLOB field in XCC.XCCFILESTORAGE
.
From a DevOps perspective, we want to automate as much as possible, so adding this customization manually is boring and could cause errors. Since HCEC is integrated, I wish to build a script to update custom.js
, without enabling the full version of HCEC.
In my example, I intend to hide the birthday widget and the new Leap widgets, as we haven’t deployed the Leap application in this environment. To be honest, I would expect that you could select the shown widgets somewhere, but until now, they have just been added, but there is no option to hide some of them.
The used custom.js
in the database and the documentation contains some examples for custom widgets, but this is not necessary for the functionality or our goal to hide some widgets.
So I created a new file with this content:
|
|
In lines 4 to 6, we set the list of widgets to undefined, so they do not appear in the list of available widgets.
The easiest way to find the widget name is to use the browser developer tools and use the ID of the widget.
Now we need to upload the file to HCEC. The official upload link is only visible for users with the Admin role in ICEC and when icec.light
is set to false
. I traced the requests and created a script to upload the file from Python.
The requirement is that the used user credentials have the admin role ICEC, but it is not necessary that icec.light
is set to false
.
"""
Author: Christoph Stoettner
Mail: christoph.stoettner@stoeps.de
Commandline script to upload custom.js to HCL Engagement Center
This even works when icec.light = true
"""
import sys
import requests
import os.path
import urllib3
urllib3.disable_warnings()
if len(sys.argv) != 5:
raise ValueError(
"Please provide hostname user password community-uuid and the upload filename.\nExample: %s hostname username password custom.js"
% sys.argv[0]
)
else:
cnx_host = sys.argv[1]
cnx_user = sys.argv[2]
cnx_pass = sys.argv[3]
upload_file_name = sys.argv[4]
if os.path.exists(upload_file_name):
pass
else:
print("The filename " + upload_file_name + " does not exist.")
sys.exit()
cnx_root_url = "https://" + cnx_host
headers = {}
login_url = cnx_root_url + "/homepage/j_security_check"
get_csrf_url = cnx_root_url + "/xcc/community"
session = requests.Session()
login_data = {"j_username": cnx_user, "j_password": cnx_pass}
login_response = session.post(login_url, data=login_data, verify=False)
if login_response.status_code == 200:
# Create a community and delete it after getting the CSRF Token
create_comm_url = cnx_root_url + "/communities/service/atom/communities/my"
headers["Content-Type"] = "application/atom+xml"
comm_name = "Test for Highlights 1293054839840483"
data = (
'<?xml version="1.0" encoding="UTF-8"?><entry xmlns="http://www.w3.org/2005/Atom" xmlns:app="http://www.w3.org/2007/app" xmlns:snx="http://www.ibm.com/xmlns/prod/sn"><title type="text">'
+ comm_name
+ '</title><summary type="text">ignored</summary><content type="html">This should be deleted within one minute</content><published>ignored</published><category term="community" scheme="http://www.ibm.com/xmlns/prod/sn/type"></category><snx:communityType>public</snx:communityType></entry>'
)
create_comm_response = session.post(
create_comm_url, data=data, headers=headers, verify=False
)
community_created = create_comm_response.headers["Location"]
community_created_uuid = community_created.split("=")[1]
json_data = {}
if create_comm_response.status_code == 201:
print("Community successfully created:" + community_created_uuid)
json_data["commId"] = community_created_uuid
headers = {
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/119.0",
"Accept": "*/*",
"Accept-Language": "en-US,en;q=0.5",
"Accept-Encoding": "gzip, deflate",
"Content-Type": "application/json; charset=utf-8",
}
headers["Origin"] = cnx_root_url
headers["Referer"] = (
cnx_root_url
+ "/communities/service/html/communityoverview?communityUuid="
+ community_created_uuid
)
get_csrf_response = session.post(
get_csrf_url, headers=headers, json=json_data, verify=False
)
# Store csrf_token_icec for later
csrf_token = session.cookies.get_dict()["csrf_token_icec"]
upload_path = "/xcc/rest/files/upload"
upload_url = cnx_root_url + upload_path
headers["Accept"] = "application/json, */*; charset=utf8"
headers["X-Requested-With"] = "XMLHttpRequest"
headers["X-Token-Icec"] = csrf_token
# Remove Content-Type (is added during update)
headers.pop("Content-Type")
files = {
"files[]": ("custom.js", open(upload_file_name), "application/x-javascript")
}
upload_response = session.post(
upload_url, files=files, headers=headers, verify=False
)
# Handle the response as needed
if upload_response.status_code == 200:
print("Uploaded successfully")
else:
print("Upload failed")
print("\n**** request headers ****")
# print(upload_response.request.headers)
for key in upload_response.request.headers.keys():
print(key + ": " + upload_response.request.headers[key])
print("\n**** request body ****")
print(upload_response.request.body[:400])
print("\n**** response content ****")
print(upload_response.status_code, upload_response.content[:400])
# Delete community (delete twice to remove from Trash)
delete_comm_response = session.delete(
cnx_root_url
+ "/communities/service/atom/community/instance?communityUuid="
+ community_created_uuid
)
delete_comm_response2 = session.delete(
cnx_root_url
+ "/communities/service/atom/community/instance?communityUuid="
+ community_created_uuid
)
if delete_comm_response.status_code == 200:
print("Temporary created community deleted: " + community_created_uuid)
if delete_comm_response2.status_code == 200:
print(
"Temporary created community removed from trash: "
+ community_created_uuid
)
else:
print("Temporary community couldn't be deleted: " + community_created)
else:
print("Community not created: " + str(create_comm_response.status_code))
else:
print("Login failed!")
# Close the session
session.close()
For the file upload we need a cookie from HCEC which is later provided as header X-Token-Icec
.
The Python script uses requests and urllib3, so please install them before using the script:
pip3 install requests urllib3
I wrote the script to automate the task with Ansible, so I just use the sys.argv
variables during the call. When you have the config.js
and this script in the same directory, you can call them with:
python3 upload_customjs.py <your connections hostname> <admin user> <admin password> <file to upload>
python3 upload_customjs.py cnx8-db2.stoeps.home stoeps password custom.js
There is no need to name the file custom.js
, you can use any name; the script uploads as custom.js
.
Update
In the first version of this blog post you had to add a community uuid to do the csrf token call. I changed this and create a community during the upload and delete it immediatly after uploading the custom.js
file.