Skip to content

Customizing the user interface

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

The cockpit came with a user interface written in React. It is an example of a cockpit user interface and not the only one there can be, and for a first deployment it is usually enough.

Since 0.9.0 the jars of the cockpit do not carry it any more, and its sources are gone from the cockpit repository. They are in its Git history, under business-cockpit/src/main/webapp. Releases says what that means for a derived cockpit. Change the theme and you have your colours and your fonts; that is one file.

Beyond that there are two ways to go. You copy the React application and maintain the copy, or you build a user interface of your own against a written description of what one has to do. For a long time only the first way existed, so copying was the honest answer. The description exists now, and this page says what each way gets you.

  1. Changing the look
    The theme object, and the colour names the lists insist on.
  2. The packages
    Which of the NPM packages a cockpit user interface uses and which a workflow module does.
  3. Copying the application
    What a copy has to keep working, and what it costs.
  4. What a user interface has to answer
    The three boundaries it sits on, whatever it is written in.
  5. The description it is built from
    Where it lives, what it covers, and why it is not in the wiki.
  6. What is still missing
    What the description does not answer yet, and the one thing React stays in charge of.

Changing the look

The delivered user interface is built on Grommet, and its theme is one exported object, theme from @vanillabp/bc-shared. It is a Grommet ThemeType whose parts are written with styled-components, and it sets colours, the font, and the look of tables, buttons, form fields and layers.

The application applies it in one place:

<Grommet theme={theme} full>

Replace that object with one of your own and everything follows. There is no configuration key for it, because a theme is code.

A few colour names are not decoration. The list components pick a colour by the state of a row, so a theme of your own has to define list-default, list-new, list-updated, list-ended and list-removed_from_list, their list-text-* counterparts, and list-refresh, link, placeholder and accent-1 through accent-4. Leave one out and the rows in that state are drawn without a colour.

The font is loaded separately, through @fontsource/roboto imports in the application's bootstrap file, so a different font is a different import there.

The packages

Four packages were published, and which one you need depends on which side you are on. The versions on npmjs.com stay there, but the cockpit repository releases no new ones.

Package Who uses it
@vanillabp/bc-official-gui-client A cockpit user interface. The client generated from the OpenAPI specification of the GUI API
@vanillabp/bc-types Both. The TypeScript types: Column, ListItem, Person, Translatable, and the two enriched objects BcUserTask and BcWorkflow
@vanillabp/bc-shared Both. The components and types a workflow module writes its forms against, the default list cell, and the theme
@vanillabp/bc-ui A cockpit user interface. The lists, the pages, and the part which loads a workflow module's federated components

A workflow module needs the middle two and never bc-ui, which is what keeps a form from pulling a whole cockpit in. User task forms and status sites is that side.

Copying the application

The React application was the library's src/main/webapp. Take it from the Git history of the cockpit repository, copy it into your own application, change what you want, and let your build produce the static files your Spring Boot application serves.

Two things a copy has to keep. It has to set window.i18n to an i18next instance, because the components of @vanillabp/bc-ui register their own texts on it and register nothing where it is unset. And it has to keep the module federation configuration of the delivered application: the cockpit shares react, react-dom, react-router-dom, grommet, i18next and react-i18next with the modules it loads, and a federated form built against those versions fails when the host no longer offers them.

What a copy costs is the usual thing. Every later release of the cockpit changes the original, and your copy does not follow along. Nothing tells you when the two have drifted apart, because what the cockpit expects of a user interface is not a signature your compiler checks.

What a user interface has to answer

A cockpit user interface sits on three boundaries, and they are the same three whatever it is written in.

flowchart TB
    U["Your user interface<br>lists, pages, the frame"]

    subgraph C["Business Cockpit server"]
        STATIC["/static and /assets<br>serve your built files"]
        GUI["GUI API /gui/api/v1<br>lists, search, actions, updates"]
        PROXY["Proxy /wm/&lt;module&gt;<br>to the workflow modules"]
    end

    W["Workflow module<br>UserTaskForm, WorkflowPage,<br>UserTaskList, WorkflowList"]

    STATIC -- "the browser loads it from here" --> U
    U -- "everything it knows" --> GUI
    U -- "loads a part, through" --> PROXY
    PROXY --> W
    W -. "React components, inside your page" .-> U
Loading

The GUI API is everything it knows. There is no second source: no access to a workflow module's database, no business logic of its own, and no login page, because the cockpit authenticates every request and answers with a cookie. What a user may see is decided on the server, and what the server does not send does not exist for the browser. Security is that half.

The parts of a workflow module are loaded into its pages. A task opens a form, a case opens a status site, and the two lists ask each module for its columns and its cells. User task forms and status sites describes that contract from the module's side. The user interface is the host of that contract.

The cockpit application serves it. The built files go below /static or /assets, the only two prefixes a browser reaches without a login, and application.spa-default-file names the document which answers a deep link. Building a custom Business Cockpit is the application around that.

One more thing belongs here although it is not a boundary: whoever writes forms for a workflow module needs a workbench, and that is the dev shell rather than a running cockpit. See Developing UI components locally.

The description it is built from

Those three boundaries are written down, in enough detail to build a user interface without reading the React sources: skills/business-cockpit-user-interface, in this repository.

It is written in the skill format, so an agent can load it, generate the user interface your organisation wants in your framework and your component library, and maintain it afterwards. A person reads it just as well. It covers:

  • the GUI API, with paging, sorting, filtering by reported business data, and the update stream
  • the module federation contract
  • which types are the contract and which are only a convenience
  • the behaviour a user expects of a list
  • the dev shell
  • the places where the obvious thing turns out to be the wrong thing

It lives in this repository, not in this wiki and not in vanillabp/skills with the other skills, for one reason: it describes this repository's own interfaces. Next to the code, a change which makes it wrong shows up in the same pull request as the change itself. Anywhere else it would go stale on a schedule of its own, and nobody would notice until somebody built against it. Where the description and the code disagree, the code is right and the description is the defect.

The skill named business-cockpit in vanillabp/skills points here for everything about the user interface rather than repeating any of it.

One note for now: the text is German until it has been reviewed, and it will be translated afterwards. Everything else, this wiki included, stays English.

What is still missing

Two things, and both are worth knowing before you plan around them.

The forms of the workflow modules are React, whichever framework you choose. They are published as React libraries, and nothing packages them as web components, whatever an older README may have said. So a user interface in another framework builds its own lists and its own pages, and it still brings React along and shares it, because that is what a loaded form runs in. Only a module whose ui-uri-path is a whole address, starting with http:// or https://, gets around React, because it is opened in a window of its own. The cockpit reads no value of ui-uri-type, so a user interface of your own agrees on the names with the modules it loads.

Nothing checks the description against a running cockpit. A test which drives a generated user interface against the GUI API is the only way to notice that the two have drifted apart, and it does not exist yet. Until it does, the description is kept right by hand. Where nobody could say what the intended behaviour was, it names the gap and does not invent an answer.

Clone this wiki locally