4.8. UI Customization

In this section we cover the possibilities of customizing the user interface. ReportServer provides the following customization options:

  • Specifying the default language
  • Customizing error messages
  • Customizing the theme
  • Customizing the PDF preview
  • Customizing the preview of the dynamic list
  • Customizing the report documentation
  • Adding tabs based on context

Further customization options are available with the use of ReportServer scripts. More information on ReportServer scripts can be found in the Administration Guide.

4.8.1. Specifying the available languages

Any visible text in ReportServer can, in principle, be displayed in any language. The languages available on log-in can be defined in the configuration file /fileserver/etc/main/localization.cf.

	<default>de</default>

The "default" property specifies which language to use as default language.

	<locales>en,fr,de</locales>

The "locales" property specifies a comma-separated list of available languages. If the property is not specified, all supported languages are available for selection.

Remark. The user's selection is stored in a cookie. Thus, the change of the default locale will not override any locale settings done by a user previously.
Remark. A large part of the translations have been generated in a semi-automatic way and are thus far from perfect. If you are a native speaker in one of the languages and would like to contribute please contact us at info@infofabrik.de.
4.8.2. Customization of error messages

Errors can occur due to various reasons.

Typical errors are:

  • an error occurs on the database during report execution, because:
    • a table does not exist,
    • a column does not exist,
    • there is a syntax error in the underlying SQL,
    • the JDBC driver was not installed
    • etc.
  • the connection pool does not have any free connections

An exception is thrown whenever an error occurs in ReportServer. The exception is composed of: a title, error message, and the stack trace.

In /fileserver/etc/main/templates.cf you can customize the error message that is displayed on errors that occur during the export of reports.

When customizing the error message you should give clear instructions as to what the affected employee should do in this case. Usually you would specify the contact address of an administrator or help desk. When customizing, the following substitutions are available: ${headline}, ${msg} and ${stacktrace}.

4.8.3. Customization of theming

In ReportServer Enterprise Edition it is possible to customize the theme via the /fileserver/ui/theme.cf config file. Further information on this can be found in the administration guide.

4.8.4. Preview for PDF reports

In the configuration file /fileserver/etc/ui/previews.cf you can specify how to render PDF previews. The options are ${native} (to use the native browser capabilities), ${jsviewer} (to use a javascript library) or ${image} to not render a PDF at all, but to only render the first page as an image. Also note that users can overwrite the settings within their profiles.

4.8.5. Customizing the preview of the dynamic list

The configuration file /fileserver/etc/ui/previews.cf allows you to configure the preview of the dynamic list. The complete default dynamicList section is shown below:

<dynamicList>
   <defaultColumnWidth>200</defaultColumnWidth>
   <maxColumnWidth>800</maxColumnWidth>
   <pageSize>
      <configs>
         <config minCols = "0" maxCols = "99">50</config>
         <config minCols = "100" maxCols = "249">25</config>
         <config minCols = "250" maxCols = "499">10</config>
         <config minCols = "500" maxCols = "MAX">5</config>
      </configs>
   </pageSize>
</dynamicList>

The defaultColumnWidth setting allows you to configure the global default width of the dynamic list columns. Note that the width of a column may be configured individually in the variant configuration screen. If no width is configured there, the default global width is used. The maxColumnWidth allows you to configure the maximum column width.

The number of rows of the dynamic list preview can be configured in the pageSize section. The number of rows is dependent of the number of columns selected. In the example above, the preview will have:

  • 50 rows if the number of colums is greater equal 0 or less equal 99
  • 25 rows if the number of colums is greater equal 100 or less equal 249
  • 10 rows if the number of colums is greater equal 250 or less equal 499
  • 5 rows if the number of colums is greater equal 500

Note that you can use the MAX keyword for denoting the maximum number of columns.

In case you don't want the number of rows to be dependent on the number of columns, you can configure it as follows:

<pageSize>
   <configs>
      <config minCols = "0" maxCols = "MAX">50</<config>
   </configs>
</pageSize>
4.8.6. Adding contextual tabs

Using the configuration file /fileserver/etc/ui/urlview.cf you can define context aware tabs to be displayed in the TeamSpace or in the administration module (e.g., report management, user management, etc.). This allows you to, for example, display the documentation report directly whenever a user selects a report in the TeamSpace.

