Creating Custom User Interfaces

The DivvyCloud client is written in AngularJS and is currently at v1.3.16. We’ve exposed several global entry points so you can easily add custom full or partial page features to the main DivvyCloud ‘console’ app.

Required Package Structure

├── HamstersPlugin
    ├── html
    |   ├── init.js
    |   ├── hamster.html
    |   ├── hamster.controller.js
    |   ├── hamster.service.js
    |   └── plugin.css
    ├── plugin.json

** You are required to have a ‘/html’ directory in your package to load frontend resources. However you can organize files into subdirectories within /html.

Getting Started with plugin.json

Plugins that consist of more than one .py file are called Plugin packages. When a new plugin is detected, our PluginManager looks for a plugin.json configuration file to 1) determine if the directory is a plugin and 2) register the plugin’s dependencies.

// plugin.json
    "initial_js" : ["hamster.service.js", "hamster.controller.js", "hamster.html", "init.js", "plugin.css"],
    "initial_modules": [""]

Frontend resources are loaded sequentially per their position in the array. If plugin.json is empty, the PluginManager will automatically look for files named: * html/init.js *

Setup Flask Blueprint

To access static resources, our Flask webserver needs an internal reference to your plugin directory. We do this by registering what Flask calls a ‘blueprint’ which is a collection of API url endpoints. If you won’t be writing API endpoints, creating a blueprint namespace is sufficient.

Inside your file, register your blueprint.

from flask.blueprints import Blueprint
from DivvyPlugins.plugin_helpers import register_api_blueprint, unregister_api_blueprints
# imports limited for brevity

blueprint = Blueprint('hamsters', __name__)

@blueprint.route('/hamsters/list', methods=['POST'])
def get_hamsters_list():
   return JsonResponse({ hamsters: [“Harry”, “Hank”, “Harvey”, “Harriette” ]})

def load():
   print "loaded"

def unload():

Loading Resources

There are two methods of loading or accessing static resources in your directory from the webserver.

  1. ocLazyLoad (via plugin.json)
  • In plugin.json “initial_js”: [...file_paths]
  • Files are async loaded on client after angular bootstraps/initializes
  1. Directly from web server
  • http://<domain>/plugin/<top_level_directory>/html/init.js
  • eg. curl localhost:8001/plugin/HamstersPlugin/html/init.js
  • same-origin: /plugin/HamstersPlugin/html/init.js

UI Navigation and Routes

Before you can interact with your UI page or component we need to add a navigation element and/or some routing to transition to that view. We’re going to do that by accessing the ‘injector’ on our DivvyCloud ‘console’ module. From this injector we can get the services needed to add our navigation and routes.

The global function get_injector() will return the injector instance from angular.modules(‘console’). Use the .get() method to retrieve: * uiService * runtimeStates

// /html/init.js
(function () {
   'use strict';
   console.log('Hamsters Package loaded');
   var $injector = get_injector();

// Note: we recommend wrapping your js code in an IIFE (Immediately Invoked Function Expression)
// to avoid polluting the global namespace

Add Navigation

With our uiService we can: * Select which navigation list to add to * Define the look and function of our element

// /html/init.js
   .get_nav_bar("top_level") /* our main app level navigation */
       "Hamsters",  /* registry unique key */
            "Hamsters",  /* visible name */
            "{'active': $state.includes('hamsters')}",  /* ui-router active state adds CSS class ‘active’ */
            "hamsters",  /* ui-router route name */
            "fa fa-sitemap" /* font-awesome icon if you choose */
       false, /* Ignore Duplicates */
       false, /* Insert in first position */
       true /* Emit Broadcast event to update navbar onload of init.js */
    /* IIFE excluded for brevity */

Add UI-router routes

Oh No it broke!!! We first need to add routes to the UI-router for our nav element to point to. The function addStates([...array]) takes an array of ui-router config objects.

// /html/init.js
           name: 'hamsters',
           url: '/hamsters',
           templateUrl: '/plugin/HamstersPlugin/html/hamsters.html',
           controller: 'HamsterController',
           resolve: {
               hamstersList: function(HamsterService) {
                   return HamsterService.getHamsters()
    /* IIFE excluded for brevity */

To test this, open your browser’s dev tools panel and do a hard reload of the page and cache. One thing you’ll notice is the page will completely load before your nav element will appear. This is because ocLazyLoader loads your content async after the angular app bootstraps. The consequences are when the page reloads your plugin content will not be immediately available and if you refresh the page on one of our plugin’s routes you will be redirected to the our default route because at that moment your route doesn’t exist.

Adding Angular controllers, services, directives, etc.

Add to the ‘console’ module using angular.module(‘console’).<controller, service, directive, etc.>()

// /html/hamsters.controller.js
(function () {
   'use strict';
      .controller(‘Hamsters’, Hamsters);

   function Hamsters(...dependencyInjectionValues) {
      var vm = this;
      vm.firstName = ‘Harry the’;
      vm.lastName = ‘Hamster’;
       /* contents */

// Note: we recommend wrapping your js code in an IIFE (Immediately Invoked Function Expression)
// to avoid polluting the global namespace