mirror of
				https://github.com/apache/cloudstack.git
				synced 2025-11-04 00:02:37 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			365 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			XML
		
	
	
	
	
	
			
		
		
	
	
			365 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			XML
		
	
	
	
	
	
<?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;
 | 
						||
]>
 | 
						||
<!-- Licensed to the Apache Software Foundation (ASF) under one
 | 
						||
    or more contributor license agreements.  See the NOTICE file
 | 
						||
    distributed with this work for additional information
 | 
						||
    regarding copyright ownership.  The ASF licenses this file
 | 
						||
    to you under the Apache License, Version 2.0 (the
 | 
						||
    "License"); you may not use this file except in compliance
 | 
						||
    with the License.  You may obtain a copy of the License at
 | 
						||
    
 | 
						||
    http://www.apache.org/licenses/LICENSE-2.0
 | 
						||
    
 | 
						||
    Unless required by applicable law or agreed to in writing,
 | 
						||
    software distributed under the License is distributed on an
 | 
						||
    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 | 
						||
    KIND, either express or implied.  See the License for the
 | 
						||
    specific language governing permissions and limitations
 | 
						||
    under the License.
 | 
						||
-->
 | 
						||
   <chapter id="third-party-ui-plugin">
 | 
						||
      <!-- CLOUDSTACK-883 -->
 | 
						||
      <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.developer@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>
 |