Skip to content

Configuration

Stephan Pelikan edited this page Oct 15, 2026 · 17 revisions

Two applications are configured here, and they share no key. The cockpit server reads business-cockpit.* and a few of Spring's own. A workflow module reads vanillabp.cockpit.* and what stands below its own module id. This page holds both.

If you are setting a deployment up, start the application and follow what it prints. Both sides report every gap in one start, each line naming the key which fills it, so the list below is the reference rather than the first thing to read.

  1. What the cockpit server needs
    The database and how it stores a key with a dot, the name, and the account workflow modules report with.
  2. Titles, live updates and e-mails
    What the user interface shows, how often it refreshes, and how a notification is sent.
  3. Where a workflow module writes its keys
    The four levels, and which of them wins.
  4. Where the reports go
    The cockpit server's address or the Kafka brokers, and the rest of that connection.
  5. What a workflow module tells the cockpit
    Where the module answers, how its forms are served, and in which languages it reports.
  6. What one workflow or one user task says for itself
    The keys a single workflow, or a single task of it, may differ from its module in.

What the cockpit server needs

Two values end the start when they are missing, and one more switches off the only way anything gets into the cockpit.

Key What it is
spring.mongodb.uri The database. It has to be a replica set, because the live updates read its change stream. Writing the connection out in parts through spring.mongodb.host counts as well
business-cockpit.title-short The short name of this cockpit, shown in the user interface and used as the realm of the login prompt
bpms-api.username, bpms-api.password The account a workflow module reports with. Without it the BPMS API is switched off and both lists stay empty
bpms-api.realm-name The realm that account is asked for in, BPMS-API in the delivered application
business-cockpit.jwt.hmacSHA256-base64 The key the session token is signed with. Without it a key is generated per start, which logs everybody out on a restart

Note the spelling of the MongoDB key. Spring Boot 4 moved these settings out of spring.data.mongodb, so the key version 1 used is no longer read at all.

Four more shape the database connection, and none of them has to be written:

Key Default What it is
mongodb.use-timeout PT5S How long a write may wait for the replica set to acknowledge it
mongodb.change-stream-max-await-time PT1S How long the server may hold an idle change stream poll
mongodb.use-tls false TLS on the Mongo connection
mongodb.mode MONGODB_4_8 AZURE_COSMOS_MONGO_4_2 for Cosmos DB, which delivers fewer change events than MongoDB does

Two more say how durable a write has to be. The delivered application writes both. A cockpit which writes neither can lose a report and says so on every start:

Key Delivered as What it is
mongodb.write-concern majority How many nodes have to have a write before the cockpit is told it is stored. majority is the value which survives a failover of the primary, and 0 ends the start
mongodb.write-concern-journal true Whether each acknowledging node has the write in its journal before it answers. Only read together with mongodb.write-concern

What each value costs, and why a ?w= in the connection string is not read, is The write concern.

One more key decides what happens to a dot in the business data. A workflow module may report details with a key like order.id, and MongoDB reads a dot in a path as a step into a nested document. So by default such a report is not stored. Over REST it is answered with 422, and the error in the log names the key and this property:

Key Default What it is
business-cockpit.mongodb.map-key-dot-replacement unset What the cockpit stores instead of each dot in a key of details, like ~. It turns back into a dot when the record is read

Setting it has two costs:

  • Search and sorting see the stored form of the key. A column or a filter has to name it as details.order~id for the replacement ~. The path with the dot finds nothing.
  • The way back is not exact. A key which holds the replacement already comes back with a dot in its place.

A value which cannot work stops the start: an empty value, a dot, a $ or the character NUL.

The lists are sorted by the paths a user interface names, a column of business data like details.customer.name for example. Each new combination of paths gets an index in MongoDB, so sorting stays fast on a large collection. One key limits how many of these indexes the cockpit creates:

Key Default What it is
business-cockpit.mongodb.sort-indexes-per-collection 30 How many indexes for sorting the cockpit creates per collection at most, from 0 to 60

Once the limit is reached, a new combination is sorted without an index of its own. The result is the same, it only takes longer on a large collection. The log says so once per combination and names the key. MongoDB takes 64 indexes per collection, so a higher limit has little room left. To free a place, drop a sort index nobody uses any more. Their names start with _sort_. A value which is no whole number from 0 to 60 stops the start.

