<html><head><style type='text/css'>p { margin: 0; }</style></head><body><div style='font-family: times new roman,new york,times,serif; font-size: 12pt; color: #000000'>Hi George,<br><br><span style="font-size:11.0pt;font-family:"Calibri","sans-serif";color:#1F497D">> If
I want to add 3 main tabs and 6 context menus, do I provide 9 plugin
definitions? Or do I provide 1 plugin definition with multiple “urls”
where each one points to a distinct path?</span><br><br>The JSON plugin definition file (maybe we should call it "plugin descriptor") should contain basic information about the plugin and how it's supposed to be loaded by WebAdmin, for example:<br><br style="font-family: courier new,courier,monaco,monospace,sans-serif;"><span style="font-family: courier new,courier,monaco,monospace,sans-serif;">{</span><br style="font-family: courier new,courier,monaco,monospace,sans-serif;"><span style="font-family: courier new,courier,monaco,monospace,sans-serif;"> "name": "test",</span><br style="font-family: courier new,courier,monaco,monospace,sans-serif;"><span style="font-family: courier new,courier,monaco,monospace,sans-serif;"> "version": "1.0",</span><br style="font-family: courier new,courier,monaco,monospace,sans-serif;"><span style="font-family: courier new,courier,monaco,monospace,sans-serif;"> "url": "/webadmin/webadmin/plugin/test/start.html", // Invokes the actual plugin code</span><br style="font-family: courier new,courier,monaco,monospace,sans-serif;"><span style="font-family: courier new,courier,monaco,monospace,sans-serif;"> ... more attributes ...</span><br style="font-family: courier new,courier,monaco,monospace,sans-serif;"><span style="font-family: courier new,courier,monaco,monospace,sans-serif;">}</span><br><br>You can do many things inside one plugin (add multiple tabs, context menu items, etc.) - you just need to add multiple event handler functions inside the actual plugin code, for example:<br><br><span style="font-family: courier new,courier,monaco,monospace,sans-serif;">pluginApi.plugins['test'] = {</span><br><br style="font-family: courier new,courier,monaco,monospace,sans-serif;"><span style="font-family: courier new,courier,monaco,monospace,sans-serif;"> // UiInit event handler function, the first function to be called on the plugin</span><br style="font-family: courier new,courier,monaco,monospace,sans-serif;"><span style="font-family: courier new,courier,monaco,monospace,sans-serif;"> UiInit: function() {</span><br style="font-family: courier new,courier,monaco,monospace,sans-serif;"><span style="font-family: courier new,courier,monaco,monospace,sans-serif;"> pluginApi.ui.addMainTab('Custom Tab One', 'custom-tab-1', 'http://www.example.com/1');</span><br style="font-family: courier new,courier,monaco,monospace,sans-serif;"><span style="font-family: courier new,courier,monaco,monospace,sans-serif;"> pluginApi.ui.addMainTab('Custom Tab Two', 'custom-tab-2', 'http://www.example.com/2');</span><br style="font-family: courier new,courier,monaco,monospace,sans-serif;"><span style="font-family: courier new,courier,monaco,monospace,sans-serif;"> },</span><br style="font-family: courier new,courier,monaco,monospace,sans-serif;"><br style="font-family: courier new,courier,monaco,monospace,sans-serif;"><span style="font-family: courier new,courier,monaco,monospace,sans-serif;"> // HostContextMenu event handler function, just an example (not implemented yet)</span><br style="font-family: courier new,courier,monaco,monospace,sans-serif;"><span style="font-family: courier new,courier,monaco,monospace,sans-serif;"> HostContextMenu: function(ctx) {</span><br><span style="font-family: courier new,courier,monaco,monospace,sans-serif;"> // 'ctx' represents the context of this event handler function, containing:<br> // - information about host(s) currently selected<br style="font-family: courier new,courier,monaco,monospace,sans-serif;"></span><span style="font-family: courier new,courier,monaco,monospace,sans-serif;"></span><span style="font-family: courier new,courier,monaco,monospace,sans-serif;"> // - API (functions) to add custom context menu items<br></span><span style="font-family: courier new,courier,monaco,monospace,sans-serif;"> }</span><br style="font-family: courier new,courier,monaco,monospace,sans-serif;"><br style="font-family: courier new,courier,monaco,monospace,sans-serif;"><span style="font-family: courier new,courier,monaco,monospace,sans-serif;"> // Similarly, we could define VmContextMenu, etc. (everything inside one plugin)</span><br style="font-family: courier new,courier,monaco,monospace,sans-serif;"><br style="font-family: courier new,courier,monaco,monospace,sans-serif;"><span style="font-family: courier new,courier,monaco,monospace,sans-serif;">};</span><br><br><span style="font-size:11.0pt;font-family:"Calibri","sans-serif";color:#1F497D">> If
“url” is configured to point to an external application server hosting
my plugin, what is the intent of “path”? For example, if I configure
“url” to point
to “https://10.10.10.10/myplugin/entrypoint.html” then presumably the application server will render the page it needs as a main tab or
context menu. It would have no need for “path” since all dependencies
would be resolved by the application server.</span><br><br>You're right, the "path" attribute makes sense only when serving plugin resources (most importantly, plugin HTML page) through a special oVirt Engine servlet (currently called <em>PluginSourcePageServlet</em>, should be renamed to something like "PluginResourceServlet"). If "url" points to some external application server, the "path" attribute can be omitted (optional attribute). However, the "url" attribute denotes the location from which plugin HTML page will be requested by the plugin's iframe.<br><br>Regards,<br>Vojtech<br><br><br><hr id="zwchr"><div style="color:#000;font-weight:normal;font-style:normal;text-decoration:none;font-family:Helvetica,Arial,sans-serif;font-size:12pt;"><b>From: </b>"George Costea" <George.Costea@netapp.com><br><b>To: </b>"Vojtech Szocs" <vszocs@redhat.com>, "Chris Frantz" <Chris.Frantz@hp.com><br><b>Cc: </b>"engine-devel" <engine-devel@ovirt.org><br><b>Sent: </b>Thursday, August 23, 2012 3:09:05 PM<br><b>Subject: </b>RE: [Engine-devel] UI Plugins configuration<br><br>
<style><!--
@font-face
        {font-family:Calibri;
        panose-1:2 15 5 2 2 2 4 3 2 4;}
