DEV Community

Abby Nduta
Abby Nduta

Posted on • Updated on

Delving into Angular in OpenStack Horizon: Directives

I've been exploring Angular within the context of OpenStack Horizon. First, it's important to know that the Horizon project uses Angular1 (AngularJS) which is quite old and deprecated.It's an old code base.

Your best bet at learning Angular1 is docs or this very useful book written by Brad Green and Shyam Seshadri.

Lastly, the code base uses ES5. Keeping this in mind will come in handy as it may be difficult to find resources or learning material. Most resources will teach you up-to-date modern Angular (Angular 2 and all subsequent versions).

Terminologies

It's advisable to learn AngularJS before tinkering with Horizon. I'll define the main concepts you need to know, though this blog focuses on the HTML, where you find directives.

AngularJS is based on the MVC concept, Model-View-Controller.

This means that you separate your code into

  • models - the data to use in your project
  • views - the HTML that the user/DOM interacts with
  • controllers - where you keep your code's logic

Views

Views in Horizon are HTML files. They are made up of HTML elements and directives.

Directives are some sort of tags that we use to make our views more dynamic. Both AngularJS and Horizon have built-in directives, though you can create custom ones too. We'll learn more about them in our example.

Horizon also uses UI Bootstrap directives, though we won't be encountering them in this article.

Models

Horizon models are a bit different, since we make API calls to the OpenStack service we want, for example, Glance for images, rather than from a database.

We, however, within the context of Angular use models to store properties, for example.

Controllers

We make the API calls to the OpenStack service in the controller, return the results, and show them via the view.

Modules

Modules come in when we want to work with dependencies.

Services

Services come in when we want to perform specific tasks.

I've tried to keep the jargon simple, but you can learn more about the concepts from the AngularJS docs

Directives in Horizon

Like we mentioned, directives are 'special tags' we add to our views to make them dynamic.

Both AngularJS and Horizon have their own directives. I'm going to be making changes to the Images table on the Horizon dashboard.

Image description

Specifically, to the additional information you see when you expand the chevron arrow (>).

Image description

To begin, let's look at how the view looks like. Here's the drawer.html file we'll be working with:

https://github.com/openstack/horizon/blob/master/openstack_dashboard/static/app/core/images/details/drawer.html
Enter fullscreen mode Exit fullscreen mode
<div ng-controller="horizon.app.core.images.DrawerController as drawerCtrl">

  <hz-resource-property-list
    resource-type-name="OS::Glance::Image"
    item="item"
    property-groups="[
      ['name', 'id'],
      ['visibility', 'protected'],
      ['disk_format', 'size'],
      ['min_disk', 'min_ram']]">
  </hz-resource-property-list>

  <div class="row" ng-if="drawerCtrl.metadataDefs">
    <div class="col-sm-12">
      <metadata-display
        available="::drawerCtrl.metadataDefs"
        existing="item.properties || item">
      </metadata-display>
    </div>
  </div>
</div>
Enter fullscreen mode Exit fullscreen mode

Right off the bat, you can see some 'strange' tags like ng-controller, hz-resource-property-list, and ng-if.

These are what we call directives. The 'ng' prefix means that they are built-in Angular directives. The 'hz' directives means that they are Horizon directives.

ng-controller specifies the controller we are going to use in the view. This should be the DrawerController aliased as drawerCtrl, and defined in the specified file path.

hz-resource-property-list is used to display a set of properties, and shows them in key-value pairs. It uses property groups, which divides the properties into lists.

These groups will be displayed as columns. We use any number of columns.

The groups can have as many properties as you need them to have.

In our code, inside the hzResourcePropertyList (this is how we write directives, though we use lowercase with dashes in the view as in hz-resource-property-list) we have 'property-groups' which shows that we'll display name, id, visibility, protected, disk_format, size, min_disk, and min_ram.

  <hz-resource-property-list
    resource-type-name="OS::Glance::Image"
    item="item"
    property-groups="[
      ['name', 'id'],
      ['visibility', 'protected'],
      ['disk_format', 'size'],
      ['min_disk', 'min_ram']]">
  </hz-resource-property-list>
Enter fullscreen mode Exit fullscreen mode

resource-type-name also has some strange syntax. Horizon uses something called HEAT type names to specify the name of the resource we're working with, Glance in this case.

