Implementing System Logs in SUSI.AI Admin Panel

The admin panel of SUSI.AI provides a lot of features required by system maintainers and admins to administer and maintain various activities of SUSI. Therefore system logs have been implemented on the admin panel so that admins can check whether SUSI server is working fine or throwing some errors. In this blog we will discuss about how logs are implemented on server and accounts.

Continue Reading

Implementing API keys on SUSI.AI server

The clients of SUSI.AI need config keys to work with some APIs like captcha, maps and blog and these keys are stored in the server of SUSI and the clients fetch them using API calls to the server. The admins can add or delete api keys from the server using the

/aaa/apiKeys.json

 

API. This API stores the API keys in a json format in the apiKeys.json file in the system_keys_dir directory which is in the data directory on the susi server.

For the clients to fetch these API keys and use them in their respective APIs, they need to use

Continue Reading

Implementing Search User Feature for Admins

The users tab of admin panel of SUSI.AI provides a list of all users registered on SUSI. This helps admins to get an overview of users and also provides the admins option like change user roles and delete accounts. The list of users is displayed in a table which also uses pagination to handle larger number of users. But to find a particular user can be a difficult task if the user base is large. Therefore a feature to search for users by their email has been implement which searches for users on SUSI server and then sends the searched users to the client. In this post we will discuss both about the server and client side implementation of this feature.

Continue Reading

Implementation of Delete Skill Feature for Admins in SUSI.AI

The admin panel of SUSI.AI has an admin panel to manage its users and skills and it also provides the admins to delete skills and restore the deleted ones. In this post we will discuss about how this works on the server and client side.

On the server side there are three APIs associated with this feature. One is to delete a skill, one to restore the skills and another to display the list of deleted skills. The following servlets are responsible for the above APIs and we will discuss about them in detail.

Continue Reading

Implementing User Stats for SUSI.AI Admin Panel

SUSI.AI has an admin panel where users with roles operator or above get an overview of various stats and manage other users and skills. The admin panel allows the system admins to get a list of all users registered on susi and also provide an option to change their user roles as well. The admin tab provides us with the statistics of its users. In this post we will discuss how this is implemented on susi server.

Continue Reading

Implementation of admin dashboard on Accounts

The admin dashboard for SUSI.AI is implemented in the susi accounts to access various admin features like list all users registered on SUSI and change user roles of other users. In this post we will discuss about the client side implementation of listing registered users and changing their user roles.

Continue Reading

List all the Users Registered on SUSI.AI

In this blog, I’ll be telling on how SUSI admins can access list of all the registered users from SUSI-server. Following this, they may modify/edit user role of any registered user.

What is User Role?

A UserRole defines the servlet access right. Not all users are allowed to access all the data and services. For  example, To list all the users, minimal user role expected is ADMIN. This classification of users are inspired by the wikipedia User Access Levels, see https://en.wikipedia.org/wiki/Wikipedia:User_access_levels.While querying SUSI, Users are classified into 7 different categories, namely :

  • BOT
  • ANONYMOUS
  • USER  
  • REVIEWER
  • ACCOUNTCREATOR
  • ADMIN
  • BUREAUCRAT

* Please see that these are as of the date of publish of this blog. These are subject to change, which is very unlikely.

All the users who are not logged in but interacting with SUSI are anonymous users. These are only subject to chat with SUSI, login, signup or may use forgot password service. Once a user login to the server, a token is generated and sent back to client to maintain the identity, hence acknowledging them. Privileged users are those who have special rights with them. These are more like moderators with much special rights than any other user. At the top level of the hierarchy are the admins. These users have more rights than anyone. They can change role of any other user, override decision of any privileged user as well.

Let us now look at the control flow of this.

First things first, make a component of User List in the project. Let us name it ListUsers and since it has to be accessible by those users who possess ADMIN rights, you will find it enclosed in Admin package in components folder. Open up

index.js file, import Listusers component  and add route to it in the following way :

...//other import statements
import ListUser from "./components/Admin/ListUser/ListUser";
...//class definition and other methods
<Route path="/listUser" component={ListUser}/>
//other routes defined

Find a suitable image for “List Users” option and add the option for List Users in static appbar component along with the image. We have used Material UI’s List image in our project.

...// other imports

import List from 'material-ui/svg-icons/action/list';

Class and method definition

