Ruby Sunstone Development¶
In OpenNebula 5.0 the graphical interface code, Sunstone, was redesigned and modularized to improve the code readability and ease the task of adding new components. Now, each component (tab, panel, form…) is defined in a separate module using requirejs and HTML code is now defined in separate files using Handlebars templates. External libraries are handled using Bower, a Javascript package manager. Zurb Foundation is used for the styles and layout of the web and additional CSS styles are added using SASS. Grunt is used as a tasker to automate the different processes to generate the optimized files.
RequireJS¶
requirejs allows you to define blocks of functionality in different modules/files and then load and reuse them in other modules.
This is an example of the images-tab files layout:
images-tab.js
images-tab/
actions.js
buttons.js
datatable.js
dialogs/
...
panels/
...
And how the different modules are used in the images-tab file:
/* images-tab.js content */
define(function(require) {
var Buttons = require('./images-tab/buttons');
var Actions = require('./images-tab/actions');
var Table = require('./images-tab/datatable');
var _dialogs = [
require('./images-tab/dialogs/clone')
];
var _panels = [
require('./images-tab/panels/info'),
require('./images-tab/panels/vms'),
require('./images-tab/panels/snapshots')
];
})
The optimization tool provided by requirejs is used to group multiple files into a single minified file (dist/main.js). The options for the optimization are defined in the Gruntfile.js file using the r.js plugin for Grunt.
Handlebars¶
Handlebars provides the power necessary to let you build semantic templates and is largely compatible with Mustache templates.
<div id="comments">
{{#each comments}}
<h2><a href="/posts/{{../permalink}}#{{id}}">{{title}}</a></h2>
<div>{{body}}</div>
{{/each}}
</div>
The integration between Handlebars and requirejs is done using the Handlebars plugin for requirejs, that allows you to use the templates just adding a prefix to the require call
var TemplateHTML = require('hbs!./auth-driver/html');
return TemplateHTML({
'dialogId': this.dialogId,
'userCreationHTML': this.userCreation.html()
});
Additional helpers can be defined just creating a new file inside the app/templates/helpers
folder. These helpers will be available for any template of the app.
SASS & Foundation¶
The Zurb Foundation framework is used for the layout of the app. It provides a powerful grid system and different nifty widgets such as tabs, sliders, dialogs…
The Zurb Foundation configuration parameters are defined in the app/scss/settings.scss
file and new styles for the app can be added in the app/scss/app.scss
file. After modifying these files, the app.css and app.min.css files must be generated as explained in the following section.
Modifying JS & CSS files¶
Sunstone can be run in two different environments:
Production, using the minified css
css/app.min.css
and javascriptdist/main.js
files.Development, using the non minified css
css/app.css
and javascript filesapp/main.js
. Note that each file/module will be retrieved in a different HTTP request and the app will take longer to start, therefore it is not recommended for production environments
By default Sunstone is configured to use the minified files, therefore any change in the source code will not apply until the minified files are generated again. But you can set the env
parameter in sunstone-server.conf to dev
to use the non minified files and test your changes.
After testing the changes, the minified files can be generated by running the grunt requirejs
task or the scons sunstone=yes
command as explained in the following section. It is recommended to change again the env
parameter in sunstone-server.conf to prod
and test again the changes.
sunstone/
public/
app/ # JS sources
bower_components/ # External libraries
css/ # CSS optimized files
dist/ # JS optimized files
node_modules/ # Development dependencies
scss/ # CSS sources
bower.json # List of external libraries
Gruntfile.js # Tasks to optimize files
package.json # List of dev dependencies
routes/ # Custom routes for Sunstone Server
Ruby Sunstone Development Dependencies¶
Install nodejs v12 and npm v6
Install the following npm packages:
sudo npm install -g bower
sudo npm install -g grunt
sudo npm install -g grunt-cli
Warning
In case of error as below try to add --force
switch:
sudo npm install -g grunt-cli
npm ERR! code EEXIST
npm ERR! path /usr/local/bin/grunt
npm ERR! EEXIST: file already exists
npm ERR! File exists: /usr/local/bin/grunt
npm ERR! Remove the existing file and try again, or run npm
npm ERR! with --force to overwrite files recklessly.
Move to the Sunstone public folder
cd src/sunstone/public/
and run:
npm install # Dependencies defined in package.json
bower install # Dependenciees define in bower.json
Warning
In order to run npm and bower commands git
is required
Warning
In case of error related with wrong python syntax one needs to install python2.7:
dnf groupinstall "development tools" cd ~/ curl -L -O https://www.python.org/ftp/python/2.7.18/Python-2.7.18.tar.xz tar xf Python-2.7.18.tar.xz cd Python-2.7.18/ ./configure --prefix=/usr/local --enable-shared --enable-unicode=ucs4 make make install echo "export LD_LIBRARY_PATH=/usr/local/lib:/usr/local/bin/python2.7:$LD_LIBRARY_PATH" >> ~/.bash_profile source ~/.bash_profile
It’s needed to downgrade node version to 10.21:
node -v npm install -g n n 10.21
Try to build sunstone again:
npm install --python=python2.7
bower install
cd ~/one-ee
scons sunstone=yes
sudo ./install.sh -s -u oneadmin -g oneadmin
Building minified JS and CSS files¶
Scons includes an option to build the minified JS and CSS files. Sunstone development dependencies must be installed before running this command.
scons sunstone=yes
Or you can do this process manually by running the following commands:
Run the following command to generate the app.css file in the css folder, including any modification done to the app/scss/app.scss
and app/scss/settings.scss
files:
grunt sass
Run the following command to generate the minified files in the dist folder, including any modification done to the js files and the app.min.css in the css folder, based on the app.css file generated in the previous step:
grunt requirejs
These are the files generated by the grunt requirejs
command:
css
app.min.css
dist
login.js login.js.map main-dist.js main.js.map
console
spice.js spice.js.map vnc.js vnc.js.map
Warning
If the following error appears when running scons sunstone=yes or any of the grunt commands, you may have skip one step, so move to the Sunstone public folder and run ‘bower install’
Running "sass:dist" (sass) task
>> Error: File to import not found or unreadable: util/util
...
>> on line 43 of scss/_settings.scss
>> >> @import 'util/util';
>> ^
Warning: Use --force to continue.
Install.sh¶
By default the install.sh script will install all the files, including the non-minified ones. Providing the -p option, only the minified files will be installed.
The script generates a symbolic link main.js pointing to VAR_LOCATION/sunstone/main.js
. This file has been generated the first time that Sunstone starts, joining the base of Sunstone and the active addons.
Adding Custom Tabs¶
New tabs can be included following these steps:
Add your code inside the
app
folder. The tab must be provided as a module.Include the new tab as a dependency in the
app/main.js
file for theapp
module.
shim: {
'app': {
deps: [
'tabs/provision-tab',
'tabs/dashboard-tab',
'tabs/system-tab',
...
'tabs/mycustom-tab'
]
},
Include the tab configuration inside the different Sunstone views
/etc/one/sunstone-views/(admin|user|...).yaml
enabled_tabs:
- dashboard-tab
- system-tab
...
- mycustom-tab
tabs:
mycustom-apps-tab:
panel_tabs:
myscustom_info_tab: true
table_columns:
- 0 # Checkbox
- 1 # ID
- 2 # Name
actions:
MyCustom.create: true
MyCustom.refresh: true
Generate the minified files including the new tab by running the
grunt requirejs
command.
You can see an example of external tabs and custom routes for AppMarket in its own Github repository
Custom Routes for Sunstone Server¶
OpenNebula Sunstone server plugins consist of a set files defining custom routes. Custom routes will have priority over default routes and allow administrators to integrate their own custom controllers in the Sunstone Server.
Configuring Ruby Sunstone Server Plugins¶
It is very easy to enable custom plugins:
Place your custom routes in the
/usr/lib/one/sunstone/routes
folder.Modify
/etc/one/sunstone-server.conf
to indicate which files should be loaded, as shown in the following example:
# This will load ''custom.rb'' and ''other.rb'' plugin files.
:routes:
- custom
- other
Creating Ruby Sunstone Server Plugins¶
Ruby Sunstone server is a Sinatra application. A server plugin is simply a file containing one or several custom routes, as defined in sinatra applications.
The following example defines 4 custom routes:
get '/myplugin/myresource/:id' do
resource_id = params[:id]
# code...
end
post '/myplugin/myresource' do
# code
end
put '/myplugin/myresource/:id' do
# code
end
del '/myplugin/myresource/:id' do
# code
end
Custom routes take preference over Sunstone server routes. In order to ease debugging and ensure that plugins are not interfering with each other, we recommend however to place the routes in a custom namespace (myplugin
in the example).
From the plugin code routes, there is access to all the variables, helpers, etc. which are defined in the main sunstone application code. For example:
opennebula_client = $cloud_auth.client(session[:user])
sunstone_config = $conf
logger.info("New route")
vm3_log = @SunstoneServer.get_vm_log(3)
ESLint¶
Install ESLint:
sudo npm install -g eslint
After the installation you can initialize ESLint with your own rules or use OpenNebula’s configuration:
Use the command
eslint --init
to create your own .eslintrc.json with your personal configuration.
or
Manually create the .eslintrc.json and copy/paste the following code:
one/src/sunstone/public/.eslintrc.json
{
"env": {
"browser": true,
"es6": true
},
"parserOptions": {
"sourceType": "module"
},
"rules": {
"linebreak-style": [
"error",
"unix"
],
"quotes": [
"error",
"double"
],
"semi": [
"error",
"always"
],
"eqeqeq": 2,
"no-trailing-spaces": [
"error"
]
//new rules here
}
}
Note
The usage of ESLint is not mandatory but we recommend our contributors to use it, to be sure that the code is standardized.
More information about ESlint project.