A deployment also wants spring.threads.virtual.enabled: true. Nothing reports it as missing, because the application runs either way, and without it every open live-update stream and every proxied request holds a platform thread.

Reports may arrive over Kafka instead of over REST. That needs all three topics and an id telling the instances of the cockpit apart:

Key What it is
bpms-api.kafka.topics.user-task The topic user task reports are consumed from
bpms-api.kafka.topics.workflow The topic workflow reports are consumed from
bpms-api.kafka.topics.workflow-module The topic registrations are consumed from
bpms-api.kafka.group-id-suffix What makes this application's consumer group its own
workerId Which instance this is, so every event is consumed once. Written as a start parameter, -DworkerId=local
spring.kafka.bootstrap-servers The brokers, read by Spring itself

All three topics or none. Naming one or two switches the ingestion off and says so at startup. workerId is only asked for once the topics are there, so a deployment reporting over REST never needs it.

Titles, live updates and e-mails

What the user interface shows about the application itself:

Key Default What it is
business-cockpit.title-long none The full title, on the login screen and in the browser tab
business-cockpit.application-version none The version shown in the user interface
business-cockpit.build-timestamp none The build time shown next to it
business-cockpit.application-uri none Where this cockpit is reachable. A notification builds its link back from it
business-cockpit.default-locale de The language a user without one of their own gets
business-cockpit.additional-properties.<name> none Anything else your own user interface wants to read at startup

The session cookie:

Key Default What it is
business-cockpit.jwt.cookie.name bc The cookie the token travels in
business-cockpit.jwt.cookie.expires-duration PT12H How long a login lasts without a request. A request in the second half renews it
business-cockpit.jwt.cookie.max-login-duration P7D How long a login lasts at most, however often it is renewed
business-cockpit.jwt.cookie.secure false Whether the cookie is sent over HTTPS only
business-cockpit.jwt.cookie.same-site none The SameSite attribute
business-cockpit.jwt.cookie.domain, .path / for the path Where the cookie is sent

The live updates of the two lists:

Key Default What it is
business-cockpit.gui-sse.update-interval 500 Milliseconds between two flushes to one browser
business-cockpit.gui-sse.max-items-per-update 100 How many items one flush may carry
business-cockpit.gui-sse.collecting-interval 250 Milliseconds between two looks at what has been collected
business-cockpit.gui-sse.filtering-interval 250 Milliseconds between two decisions which browser a change concerns
business-cockpit.gui-sse.max-known-ids-per-stream 2000 How many ids of tasks and workflows one browser tab is remembered to show

The collector looks at every browser once per collecting-interval. A browser gets a flush only if its last one is longer ago than the update-interval. So the update interval is only checked in steps of the collecting interval. Set it above the update interval and every flush comes late.

A change reaches only the browsers it concerns, and Security says which those are. The cockpit decides that once per filtering-interval, for all changes since the last time. It reads the changed tasks and workflows with one query and decides for every open tab in memory. Without a change it asks nothing. A longer interval saves a little work and makes every change arrive later.

max-known-ids-per-stream limits what one tab costs on the server, about 125 bytes per id. A tab which goes over it loads its lists again. Set it to a positive number; any other value is replaced by the default, and the log says so at startup.

A follow-up date which has passed is a live update too, so the cockpit has to look for those dates:

Key Default What it is
business-cockpit.follow-up.check-rate PT1M How often the cockpit looks for follow-up dates which have passed

A task whose follow-up date passed within the last window is sent to the open browsers as a FOLLOWUP event. Every tick asks for the window since the tick before, so a longer rate means a later reminder and not a lost one. A restart opens a new window, so a date which passed while the cockpit was down is not reported.

Notifications are off until an e-mail sender is configured:

Key Default What it is
business-cockpit.notification.smtp.enabled false true switches the e-mail medium on
business-cockpit.notification.smtp.from none The sender address
spring.mail.host, spring.mail.port none The mail server, read by Spring itself
business-cockpit.notification.interval PT1M How often pending notifications are collected and sent
business-cockpit.notification.max-delivery-attempts 120 Attempts before a notification is given up on, which is two hours at the default interval
business-cockpit.notification.cleanup-interval PT1H How often sent notifications are cleared out
business-cockpit.notification.cleanup-sent-older-than PT48H How long a sent one is kept
business-cockpit.notification.templates.email classpath:templates/notification/email/ Where the e-mail templates are read from