The configuration of extra tabs is split into two parts:

	<?xml version="1.0" encoding="UTF-8"?>
	<configuration>
		<adminviews>
		
		</adminviews>
		<objectinfo>
		
		</objectinfo>
	</configuration>

Tabs to be displayed in the administration module go into the adminviews tags and tabs for the TeamSpace are put within the objectinfo tags, respectively. The default configuration does not add any additional tabs for the admin interface, but adds several tabs to the TeamSpace:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
   <adminviews>
    </adminviews>
   <objectinfo>
     <view>
       <types>net.datenwerke.rs.tsreportarea.client.tsreportarea.dto.TsDiskReportReferenceDto</types>
       <name>${msgs['net.datenwerke.rs.core.service.urlview.locale.UrlViewMessages']['info']}</name>
       <url>rs:reportdoc://${reportId}/${id}</url>
     </view>   
     <view>
      <types>net.datenwerke.rs.tsreportarea.client.tsreportarea.dto.TsDiskReportReferenceDto</types>
      <name>${msgs['net.datenwerke.rs.core.service.urlview.locale.UrlViewMessages']['history']}</name>
      <url>rs:revisions://${reportId}</url>
     </view>
     <view>
      <types>net.datenwerke.rs.tsreportarea.client.tsreportarea.dto.TsDiskReportReferenceDto</types>
      <name>${msgs['net.datenwerke.rs.core.service.urlview.locale.UrlViewMessages']['preview']}</name>
      <url>rs:reportpreview://${reportId}</url>
     </view>
   </objectinfo>
</configuration>

