Zum Hauptinhalt springen
Sachbearbeiter-Cockpit

Sachbearbeiter-Cockpit aufsetzen

Ziel dieser Seite: ein lauffähiges Sachbearbeiter-Cockpit mit Anmeldung, App-Übersicht und einer (noch leeren) App.

Voraussetzungen

  • ein React-19-Projekt mit den installierten Peer-Dependencies
  • eine Keycloak-Instanz mit Realm und Client — und ein Benutzerkonto darin, mit dem du dich anmelden kannst
Keycloak brauchst du immer — auch im Mock-Modus

Der Anmelde-Schritt steckt fest in BaseCockpit: solange kein User angemeldet ist, rendert das Sachbearbeiter-Cockpit ausschließlich den Login-Bildschirm — es gibt keinen Schalter, der das überspringt. Der Mock-Modus ersetzt nur die Datenabrufe, nicht die Anmeldung.

Vier Schritte:

  1. authConfig.json bereitstellen
  2. API-Proxy einrichten
  3. Apps definieren und BaseCockpit rendern
  4. Keycloak-Rollen setzen (nur wenn du die Rechteprüfung willst)

1. authConfig.json

Die Anmelde-Einstellungen kommen aus einer JSON-Datei, die das Sachbearbeiter-Cockpit beim Start lädt — sie steckt also nicht im Build und kann pro Umgebung anders sein.

Sie gehört im Projekt nach:

conf/authConfig.json

conf/ wird über publicDir: 'conf' zum öffentlichen Verzeichnis — deshalb landet die Datei unter /authConfig.json, wo das Sachbearbeiter-Cockpit sie erwartet. Die Einstellung steht im vite.config.ts-Block in Schritt 2.

{
"authority": "https://keycloak.example.com/realms/actanova",
"client_id": "cockpit",
"checkPermission": true
}
  • authority und client_id sind Pflicht. Fehlt eines, zeigt das Sachbearbeiter-Cockpit statt der App einen Fehlerbildschirm.
  • checkPermission: true blendet Apps aus, für die dem angemeldeten User die Rolle fehlt (siehe Schritt 4). Ohne das Feld sind alle Apps sichtbar.
  • Alle weiteren Felder (redirect_uri, post_logout_redirect_uri, scope, …) gehen unverändert an die OIDC-Anmeldung. Ohne redirect_uri wird die Startadresse der App verwendet.

Liegt die Datei woanders, gib den Pfad mit: <BaseCockpit configUrl="/config/auth.json" … />.

2. API-Proxy

Alle Services rufen relativ zur Base-URL /api/cockpit/an auf, ergänzt um den Tenant-Pfad, z. B.:

GET /api/cockpit/an/Tenants/Items/<tenant>/Files/Items?$top=100

Diese Base-URL ist in der Library fest verdrahtet und existiert bei ActaNova nicht. Der Proxy hat deshalb zwei Aufgaben: weiterleiten und das Präfix auf den echten API-Pfad /api/main/v1 umschreiben.

Aufruf der App /api/cockpit/an/Tenants/Items/<tenant>/Files/Items?$top=100
^^^^^^^^^^^^^^^ dieses Präfix ersetzt der Proxy …
ActaNova https://actanova.example.com/api/main/v1/Tenants/Items/<tenant>/Files/Items?$top=100
^^^^^^^^^^^^ … durch diesen Pfad

Der Rest des Pfades bleibt unverändert, ebenso der Query-String ($top, $filter, …) — die Services bauen ihre OData-Parameter selbst.

ActaNova braucht außerdem den Authorization-Header mit dem Bearer-Token, den die Library bei jedem Aufruf setzt — der Proxy muss ihn also weiterreichen.

Lokale Entwicklung

Hier übernimmt das der Dev-Server. Bei Vite über die vite.config.ts — dieselbe Datei, die mit publicDir auch die authConfig.json aus Schritt 1 ausliefert:

// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import tailwindcss from '@tailwindcss/vite';

export default defineConfig({
// conf/ statt public/ als öffentliches Verzeichnis:
// conf/authConfig.json wird dadurch als /authConfig.json ausgeliefert.
publicDir: 'conf',
plugins: [react(), tailwindcss()],
server: {
proxy: {
'/api/cockpit/an': {
// Das Ziel enthält den ActaNova-Pfad bereits …
target: 'https://actanova.example.com/api/main/v1',
// … deshalb wird das Präfix der App hier entfernt:
// /api/cockpit/an/Tenants/… → /api/main/v1/Tenants/…
rewrite: (path) => path.replace(/^\/api\/cockpit\/an/, ''),
changeOrigin: true,
// Nur nötig, wenn das Zielsystem ein selbst signiertes Zertifikat hat.
secure: false,
},
},
},
});

Den Authorization-Header schickt der Dev-Server-Proxy von selbst mit; du musst ihn hier nicht setzen.

Betrieb