<MenuItem primaryText="List Users"
          onTouchTap={this.handleClose}
          containerElement={<Link to="/listUser" />}
                rightIcon={<List/>}
      />

...//other options in top right corner menu

Above code snippet will add an option to redirect admins to ‘/listUsers’ route. Let us now have a closer look at functionality of both client and server. By now you must have known what ComponentDidMount does. {If not, I’ll tell you. This is a method which is given first execution after the page is rendered. For more information, visit this link}. As mentioned earlier as well that this list will be available only for admins and may be even extended for privileged users but not for anonymous or any other user, an AJAX call is made to server in ComponentDidMount of ‘listuser’ route which returns the base user role of current user. If user is an Admin, another method, fetchUsers() is called.

let url;
        url = "http://api.susi.ai/aaa/account-permissions.json";
        $.ajax({
            url: url,
            dataType: 'jsonp',
            jsonpCallback: 'py',
            jsonp: 'callback',
            crossDomain: true,
            success: function (response) {
                console.log(response.userRole)
                if (response.userRole !== "admin") {
                    console.log("Not an admin")
                } else {
                    this.fetchUsers();
                    console.log("Admin")
                }
            }.bind(this),
});

In fetchUsers method, an AJAX call is made to server which returns username in JSONArray. The response looks something likes this :

{
	"users" : {
		"email:""[email protected]",
...
},
"Username":["[email protected]", "[email protected]"...]
}

Now, only rendering this data in a systematic form is left. To give it a proper look, we have used material-ui’s table. Import Table, TableBody, TableHeader,

   TableHeaderColumn, TableRow, TableRowColumn from material-ui/table.

In fetchUsers method, response is catched in data Oblect. Now the keys are extracted from the JSON response and mapped with an array. Iterating through array received as username array, we get list of all the registered users. Now, popuulate the data in the table you generated.

return (
                        <TableRow key={i}>
                            <TableRowColumn>{++i}>
                            <TableRowColumn>{name}</TableRowColumn>
                            <TableRowColumn> </TableRowColumn>
                            <TableRowColumn> </TableRowColumn>
                            <TableRowColumn> </TableRowColumn>
                            <TableRowColumn> </TableRowColumn>
                        </TableRow>
                    )

Above piece of code may help you while populating the table. These details are returned from susi server which gets a list of all the registered in the following manner. First, it checks if base url of this user is something apart from admin. If not, it returns error which may look like this :

Failed to load resource: the server responded with a status of 401 (Base user role not sufficient. Your base user role is 'ANONYMOUS', your user role is 'anonymous')

Otherwise, it will generate a client identity, use to to get an authorization object which will loop through authorization.json file and return all the users encoded as JSONArray.

Additional Resources

  1. Official Material UI Documentation on Tables from marterial-ui
  2. Answer by Marco Bonelli on Stackoverflow on How to map JSON response in JavaScript?
  3. Answer by janpieter_z on Stackoverflow – on Render JSON data in ReactJS table
Continue Reading

Deleting SUSI Skills from Server

SUSI Skill CMS is a web application to create and edit skills. In this blog post I will be covering how we made the skill deleting feature in Skill CMS from the SUSI Server.
The deletion of skill was to be made in such a way that user can click a button to delete the skill. As soon as they click the delete button the skill is deleted it is removed from the directory of SUSI Skills. But admins have an option to recover the deleted skill before completion of 30 days of deleting the skill.

First we will accept all the request parameters from the GET request.

        String model_name = call.get("model", "general");
        String group_name = call.get("group", "Knowledge");
        String language_name = call.get("language", "en");
        String skill_name = call.get("skill", "wikipedia");

In this we get the model name, category, language name, skill name and the commit ID. The above 4 parameters are used to make a file path that is used to find the location of the skill in the Susi Skill Data repository.

 if(!DAO.deleted_skill_dir.exists()){
            DAO.deleted_skill_dir.mkdirs();
   }