@font-face
        {font-family:Tahoma;
        panose-1:2 11 6 4 3 5 4 4 2 4;}
p.MsoNormal, li.MsoNormal, div.MsoNormal
        {margin:0in;
        margin-bottom:.0001pt;
        font-size:12.0pt;
        font-family:"Times New Roman","serif";}
a:link, span.MsoHyperlink
        {mso-style-priority:99;
        color:blue;
        text-decoration:underline;}
a:visited, span.MsoHyperlinkFollowed
        {mso-style-priority:99;
        color:purple;
        text-decoration:underline;}
p
        {mso-style-priority:99;
        margin:0in;
        margin-bottom:.0001pt;
        font-size:12.0pt;
        font-family:"Times New Roman","serif";}
span.EmailStyle19
        {mso-style-type:personal-reply;
        font-family:"Calibri","sans-serif";
        color:#1F497D;}
.MsoChpDefault
        {mso-style-type:export-only;
        font-size:10.0pt;}
@page WordSection1
        {size:8.5in 11.0in;
        margin:1.0in 1.0in 1.0in 1.0in;}
div.WordSection1
        {page:WordSection1;}
@list l0
        {mso-list-id:306739626;
        mso-list-template-ids:-2112482452;}
ol
        {margin-bottom:0in;}
ul
        {margin-bottom:0in;}
--></style>
<div class="WordSection1">
<p class="MsoNormal"><span style="font-size:11.0pt;font-family:"Calibri","sans-serif";color:#1F497D">Thanks Chris and Vojtech for continuing this discussion. I think I’m missing the link between providing the plugin definition file and defining the plugins.
If I want to add 3 main tabs and 6 context menus, do I provide 9 plugin definitions? Or do I provide 1 plugin definition with multiple “urls” where each one points to a distinct path?</span></p>
<p class="MsoNormal"><span style="font-size:11.0pt;font-family:"Calibri","sans-serif";color:#1F497D"> </span></p>
<p class="MsoNormal"><span style="font-size:11.0pt;font-family:"Calibri","sans-serif";color:#1F497D">If “url” is configured to point to an external application server hosting my plugin, what is the intent of “path”? For example, if I configure “url” to point
to “https://10.10.10.10/myplugin/entrypoint.html” then presumably the application server will render the page it needs as a main tab or context menu. It would have no need for “path” since all dependencies would be resolved by the application server.</span></p>
<p class="MsoNormal"><span style="font-size:11.0pt;font-family:"Calibri","sans-serif";color:#1F497D"> </span></p>
<p class="MsoNormal"><span style="font-size:11.0pt;font-family:"Calibri","sans-serif";color:#1F497D">George</span></p>
<p class="MsoNormal"><a name="_MailEndCompose"><span style="font-size:11.0pt;font-family:"Calibri","sans-serif";color:#1F497D"> </span></a></p>
<div>
<div style="border:none;border-top:solid #B5C4DF 1.0pt;padding:3.0pt 0in 0in 0in">
<p class="MsoNormal"><b><span style="font-size:10.0pt;font-family:"Tahoma","sans-serif"">From:</span></b><span style="font-size:10.0pt;font-family:"Tahoma","sans-serif""> engine-devel-bounces@ovirt.org [mailto:engine-devel-bounces@ovirt.org]
<b>On Behalf Of </b>Vojtech Szocs<br>
<b>Sent:</b> Thursday, August 23, 2012 8:14 AM<br>
<b>To:</b> Chris Frantz<br>
<b>Cc:</b> engine-devel<br>
<b>Subject:</b> Re: [Engine-devel] UI Plugins configuration</span></p>
</div>
</div>
<p class="MsoNormal"> </p>
<div>
<p class="MsoNormal"><span style="color:black">Hi Chris,<br>
<br>
thanks for taking the time to make this patch, these are some excellent ideas! (CC'ing engine-devel so that we can discuss this with other guys as well)<br>
<br>
First of all, I really like the way you designed plugin source page URLs (going through
<em>PluginSourcePageServlet</em>), e.g. "/webadmin/webadmin/plugin/<pluginName>/<pluginSourcePage>.html", plus the concept of "path" JSON attribute.<br>
<br>
<i>WebadminDynamicHostingServlet</i> loads and caches all plugin definitions (<i>*.json</i> files), and directly embeds them into WebAdmin host page as
<i>pluginDefinitions</i> JavaScript object. I'm assuming that <i>pluginDefinitions</i> object will now look like this:<br>
<br>
</span><span style="font-family:"Courier New";color:black">var pluginDefinitions = {<br>
"test": {<br>
"name": "test",<br>
"version": "1.0",<br>
"url": "/webadmin/webadmin/plugin/test/foo.html",<br>
"path": "/tmp",<br>
"config": {"a":1, "b":2, "c":3}<br>
}<br>
}</span><span style="color:black"><br>
<br>
Originally, the <i>pluginDefinitions</i> object looked like this:<br>
</span><span style="font-family:"Courier New";color:black"><br>
var pluginDefinitions = {<br>
"test": "/webadmin/webadmin/plugin/test/foo.html" // Simple pluginName -> pluginSourcePageUrl mappings<br>
}</span><span style="color:black"><br>
<br>
This is because PluginManager (WebAdmin) only needs <i>pluginName</i> ("name") and
<i>pluginSourcePageUrl</i> ("url") during startup, when creating plugin iframe. But this can be changed :)<br>
<br>
Plugin "version" makes sense, plus the plugin configuration object ("config") can be useful directly on the client. Let me explain:<br>
<br>
Originally, plugin configuration was supposed to be passed to actual plugin code (through immediately-invoked-function-expression, or IIFE), just like this:<br>
</span><span style="font-family:"Courier New";color:black"><br>
(function (pluginApi, pluginConfig) { // JavaScript IIFE<br>
// ... actual plugin code ...<br>
})(<br>
parent.pluginApi, /* reference to global pluginApi object */<br>
{"a":1, "b":2, "c":3} /* embedded plugin configuration as JavaScript object */<br>
);</span><span style="color:black"><br>
<br>
The whole purpose of <i>PluginSourcePageServlet</i> was to "wrap" actual plugin code into HTML, so that users don't need to write HTML pages for their plugins manually.
<i>PluginSourcePageServlet</i> would handle any plugin dependencies (placed into HTML head), with actual plugin code being wrapped into IIFE, as shown above. Plugin configuration was meant to be stored in a separate file, e.g.
<i><pluginName>-config.json</i>, so that users could change the default plugin configuration to suit their needs.<br>
<br>
Inspired by your patch, rather than reading/embedding plugin configuration when serving plugin HTML page (<i>PluginSourcePageServlet</i>), it's even better to have the plugin configuration embedded directly into WebAdmin host page, along with introducing new
<i>pluginApi</i> function to retrieve the plugin configuration object.<br>
<br>
Based on this, I suggest following modifications to the original concept:<br>
<br>
- modify original <i>pluginDefinitions</i> structure, from <i>pluginName -> pluginSourcePageUrl</i>, to
<i>pluginName -> pluginDefObject</i><br>
- <i>pluginDefObject</i> is basically a subset of physical plugin definition (<i>test.json</i>, see below), suitable for use on the client<br>
- add following attributes to <i>pluginDefObject</i>: <i>version</i>, <i>url</i>,
<i>config</i><br>
* note #1: <i>name</i> is not needed, since it's already the key of <i>pluginName -> pluginDefObject</i> mapping<br>
* note #2: <i>path</i> is not needed on the client (more on this below)<br>
- introduce <i>pluginApi.config(pluginName)</i> function for plugins to retrieve their configuration object, and remove
<i>pluginConfig</i> parameter from main IIFE (as shown above)<br>
<br>
[a] Physical plugin definition file (JSON) might be located at oVirt "DataDir", e.g.
<i>/usr/share/ovirt-engine/ui-plugins/test.json</i>, for example:<br>
</span><span style="font-family:"Courier New";color:black"><br>
{<br>
"name": "test",<br>
"version": "1.0",<br>
"url": "/webadmin/webadmin/plugin/test/start.html",<br>
"path": "/tmp",<br>
"config": "test-config.json"<br>
}</span><span style="color:black"><br>
<br>
[b] Plugin configuration file (JSON) might be located at oVirt "ConfigDir", e.g. <i>
/etc/ovirt-engine/ui-plugins/test-config.json</i>, for example:<br>
</span><span style="font-family:"Courier New";color:black"><br>
{<br>
"a":1, "b":2, "c":3<br>
}</span><span style="color:black"><br>
<br>
[c] Finally, plugin static resources (plugin source page, actual plugin code, plugin dependencies, CSS/images, etc.) would be located at
<i>/tmp</i> (as shown in [a]), for example:<br>
</span><span style="font-family:"Courier New";color:black"><br>
/tmp/start.html -> plugin source page, used to load actual plugin code<br>
/tmp/test.js -> actual plugin code<br>
/tmp/deps/jquery-min.js -> simulate 3rd party plugin dependency</span><span style="color:black"><br>
<br>
For example:<br>
"/webadmin/webadmin/plugin/test/start.html" will be mapped to <i>/tmp/start.html</i><br>
"/webadmin/webadmin/plugin/test/deps/jquery-min.js" will be mapped to <i>/tmp/deps/jquery-min.js</i><br>
<br>
This approach has some pros and cons:<br>
(+) plugin static resources can be served through <i>PluginSourcePageServlet</i> (pretty much like oVirt documentation resources, served through oVirt Engine root war's
<i>FileServlet</i>)<br>
(+) plugin author has complete control over plugin source page<br>
(-) plugin author actually needs to write plugin source page<br>
<br>
Overall, I think this approach is better than the previous one (where <i>PluginSourcePageServlet</i> took care of rendering plugin source page, but sacrificed some flexibility).<br>
<br>
By the way, here's what would happen behind the scenes:</span></p>
<ol start="1" type="1">
<li class="MsoNormal" style="color:black;mso-margin-top-alt:auto;margin-bottom:12.0pt;mso-list:l0 level1 lfo1">
user requests WebAdmin host page, <i>WebadminDynamicHostingServlet</i> loads and caches all plugin definitions [a] + plugin configurations [b] and constructs/embeds appropriate
<i>pluginDefinitions</i> JavaScript object</li><li class="MsoNormal" style="color:black;mso-margin-top-alt:auto;margin-bottom:12.0pt;mso-list:l0 level1 lfo1">
during WebAdmin startup, <i>PluginManager</i> registers the plugin (name/version/url/config), and creates/attaches the iframe to fetch plugin source page ansynchronously</li><li class="MsoNormal" style="color:black;mso-margin-top-alt:auto;margin-bottom:12.0pt;mso-list:l0 level1 lfo1">
<i>PluginSourcePageServlet</i> handles plugin source page request, resolves the correct path [c] and just streams the file content back to client</li></ol>
<p class="MsoNormal" style="margin-bottom:12.0pt"><span style="color:black">> 1. The plugin configuration files should probably have an "enabled" field and an "apiVersion" field that should be examined to determine whether or not to use the plugin.<br>
<br>
Sounds good, we can implement these later on :)<br>
<br>
> 2. I suspect the way I've modified PluginSourcePage makes it vulnerable to directory climbing attacks.<br>
<br>
Yes, but we can defend against these, restricting access only to plugin's "path" and its sub-directories.<br>
<br>
> 3. Is /usr/share/ovirt-engine the right place for the plugin config files?<br>
<br>
I suppose you mean plugin definition files [a], cannot tell for sure, but we can change this anytime :)<br>
<br>
<br>
Chris, please let me know what you think, and again - many thanks for sending the patch!<br>
<br>
<br>
Regards,<br>
Vojtech<br>
<br>
</span></p>
<div class="MsoNormal" style="text-align:center" align="center"><span style="color:black">
<hr id="zwchr" size="2" width="100%" align="center">
</span></div>
<p class="MsoNormal" style="margin-bottom:12.0pt"><span style="color:black"><br>
From: "Chris Frantz" <<a href="mailto:Chris.Frantz@hp.com" target="_blank">Chris.Frantz@hp.com</a>><br>
To: <a href="mailto:vszocs@redhat.com" target="_blank">vszocs@redhat.com</a><br>
Sent: Wednesday, August 22, 2012 7:56:45 PM<br>
Subject: UI Plugins configuration<br>
<br>
Vojtech,<br>
<br>
I decided to work on making the plugin patch a bit more configurable, following some of the ideas expressed by Itamar and others in the meeting yesterday. The attached patch is a simple first-attempt.<br>
<br>
Plugin configurations are stored in /usr/share/ovirt-engine/ui-plugins/*.json.<br>
<br>
Example:<br>
{<br>
"name": "test",<br>
"version": "1.0",<br>
"url": "/webadmin/webadmin/plugin/test/foo.html",<br>
"path": "/tmp",<br>
"config": {"a":1, "b":2, "c": 3}<br>
}<br>
<br>
The engine reads all of the *.json files in that directory to build the list of known plugins and gives that list to the webadmin.<br>
<br>
When webadmin loads a plugin, it requests the URL given in the plugin config file. The "plugin" URL is mapped to PluginSourcePage, which will translate the first part of the path ("test") into whatever path is stored in pluginConfig ("/tmp") in this case,
and then serve the static file (e.g. "/tmp/foo.html").<br>
<br>
I didn't use the renderPluginSourcePage() method in favor of just serving a static file, but I have no strong opinion on the matter. However, a plugin may want to store static resources at "path" and have the engine serve those resources. By just serving
files through PluginSourcePage, we don't need any other servlets to provide those resources.<br>
<br>
There is still a bit of work to do:<br>
<br>
1. The plugin configuration files should probably have an "enabled" field and an "apiVersion" field that should be examined to determine whether or not to use the plugin.<br>
<br>
2. I suspect the way I've modified PluginSourcePage makes it vulnerable to directory climbing attacks.<br>
<br>
3. Is /usr/share/ovirt-engine the right place for the plugin config files?<br>
<br>
Let me know what you think,<br>
--Chris</span></p>
</div>
</div>
</div><br></div></body></html>