Two files are looked for below that path, email-header for the subject and email-body for the text, each as .ftlh before .ftl. An .ftlh produces an HTML mail, an .ftl a plain one, and a suffix per language works as well, email-body_de.ftl next to email-body.ftl.

The path says where the directory is, and it has to. classpath:templates/mine/ reads the templates from the application, file:/etc/business-cockpit/templates/ from the file system of the machine the cockpit runs on. A value without either prefix ends the boot, and the message repeats your value with both prefixes in front of it, so the fix is to copy the one you meant.

A directory of the file system is what lets an operator change the wording of a mail without a new build. Freemarker reads a changed file again a few seconds later, so the edit takes effect while the cockpit runs. A directory which cannot be read ends the boot as well, rather than turning up as a mail nobody received.

The same rule holds for vanillabp.cockpit.template-loader-path in the workflow module, and for the same reason: the two sides of the cockpit read one value the same way. Version 1 let that key name a directory of the file system with no prefix at all, so an application which upgrades writes file: in front of it.

Notifications are also off per user until that user switches them on, and a user who never logged in is never notified, because the address comes from the record written at login.

A user can switch notifications on for all workflows and make an exception for single workflows. A workflow there is a kind of business case: the workflow module and the BPMN process of the case. A user task of a process which the case started by a call activity counts for the case, because to the user it is one process, only split into several models. The cockpit takes the case from the workflow the task is filed under, so a called process which the workflow module reports as a case of its own gets an exception of its own. While the workflow of a task has not been reported yet, the task counts for its own process.

Where a workflow module writes its keys

Everything from here on is configured in the application running the workflows, not in the cockpit. The keys are the ones version 1 read, in the places version 1 read them:

vanillabp.cockpit.<key>
vanillabp.workflow-modules.<moduleId>.cockpit.<key>
vanillabp.workflow-modules.<moduleId>.workflows.<workflow>.cockpit.<key>
vanillabp.workflow-modules.<moduleId>.workflows.<workflow>.user-tasks.<task>.cockpit.<key>

The most specific value wins per key. What a user task says beats what its workflow says, and what a workflow says beats what its workflow module says. That order is not the cockpit's own. VanillaBP walks the levels of every plug-in the same way. The cockpit hands it these sections and keeps its own key names. The same walk also knows a place per adapter at every level (vanillabp.adapters.<id>.cockpit.<key> and the same below a workflow module). The cockpit does not use it yet, so nothing binds those keys today. On Quarkus no configuration mapping declares them, and a key below vanillabp which no mapping declares ends the startup. Spring Boot does not check for unknown keys, so there such a key is ignored without a word. What holds for the whole application, which is the transport, the template directory and the two switches, is written once at the top and exists nowhere else. There is no global default for what is per module: the modules answer at different addresses, so a value written once for all of them would promise something the cockpit cannot keep. initiator-source is the one key which stands at the top and still means something per module and per workflow, because who answers the initiator is a question about code and not about an address.

A workflow module's own defaults file carries these keys as well, <moduleId>.yaml on the classpath root or below a directory of the module's name, below everything the application itself writes. That is how a module ships a template path only it knows about.

VanillaBP reads nothing below vanillabp.cockpit by itself, so the cockpit adapter binds that part of the tree. On Quarkus that binding is also what lets the application start: a key below vanillabp which nothing declares ends the build there, so a misspelled key is named, together with the key it was nearest to or with what became of it. Spring Boot ignores such a key.

Where the reports go

Exactly one transport is configured. Neither of them, or both, ends the boot with a message naming the keys of both. These stand below vanillabp.cockpit.

