Building Custom AngularJS Panels in the OpenStack Dashboard (Horizon)

In the Kilo release of Horizon, the OpenStack Dashboard, we started to see the rise of AngularJS based user interfaces. This included some features, like a compute launch instance wizard you can opt into, and a number of re-usable AngularJS directives you can build your own add-ons with.

The need to build add-ons isn't something new. Every web platform has it and I'm aware of many cases and companies that have leveraged what's in Horizon. There's even a fairly straight forward tutorial covering what you need to build a classic non-AngularJS based panel. The gap in the tutorial is that it, as of today, doesn't cover what you can do with AngularJS.

This post provides an introduction into building AngularJS based panels. This material is built on the tutorial which is a prerequisite.

Adding AngularJS To Your Panel

In the section of the tutorial that covers enabling your panel it provides an example file named _50_mydashboard.py. This file is where you tell it about your AngularJS and related tests. To illistrate this here's an expanded version with some AngularJS.

# The name of the dashboard to be added to HORIZON['dashboards']. Required.
DASHBOARD = 'mydashboard'

# If set to True, this dashboard will not be added to the settings.
DISABLED = False

# A list of applications to be added to INSTALLED_APPS.
ADD_INSTALLED_APPS = [
  'openstack_dashboard.dashboards.mydashboard',
]

# The AngularJS module name.
ADD_ANGULAR_MODULES = ['mydashboard']

# AngularJS files to load for the application.
ADD_JS_FILES = [
    'mydashboard/dashboard.module.js',
]

# Files to load when tests are being run.
ADD_JS_SPEC_FILES = [
    'mydashboard/dashboard.module.spec.js',
]

The Files loaded here are relative to the static directory. If you're building an add-on that resides within the Horizon codebase the code will need to be in the static folder within the openstack_dashbaord directory. If you're building the add-on as a separate project symlinked into the environment you'll need to have the files within the static folder for that project.

The files listed in ADD_JS_FILES are loaded on each page and will look for the appropriate markup or configuration like any other AngularJS file. The files listed in ADD_JS_SPEC_FILES are loaded when the Jasmine test suite is run.

ADD_ANGULAR_MODULES lists the modules within your code. This is important because Horizon creates an AngularJS application and sets what's used by the application. The list is generated by Horison at runtime and this is your opportunity to inject your modules into the application.

The order the modules are listed and JavaScript is loaded depends on the order of the files in the enabled directory and within your own listing.

Using The Horizon Directives

Horizon contains a number of AngularJS directives that you can use in your own code. These directives make it fairly easy to start building AngularJS into your panels while inheriting the same patterns and styled UI used elsewhere.

To learn how to use each directive start with the documentation in the directive. For example, the table directive as an example of the table and details what you can do. Each of the directives are this way.

There are directives for tables (including filter/searching), modals, action lists, wizards (you can create your own wizards like the launch instance one), and more.

AngularJS Calling APIs

The AngularJS code within Horizon regularly calls REST APIs to get data to display. Due to issues like CORS and the desire to control the data going to JavaScript there is an API layer for Horizon. This API layer has some helpers to make it easy to handle REST request, works with the rest of Horizon for authentication/authorization, and constrains the calls to AJAX requests so they aren't general endpoints.

The easiest way to start creating Horizon API endpoints for your JavaScript to call is by creating a python file in your panel called something like rest.py. An example file might look like:

from django.views import generic
from openstack_dashboard.api.rest import urls
from openstack_dashboard.api.rest import utils

@urls.register
class MyDashboardThingy(generic.View):

    url_regex = r'mydashboard/thingy/$'

    @utils.ajax()
    def get(self, request):

        return {'items': []}

This file specifies the handling of a GET request to /api/mydashboard/thingy. In this case it returns a JSON response with a key of items containing an empty array.

You can define functions when they have the decorator @utils.ajax() for get, post, and the other REST methods.

By using the decorator @urls.register on the class the REST API system within the OpenStack Dashboard knows this provides a REST service that should be made available. The path within url_regex is the path for the GET, POST, and other requests. In order for the auto-registration to work there needs to be an addition to the __init__.py file for the directory the rest.py is within. The __init__.py needs to contain an import for the rest.py file. For example:

import rest

If you want to see how the registration is handled checkout the rest urls file. The openstack_dashbaord/api/rest directory contains numerous examples of APIs built for the different OpenStack services.

This Is Just The Beginning

In the Kilo release a real foundation for AngularJS panels landed. This provides a foundation for building custom panels and for re-working existing dashboard panels to operate in a more modern and UX friendly manner. This is a great time to start embracing AngularJS within Horizon.