signalk-embedded-webapp-proxy
v0.2.0
Published
Signal K embedded webapp plugin — Run any local web application as an embedded Webapp
Maintainers
Readme
signalk-embedded-webapp-proxy
SignalK embedded webapp plugin that acts as a general reverse proxy, letting you embed any web application into the SignalK admin UI.
Configure one or more applications (Portainer CE, Grafana, Node-RED, etc.). A selector UI lets you switch between them directly from the SignalK webapp panel.
Prerequisites
- SignalK server (v2.x or later)
- Node.js >= 18
- The web applications you want to embed, accessible from the SignalK host
Installation
Via SignalK Appstore (recommended)
- Open the SignalK admin UI
- Navigate to Appstore > Available
- Search for Embedded Webapp Proxy
- Click Install
- Restart SignalK server
Via npm
cd ~/.signalk
npm install signalk-embedded-webapp-proxyThen restart the SignalK server.
Plugin Configuration
After installation, configure the plugin in the SignalK admin UI:
- Navigate to Server > Plugin Config
- Find Embedded Webapp Proxy in the plugin list
- Enable the plugin
- Add one or more applications under Web Applications:
| Field | Description | Default |
| ---------------------------------- | ------------------------------------------------------------------------------------ | -------------------- |
| Name | Display name shown in the app selector | My App |
| Proxy Path | Custom path identifier (e.g. portainer). When set, the app is also accessible at /plugins/signalk-embedded-webapp-proxy/proxy/<appPath>. Must start with a letter; only letters, digits, and hyphens allowed. | (none) |
| Application URL | URL with protocol and host required; port is optional (defaults to 80 for http, 443 for https); base path is optional — e.g. http://192.168.1.100:9000, https://myapp.local/admin | http://127.0.0.1 |
| Allow Self-Signed Certificates | Accept self-signed TLS certs (HTTPS only) | false |
| Rewrite Absolute Paths | Inject a script into HTML responses that rewrites absolute API paths (e.g. /api/auth) so they route through the proxy. Enable for SPAs like Portainer or Grafana — eliminates the need for --base-url on the target container. | false |
| Timeout (ms) | apps[].timeout — milliseconds to wait for the target before returning a 502. 0 disables the timeout. E.g. 5000 for 5 s. | 0 |
- Click Submit to save
Example: Portainer CE + Grafana
{
"apps": [
{
"name": "Portainer CE",
"appPath": "portainer",
"url": "https://127.0.0.1:9443",
"allowSelfSigned": true,
"rewritePaths": true
},
{
"name": "Grafana",
"url": "http://127.0.0.1:3000",
"rewritePaths": true
}
]
}Usage
Once the plugin is enabled and configured:
- Open the SignalK admin UI
- Navigate to Webapps
- Click Embedded Webapp Proxy
- If multiple apps are configured, select one from the dropdown
- The selected application loads embedded in the SignalK admin UI
If only one application is configured, it loads automatically without requiring a selection.
Proxy URL structure
Each application is accessible by its numeric index:
/plugins/signalk-embedded-webapp-proxy/proxy/{index}/If an appPath is configured, the app is also accessible at:
/plugins/signalk-embedded-webapp-proxy/proxy/{appPath}/| App | Proxy URLs |
|-----|-----------|
| First app (index 0, appPath portainer) | /plugins/signalk-embedded-webapp-proxy/proxy/0/ or /plugins/signalk-embedded-webapp-proxy/proxy/portainer/ |
| Second app (index 1, no appPath) | /plugins/signalk-embedded-webapp-proxy/proxy/1/ |
The list of configured apps (name, index, appPath) is also available as JSON:
GET /plugins/signalk-embedded-webapp-proxy/appsApplication-specific setup notes
Grafana
Grafana works out of the box with rewritePaths: true. However, if you set Grafana's root_url (via the GF_SERVER_ROOT_URL environment variable), links generated by Grafana (e.g. dashboard share links, alert notification URLs) will use that root URL as their base.
When Grafana is served through this proxy, the effective URL is:
/plugins/signalk-embedded-webapp-proxy/proxy/grafana/If you want Grafana-generated links to point to the proxied path, set GF_SERVER_ROOT_URL to match the full proxy path on your SignalK host:
docker run -d \
-p 3001:3000 \
--name grafana \
--restart=always \
-e GF_SERVER_ROOT_URL: "%(protocol)s://%(domain)s/grafana/" \
grafana/grafanaNote:
GF_SERVER_ROOT_URLonly affects how Grafana constructs outgoing links (share URLs, email notifications, OAuth redirects). It does not change how Grafana serves its assets or API — the proxy handles that via path rewriting. If you don't need correct share links or OAuth callbacks, you can omitROOT_URLentirely and simply enablerewritePaths.
Plugin configuration example:
{
"name": "Grafana",
"appPath": "grafana",
"url": "http://127.0.0.1:3001/grafana/",
"rewritePaths": true
}Portainer CE
Portainer's frontend makes API calls using absolute paths (e.g. POST /api/auth). Without path rewriting these requests bypass the proxy and hit the SignalK server, causing login to fail silently.
Fix: Enable Rewrite Absolute Paths (rewritePaths: true) in the plugin config. The proxy automatically injects a script into HTML responses that rewrites absolute API paths through the proxy. No --base-url flag is needed on the Portainer container.
# HTTPS (default for Portainer CE v2.9+)
docker run -d \
-p 9443:9443 \
--name portainer \
--restart=always \
-v /var/run/docker.sock:/var/run/docker.sock \
-v portainer_data:/data \
portainer/portainer-ce:lts
# HTTP (legacy / manually enabled)
docker run -d \
-p 9000:9000 \
--name portainer \
--restart=always \
-v /var/run/docker.sock:/var/run/docker.sock \
-v portainer_data:/data \
portainer/portainer-ce:lts \
--http-enabledThe same applies to other SPAs like Grafana that use absolute API paths. Enable
rewritePathsfor any application where login or POST requests fail silently.
Security considerations
- Only proxy trusted internal applications. The plugin performs no authentication of its own; any app reachable at the configured host:port will be forwarded to anyone with access to the SignalK UI.
- iframe same-origin access. The embedded iframe uses
allow-same-originin its sandbox so that cookie and session-based authentication works in proxied apps (e.g. Portainer). This means proxied content runs at the SignalK admin origin and can access admin-origin cookies. Only configure apps you fully trust. - Port is optional. If omitted from the URL, the port defaults to
80forhttpand443forhttps. An invalid host causes the app entry to be skipped and logged viaapp.error. - Cloud metadata endpoints are blocked. Hosts
169.254.169.254andmetadata.google.internal(and case/dot variants) are rejected to prevent SSRF against cloud instance metadata APIs.
Troubleshooting
Application not loading (503 error)
- Ensure the target application is running and reachable from the SignalK host
- Verify the application URL in the plugin settings
- Check that the plugin is enabled in SignalK Plugin Config
Connection refused
- Confirm the application port is bound on the host (e.g.
-p 9443:9443for Docker) - Check firewall rules if the application runs on a different host
- Test connectivity directly:
curl http://127.0.0.1:<port>
Login or POST requests hang
The application's frontend is making API calls with absolute paths that bypass the proxy. Enable Rewrite Absolute Paths (rewritePaths: true) in the plugin config for that app. This injects a script that rewrites /api/... calls to go through the proxy. See the Portainer CE section above.
Container console/terminal not working (WebSocket)
- WebSocket upgrade connections are proxied automatically per app
- Ensure no upstream reverse proxy is stripping
Upgrade/Connectionheaders
Blank page or assets not loading
- Clear browser cache and reload
- Check the browser developer console for errors
- Ensure the application is fully started before accessing it
Development
Setup
git clone https://github.com/KEGustafsson/signalk-portainer.git
cd signalk-portainer
npm installBuild
npm run build # Build both plugin and UI
npm run build:plugin # Build plugin only (TypeScript)
npm run build:ui # Build UI only (webpack)Test
npm test # Run all tests with coverage
npm run test:watch # Run tests in watch modeLint & Format
npm run lint # Check for lint errors
npm run lint:fix # Auto-fix lint errors
npm run format # Format code with Prettier
npm run format:check # Check formattingLink for local development
npm run build
cd ~/.signalk
npm link /path/to/signalk-portainerThen restart SignalK server. Changes to the plugin source require rebuilding and restarting.
