Building and installing a UI

The build command does not change once you add a UI.

cd logpresso-sample-app
mvn clean package

The frontend build is part of the Maven build, so you do not run it separately, and you do not need Node.js installed beforehand.

The build process

The build runs in this order.

generate-resources
  1. download node v20.18.0, npm 10.9.0   → src/main/ui/node/
  2. npm install                           → src/main/ui/node_modules/
  3. npm run build (tsc -b && vite build)  → src/main/resources/WEB-INF/
process-resources
  4. copy src/main/resources               → target/classes/
compile
  5. compile Java sources                  → target/classes/
package
  6. build the OSGi bundle                 → target/*.jar
  7. instrument bytecode with iPOJO
  8. copy the .app file                    → target/*.app

The frontend belongs to generate-resources because of ordering. Vite writes files into src/main/resources/WEB-INF, and process-resources then copies src/main/resources into the bundle. Run the frontend build any later and the files you just produced never make it into the bundle.

Below is the output of the frontend build step.

[INFO] --- frontend:1.15.0:npm (npm-build) @ logpresso-sample-app ---
[INFO] Running 'npm run build' in D:\github\logpresso-app-examples\logpresso-sample-app\src\main\ui
[INFO] > logpresso-sample-app-ui@1.1.2608.0 build
[INFO] > tsc -b && vite build
[INFO] vite v6.4.3 building for production...
[INFO] transforming...
[INFO] 37 modules transformed.
[INFO] rendering chunks...
[INFO] computing gzip size...
[INFO] ../resources/WEB-INF/index.html                   0.42 kB  gzip:  0.27 kB
[INFO] ../resources/WEB-INF/assets/index-DpGY6jAl.css   10.88 kB  gzip:  3.17 kB
[INFO] ../resources/WEB-INF/assets/index-B5j1U51f.js   208.08 kB  gzip: 66.03 kB
[INFO] built in 1.43s

Resources are then copied into the bundle and the Java sources are compiled. The five resources are the manifest, the logo, and the three WEB-INF files just produced.

[INFO] --- resources:3.5.0:resources (default-resources) @ logpresso-sample-app ---
[INFO] Copying 5 resources from src\main\resources to target\classes
[INFO] --- compiler:3.8.1:compile (default-compile) @ logpresso-sample-app ---
[INFO] Compiling 12 source files to D:\github\logpresso-app-examples\logpresso-sample-app\target\classes

Finally the bundle is built, iPOJO instruments the bytecode, and the app file is copied.

[INFO] --- bundle:5.1.4:bundle (default-bundle) @ logpresso-sample-app ---
[INFO] Building bundle: D:\github\...\target\logpresso-sample-app-1.1.2608.0.jar
[INFO] --- ipojo:1.12.1.asm8:ipojo-bundle (default) @ logpresso-sample-app ---
[INFO] Bundle manipulation - SUCCESS
[INFO] --- antrun:3.1.0:run (default) @ logpresso-sample-app ---
[INFO]      [copy] Copying 1 file to D:\github\...\target
[INFO] BUILD SUCCESS

Build output

Two files appear in the target directory.

logpresso-sample-app-1.1.2608.0.jar     103,289 bytes
logpresso-sample-app-1.1.2608.0.app     103,289 bytes

Their sizes match, because the app file is the bundle JAR copied under a different name. That is, an app file is not a separate format — it is a ZIP file with a different extension.

The bundle contains these UI files.

WEB-INF/index.html
WEB-INF/assets/index-DpGY6jAl.css
WEB-INF/assets/index-B5j1U51f.js
sonar_app.json
sonar_app_logo.png

Open the built WEB-INF/index.html and you can see the base path from the Vite configuration written into the script addresses.

<script type="module" crossorigin src="/app/sample/assets/index-B5j1U51f.js"></script>
<link rel="stylesheet" crossorigin href="/app/sample/assets/index-DpGY6jAl.css">

Get into the habit of checking this file after a build. If the path does not start with /app/{app_code}/, the screen comes up blank after installation, and tracking that down from the browser takes time.

Installation

Upload the .app file under Apps in the web console. Once installed, the items you registered in the manifest appear in the app menu.

Register a connect profile after installing the app, or the screen has no data to display. The sample app's screen reads the endpoint and API key from a connect profile of type Sample. See Install Sample App for how to register one.

Iterating on a screen quickly

Repeating a full build and installation for every line of CSS slows you down considerably. Run the UI on its own and the screen updates as soon as you save a file.

cd logpresso-sample-app/src/main/ui
npm install
npm run dev

This requires Node.js 20 installed locally. You can also use the executable in the src/main/ui/node directory that Maven downloaded.

The dev server serves the screen at http://localhost:6100 and forwards /api/sonar/sample requests through its proxy, so you can query a real Logpresso Sonar instance.

The dev server needs authentication handled separately. Once the app is installed, its screen is served from the same address as the web console and inherits the login session. The dev server has a different address, so the browser does not send the session cookie. Do nothing about it and every API call fails with 401 Unauthorized.

The Logpresso Sonar REST API supports API key authentication, so configure the proxy to attach an authorization header. The Logpresso address and the API key come from an .env.local file.

export default defineConfig(({ mode }) => {
  const env = loadEnv(mode, process.cwd(), '');

  return {
    server: {
      port: 6100,
      proxy: {
        '/api/sonar/sample': {
          target: env.SONAR_URL || 'https://localhost',
          changeOrigin: true,
          secure: false,
          configure: proxy => {
            proxy.on('proxyReq', proxyReq => {
              if (env.SONAR_API_KEY)
                proxyReq.setHeader('Authorization', `Bearer ${env.SONAR_API_KEY}`);
            });
          },
        },
      },
    },
  };
});

There is a reason the third argument to loadEnv is an empty string. By default Vite reads only variables that start with VITE_ and includes them in the client code. The values needed here configure the dev server and must not end up in the screen, so the prefix restriction is removed and the values are read straight from the configuration file.

Create an .env.local file to set the values. The sample app ships an .env.example you can copy.

cmd> cd logpresso-sample-app\src\main\ui
cmd> copy .env.example .env.local
SONAR_URL=https://192.0.2.10
SONAR_API_KEY=your-issued-api-key

Do not write the API key directly into vite.config.ts. The .env.local file is excluded from version control, so the key does not reach the repository. An API key is equivalent to a password, so keep it out of shared configurations and screenshots.

secure: false covers development environments that terminate HTTPS with a private certificate. It skips certificate validation, so use it only on the dev server.

Two things cannot be verified on the dev server. There is no web console, so the screen receives no navigation messages, and the theme follows the operating system setting instead of the console. Check navigation and theme integration after installing.

Troubleshooting

SymptomCauseFix
Blank screen, script requests return 404The Vite base path does not match the app codeSet base in vite.config.ts to /app/{app_code}/
An error page instead of the screenThe bundle has no WEB-INFCheck that the frontend plugin binds to generate-resources
The menu does not appearManifest validation failed, malformed app code, missing logoCheck the required fields in sonar_app.json and that sonar_app_logo.png exists
NoSuchMethodError right after the app startsThe sonar-app-api version does not match the installed platformChange to the sonar-app-api version that matches the platform
The build fails at the bundle stepUsing maven-bundle-plugin 5.1.5 or laterPin it to 5.1.4
The build fails on a type errortsc -b stopped at the type checkFix the type error; do not skip the check
The bundle grows with every buildHashed files from earlier builds remainSet build.emptyOutDir to true
API calls return 401The connect profile's API key is not validReissue the API key and update the connect profile

Agent prompt

Use this when you delegate a build or installation problem to an agent.

Diagnose a build problem in a Logpresso app's XDR UI.

Symptom: (describe what you observed, specifically)

Check in this order:
1. which step failed in the mvn clean package output
2. whether the script path in the generated src/main/resources/WEB-INF/index.html
   starts with /app/{app_code}/assets/
3. whether the bundle contains WEB-INF/, sonar_app.json, and sonar_app_logo.png
4. base and outDir in vite.config.ts
5. whether frontend-maven-plugin binds to generate-resources in pom.xml,
   and whether maven-bundle-plugin is 5.1.4

Reference: https://docs.logpresso.com/en/app-sdk/build-ui-app

When you find the cause, fix it and run the build again to confirm. Do not change
several things at once on a guess — verify one at a time.

The next section explains how to register an app screen in the web console menu.