<hz-resource-property-list
    resource-type-name="OS::Glance::Image"
    item="item"
    property-groups="[
      ['name', 'id'],
      ['visibility', 'protected'],
      ['disk_format', 'size'],
      ['min_disk', 'min_ram']]">
  </hz-resource-property-list>
Enter fullscreen mode Exit fullscreen mode

item is the data object that contains the properties that we want to display.

ng-if is a built-in AngularJS directive that helps us show information conditionally, just like you would in an if-else statement.

Additional Files

To understand the code in the drawer.html file, you need to look at additional files.

For example, ng-controller points to a controller named "Drawer Controller". This is defined in

https://github.com/openstack/horizon/blob/master/openstack_dashboard/static/app/core/images/summary.controller.js
Enter fullscreen mode Exit fullscreen mode
(function() {
  'use strict';

  /**
   * @ngdoc controller
   * @name horizon.app.core.images.DrawerController
   * @description
   * This is the controller for the images drawer (summary) view.
   * Its primary purpose is to provide the metadata definitions to
   * the template via the ctrl.metadataDefs member.
   */
  angular
    .module('horizon.app.core.images')
    .controller('horizon.app.core.images.DrawerController', controller);

  controller.$inject = [
    'horizon.app.core.openstack-service-api.glance',
    'horizon.app.core.images.resourceType'
  ];

  var metadataDefs;

  function controller(glance, imageResourceType) {
    var ctrl = this;

    ctrl.metadataDefs = metadataDefs;
    if (!ctrl.metadataDefs) {
      applyMetadataDefinitions();
    }

    function applyMetadataDefinitions() {
      glance.getNamespaces({resource_type: imageResourceType}, true)
        .then(function setMetadefs(data) {
          ctrl.metadataDefs = data.data.items;
        });
    }
  }

})();
Enter fullscreen mode Exit fullscreen mode

I won't delve into this code much.

You might want to also look at the overview file

https://github.com/openstack/horizon/blob/master/openstack_dashboard/static/app/core/images/details/overview.html
Enter fullscreen mode Exit fullscreen mode

It displays all the possible properties we can get from Glance.

On the dashboard, you'll see all these properties when you click on the name of the image, in this case, 'ndutas_image'
Image description

You should see all the properties.
Image description

Of importance is that the properties are grouped into three. This is reflected in the overview.html code.

<div ng-controller="ImageOverviewController as ctrl">
  <div class="row">
    <div class="col-md-6 detail">
      <h3 translate>Image</h3>
      <hr>
      <hz-resource-property-list
        resource-type-name="OS::Glance::Image"
        cls="dl-horizontal"
        item="ctrl.image"
        property-groups="[[
          'id', 'name', 'type', 'status', 'size', 'min_disk', 'min_ram', 'disk_format',
          'container_format', 'created_at', 'updated_at']]">
      </hz-resource-property-list>
    </div>
    <div class="col-md-6 detail">
      <h3>{$ 'Security' | translate $}</h3>
      <hr>
      <dl class="dl-horizontal">
        <dt translate>Owner</dt>
        <dd>{$ ctrl.image.owner $}</dd>
        <dt translate>Filename</dt>
        <dd>{$ ctrl.image.properties.filename | noValue $}</dd>
        <dt translate>Visibility</dt>
        <dd>{$ ctrl.image | imageVisibility:ctrl.projectId $}</dd>
        <dt translate>Protected</dt>
        <dd>{$ ctrl.image.protected | yesno $}</dd>
        <dt translate>Checksum</dt>
        <dd>{$ ctrl.image.checksum | noValue $}</dd>
      </dl>
    </div>
  </div>
  <div class="row">
    <div class="col-md-6 detail">
      <h3 translate>Custom Properties</h3>
      <hr>
      <dl class="dl-horizontal">
        <div ng-repeat="prop in ctrl.image.properties">
          <dt data-toggle="tooltip" title="{$ prop.name $}">{$ ctrl.resourceType.label(prop.name) $}</dt>
          <dd>{$ prop.value $}</dd>
        </div>
      </dl>
    </div>
  </div>
</div>
Enter fullscreen mode Exit fullscreen mode

This will become important when we start to make changes.

If you want to see where this properties are listed, go to

https://github.com/openstack/horizon/blob/master/openstack_dashboard/static/app/core/images/images.module.js
Enter fullscreen mode Exit fullscreen mode

