Skip to content

[Feature] Plugin system: support embedded frontend UIs (iframe tab) + expose has_frontend in API #381

@custosonlinux

Description

@custosonlinux

Background

I've built a community plugin for PegaProx — https://github.com/custosonlinux/pegaprox-netapp-ontap — that
provides VM-consistent NetApp ONTAP snapshots, restore, clone, and SnapMirror® DR integration for NFS
datastores. The plugin has a full management UI served as a self-contained ui.html.

To make this work I currently maintain three local patches against PegaProx core that get overwritten every
time I run the built-in update. I'd like to upstream these changes so the plugin system can support any
plugin with a frontend UI — not just mine.


The three changes needed

  1. Security headers — allow same-origin iframe embedding (2 lines)

pegaprox/app.py:

before:

response.headers['X-Frame-Options'] = 'DENY'

CSP: frame-ancestors 'none';

after:

response.headers['X-Frame-Options'] = 'SAMEORIGIN'

CSP: frame-ancestors 'self';

Plugin UIs are served under /api/plugins/{id}/api/ui — the same origin as the dashboard. DENY /
frame-ancestors 'none' prevents them from being embedded in an iframe at all. SAMEORIGIN / frame-ancestors
'self' restricts embedding to the same origin, which is the correct security posture.


  1. Expose has_frontend and frontend_route in the plugin list API (2 lines)

pegaprox/api/plugins.py, in list_plugins():
'has_frontend': bool(plugin.get('has_frontend', False)),
'frontend_route': plugin.get('frontend_route', ''),

Plugins already declare these in their manifest.json. The API just needs to forward them to the frontend so
the dashboard can decide which plugins get a tab.

Example manifest.json:
{
"id": "netapp_ontap",
"has_frontend": true,
"frontend_route": "/api/plugins/netapp_ontap/api/ui"
}


  1. Dynamic plugin tabs in dashboard.js — generic, not hardcoded

Currently I have a hardcoded netapp tab block in dashboard.js. The right solution is to make tab rendering
generic so any enabled plugin with has_frontend: true automatically gets a tab — no changes to core needed
when new plugins are installed.

Proposed approach:

In the tab definition array, replace the hardcoded netapp entry with a dynamic list from enabledPlugins:

// Replace the static tab list with plugins appended dynamically:
const pluginTabs = enabledPlugins
.filter(p => p.has_frontend)
.map(p => ({ id: plugin:${p.id}, label: p.name, icon: Icons.Package }));

In the tab content renderer, add a generic plugin iframe block:

{activeTab.startsWith('plugin:') && (() => {
const pluginId = activeTab.replace('plugin:', '');
const plugin = enabledPlugins.find(p => p.id === pluginId);
if (!plugin?.frontend_route) return null;
const theme = isCorporate
? (localStorage.getItem('corp-theme') === 'light' ? 'corp-light' : 'corp-dark')
: 'dark';
const src = ${plugin.frontend_route}?theme=${theme};
return (
<div style={{ margin: isCorporate ? '-8px -12px' : '0 0 -24px',
height: 'calc(100vh - 130px)' }}>
<iframe key={${selectedCluster?.id}-${src}}
src={src}
style={{ width:'100%', height:'100%', border:'none', display:'block' }}
title={plugin.name} />

);
})()}

This approach:

  • Works for any plugin, not just NetApp
  • Uses the frontend_route declared in the manifest as the iframe src
  • Passes the current theme so plugins can style themselves accordingly
  • Survives PegaProx updates without any re-patching

Why this matters for the update mechanism

With the built-in updater, every update to PegaProx currently wipes my three patches. A generic plugin
frontend system means plugin authors can ship a self-contained plugin directory and have it fully integrated
— including a UI tab — without touching core files.


Offer to contribute

I'm happy to submit a PR with all three changes if the approach sounds good to you. Changes 1 and 2 are
trivial. Change 3 requires building web/index.html from source, so coordination on that would be appreciated.

Plugin repo for reference: https://github.com/custosonlinux/pegaprox-netapp-ontap

Thank you an kind regards

Birger

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions