@wincc-oa/webui-runtime
v1.2.2
Published
WinCC Open Architecture Dashboard project.
Maintainers
Readme
ETM Professional Control – WebUI runtime
This workspace contains the Dashboard web application released in WinCC OA 3.21 by ETM Professional Control. This application can be extended with Web Components. This file explains how to develop and build the web app locally and how to run it together with your WinCC OA project.
More information about Dashboard basics at run-time can be found in the official documentation: https://www.winccoa.com/documentation/WinCCOA/latest/en_US/Dashboard/topics/Dashboard_Basics.html
Workspace Overview
This workspace is used to build and modify the web application. The individual building blocks are split into separate npm packages (widgets, API, and service implementations). In this workspace these packages are already installed or can be installed via npm.
WinCC OA uses individual projects that must be configured accordingly. Within these projects, the WebServer and WebSocket server run and interact with this web app.
Setup & Operation
Initial Setup (First-Time Only)
The @wincc-oa/webui-runtime npm package ships the workspace sources. To start a new project, install the package and run the webui-runtime-init command to copy its contents into your project directory:
mkdir my-dashboard && cd my-dashboard
npm install @wincc-oa/webui-runtime
npx webui-runtime-initThe init command copies the workspace files (apps, libs, configs, scripts) into the current directory. It is interactive by default — pass -y for non-interactive use (e.g. in CI):
npx webui-runtime-init -yAfter the files are deployed, install the development dependencies and create the oa-data/ directory:
npm install --no-audit --no-fund --include dev
npm run init:oa-dataThe workspace is now ready. Continue with Standard Setup for the day-to-day development workflow.
Standard Setup
- Install dependencies (skip if you just ran the Initial Setup):
npm install- Start development (connected to a WinCC OA project):
Assuming WinCC OA is configured so the WebServer is available at https://localhost and the project is located at C:\WinCC_OA_Proj\3.21\example_dashboard, start the web app to work against the configured server:
# Linux / Mac / Git Bash
BASE_URL="https://localhost" npm run start
# Windows PowerShell
$env:BASE_URL="https://localhost"; npm run start
# Windows CMD
set BASE_URL=https://localhost&& npm run startThe served application is then available at http://localhost:4300
- Build for production and deploy into the WinCC OA project:
Using environment variable to set output directory:
# Linux / Mac / Git Bash
OUT_DIR="/path/to/WinCC_OA_Proj/3.21/example_dashboard/data/dashboard-wc" npm run build
# Windows PowerShell
$env:OUT_DIR="C:\WinCC_OA_Proj\3.21\example_dashboard\data\dashboard-wc"; npm run build
# Windows CMD
set OUT_DIR=C:\WinCC_OA_Proj\3.21\example_dashboard\data\dashboard-wc&& npm run buildImportant: In Windows CMD, do not add a space before
&&- it becomes part of the variable value and the path will not resolve correctly.
Default behavior (no OUT_DIR):
Builds to dist/ folder within the webui-runtime workspace.
Available build scripts:
npm run build- Full build: shared bundles + main app + standalone pagesnpm run build:shared-bundles- Build shared bundles only (lit, ix, rxjs, wui)npm run build:pages- Build standalone pages only (for fast iteration during page development)npm run init:oa-data- Create theoa-data/directory for translations, icons, and SVGsnpm run deploy:oa-data- Deployoa-data/contents to the WinCC OA project (also runs as part ofbuild)
Notes:
BASE_URL: Base URL of the WinCC OA WebServer used by the web app to access backend APIs/assets.OUT_DIR: Target directory for all build commands (environment variable). Works withbuild,build:shared-bundles,build:app, andbuild:pages.- Full build order: shared bundles first, then main app and finally standalone pages.
Build Architecture
The build process uses import maps to share dependencies across the main app and standalone pages.
Build Phases
- Shared bundles build (
build:shared-bundles) - Creates shared dependency bundles inentry/ - App build (
build:app) - Creates main application with shared bundles externalized - Pages build (
build:pages) - Creates standalone pages (can be run independently for fast iteration)
Import Map
The index.html contains an import map that redirects bare module specifiers to pre-built shared bundles:
| Specifier Pattern | Bundle |
| ------------------------------------ | --------------------- |
| @siemens/ix, @siemens/ix-echarts | ./entry/ix.js |
| @siemens/ix-icons | ./entry/ix-icons.js |
| lit, @lit/*, lit-* | ./entry/lit.js |
| rxjs | ./entry/rxjs.js |
| @wincc-oa/wui-*, etc. | ./entry/wui.js |
This allows standalone pages to use the same dependencies as the main app without bundling them separately.
Shared Bundle Configuration
The bundle exports are defined in config/shared-bundle-exports.jsonc. This file is not copied to production - it's build-time only configuration.
For details on how bundles work, available entry types (manual, autoDiscover, importMapEntries, etc.), and how to add custom context/widget components via aliasing, see docs/knowledge/webui-runtime-shared-bundles.md.
To regenerate the export entry files after modifying the config:
npm run generate:shared-bundlesStandalone Pages
Standalone pages are full-screen views that run alongside the dashboard. They can connect to WinCC OA datapoints for real-time data and are added to the application via menu configuration.
Adding a new standalone page:
- Create a new
.tsfile inlibs/default-components/src/lib/standalone-pages/ - Add a menu entry in
apps/dashboard-wc/config/menuconfig.jsonc - Run
npm run build:pagesfor fast iteration, ornpm run buildfor a full rebuild
Pages are automatically discovered at build time - no build configuration changes needed.
Minimal Example
import { OaRxJsApi } from '@etm-professional-control/oa-rx-js-api';
import { LitElement, css, html } from 'lit';
import { customElement, state } from 'lit/decorators.js';
import { Subscription, map } from 'rxjs';
import { container } from 'tsyringe';
@customElement('wui-my-page')
export class WuiMyPage extends LitElement {
static override styles = css`
:host {
display: block;
padding: 1rem;
}
`;
@state() private myValue = 0;
private readonly oaRxJsApi = container.resolve<OaRxJsApi>(OaRxJsApi);
private subscriptions: Subscription[] = [];
override connectedCallback(): void {
super.connectedCallback();
const sub = this.oaRxJsApi
.dpConnect('System1:ExampleDP_Arg1.', true)
.pipe(map((data) => (data.value[0] as number) ?? 0))
.subscribe((value) => {
this.myValue = value;
});
this.subscriptions.push(sub);
}
override disconnectedCallback(): void {
super.disconnectedCallback();
for (const sub of this.subscriptions) sub.unsubscribe();
this.subscriptions = [];
}
override render() {
return html`<h2>Value: ${this.myValue}</h2>`;
}
}Menu Configuration
Add an entry to apps/dashboard-wc/config/menuconfig.jsonc:
{
"title": { "en_US": "My Page", "de_DE": "Meine Seite" },
"icon": "capacity",
"path": "/my-page",
"module": "/data/dashboard-wc/pages/my-page.js",
"routeId": "my-page",
"component": "wui-my-page",
"permission": ["connected"]
}For declarative data binding, you can also use <wui-context-generator> with context types (data-point, translate, group, etc.) instead of direct API subscriptions.
Detailed guides for standalone page development - including advanced data binding, context types, styling, and SVG graphics - are available in the online documentation.
Dynamic Widget Loading
Widgets can access services from the host application via the tsyringe DI container.
Available Services
| Token | Class | Description |
| --------------------------- | ------------------------- | ---------------------------------------------------- |
| OaRxJsApi | OaRxJsApi | WinCC OA backend API (dpGet, dpSet, dpConnect, etc.) |
| WuiBackendConnectionToken | WuiBackendConnection | WebSocket connection state monitoring |
| WuiSettingsService | WuiSettingsService | Backend settings (OIDC, WebSocket servers, etc.) |
| WuiUserService | WuiUserService | Current user information and permissions |
| DateService | DateService | Date/time utilities with backend time sync |
| WuiRouterServiceToken | WuiRouterFacade | Router navigation and route management |
| WuiToastService | WuiToastService | Toast notifications (success, error, warning) |
| WuiCleanupService | WuiCleanupService | Resource cleanup on logout/session end |
| WuiConfigServiceToken | WebuiConfigService | App and menu configuration from JSON files |
| RuntimeComponentService | RuntimeComponentService | Runtime component installation and management |
Service Usage Examples
import { OaRxJsApi } from '@etm-professional-control/oa-rx-js-api';
import { container } from 'tsyringe';
const api = container.resolve(OaRxJsApi);
// Read a single datapoint
api.dpGet(['System1:ExampleDP_Arg1.']).subscribe((data) => {
console.log('Value:', data.value);
});
// Subscribe to datapoint changes
const subscription = api.dpConnect('System1:ExampleDP_Arg1.').subscribe((data) => {
console.log('New value:', data.value);
});
// Write to a datapoint
api.dpSet('System1:ExampleDP_Arg1.', 42).subscribe((result) => {
console.log('Write successful:', result);
});
// Execute custom command
api.customCommand('myFunction', { param1: 'value' }).subscribe((result) => {
console.log('Result:', result);
});import { WuiBackendConnection } from '@wincc-oa/wui-oarxjs-data';
import { WuiBackendConnectionToken } from '@wincc-oa/wui-shared/tokens/wui-backend-connection.token.js';
import { container } from 'tsyringe';
container.registerSingleton(WuiBackendConnectionToken, WuiBackendConnection);
const connection = container.resolve(WuiBackendConnectionToken);
// Monitor connection state
connection.status$.subscribe((state) => {
// state: 'connected' | 'disconnected' | 'connecting' | 'reconnecting' | 'error'
console.log('Connection state:', state);
});
// Check if currently connected
if (connection.status$.value === 'connected') {
// Safe to perform operations
}import { WuiUserService } from '@wincc-oa/wui-iam-data';
import { container } from 'tsyringe';
const userService = container.resolve(WuiUserService);
// Access user properties
console.log('User ID:', userService.id);
console.log('Username:', userService.name);
console.log('Locale:', userService.locale);
// Check permissions
if (userService.canWrite) {
// Show edit UI
}
if (userService.canEdit) {
// Enable edit mode
}
if (userService.canPublish) {
// Show publish button
}
// Access favorites
const favorites = userService.favorites; // number[]
// Wait for user data to load
userService.ready$.subscribe((ready) => {
if (ready) {
console.log('User data loaded');
}
});import { WuiToastService } from '@wincc-oa/wui-shared/services/wui-toast/wui-toast.service.js';
import { container } from 'tsyringe';
const toastService = container.resolve(WuiToastService);
// Success notification
await toastService.success('Operation completed successfully');
// Error notification
await toastService.error('Failed to save data', 'Error Title');
// Warning notification
await toastService.warning('Please check your input');
// Custom toast with full options
await toastService.toast({
message: 'Custom message',
type: 'info',
autoClose: true,
position: 'top-right'
});import { WuiRouterFacade } from '@wincc-oa/wui-models/interfaces/wui-router/wui-router.facade.js';
import { WuiRouterServiceToken } from '@wincc-oa/wui-shared/tokens/wui-router-service.token.js';
import { container } from 'tsyringe';
const router = container.resolve<WuiRouterFacade>(WuiRouterServiceToken);
// Navigate to a route
router.render('/dashboard/overview');
// Navigate with parameters
const pathname = router.getPathname('dashboard', { id: '123' });
router.render(pathname, true);
// Get current route information
const routeId = router.getRouteId();
const params = router.getParam('id');
const searchParams = router.searchParams;import { DateService } from '@wincc-oa/wui-shared/services/date/date.service.js';
import { container } from 'tsyringe';
const dateService = container.resolve(DateService);
// Get backend UTC time
const backendTime = await dateService.getBackendUTCTime();
console.log('Backend time:', backendTime);
// Get time offset between frontend and backend
const offset = dateService.getTimeOffsetToBackend();
console.log('Time offset (ms):', offset);
// Check if offset exceeds threshold (30 seconds)
const exceedsThreshold = await dateService.doesOffsetExceedThreshold();
if (exceedsThreshold) {
console.warn('Time offset too large!');
}
// Monitor offset continuously
dateService.doesOffsetExceedThreshold$.subscribe((exceeds) => {
if (exceeds) {
// Show warning to user
}
});Utility Functions
// For reactive Lit templates (auto-updates on language change)
import { translate } from 'lit-translate';
import { html } from 'lit';
html`<h1>${translate('WUI_General.App.Title')}</h1>`;
// For static usage (JavaScript/TypeScript)
import { get } from 'lit-translate';
const title = get('WUI_General.App.Title');
console.log(title); // => 'WinCC OA Dashboard'
// Multi-language strings (objects with locale keys)
import { translateOrLocalize, translateOrLocalizeStatic } from '@wincc-oa/wui-i18n-shared/localize-multilang.js';
// Works with translation keys
html`<h1>${translateOrLocalize('WUI_General.App.Title')}</h1>`;
// Works with multi-language objects
html`<h1>${translateOrLocalize({ en_US: 'Title', de_DE: 'Titel' })}</h1>`;
// Static version (for JavaScript, not Lit templates)
const text = translateOrLocalizeStatic('WUI_General.App.Title');import { getCssCustomPropertyValue } from '@wincc-oa/wui-shared/helper-css.js';
// Transform OA color names to HTML colors via CSS variables
const color1 = getCssCustomPropertyValue('--oa-color--FwGreen');
// => '#00ff00' (resolved from CSS variable)
const color2 = getCssCustomPropertyValue('var(--oa-color--FwRed)');
// => '#ff0000' (works with var() syntax too)
// Regular colors pass through unchanged
const color3 = getCssCustomPropertyValue('#123456');
// => '#123456'
// Use in Lit component styles
import { css, html, LitElement } from 'lit';
class MyComponent extends LitElement {
static styles = css`
:host {
/* OA colors are available as CSS variables */
color: var(--oa-color--FwGreen);
background: var(--oa-color--FwWhite);
}
`;
}import { localizeDate } from '@wincc-oa/wui-i18n-shared/localize-date.js';
// Format dates according to current locale
const date = new Date();
const formattedDate = localizeDate(date); // e.g., "12/31/2023" or "31.12.2023"Basic Example
import { LitElement, html } from 'lit';
import { container } from 'tsyringe';
import { OaRxJsApi } from '@etm-professional-control/oa-rx-js-api';
export class MyWidget extends LitElement {
constructor() {
super();
this.api = container.resolve(OaRxJsApi);
}
connectedCallback() {
super.connectedCallback();
// Subscribe to a datapoint
this.subscription = this.api.dpConnect('System1:ExampleDP_Arg1.').subscribe((data) => console.log('Value:', data.value));
}
disconnectedCallback() {
super.disconnectedCallback();
this.subscription?.unsubscribe();
}
render() {
return html`<p>My Widget</p>`;
}
}
customElements.define('my-widget', MyWidget);See widgets-v2/wc/dynamic-widget.js for a complete example.
Routing
The application uses Vaadin Router with hash-based routing and config-driven routes from menuconfig.json. Routes are generated at startup from the menu configuration and registered through the DI container; workspace-supplied route generators contribute named subtrees. Authentication routes (/login, /logout) and the error/404 routes are part of the runtime's static frame and cannot be overridden by menu configuration.
i18n
For translations we use the lit-translate library.
Additionally, the @wincc-oa/wui-i18n package provides custom functions and directives to handle localization for dates, numbers, etc.
Documentation
Technical documentation is included in the docs/knowledge/ directory:
- Widget development: Creating widgets, JSON schemas, integrating external components
- Dashboard customization: Menu configuration, branding, custom themes
- Standalone pages: Building full-screen pages with data binding
- Architecture: Application shell, routing, DI setup, controllers
- Siemens iX: Component usage, icons, color system
- Web technologies: Lit state management, WebComponent best practices, styling
- Backend integration: WebSocket protocol, authentication, API reference
