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
| Symptom | Cause | Fix |
|---|---|---|
| Blank screen, script requests return 404 | The Vite base path does not match the app code | Set base in vite.config.ts to /app/{app_code}/ |
| An error page instead of the screen | The bundle has no WEB-INF | Check that the frontend plugin binds to generate-resources |
| The menu does not appear | Manifest validation failed, malformed app code, missing logo | Check the required fields in sonar_app.json and that sonar_app_logo.png exists |
| NoSuchMethodError right after the app starts | The sonar-app-api version does not match the installed platform | Change to the sonar-app-api version that matches the platform |
| The build fails at the bundle step | Using maven-bundle-plugin 5.1.5 or later | Pin it to 5.1.4 |
| The build fails on a type error | tsc -b stopped at the type check | Fix the type error; do not skip the check |
| The bundle grows with every build | Hashed files from earlier builds remain | Set build.emptyOutDir to true |
| API calls return 401 | The connect profile's API key is not valid | Reissue 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.