Skip to content

Commit

Permalink
feat(webapp): allow customizing the UI (#269)
Browse files Browse the repository at this point in the history
* implement white label feature for changing logo, colors, and custom javascript
  • Loading branch information
nitram509 authored Jul 30, 2021
1 parent acd2336 commit b0967f6
Show file tree
Hide file tree
Showing 10 changed files with 178 additions and 41 deletions.
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,19 @@ server:

It is then available under http://localhost:8082/monitor.

#### change look & feel, a.k.a. white-labeling

You can change the look & feel of Zeebe Simple Monitor to match your own logo
or alter the background color like for example: test=green, prod=red.
These options can be adopted.

```
- white-label.logo.path=img/logo.png
- white-label.custom.title=Zeebe Simple Monitor
- white-label.custom.css.path=css/custom.css
- white-label.custom.js.path=js/custom.js
```

#### Change the Database

For example, using PostgreSQL:
Expand Down
5 changes: 5 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,11 @@

<!-- testing -->

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
Expand Down
44 changes: 34 additions & 10 deletions src/main/java/io/zeebe/monitor/rest/ViewController.java
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ public class ViewController {
private static final List<String> JOB_COMPLETED_INTENTS = Arrays.asList("completed", "canceled");

private final String basePath;
private final String logoPath;
private final String customCssPath;
private final String customJsPath;
private final String customTitle;

@Autowired private ProcessRepository processRepository;
@Autowired private ProcessInstanceRepository processInstanceRepository;
Expand All @@ -81,13 +85,21 @@ public class ViewController {
@Autowired private VariableRepository variableRepository;
@Autowired private ErrorRepository errorRepository;

public ViewController(@Value("${server.servlet.context-path}") final String basePath) {
public ViewController(@Value("${server.servlet.context-path}") final String basePath,
@Value("${white-label.logo.path}") final String logoPath,
@Value("${white-label.custom.title}") final String customTitle,
@Value("${white-label.custom.css.path}") final String customCssPath,
@Value("${white-label.custom.js.path}") final String customJsPath){
this.basePath = basePath.endsWith("/") ? basePath : basePath + "/";
this.logoPath = logoPath;
this.customTitle = customTitle;
this.customCssPath = customCssPath;
this.customJsPath = customJsPath;
}

@GetMapping("/")
public String index(final Map<String, Object> model, final Pageable pageable) {
addContextPathToModel(model);
addCommonVariablesToModel(model);
return processList(model, pageable);
}

Expand All @@ -105,7 +117,7 @@ public String processList(final Map<String, Object> model, final Pageable pageab
model.put("processes", processes);
model.put("count", count);

addContextPathToModel(model);
addCommonVariablesToModel(model);
addPaginationToModel(model, pageable, count);

return "process-list-view";
Expand Down Expand Up @@ -168,7 +180,7 @@ public String processDetail(
final var bpmn = Bpmn.readModelFromStream(resourceAsStream);
model.put("instance.bpmnElementInfos", getBpmnElementInfos(bpmn));

addContextPathToModel(model);
addCommonVariablesToModel(model);
addPaginationToModel(model, pageable, count);

return "process-detail-view";
Expand Down Expand Up @@ -244,7 +256,7 @@ public String instanceList(final Map<String, Object> model, final Pageable pagea
model.put("instances", instances);
model.put("count", count);

addContextPathToModel(model);
addCommonVariablesToModel(model);
addPaginationToModel(model, pageable, count);

return "instance-list-view";
Expand All @@ -266,7 +278,7 @@ public String instanceDetail(

model.put("instance", toInstanceDto(instance));

addContextPathToModel(model);
addCommonVariablesToModel(model);

return "instance-detail-view";
}
Expand Down Expand Up @@ -668,7 +680,7 @@ public String incidentList(final Map<String, Object> model, final Pageable pagea
model.put("incidents", incidents);
model.put("count", count);

addContextPathToModel(model);
addCommonVariablesToModel(model);
addPaginationToModel(model, pageable, count);

return "incident-list-view";
Expand Down Expand Up @@ -715,7 +727,7 @@ public String jobList(final Map<String, Object> model, final Pageable pageable)
model.put("jobs", dtos);
model.put("count", count);

addContextPathToModel(model);
addCommonVariablesToModel(model);
addPaginationToModel(model, pageable, count);

return "job-list-view";
Expand Down Expand Up @@ -750,7 +762,7 @@ public String messageList(final Map<String, Object> model, final Pageable pageab
model.put("messages", dtos);
model.put("count", count);

addContextPathToModel(model);
addCommonVariablesToModel(model);
addPaginationToModel(model, pageable, count);

return "message-list-view";
Expand All @@ -770,7 +782,7 @@ public String errorList(final Map<String, Object> model, final Pageable pageable
model.put("errors", dtos);
model.put("count", count);

addContextPathToModel(model);
addCommonVariablesToModel(model);
addPaginationToModel(model, pageable, count);

return "error-list-view";
Expand Down Expand Up @@ -846,10 +858,22 @@ private String getProcessResource(final ProcessEntity process) {
return resource.replaceAll("`", "\"");
}

private void addCommonVariablesToModel(final Map<String, Object> model) {
addContextPathToModel(model);
addWhitelabelingOptionsToModel(model);
}

private void addContextPathToModel(final Map<String, Object> model) {
model.put("context-path", basePath);
}

private void addWhitelabelingOptionsToModel(final Map<String, Object> model) {
model.put("logo-path", logoPath);
model.put("custom-css-path", customCssPath);
model.put("custom-js-path", customJsPath);
model.put("custom-title", customTitle);
}

private void addPaginationToModel(
final Map<String, Object> model, final Pageable pageable, final long count) {

Expand Down
6 changes: 6 additions & 0 deletions src/main/resources/application.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,9 @@ logging:
io.zeebe: INFO
io.zeebe.monitor: DEBUG
com.hazelcast: WARN

white-label:
logo.path: img/logo.png
custom.title: Zeebe Simple Monitor
custom.css.path: css/custom.css
custom.js.path: js/custom.js
2 changes: 2 additions & 0 deletions src/main/resources/public/css/custom.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/* empty by default */
/* when deployed via Docker or Helm, this file can be replaced with custom CSS */
2 changes: 2 additions & 0 deletions src/main/resources/public/js/custom.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/* empty by default */
/* when deployed via Docker or Helm, this file can be replaced with custom JS */
1 change: 1 addition & 0 deletions src/main/resources/templates/layout/footer.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

<script type="text/javascript" src="{{context-path}}js/bpmn-navigated-viewer.production.min.js"></script>
<script src="{{context-path}}js/app.js"></script>
<script src="{{context-path}}{{custom-js-path}}"></script>

</body>
</html>
7 changes: 4 additions & 3 deletions src/main/resources/templates/layout/header.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<meta http-equiv=X-UA-Compatible content="IE=edge">
<meta name=viewport content="width=device-width,initial-scale=1">

<title> Zeebe Simple Monitor </title>
<title>{{custom-title}}</title>

<!-- bpmn-js viewer -->
<link type="text/css" rel="stylesheet" href="{{context-path}}css/modeler.css"/>
Expand All @@ -14,6 +14,7 @@
<link rel="stylesheet" type="text/css" href="{{context-path}}css/bootstrap.min.css"/>

<link rel="stylesheet" type="text/css" href="{{context-path}}css/app.css"/>
<link rel="stylesheet" type="text/css" href="{{context-path}}{{custom-css-path}}"/>

<link rel=icon href="{{context-path}}img/favicon.ico">

Expand All @@ -23,8 +24,8 @@

<nav class="navbar navbar-expand-md navbar-light bg-white mb-2 top-nav-border">
<a class="navbar-brand" href="{{context-path}}">
<img src="{{context-path}}img/logo.png" width="40" alt="Logo">
<span class="h3">Zeebe Simple Monitor</span>
<img src="{{context-path}}{{logo-path}}" height="36" alt="Logo">
<span id="header-title" class="h3">{{custom-title}}</span>
</a>

<div class="collapse navbar-collapse">
Expand Down
28 changes: 0 additions & 28 deletions src/test/java/io/zeebe/monitor/NoTestsYetTest.java

This file was deleted.

111 changes: 111 additions & 0 deletions src/test/java/io/zeebe/monitor/rest/ViewControllerTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package io.zeebe.monitor.rest;

import io.zeebe.monitor.ZeebeSimpleMonitorApp;
import io.zeebe.monitor.repository.*;
import io.zeebe.monitor.zeebe.ZeebeHazelcastService;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.junit4.SpringRunner;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;

@RunWith(SpringRunner.class)
@AutoConfigureMockMvc
@SpringBootTest(classes = ZeebeSimpleMonitorApp.class,
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
properties = {
"white-label.logo.path: img/test-logo.png",
"white-label.custom.title: Test Zeebe Simple Monitor",
"white-label.custom.css.path: css/test-custom.css",
"white-label.custom.js.path: js/test-custom.js",
})
public class ViewControllerTest {

@LocalServerPort
private int port;

@Autowired
private ViewController controller;

@Autowired
private TestRestTemplate restTemplate;

@MockBean
private HazelcastConfigRepository hazelcastConfigRepository;
@MockBean
private ZeebeHazelcastService zeebeHazelcastService;
@MockBean
private ProcessRepository processRepository;
@MockBean
private ProcessInstanceRepository processInstanceRepository;
@MockBean
private ElementInstanceRepository activityInstanceRepository;
@MockBean
private IncidentRepository incidentRepository;
@MockBean
private JobRepository jobRepository;
@MockBean
private MessageRepository messageRepository;
@MockBean
private MessageSubscriptionRepository messageSubscriptionRepository;
@MockBean
private TimerRepository timerRepository;
@MockBean
private VariableRepository variableRepository;
@MockBean
private ErrorRepository errorRepository;

@Before
public void setUp() throws Exception {
when(processRepository.findAll(any(Pageable.class))).thenReturn(Page.empty());
}

@Test
public void index_page_successfully_responded() {
ResponseEntity<String> entity = restTemplate.getForEntity("/", String.class);

assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
}

@Test
public void index_page_contains_whitelabeling_title() {
ResponseEntity<String> entity = restTemplate.getForEntity("/", String.class);

assertThat(entity.getBody()).contains("Test Zeebe Simple Monitor");
}

@Test
public void index_page_contains_whitelabeling_logo() {
ResponseEntity<String> entity = restTemplate.getForEntity("/", String.class);

assertThat(entity.getBody()).contains("<img src=\"/img/test-logo.png\"");
}

@Test
public void index_page_contains_whitelabeling_js() {
ResponseEntity<String> entity = restTemplate.getForEntity("/", String.class);

assertThat(entity.getBody()).contains("<script src=\"/js/test-custom.js\"></script>");
}

@Test
public void index_page_contains_whitelabeling_css() {
ResponseEntity<String> entity = restTemplate.getForEntity("/", String.class);

assertThat(entity.getBody()).contains("<link rel=\"stylesheet\" type=\"text/css\" href=\"/css/test-custom.css\"/>");
}

}

0 comments on commit b0967f6

Please sign in to comment.