mirror of
https://github.com/apache/cloudstack.git
synced 2025-11-02 11:52:28 +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>
|