Updated Branches: refs/heads/master f002b8e76 -> 75041b9c5
CLOUDSTACK-883. DOC. How to integrate your 3rd-party plugin with the UI. Contains brief third-party plugin code tutorial. Project: http://git-wip-us.apache.org/repos/asf/cloudstack/repo Commit: http://git-wip-us.apache.org/repos/asf/cloudstack/commit/75041b9c Tree: http://git-wip-us.apache.org/repos/asf/cloudstack/tree/75041b9c Diff: http://git-wip-us.apache.org/repos/asf/cloudstack/diff/75041b9c Branch: refs/heads/master Commit: 75041b9c55e536eb0a0020f983ec750df4af8832 Parents: f002b8e Author: Jessica <jessica.tomec...@citrix.com> Authored: Wed Aug 28 17:01:30 2013 -0700 Committer: Jessica <jessica.tomec...@citrix.com> Committed: Wed Aug 28 17:01:30 2013 -0700 ---------------------------------------------------------------------- docs/en-US/Developers_Guide.xml | 1 + docs/en-US/images/plugin1.jpg | Bin 0 -> 32999 bytes docs/en-US/images/plugin2.jpg | Bin 0 -> 35149 bytes docs/en-US/images/plugin3.jpg | Bin 0 -> 41983 bytes docs/en-US/images/plugin4.jpg | Bin 0 -> 32125 bytes docs/en-US/images/plugin_intro.jpg | Bin 0 -> 22247 bytes docs/en-US/third-party-ui-plugin.xml | 347 ++++++++++++++++++++++++++++++ 7 files changed, 348 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cloudstack/blob/75041b9c/docs/en-US/Developers_Guide.xml ---------------------------------------------------------------------- diff --git a/docs/en-US/Developers_Guide.xml b/docs/en-US/Developers_Guide.xml index e6fb5ce..7452e29 100644 --- a/docs/en-US/Developers_Guide.xml +++ b/docs/en-US/Developers_Guide.xml @@ -51,6 +51,7 @@ <xi:include href="api-calls.xml" xmlns:xi="http://www.w3.org/2001/XInclude" /> <xi:include href="working-with-usage-data.xml" xmlns:xi="http://www.w3.org/2001/XInclude" /> <xi:include href="storage-plugins.xml" xmlns:xi="http://www.w3.org/2001/XInclude" /> + <xi:include href="third-party-ui-plugin.xml" xmlns:xi="http://www.w3.org/2001/XInclude" /> <xi:include href="working-with-documentation.xml" xmlns:xi="http://www.w3.org/2001/XInclude" /> <xi:include href="tools.xml" xmlns:xi="http://www.w3.org/2001/XInclude" /> <xi:include href="event-types.xml" xmlns:xi="http://www.w3.org/2001/XInclude" /> http://git-wip-us.apache.org/repos/asf/cloudstack/blob/75041b9c/docs/en-US/images/plugin1.jpg ---------------------------------------------------------------------- diff --git a/docs/en-US/images/plugin1.jpg b/docs/en-US/images/plugin1.jpg new file mode 100644 index 0000000..970233d Binary files /dev/null and b/docs/en-US/images/plugin1.jpg differ http://git-wip-us.apache.org/repos/asf/cloudstack/blob/75041b9c/docs/en-US/images/plugin2.jpg ---------------------------------------------------------------------- diff --git a/docs/en-US/images/plugin2.jpg b/docs/en-US/images/plugin2.jpg new file mode 100644 index 0000000..9c8a610 Binary files /dev/null and b/docs/en-US/images/plugin2.jpg differ http://git-wip-us.apache.org/repos/asf/cloudstack/blob/75041b9c/docs/en-US/images/plugin3.jpg ---------------------------------------------------------------------- diff --git a/docs/en-US/images/plugin3.jpg b/docs/en-US/images/plugin3.jpg new file mode 100644 index 0000000..07fae79 Binary files /dev/null and b/docs/en-US/images/plugin3.jpg differ http://git-wip-us.apache.org/repos/asf/cloudstack/blob/75041b9c/docs/en-US/images/plugin4.jpg ---------------------------------------------------------------------- diff --git a/docs/en-US/images/plugin4.jpg b/docs/en-US/images/plugin4.jpg new file mode 100644 index 0000000..2bcec9f Binary files /dev/null and b/docs/en-US/images/plugin4.jpg differ http://git-wip-us.apache.org/repos/asf/cloudstack/blob/75041b9c/docs/en-US/images/plugin_intro.jpg ---------------------------------------------------------------------- diff --git a/docs/en-US/images/plugin_intro.jpg b/docs/en-US/images/plugin_intro.jpg new file mode 100644 index 0000000..113ffb3 Binary files /dev/null and b/docs/en-US/images/plugin_intro.jpg differ http://git-wip-us.apache.org/repos/asf/cloudstack/blob/75041b9c/docs/en-US/third-party-ui-plugin.xml ---------------------------------------------------------------------- diff --git a/docs/en-US/third-party-ui-plugin.xml b/docs/en-US/third-party-ui-plugin.xml new file mode 100644 index 0000000..62ee485 --- /dev/null +++ b/docs/en-US/third-party-ui-plugin.xml @@ -0,0 +1,347 @@ +<?xml version='1.0' encoding='utf-8' ?> +<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN" "file:///C:/Program%20Files%20(x86)/Publican/DocBook_DTD/docbookx.dtd" [ +<!ENTITY % BOOK_ENTITIES SYSTEM "cloudstack.ent"> +%BOOK_ENTITIES; +]> + <chapter id="third-party-ui-plugin"> + <!-- CS-17459 --> + <title>Third-Party UI Plugin Framework</title> + <para>Using the new third-party plugin framework, you can write and install extensions to + &PRODUCT;. The installed and enabled plugins will appear in the UI alongside the + other features. + The code for the plugin is simply placed in a special directory + within &PRODUCT;âs installed code at any time after &PRODUCT; installation. The new plugin + appears only when it is enabled by the cloud administrator.</para> + <mediaobject> + <imageobject> + <imagedata fileref="./images/plugin_intro.jpg"/> + </imageobject> + <textobject> + <phrase>plugin_intro.jpg: New plugin button in product navbar</phrase> + </textobject> + </mediaobject> + <para>The left navigation bar of the &PRODUCT; UI has a new Plugins button to help you work with UI plugins.</para> + <section id="plugin-howto-overview"> + <title>How to Write a Plugin: Overview</title> + <para>The basic procedure for writing a plugin is:</para> + <orderedlist> + <listitem> + <para>Write the code and create the other files needed. You will need the plugin code + itself (in Javascript), a thumbnail image, the plugin listing, and a CSS file.</para> + <mediaobject> + <imageobject> + <imagedata fileref="./images/plugin1.jpg"/> + </imageobject> + <textobject> + <phrase>plugin1.jpg: Write the plugin code</phrase> + </textobject> + </mediaobject> + <para>All UI plugins have the following set of files:</para> + <programlisting>+-- cloudstack/ + +-- ui/ + +-- plugins/ + +-- csMyFirstPlugin/ + +-- config.js --> Plugin metadata (title, author, vendor URL, etc.) + +-- icon.png --> Icon, shown on side nav bar and plugin listing + (should be square, and ~50x50px) + +-- csMyFirstPlugin.css --> CSS file, loaded automatically when plugin loads + +-- csMyFirstPlugin.js --> Main JS file, containing plugin code + </programlisting> + <para>The same files must also be present at /tomcat/webapps/client/plugins.</para> + </listitem> + <listitem> + <para>The &PRODUCT; administrator adds the folder containing your plugin code under the + &PRODUCT; PLUGINS folder.</para> + <mediaobject> + <imageobject> + <imagedata fileref="./images/plugin2.jpg"/> + </imageobject> + <textobject> + <phrase>plugin2.jpg: The plugin code is placed in the PLUGINS folder</phrase> + </textobject> + </mediaobject> + </listitem> + <listitem> + <para>The administrator also adds the name of your plugin to the plugin.js file in the + PLUGINS folder.</para> + <mediaobject> + <imageobject> + <imagedata fileref="./images/plugin3.jpg"/> + </imageobject> + <textobject> + <phrase>plugin3.jpg: The plugin name is added to plugin.js in the PLUGINS + folder</phrase> + </textobject> + </mediaobject> + </listitem> + <listitem> + <para>The next time the user refreshes the UI in the browser, your plugin will appear in + the left navigation bar.</para> + <mediaobject> + <imageobject> + <imagedata fileref="./images/plugin4.jpg"/> + </imageobject> + <textobject> + <phrase>plugin4.jpg: The plugin appears in the UI</phrase> + </textobject> + </mediaobject> + </listitem> + </orderedlist> + </section> + <section id="plugin-howto-details"> + <title>How to Write a Plugin: Implementation Details</title> + <para>This section requires an understanding of JavaScript and the &PRODUCT; API. You don't + need knowledge of specific frameworks for this tutorial (jQuery, etc.), since the + &PRODUCT; UI handles the front-end rendering for you.</para> + <para>There is much more to the &PRODUCT; UI framework than can be described here. The UI is + very flexible to handle many use cases, so there are countless options and variations. The + best reference right now is to read the existing code for the main UI, which is in the /ui + folder. Plugins are written in a very similar way to the main UI.</para> + <orderedlist> + <listitem> + <para><emphasis role="bold">Create the directory to hold your plugin.</emphasis></para> + <para>All plugins are composed of set of required files in the directory + /ui/plugins/pluginID, where pluginID is a short name for your plugin. It's recommended + that you prefix your folder name (for example, bfMyPlugin) to avoid naming conflicts + with other people's plugins.</para> + <para>In this example, the plugin is named csMyFirstPlugin.</para> + <programlisting>$ cd cloudstack/ui/plugins +$ mkdir csMyFirstPlugin +$ ls -l + +total 8 +drwxr-xr-x 2 bgregory staff 68 Feb 11 14:44 csMyFirstPlugin +-rw-r--r-- 1 bgregory staff 101 Feb 11 14:26 plugins.js + </programlisting> + </listitem> + <listitem> + <para><emphasis role="bold">Change to your new plugin directory.</emphasis></para> + <programlisting>$ cd csMyFirstPlugin + </programlisting> + </listitem> + <listitem> + <para><emphasis role="bold">Set up the listing.</emphasis></para> + <para>Add the file config.js, using your favorite editor.</para> + <programlisting>$ vi config.js</programlisting> + <para>Add the following content to config.js. This information will be displayed on the + plugin listing page in the UI:</para> + <programlisting>(function (cloudStack) { + cloudStack.plugins.csMyFirstPlugin.config = { + title: 'My first plugin', + desc: 'Tutorial plugin', + externalLink: 'http://www.cloudstack.org/', + authorName: 'Test Plugin Developer', + authorEmail: 'plugin.develo...@example.com' + }; +}(cloudStack)); + </programlisting> + </listitem> + <listitem> + <para><emphasis role="bold">Add a new main section.</emphasis></para> + <para>Add the file csMyFirstPlugin.js, using your favorite editor.</para> + <programlisting>$ vi csMyFirstPlugin.js</programlisting> + <para>Add the following content to csMyFirstPlugin.js:</para> + <programlisting>(function (cloudStack) { + cloudStack.plugins.csMyFirstPlugin = function(plugin) { + plugin.ui.addSection({ + id: 'csMyFirstPlugin', + title: 'My Plugin', + preFilter: function(args) { + return isAdmin(); + }, + show: function() { + return $('<div>').html('Content will go here'); + } + }); + }; +}(cloudStack)); + </programlisting> + </listitem> + <listitem> + <para><emphasis role="bold">Register the plugin.</emphasis></para> + <para>You now have the minimal content needed to run the plugin, so you can activate the + plugin in the UI by adding it to plugins.js. First, edit the file:</para> + <programlisting>$ cd cloudstack/ui/plugins +$ vi plugins.js + </programlisting> + <para>Now add the following to plugins.js:</para> + <programlisting>(function($, cloudStack) { + cloudStack.plugins = [ + 'csMyFirstPlugin' + ]; +}(jQuery, cloudStack)); + </programlisting> + </listitem> + <listitem> + <para><emphasis role="bold">Check the plugin in the UI.</emphasis></para> + <para>First, copy all the plugin code that you have created so far to + /tomcat/webapps/client/plugins. Then refresh the browser and click Plugins in the side + navigation bar. You should see your new plugin.</para> + </listitem> + <listitem> + <para><emphasis role="bold">Make the plugin do something.</emphasis></para> + <para>Right now, you just have placeholder content in the new plugin. It's time to add + real code. In this example, you will write a basic list view, which renders data from + an API call. You will list all virtual machines owned by the logged-in user. To do + this, replace the 'show' function in the plugin code with a 'listView' block, + containing the required syntax for a list view. To get the data, use the + listVirtualMachines API call. Without any parameters, it will return VMs only for your + active user. Use the provided 'apiCall' helper method to handle the server call. Of + course, you are free to use any other method for making the AJAX call (for example, + jQuery's $.ajax method).</para> + <para>First, open your plugin's JavaScript source file in your favorite editor:</para> + <programlisting>$ cd csMyFirstPlugin +$ vi csMyFirstPlugin.js + </programlisting> + <para>Add the following code in csMyFirstPlugin.js:</para> + <programlisting>(function (cloudStack) { + cloudStack.plugins.csMyFirstPlugin = function(plugin) { + plugin.ui.addSection({ + id: 'csMyFirstPlugin', + title: 'My Plugin', + preFilter: function(args) { + return isAdmin(); + }, + + // Render page as a list view + listView: { + id: 'testPluginInstances', + fields: { + name: { label: 'label.name' }, + instancename: { label: 'label.internal.name' }, + displayname: { label: 'label.display.name' }, + zonename: { label: 'label.zone.name' } + }, + dataProvider: function(args) { + // API calls go here, to retrive the data asynchronously + // + // On successful retrieval, call + // args.response.success({ data: [data array] }); + plugin.ui.apiCall('listVirtualMachines', { + success: function(json) { + var vms = json.listvirtualmachinesresponse.virtualmachine; + + args.response.success({ data: vms }); + }, + error: function(errorMessage) { + args.response.error(errorMessage) + } + }); + } + } + }); + }; +}(cloudStack)); + </programlisting> + </listitem> + <listitem> + <para><emphasis role="bold">Test the plugin.</emphasis></para> + <para>First, copy all the plugin code that you have created so far to + /tomcat/webapps/client/plugins. Then refresh the browser. You can see that your + placeholder content was replaced with a list table, containing 4 columns of virtual + machine data.</para> + </listitem> + <listitem> + <para><emphasis role="bold">Add an action button.</emphasis></para> + <para>Let's add an action button to the list view, which will reboot the VM. To do this, + add an actions block under listView. After specifying the correct format, the actions + will appear automatically to the right of each row of data.</para> + <programlisting>$ vi csMyFirstPlugin.js + </programlisting> + <para>Now add the following new code in csMyFirstPlugin.js. (The dots ... show where we + have omitted some existing code for the sake of space. Don't actually cut and paste + that part):</para> + <programlisting>... + listView: { + id: 'testPluginInstances', + ... + + actions: { + // The key/ID you specify here will determine what icon is + // shown in the UI for this action, + // and will be added as a CSS class to the action's element + // (i.e., '.action.restart') + // + // -- here, 'restart' is a predefined name in &PRODUCT; that will + // automatically show a 'reboot' arrow as an icon; + // this can be changed in csMyFirstPlugin.css + restart: { + label: 'Restart VM', + messages: { + confirm: function() { return 'Are you sure you want to restart this VM?' }, + notification: function() { return 'Rebooted VM' } + }, + action: function(args) { + // Get the instance object of the selected row from context + // + // -- all currently loaded state is stored in 'context' as objects, + // such as the selected list view row, + // the selected section, and active user + // + // -- for list view actions, the object's key will be the same as + // listView.id, specified above; + // always make sure you specify an 'id' for the listView, + // or else it will be 'undefined!' + var instance = args.context.testPluginInstances[0]; + + plugin.ui.apiCall('rebootVirtualMachine', { + // These will be appended to the API request + // + // i.e., rebootVirtualMachine&id=... + data: { + id: instance.id + }, + success: function(json) { + args.response.success({ + // This is an async job, so success here only indicates + // that the job was initiated. + // + // To pass the job ID to the notification UI + // (for checking to see when action is completed), + // '_custom: { jobID: ... }' needs to always be passed on success, + // in the same format as below + _custom: { jobId: json.rebootvirtualmachineresponse.jobid } + }); + }, + + + error: function(errorMessage) { + args.response.error(errorMessage); // Cancel action, show error message returned + } + }); + }, + + // Because rebootVirtualMachine is an async job, we need to add + // a poll function, which will perodically check + // the management server to see if the job is ready + // (via pollAsyncJobResult API call) + // + // The plugin API provides a helper function, 'plugin.ui.pollAsyncJob', + / which will work for most jobs + // in &PRODUCT; + notification: { + poll: plugin.ui.pollAsyncJob + } + } + }, + + dataProvider: function(args) { + ... +... + </programlisting> + </listitem> + <listitem> + <para><emphasis role="bold">Add the thumbnail icon.</emphasis></para> + <para>Create an icon file; it should be square, about 50x50 pixels, and named icon.png. + Copy it into the same directory with your plugin code: + cloudstack/ui/plugins/csMyFirstPlugin/icon.png.</para> + </listitem> + <listitem> + <para><emphasis role="bold">Add the stylesheet.</emphasis></para> + <para>Create a CSS file, with the same name as your .js file. Copy it into the same + directory with your plugin code: + cloudstack/ui/plugins/csMyFirstPlugin/csMyFirstPlugin.css.</para> + </listitem> + </orderedlist> + </section> + </chapter>