Key What it is
user-tasks-enabled false stops user tasks from being reported at all
workflow-list-enabled false stops workflows from being reported at all
template-loader-path The directory the title templates are loaded from, with classpath: or file: in front of it, described on Templates
initiator-source Who answers the initiator of a report: by-application or system. It has no default, and an application which reports to the cockpit and leaves it out does not start. Who caused it says what each value means
rest.base-url The address of the cockpit server's BPMS API, and with it the choice of REST
rest.authentication.basic true sends a basic authentication with every report
rest.authentication.username, rest.authentication.password The account it is sent with, which is the one the cockpit configured as bpms-api.username
rest.authentication.oauth.base-url Where tokens are issued, and with it the choice of the client credentials flow
rest.authentication.oauth.client-id, rest.authentication.oauth.client-secret The client the token is asked for
rest.authentication.oauth.basic true sends the client in an Authorization header rather than in the token request
rest.connect-timeout, rest.read-timeout How long connecting and answering may take, as a number of milliseconds or written with a unit (1500ms, PT20S); 1.5 and 10 seconds by default
rest.proxy.host, rest.proxy.port The HTTP proxy the cockpit server is reached through
rest.proxy.username, rest.proxy.password The account that proxy expects, where it expects one
rest.verify-ssl false switches the certificate check off altogether
rest.ssl-truststore-filename, rest.ssl-truststore-password The PKCS12 file the server's certificate is checked against
kafka.bootstrap-servers The brokers, and with them the choice of Kafka
kafka.topics.user-task The topic user task reports go to
kafka.topics.workflow The topic workflow reports go to
kafka.topics.workflow-module The topic a module registers itself on
kafka.properties.<name> Any further producer setting, handed to the Kafka client unchanged
process-engine-api.remembered-user-tasks How many user tasks the Process-Engine-API half holds between a delivery and its dispatch

The client credentials flow describes its own connection: connect-timeout, read-timeout, verify-ssl, ssl-truststore-filename, ssl-truststore-password and proxy.* below rest.authentication.oauth say how the authorization server is reached and inherit nothing from rest.*. An authorization server behind another proxy stays reachable because of it.

There is no key switching a request log on. The REST client logs through SLF4J, so set the logger io.vanillabp.cockpit.bpms.api.v1_1.BpmsApi to DEBUG to see what is sent.

What a workflow module tells the cockpit

These stand below vanillabp.workflow-modules.<moduleId>.cockpit, because that is what they are about.

Key What it is
workflow-module-uri Where the module answers the cockpit, and where the proxy forwards to
ui-uri-type Which kind of user interface the module brings, a name agreed with your cockpit user interface
ui-uri-path Where the module's user interface is served from, /remoteEntry.js for a federated module and the address of a page for an external one
i18n-languages The languages titles are reported in, written as a list
bpmn-description-language The language the names in the BPMN files are written in; mandatory without templates
group-hierarchy.<group> Which groups a group stands for, written as a list
template-path The segment this module contributes to the template lookup path
initiator-source Who answers this module's initiators, where it differs from the application

A gap is reported at startup, and every gap of the application is reported in one boot. A workflow module which writes none of these keys reports nothing to the cockpit, and the log says so while the application starts.

VanillaBP passes ui-uri-type on and knows no names for it. Which names there are is agreed between your workflow modules and your cockpit user interface, and it is written down with that user interface. Whether a page is loaded or opened is decided by ui-uri-path. A path which starts with http:// or https:// is an address of your own and is opened as it is, which is the way for a system the cockpit cannot load anything from. Every other path is loaded from the workflow module through the proxy. A details provider may replace the path per task or per case, so that the user lands where that one task is handled. User task forms and status sites says what each of the two means for a module written in another framework.

What one workflow or one user task says for itself

Four of the module's keys mean something for a single workflow as well, and one of them for a single user task. Both levels have a cockpit section of their own.

vanillabp:
  workflow-modules:
    taxi-ride:
      cockpit:
        i18n-languages:
          - en
          - de
        bpmn-description-language: en
        template-path: rides
      workflows:
        TaxiRide:
          cockpit:
            i18n-languages:
              - fr
            bpmn-description-language: fr
            template-path: taxi
          user-tasks:
            approve:
              cockpit:
                template-path: approval
Key What it is
workflows.<workflow>.cockpit.i18n-languages The languages this workflow's titles are reported in
workflows.<workflow>.cockpit.bpmn-description-language The language this workflow's BPMN names are written in
workflows.<workflow>.cockpit.template-path The segment this workflow contributes to the template lookup path
workflows.<workflow>.cockpit.initiator-source Who answers this workflow's initiators, where it differs from its module
workflows.<workflow>.user-tasks.<task>.cockpit.template-path The segment this user task contributes

The module still has to name i18n-languages and bpmn-description-language. A workflow which says nothing is a workflow reported the way its module is.

What your engine adds to this, and what it reads out of vanillabp.adapters.<id>.*, is on the Configuration page of that BPMS' own wiki.

Clone this wiki locally