Die Weiterleitung muss der Webserver machen, der die gebaute App ausliefert. Im Demo-Projekt ist das ein nginx, der /api/cockpit/an/ auf /api/main/v1/ umschreibt und zugleich die authConfig.json ausliefert:

# /api/cockpit/an/<x> -> https://actanova.example.com/api/main/v1/<x>
# nginx ersetzt den gematchten Location-Prefix (/api/cockpit/an/) durch den
# URI-Teil aus proxy_pass (/api/main/v1/). Der Query-String bleibt erhalten.
location /api/cockpit/an/ {
proxy_pass https://actanova.example.com/api/main/v1/;

proxy_http_version 1.1;
proxy_ssl_server_name on; # SNI senden (nötig bei TLS-vHosts)
proxy_set_header Host $proxy_host;
}

# authConfig.json nie cachen (das Sachbearbeiter-Cockpit lädt sie mit `cache: no-store`)
location = /authConfig.json {
add_header Cache-Control "no-store";
try_files $uri =404;
}

Der abschließende Slash in proxy_pass ist entscheidend: nur mit URI-Teil ersetzt nginx das Präfix. Ohne ihn (proxy_pass https://host;) wird der Original-Pfad /api/cockpit/an/… unverändert durchgereicht und ActaNova antwortet mit 404. Den Authorization-Header reicht nginx standardmäßig weiter — überschreibe ihn hier nicht.

Die Ziel-URL gehört nicht ins Image

Oben steht sie fest im Block, damit das Muster erkennbar bleibt. Im Betrieb willst du sie zur Laufzeit setzen, sonst brauchst du pro Umgebung ein eigenes Image. Das Demo-Projekt liefert dafür einen fertigen Pfad: eine NGINX-Vorlage mit ${ACTANOVA_BASE_URL}, die beim Containerstart gerendert wird, plus die als Volume gemountete authConfig.json. Alles dazu — Dockerfile, envsubst, OpenShift — steht unter Deployment.

Erst mal ohne Backend starten

Zum Ausprobieren brauchst du den Proxy nicht — im Mock-Modus liefert die Library Beispieldaten. Keycloak und die authConfig.json aus Schritt 1 brauchst du trotzdem.

3. Apps definieren und BaseCockpit rendern

// src/main.tsx
import { createRoot } from 'react-dom/client';
import {
BaseCockpit,
configureCockpit,
type CockpitApp,
} from '@gentics/cockpit/core';
import '@gentics/cockpit/style.css';
import { AktenApp } from './apps/akten/AktenApp';

configureCockpit({ mock: import.meta.env.VITE_COCKPIT_MOCK === 'true' });

const apps: CockpitApp[] = [
{
id: 'akten',
title: 'Akten',
description: 'Akten suchen, ansehen und zuweisen',
tenant: ['MeinMandant'],
icon: '📁',
component: AktenApp,
},
];

createRoot(document.getElementById('root')!).render(
<BaseCockpit apps={apps} branding={{ name: 'Mein Sachbearbeiter-Cockpit' }} />,
);

CockpitApp

FeldTypBedeutung
idstringEindeutige Kennung. Bestimmt den URL-Abschnitt (/<id>/…) und den Rollennamen (cockpit-<id>).
titlestringTitel auf der Kachel
descriptionstringText auf der Kachel
tenantstring[]ActaNova-Mandanten dieser App. Genau einer → automatisch aktiv; mehrere → der User wählt. Leer → Fehler. Siehe Mandanten.
iconReactNodeOptional: Emoji-String oder Icon-Element
componentAppComponentDie zu rendernde App-UI. Bekommt { app } als Props.

BaseCockpit-Props

PropTypDefaultBedeutung
appsCockpitApp[]Pflicht. Welche Apps das Sachbearbeiter-Cockpit anbietet.
configUrlstring/authConfig.jsonPfad der Auth-Config
router"hash" | "browser" | "none""hash"Wer den Router stellt — siehe Routing
basenamestringNur bei router="browser": Basis-Pfad
branding{ name?, logo? }Name und Logo in der Kopfzeile
OverviewComponentKomponenteKachel-GridSlot: eigene App-Übersicht
HeaderComponentKomponenteBranding + Home + Profil-MenüSlot: eigene Kopfzeile
className / styleWerden auf den äußeren Container gelegt

4. Rollen in Keycloak

Dieser Schritt ist nur nötig, wenn in der authConfig.json aus Schritt 1 checkPermission: true steht. Dann zeigt das Sachbearbeiter-Cockpit einer Person nur die Apps, für die sie eine passende Realm-Rolle hat — der Name ergibt sich aus der App-Id:

App-Id "akten" → Realm-Rolle "cockpit-akten"

In Keycloak sind das zwei Dinge:

  1. Im Realm eine Realm-Rolle cockpit-<appId> pro App anlegen.
  2. Diese Rolle den Benutzern zuweisen — direkt oder über eine Gruppe.

Ohne checkPermission sind alle Apps für jeden angemeldeten User sichtbar. Mehr dazu unter Auth & Rechte.

Weiter

Erste App bauen.