Each <view> tag adds a new tab. The <types> tag allows to define for which types of objects the tab is displayed and <name> provides a name (in the above example, the name is localized, but you could also simply write <name>SomeName</name>. Finally, the <url> tag takes a URL that is to be displayed. In the above example we have three custom ReportServer URLs that access custom functionality, the first accesses a documentation report, the second a revisions report and the last one a preview of the report. Via the replacement ${reportId} the id of the object is added to the URL.

In the following we go through the process of adding new tabs step by step.

Adding a new Tab

To add a new tab, you define a <view> tag. For adding a tab to the TeamSpace whenever a report is selected add the following <view> tag within the <objectinfo> section.

	<view>
		<types>
			net.datenwerke.rs.tsreportarea.client.tsreportarea.dto.TsDiskReportReferenceDto
		</types>
		<name>Some Additional Information</name>
		<url>
			reportserver/reportexport?key=someKey&amp;format=html&amp;p_reportId=${reportId}
		</url>
	</view>

The above will execute the report with key "someKey" and pass the given report id as parameter. The following types are available in TeamSpaces.

All Objects in a TeamSpace:

net.datenwerke.rs.tsreportarea.client.tsreportarea.dto.AbstractTsDiskNodeDto

All folders in a TeamSpace:

net.datenwerke.rs.tsreportarea.client.tsreportarea.dto.TsDiskFolderDto

All reports and variants in a TeamSpace:

net.datenwerke.rs.tsreportarea.client.tsreportarea.dto.TsDiskReportReferenceDto

All exported reports which were, for example, created by the scheduler:

net.datenwerke.rs.scheduleasfile.client.scheduleasfile.dto.ExecutedReportFileReferenceDto

The name field defines the tab's name. The url is the address that is displayed. This also allows you to access external addresses, that are then displayed within the tab.

For the report documentation you need to use the special ReportServer URL:

rs:reportdoc://${reportId}/${id}

As you can see there are two placeholders in the above url: ${reportId} and . The following replacements are available

id the object's id
type the object's type
username the current user's username

Note that TeamSpaces do not only contain report references. Thus, the replacement ${id} will contain the id of the reference rather than the id of the referenced report. For this, there is the special replacement called ${reportId} which is only available for report references.

Similarly, to the report documentation in the TeamSpace you can display additional information on any selected object in the administration module. In the administration module you can add tabs to objects in the report management, user management, dadget management, datasource management and fileserver modules. These are configured within the <adminviews> tag.

The following tables describe which types can be used. Note that the type must be used together with the corresponding prefix.

4.8.7. Customization of the Login Page

Following is a quick guide for those who want to completely exchange the ReportServer login page by a custom looking page. In the following we assume that the FileServer contains a folder /resources/public and that the public folder is marked as ''web accessible''. In case not, you can either mark it with the ''web accessible'' checkbox in the UI, or use the following commands:

cd /fileserver/resources
dirmod webaccess public true

As a first step we are going to create a very simple login page, something along the following lines:

<html>
   <head>
      <title>Custom Login</title>
   </head>
   <body>
      My custom login page
      <form method="post" action="">
         <label for="user">username:</label>
         <input type="text" name="user" /> <br/>
         <label for="pw">password:</label>
         <input type="password" name="pw" /></br>
         <input type="submit"/>
      </form>
   </body>
</html>

The page consists of a single form with a username and password field. We store the above as login.html in the folder /resources/public. For this, for example open the terminal (CTRL+ALT+T) and go for

cd /fileserver/resources/public
createTextFile login.html

As the public folder is accessible for anybody (even if the user is not logged in) we can access it via the fileServerAccess servlet. That is, if your ReportServer is accessible via the url reporting.mycompany.com/ then you should see the login page if you go to http://reporting.mycompany.com/reportserver/fileServerAccess?path=/resources/public/login.html.

Now what was missing in the above login page was the intended target of the form. For this we will create a publicly accessible script that handles the form data. As scripts always go into the bin folder we create a folder public beneath the bin folder and add a script customauth.groovy.

cd /fileserver/bin
mkdir public
cd public
createTextFile customauth.groovy
dirmod webaccess public true

To see that everything worked, we use the following simple script which simply outputs the parameter user:

def user = httpRequest.getParameter('user')
return user

Note that you have access to the request and response via the httpRequest and httpResponse variables. What is left is to change the action attribute of the login.html page to point to the script, that is, we need to change it to http://reporting.mycompany.com/reportserver/scriptAccess?path=/bin/public/customauth.groovy.

<html>
   <head>
      <title>Custom Login</title>
   </head>
   <body>
      My custom login page
      <form method="post" action="http://reporting.mycompany.com/ReportServer/reportserver/scriptAccess?path=/bin/public/customauth.groovy">
         <label for="user">username:</label>
         <input type="text" name="user" /> <br/>
         <label for="pw">password:</label>
         <input type="password" name="pw" /></br>
         <input type="submit"/>
      </form>
   </body>
</html>
The Authentication

What we have so far is that we have a custom login page and a script which is the target. What we need is that the script can actually perform the login operation if the provided credentials match. For this we will use the AuthenticatorService located in

net.datenwerke.security.service.authenticator.AuthenticatorService

In the following we check for a username root and a password 123. If found we perform a login for user with id 6 (which in my case is the root user).

import net.datenwerke.security.service.authenticator.AuthenticatorService

def service = GLOBALS.getInstance(AuthenticatorService).get()

def user = httpRequest.getParameter('user')
def pw = httpRequest.getParameter('pw')

if( 'root' == user && '123' == pw ){
  service.setAuthenticated(3) // the id of the root user
  httpResponse.sendRedirect('http://reporting.mycompany.com/ReportServer/ReportServer.html');
  return null  
} else {
  return 'Could not authenticate'
}

If all went well you can now logoff and log in via the custom page http://reporting.mycompany.com/reportserver/fileServerAccess?path=/resources/public/login.html.

Authentication against the ReportServer User Database

In the above example we had a custom script to handle the authentication of a single user. This of course does not scale well and for any real scenario one would like to authenticate against a user database. In the following we show how to authenticate against ReportServer's own user database. For this we can again use the AuthenticatorService which offers a method authenticate that triggers the built-in authentication mechanisms.

ReportServer's built-in authentication is structured in so called pluggable authentication modules (short PAM) which perform the actual authentication. The active PAMs are configured in the reportserver.properties configuration file and usually only a single PAM is active:

rs.authenticator.pams = net.datenwerke.rs.authenticator.service.pam.UserPasswordPAMAuthoritative

The above config loads the UserPasswordPAM module in authoritative mode (more information on the PAMs can be found in the configuration guide). This PAM expects a username and password and then sets of to authenticate against the ReportServer user database. The authoritative flag means that if the UserPasswordPAM cannot authenticate a user that it will then trigger an abort. This can become necessary when you would like to combine multiple different PAMs.

As explained, the AuthenticatorService's authenticate method triggers the internal authentication process. That is, it expects an array of so called AuthTokens (which can be basically anything) and then hands these to the registered PAMs. The PAMs are then asked in turn whether or not they can authenticate a user. For this they use the AuthToken array. The UserPasswordPAM thus expects a username and password as an AuthToken. This is encapsulated in the UserPasswordAuthToken which is located in

net.datenwerke.rs.authenticator.client.login.dto.UserPasswordAuthToken

The authenticate method returns an AuthenticationResult which can be asked whether the authentication succeeded (isAllowed()). In the following script we combine our earlier example with an authentication against ReportServer's user database.

import net.datenwerke.security.service.authenticator.AuthenticatorService 
import net.datenwerke.security.client.login.AuthToken
import net.datenwerke.rs.authenticator.client.login.dto.UserPasswordAuthToken

def service = GLOBALS.getInstance(AuthenticatorService)
 
def user = httpRequest.getParameter('user')
def pw = httpRequest.getParameter('pw')

/* construct authentication tokens */
def token = new UserPasswordAuthToken()
token.username = user
token.password = pw

def result = service.authenticate([token] as AuthToken[])

if(result.isAllowed()){
  httpResponse.sendRedirect('http://reporting.mycompany.com/ReportServer/ReportServer.html') 
  return null
}

return 'Could not authenticate'

Now your custom login page should be fully functional. The logout part, explained next, should be customized as well.

Custom Logout

Currently when a user logs out, the user will be taken back to the original ReportServer login page. In order to change that we need to change the config file located in etc/security/misc.cf within the ReportServer filesystem. If we add

<logout>
   <url>http://reporting.mycompany.com/reportserver/fileServerAccess?path=/resources/public/login.html</url>
</logout>

to the config, then on logout ReportServer will redirect the user to http://reporting.mycompany.com/reportserver/fileServerAccess?path=/resources/public/login.html.

The complete example can found here: https://github.com/infofabrik/reportserver-samples/tree/main/src/net/datenwerke/rs/samples/admin/login/simple.

Advanced example

Based on the previous examples, you can used advanced techniques, together with e.g. JQuery: https://jquery.com/ to further customize your login page. An example of this can be found here: https://github.com/infofabrik/reportserver-samples/tree/main/src/net/datenwerke/rs/samples/admin/login/jquery.

4.8.8. Objects in Report Management

Prefix net.datenwerke.rs.core.client.reportmanager.dto.reports.

Type Description
AbstractReportManagerNodeDto All objects in the report management tree
ReportDto reports
ReportFolderDto folders
4.8.9. Objects in User Management

Prefix net.datenwerke.security.client.usermanager.dto.

Type Description
AbstractUserManagerNodeDto All objects in the user management tree
UserDto users
GroupDto groups
OrganisationalUnitDto organisational units (folders)
4.8.10. Objects in the file server

Prefix net.datenwerke.rs.fileserver.client.fileserver.dto.

Type Description
AbstractFileServerNodeDto All objects in the file server
FileServerFolderDto folders
FileServerFileDto files
4.8.11. Objects in Datasource Management

Prefix net.datenwerke.rs.core.client.datasourcemanager.dto.

Type Description
AbstractDatasourceManagerNodeDto all objects in datasource management
DatasourceFolderDto folders
DatasourceDefinitionDto datasources
4.8.12. Objects in Dadget Management

Prefix net.datenwerke.rs.dashboard.client.dashboard.dto.

Type Description
AbstractDashboardManagerNodeDto All objects in the dashboard tree
DashboardNodeDto dashboards
DashboardFolderDto folders

The following example would display a tab User Information which displays the website at Url http://www.mycompany.com/employee.

	<adminviews>
		<view>
			<types>net.datenwerke.security.client.usermanager.dto.UserDto</types>
			<name>User Information</name>
			<url>http://www.mycompany.com/employee=${id}</url>
		</view>
	</adminviews>