Pular para o conteúdo principal
Página não listada
Esta página não está listada. Mecanismos de busca não irão indexá-la, e somente usuários que possuam o link direto poderão acessá-la

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 (pacote io.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 classpath static/, com a resolver chain descrita em §3 (KikwiMonitorWebMvcConfiguration.java:35-42). MONITOR_UI_PATH = "/monitor-ui" é uma constante Java, não uma property — o basePath do Next é fixado em build time no next.config.ts do 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.
    • MonitorUiConfigControllerGET /monitor-ui/config.json, reflete MonitorUiProperties (MonitorUiConfigController.java:40).
    • MonitorUiRootController — cobre especificamente GET /monitor-ui e GET /monitor-ui/ (ver gotcha em §4.1).
    • MonitorUiProperties — record @ConfigurationProperties(prefix = "kikwiflow.monitor-ui").
    • Sem nenhuma dependência de kikwi-core, kikwi-management-rest ou kikwi-security-*.
  • kikwi-monitor-spring-boot-starter: agregador puro (sem código), kikwi-monitor-spring-boot-autoconfigure
    • spring-boot-starter-web. Ao contrário do starter de management-rest, não depende de kikwi-security-spring-starterkikwi-monitor não participa de auth.

2.1 Propriedades (kikwiflow.monitor-ui.*)

PropriedadeDefaultDescrição
enabledtrueLiga/desliga a auto-configuration inteira
api-urlhttp://localhost:8081Host 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-authtrueSe false, o frontend não exige token (nem OIDC nem manual)
read-onlyfalseEsconde 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: /monitormonitor.html, /monitor/advancedmonitor/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):

  1. Asset real (super.getResource(...)).
  2. <resourcePath>.html — a página pré-renderizada da rota exata pedida (monitormonitor.html, monitor/advancedmonitor/advanced.html).
  3. _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]).
  4. index.html como último recurso, se nem _not-found.html existir 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:

  • KikwiMonitorAutoConfigurationTestWebApplicationContextRunner/ApplicationContextRunner (sem subir servidor): beans registrados por padrão; nenhum bean registrado com kikwiflow.monitor-ui.enabled=false; binding de properties customizadas; auto-configuration recua em contexto não-web.
  • MonitorUiIntegrationTest@SpringBootTest(webEnvironment = RANDOM_PORT) + TestRestTemplate, com uma @SpringBootApplication mínima só de teste (support/TestMonitorApplication): config.json reflete MonitorUiProperties; asset estático real devolve content-type correto; raiz (/monitor-ui, /monitor-ui/) serve index.html; rota conhecida com query string serve o HTML daquela rota específica (não sempre index.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:

  1. Rota dinâmica → query param: app/[processId]/page.tsx (rota dinâmica, incompatível com export estático puro sem generateStaticParams) virou app/monitor/page.tsx, lendo processId via useSearchParams() dentro de um <Suspense>. Todo router.push(`/${id}`) virou router.push(`/monitor?processId=${id}`) (PulseGroupedCard.tsx, ProcessInstancesTab.tsx).
  2. Runtime config em vez de NEXT_PUBLIC_*: novo store Zustand packages/feature-monitor/src/store/useRuntimeConfigStore.tsnão-nulo (config começa em DEFAULT_RUNTIME_CONFIG, um isLoaded separado indica se o fetch real já chegou), hidratado uma vez por apps/monitor-embedded/lib/runtimeConfig.ts (fetch('/monitor-ui/config.json'), com fallback pros defaults se a chamada falhar — importante pro pnpm dev local funcionar sem a lib Java no ar) dentro de ClientProviders.tsx, que segura a renderização de children até o config carregar (assim KikwiAuthProvider, que lê oidcAuthority no primeiro render, já enxerga o valor real). Todo process.env.NEXT_PUBLIC_* foi substituído por leitura desse store (via hook em componentes React, ou useRuntimeConfigStore.getState() em código fora de React como httpClient.ts/kikwiflow.service.ts).
  3. Export estático: next.config.ts ganhou output: 'export' + basePath/assetPrefix: '/monitor-ui' (precisa bater exatamente com KikwiMonitorWebMvcConfiguration.MONITOR_UI_PATH do 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-ui fixo como constante Java, não property (§2) — mudar de prefixo exige rebuildar o frontend com o basePath correspondente, não só mudar config Spring.
  • Sem pipeline de CI: os estáticos em src/main/resources/static podem ficar desatualizados em relação ao monitor-embedded se 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.