diff --git a/docs/en-US/Developers_Guide.xml b/docs/en-US/Developers_Guide.xml
index e6fb5ce9a41..7452e29ecf2 100644
--- a/docs/en-US/Developers_Guide.xml
+++ b/docs/en-US/Developers_Guide.xml
@@ -51,6 +51,7 @@
+
diff --git a/docs/en-US/images/plugin1.jpg b/docs/en-US/images/plugin1.jpg
new file mode 100644
index 00000000000..970233d8475
Binary files /dev/null and b/docs/en-US/images/plugin1.jpg differ
diff --git a/docs/en-US/images/plugin2.jpg b/docs/en-US/images/plugin2.jpg
new file mode 100644
index 00000000000..9c8a6107ba9
Binary files /dev/null and b/docs/en-US/images/plugin2.jpg differ
diff --git a/docs/en-US/images/plugin3.jpg b/docs/en-US/images/plugin3.jpg
new file mode 100644
index 00000000000..07fae790e22
Binary files /dev/null and b/docs/en-US/images/plugin3.jpg differ
diff --git a/docs/en-US/images/plugin4.jpg b/docs/en-US/images/plugin4.jpg
new file mode 100644
index 00000000000..2bcec9f773a
Binary files /dev/null and b/docs/en-US/images/plugin4.jpg differ
diff --git a/docs/en-US/images/plugin_intro.jpg b/docs/en-US/images/plugin_intro.jpg
new file mode 100644
index 00000000000..113ffb32781
Binary files /dev/null and b/docs/en-US/images/plugin_intro.jpg differ
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 00000000000..62ee485b1d1
--- /dev/null
+++ b/docs/en-US/third-party-ui-plugin.xml
@@ -0,0 +1,347 @@
+
+
+%BOOK_ENTITIES;
+]>
+
+
+ Third-Party UI Plugin Framework
+ 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.
+
+
+
+
+
+ plugin_intro.jpg: New plugin button in product navbar
+
+
+ The left navigation bar of the &PRODUCT; UI has a new Plugins button to help you work with UI plugins.
+
+ How to Write a Plugin: Overview
+ The basic procedure for writing a plugin is:
+
+
+ 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.
+
+
+
+
+
+ plugin1.jpg: Write the plugin code
+
+
+ All UI plugins have the following set of files:
+ +-- 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
+
+ The same files must also be present at /tomcat/webapps/client/plugins.
+
+
+ The &PRODUCT; administrator adds the folder containing your plugin code under the
+ &PRODUCT; PLUGINS folder.
+
+
+
+
+
+ plugin2.jpg: The plugin code is placed in the PLUGINS folder
+
+
+
+
+ The administrator also adds the name of your plugin to the plugin.js file in the
+ PLUGINS folder.
+
+
+
+
+
+ plugin3.jpg: The plugin name is added to plugin.js in the PLUGINS
+ folder
+
+
+
+
+ The next time the user refreshes the UI in the browser, your plugin will appear in
+ the left navigation bar.
+
+
+
+
+
+ plugin4.jpg: The plugin appears in the UI
+
+
+
+
+
+
+ How to Write a Plugin: Implementation Details
+ 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.
+ 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.
+
+
+ Create the directory to hold your plugin.
+ 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.
+ In this example, the plugin is named csMyFirstPlugin.
+ $ 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
+
+
+
+ Change to your new plugin directory.
+ $ cd csMyFirstPlugin
+
+
+
+ Set up the listing.
+ Add the file config.js, using your favorite editor.
+ $ vi config.js
+ Add the following content to config.js. This information will be displayed on the
+ plugin listing page in the UI:
+ (function (cloudStack) {
+ cloudStack.plugins.csMyFirstPlugin.config = {
+ title: 'My first plugin',
+ desc: 'Tutorial plugin',
+ externalLink: 'http://www.cloudstack.org/',
+ authorName: 'Test Plugin Developer',
+ authorEmail: 'plugin.developer@example.com'
+ };
+}(cloudStack));
+
+
+
+ Add a new main section.
+ Add the file csMyFirstPlugin.js, using your favorite editor.
+ $ vi csMyFirstPlugin.js
+ Add the following content to csMyFirstPlugin.js:
+ (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));
+
+
+
+ Register the plugin.
+ 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:
+ $ cd cloudstack/ui/plugins
+$ vi plugins.js
+
+ Now add the following to plugins.js:
+ (function($, cloudStack) {
+ cloudStack.plugins = [
+ 'csMyFirstPlugin'
+ ];
+}(jQuery, cloudStack));
+
+
+
+ Check the plugin in the UI.
+ 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.
+
+
+ Make the plugin do something.
+ 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).
+ First, open your plugin's JavaScript source file in your favorite editor:
+ $ cd csMyFirstPlugin
+$ vi csMyFirstPlugin.js
+
+ Add the following code in csMyFirstPlugin.js:
+ (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));
+
+
+
+ Test the plugin.
+ 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.
+
+
+ Add an action button.
+ 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.
+ $ vi csMyFirstPlugin.js
+
+ 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):
+ ...
+ 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) {
+ ...
+...
+
+
+
+ Add the thumbnail icon.
+ 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.
+
+
+ Add the stylesheet.
+ 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.
+
+
+
+