kikwi-monitor: Lib Spring que Embarca o Kikwiflow Monitor
:::tip Status (2026-08-25)
Implementado e verificado ponta a ponta (build automatizado + navegação manual real contra
sample-onboarding-process). Não há pipeline de CI/CD ainda: o build do frontend e a cópia dos estáticos para
este módulo continuam manuais — ver §6. A especificação original (escrita antes da implementação) vive no repo
privado da UI em kikwiflow-pulse-ui/docs/kikwi-monitor-lib-especificacao.md; este documento é o registro do
que foi de fato construído, incluindo dois problemas reais encontrados só na verificação manual em browser
(§4.2).
:::
Este documento descreve o par de módulos novos kikwi-monitor-spring-boot-autoconfigure /
kikwi-monitor-spring-boot-starter, que empacota os artefatos estáticos do Kikwiflow Monitor
(apps/monitor-embedded no repo privado kikwiflow-pulse-ui) dentro de uma lib Spring Boot — qualquer
aplicação que já use kikwi-management-rest-spring-boot-starter pode adicionar essa dependência e ganhar a UI
de monitoramento servida no próprio processo, sem deploy separado.
1. Modelo mental
A lib só serve a SPA estática + um endpoint de configuração. Ela não é um proxy, não fala com o engine e
não valida/repassa token. O browser continua conversando diretamente com kikwi-management-rest (REST +
SSE), exatamente como o monitor-embedded standalone sempre fez — a única mudança é onde os assets
HTML/JS/CSS são hospedados.
Browser ──(1) GET /monitor-ui/**────────────► kikwi-monitor (assets estáticos)
Browser ──(2) GET /monitor-ui/config.json────► kikwi-monitor (aponta pro host do engine)
Browser ──(3) REST + SSE, Authorization: Bearer► kikwi-management-rest (host configurado em (2))
CORS continua responsabilidade de quem hospeda o engine (kikwiflow.rest.cors.allowed-origins, ver
Convenções de Autenticação e Erros) e identidade/permissão
continua em HttpIdentityResolver (kikwi-security-spring-starter) — kikwi-monitor não depende de nenhum
dos dois módulos.
2. Módulos novos (atoxfy)
Seguem a convenção -spring-boot-autoconfigure / -spring-boot-starter já usada por
kikwi-management-rest-spring-boot-autoconfigure/-starter:
kikwi-monitor-spring-boot-autoconfigure(pacoteio.kikwiflow.monitor.autoconfigure):KikwiMonitorAutoConfiguration—@AutoConfiguration,@ConditionalOnWebApplication(SERVLET),@ConditionalOnProperty(prefix="kikwiflow.monitor-ui", name="enabled", havingValue="true", matchIfMissing=true)(KikwiMonitorAutoConfiguration.java:39-44).KikwiMonitorWebMvcConfiguration— registra o resource handler para/monitor-ui/**apontando pro classpathstatic/, com a resolver chain descrita em §3 (KikwiMonitorWebMvcConfiguration.java:35-42).MONITOR_UI_PATH = "/monitor-ui"é uma constante Java, não uma property — obasePathdo Next é fixado em build time nonext.config.tsdo frontend, então uma property Spring "configurável" criaria uma falsa promessa de flexibilidade: mudar só um lado quebraria os assets internos (/monitor-ui/_next/**) silenciosamente.MonitorUiConfigController—GET /monitor-ui/config.json, refleteMonitorUiProperties(MonitorUiConfigController.java:40).MonitorUiRootController— cobre especificamenteGET /monitor-uieGET /monitor-ui/(ver gotcha em §4.1).MonitorUiProperties— record@ConfigurationProperties(prefix = "kikwiflow.monitor-ui").- Sem nenhuma dependência de
kikwi-core,kikwi-management-restoukikwi-security-*.
kikwi-monitor-spring-boot-starter: agregador puro (sem código),kikwi-monitor-spring-boot-autoconfigurespring-boot-starter-web. Ao contrário do starter de management-rest, não depende dekikwi-security-spring-starter—kikwi-monitornão participa de auth.
2.1 Propriedades (kikwiflow.monitor-ui.*)
| Propriedade | Default | Descrição |
|---|---|---|
enabled | true | Liga/desliga a auto-configuration inteira |
api-url | http://localhost:8081 | Host onde kikwi-management-rest está rodando — pode ser o mesmo processo, outro processo, ou outro host; indiferente para esta lib |
oidc-authority | "" | Repassado ao config.json; vazio desliga o fluxo OIDC no frontend |
oidc-client-id | "" | Idem |
oidc-redirect-uri | "" | Idem |
require-auth | true | Se false, o frontend não exige token (nem OIDC nem manual) |
read-only | false | Esconde comandos de escrita na UI (iniciar instância, retry, complete, claim/unclaim) |
GET /monitor-ui/config.json devolve esses 6 últimos campos em camelCase (apiUrl, oidcAuthority,
oidcClientId, oidcRedirectUri, requireAuth, readOnly) — o repo não configura nenhuma
PropertyNamingStrategy custom no Jackson, então o serializador padrão já bate 1:1 com o que o frontend
espera.
3. Mecanismo de serving: resource handler + fallback SPA
KikwiMonitorWebMvcConfiguration registra um ResourceHttpRequestHandler para /monitor-ui/** apontando pro
classpath:/static/, com .resourceChain(true).addResolver(new SpaFallbackResourceResolver()). Preferido a
um @Controller catch-all porque herda de graça content-type/cache/ETag/HEAD do mecanismo padrão do Spring
para recursos estáticos, e não compete por precedência de HandlerMapping com
MonitorUiConfigController.
4. Duas armadilhas reais, achadas só na verificação manual em browser
O smoke-check automatizado (testes de integração + curl) passou limpo em ambos os casos — os dois bugs só
apareceram numa navegação real (clique + F5), o que motivou a decisão de sempre validar esse tipo de
integração com um browser de verdade, não só HTTP client de linha de comando.
4.1 ResourceHttpRequestHandler rejeita resourcePath vazio antes de consultar resolvers
GET /monitor-ui e GET /monitor-ui/ (a raiz, sem nenhum path adicional) devolviam 404, mesmo com o
SpaFallbackResourceResolver registrado. O Spring MVC valida o sub-path (o que sobra depois de casar
/monitor-ui/**) antes de chamar qualquer ResourceResolver — path vazio nunca chega no resolver
customizado. Corrigido com MonitorUiRootController, um @Controller mínimo específico só para esses dois
paths, fazendo forward:/monitor-ui/index.html.
4.2 Next.js output: 'export' gera um HTML por rota, não um shell único
Diferente de uma SPA clássica (Create React App: um index.html genérico que carrega um bundle JS que decide
a rota no cliente), o Next.js com output: 'export' pré-renderiza um arquivo HTML separado por rota:
/monitor → monitor.html, /monitor/advanced → monitor/advanced.html, / → index.html.
A primeira versão de SpaFallbackResourceResolver caía sempre em index.html para qualquer path sem
extensão que não batesse com um asset real. Isso passa limpo num teste automatizado (GET retorna 200,
Content-Type: text/html, corpo não vazio) — mas ao testar de verdade no browser, um F5 em
/monitor-ui/monitor?processId=X renderizava a tela de overview (lista de processos) em vez da tela de
detalhe daquele processo específico: o HTML servido (index.html) correspondia à rota /, não à rota
/monitor que o usuário realmente pediu.
Corrigido com uma ordem de fallback em 3 níveis (SpaFallbackResourceResolver.java:49 em diante):
- Asset real (
super.getResource(...)). <resourcePath>.html— a página pré-renderizada da rota exata pedida (monitor→monitor.html,monitor/advanced→monitor/advanced.html)._not-found.html— a página de not-found que o próprio Next gera no export, para rotas realmente desconhecidas (ex.: um link antigo pra extinta rota dinâmica/[processId]).index.htmlcomo último recurso, se nem_not-found.htmlexistir no bundle.
Os 3 primeiros níveis (mais a raiz de §4.1) têm teste de integração dedicado em
MonitorUiIntegrationTest (ver §5) usando fixtures dummy que espelham essa mesma forma (um index.html, um
monitor.html e um _not-found.html de mentira em src/test/resources/static/).
Lição geral: ao integrar qualquer serving de SPA/export estático, validar com navegação real (clique +
F5 numa rota profunda) — o modo de falha é silencioso (200 OK, HTML plausível, conteúdo errado), invisível
para um smoke-check só de curl/status code.
5. Testes automatizados
Sem precedente de ApplicationContextRunner nem de teste HTTP em nenhum módulo -autoconfigure do repo até
este trabalho — padrão introduzido do zero, alinhado ao estilo Spring Boot padrão:
KikwiMonitorAutoConfigurationTest—WebApplicationContextRunner/ApplicationContextRunner(sem subir servidor): beans registrados por padrão; nenhum bean registrado comkikwiflow.monitor-ui.enabled=false; binding de properties customizadas; auto-configuration recua em contexto não-web.MonitorUiIntegrationTest—@SpringBootTest(webEnvironment = RANDOM_PORT)+TestRestTemplate, com uma@SpringBootApplicationmínima só de teste (support/TestMonitorApplication):config.jsonrefleteMonitorUiProperties; asset estático real devolve content-type correto; raiz (/monitor-ui,/monitor-ui/) serveindex.html; rota conhecida com query string serve o HTML daquela rota específica (não sempreindex.html— regressão do §4.2); rota totalmente desconhecida serve_not-found.html; path fora de/monitor-ui/**não é afetado (404 puro).
Os fixtures de teste (index.html, monitor.html, _not-found.html, assets/app.css — todos dummy) ficam
em src/test/resources/static/; como target/test-classes precede target/classes na resolução de
classpath do Maven, os testes sempre exercitam esses fixtures, independente de os estáticos reais já terem
sido copiados para src/main/resources/static ou não (§6). mvn -pl kikwi-monitor-spring-boot-autoconfigure test roda a qualquer momento, sem depender do build do frontend.
6. Mudanças necessárias no frontend (kikwiflow-pulse-ui)
Três mudanças em apps/monitor-embedded + packages/feature-monitor tornaram o app compatível com
output: 'export' e com um host desconhecido em build time:
- Rota dinâmica → query param:
app/[processId]/page.tsx(rota dinâmica, incompatível com export estático puro semgenerateStaticParams) virouapp/monitor/page.tsx, lendoprocessIdviauseSearchParams()dentro de um<Suspense>. Todorouter.push(`/${id}`)virourouter.push(`/monitor?processId=${id}`)(PulseGroupedCard.tsx,ProcessInstancesTab.tsx). - Runtime config em vez de
NEXT_PUBLIC_*: novo store Zustandpackages/feature-monitor/src/store/useRuntimeConfigStore.ts— não-nulo (configcomeça emDEFAULT_RUNTIME_CONFIG, umisLoadedseparado indica se o fetch real já chegou), hidratado uma vez porapps/monitor-embedded/lib/runtimeConfig.ts(fetch('/monitor-ui/config.json'), com fallback pros defaults se a chamada falhar — importante propnpm devlocal funcionar sem a lib Java no ar) dentro deClientProviders.tsx, que segura a renderização dechildrenaté o config carregar (assimKikwiAuthProvider, que lêoidcAuthorityno primeiro render, já enxerga o valor real). Todoprocess.env.NEXT_PUBLIC_*foi substituído por leitura desse store (via hook em componentes React, ouuseRuntimeConfigStore.getState()em código fora de React comohttpClient.ts/kikwiflow.service.ts). - Export estático:
next.config.tsganhououtput: 'export'+basePath/assetPrefix: '/monitor-ui'(precisa bater exatamente comKikwiMonitorWebMvcConfiguration.MONITOR_UI_PATHdo lado Java — ver §2).
Nenhuma dessas mudanças tem teste automatizado dedicado no frontend (o monorepo não tinha Jest/Vitest/
Playwright configurado antes deste trabalho, e introduzir um framework do zero foi considerado desproporcional
ao escopo "sem CI/CD por enquanto"). Rede de segurança usada: next build já falha se sobrar rota
incompatível com export estático, e um grep -r "NEXT_PUBLIC_" out/ no bundle gerado confirma que nenhum
valor baked-in vazou.
7. Empacotamento (manual, sem CI ainda)
# 1. No repo kikwiflow-pulse-ui: build do frontend
pnpm --filter monitor-embedded build # gera apps/monitor-embedded/out/**
# 2. Copiar os estáticos pro módulo Java (sobrescreve o conteúdo anterior)
cp -r apps/monitor-embedded/out/. \
atoxfy/kikwi-monitor-spring-boot-autoconfigure/src/main/resources/static/
Os estáticos ficam versionados dentro do atoxfy (não há pipeline de build do frontend integrado ao Maven
ainda) — é a única forma de reproduzir o jar sem depender de um passo manual toda vez. Cada release do
monitor-embedded implica repetir esse passo antes de publicar uma nova versão do jar.
8. Como adotar (app hospedeira)
<dependency>
<groupId>io.kikwiflow</groupId>
<artifactId>kikwi-monitor-spring-boot-starter</artifactId>
<version>${kikwiflow.version}</version>
</dependency>
kikwiflow:
monitor-ui:
enabled: true
api-url: "http://localhost:8081" # onde kikwi-management-rest está rodando
require-auth: false # ou true + oidc-* pro fluxo OIDC completo
Verificado de ponta a ponta contra sample-onboarding-process (mvn -pl sample-onboarding-process spring-boot:run -Psample, kikwiflow.monitor-ui.* em application.yml): http://localhost:8081/monitor-ui/
carrega a lista de processos reais, clique num card navega para /monitor?processId=... com o canvas do
processo renderizado, e um F5 nesse deep link continua correto (§4.2) — o browser conversa direto com
http://localhost:8081/kikwiflow/api/v1/**, nunca passando pela lib.
9. Pendências / riscos conhecidos
/monitor-uifixo como constante Java, não property (§2) — mudar de prefixo exige rebuildar o frontend com obasePathcorrespondente, não só mudar config Spring.- Sem pipeline de CI: os estáticos em
src/main/resources/staticpodem ficar desatualizados em relação aomonitor-embeddedse o passo manual do §7 for esquecido antes de um release. - Nomes de properties (
kikwiflow.monitor-ui.*) tratados como definitivos desde a primeira versão publicada, pra evitar breaking change de configuração depois.