You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by mc...@apache.org on 2017/09/06 17:45:22 UTC

[2/8] nifi-registry git commit: [NIFIREG-13] Initial implementation of the registry UI/UX. This closes #8

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/webapp/services/nf-registry.service.js
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/webapp/services/nf-registry.service.js b/nifi-registry-web-ui/src/main/webapp/services/nf-registry.service.js
new file mode 100644
index 0000000..55f58d1
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/webapp/services/nf-registry.service.js
@@ -0,0 +1,981 @@
+/*
+ * 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.
+ */
+
+var covalentCore = require('@covalent/core');
+
+function filterData(data, searchTerm, ignoreCase) {
+    var field = '';
+    if (searchTerm.indexOf(":") > -1) {
+        field = searchTerm.split(':')[0].trim();
+        searchTerm = searchTerm.split(':')[1].trim();
+    }
+    var filter = searchTerm ? (ignoreCase ? searchTerm.toLowerCase() : searchTerm) : '';
+
+    if (filter) {
+        data = data.filter(function (item) {
+            var res = Object.keys(item).find(function (key) {
+                if (field.indexOf(".") > -1) {
+                    var objArray = field.split(".");
+                    var obj = item;
+                    var arrayLength = objArray.length;
+                    for (var i = 0; i < arrayLength; i++) {
+                        try {
+                            obj = obj[objArray[i]];
+                        } catch (e) {
+                            return false;
+                        }
+                    }
+                    var preItemValue = ('' + obj);
+                    var itemValue = ignoreCase ? preItemValue.toLowerCase() : preItemValue;
+                    return itemValue.indexOf(filter) > -1;
+                } else {
+                    if (key !== field && field !== '') {
+                        return false;
+                    }
+                    var preItemValue = ('' + item[key]);
+                    var itemValue = ignoreCase ? preItemValue.toLowerCase() : preItemValue;
+                    return itemValue.indexOf(filter) > -1;
+                }
+            });
+            return !(typeof res === 'undefined');
+        });
+    }
+    return data;
+};
+
+function NfRegistryService(TdDataTableService) {
+    this.registries = [];
+    this.registry = {};
+    this.bucket = {};
+    this.buckets = [];
+    this.droplet = {};
+    this.droplets = [];
+    this.certifications = [];
+    this.user = {};
+    this.users = [];
+    this.alerts = [];
+    this.explorerViewType = '';
+    this.perspective = '';
+    this.breadCrumbState = 'out';
+    this.dataTableService = TdDataTableService;
+
+    this.filteredDroplets = [];
+
+    this.dropletColumns = [
+        {name: 'name', label: 'Name', sortable: true},
+        {name: 'updated', label: 'Updated', sortable: true}
+    ];
+
+    this.autoCompleteDroplets = [];
+    this.dropletsSearchTerms = [];
+
+    this.filteredBuckets = [];
+
+    this.bucketColumns = [
+        {name: 'name', label: 'Bucket Name', sortable: true, tooltip: 'Sort Buckets by name.'}
+    ];
+
+    this.allBucketsSelected = false;
+    this.autoCompleteBuckets = [];
+    this.bucketsSearchTerms = [];
+
+    this.filteredUsers = [];
+
+    this.userColumns = [
+        {name: 'status', label: 'Status', sortable: true, tooltip: 'User Status.', width: 18},
+        {name: 'name', label: 'Name', sortable: true, tooltip: 'User name.', width: 30},
+        {name: 'provider', label: 'Provider', sortable: true, tooltip: 'Authentication provider.', width: 30}
+    ];
+
+    this.allUsersSelected = false;
+    this.autoCompleteUsers = [];
+    this.selectedUsers = [];
+
+    this.usersSearchTerms = [];
+    this.usersFromRow = 1;
+    this.usersCurrentPage = 1;
+    this.usersPageSize = 5;
+    this.usersPageCount = 0;
+
+    this.filteredCertifications = [];
+
+    this.certificationColumns = [
+        {name: 'name', label: 'Label Name', sortable: true, tooltip: 'Sort Certifications by name.', width: 40},
+        {name: 'usage', label: 'Usage', sortable: true, tooltip: 'Sort Certifications by usage.', width: 30},
+        {name: 'badge', label: 'Badge Design', sortable: false, tooltip: 'Certification badge.', width: 30}
+    ];
+
+    this.autoCompleteCertifications = [];
+    this.certificationsSearchTerms = [];
+};
+
+NfRegistryService.prototype = {
+    constructor: NfRegistryService,
+
+    deleteDroplet: function (id) {
+        //TODO: REST call to API to delete droplet by id.
+    },
+
+    deleteBucket: function (id) {
+        //TODO: REST call to API to delete bucket by id.
+    },
+
+    deleteUser: function (id) {
+        //TODO: REST call to API to delete user by id.
+    },
+
+    suspendUser: function (id) {
+        //TODO: REST call to API to suspend user by id.
+    },
+
+    getRegistries: function () {
+        //TODO: leverage $http service to make call to nifi registry api. For now just return mock data...
+        var self = this;
+        var date = new Date();
+        return new Promise(
+            function (resolve) {
+                setTimeout(
+                    function () {
+                        resolve(self.registries = [{
+                            id: '23f6cc59-0156-1000-06b4-2b0810089090',
+                            name: "Nifi Registry",
+                            users: [{
+                                id: '23f6cc59-0156-1000-06b4-2b0810089090',
+                                name: 'Scotty 2 Hotty',
+                                status: 'authorized',
+                                provider: 'Friendly LDAP Provider',
+                                type: 'user',
+                                activities: [{
+                                    id: '25fd6vv87-3249-0001-05g6-4d4767890765',
+                                    description: 'Saved something...',
+                                    created: date.setDate(date.getDate() - 1),
+                                    updated: new Date()
+                                }],
+                                actions: [{
+                                    'name': 'details',
+                                    'icon': 'fa fa-info-circle',
+                                    'tooltip': 'User Details',
+                                    'type': 'sidenav',
+
+                                }, {
+                                    'name': 'permissions',
+                                    'icon': 'fa fa-key',
+                                    'tooltip': 'Manage User Policies',
+                                    'type': 'sidenav'
+                                }, {
+                                    'name': 'Delete',
+                                    'icon': 'fa fa-trash',
+                                    'tooltip': 'Delete User'
+                                }, {
+                                    'name': 'Suspend',
+                                    'icon': 'fa fa-ban',
+                                    'tooltip': 'Suspend User'
+                                }]
+                            }, {
+                                id: '25fd6vv87-3249-0001-05g6-4d4767890765',
+                                name: 'Group 1',
+                                status: 'suspended',
+                                provider: 'IOAT',
+                                type: 'group',
+                                actions: [{
+                                    'name': 'details',
+                                    'icon': 'fa fa-info-circle',
+                                    'tooltip': 'User Details',
+                                    'type': 'sidenav'
+                                }, {
+                                    'name': 'permissions',
+                                    'icon': 'fa fa-key',
+                                    'tooltip': 'Manage User Policies',
+                                    'type': 'sidenav'
+                                }, {
+                                    'name': 'Delete',
+                                    'icon': 'fa fa-trash',
+                                    'tooltip': 'Delete User'
+                                }, {
+                                    'name': 'Reauthorize',
+                                    'icon': 'fa fa-check-circle',
+                                    'tooltip': 'Reauthorize User'
+                                }]
+                            }, {
+                                id: '98f6cc59-0156-1000-06b4-2b0810089090',
+                                name: 'G$',
+                                status: 'authorized',
+                                provider: 'Friendly LDAP Provider',
+                                type: 'user',
+                                actions: [{
+                                    'name': 'details',
+                                    'icon': 'fa fa-info-circle',
+                                    'tooltip': 'User Details',
+                                    'type': 'sidenav'
+                                }, {
+                                    'name': 'permissions',
+                                    'icon': 'fa fa-key',
+                                    'tooltip': 'Manage User Policies',
+                                    'type': 'sidenav'
+                                }, {
+                                    'name': 'Delete',
+                                    'icon': 'fa fa-trash',
+                                    'tooltip': 'Delete User'
+                                }, {
+                                    'name': 'Suspend',
+                                    'icon': 'fa fa-ban',
+                                    'tooltip': 'Suspend User'
+                                }]
+                            }, {
+                                id: '65fd6vv87-3249-0001-05g6-4d4767890765',
+                                name: 'Group 2',
+                                status: 'suspended',
+                                provider: 'IOAT',
+                                type: 'group',
+                                actions: [{
+                                    'name': 'details',
+                                    'icon': 'fa fa-info-circle',
+                                    'tooltip': 'User Details',
+                                    'type': 'sidenav'
+                                }, {
+                                    'name': 'permissions',
+                                    'icon': 'fa fa-key',
+                                    'tooltip': 'Manage User Policies',
+                                    'type': 'sidenav'
+                                }, {
+                                    'name': 'Delete',
+                                    'icon': 'fa fa-trash',
+                                    'tooltip': 'Delete User'
+                                }, {
+                                    'name': 'Reauthorize',
+                                    'icon': 'fa fa-check-circle',
+                                    'tooltip': 'Reauthorize User'
+                                }]
+                            }],
+                            buckets: [{
+                                id: '25fd6vv87-3549-0001-05g6-4d4567890765',
+                                name: "My Flows",
+                                actions: [{
+                                    'name': 'permissions',
+                                    'icon': 'fa fa-key',
+                                    'tooltip': 'Manage Bucket Policies',
+                                    'type': 'sidenav'
+                                }, {
+                                    'name': 'Delete',
+                                    'icon': 'fa fa-trash',
+                                    'tooltip': 'Delete Bucket'
+                                }],
+                                droplets: [{
+                                    id: '23f6cc59-0156-1000-09b4-2b0610089090',
+                                    name: "Security_Dev_Ops",
+                                    displayName: 'Security Dev Ops',
+                                    type: 'Data Flow',
+                                    sublabel: 'Some info',
+                                    updated: new Date(),
+                                    description: 'This is the most secure flow ever!',
+                                    versions: [{
+                                        id: '23f6cc59-0156-1000-06b4-2b0810089090',
+                                        revision: '1',
+                                        dependentFlows: [{
+                                            id: '25fd6vv87-3549-0001-05g6-4d4567890765'
+                                        }],
+                                        author: '2Hot',
+                                        comment: 'delete ListenHttp',
+                                        created: new Date(date.setDate(date.getDate() - 1)),
+                                        updated: new Date()
+                                    }, {
+                                        id: '25fd6vv87-3549-0001-05g6-4d4567890765',
+                                        revision: '2',
+                                        dependentFlows: [{
+                                            id: '23f6cc59-0156-1000-06b4-2b0810089090'
+                                        }],
+                                        author: '2Hot',
+                                        comment: 'added Labels for better description of groups of processors',
+                                        created: new Date(),
+                                        updated: new Date()
+                                    }],
+                                    flows: [],
+                                    extensions: [],
+                                    assets: [],
+                                    actions: [{
+                                        'name': 'Delete',
+                                        'icon': 'fa fa-close',
+                                        'tooltip': 'Delete User'
+                                    }]
+                                }]
+                            }, {
+                                id: '23f6cc59-0156-1000-09b4-2b0810089080',
+                                name: "Development Flows",
+                                droplets: [{
+                                    id: '23f6cc59-0156-1000-09b4-2b0610089090',
+                                    name: "Fraud Detection Flow",
+                                    displayName: 'Fraud Detection Flow',
+                                    type: 'Data Flow',
+                                    sublabel: 'A sublabel',
+                                    updated: new Date(date.setDate(date.getDate() - 2)),
+                                    description: 'This flow detects fraud!',
+                                    versions: [{
+                                        id: '23f6cc59-0156-1000-06b4-2b0810089090',
+                                        revision: '1',
+                                        dependentFlows: [{
+                                            id: '25fd6vv87-3549-0001-05g6-4d4567890765'
+                                        }],
+                                        author: 'G$',
+                                        comment: 'added funnel',
+                                        created: new Date(date.setDate(date.getDate() - 1)),
+                                        updated: new Date()
+                                    }, {
+                                        id: '25fd6vv87-3549-0001-05g6-4d4567890765',
+                                        revision: '2',
+                                        dependentFlows: [{
+                                            id: '23f6cc59-0156-1000-06b4-2b0810089090'
+                                        }],
+                                        author: '2Hot',
+                                        comment: 'added Execute script',
+                                        created: new Date(date.setDate(date.getDate() - 1)),
+                                        updated: new Date()
+                                    }, {
+                                        id: '77fd6vv87-3549-0001-05g6-4d4567890765',
+                                        revision: '3',
+                                        dependentFlows: [{
+                                            id: '23f6cc59-0156-1000-06b4-2b0810089090'
+                                        }],
+                                        author: 'Payne',
+                                        comment: 'removed Execute script',
+                                        created: new Date(date.setDate(date.getDate() - 1)),
+                                        updated: new Date()
+                                    }, {
+                                        id: '96fd6vv87-3549-0001-05g6-4d4567890765',
+                                        revision: '4',
+                                        dependentFlows: [{
+                                            id: '23f6cc59-0156-1000-06b4-2b0810089090'
+                                        }],
+                                        author: 'G$',
+                                        comment: 'add Execute script',
+                                        created: new Date(date.setDate(date.getDate() - 1)),
+                                        updated: new Date()
+                                    }],
+                                    flows: [],
+                                    extensions: [],
+                                    assets: [],
+                                    actions: [{
+                                        'name': 'Delete',
+                                        'icon': 'fa fa-close'
+                                    }]
+                                }, {
+                                    id: '59f6cc23-0156-1000-09b4-2b0610089090',
+                                    name: "Cyber Security",
+                                    displayName: 'Cyber Security',
+                                    type: 'Data Flow',
+                                    sublabel: 'A sublabel',
+                                    updated: new Date(date.setDate(date.getDate() - 1)),
+                                    description: 'This is the most cyber secure flow ever!',
+                                    versions: [{
+                                        id: '23f6cc59-0156-1000-06b4-2b0810089090',
+                                        revision: '1',
+                                        dependentFlows: [{
+                                            id: '25fd6vv87-3549-0001-05g6-4d4567890765'
+                                        }],
+                                        author: 'G$',
+                                        comment: 'added funnel',
+                                        created: new Date(date.setDate(date.getDate() - 1)),
+                                        updated: new Date()
+                                    }],
+                                    flows: [],
+                                    extensions: [],
+                                    assets: [],
+                                    actions: [{
+                                        'name': 'Delete',
+                                        'icon': 'fa fa-close',
+                                        'tooltip': 'Delete User'
+                                    }]
+                                }],
+                                actions: [{
+                                    'name': 'permissions',
+                                    'icon': 'fa fa-key',
+                                    'tooltip': 'Manage Bucket Policies',
+                                    'type': 'sidenav'
+                                }, {
+                                    'name': 'Delete',
+                                    'icon': 'fa fa-trash',
+                                    'tooltip': 'Delete Bucket'
+                                }]
+                            }] // some data model for the contents of a registry
+                        }])
+                    }, 0);
+            }
+        );
+    },
+
+    getRegistry: function (registryId) {
+        return this.getRegistries().then(
+            function (registries) {
+                return registries.find(
+                    function (registry) {
+                        if (registryId === registry.id) {
+                            return registry;
+                        }
+                    });
+            });
+    },
+
+    getDroplet: function (registryId, bucketId, dropletId) {
+        return this.getDroplets(registryId, bucketId, dropletId).then(
+            function (droplets) {
+                return droplets[0];
+            });
+    },
+
+    getDroplets: function (registryIds, bucketIds, dropletIds) {
+        var self = this;
+        return this.getRegistries().then(
+            function (registries) {
+                var buckets = [];
+
+                registries.find(
+                    function (registry) {
+                        if (registryIds === undefined || registryIds.indexOf(registry.id) >= 0) {
+                            registry.buckets.find(
+                                function (bucket) {
+                                    if (bucketIds === undefined || bucketIds.indexOf(bucket.id) >= 0) {
+                                        buckets.push(bucket);
+                                    }
+                                });
+                        }
+                    });
+
+                var droplets = [];
+
+                buckets.find(
+                    function (bucket) {
+                        bucket.droplets.find(
+                            function (droplet) {
+                                if (dropletIds === undefined || dropletIds.indexOf(droplet.id) >= 0) {
+                                    droplets.push(droplet);
+                                }
+                            });
+                    });
+
+                return droplets;
+            });
+
+    },
+
+    isDropletFilterChecked: function (term) {
+        return (this.dropletsSearchTerms.indexOf(term) > -1);
+    },
+
+    getDropletTypeCount: function (type) {
+        return this.filteredDroplets.filter(function (droplet) {
+            return droplet.type === type;
+        }).length;
+    },
+
+    getDropletCertificationCount: function (certification) {
+        return this.filteredDroplets.filter(function (droplet) {
+            return Object.keys(droplet).find(function (key) {
+                if (key === certification && droplet[certification].type === 'certification') {
+                    return droplet;
+                }
+            });
+        }).length;
+    },
+
+    getSortByLabel: function () {
+        var sortByColumn;
+        var arrayLength = this.dropletColumns.length;
+        for (var i = 0; i < arrayLength; i++) {
+            if (this.dropletColumns[i].active === true) {
+                sortByColumn = this.dropletColumns[i];
+                break;
+            }
+        }
+
+        if (sortByColumn) {
+            var label = '';
+            switch (sortByColumn.label) {
+                case 'Updated':
+                    label = (sortByColumn.sortOrder === 'ASC') ? 'Newest (update)' : 'Oldest (update)';
+                    break;
+                case 'Name':
+                    label = (sortByColumn.sortOrder === 'ASC') ? 'Name (a - z)' : 'Name (z - a)';
+                    break;
+            }
+            return label;
+        }
+    },
+
+    generateSortMenuLabels: function (col) {
+        var label = '';
+        switch (col.label) {
+            case 'Updated':
+                label = (col.sortOrder !== 'ASC') ? 'Newest (update)' : 'Oldest (update)';
+                break;
+            case 'Name':
+                label = (col.sortOrder !== 'ASC') ? 'Name (a - z)' : 'Name (z - a)';
+                break;
+        }
+        return label;
+    },
+
+    sortDroplets: function (sortEvent, column) {
+        if (column.sortable === true) {
+            // toggle column sort order
+            var sortOrder = column.sortOrder = (column.sortOrder === 'ASC') ? 'DESC' : 'ASC';
+            this.filterDroplets(column.name, sortOrder);
+            this.activeDropletColumn = column;
+            //only one column can be actively sorted so we reset all to inactive
+            this.dropletColumns.forEach(function (c) {
+                c.active = false;
+            });
+            //and set this column as the actively sorted column
+            column.active = true;
+        }
+    },
+
+    dropletsSearchRemove: function (searchTerm) {
+        this.filterDroplets(this.activeDropletColumn.name, this.activeDropletColumn.sortOrder);
+    },
+
+    dropletsSearchAdd: function (searchTerm) {
+        this.filterDroplets(this.activeDropletColumn.name, this.activeDropletColumn.sortOrder);
+    },
+
+    toggleDropletsFilter: function (searchTerm) {
+        var applySearchTerm = true;
+        // check if the search term is already applied and remove it if true
+        if (this.dropletsSearchTerms.length > 0) {
+            var arrayLength = this.dropletsSearchTerms.length;
+            for (var i = 0; i < arrayLength; i++) {
+                var index = this.dropletsSearchTerms.indexOf(searchTerm);
+                if (index > -1) {
+                    this.dropletsSearchTerms.splice(index, 1);
+                    applySearchTerm = false;
+                }
+            }
+        }
+
+        // if we just removed the search term do NOT apply it again
+        if (applySearchTerm) {
+            this.dropletsSearchTerms.push(searchTerm);
+        }
+
+        this.filterDroplets(this.activeDropletColumn.name, this.activeDropletColumn.sortOrder);
+    },
+
+    filterDroplets: function (sortBy, sortOrder) {
+        // if `sortOrder` is `undefined` then use 'ASC'
+        if (sortOrder === undefined) {
+            sortOrder = 'ASC'
+        }
+        // if `sortBy` is `undefined` then find the first sortable column in this.dropletColumns
+        if (sortBy === undefined) {
+            var arrayLength = this.dropletColumns.length;
+            for (var i = 0; i < arrayLength; i++) {
+                if (this.dropletColumns[i].sortable === true) {
+                    sortBy = this.dropletColumns[i].name;
+                    this.activeDropletColumn = this.dropletColumns[i];
+                    //only one column can be actively sorted so we reset all to inactive
+                    this.dropletColumns.forEach(function (c) {
+                        c.active = false;
+                    });
+                    //and set this column as the actively sorted column
+                    this.dropletColumns[i].active = true;
+                    this.dropletColumns[i].sortOrder = sortOrder;
+                    break;
+                }
+            }
+        }
+
+        var newData = this.droplets;
+
+        for (var i = 0; i < this.dropletsSearchTerms.length; i++) {
+            newData = filterData(newData, this.dropletsSearchTerms[i], true, this.activeDropletColumn.name);
+        }
+
+        newData = this.dataTableService.sortData(newData, sortBy, sortOrder);
+        this.filteredDroplets = newData;
+        this.getAutoCompleteDroplets();
+    },
+
+    getAutoCompleteDroplets: function () {
+        var self = this;
+        this.autoCompleteDroplets = [];
+        this.dropletColumns.forEach(function (c) {
+            return self.filteredDroplets.forEach(function (r) {
+                return (r[c.name.toLowerCase()]) ? self.autoCompleteDroplets.push(r[c.name.toLowerCase()].toString()) : '';
+            })
+        });
+    },
+
+    getBucket: function (registryId, bucketId) {
+        return this.getBuckets(registryId, bucketId).then(
+            function (buckets) {
+                return buckets[0];
+            });
+    },
+
+    getBuckets: function (registryIds, bucketIds) {
+        var self = this;
+        return this.getRegistries().then(
+            function (registries) {
+                var buckets = [];
+
+                registries.find(
+                    function (registry) {
+                        if (registryIds === undefined || registryIds.indexOf(registry.id) >= 0) {
+                            registry.buckets.find(
+                                function (bucket) {
+                                    if (bucketIds === undefined || bucketIds.indexOf(bucket.id) >= 0) {
+                                        buckets.push(bucket);
+                                    }
+                                });
+                        }
+                    });
+
+                return buckets;
+            });
+
+    },
+
+    filterBuckets: function (sortBy, sortOrder) {
+        // if `sortOrder` is `undefined` then use 'ASC'
+        if (sortOrder === undefined) {
+            sortOrder = 'ASC'
+        }
+
+        // if `sortBy` is `undefined` then find the first sortable column in this.bucketColumns
+        if (sortBy === undefined) {
+            var arrayLength = this.bucketColumns.length;
+            for (var i = 0; i < arrayLength; i++) {
+                if (this.bucketColumns[i].sortable === true) {
+                    sortBy = this.bucketColumns[i].name;
+                    this.activeBucketColumn = this.bucketColumns[i];
+                    //only one column can be actively sorted so we reset all to inactive
+                    this.bucketColumns.forEach(function (c) {
+                        c.active = false;
+                    });
+                    //and set this column as the actively sorted column
+                    this.bucketColumns[i].active = true;
+                    this.bucketColumns[i].sortOrder = sortOrder;
+                    break;
+                }
+            }
+        }
+
+        var newData = this.buckets;
+
+        for (var i = 0; i < this.bucketsSearchTerms.length; i++) {
+            newData = filterData(newData, this.bucketsSearchTerms[i], true, this.activeBucketColumn.name);
+        }
+
+        newData = this.dataTableService.sortData(newData, sortBy, sortOrder);
+        this.filteredBuckets = newData;
+        this.getAutoCompleteBuckets();
+    },
+
+    getAutoCompleteBuckets: function () {
+        var self = this;
+        this.autoCompleteBuckets = [];
+        this.bucketColumns.forEach(function (c) {
+            return self.filteredBuckets.forEach(function (r) {
+                return (r[c.name.toLowerCase()]) ? self.autoCompleteBuckets.push(r[c.name.toLowerCase()].toString()) : '';
+            });
+        });
+    },
+
+    sortBuckets: function (sortEvent, column) {
+        if (column.sortable === true) {
+            // toggle column sort order
+            var sortOrder = column.sortOrder = (column.sortOrder === 'ASC') ? 'DESC' : 'ASC';
+            this.filterBuckets(column.name, sortOrder);
+            this.activeBucketsColumn = column;
+            //only one column can be actively sorted so we reset all to inactive
+            this.bucketColumns.forEach(function (c) {
+                c.active = false;
+            });
+            //and set this column as the actively sorted column
+            column.active = true;
+        }
+    },
+
+    bucketsSearchRemove: function (searchTerm) {
+        this.filterDroplets(this.activeBucketsColumn.name, this.activeBucketsColumn.sortOrder);
+    },
+
+    bucketsSearchAdd: function (searchTerm) {
+        this.filterDroplets(this.activeBucketsColumn.name, this.activeBucketsColumn.sortOrder);
+    },
+
+    allFilteredBucketsSelected: function () {
+        this.filteredBuckets.forEach(function (c) {
+            if (c.checked === undefined || c.checked === false) {
+                return false;
+            }
+        });
+
+        return true;
+    },
+
+    toggleBucketSelect: function (row) {
+        if (this.allFilteredBucketsSelected()) {
+            this.allBucketsSelected = true;
+        } else {
+            this.allBucketsSelected = false;
+        }
+    },
+
+    getCertification: function (registryId, certificatonId) {
+        return this.getCertifications(registryId, certificatonId).then(
+            function (certificatons) {
+                return certificatons[0];
+            });
+    },
+    getCertifications: function (registryIds, certificatonIds) {
+        var self = this;
+        return this.getRegistries().then(
+            function (registries) {
+                var certificatons = [];
+
+                registries.find(
+                    function (registry) {
+                        if (registryIds === undefined || registryIds.indexOf(registry.id) >= 0) {
+                            registry.certifications.find(
+                                function (certificaton) {
+                                    if (certificatonIds === undefined || certificatonIds.indexOf(certificaton.id) >= 0) {
+                                        certificatons.push(certificaton);
+                                    }
+                                });
+                        }
+                    });
+
+                return certificatons;
+            });
+
+    },
+
+    filterCertifications: function (sortBy, sortOrder) {
+        // if `sortOrder` is `undefined` then use 'ASC'
+        if (sortOrder === undefined) {
+            sortOrder = 'ASC'
+        }
+
+        // if `sortBy` is `undefined` then find the first sortable column in this.bucketColumns
+        if (sortBy === undefined) {
+            var arrayLength = this.bucketColumns.length;
+            for (var i = 0; i < arrayLength; i++) {
+                if (this.bucketColumns[i].sortable === true) {
+                    sortBy = this.bucketColumns[i].name;
+                    this.activeBucketColumn = this.bucketColumns[i];
+                    //only one column can be actively sorted so we reset all to inactive
+                    this.bucketColumns.forEach(function (c) {
+                        c.active = false;
+                    });
+                    //and set this column as the actively sorted column
+                    this.bucketColumns[i].active = true;
+                    this.bucketColumns[i].sortOrder = sortOrder;
+                    break;
+                }
+            }
+        }
+
+        var newData = this.certifications;
+
+        for (var i = 0; i < this.certificationsSearchTerms.length; i++) {
+            newData = filterData(newData, this.certificationsSearchTerms[i], true, this.activeBucketColumn.name);
+        }
+
+        newData = this.dataTableService.sortData(newData, sortBy, sortOrder);
+        this.filteredCertifications = newData;
+        this.getAutoCompleteCertifications();
+    },
+
+    getAutoCompleteCertifications: function () {
+        var self = this;
+        this.autoCompleteCertifications = [];
+        this.bucketColumns.forEach(function (c) {
+            return self.filteredCertifications.forEach(function (r) {
+                return (r[c.name.toLowerCase()]) ? self.autoCompleteCertifications.push(r[c.name.toLowerCase()].toString()) : '';
+            });
+        });
+    },
+
+    sortCertifications: function (sortEvent, column) {
+        if (column.sortable === true) {
+            // toggle column sort order
+            var sortOrder = column.sortOrder = (column.sortOrder === 'ASC') ? 'DESC' : 'ASC';
+            this.filterCertifications(column.name, sortOrder);
+            this.activeCertificationsColumn = column;
+            //only one column can be actively sorted so we reset all to inactive
+            this.bucketColumns.forEach(function (c) {
+                c.active = false;
+            });
+            //and set this column as the actively sorted column
+            column.active = true;
+        }
+    },
+
+    certificationsSearchRemove: function (searchTerm) {
+        this.filterDroplets(this.activeCertificationsColumn.name, this.activeCertificationsColumn.sortOrder);
+    },
+
+    certificationsSearchAdd: function (searchTerm) {
+        this.filterDroplets(this.activeCertificationsColumn.name, this.activeCertificationsColumn.sortOrder);
+    },
+
+    getUser: function (registryId, userId) {
+        return this.getUsers(registryId, userId).then(
+            function (users) {
+                return users[0];
+            });
+    },
+
+    getUsers: function (registryIds, userIds) {
+        var self = this;
+        return this.getRegistries().then(
+            function (registries) {
+                var users = [];
+
+                registries.find(
+                    function (registry) {
+                        if (registryIds === undefined || registryIds.indexOf(registry.id) >= 0) {
+                            registry.users.find(
+                                function (user) {
+                                    if (userIds === undefined || userIds.indexOf(user.id) >= 0) {
+                                        users.push(user);
+                                    }
+                                });
+                        }
+                    });
+
+                return users;
+            });
+
+    },
+
+    sortUsers: function (sortEvent, column) {
+        if (column.sortable) {
+            var sortBy = column.name;
+            var sortOrder = column.sortOrder = (column.sortOrder === 'ASC') ? 'DESC' : 'ASC';
+            this.filterUsers(sortBy, sortOrder);
+
+            //only one column can be actively sorted so we reset all to inactive
+            this.userColumns.forEach(function (c) {
+                c.active = false;
+            });
+            //and set this column as the actively sorted column
+            column.active = true;
+        }
+    },
+
+    usersSearchRemove: function (searchTerm) {
+        //only remove the first occurrence of the search term
+        var index = this.usersSearchTerms.indexOf(searchTerm);
+        if (index !== -1) {
+            this.usersSearchTerms.splice(index, 1);
+        }
+        this.usersCurrentPage = 1;
+        this.usersFromRow = 1;
+        this.usersPageSize = 1;
+        this.filterUsers();
+    },
+
+    usersSearchAdd: function (searchTerm) {
+        this.usersSearchTerms.push(searchTerm);
+        this.usersCurrentPage = 1;
+        this.usersFromRow = 1;
+        this.usersPageSize = 1;
+        this.filterUsers();
+    },
+
+    pageUsers: function (pagingEvent) {
+        this.usersFromRow = pagingEvent.fromRow;
+        this.usersCurrentPage = pagingEvent.page;
+        this.usersPageSize = pagingEvent.pageSize;
+        this.filterUsers();
+    },
+
+    filterUsers: function (sortBy, sortOrder) {
+        if (this.allUsersSelected) {
+            this.toggleUsersSelectAll();
+        }
+        this.deselectAllUsers();
+        var newData = this.users;
+
+        for (var i = 0; i < this.usersSearchTerms.length; i++) {
+            newData = filterData(newData, this.usersSearchTerms[i], true);
+        }
+        newData = this.dataTableService.sortData(newData, sortBy, sortOrder);
+        this.usersPageCount = newData.length;
+        newData = this.dataTableService.pageData(newData, this.usersFromRow, this.usersCurrentPage * this.usersPageSize);
+        this.filteredUsers = newData;
+        this.getAutoCompleteUsers();
+    },
+
+    toggleUserSelect: function (row) {
+        if (this.allFilteredUsersSelected()) {
+            this.allUsersSelected = true;
+        } else {
+            this.allUsersSelected = false;
+        }
+    },
+
+    toggleUsersSelectAll: function () {
+        if (this.allUsersSelected) {
+            this.selectAllUsers();
+        } else {
+            this.deselectAllUsers();
+        }
+    },
+
+    selectAllUsers: function () {
+        this.filteredUsers.forEach(function (c) {
+            c.checked = true;
+        });
+    },
+
+    deselectAllUsers: function () {
+        this.filteredUsers.forEach(function (c) {
+            c.checked = false;
+        });
+    },
+    allFilteredUsersSelected: function () {
+        var allFilteredUsersSelected = true;
+        this.filteredUsers.forEach(function (c) {
+            if (c.checked === undefined || c.checked === false) {
+                allFilteredUsersSelected = false;
+            }
+        });
+
+        return allFilteredUsersSelected;
+    },
+
+    getAutoCompleteUsers: function () {
+        var self = this;
+        this.autoCompleteUsers = [];
+        this.userColumns.forEach(function (c) {
+            self.filteredUsers.forEach(function (r) {
+                (r[c.name.toLowerCase()]) ? self.autoCompleteUsers.push(r[c.name.toLowerCase()].toString()) : '';
+            });
+        });
+    },
+
+    //</editor-fold>
+
+    setBreadcrumbState: function (state) {
+        this.breadCrumbState = state;
+    }
+};
+
+NfRegistryService.parameters = [covalentCore.TdDataTableService];
+
+module.exports = NfRegistryService;

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/webapp/systemjs-angular-loader.js
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/webapp/systemjs-angular-loader.js b/nifi-registry-web-ui/src/main/webapp/systemjs-angular-loader.js
new file mode 100644
index 0000000..8e33bb5
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/webapp/systemjs-angular-loader.js
@@ -0,0 +1,66 @@
+/*
+ * 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.
+ */
+
+var templateUrlRegex = /templateUrl\s*:(\s*['"`](.*?)['"`]\s*)/gm;
+var stylesRegex = /styleUrls *:(\s*\[[^\]]*?\])/g;
+var stringRegex = /(['`"])((?:[^\\]\\\1|.)*?)\1/g;
+
+module.exports.translate = function (load) {
+    if (load.source.indexOf('moduleId') != -1) return load;
+
+    var url = document.createElement('a');
+    url.href = load.address;
+
+    var basePathParts = url.pathname.split('/');
+
+    basePathParts.pop();
+    var basePath = basePathParts.join('/');
+
+    var baseHref = document.createElement('a');
+    baseHref.href = this.baseURL;
+    baseHref = baseHref.pathname;
+
+    if (!baseHref.startsWith('/base/')) { // it is not karma
+        basePath = basePath.replace(baseHref, '');
+    }
+
+    load.source = load.source
+        .replace(templateUrlRegex, function (match, quote, url) {
+            var resolvedUrl = url;
+
+            if (url.startsWith('.')) {
+                resolvedUrl = basePath + url.substr(1);
+            }
+
+            return 'templateUrl: "' + resolvedUrl + '"';
+        })
+        .replace(stylesRegex, function (match, relativeUrls) {
+            var urls = [];
+
+            while ((match = stringRegex.exec(relativeUrls)) !== null) {
+                if (match[2].startsWith('.')) {
+                    urls.push('"' + basePath + match[2].substr(1) + '"');
+                } else {
+                    urls.push('"' + match[2] + '"');
+                }
+            }
+
+            return "styleUrls: [" + urls.join(', ') + "]";
+        });
+
+    return load;
+};

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/webapp/systemjs.builder.config.js
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/webapp/systemjs.builder.config.js b/nifi-registry-web-ui/src/main/webapp/systemjs.builder.config.js
new file mode 100644
index 0000000..1d96854
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/webapp/systemjs.builder.config.js
@@ -0,0 +1,137 @@
+/*
+ * 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.
+ */
+
+(function (global) {
+    System.config({
+        baseURL: "./webapp/",
+        paths: {
+            // paths serve as alias
+            'npm:': './node_modules/'
+        },
+        // map tells the System loader where to look for things
+        map: {
+            'text': 'npm:systemjs-plugin-text/text.js',
+            'app': 'webapp/',
+
+            // jquery
+            'jquery': 'npm:jquery/dist/jquery.min.js',
+
+            // Angular
+            '@angular/core': 'npm:@angular/core/bundles/core.umd.js',
+            '@angular/common': 'npm:@angular/common/bundles/common.umd.js',
+            '@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js',
+            '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
+            '@angular/http': 'npm:@angular/http/bundles/http.umd.js',
+            '@angular/router': 'npm:@angular/router/bundles/router.umd.js',
+            '@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js',
+            '@angular/flex-layout': 'npm:@angular/flex-layout/bundles/flex-layout.umd.js',
+            '@angular/material': 'npm:@angular/material/bundles/material.umd.js',
+            '@angular/platform-browser/animations': 'npm:@angular/platform-browser/bundles/platform-browser-animations.umd.js',
+            '@angular/cdk': 'npm:@angular/cdk/bundles/cdk.umd.js',
+            '@angular/cdk/a11y': 'npm:@angular/cdk/bundles/cdk-a11y.umd.js',
+            '@angular/cdk/collections': 'npm:@angular/cdk/bundles/cdk-collections.umd.js',
+            '@angular/cdk/observers': 'npm:@angular/cdk/bundles/cdk-observers.umd.js',
+            '@angular/cdk/overlay': 'npm:@angular/cdk/bundles/cdk-overlay.umd.js',
+            '@angular/cdk/platform': 'npm:@angular/cdk/bundles/cdk-platform.umd.js',
+            '@angular/cdk/portal': 'npm:@angular/cdk/bundles/cdk-portal.umd.js',
+            '@angular/cdk/keycodes': 'npm:@angular/cdk/bundles/cdk-keycodes.umd.js',
+            '@angular/cdk/bidi': 'npm:@angular/cdk/bundles/cdk-bidi.umd.js',
+            '@angular/cdk/coercion': 'npm:@angular/cdk/bundles/cdk-coercion.umd.js',
+            '@angular/cdk/table': 'npm:@angular/cdk/bundles/cdk-table.umd.js',
+            '@angular/cdk/rxjs': 'npm:@angular/cdk/bundles/cdk-rxjs.umd.js',
+            '@angular/cdk/scrolling': 'npm:@angular/cdk/bundles/cdk-scrolling.umd.js',
+            '@angular/animations': 'npm:@angular/animations/bundles/animations.umd.js',
+            '@angular/animations/browser': 'npm:@angular/animations/bundles/animations-browser.umd.js',
+            '@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js',
+
+            // needed to support gestures for angular material
+            'hammerjs': 'npm:hammerjs/hammer.min.js',
+
+            // Covalent
+            '@covalent/core': 'npm:@covalent/core/core.umd.js',
+
+            // other libraries
+            'rxjs': 'npm:rxjs',
+            'moment': 'npm:moment',
+            'angular2-moment': 'npm:angular2-moment',
+            'switchMap': 'npm:rxjs/add/operator/switchMap',
+            'zone.js': 'npm:zone.js/dist/zone.js',
+            'core-js': 'npm:core-js/client/shim.min.js',
+
+            // Fluid Design System
+            '@fluid-design-system/core': 'npm:@fluid-design-system/dist/platform/core/fluid-design-system.module.js',
+            '@fluid-design-system/dialogs': 'npm:@fluid-design-system/dist/platform/core/dialogs/fds-dialogs.module.js',
+            '@fluid-design-system/dialog-component': 'npm:@fluid-design-system/dist/platform/core/dialogs/fds-dialog.component.js',
+            '@fluid-design-system/dialog-service': 'npm:@fluid-design-system/dist/platform/core/dialogs/services/dialog.service.js',
+            '@fluid-design-system/confirm-dialog-component': 'npm:@fluid-design-system/dist/platform/core/dialogs/confirm-dialog/confirm-dialog.component.js',
+
+            // Nifi Registry
+            'nifi-registry/nf-registry.module.js': 'nf-registry.module.js',
+            'nifi-registry/nf-registry.animations.js': 'nf-registry.animations.js',
+            'nifi-registry/nf-registry.routes.js': 'nf-registry.routes.js',
+            'nifi-registry/components/fluid-design-system/fds-demo.js': 'components/fluid-design-system/fds-demo.js',
+            'nifi-registry/nf-registry.js': 'nf-registry.js',
+            'nifi-registry/services/nf-registry.service.js': 'services/nf-registry.service.js',
+            'nifi-registry/components/page-not-found/nf-registry-page-not-found.js': 'components/page-not-found/nf-registry-page-not-found.js',
+            'nifi-registry/components/explorer/nf-registry-explorer.js': 'components/explorer/nf-registry-explorer.js',
+            'nifi-registry/components/explorer/list/nf-registry-explorer-list-viewer.js': 'components/explorer/list/nf-registry-explorer-list-viewer.js',
+            'nifi-registry/components/explorer/grid-list/nf-registry-explorer-grid-list-viewer.js': 'components/explorer/grid-list/nf-registry-explorer-grid-list-viewer.js',
+            'nifi-registry/components/administration/nf-registry-administration.js': 'components/administration/nf-registry-administration.js',
+            'nifi-registry/components/administration/general/nf-registry-general-administration.js': 'components/administration/general/nf-registry-general-administration.js',
+            'nifi-registry/components/administration/users/nf-registry-users-administration.js': 'components/administration/users/nf-registry-users-administration.js',
+            'nifi-registry/components/administration/users/add/nf-registry-add-user.js': 'components/administration/users/add/nf-registry-add-user.js',
+            'nifi-registry/components/administration/users/details/nf-registry-user-details.js': 'components/administration/users/details/nf-registry-user-details.js',
+            'nifi-registry/components/administration/users/permissions/nf-registry-user-permissions.js': 'components/administration/users/permissions/nf-registry-user-permissions.js',
+            'nifi-registry/components/administration/workflow/buckets/permissions/nf-registry-bucket-permissions.js': 'components/administration/workflow/buckets/permissions/nf-registry-bucket-permissions.js',
+            'nifi-registry/components/administration/workflow/nf-registry-workflow-administration.js': 'components/administration/workflow/nf-registry-workflow-administration.js',
+            'nifi-registry/components/explorer/list/registry/nf-registry-list-viewer.js': 'components/explorer/list/registry/nf-registry-list-viewer.js',
+            'nifi-registry/components/explorer/grid-list/registry/nf-registry-grid-list-viewer.js': 'components/explorer/grid-list/registry/nf-registry-grid-list-viewer.js',
+            'nifi-registry/components/explorer/list/registry/bucket/nf-registry-bucket-list-viewer.js': 'components/explorer/list/registry/bucket/nf-registry-bucket-list-viewer.js',
+            'nifi-registry/components/explorer/grid-list/registry/bucket/nf-registry-bucket-grid-list-viewer.js': 'components/explorer/grid-list/registry/bucket/nf-registry-bucket-grid-list-viewer.js',
+            'nifi-registry/components/explorer/list/registry/bucket/droplet/nf-registry-droplet-list-viewer.js': 'components/explorer/list/registry/bucket/droplet/nf-registry-droplet-list-viewer.js',
+            'nifi-registry/components/explorer/grid-list/registry/bucket/droplet/nf-registry-droplet-grid-list-viewer.js': 'components/explorer/grid-list/registry/bucket/droplet/nf-registry-droplet-grid-list-viewer.js',
+            'nifi-registry/components/explorer/list/registry/nf-registry-details-viewer.js': 'components/explorer/list/registry/nf-registry-details-viewer.js',
+            'nifi-registry/components/explorer/list/registry/bucket/nf-registry-bucket-details-viewer.js': 'components/explorer/list/registry/bucket/nf-registry-bucket-details-viewer.js',
+            'nifi-registry/components/explorer/list/registry/bucket/droplet/nf-registry-droplet-details-viewer.js': 'components/explorer/list/registry/bucket/droplet/nf-registry-droplet-details-viewer.js'
+        },
+        // packages tells the System loader how to load when no filename and/or no extension
+        packages: {
+            'app': {
+                defaultExtension: 'js',
+                meta: {
+                    './*.js': {
+                        loader: 'systemjs-angular-loader.js'
+                    }
+                }
+            },
+            'systemjs-angular-loader.js': {
+                loader: false
+            },
+            'rxjs': {
+                defaultExtension: 'js'
+            },
+            'moment': {
+                main: './moment.js',
+                defaultExtension: 'js'
+            },
+            'angular2-moment': {
+                main: './index.js',
+                defaultExtension: 'js'
+            }
+        }
+    });
+})(this);

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/webapp/systemjs.config.extras.js
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/webapp/systemjs.config.extras.js b/nifi-registry-web-ui/src/main/webapp/systemjs.config.extras.js
new file mode 100644
index 0000000..e2a256a
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/webapp/systemjs.config.extras.js
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ */
+
+/**
+ * Add barrels and stuff
+ */
+(function (global) {
+    System.config({
+        packages: {
+            // add packages here
+        }
+    });
+})(this);

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/webapp/systemjs.spec.config.js
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/webapp/systemjs.spec.config.js b/nifi-registry-web-ui/src/main/webapp/systemjs.spec.config.js
new file mode 100644
index 0000000..a9d72c6
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/webapp/systemjs.spec.config.js
@@ -0,0 +1,76 @@
+/*
+ * 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.
+ */
+
+(function (global) {
+    System.config({
+        paths: {
+            // paths serve as alias
+            'npm:': 'nifi-registry/node_modules/'
+        },
+        // map tells the System loader where to look for things
+        map: {
+            'text': 'npm:systemjs-plugin-text/text.js',
+            'app': './webapp',
+
+            // jquery
+            'jquery': 'npm:jquery/dist/jquery.min.js',
+
+            // Angular
+            '@angular/core': 'npm:@angular/core/bundles/core.umd.min.js',
+            '@angular/common': 'npm:@angular/common/bundles/common.umd.min.js',
+            '@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.min.js',
+            '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.min.js',
+            '@angular/http': 'npm:@angular/http/bundles/http.umd.min.js',
+            '@angular/router': 'npm:@angular/router/bundles/router.umd.min.js',
+            '@angular/forms': 'npm:@angular/forms/bundles/forms.umd.min.js',
+            '@angular/flex-layout': 'npm:@angular/flex-layout/bundles/flex-layout.umd.js',
+            '@angular/material': 'npm:@angular/material/bundles/material.umd.min.js',
+            '@angular/platform-browser/animations': 'npm:@angular/platform-browser/bundles/platform-browser-animations.umd.min.js',
+            '@angular/cdk': 'npm:@angular/cdk/bundles/cdk.umd.min.js',
+            '@angular/animations': 'npm:@angular/animations/bundles/animations.umd.min.js',
+            '@angular/animations/browser': 'npm:@angular/animations/bundles/animations-browser.umd.min.js',
+            '@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.min.js',
+
+            // Covalent
+            '@covalent/core': 'npm:@covalent/core/core.umd.js',
+
+            // other libraries
+            'rxjs': 'npm:rxjs',
+            'switchMap': 'npm:rxjs/add/operator/switchMap',
+
+            // Fluid Design System
+            '@fluid-design-system/core': 'npm:@fluid-design-system/dist/platform/core/fluid-design-system.module.js',
+        },
+        // packages tells the System loader how to load when no filename and/or no extension
+        packages: {
+            app: {
+                defaultExtension: 'js',
+                meta: {
+                    './*.js': {
+                        loader: 'nifi-registry/systemjs-angular-loader.js'
+                    }
+                }
+            },
+            'nifi-registry/systemjs-angular-loader.js': {
+                loader: false
+            },
+            rxjs: {
+                defaultExtension: 'js'
+            }
+        }
+    });
+})(this);

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/webapp/theming/_helperClasses.scss
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/webapp/theming/_helperClasses.scss b/nifi-registry-web-ui/src/main/webapp/theming/_helperClasses.scss
new file mode 100644
index 0000000..a106f77
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/webapp/theming/_helperClasses.scss
@@ -0,0 +1,63 @@
+/*
+ * 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.
+ */
+
+.fa-rotate-45 {
+    -webkit-transform: rotate(45deg);
+    -moz-transform: rotate(45deg);
+    -ms-transform: rotate(45deg);
+    -o-transform: rotate(45deg);
+    transform: rotate(45deg);
+}
+
+.capitalize {
+    text-transform: capitalize;
+}
+
+.uppercase {
+    text-transform: uppercase;
+}
+
+.info {
+    color: $blue8;
+}
+
+.authorized {
+    color: $red2;
+}
+
+.suspended {
+    color: $green3;
+}
+
+.nf-registry-button-container {
+    position: absolute;
+    bottom: 0px;
+    height: 64px;
+    left: 0px;
+    right: 0px;
+    border-top: 1px solid $grey4;
+}
+
+.nf-registry-button-container .done-button {
+    float: right;
+    margin-right: 15px;
+    margin-top: 15px;
+}
+
+.td-expansion-content {
+    background: $grey6;
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/webapp/theming/_structureElements.scss
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/webapp/theming/_structureElements.scss b/nifi-registry-web-ui/src/main/webapp/theming/_structureElements.scss
new file mode 100644
index 0000000..78c19d1
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/webapp/theming/_structureElements.scss
@@ -0,0 +1,82 @@
+/*
+ * 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.
+ */
+
+body {
+    background: $grey5;
+}
+
+#nf-registry-app-container {
+    margin: 0;
+    width: 100%;
+    height: 100%;
+}
+
+#nifi-registry-logo {
+    height: 44px;
+}
+
+#nifi-registry-alerts-count {
+    border-radius: 50%;
+    background: $warnColor;
+    height: 14px;
+    width: 14px;
+    position: relative;
+    left: 9px;
+    color: #FFFFFF;
+    font-size: 9px;
+    text-align: center;
+    line-height: 14px;
+}
+
+#nifi-registry-toolbar {
+    min-width: 1045px;
+    background-color: #FFFFFF;
+    position: absolute;
+    z-index: 1000;
+    background: $grey1;
+}
+
+#nifi-registry-toolbar .mat-icon-button {
+    color: $grey5;
+    font-size: 20px;
+    margin: 0;
+}
+
+#nifi-registry-toolbar .mat-select-value-text, #nifi-registry-toolbar .mat-select-arrow{
+    color: white;
+}
+
+#nifi-registry-toolbar span, #nifi-registry-toolbar .link{
+    color: $grey5;
+}
+
+#nf-registry-perspectives-container {
+    position: absolute;
+    top: 64px;
+    left: 0px;
+    right: 0px;
+    bottom: 0px;
+    background: $grey5;
+    min-height: 370px;
+    min-width: 1045px;
+    overflow: auto;
+}
+
+md-sidenav {
+    width: 40%;
+    min-width: 418px;
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/webapp/theming/components/administration/_structureElements.scss
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/webapp/theming/components/administration/_structureElements.scss b/nifi-registry-web-ui/src/main/webapp/theming/components/administration/_structureElements.scss
new file mode 100644
index 0000000..aa59100
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/webapp/theming/components/administration/_structureElements.scss
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+
+#nifi-registry-administration-perspective {
+    position: absolute;
+    top: 20px;
+    left: 50px;
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/webapp/theming/components/administration/general/_structureElements.scss
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/webapp/theming/components/administration/general/_structureElements.scss b/nifi-registry-web-ui/src/main/webapp/theming/components/administration/general/_structureElements.scss
new file mode 100644
index 0000000..ab3548d
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/webapp/theming/components/administration/general/_structureElements.scss
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+
+#nifi-registry-general-administration-perspective {
+    position: absolute;
+    top: 79px;
+    left: 50px;
+    right: calc(50% - 5px);
+    bottom: 20px;
+    background: white;
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/webapp/theming/components/administration/users/_structureElements.scss
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/webapp/theming/components/administration/users/_structureElements.scss b/nifi-registry-web-ui/src/main/webapp/theming/components/administration/users/_structureElements.scss
new file mode 100644
index 0000000..650593f
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/webapp/theming/components/administration/users/_structureElements.scss
@@ -0,0 +1,61 @@
+/*
+ * 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.
+ */
+
+#nifi-registry-users-administration-perspective {
+    position: absolute;
+    top: 79px;
+    left: 50px;
+    right: 20px;
+    bottom: 20px;
+    background: white;
+}
+
+#nifi-registry-users-administration-list-container {
+    overflow-y: auto;
+    position: absolute;
+    bottom: 49px;
+    top: 124px;
+    left: 30px;
+    right: 30px;
+    overflow-x: hidden;
+}
+
+#nifi-registry-users-administration-list-paging-bar {
+    position: absolute;
+    bottom: 0;
+    left: 0;
+    right: 0;
+}
+
+#nf-registry-user-permissions-side-nav-container {
+    position: absolute;
+    bottom: 15px;
+    right: 10px;
+}
+
+#nf-registry-add-user-side-nav-container {
+    position: absolute;
+    bottom: 15px;
+    right: 10px;
+}
+
+#nf-registry-user-details-side-nav-container {
+    position: absolute;
+    bottom: 15px;
+    right: 10px;
+}
+

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/webapp/theming/components/administration/workflow/_structureElements.scss
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/webapp/theming/components/administration/workflow/_structureElements.scss b/nifi-registry-web-ui/src/main/webapp/theming/components/administration/workflow/_structureElements.scss
new file mode 100644
index 0000000..0384079
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/webapp/theming/components/administration/workflow/_structureElements.scss
@@ -0,0 +1,61 @@
+/*
+ * 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.
+ */
+
+#nifi-registry-workflow-administration-perspective-buckets-container {
+    position: absolute;
+    top: 79px;
+    left: 50px;
+    right: calc(50% - 5px);
+    bottom: 20px;
+    background: white;
+}
+
+//#nifi-registry-workflow-administration-perspective-certifications-container {
+//    position: absolute;
+//    top: 79px;
+//    left: calc(50% + 25px);
+//    right: 20px;
+//    bottom: 20px;
+//    background: white;
+//}
+
+#nifi-registry-workflow-administration-buckets-list-container {
+    overflow-y: auto;
+    position: absolute;
+    bottom: 30px;
+    top: 171px;
+    left: 30px;
+    right: 30px;
+    overflow-x: hidden;
+}
+
+//#nifi-registry-workflow-administration-certifications-list-container {
+//    overflow-y: auto;
+//    position: absolute;
+//    bottom: 30px;
+//    top: 171px;
+//    left: 30px;
+//    right: 30px;
+//    overflow-x: hidden;
+//}
+
+#nf-registry-workflow-bucket-permissions-side-nav-container {
+    position: absolute;
+    bottom: 15px;
+    right: 10px;
+}
+

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/webapp/theming/components/explorer/grid-list/_structureElements.scss
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/webapp/theming/components/explorer/grid-list/_structureElements.scss b/nifi-registry-web-ui/src/main/webapp/theming/components/explorer/grid-list/_structureElements.scss
new file mode 100644
index 0000000..0038782
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/webapp/theming/components/explorer/grid-list/_structureElements.scss
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ */
+
+#nf-registry-droplet-filter-clear-grouping-button-container {
+    height: 36px;
+    line-height: 36px;
+}
+
+#nf-registry-droplet-filter-clear-grouping-button-container i{
+    color: $red2;
+}
+
+#droplet-sort-by-field {
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    //width: 85px;
+    text-align: end;
+}
+
+#nifi-registry-explorer-grid-list-viewer-droplet-container-details {
+    position: relative;
+}
+
+#nifi-registry-explorer-grid-list-viewer-droplet-container-details-change-log {
+    position: relative;
+    left: 0px;
+    max-height: 230px;
+    overflow: auto;
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/webapp/theming/components/fluid-design-system/_structureElements.scss
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/webapp/theming/components/fluid-design-system/_structureElements.scss b/nifi-registry-web-ui/src/main/webapp/theming/components/fluid-design-system/_structureElements.scss
new file mode 100644
index 0000000..75c5ebb
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/webapp/theming/components/fluid-design-system/_structureElements.scss
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+
+#fds-demo {
+    position: absolute;
+    top: 0px;
+    bottom: 0px;
+    left: 0px;
+    right: 0px;
+    overflow: auto;
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/7fa56bea/nifi-registry-web-ui/src/main/webapp/theming/nf-registry.scss
----------------------------------------------------------------------
diff --git a/nifi-registry-web-ui/src/main/webapp/theming/nf-registry.scss b/nifi-registry-web-ui/src/main/webapp/theming/nf-registry.scss
new file mode 100644
index 0000000..8d9e878
--- /dev/null
+++ b/nifi-registry-web-ui/src/main/webapp/theming/nf-registry.scss
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+
+/* Welcome to Compass.
+ * In this file you should centralize your imports. After compilation simply import this file using the following HTML or equivalent:
+ * <link href='/stylesheets/nifi-flow-registry.css' media='screen, projection' rel='stylesheet' type='text/css' /> */
+
+@import '../../platform/core/common/styles/globalVars';
+@import 'structureElements';
+@import 'helperClasses';
+@import 'components/administration/structureElements';
+@import 'components/administration/general/structureElements';
+@import 'components/administration/users/structureElements';
+@import 'components/administration/workflow/structureElements';
+@import 'components/explorer/grid-list/structureElements';
+@import 'components/fluid-design-system/structureElements';