We need to move the skill to a directory called deleted_skills_dir. So we check if the directory exists or not. If it not exists then we create a directory for the deleted skills.

  if (skill.exists()) {
   File file = new File(DAO.deleted_skill_dir.getPath()+path);
   file.getParentFile().mkdirs();
   if(skill.renameTo(file)){
   Boolean changed =  new File(DAO.deleted_skill_dir.getPath()+path).setLastModified(System.currentTimeMillis());
     }

This is the part where the real deletion happens. We get the path of the skill and rename that to a new path which is in the directory of deleted skills.

Also here change the last modified time of the skill as the current time. This time is used to check if the skill deleted is older than 30 days or not.

    try (Git git = DAO.getGit()) {
                DAO.pushCommit(git, "Deleted " + skill_name, rights.getIdentity().isEmail() ? rights.getIdentity().getName() : "[email protected]");
                json.put("accepted", true);
                json.put("message", "Deleted " + skill_name);
            } catch (IOException | GitAPIException e) {

Finally we add the changes to Git. DAO.pushCommit pushes to commit to the Susi Skill Data repository. If the user is logged in we get the email of the user and set that email as the commit author. Else we set the username “[email protected]”.

Then in the caretaker class there is a method deleteOldFiles that checks for all the files whose last modified time was older than 30 days. If there is any file whose last modified time was older than 30 days then it quietly delete the files.

public void deleteOldFiles() {
     Collection<File> filesToDelete = FileUtils.listFiles(new         File(DAO.deleted_skill_dir.getPath()),
     new 
(DateTime.now().withTimeAtStartOfDay().minusDays(30).toDate()),
            TrueFileFilter.TRUE);    // include sub dirs
        for (File file : filesToDelete) {
               boolean success = FileUtils.deleteQuietly(file);
            if (!success) {
                System.out.print("Deleted skill older than 30 days.");
            }
      }
}

To test this API endpoint, we need to call http://localhost:4000/cms/deleteSkill.txt?model=general&group=Knowledge&language=en&skill=<skill_name>

Resources

JGit Documentation: https://eclipse.org/jgit/documentation/

Commons IO: https://commons.apache.org/proper/commons-io/

Age Filter: https://commons.apache.org/proper/commons-io/javadocs/api-1.4/org/apache/commons/io/filefilter/AgeFileFilter.html

JGit User Guide: http://wiki.eclipse.org/JGit/User_Guide

JGit Repository access: http://www.codeaffine.com/2014/09/22/access-git-repository-with-jgit/

Continue Reading

Dynamic Segments in Open Event Frontend

In the Open Event Frontend project we have a page where we show all types of events. We have classified events into six classes like live, draft, past etc. Now each event type should have it’s own page describing it. What should we do now? Make six different routes corresponding to each class of event? Isn’t that too cumbersome. Is there any other method to do it?

Dynamic segment is the answer to the above question. Dynamic segment is that segment of the path for a route that will change based on content of  the page. Dynamic segments are frequently used in Open Event Frontend.

One such use is in /admin/events. Here we have button for different categories of events. We do not make separate routes for each of them, instead we use dynamic segments. We’ll have a single route and we will change the data in the route corresponding to the tab chosen.

Lets now add dynamic segments to /admin/events. First of all we’ll add the following code snippet in router.js.

this.route(‘events’, function() {
     this.route(‘list’, { path: ‘/:events_status’ });
     this.route(‘import’);
   });

Here : signifies the presence of dynamic segment and it is followed by an identifier. It’s the identifier by which the route matches the corresponding model to show.

Now as our route would show data depending upon the tab selected, we must change the title of page depending upon the same. For this we add the following code snippet in admin/events/list.js. Here we set the title of the page using titleToken() function and access the dynamic portion of url using params.events_status

export default Route.extend({
 titleToken() {
   switch (this.get(‘params.events_status’)) {
     case ‘live’:
       return this.l10n.t(‘Live’);
     case ‘draft’:
       return this.l10n.t(‘Draft’);
     case ‘past’:
       return this.l10n.t(‘Past’);
     case ‘deleted’:
       return this.l10n.t(‘Deleted’);
   }
 },
 model(params) {
   this.set(‘params’, params);
   return [{
   // Events data
   }];
 }
});

We’ll now link the tabs template/admin/events to the corresponding models using Link-to the helper.Here we have linked ‘Live’, ‘Draft’, ‘Past’, ‘Deleted’ buttons to dynamic segments. Let’s understand how it works.Let’s take example of Live button.Live button is linked to admin/events/list and this list is replaced by ‘live’. So our final route becomes admin/events/live.

<div class=“ui grid stackable”>
 <div class=“row”>
   <div class=“sixteen wide column”>
     {{#tabbed-navigation isNonPointing=true}}
       {{#link-to ‘admin.events.index’ class=’item’}}
         {{t ‘All Events’}}
       {{/link-to}}
       {{#link-to ‘admin.events.list’ ‘live’ class=’item’}}
         {{t ‘All Live’}}
       {{/link-to}}
       {{#link-to ‘admin.events.list’ ‘draft’ class=’item’}}
         {{t ‘All Draft’}}
       {{/link-to}}
       {{#link-to ‘admin.events.list’ ‘past’ class=’item’}}
         {{t ‘All Past’}}
       {{/link-to}}
       {{#link-to ‘admin.events.list’ ‘deleted’ class=’item’}}
         {{t ‘All Deleted’}}
       {{/link-to}}
       {{#link-to ‘admin.events.import’ class=’item’}}
         {{t ‘Import’}}
       {{/link-to}}
     {{/tabbed-navigation}}
   </div>
 </div>
 <div class=“row”>
   {{outlet}}
 </div>
</div>

Additional Resources

Continue Reading

Adding Messaging Route in Ember.js for Admin UI of Open Event Frontend

In this blog post I am explaining how we implement a messages page for admins to keep track of all types of system messages sent to users in the Open Event Frontend. The page shows the types of messages sent out to various users at one place and as well as additional details. It offers configuration options to control which messages get sent out  as emails or notifications or both. And, the page shows when and what message should be sent via notification or mail.
To create the messages page we’ll run the following command

ember generate route admin/messages

This will create

This command will also add  this.route(‘messages’);  to router.js. As admin is the parent route for messages, messages will be nested inside admin in router.js

this.route(‘admin’, function(){
  this.route(‘messages’);
});

Let’s now understand the content of each of above files.

  1. Messages.js

In admin/messages.js we have used titletoken helper to set the title of the tab. Here we have created the message model and added attribute like recipient, trigger, emailMessage, notificationMessage, options and sentAt. We have returned this model from the js file to template.

import Ember from ’ember’;
const { Route } = Ember;
export default Route.extend({
 titleToken() {
   return this.l10n.t(‘Messages’);
 },
 model() {
   return [{
     recipient: [
       {
         name: ‘Organizer’
       },
       {
         name: ‘Speaker’
       }
     ],
     trigger      : ‘Title1’,
     emailMessage : {
       subject : ‘Email subject1’,
       message : ‘Hi, the schedule for session1 has been changed’
     },
     notificationMessage: {
       subject : ‘Notification subject1’,
       message : ‘Hi, the schedule for session1 has been changed’
     },
     option: {
       mail         : true,
       notification : false,
       userControl  : true
     },
     sentAt: new Date()
   }];
 }
});

 

  1. Messages.hbs

In template we have created a table and added classes like stackable and compact. Stackable class makes the table responsive and stacks all the rows for devices with smaller screen size. Compact class helps to show more number of rows at a time.

Then in the template we iterate through the model using a loop. Here we have used other semantic-ui elements like ui ordered list , ui header, ui-checkbox inside the table. For options column we have three attributes denoting how the admin wants to send the message to the user. Here we have grouped three fields using the class grouped fields .In each field we have used semantic’s  ui-checkbox .In check-box we are mutating values on click by using mut helper.

<div class=“grouped fields”>
 <div class=“field”>
   {{ui-checkbox checked=message.option.mail
                 label=(t ‘Mail’)      
                 onChange=(action (mut message.option.mail))}}
 </div>
 <div class=“field”>
   {{ui-checkbox checked=message.option.notification
                 label=(t ‘Notification’)  
               onChange=(action (mut message.option.notification))}}
 </div>

 <div class=“field”>
   {{ui-checkbox checked=message.option.userControl
                label=(t ‘User Control’)  
               onChange=(action (mut message.option.userControl))}}
 </div>
</div>

We are getting date object from js and to convert it into human readable format we have used moment like {{moment-format message.sentAt ‘dddd, DD MMMM YYYY’}}

  1. Messages-test.js
import { test } from ’ember-qunit’;
import moduleFor from ‘open-event-frontend/tests/helpers/unit-helper’;

moduleFor(‘route:admin/messages’, ‘Unit | Route | admin/messages’, []);

test(‘it exists’, function(assert) {
 let route = this.subject();
 assert.ok(route);
});

Using this we can test the existence of our route. These tests are run using the command ember t.

Our message page is ready now. The admin can have a check at all the messages sent to users.

Additional Resources

Continue Reading
  • 1
  • 2
Close Menu