Check the imageProperties function.

  function imageProperties(imagesService, statuses) {
    return {
      id: gettext('ID'),
      checksum: gettext('Checksum'),
      members: gettext('Members'),
      min_disk: gettext('Min. Disk'),
      min_ram: gettext('Min. RAM'),
      name: gettext('Name'),
      owner: gettext('Owner'),
      tags: gettext('Tags'),
      'updated_at': {label: gettext('Updated At'), filters: ['mediumDate'] },
      virtual_size: gettext('Virtual Size'),
      visibility: gettext('Visibility'),
      description: gettext('Description'),
      architecture: gettext('Architecture'),
      kernel_id: gettext('Kernel ID'),
      ramdisk_id: gettext('Ramdisk ID'),
      'created_at': {label: gettext('Created At'), filters: ['mediumDate'] },
      container_format: { label: gettext('Container Format'), filters: ['uppercase'] },
      disk_format: { label: gettext('Disk Format'), filters: ['noValue', 'uppercase'] },
      is_public: { label: gettext('Is Public'), filters: ['yesno'] },
      type: { label: gettext('Type')},
      'protected': { label: gettext('Protected'), filters: ['yesno'] },
      size: { label: gettext('Size'), filters: ['bytes'] },
      status: { label: gettext('Status'), values: statuses }
    };
  }
Enter fullscreen mode Exit fullscreen mode

Showing more properties in the images drawer

I want to add more properties to the images drawer, specifically, checksum, os_hash_value and os_hash_algo.

Currently, these do not exist on the dashboard.

Image description

Checksum is listed under the main properties to be shown in images.module.js.

This means that we can display it under the hzResourcePropertyList directive in drawer.html.

To do this, we need to add it as a property group.

  <hz-resource-property-list
    resource-type-name="OS::Glance::Image"
    item="item"
    property-groups="[
      ['name', 'id'],
      ['visibility', 'protected'],
      ['disk_format', 'size'],
      ['min_disk', 'min_ram'],
      ['checksum']]">
  </hz-resource-property-list>
Enter fullscreen mode Exit fullscreen mode

os_hash_value and os_hash_algo are however not listed. If we check overview.html, there's custom properties code.

  <div class="row">
    <div class="col-md-6 detail">
      <h3 translate>Custom Properties</h3>
      <hr>
      <dl class="dl-horizontal">
        <div ng-repeat="prop in ctrl.image.properties">
          <dt data-toggle="tooltip" title="{$ prop.name $}">{$ ctrl.resourceType.label(prop.name) $}</dt>
          <dd>{$ prop.value $}</dd>
        </div>
      </dl>
    </div>
  </div>
Enter fullscreen mode Exit fullscreen mode

os_hash_value and os_hash_algo are probably custom properties. How do we display them in the drawer?

Given that custom properties are still shown when you click the name of the image, it means that they are returned when we make the API call from Glance.

Image description

Since we don't want to show all the custom properties, we can conditionally show just os_hash_algo and os_hash_value.

  <div class="row" >
    <div class="col-sm-12">
      <dl>
        <div ng-if="item.properties.os_hash_algo">
          <dt>os_hash_algo</dt>
          <dd>{$ item.properties.os_hash_algo $}</dd>
        </div>
        <div ng-if="item.properties.os_hash_value">
          <dt>os_hash_value</dt>
          <dd>{$ item.properties.os_hash_value $}</dd>
        </div>
      </dl>
    </div>
  </div>
Enter fullscreen mode Exit fullscreen mode

Restart the server

tox -e runserver
Enter fullscreen mode Exit fullscreen mode

Go to

localhost:8000
Enter fullscreen mode Exit fullscreen mode

We now see the changes. Note that you may need to restart your computer if you can't see any changes.

Image description

Forging Forward

This is a very simple introduction to Angular in OpenStack Horizon. There's definitely lots more to learn!!! I hope this guides you to try out Angular within the OpenStack Horizon context.

Resources

https://superuser.openinfra.dev/articles/dive-deep-into-openstack-horizon/
https://docs.angularjs.org/guide
https://www.amazon.com/AngularJS-Brad-Green/dp/1449344852
https://www.w3schools.com/js/js_es5.asp
https://www.tutorialspoint.com/angularjs/angularjs_mvc_architecture.htm
https://github.com/openstack/horizon/

Top comments (0)