composer configuration
I am new on logi, I don't have any idea to configure and use as you are doing in developer.logianalytics
I have the code and it is working fine(no error in console)
but the server creating problems in response
<!DOCTYPE html>
<html>
<head>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<style id="Bootstrap5-overrides">
/*Bootstrap 5 _utilities.scss mixin override */
.logi-embed .zdView-Widget[widget-mode-size]:hover .widgetHeader .widget-controls.invisible,
.logi-embed .zdView-Widget[widget-mode-size].dragging .widgetHeader .widget-controls.invisible,
.logi-embed .zdView-Widget[widget-mode-size].active-dragging .widgetHeader .widget-controls.invisible,
.logi-embed .zdView-Widget[widget-mode-size].active-resizing .widgetHeader .widget-controls.invisible {
display: flex;
visibility: visible!important;
}
</style>
<!-- 2021-09-03 GM: updated src path to point to local:8080 **Update if your Composer server is installed elsewhere -->
<script data-name="logi-embed-manager"
src="http://localhost:8080/composer/embed/embed.js"></script>
<script>
//2021-09-03 GM: Updated globals below to match local docker trial. **Update as necessary if your server is different
//globals - defaults
const ComposerUrl = 'http://localhost:8080/composer';
const SupervisorCreds = window.btoa('supervisor:Password1!');
const ClientName = 'ClientExample';
const AdminUsername = 'admin';
const EndUsername = 'admin';
const EndUserGroup = 'Administrators';
const componentConfig = {
"theme":"modern",
"interactivityProfileName":"interactive",
// START: Interactivity profile overrides
// The interactiveProfileName property must be specified before
// the interactivity overrides (interactivityOverrides object)
// can work. The interactivityOverrides object properties affect
// the current embedded dashboard properties. The visualSettings
// object properties affect all visuals properties. Please read the
// Documentation Helper for more properties that can be overridden.
"interactivityOverrides": {
"name": "interactive",
"type": "SYSTEM",
"overrideVisualInteractivity": true,
"settings": {
"REFRESH": true,
"CHANGE_LAYOUT": true,
"RENAME": true,
"SHARE_FILTER_SETS": true,
"DASHBOARD_INTERACTIONS": true,
"ADD_TO_FAVORITES": true,
"DELETE": true,
"FILTER": true,
"EXPORT_PNG_PDF": true,
"ADD_VISUALS": true,
"EXPORT_CONFIGURATION": true,
"DASHBOARD_LINKS": true,
"SAVE": true
},
"visualSettings": {
"ACTIONS_ACTION": true,
"RULERS": true,
"ZOOM_ACTION": true,
"FILTER_ACTION": true,
"COLORS": true,
"METRICS": true,
"ACTIONS": true,
"TREND_ACTION": true,
"VISUAL_STYLE": true,
"KEYSET_ACTION": true,
"KEYSET": undefined,
"COPY": true,
"SETTINGS": true,
"EXPORT": true,
"TIMEBAR_PANEL": true,
"DETAILS_ACTION": true,
"MAXIMIZE": true,
"LINK_ACTION": true,
"FILTER": true,
"REMOVE": true,
"GROUPING": true,
"RENAME": true,
"SORT": true,
"TIMEBAR_FIELD": true
}
},
/* END: interactivity profile overrides */
"editor": { "placement": "dockRight" },
"header": {
"showTitle": false,
}
}
</script>
<script>
// - set by functions to reuse
Client = {};
ClientCredsForAccessTokenGeneration = {};
AdminAccessToken = {};
AdminGroups = {};
EndUser = {};
EndUserAccessToken = {};
EndUserDashboards = {};
msgToggle = true; //true: show - false: hide
/* Helper functions to talk to Composer and present info on page ---------------------- */
const Get = async (GetUrl, AccessToken, AuthType = 'Bearer') => {
return await fetch(GetUrl, {
headers: {
'Origin': 'http://localhost',
'Access-Control-Request-Method': 'GET',
'Access-Control-Request-Headers': 'Authorization, Content-Type',
'Content-Type': 'application/vnd.composer.v3+json',
'Authorization': `${AuthType} ${AccessToken}`,
},
}).then(x => x.json());
};
const Post = async (ReqUrl, AuthCreds, AuthType = 'Basic', ReqBody) => {
return await fetch(ReqUrl, {
method: 'POST',
headers: {
'Accept': 'application/vnd.composer.v3+json',
'Origin': 'http://localhost',
'Access-Control-Request-Method': 'GET',
'Access-Control-Request-Headers': 'Authorization, Content-Type',
'Content-Type': 'application/vnd.composer.v3+json',
'Authorization': `${AuthType} ${AuthCreds}`,
},
body: JSON.stringify(ReqBody)
}).then(x => x.json());
};
const Del = async (GetUrl, AuthCreds, AuthType = 'Basic') => {
return await fetch(GetUrl, {
method: 'DELETE',
headers: {
'Accept': '*/*',
'Content-Type': 'application/vnd.composer.v2+json',
'Authorization': `${AuthType} ${AuthCreds}`
}
});
};
const logMessage = (message) => {
let messageContainer = document.getElementById('messagebox');
if (messageContainer == null) {
document.getElementById("message").innerHTML = '<div id="messagebox" style="max-height: 300px; overflow: auto;" class="alert alert-success">' + message + '<BR></div>';
} else {
messageContainer.innerHTML = messageContainer.innerHTML + message + '<BR>';
messageContainer.scrollTop = messageContainer.scrollHeight;
}
};
const toggleMsgPnl = () => {
let msgPnl = document.getElementById("message").classList;
msgToggle = msgToggle ? 0 : 1;
if ( msgToggle ) { msgPnl.remove( 'd-none' )} else { msgPnl.add( 'd-none' )}
}
/* ------------------------------------------------------------------------------------------ */
/*** -- ONE TIME ACTION for Parent App, possibly handled by deployment scripts ***/
// In this example page, for the sake of demonstration, we are first checking to see
// if it already exists and cleaning up by deleting it before recreating it and
// and retrieving the new secret. This is normally a one time action on instance deployment
// as it is just used as a handshake mechannism between the parent app and the Logi Server
const getClients = async () => {
let clientExists = await Get( `${ComposerUrl}/api/trusted-access/clients`, SupervisorCreds, "Basic" )
.then(( result ) => {
Client = result.content.find( x => x.client_name === ClientName );
return Client
});
return clientExists;
}
const deleteClient = async ( client_id ) => {
deletedClient = await Del( `${ComposerUrl}/api/trusted-access/clients/${client_id}`,SupervisorCreds, 'Basic' );
return deletedClient;
}
async function processClient() {
async function createClient(){
// create/re-create client
var ReqBody = {
"access_token_validity_seconds": 2000,
"client_name": ClientName
};
result = await Post( `${ComposerUrl}/api/trusted-access/clients`, SupervisorCreds, "Basic", ReqBody )
.then(( result ) => {
ClientCredsForAccessTokenGeneration = window.btoa( `${ result.client_id }:${ result.client_secret }` );
localStorage.setItem( 'ComposerTAClientKey', ClientCredsForAccessTokenGeneration );
logMessage( "client response: <pre>" + JSON.stringify( result, undefined, 2 )) + "</pre><BR>";
return ClientCredsForAccessTokenGeneration
});
return result;
};
async function validateClient() {
// first check localStorage for stored client secret info
try {
var localTAClient = localStorage.getItem( 'ComposerTAClientKey' );
} catch( e ) {
console.log( "localTAClient is not available in localStorage" );
}
// check whether client is registered in Composer - not required in normal deployments
let clientExists = await getClients();
// if the client exists and there is a saved secret
if ( clientExists && !localTAClient ) {
deletedClient = await deleteClient( clientExists.client_id );
} else if ( clientExists && localTAClient ) {
logMessage( "client response: <pre>" + atob( localTAClient ) + " client secret exists in localStorage </pre><BR>" );
return localTAClient;
}
try {
createClient();
} catch ( e ) {
console.log( e );
deletedClient = await deleteClient( clientExists.client_id );
console.log( deletedClient );
}
};
ClientCredsForAccessTokenGeneration = await validateClient();
}
/*** -- PARENT APP BACKEND FUNCTIONS coded by developers in Java, .NET, nodeJS/ExpressJS, Python, PHP, ... ***/
// In this example page, and for the sake of easily demonstrating the TA workflow, we are
// performing these tasks in the HTML on the client side. This should never be the case in a
// production deployment where creating access tokens or provisioning users
function createAdminAccessToken() {
var ReqBody = {
"username": AdminUsername
};
Post( `${ComposerUrl}/api/trusted-access/token`, ClientCredsForAccessTokenGeneration, "Basic", ReqBody )
.then(( result ) => {
AdminAccessToken = result;
logMessage("admin access token: <pre>" + JSON.stringify(result, undefined, 2)) + "</pre><BR>";
});
}
function getGroups() {
Get( `${ComposerUrl}/api/groups`, AdminAccessToken.access_token )
.then(( result ) => {
AdminGroups = result.content;
logMessage( "groups available to admin: <pre>" + JSON.stringify( result, undefined, 2 ) ) + "</pre><BR>";
})
}
// NOTE: When provisioning the end user, this may be driven by the parent app, or we provision "group users" if
// limiting the user management tasks in Logi Composer. In this sample we are hard-coding the Administrators
// group. This can be changed to another group with different security settings if desired, or set dynamically
function provisionEndUser() {
// check if user exists
var userExists = Get( `${ComposerUrl}/api/users`, AdminAccessToken.access_token )
.then(( result ) => {
if ( result.content.find( ( g ) => g.name === EndUsername ) ) {
// could delete user instead?
logMessage( "user exists: <pre>" + JSON.stringify( EndUsername, undefined, 2 )) + "</pre><BR>";
return true;
} else {
var ReqBody = {
"name": EndUsername,
"accounts": [
{
"accountId": AdminGroups[0].accountId,
"groups": [
AdminGroups.find(g => g.label === EndUserGroup ).id
],
"roles": [],
"userAttributes": [
{ "key": "org_id",
"value": "USA",
"encrypted": false
},{
"key": "Disaster",
"value": "Severe Storm,Tropical Cyclone,Flooding,Wildfire",
"encrypted": false
}]
}
],
"fullname": EndUsername
}
Post( `${ComposerUrl}/api/users`, AdminAccessToken.access_token, "Bearer", ReqBody )
.then(( result ) => {
EndUser = result;
logMessage("user created: <pre>" + JSON.stringify( result, undefined, 2 )) + "</pre><BR>";
});
}
});
}
function createEndUserAccessToken() {
var ReqBody = {
"username": EndUsername
};
Post(`${ComposerUrl}/api/trusted-access/token`, ClientCredsForAccessTokenGeneration, "Basic", ReqBody)
.then((result) => {
EndUserAccessToken = result;
logMessage("end user access token: <pre>" + JSON.stringify(result, undefined, 2)) + "</pre><BR>";
});
}
/*** PARENT APP FRONTEND FUNCTIONS FOR EMBEDDING and listing Composer Content. Get token from Backend, using logic defined in parent backend that communicates to Composer ***/
// once the end user is provisioned and the end user access token (bearer token) is generated,
// the token is passed to the client browser from the parent app server
function getDashboardsAvailableToEndUser() {
Get(`${ComposerUrl}/api/inventory`, EndUserAccessToken.access_token)
.then((result) => {
EndUserDashboards = result.content;
//Add create new dashboard button
document.getElementById('dashboards').innerHTML = document.getElementById('dashboards').innerHTML + `<button id="newdashboard" onclick="addNewDashboard()" class="btn btn-secondary btn-sm me-2 mb-1"> Create new dashboard</button>`;
logMessage("dashboards available to end user: <pre>" + JSON.stringify(result, undefined, 2)) + "</pre><BR>";
for ( const dashboard of EndUserDashboards ) {
document.getElementById('dashboards').innerHTML = document.getElementById('dashboards').innerHTML + `<button id="dashboard-${dashboard.inventoryItemId}" onclick="EmbedDashboard('${dashboard.inventoryItemId}')" class="btn btn-info btn-sm me-1 mb-1">${dashboard.name}</button>`;
}
})
}
function EmbedDashboard( dashboardid ) {
const embedManagerPromise = window.initComposerEmbedManager({
initialToken: EndUserAccessToken.access_token // tokenValue is provided by the Trusted Access API
});
embedManagerPromise.then((embedManager) => {
//2021-09-03 GM: Updated to clean-up old dashboard components before adding a new one
for ( var component in embedManager.componentList ) {
embedManager.removeComponent( component);
}
componentConfig.dashboardId = dashboardid;
embedManager.createComponent( 'dashboard', componentConfig )
.then(dashboard => dashboard.render(
document.getElementById( "embedDash" ), // htmlElement
{ width: '100%', height: '100%' }
))
});
}
function addNewDashboard() {
const embedManagerPromise = window.initComposerEmbedManager({
initialToken: EndUserAccessToken.access_token // tokenValue is provided by the Trusted Access API
});
//delete dashboardId from componentConfig if exists
delete componentConfig.dashboardId;
embedManagerPromise.then(( embedManager ) => {
//2021-09-03 GM: Updated to clean-up old dashboard components before adding a new one
for ( var component in embedManager.componentList ) {
embedManager.removeComponent( component);
}
embedManager.createComponent( "dashboard", componentConfig ).then( component => {
component.render(document.getElementById( 'embedDash' ), { width:"100%", height: "100%" });
})
});
}
</script>
</head>
<body>
<div class="container">
<h2>Composer 6.9 Trusted Access and Embed Manager Workflow</h2>
<div id="controls" class="mb-3">
<i class="d-block text-muted">*** Performed once during instance deployment - manual or scripted ***</i>
<button id="createClient" onclick="processClient()" class="btn btn-success btn-sm">1. Create Client</button>
</div>
<div id="controls" class="mb-3">
<i class="d-block text-muted">*** Parent application server-side functions ***</i>
<button id="createAdminAccessToken" onclick="createAdminAccessToken()" class="btn btn-warning btn-sm">2. Generate
Admin Token</button>
<button id="getGroups" onclick="getGroups()" class="btn btn-warning btn-sm">3. Get Groups</button>
<button id="provisionEndUser" onclick="provisionEndUser()" class="btn btn-warning btn-sm">4. Create End
User</button>
<button id="createEndUserAccessToken" onclick="createEndUserAccessToken()" class="btn btn-warning btn-sm">5.
Generate
End User Token</button>
</div>
<div id="controls" class="mb-3">
<i class="d-block text-muted">*** Parent application client-side functions ***</i>
<button id="getDashboardsAvailableToEndUser" onclick="getDashboardsAvailableToEndUser()"
class="btn btn-info btn-sm">6. Get Dashboards available to End User</button>
<button id="toggleMsgPnl" onclick="toggleMsgPnl()"
class="btn btn-sm text-muted float-end">+/- Messages</button>
</div>
<div id="message" class="mb-3"></div>
<div id="dashboards" class="mb-3"></div>
<div id="embedDash" class="mb-3" style="position:relative; height: 80vh; border:dashed 1px red;"></div>
</div>
</body>
</html>
-
I think in logi there is a bug for getting client list
When I have called API in "Swagger" the response also gives me
for this request i have passed the uesername{ "error": "invalid_client", "error_description": "Client authentication failed" }
please help me
Thanks
Preetam k joshi0 -
I see almost all API calls return unauthorized
0 -
I have fixed the issue but one very important API hit not working for me please let me know
about an access token API
This API hit gives me{ "timestamp": "2022-01-14 06:48:39.498", "status": 403, "error": "Forbidden", "message": "Your user session has expired. Please refresh the page to get a new user session established before changes can be saved.", "path": "/composer/api/trusted-access/token" }
it gives me a session issue but if I hit another API works fine
so as per my knowledge this is not the session issueplease please please let me know and guide me for this issue
I will really happy to receive your responce
Please response a bit fast please0 -
Hi Preetam,
Are you just trying to get the example HTML page working against your server? The reason I ask is because it's just intended as a portable example used to demonstrate integration workflow for the API calls. Many of the calls would need to be written into server side code, not frontend JavaScript as in the example. The example page was written so that you should just need to update some of the constants at the top of the page. Alternatively are you just using the HTML page as a guide to develop your own embedding workflow?
The first action where the example shows API calls to get/manage a client secret would not be required in any frontend or backend code normally. A client secret is generally created as a one time exercise as part of the deployment of new Logi Composer instance. It is used as a trusted handshake between the parent application and the Logi Composer instance. The secret that the server returns is for a specific client name that's been submitted with the initial request, for example in the HTML page you're using it's set to 'ClientExample'. The developer/DevOps team then generally store the client name and secret on the host server in an encrypted file or via some other method and the parent application server-side code will retrieve the secret so that admin access tokens can be generated in order to provision users and get a user bearer token that can then be used in front end code to request dashboards etc.
Regarding the particular issue, it's very difficult to know what's happening without seeing the code. I think maybe you're referring to this function
function createEndUserAccessToken() {
varReqBody = {
"username":EndUsername
};
Post(`${ComposerUrl}/api/trusted-access/token`, ClientCredsForAccessTokenGeneration, "Basic", ReqBody).then((result) => {
EndUserAccessToken = result;
logMessage("end user access token: <pre>" + JSON.stringify(result, undefined, 2)) + "</pre><BR>";
});
}The end user access token (bearer token) has a timeout based on the access-token-validity set when you create the client secret. If this has changed it may be the cause of the issue. Originally it was set to 2000 seconds.async function createClient(){
// create/re-create client
varReqBody = {
"access_token_validity_seconds":2000,
"client_name":ClientName
};
result = awaitPost( `${ComposerUrl}/api/trusted-access/clients`, SupervisorCreds, "Basic", ReqBody ).then(( result ) => {
ClientCredsForAccessTokenGeneration = window.btoa( `${result.client_id}:${result.client_secret}` );
localStorage.setItem( 'ComposerTAClientKey', ClientCredsForAccessTokenGeneration );
logMessage( "client response: <pre>" + JSON.stringify( result, undefined, 2 )) + "</pre><BR>";
returnClientCredsForAccessTokenGeneration
});
return result;
};Alternatively there maybe another issue, which would probably be easiest to figure out using the browser development tools and reviewing the request and response headers for the 403 you're receiving.Best regardsGlyn0 -
Hello Glyn McKenna
I am really so happy to see your reply
I am using the HTML file only for checking, how the logi API works
but if you see in the image it should be work
Why it is not working here?
Thanks
Preetam Kumar Joshi0 -
Hello Glyn McKenna
In swagger, as I have shared screenshot this should work
There is no code
It's a simple API hit
And I logged in on the composer instance
Why is the API hit in swagger not working
Please let me know I have so many tasks on that
Please Please tell me, I will do as it is because I have so many works on Logi firstly need to integrate and then create the chart and so many things
Is it possible to resolve this problem in Logi?
Your faithful friend
Preetam Kumar Joshi0 -
Hello Glyn McKenna
My issue is resolved,
Thanks for the reply I am always thankful for your reply
Your faithful friend
Preetam Kumar Joshi0 -
Hi Preetam,
For the benefit of others who may read your post, the following is required in order to get an end user access token (Bearer token):
- First get a client secret (client id & secret) based on a client name you choose for your instance of Logi Composer. You should only need to do this once on server deployment (unless you have a specific use case to add more). This is usually scripted or done manually as part of the deployment pipeline. Here is an example using PHP:
Example client secret request:
<?php
$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_URL => 'http://localhost:8080/composer/api/trusted-access/clients',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '',
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 0,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_POSTFIELDS =>'{
"access_token_validity_seconds": 2000,
"client_name": "my_client"
}',
CURLOPT_HTTPHEADER => array(
'Content-Type: application/vnd.composer.v3+json',
'Authorization: Basic c3VwZXJ2aXNvcjpzdXBlcnZpc29y'
),
));
$response = curl_exec($curl);
curl_close($curl);
echo $response;Example response:
{
"client_name": "myclient",
"access_token_validity_seconds": 2000,
"client_id": "0ebfec89-4e58-4a83-9a65-44cbe8ebfe30",
"client_secret": "vEC9qWH1PNpZk7triAsOo6hRX3TblPI5aYGd",
"client_secret_expires_at": 0,
"token_endpoint_auth_method": "client_secret_basic"
}- Once you have your client_id and client_secret from the response (these will normally be stored in and encrypted file or db accessible to the hosting application server) you can use these as the user name and password for basic authentication on the end user access token request:
Example user token request:
<?php
$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_URL => 'http://localhost:8080/composer/api/trusted-access/token?',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '',
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 0,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_POSTFIELDS =>'{
"username": "readonly"
}',
CURLOPT_HTTPHEADER => array(
'Content-Type: application/vnd.composer.v3+json',
'Authorization: Basic NDM1ZGY5MDMtNWZiNy00N2QyLWFkNjQtYzRjNGYwZWJjYTdjOmtVbFBHV2hhRFhXMXRKZVowUWgwN0tlekNSWmc3MTR0b1FLeQ=='
),
));
$response = curl_exec($curl);
curl_close($curl);
echo $response;- You should get an access token in the response, which you can then use in client-side script to retrieve dashboards the user has access to
Best regards
Glyn
0
Please sign in to leave a comment.
Comments
8 comments